From 6a6a13589186fff34c2870e6d165f5d9ae38d489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20FR=C3=89VILLE?= Date: Wed, 12 Jun 2024 09:05:30 +0200 Subject: [PATCH] Allow uploading images (#13) Adds an optional field for the post image. Fixes #12 Co-authored-by: clfreville2 Reviewed-on: https://codefirst.iut.uca.fr/git/clement.freville2/herbarium/pulls/13 Reviewed-by: Bastien OLLIER --- .gitignore | 1 + composer.json | 3 +- composer.lock | 175 +++++++++++++++++++++++- config/bundles.php | 1 + config/packages/vich_uploader.yaml | 11 ++ src/Entity/Post.php | 50 +++++++ src/Form/PostType.php | 2 + symfony.lock | 12 ++ templates/post/show.html.twig | 6 + tests/Controller/PostControllerTest.php | 21 +++ tests/image.png | Bin 0 -> 67 bytes 11 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 config/packages/vich_uploader.yaml create mode 100644 tests/image.png diff --git a/.gitignore b/.gitignore index be35b2f..f87ea38 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ phpstan.neon ###< phpstan/phpstan ### migrations +public/images diff --git a/composer.json b/composer.json index c02ecbd..eddf999 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,8 @@ "symfony/yaml": "7.0.*", "symfonycasts/verify-email-bundle": "^1.17", "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^2.12|^3.0" + "twig/twig": "^2.12|^3.0", + "vich/uploader-bundle": "^2.3" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index 12a1306..590bf82 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": "9df70d112adfdfa4fbcde08b504d0bb9", + "content-hash": "0f3c68d9e3c5fbe96fcb3367231dc539", "packages": [ { "name": "api-platform/core", @@ -1565,6 +1565,70 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "jms/metadata", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "7ca240dcac0c655eb15933ee55736ccd2ea0d7a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/7ca240dcac0c655eb15933ee55736ccd2ea0d7a6", + "reference": "7ca240dcac0c655eb15933ee55736ccd2ea0d7a6", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "require-dev": { + "doctrine/cache": "^1.0", + "doctrine/coding-standard": "^8.0", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^8.5|^9.0", + "psr/container": "^1.0|^2.0", + "symfony/cache": "^3.1|^4.0|^5.0", + "symfony/dependency-injection": "^3.1|^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Metadata\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "Class/method/property metadata management in PHP", + "keywords": [ + "annotations", + "metadata", + "xml", + "yaml" + ], + "support": { + "issues": "https://github.com/schmittjoh/metadata/issues", + "source": "https://github.com/schmittjoh/metadata/tree/2.8.0" + }, + "time": "2023-02-15T13:44:18+00:00" + }, { "name": "monolog/monolog", "version": "3.6.0", @@ -7742,6 +7806,115 @@ ], "time": "2024-05-16T10:04:27+00:00" }, + { + "name": "vich/uploader-bundle", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/dustin10/VichUploaderBundle.git", + "reference": "4002ecc83bae414b5be287907ccecb27611eaa69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/4002ecc83bae414b5be287907ccecb27611eaa69", + "reference": "4002ecc83bae414b5be287907ccecb27611eaa69", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^3", + "ext-simplexml": "*", + "jms/metadata": "^2.4", + "php": "^8.1", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher-contracts": "^3.1", + "symfony/http-foundation": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/mime": "^5.4 || ^6.0 || ^7.0", + "symfony/property-access": "^5.4 || ^6.0 || ^7.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "league/flysystem": "<2.0" + }, + "require-dev": { + "dg/bypass-finals": "^1.3", + "doctrine/doctrine-bundle": "^2.7", + "doctrine/mongodb-odm": "^2.4", + "doctrine/orm": "^2.13", + "ext-sqlite3": "*", + "knplabs/knp-gaufrette-bundle": "dev-master", + "league/flysystem-bundle": "^2.4 || ^3.0", + "league/flysystem-memory": "^2.0 || ^3.0", + "matthiasnoback/symfony-dependency-injection-test": "^5.1", + "mikey179/vfsstream": "^1.6.11", + "phpunit/phpunit": "^9.6", + "symfony/asset": "^5.4 || ^6.0 || ^7.0", + "symfony/browser-kit": "^5.4 || ^6.0 || ^7.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/dom-crawler": "^5.4 || ^6.0 || ^7.0", + "symfony/form": "^5.4 || ^6.0 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^7.0", + "symfony/security-csrf": "^5.4 || ^6.0 || ^7.0", + "symfony/translation": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "suggest": { + "doctrine/annotations": "If you use doctrine/doctrine-bundle >2.7, this package is required to use annotations", + "doctrine/doctrine-bundle": "For integration with Doctrine", + "doctrine/mongodb-odm-bundle": "For integration with Doctrine ODM", + "doctrine/orm": "For integration with Doctrine ORM", + "doctrine/phpcr-odm": "For integration with Doctrine PHPCR", + "knplabs/knp-gaufrette-bundle": "For integration with Gaufrette", + "league/flysystem-bundle": "For integration with Flysystem", + "liip/imagine-bundle": "To generate image thumbnails", + "oneup/flysystem-bundle": "For integration with Flysystem", + "symfony/asset": "To generate better links", + "symfony/form": "To handle uploads in forms", + "symfony/yaml": "To use YAML mapping" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Vich\\UploaderBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dustin Dobervich", + "email": "ddobervich@gmail.com" + } + ], + "description": "Ease file uploads attached to entities", + "homepage": "https://github.com/dustin10/VichUploaderBundle", + "keywords": [ + "file uploads", + "upload" + ], + "support": { + "issues": "https://github.com/dustin10/VichUploaderBundle/issues", + "source": "https://github.com/dustin10/VichUploaderBundle/tree/2.3.3" + }, + "time": "2024-04-24T07:17:05+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/config/bundles.php b/config/bundles.php index fbce34f..168b1c5 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -18,4 +18,5 @@ return [ Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], + Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true], ]; diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml new file mode 100644 index 0000000..4106375 --- /dev/null +++ b/config/packages/vich_uploader.yaml @@ -0,0 +1,11 @@ +vich_uploader: + db_driver: orm + + metadata: + type: attribute + + mappings: + posts: + uri_prefix: /images/posts + upload_destination: '%kernel.project_dir%/public/images/posts' + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer diff --git a/src/Entity/Post.php b/src/Entity/Post.php index 4d0b54f..1ec9f69 100644 --- a/src/Entity/Post.php +++ b/src/Entity/Post.php @@ -12,8 +12,10 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Validator\Constraints as Assert; +use Vich\UploaderBundle\Mapping\Annotation as Vich; #[ORM\Entity(repositoryClass: PostRepository::class)] #[ORM\HasLifecycleCallbacks] @@ -23,6 +25,7 @@ use Symfony\Component\Validator\Constraints as Assert; )] #[GetCollection(normalizationContext: ['groups' => ['post:collection:read']])] #[Metadata\ApiFilter(filterClass: SearchFilter::class, properties: ['species' => 'exact'])] +#[Vich\Uploadable] class Post { #[ORM\Id] @@ -41,6 +44,9 @@ class Post #[Groups(['post:collection:read'])] private ?\DateTimeImmutable $publicationDate = null; + #[ORM\Column] + private ?\DateTimeImmutable $updatedAt = null; + #[ORM\Column(nullable: true)] #[Groups(['post:collection:read'])] private ?float $latitude = null; @@ -53,6 +59,13 @@ class Post #[Groups(['post:collection:read'])] private ?float $altitude = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $image = null; + + #[Vich\UploadableField(mapping: 'posts', fileNameProperty: 'image')] + #[Assert\Image] + private ?File $imageFile = null; + #[ORM\Column(type: Types::TEXT)] #[Groups(['post:read'])] #[Assert\NotBlank] @@ -103,6 +116,18 @@ class Post return $this; } + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(\DateTimeImmutable $updatedAt): static + { + $this->updatedAt = $updatedAt; + + return $this; + } + public function getLatitude(): ?float { return $this->latitude; @@ -139,6 +164,30 @@ class Post return $this; } + public function getImage(): ?string + { + return $this->image; + } + + public function setImage(?string $image): static + { + $this->image = $image; + + return $this; + } + + public function getImageFile(): ?File + { + return $this->imageFile; + } + + public function setImageFile(?File $imageFile): static + { + $this->imageFile = $imageFile; + + return $this; + } + public function getCommentary(): ?string { return $this->commentary; @@ -170,6 +219,7 @@ class Post if ($this->publicationDate === null) { $this->publicationDate = new \DateTimeImmutable(); } + $this->updatedAt = new \DateTimeImmutable(); } /** diff --git a/src/Form/PostType.php b/src/Form/PostType.php index bea7e6f..d384eb3 100644 --- a/src/Form/PostType.php +++ b/src/Form/PostType.php @@ -6,6 +6,7 @@ use App\Entity\Post; use App\Entity\Species; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -20,6 +21,7 @@ class PostType extends AbstractType ->add('latitude') ->add('longitude') ->add('altitude') + ->add('imageFile', FileType::class) ->add('commentary') ->add('species', EntityType::class, [ 'class' => Species::class, diff --git a/symfony.lock b/symfony.lock index 5e18ec6..0bc2d80 100644 --- a/symfony.lock +++ b/symfony.lock @@ -343,5 +343,17 @@ }, "twig/extra-bundle": { "version": "v3.10.0" + }, + "vich/uploader-bundle": { + "version": "2.3", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.13", + "ref": "1b3064c2f6b255c2bc2f56461aaeb76b11e07e36" + }, + "files": [ + "config/packages/vich_uploader.yaml" + ] } } diff --git a/templates/post/show.html.twig b/templates/post/show.html.twig index be2823c..76d00e5 100644 --- a/templates/post/show.html.twig +++ b/templates/post/show.html.twig @@ -31,6 +31,12 @@ Altitude {{ post.altitude }} + {% if post.image %} + + Image + + + {% endif %} Commentary {{ post.commentary }} diff --git a/tests/Controller/PostControllerTest.php b/tests/Controller/PostControllerTest.php index bd0ddc2..963c410 100644 --- a/tests/Controller/PostControllerTest.php +++ b/tests/Controller/PostControllerTest.php @@ -8,6 +8,7 @@ use App\Repository\PostRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Component\HttpFoundation\File\UploadedFile; class PostControllerTest extends WebTestCase { @@ -147,4 +148,24 @@ class PostControllerTest extends WebTestCase $comments = $this->repository->find($fixture->getId())->getComments(); self::assertSame(1, $comments->count()); } + + public function testUploadImage() + { + $fixture = new Post(); + $fixture->setFoundDate(new \DateTimeImmutable('2024-01-01 00:00:00')); + $fixture->setCommentary('Cool stuff'); + + $this->manager->persist($fixture); + $this->manager->flush(); + + $file = new UploadedFile(__DIR__ . '/../image.png', 'image.png'); + + $this->client->request('GET', sprintf('%s%s/edit', $this->path, $fixture->getId())); + + $this->client->submitForm('Update', [ + 'post[imageFile]' => $file, + ]); + + self::assertNotNull($this->repository->find($fixture->getId())->getImage()); + } } diff --git a/tests/image.png b/tests/image.png new file mode 100644 index 0000000000000000000000000000000000000000..252d9502d8573d033e633f5e377d81bebf8afd36 GIT binary patch literal 67 zcmeAS@N?(olHy`uVBq!ia0vp^j35jm7|ip2ssJf2PZ!6K3dZCFAe)JSvAC2`0?1