From 11fba132d4c6a6ec700ccaa754d0830a9aeb7c15 Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:12:42 +0100
Subject: [PATCH 01/13] add table for comments

---
 migrations/Version20230122200801.php |  33 +++++++++
 src/Entity/Comment.php               | 102 +++++++++++++++++++++++++++
 src/Entity/Page.php                  |  34 +++++++++
 src/Repository/CommentRepository.php |  66 +++++++++++++++++
 4 files changed, 235 insertions(+)
 create mode 100644 migrations/Version20230122200801.php
 create mode 100644 src/Entity/Comment.php
 create mode 100644 src/Repository/CommentRepository.php

diff --git a/migrations/Version20230122200801.php b/migrations/Version20230122200801.php
new file mode 100644
index 00000000..525c943e
--- /dev/null
+++ b/migrations/Version20230122200801.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DoctrineMigrations;
+
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\Migrations\AbstractMigration;
+
+/**
+ * Auto-generated Migration: Please modify to your needs!
+ */
+final class Version20230122200801 extends AbstractMigration
+{
+    public function getDescription(): string
+    {
+        return '';
+    }
+
+    public function up(Schema $schema): void
+    {
+        // this up() migration is auto-generated, please modify it to your needs
+        $this->addSql('CREATE TABLE comment (id INT AUTO_INCREMENT NOT NULL, page_id INT NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, comment LONGTEXT NOT NULL, INDEX IDX_9474526CC4663E4 (page_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
+        $this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CC4663E4 FOREIGN KEY (page_id) REFERENCES page (id)');
+    }
+
+    public function down(Schema $schema): void
+    {
+        // this down() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CC4663E4');
+        $this->addSql('DROP TABLE comment');
+    }
+}
diff --git a/src/Entity/Comment.php b/src/Entity/Comment.php
new file mode 100644
index 00000000..ecba597b
--- /dev/null
+++ b/src/Entity/Comment.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace App\Entity;
+
+use App\Repository\CommentRepository;
+use Doctrine\DBAL\Types\Types;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ORM\Entity(repositoryClass: CommentRepository::class)]
+class Comment
+{
+    #[ORM\Id]
+    #[ORM\GeneratedValue]
+    #[ORM\Column]
+    private ?int $id = null;
+
+    #[ORM\ManyToOne(inversedBy: 'comments')]
+    #[ORM\JoinColumn(nullable: false)]
+    private ?Page $page = null;
+
+    #[ORM\Column(length: 255)]
+    private ?string $name = null;
+
+    #[ORM\Column(length: 255)]
+    private ?string $email = null;
+
+    #[ORM\Column(type: Types::TEXT)]
+    private ?string $comment = null;
+
+    #[ORM\Column(type: Types::DATETIME_MUTABLE)]
+    private \DateTimeInterface $date;
+
+    public function __construct()
+    {
+        $this->date = new \DateTimeImmutable();
+    }
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function getPage(): ?Page
+    {
+        return $this->page;
+    }
+
+    public function setPage(?Page $page): self
+    {
+        $this->page = $page;
+
+        return $this;
+    }
+
+    public function getName(): ?string
+    {
+        return $this->name;
+    }
+
+    public function setName(string $name): self
+    {
+        $this->name = $name;
+
+        return $this;
+    }
+
+    public function getEmail(): ?string
+    {
+        return $this->email;
+    }
+
+    public function setEmail(string $email): self
+    {
+        $this->email = $email;
+
+        return $this;
+    }
+
+    public function getComment(): ?string
+    {
+        return $this->comment;
+    }
+
+    public function setComment(string $comment): self
+    {
+        $this->comment = $comment;
+
+        return $this;
+    }
+
+    public function getDate(): ?\DateTimeInterface
+    {
+        return $this->date;
+    }
+
+    public function setDate(\DateTimeInterface $date): self
+    {
+        $this->date = $date;
+
+        return $this;
+    }
+}
diff --git a/src/Entity/Page.php b/src/Entity/Page.php
index 755e58c7..3a09c8e0 100644
--- a/src/Entity/Page.php
+++ b/src/Entity/Page.php
@@ -31,10 +31,14 @@ class Page
     #[ORM\OneToMany(mappedBy: 'page', targetEntity: Navigation::class)]
     private Collection $navigations;
 
+    #[ORM\OneToMany(mappedBy: 'page', targetEntity: Comment::class)]
+    private Collection $comments;
+
     public function __construct()
     {
         $this->createDate = new \DateTimeImmutable();
         $this->navigations = new ArrayCollection();
+        $this->comments = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -119,4 +123,34 @@ class Page
 
         return $this;
     }
+
+    /**
+     * @return Collection<int, Comment>
+     */
+    public function getComments(): Collection
+    {
+        return $this->comments;
+    }
+
+    public function addComment(Comment $comment): self
+    {
+        if (!$this->comments->contains($comment)) {
+            $this->comments->add($comment);
+            $comment->setPage($this);
+        }
+
+        return $this;
+    }
+
+    public function removeComment(Comment $comment): self
+    {
+        if ($this->comments->removeElement($comment)) {
+            // set the owning side to null (unless already changed)
+            if ($comment->getPage() === $this) {
+                $comment->setPage(null);
+            }
+        }
+
+        return $this;
+    }
 }
diff --git a/src/Repository/CommentRepository.php b/src/Repository/CommentRepository.php
new file mode 100644
index 00000000..2635be1b
--- /dev/null
+++ b/src/Repository/CommentRepository.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace App\Repository;
+
+use App\Entity\Comment;
+use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\Persistence\ManagerRegistry;
+
+/**
+ * @extends ServiceEntityRepository<Comment>
+ *
+ * @method Comment|null find($id, $lockMode = null, $lockVersion = null)
+ * @method Comment|null findOneBy(array $criteria, array $orderBy = null)
+ * @method Comment[]    findAll()
+ * @method Comment[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
+ */
+class CommentRepository extends ServiceEntityRepository
+{
+    public function __construct(ManagerRegistry $registry)
+    {
+        parent::__construct($registry, Comment::class);
+    }
+
+    public function save(Comment $entity, bool $flush = false): void
+    {
+        $this->getEntityManager()->persist($entity);
+
+        if ($flush) {
+            $this->getEntityManager()->flush();
+        }
+    }
+
+    public function remove(Comment $entity, bool $flush = false): void
+    {
+        $this->getEntityManager()->remove($entity);
+
+        if ($flush) {
+            $this->getEntityManager()->flush();
+        }
+    }
+
+//    /**
+//     * @return Comment[] Returns an array of Comment objects
+//     */
+//    public function findByExampleField($value): array
+//    {
+//        return $this->createQueryBuilder('c')
+//            ->andWhere('c.exampleField = :val')
+//            ->setParameter('val', $value)
+//            ->orderBy('c.id', 'ASC')
+//            ->setMaxResults(10)
+//            ->getQuery()
+//            ->getResult()
+//        ;
+//    }
+
+//    public function findOneBySomeField($value): ?Comment
+//    {
+//        return $this->createQueryBuilder('c')
+//            ->andWhere('c.exampleField = :val')
+//            ->setParameter('val', $value)
+//            ->getQuery()
+//            ->getOneOrNullResult()
+//        ;
+//    }
+}
-- 
GitLab


From f77cdeaabb3a66e660c1a4ba8baba0030b98a7f3 Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:18:14 +0100
Subject: [PATCH 02/13] add service to generate gravatar url

---
 src/Service/GravatarService.php | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
 create mode 100644 src/Service/GravatarService.php

diff --git a/src/Service/GravatarService.php b/src/Service/GravatarService.php
new file mode 100644
index 00000000..caebf8c0
--- /dev/null
+++ b/src/Service/GravatarService.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Service;
+
+class GravatarService
+{
+    public function getGravatar(string $email, int $size, string $defaultImageset, string $maximumRating): string
+    {
+        $url = 'https://www.gravatar.com/avatar/';
+        $url .= md5(strtolower(trim($email)));
+        $url .= "?s=$size&d=$defaultImageset&r=$maximumRating";
+        return $url;
+    }
+}
-- 
GitLab


From c2f495f57bffe2152dece22bb968c5b29e44141b Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:31:42 +0100
Subject: [PATCH 03/13] add migration for date column in comments

---
 migrations/Version20230122201922.php | 31 ++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 migrations/Version20230122201922.php

diff --git a/migrations/Version20230122201922.php b/migrations/Version20230122201922.php
new file mode 100644
index 00000000..e6dea040
--- /dev/null
+++ b/migrations/Version20230122201922.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DoctrineMigrations;
+
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\Migrations\AbstractMigration;
+
+/**
+ * Auto-generated Migration: Please modify to your needs!
+ */
+final class Version20230122201922 extends AbstractMigration
+{
+    public function getDescription(): string
+    {
+        return '';
+    }
+
+    public function up(Schema $schema): void
+    {
+        // this up() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE comment ADD date DATETIME NOT NULL');
+    }
+
+    public function down(Schema $schema): void
+    {
+        // this down() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE comment DROP date');
+    }
+}
-- 
GitLab


From f67354e35aba23259ca3cb9cacd584d382a74c7a Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:31:53 +0100
Subject: [PATCH 04/13] add get comments by page function

---
 src/Repository/CommentRepository.php | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/Repository/CommentRepository.php b/src/Repository/CommentRepository.php
index 2635be1b..85d0d58c 100644
--- a/src/Repository/CommentRepository.php
+++ b/src/Repository/CommentRepository.php
@@ -3,6 +3,7 @@
 namespace App\Repository;
 
 use App\Entity\Comment;
+use App\Entity\Page;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Doctrine\Persistence\ManagerRegistry;
 
@@ -39,6 +40,18 @@ class CommentRepository extends ServiceEntityRepository
         }
     }
 
+    /**
+     * @return array<Comment>
+     */
+    public function getCommentsByPage(Page $page): array
+    {
+        return $this->createQueryBuilder('c')
+            ->where('c.page = :page')
+            ->setParameter('page', $page)
+            ->getQuery()
+            ->getArrayResult();
+    }
+
 //    /**
 //     * @return Comment[] Returns an array of Comment objects
 //     */
-- 
GitLab


From 7c1a06e52486c0c13530aca7340336c854bdd091 Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:39:09 +0100
Subject: [PATCH 05/13] set default values

---
 src/Service/GravatarService.php | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/Service/GravatarService.php b/src/Service/GravatarService.php
index caebf8c0..81c5a5df 100644
--- a/src/Service/GravatarService.php
+++ b/src/Service/GravatarService.php
@@ -4,8 +4,12 @@ namespace App\Service;
 
 class GravatarService
 {
-    public function getGravatar(string $email, int $size, string $defaultImageset, string $maximumRating): string
-    {
+    public function getGravatar(
+        string $email,
+        int $size = 80,
+        string $defaultImageset = 'mp',
+        string $maximumRating = 'g'
+    ): string {
         $url = 'https://www.gravatar.com/avatar/';
         $url .= md5(strtolower(trim($email)));
         $url .= "?s=$size&d=$defaultImageset&r=$maximumRating";
-- 
GitLab


From 2c0f774ec632b913db3984c431feb83834ec51cb Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:42:05 +0100
Subject: [PATCH 06/13] add gravatar function

---
 src/Entity/Comment.php | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/Entity/Comment.php b/src/Entity/Comment.php
index ecba597b..88125a7c 100644
--- a/src/Entity/Comment.php
+++ b/src/Entity/Comment.php
@@ -3,6 +3,7 @@
 namespace App\Entity;
 
 use App\Repository\CommentRepository;
+use App\Service\GravatarService;
 use Doctrine\DBAL\Types\Types;
 use Doctrine\ORM\Mapping as ORM;
 
@@ -99,4 +100,10 @@ class Comment
 
         return $this;
     }
+
+    public function getGravatar(): string
+    {
+        $gravatarService = new GravatarService();
+        return $gravatarService->getGravatar($this->email);
+    }
 }
-- 
GitLab


From 4fd20fac1b1ec5fbacffb0562069580d169e3cc8 Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:56:03 +0100
Subject: [PATCH 07/13] add knp time bundle

---
 composer.json      |   1 +
 composer.lock      | 143 ++++++++++++++++++++++++++++++++++++++++++++-
 config/bundles.php |   1 +
 symfony.lock       |   3 +
 4 files changed, 147 insertions(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index 1140c7e3..8175bb4f 100644
--- a/composer.json
+++ b/composer.json
@@ -13,6 +13,7 @@
         "doctrine/orm": "^2.14",
         "easycorp/easyadmin-bundle": "^4.5",
         "erusev/parsedown": "^1.7",
+        "knplabs/knp-time-bundle": "^1.20",
         "symfony/console": "6.2.*",
         "symfony/dotenv": "6.2.*",
         "symfony/flex": "^2",
diff --git a/composer.lock b/composer.lock
index 59892c57..2519983f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "45707a5bd926f5a90df86c4b5f7950e4",
+    "content-hash": "7e75dc360cff11372bd5ea21b557e7dc",
     "packages": [
         {
             "name": "aws/aws-crt-php",
@@ -2014,6 +2014,79 @@
             ],
             "time": "2022-10-26T14:07:24+00:00"
         },
+        {
+            "name": "knplabs/knp-time-bundle",
+            "version": "v1.20.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/KnpLabs/KnpTimeBundle.git",
+                "reference": "81ffad54d91f62528466267fd4a2fb8a5a0335f4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/KnpLabs/KnpTimeBundle/zipball/81ffad54d91f62528466267fd4a2fb8a5a0335f4",
+                "reference": "81ffad54d91f62528466267fd4a2fb8a5a0335f4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/config": "^5.4|^6.0",
+                "symfony/dependency-injection": "^5.4|^6.0",
+                "symfony/templating": "^5.4|^6.0",
+                "symfony/translation": "^5.4|^6.0"
+            },
+            "conflict": {
+                "phpunit/phpunit": "<8.0"
+            },
+            "require-dev": {
+                "symfony/framework-bundle": "^5.4|^6.0",
+                "symfony/phpunit-bridge": "^5.4|^6.0",
+                "symfony/twig-bundle": "^5.4|^6.0"
+            },
+            "suggest": {
+                "symfony/twig-bundle": "to use the Twig `time_diff()` function or `|ago` filter"
+            },
+            "type": "symfony-bundle",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Knp\\Bundle\\TimeBundle\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "KnpLabs Team",
+                    "homepage": "http://knplabs.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://github.com/KnpLabs/KnpTimeBundle/contributors"
+                }
+            ],
+            "description": "Making your dates look sensible and descriptive",
+            "homepage": "http://github.com/KnpLabs/KnpTimeBundle",
+            "keywords": [
+                "bundle",
+                "date",
+                "descriptive time",
+                "knp",
+                "knplabs",
+                "time"
+            ],
+            "support": {
+                "issues": "https://github.com/KnpLabs/KnpTimeBundle/issues",
+                "source": "https://github.com/KnpLabs/KnpTimeBundle/tree/v1.20.0"
+            },
+            "time": "2022-10-11T15:32:58+00:00"
+        },
         {
             "name": "laminas/laminas-code",
             "version": "4.8.0",
@@ -6140,6 +6213,74 @@
             ],
             "time": "2022-12-14T16:11:27+00:00"
         },
+        {
+            "name": "symfony/templating",
+            "version": "v6.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/templating.git",
+                "reference": "595f786b4d2bb811949ead7831f50bdcab1d4e31"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/templating/zipball/595f786b4d2bb811949ead7831f50bdcab1d4e31",
+                "reference": "595f786b4d2bb811949ead7831f50bdcab1d4e31",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2|^3"
+            },
+            "suggest": {
+                "psr/log-implementation": "For using debug logging in loaders"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Templating\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides all the tools needed to build any kind of template system",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/templating/tree/v6.2.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-08-25T15:27:04+00:00"
+        },
         {
             "name": "symfony/translation",
             "version": "v6.2.3",
diff --git a/config/bundles.php b/config/bundles.php
index e3fdb8b2..c04e1849 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -14,4 +14,5 @@ return [
     Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
     Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
     EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
+    Knp\Bundle\TimeBundle\KnpTimeBundle::class => ['all' => true],
 ];
diff --git a/symfony.lock b/symfony.lock
index 58c6d5aa..5bbdd4f5 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -35,6 +35,9 @@
             "ref": "b131e6cbfe1b898a508987851963fff485986285"
         }
     },
+    "knplabs/knp-time-bundle": {
+        "version": "v1.20.0"
+    },
     "squizlabs/php_codesniffer": {
         "version": "3.7",
         "recipe": {
-- 
GitLab


From 50dd09b86e9b4ea3d0015c60245e6d22bb3f0a0e Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:56:14 +0100
Subject: [PATCH 08/13] display comments

---
 assets/styles/lib/_comments.scss | 90 +++++---------------------------
 templates/pages/page.html.twig   | 13 +++++
 2 files changed, 25 insertions(+), 78 deletions(-)

diff --git a/assets/styles/lib/_comments.scss b/assets/styles/lib/_comments.scss
index 7b079638..a0419ece 100644
--- a/assets/styles/lib/_comments.scss
+++ b/assets/styles/lib/_comments.scss
@@ -1,81 +1,15 @@
-
-#newComment {
-  form {
-    input, textarea{
-      width: 100%;
-      background-color: $back-color;
-      color: $text-color;
-      border: solid $back-color-2;
-      border-radius: 5px;
-      padding: 5px;
-    }
-
-    input:focus, textarea:focus {
-      outline: none;
-    }
-
-    textarea{
-      resize: vertical;
-    }
-
-    input[type=submit]{
-      background-color: $accent-color;
-      color: #000;
-      text-transform: uppercase;
-      padding: 15px;
-      font-size: 14px;
-      cursor: pointer;
-      outline: 0;
-      border: 0;
-      transition: background-color $link-hover-animation-time linear;
-
-      &:HOVER{
-        background-color: $accent-color-2;
-      }
+.comment {
+  display: flex;
+  justify-content: left;
+  .comment-image {
+    width: fit-content;
+    img {
+      width: 60px;
+      height: 60px;
+      max-width: unset !important;
     }
   }
-}
-
-.comment{
-  display: flex;
-  img{
-    margin-right: 10px;
-    width: 100px;
-    height: 100px;
+  .comment-content {
+    margin-left: 10px;
   }
-}
-
-.commentTitle{
-  margin-bottom: 5px;
-}
-
-.commentArticle{
-  display: flex;
-  justify-content: center;
-  flex-direction: column;
-  align-items: center;
-  min-height: 100px;
-}
-
-.commentText{
-  margin: 0;
-  width: 100%;
-}
-
-.emailBox {
-  display: inline;
-}
-
-.bigButton {
-  background-color: $accent-color;
-  color: #000;
-  text-transform: uppercase;
-  padding: 15px;
-  font-size: 14px;
-  cursor: pointer;
-  outline: 0;
-  border: 0;
-  transition: background-color $link-hover-animation-time linear;
-  width: 100%;
-  margin-top: 10px;
-}
+}
\ No newline at end of file
diff --git a/templates/pages/page.html.twig b/templates/pages/page.html.twig
index e6c345e6..bfdabde7 100644
--- a/templates/pages/page.html.twig
+++ b/templates/pages/page.html.twig
@@ -4,4 +4,17 @@
     {% apply markdown_to_html %}
         {{ page.content | raw }}
     {% endapply %}
+    <div class="comments">
+        {% for comment in page.comments %}
+            <div class="comment">
+                <div class="comment-image">
+                    <img src="{{ comment.gravatar }}">
+                </div>
+                <div class="comment-content">
+                    <small><b>{{ comment.name }}</b>&nbsp;{{ comment.date | ago(locale='de') }}</small>
+                    <p>{{ comment.comment }}</p>
+                </div>
+            </div>
+        {% endfor %}
+    </div>
 {% endblock %}
\ No newline at end of file
-- 
GitLab


From 1124bf5ec472a4bffd1e8a19ff27f7790ceab717 Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Sun, 22 Jan 2023 21:59:00 +0100
Subject: [PATCH 09/13] add option to disable comments

---
 migrations/Version20230122205655.php | 31 ++++++++++++++++++++++++++++
 src/Entity/Page.php                  | 15 ++++++++++++++
 templates/pages/page.html.twig       | 26 ++++++++++++-----------
 3 files changed, 60 insertions(+), 12 deletions(-)
 create mode 100644 migrations/Version20230122205655.php

diff --git a/migrations/Version20230122205655.php b/migrations/Version20230122205655.php
new file mode 100644
index 00000000..26d91cad
--- /dev/null
+++ b/migrations/Version20230122205655.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace DoctrineMigrations;
+
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\Migrations\AbstractMigration;
+
+/**
+ * Auto-generated Migration: Please modify to your needs!
+ */
+final class Version20230122205655 extends AbstractMigration
+{
+    public function getDescription(): string
+    {
+        return '';
+    }
+
+    public function up(Schema $schema): void
+    {
+        // this up() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE page ADD comments_enabled TINYINT(1) NOT NULL');
+    }
+
+    public function down(Schema $schema): void
+    {
+        // this down() migration is auto-generated, please modify it to your needs
+        $this->addSql('ALTER TABLE page DROP comments_enabled');
+    }
+}
diff --git a/src/Entity/Page.php b/src/Entity/Page.php
index 3a09c8e0..d5a88014 100644
--- a/src/Entity/Page.php
+++ b/src/Entity/Page.php
@@ -34,6 +34,9 @@ class Page
     #[ORM\OneToMany(mappedBy: 'page', targetEntity: Comment::class)]
     private Collection $comments;
 
+    #[ORM\Column]
+    private bool $commentsEnabled = true;
+
     public function __construct()
     {
         $this->createDate = new \DateTimeImmutable();
@@ -153,4 +156,16 @@ class Page
 
         return $this;
     }
+
+    public function isCommentsEnabled(): ?bool
+    {
+        return $this->commentsEnabled;
+    }
+
+    public function setCommentsEnabled(bool $commentsEnabled): self
+    {
+        $this->commentsEnabled = $commentsEnabled;
+
+        return $this;
+    }
 }
diff --git a/templates/pages/page.html.twig b/templates/pages/page.html.twig
index bfdabde7..32f2a14b 100644
--- a/templates/pages/page.html.twig
+++ b/templates/pages/page.html.twig
@@ -4,17 +4,19 @@
     {% apply markdown_to_html %}
         {{ page.content | raw }}
     {% endapply %}
-    <div class="comments">
-        {% for comment in page.comments %}
-            <div class="comment">
-                <div class="comment-image">
-                    <img src="{{ comment.gravatar }}">
+    {% if page.commentsEnabled %}
+        <div class="comments">
+            {% for comment in page.comments %}
+                <div class="comment">
+                    <div class="comment-image">
+                        <img src="{{ comment.gravatar }}">
+                    </div>
+                    <div class="comment-content">
+                        <small><b>{{ comment.name }}</b>&nbsp;{{ comment.date | ago(locale='de') }}</small>
+                        <p>{{ comment.comment }}</p>
+                    </div>
                 </div>
-                <div class="comment-content">
-                    <small><b>{{ comment.name }}</b>&nbsp;{{ comment.date | ago(locale='de') }}</small>
-                    <p>{{ comment.comment }}</p>
-                </div>
-            </div>
-        {% endfor %}
-    </div>
+            {% endfor %}
+        </div>
+    {% endif %}
 {% endblock %}
\ No newline at end of file
-- 
GitLab


From eacc30715b375d87ff4e8a6904c0a86ea75d66ce Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Mon, 23 Jan 2023 09:40:44 +0100
Subject: [PATCH 10/13] add form to publish comments

---
 assets/styles/lib/_comments.scss  | 54 +++++++++++++++++++++++++++++++
 src/Controller/PageController.php | 25 ++++++++++++++
 templates/pages/page.html.twig    | 26 ++++++++++++++-
 3 files changed, 104 insertions(+), 1 deletion(-)

diff --git a/assets/styles/lib/_comments.scss b/assets/styles/lib/_comments.scss
index a0419ece..0b88bf6e 100644
--- a/assets/styles/lib/_comments.scss
+++ b/assets/styles/lib/_comments.scss
@@ -12,4 +12,58 @@
   .comment-content {
     margin-left: 10px;
   }
+}
+
+.comment-new {
+  form {
+    input, textarea{
+      width: 100%;
+      box-sizing: border-box;
+      background-color: $back-color;
+      color: $text-color;
+      border: solid $back-color-2;
+      border-radius: 5px;
+      padding: 5px;
+    }
+
+    input:focus, textarea:focus {
+      outline: none;
+    }
+
+    textarea{
+      resize: vertical;
+    }
+
+    input[type=submit]{
+      background-color: $accent-color;
+      color: #000;
+      text-transform: uppercase;
+      padding: 15px;
+      font-size: 14px;
+      cursor: pointer;
+      outline: 0;
+      border: 0;
+      transition: background-color $link-hover-animation-time linear;
+      margin-top: 10px ;
+
+      &:HOVER{
+        background-color: $accent-color-2;
+      }
+    }
+  }
+  margin-bottom: 20px;
+}
+
+.comment-alert {
+  width: 100%;
+  padding: 20px;
+  box-sizing: border-box;
+  border: solid 2px;
+  border-radius: 5px;
+
+  &.green {
+    color: #4dd0af;
+    background-color: #005e46;
+    border-color: #007154;
+  }
 }
\ No newline at end of file
diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php
index 52065207..9d6e41b6 100644
--- a/src/Controller/PageController.php
+++ b/src/Controller/PageController.php
@@ -2,6 +2,8 @@
 
 namespace App\Controller;
 
+use App\Entity\Comment;
+use App\Repository\CommentRepository;
 use App\Repository\PageRepository;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
@@ -21,4 +23,27 @@ class PageController extends AbstractController
             'page' => $page
         ]);
     }
+
+    #[Route('/{all}', requirements: ['all' => '.*'], methods: ['POST'], priority: -1)]
+    public function newComment(
+        Request $request,
+        PageRepository $pageRepository,
+        CommentRepository $commentRepository
+    ): Response {
+        $page = $pageRepository->getPageFromURL($request->getPathInfo());
+        if (!$page) {
+            throw $this->createNotFoundException();
+        }
+
+        $comment = new Comment();
+        $comment->setName((string)$request->request->get('name'));
+        $comment->setEmail((string)$request->request->get('email'));
+        $comment->setComment((string)$request->request->get('comment'));
+        $comment->setPage($page);
+        $commentRepository->save($comment, true);
+        return $this->render('pages/page.html.twig', [
+            'page' => $page,
+            'commentPublished' => true
+        ]);
+    }
 }
diff --git a/templates/pages/page.html.twig b/templates/pages/page.html.twig
index 32f2a14b..6a4501b2 100644
--- a/templates/pages/page.html.twig
+++ b/templates/pages/page.html.twig
@@ -6,10 +6,34 @@
     {% endapply %}
     {% if page.commentsEnabled %}
         <div class="comments">
+            {% if commentPublished %}
+                <div class="comment-alert green">
+                    Das Kommentar wurde erfolgreich veröffentlicht.
+                </div>
+            {% endif %}
+            <div class="comment-new">
+                <form method="post">
+                    <div>
+                        <label for="name">Name: </label>
+                        <input type="text" name="name" id="name" maxlength="255">
+                    </div>
+                    <div>
+                        <label for="email">E-Mail: </label>
+                        <input type="email" name="email" id="email" maxlength="255">
+                    </div>
+                    <div>
+                        <label for="comment">Kommentar:</label><br>
+                        <textarea id="comment" name="comment"></textarea>
+                    </div>
+                    <div>
+                        <input type="submit" value="Veröffentlichen">
+                    </div>
+                </form>
+            </div>
             {% for comment in page.comments %}
                 <div class="comment">
                     <div class="comment-image">
-                        <img src="{{ comment.gravatar }}">
+                        <img alt="gravatar icon" src="{{ comment.gravatar }}">
                     </div>
                     <div class="comment-content">
                         <small><b>{{ comment.name }}</b>&nbsp;{{ comment.date | ago(locale='de') }}</small>
-- 
GitLab


From 8efde46e97a9516f8807ce267acb6642c342851b Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Mon, 23 Jan 2023 10:15:21 +0100
Subject: [PATCH 11/13] add local generated captcha to comments

---
 assets/styles/lib/_comments.scss  |  6 ++++
 composer.json                     |  2 ++
 composer.lock                     | 59 ++++++++++++++++++++++++++++++-
 src/Controller/PageController.php | 36 ++++++++++++++++---
 src/Service/CaptchaService.php    | 29 +++++++++++++++
 templates/pages/page.html.twig    | 17 +++++----
 6 files changed, 137 insertions(+), 12 deletions(-)
 create mode 100644 src/Service/CaptchaService.php

diff --git a/assets/styles/lib/_comments.scss b/assets/styles/lib/_comments.scss
index 0b88bf6e..c3727656 100644
--- a/assets/styles/lib/_comments.scss
+++ b/assets/styles/lib/_comments.scss
@@ -66,4 +66,10 @@
     background-color: #005e46;
     border-color: #007154;
   }
+
+  &.red {
+    color: #ee8277;
+    background-color: #74261e;
+    border-color: #8b2e24;
+  }
 }
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 8175bb4f..11783c19 100644
--- a/composer.json
+++ b/composer.json
@@ -13,6 +13,7 @@
         "doctrine/orm": "^2.14",
         "easycorp/easyadmin-bundle": "^4.5",
         "erusev/parsedown": "^1.7",
+        "gregwar/captcha": "^1.1",
         "knplabs/knp-time-bundle": "^1.20",
         "symfony/console": "6.2.*",
         "symfony/dotenv": "6.2.*",
@@ -22,6 +23,7 @@
         "symfony/maker-bundle": "^1.48",
         "symfony/monolog-bundle": "^3.0",
         "symfony/runtime": "6.2.*",
+        "symfony/security-csrf": "6.2.*",
         "symfony/twig-bundle": "6.2.*",
         "symfony/ux-twig-component": "^2.6",
         "symfony/validator": "6.2.*",
diff --git a/composer.lock b/composer.lock
index 2519983f..f903c319 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "7e75dc360cff11372bd5ea21b557e7dc",
+    "content-hash": "d2c496e84c14df48b51601d285adc727",
     "packages": [
         {
             "name": "aws/aws-crt-php",
@@ -1683,6 +1683,63 @@
             ],
             "time": "2022-10-17T19:48:16+00:00"
         },
+        {
+            "name": "gregwar/captcha",
+            "version": "v1.1.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Gregwar/Captcha.git",
+                "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5",
+                "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gd": "*",
+                "ext-mbstring": "*",
+                "php": ">=5.3.0",
+                "symfony/finder": "*"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.4"
+            },
+            "type": "captcha",
+            "autoload": {
+                "psr-4": {
+                    "Gregwar\\": "src/Gregwar"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Grégoire Passault",
+                    "email": "g.passault@gmail.com",
+                    "homepage": "http://www.gregwar.com/"
+                },
+                {
+                    "name": "Jeremy Livingston",
+                    "email": "jeremy.j.livingston@gmail.com"
+                }
+            ],
+            "description": "Captcha generator",
+            "homepage": "https://github.com/Gregwar/Captcha",
+            "keywords": [
+                "bot",
+                "captcha",
+                "spam"
+            ],
+            "support": {
+                "issues": "https://github.com/Gregwar/Captcha/issues",
+                "source": "https://github.com/Gregwar/Captcha/tree/master"
+            },
+            "time": "2020-03-24T14:39:05+00:00"
+        },
         {
             "name": "guzzlehttp/guzzle",
             "version": "7.5.0",
diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php
index 9d6e41b6..cf8bda7d 100644
--- a/src/Controller/PageController.php
+++ b/src/Controller/PageController.php
@@ -5,6 +5,7 @@ namespace App\Controller;
 use App\Entity\Comment;
 use App\Repository\CommentRepository;
 use App\Repository\PageRepository;
+use App\Service\CaptchaService;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
@@ -13,14 +14,21 @@ use Symfony\Component\Routing\Annotation\Route;
 class PageController extends AbstractController
 {
     #[Route('/{all}', requirements: ['all' => '.*'], methods: ['GET'], priority: -1)]
-    public function renderPage(Request $request, PageRepository $pageRepository): Response
-    {
+    public function renderPage(
+        Request $request,
+        PageRepository $pageRepository,
+        CaptchaService $captchaService
+    ): Response {
         $page = $pageRepository->getPageFromURL($request->getPathInfo());
         if (!$page) {
             throw $this->createNotFoundException();
         }
         return $this->render('pages/page.html.twig', [
-            'page' => $page
+            'page' => $page,
+            'captcha' => $captchaService->getInlineCaptcha(),
+            'name' => '',
+            'email' => '',
+            'comment' => ''
         ]);
     }
 
@@ -28,13 +36,26 @@ class PageController extends AbstractController
     public function newComment(
         Request $request,
         PageRepository $pageRepository,
-        CommentRepository $commentRepository
+        CommentRepository $commentRepository,
+        CaptchaService $captchaService
     ): Response {
         $page = $pageRepository->getPageFromURL($request->getPathInfo());
         if (!$page) {
             throw $this->createNotFoundException();
         }
 
+        if (!$captchaService->validate((string)$request->request->get('captcha'))) {
+            return $this->render('pages/page.html.twig', [
+                'page' => $page,
+                'message' => 'Fehler beim verifizieren des Captchas.',
+                'messageColor' => 'red',
+                'captcha' => $captchaService->getInlineCaptcha(),
+                'name' => $request->request->get('name'),
+                'email' => $request->request->get('email'),
+                'comment' => $request->request->get('comment')
+            ]);
+        }
+
         $comment = new Comment();
         $comment->setName((string)$request->request->get('name'));
         $comment->setEmail((string)$request->request->get('email'));
@@ -43,7 +64,12 @@ class PageController extends AbstractController
         $commentRepository->save($comment, true);
         return $this->render('pages/page.html.twig', [
             'page' => $page,
-            'commentPublished' => true
+            'message' => 'Das Kommentar wurde erfolgreich veröffentlicht.',
+            'messageColor' => 'green',
+            'captcha' => $captchaService->getInlineCaptcha(),
+            'name' => '',
+            'email' => '',
+            'comment' => ''
         ]);
     }
 }
diff --git a/src/Service/CaptchaService.php b/src/Service/CaptchaService.php
new file mode 100644
index 00000000..5761fe85
--- /dev/null
+++ b/src/Service/CaptchaService.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Service;
+
+use Gregwar\Captcha\CaptchaBuilder;
+use Gregwar\Captcha\PhraseBuilder;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+class CaptchaService
+{
+    private CaptchaBuilder $captchaBuilder;
+
+    public function __construct(
+        private readonly RequestStack $requestStack
+    ) {
+        $this->captchaBuilder = new CaptchaBuilder();
+    }
+
+    public function getInlineCaptcha(): string
+    {
+        $this->requestStack->getSession()->set('captcha', $this->captchaBuilder->getPhrase());
+        return $this->captchaBuilder->build()->inline();
+    }
+
+    public function validate(string $userInput): bool
+    {
+        return PhraseBuilder::comparePhrases($userInput, $this->requestStack->getSession()->get('captcha'));
+    }
+}
diff --git a/templates/pages/page.html.twig b/templates/pages/page.html.twig
index 6a4501b2..376c3826 100644
--- a/templates/pages/page.html.twig
+++ b/templates/pages/page.html.twig
@@ -6,24 +6,29 @@
     {% endapply %}
     {% if page.commentsEnabled %}
         <div class="comments">
-            {% if commentPublished %}
-                <div class="comment-alert green">
-                    Das Kommentar wurde erfolgreich veröffentlicht.
+            {% if message is defined %}
+                <div class="comment-alert {{ messageColor }}">
+                    {{ message }}
                 </div>
             {% endif %}
             <div class="comment-new">
                 <form method="post">
                     <div>
                         <label for="name">Name: </label>
-                        <input type="text" name="name" id="name" maxlength="255">
+                        <input type="text" name="name" id="name" maxlength="255" value="{{ name }}">
                     </div>
                     <div>
                         <label for="email">E-Mail: </label>
-                        <input type="email" name="email" id="email" maxlength="255">
+                        <input type="email" name="email" id="email" maxlength="255" value="{{ email }}">
                     </div>
                     <div>
                         <label for="comment">Kommentar:</label><br>
-                        <textarea id="comment" name="comment"></textarea>
+                        <textarea id="comment" name="comment">{{ comment }}</textarea>
+                    </div>
+                    <div>
+                        <label for="captcha">Captcha: </label>
+                        <input type="text" name="captcha" id="captcha"><br>
+                        <img src="{{ captcha }}" alt="captcha">
                     </div>
                     <div>
                         <input type="submit" value="Veröffentlichen">
-- 
GitLab


From 09bc855f23b70b54f2c74dedbaef956754676c86 Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Mon, 23 Jan 2023 10:25:30 +0100
Subject: [PATCH 12/13] add csrf token in form

---
 src/Controller/PageController.php | 12 ++++++++++++
 templates/pages/page.html.twig    |  9 +++++----
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/Controller/PageController.php b/src/Controller/PageController.php
index cf8bda7d..7c681a3e 100644
--- a/src/Controller/PageController.php
+++ b/src/Controller/PageController.php
@@ -56,6 +56,18 @@ class PageController extends AbstractController
             ]);
         }
 
+        if (!$this->isCsrfTokenValid((string)$page->getId(), (string)$request->request->get('csrf'))) {
+            return $this->render('pages/page.html.twig', [
+                'page' => $page,
+                'message' => 'Fehler beim verifizieren des CSRF Tokens, bitte versuchen sie es erneut.',
+                'messageColor' => 'red',
+                'captcha' => $captchaService->getInlineCaptcha(),
+                'name' => $request->request->get('name'),
+                'email' => $request->request->get('email'),
+                'comment' => $request->request->get('comment')
+            ]);
+        }
+
         $comment = new Comment();
         $comment->setName((string)$request->request->get('name'));
         $comment->setEmail((string)$request->request->get('email'));
diff --git a/templates/pages/page.html.twig b/templates/pages/page.html.twig
index 376c3826..9f65ba9f 100644
--- a/templates/pages/page.html.twig
+++ b/templates/pages/page.html.twig
@@ -15,21 +15,22 @@
                 <form method="post">
                     <div>
                         <label for="name">Name: </label>
-                        <input type="text" name="name" id="name" maxlength="255" value="{{ name }}">
+                        <input type="text" name="name" id="name" maxlength="255" value="{{ name }}" required>
                     </div>
                     <div>
                         <label for="email">E-Mail: </label>
-                        <input type="email" name="email" id="email" maxlength="255" value="{{ email }}">
+                        <input type="email" name="email" id="email" maxlength="255" value="{{ email }}" required>
                     </div>
                     <div>
                         <label for="comment">Kommentar:</label><br>
-                        <textarea id="comment" name="comment">{{ comment }}</textarea>
+                        <textarea id="comment" name="comment" required>{{ comment }}</textarea>
                     </div>
                     <div>
                         <label for="captcha">Captcha: </label>
-                        <input type="text" name="captcha" id="captcha"><br>
+                        <input type="text" name="captcha" id="captcha" required><br>
                         <img src="{{ captcha }}" alt="captcha">
                     </div>
+                    <input type="hidden" name="csrf" value="{{csrf_token(page.id)}}">
                     <div>
                         <input type="submit" value="Veröffentlichen">
                     </div>
-- 
GitLab


From b917b995a55b7b773ce52e2edd3bbbbd7b34a39d Mon Sep 17 00:00:00 2001
From: Jonas Leder <jonas@jonasled.de>
Date: Mon, 23 Jan 2023 10:36:53 +0100
Subject: [PATCH 13/13] install missing php extensions

---
 .gitlab-ci.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e4a7a587..62d7ee6c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,8 +3,9 @@ stages:
 
 PHPstan:
   stage: test
-  image: composer:2
+  image: alpine
   before_script:
+    - apk add php-cli composer php-gd php-ctype php-xml php-simplexml php-xmlwriter php-tokenizer php-session php-dom
     - composer install
   script:
     - vendor/bin/phpstan analyse
@@ -13,6 +14,7 @@ PHPCS:
   stage: test
   image: composer:2
   before_script:
+    - apk add php-cli composer php-gd php-ctype php-xml php-simplexml php-xmlwriter php-tokenizer php-session php-dom
     - composer install
   script:
     - vendor/bin/phpcs
\ No newline at end of file
-- 
GitLab