From 7c4a2b46c09a4fab7ff86f7a220b1cf046a48f0c Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 25 Apr 2025 22:17:27 +0200 Subject: [PATCH] Implement snip hiding --- composer.json | 5 + composer.lock | 380 ++++++++++++++++++++++++++- migrations/Version20250425182334.php | 38 +++ src/Controller/SnipController.php | 13 +- src/Dto/SnipFilterRequest.php | 17 ++ src/Entity/Snip.php | 15 ++ src/Form/SnipType.php | 3 +- src/Form/SwitchType.php | 22 ++ src/Repository/SnipRepository.php | 26 +- templates/snip/badge.html.twig | 10 + templates/snip/edit.html.twig | 6 +- templates/snip/index.html.twig | 14 +- templates/snip/public.html.twig | 16 ++ 13 files changed, 541 insertions(+), 24 deletions(-) create mode 100644 migrations/Version20250425182334.php create mode 100644 src/Dto/SnipFilterRequest.php create mode 100644 src/Form/SwitchType.php create mode 100644 templates/snip/public.html.twig diff --git a/composer.json b/composer.json index a8ddc5d..a95702b 100644 --- a/composer.json +++ b/composer.json @@ -12,14 +12,19 @@ "doctrine/orm": "^2.14", "league/commonmark": "^2.6", "league/pipeline": "^1.0", + "phpdocumentor/reflection-docblock": "^5.6", + "phpstan/phpdoc-parser": "^2.1", "symfony/console": "7.2.*", "symfony/dotenv": "7.2.*", "symfony/flex": "^2", "symfony/form": "7.2.*", "symfony/framework-bundle": "7.2.*", "symfony/monolog-bundle": "^3.0", + "symfony/property-access": "7.2.*", + "symfony/property-info": "7.2.*", "symfony/runtime": "7.2.*", "symfony/security-bundle": "7.2.*", + "symfony/serializer": "7.2.*", "symfony/twig-bundle": "7.2.*", "symfony/uid": "7.2.*", "symfony/validator": "7.2.*", diff --git a/composer.lock b/composer.lock index b321163..4c48be7 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": "75aeaffb0d55910fec640906e3460861", + "content-hash": "ea24d0c80afdb2f4436ce6907ae3e570", "packages": [ { "name": "dflydev/dot-access-data", @@ -1899,6 +1899,228 @@ }, "time": "2025-03-30T21:06:30+00:00" }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + }, + "time": "2025-04-13T19:20:35+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" + }, + "time": "2025-02-19T13:28:12+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -5287,6 +5509,104 @@ ], "time": "2025-02-11T16:46:20+00:00" }, + { + "name": "symfony/serializer", + "version": "v7.2.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "d8b75b2c8144c29ac43b235738411f7cca6d584d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/d8b75b2c8144c29ac43b235738411f7cca6d584d", + "reference": "d8b75b2c8144c29ac43b235738411f7cca6d584d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", + "symfony/error-handler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "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": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v7.2.5" + }, + "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": "2025-03-24T12:37:32+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.5.1", @@ -6420,6 +6740,64 @@ } ], "time": "2025-02-13T08:34:43+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" } ], "packages-dev": [ diff --git a/migrations/Version20250425182334.php b/migrations/Version20250425182334.php new file mode 100644 index 0000000..e1c008c --- /dev/null +++ b/migrations/Version20250425182334.php @@ -0,0 +1,38 @@ +addSql(<<<'SQL' + ALTER TABLE snip ADD visible TINYINT(1) NOT NULL + SQL); + $this->addSql(<<<'SQL' + UPDATE snip SET visible = 1 + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE snip DROP visible + SQL); + } +} diff --git a/src/Controller/SnipController.php b/src/Controller/SnipController.php index 1446b38..90b3d83 100644 --- a/src/Controller/SnipController.php +++ b/src/Controller/SnipController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\Dto\SnipFilterRequest; use App\Entity\Snip; use App\Form\ConfirmationType; use App\Form\SnipType; @@ -13,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\MapQueryString; use Symfony\Component\Routing\Attribute\Route; #[Route('/snip', name: 'snip')] @@ -24,20 +26,19 @@ class SnipController extends AbstractController ) {} #[Route('/', name: '_index')] - public function index(): Response + public function index(#[MapQueryString] SnipFilterRequest $request): Response { return $this->render('snip/index.html.twig', [ - 'snips' => $this->repository->findByUser($this->getUser()), - 'title' => 'My Snips', + 'snips' => $this->repository->findByRequest($this->getUser(), $request), + 'request' => $request, ]); } #[Route('/public', name: '_public')] public function public(): Response { - return $this->render('snip/index.html.twig', [ + return $this->render('snip/public.html.twig', [ 'snips' => $this->repository->findPublic(), - 'title' => 'Public Snips', ]); } @@ -86,7 +87,7 @@ class SnipController extends AbstractController * It technically fully works, but rendering the version history needs an update first */ $isLatest = $snip->getActiveVersion() === $snip->getLatestVersion(); - if(!$isLatest) { + if (!$isLatest) { $this->addFlash('error', 'Snip is not the latest version, changes will not be saved.'); } diff --git a/src/Dto/SnipFilterRequest.php b/src/Dto/SnipFilterRequest.php new file mode 100644 index 0000000..d4c3aec --- /dev/null +++ b/src/Dto/SnipFilterRequest.php @@ -0,0 +1,17 @@ + $this->onlyVisible, + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Snip.php b/src/Entity/Snip.php index c4cb12c..26851d7 100644 --- a/src/Entity/Snip.php +++ b/src/Entity/Snip.php @@ -33,6 +33,9 @@ class Snip #[ORM\Column(length: 255)] private ?string $parser = null; + #[ORM\Column] + private bool $visible = true; + public function __construct() { $this->snipContents = new ArrayCollection(); @@ -130,4 +133,16 @@ class Snip return $this; } + + public function isVisible(): ?bool + { + return $this->visible; + } + + public function setVisible(bool $visible): static + { + $this->visible = $visible; + + return $this; + } } diff --git a/src/Form/SnipType.php b/src/Form/SnipType.php index 1b746f0..b28cee1 100644 --- a/src/Form/SnipType.php +++ b/src/Form/SnipType.php @@ -28,7 +28,8 @@ class SnipType extends AbstractType 'attr' => ['rows' => 20], 'mapped' => false, ]) - ->add('public') + ->add('public', SwitchType::class) + ->add('visible', SwitchType::class) ; } diff --git a/src/Form/SwitchType.php b/src/Form/SwitchType.php new file mode 100644 index 0000000..4ef183c --- /dev/null +++ b/src/Form/SwitchType.php @@ -0,0 +1,22 @@ +vars['label_attr']['class'] = trim(($view->vars['label_attr']['class'] ?? '') . ' checkbox-switch'); + $view->vars['required'] = false; + } +} diff --git a/src/Repository/SnipRepository.php b/src/Repository/SnipRepository.php index ec4ab6f..08b8053 100644 --- a/src/Repository/SnipRepository.php +++ b/src/Repository/SnipRepository.php @@ -2,10 +2,12 @@ namespace App\Repository; +use App\Dto\SnipFilterRequest; use App\Entity\Snip; use App\Entity\User; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\Security\Core\User\UserInterface; /** * @extends ServiceEntityRepository @@ -40,12 +42,17 @@ class SnipRepository extends ServiceEntityRepository } } - public function findByUser(User $user): array + public function findByRequest(UserInterface $user, SnipFilterRequest $request): array { - $qb = $this->createQueryBuilder('s'); - $qb->where('s.createdBy = :user') - ->setParameter('user', $user) - ->orderBy('s.createdAt', 'DESC') + $qb = $this + ->createQueryBuilder('s') + ->where('s.createdBy = :user') + ->setParameter('user', $user) + ->orderBy('s.createdAt', 'DESC') + ; + + $qb->andWhere('s.visible = :visible') + ->setParameter('visible', $request->onlyVisible) ; return $qb->getQuery()->getResult(); @@ -53,10 +60,11 @@ class SnipRepository extends ServiceEntityRepository public function findPublic(?User $user = null): array { - $qb = $this->createQueryBuilder('s') - ->where('s.public = :public') - ->setParameter('public', true) - ->orderBy('s.createdAt', 'DESC') + $qb = $this + ->createQueryBuilder('s') + ->where('s.public = true') + ->andWhere('s.visible = true') + ->orderBy('s.createdAt', 'DESC') ; if ($user) { diff --git a/templates/snip/badge.html.twig b/templates/snip/badge.html.twig index 6c4226b..18dcb61 100644 --- a/templates/snip/badge.html.twig +++ b/templates/snip/badge.html.twig @@ -1,3 +1,13 @@ +{% if snip.visible %} + + + +{% else %} + + + +{% endif %} + {% if snip.public %} diff --git a/templates/snip/edit.html.twig b/templates/snip/edit.html.twig index f1a1e20..7b69eef 100644 --- a/templates/snip/edit.html.twig +++ b/templates/snip/edit.html.twig @@ -1,6 +1,10 @@ {% extends 'base/single.column.html.twig' %} -{% set title = 'Snip edit ' ~ snip %} +{% if snip.id %} + {% set title = 'Edit Snip ' ~ snip %} +{% else %} + {% set title = 'Create Snip' %} +{% endif %} {% block body %} {% if snip.id %} diff --git a/templates/snip/index.html.twig b/templates/snip/index.html.twig index 0a750ab..6a0d4b7 100644 --- a/templates/snip/index.html.twig +++ b/templates/snip/index.html.twig @@ -1,22 +1,24 @@ {% extends 'base/single.column.html.twig' %} +{% set title = 'My Snips' %} + {% block body %} Add + {% if request.onlyVisible %} + Show hidden + {% else %} + Hide hidden + {% endif %}

diff --git a/templates/snip/public.html.twig b/templates/snip/public.html.twig new file mode 100644 index 0000000..adfb57f --- /dev/null +++ b/templates/snip/public.html.twig @@ -0,0 +1,16 @@ +{% extends 'base/single.column.html.twig' %} + +{% set title = 'Public Snips' %} + +{% block body %} + +{% endblock %} \ No newline at end of file