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> {{ 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> {{ comment.date | ago(locale='de') }}</small> + <p>{{ comment.comment }}</p> + </div> </div> - <div class="comment-content"> - <small><b>{{ comment.name }}</b> {{ 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> {{ 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