modalité uploder des ficher avec VichUploaderBundle

This commit is contained in:
maher
2026-01-11 16:33:43 +01:00
parent 1d2b4fbb6b
commit 756cae51d2
16 changed files with 849 additions and 5 deletions

View File

@@ -21,7 +21,8 @@
"symfony/translation": "8.0.*", "symfony/translation": "8.0.*",
"symfony/twig-bundle": "8.0.*", "symfony/twig-bundle": "8.0.*",
"symfony/validator": "8.0.*", "symfony/validator": "8.0.*",
"symfony/yaml": "8.0.*" "symfony/yaml": "8.0.*",
"vich/uploader-bundle": "^2.9"
}, },
"require-dev": { "require-dev": {
"symfony/maker-bundle": "^1.65", "symfony/maker-bundle": "^1.65",

347
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "a65a9ed16594341ca6c22d4c6316bce1", "content-hash": "754cd146956f25fd7cf8b7b7a01e4aa3",
"packages": [ "packages": [
{ {
"name": "doctrine/collections", "name": "doctrine/collections",
@@ -1120,6 +1120,70 @@
}, },
"time": "2025-10-26T09:35:14+00:00" "time": "2025-10-26T09:35:14+00:00"
}, },
{
"name": "jms/metadata",
"version": "2.9.0",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/metadata.git",
"reference": "554319d2e5f0c5d8ccaeffe755eac924e14da330"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/schmittjoh/metadata/zipball/554319d2e5f0c5d8ccaeffe755eac924e14da330",
"reference": "554319d2e5f0c5d8ccaeffe755eac924e14da330",
"shasum": ""
},
"require": {
"php": "^7.2|^8.0"
},
"require-dev": {
"doctrine/cache": "^1.0|^2.0",
"doctrine/coding-standard": "^8.0",
"mikey179/vfsstream": "^1.6.7",
"phpunit/phpunit": "^8.5.42|^9.6.23",
"psr/container": "^1.0|^2.0",
"symfony/cache": "^3.1|^4.0|^5.0|^6.0|^7.0|^8.0",
"symfony/dependency-injection": "^3.1|^4.0|^5.0|^6.0|^7.0|^8.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.9.0"
},
"time": "2025-11-30T20:12:26+00:00"
},
{ {
"name": "psr/cache", "name": "psr/cache",
"version": "3.0.0", "version": "3.0.0",
@@ -3057,6 +3121,92 @@
], ],
"time": "2025-12-08T07:59:34+00:00" "time": "2025-12-08T07:59:34+00:00"
}, },
{
"name": "symfony/mime",
"version": "v8.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "7576ce3b2b4d3a2a7fe7020a07a392065d6ffd40"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/7576ce3b2b4d3a2a7fe7020a07a392065d6ffd40",
"reference": "7576ce3b2b4d3a2a7fe7020a07a392065d6ffd40",
"shasum": ""
},
"require": {
"php": ">=8.4",
"symfony/polyfill-intl-idn": "^1.10",
"symfony/polyfill-mbstring": "^1.0"
},
"conflict": {
"egulias/email-validator": "~3.0.0",
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0"
},
"require-dev": {
"egulias/email-validator": "^2.1.10|^3.1|^4",
"league/html-to-markdown": "^5.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
"symfony/dependency-injection": "^7.4|^8.0",
"symfony/process": "^7.4|^8.0",
"symfony/property-access": "^7.4|^8.0",
"symfony/property-info": "^7.4|^8.0",
"symfony/serializer": "^7.4|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Mime\\": ""
},
"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": "Allows manipulating MIME messages",
"homepage": "https://symfony.com",
"keywords": [
"mime",
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v8.0.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-11-16T10:17:21+00:00"
},
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
"version": "v8.0.0", "version": "v8.0.0",
@@ -3371,6 +3521,93 @@
], ],
"time": "2025-06-20T22:24:30+00:00" "time": "2025-06-20T22:24:30+00:00"
}, },
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3",
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3",
"shasum": ""
},
"require": {
"php": ">=7.2",
"symfony/polyfill-intl-normalizer": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Laurent Bassin",
"email": "laurent@bassin.info"
},
{
"name": "Trevor Rowbotham",
"email": "trevor.rowbotham@pm.me"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"idn",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-10T14:38:51+00:00"
},
{ {
"name": "symfony/polyfill-intl-normalizer", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.33.0", "version": "v1.33.0",
@@ -5395,6 +5632,114 @@
} }
], ],
"time": "2025-12-14T11:28:47+00:00" "time": "2025-12-14T11:28:47+00:00"
},
{
"name": "vich/uploader-bundle",
"version": "v2.9.1",
"source": {
"type": "git",
"url": "https://github.com/dustin10/VichUploaderBundle.git",
"reference": "945939a04a33c0b78c5fbb7ead31533d85112df5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/945939a04a33c0b78c5fbb7ead31533d85112df5",
"reference": "945939a04a33c0b78c5fbb7ead31533d85112df5",
"shasum": ""
},
"require": {
"doctrine/persistence": "^3.0 || ^4.0",
"ext-simplexml": "*",
"jms/metadata": "^2.4",
"php": "^8.1",
"symfony/config": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/event-dispatcher-contracts": "^3.1",
"symfony/http-foundation": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/mime": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/property-access": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/string": "^5.4 || ^6.0 || ^7.0 || ^8.0"
},
"conflict": {
"doctrine/annotations": "<1.12",
"league/flysystem": "<2.0"
},
"require-dev": {
"dg/bypass-finals": "^1.9",
"doctrine/common": "^3.0",
"doctrine/doctrine-bundle": "^2.7 || ^3.0",
"doctrine/mongodb-odm": "^2.4",
"doctrine/orm": "^2.13 || ^3.0",
"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 || ^6.0",
"mikey179/vfsstream": "^1.6.11",
"phpunit/phpunit": "^10.5 || ^11.5 || ^12.2",
"symfony/asset": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/browser-kit": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/dom-crawler": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/form": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/phpunit-bridge": "^7.3",
"symfony/security-csrf": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/translation": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/validator": "^5.4.22 || ^6.0 || ^7.0 || ^8.0",
"symfony/var-dumper": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0"
},
"suggest": {
"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/v2.9.1"
},
"time": "2025-12-10T08:23:38+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [

View File

@@ -8,4 +8,5 @@ return [
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true],
]; ];

0
config/packages/csrf.yaml Normal file → Executable file
View File

View File

@@ -0,0 +1,15 @@
vich_uploader:
db_driver: orm
mappings:
attachments:
uri_prefix: /uploads/questions
upload_destination: '%kernel.project_dir%/public/uploads/questions'
namer: Vich\UploaderBundle\Naming\UniqidNamer
directory_namer: ~
inject_on_load: false
delete_on_update: true
delete_on_remove: true
# Optionnel : pour générer des URLs absolues
# uri_prefix_absolute: true

View File

@@ -1242,6 +1242,44 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
* intercept_redirects?: bool, // Default: false * intercept_redirects?: bool, // Default: false
* excluded_ajax_paths?: scalar|null, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt" * excluded_ajax_paths?: scalar|null, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt"
* } * }
* @psalm-type VichUploaderConfig = array{
* default_filename_attribute_suffix?: scalar|null, // Default: "_name"
* db_driver: scalar|null,
* storage?: scalar|null, // Default: "file_system"
* use_flysystem_to_resolve_uri?: bool, // Default: false
* twig?: scalar|null, // twig requires templating // Default: true
* form?: scalar|null, // Default: true
* metadata?: array{
* cache?: scalar|null, // Default: "file"
* type?: scalar|null, // Default: "attribute"
* file_cache?: array{
* dir?: scalar|null, // Default: "%kernel.cache_dir%/vich_uploader"
* },
* auto_detection?: bool, // Default: true
* directories?: list<array{ // Default: []
* path: scalar|null,
* namespace_prefix?: scalar|null, // Default: ""
* }>,
* },
* mappings?: array<string, array{ // Default: []
* uri_prefix?: scalar|null, // Default: "/uploads"
* upload_destination?: scalar|null, // Default: null
* namer?: string|array{
* service?: scalar|null, // Default: null
* options?: mixed, // Default: null
* },
* directory_namer?: string|array{
* service?: scalar|null, // Default: null
* options?: mixed, // Default: null
* },
* delete_on_remove?: scalar|null, // Default: true
* erase_fields?: scalar|null, // Default: true
* delete_on_update?: scalar|null, // Default: true
* inject_on_load?: scalar|null, // Default: false
* namer_keep_extension?: scalar|null, // Default: false
* db_driver?: scalar|null, // Default: null
* }>,
* }
* @psalm-type ConfigType = array{ * @psalm-type ConfigType = array{
* imports?: ImportsConfig, * imports?: ImportsConfig,
* parameters?: ParametersConfig, * parameters?: ParametersConfig,
@@ -1251,6 +1289,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
* doctrine?: DoctrineConfig, * doctrine?: DoctrineConfig,
* doctrine_migrations?: DoctrineMigrationsConfig, * doctrine_migrations?: DoctrineMigrationsConfig,
* security?: SecurityConfig, * security?: SecurityConfig,
* vich_uploader?: VichUploaderConfig,
* "when@dev"?: array{ * "when@dev"?: array{
* imports?: ImportsConfig, * imports?: ImportsConfig,
* parameters?: ParametersConfig, * parameters?: ParametersConfig,
@@ -1262,6 +1301,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
* doctrine_migrations?: DoctrineMigrationsConfig, * doctrine_migrations?: DoctrineMigrationsConfig,
* security?: SecurityConfig, * security?: SecurityConfig,
* web_profiler?: WebProfilerConfig, * web_profiler?: WebProfilerConfig,
* vich_uploader?: VichUploaderConfig,
* }, * },
* "when@prod"?: array{ * "when@prod"?: array{
* imports?: ImportsConfig, * imports?: ImportsConfig,
@@ -1272,6 +1312,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
* doctrine?: DoctrineConfig, * doctrine?: DoctrineConfig,
* doctrine_migrations?: DoctrineMigrationsConfig, * doctrine_migrations?: DoctrineMigrationsConfig,
* security?: SecurityConfig, * security?: SecurityConfig,
* vich_uploader?: VichUploaderConfig,
* }, * },
* "when@test"?: array{ * "when@test"?: array{
* imports?: ImportsConfig, * imports?: ImportsConfig,
@@ -1283,6 +1324,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
* doctrine_migrations?: DoctrineMigrationsConfig, * doctrine_migrations?: DoctrineMigrationsConfig,
* security?: SecurityConfig, * security?: SecurityConfig,
* web_profiler?: WebProfilerConfig, * web_profiler?: WebProfilerConfig,
* vich_uploader?: VichUploaderConfig,
* }, * },
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias * ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
* imports?: ImportsConfig, * imports?: ImportsConfig,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@@ -13,7 +13,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use App\Entity\State; use App\Entity\Question;
use App\Entity\Investigation; use App\Entity\Investigation;
use App\Repository\StateRepository; use App\Repository\StateRepository;
@@ -68,6 +68,16 @@ final class InvestigationController extends AbstractController
$investigation = $form->getData(); $investigation = $form->getData();
$investigation->setCreateBy($this->getUser()); $investigation->setCreateBy($this->getUser());
;
foreach($investigation->getQuestions() as $question){
foreach ($question->getAttachments() as $attachment) {
if ($attachment->getFile()) {
// Optionnel : calculer les dimensions pour les images
$attachment->setDimensionsFromFile($attachment->getFile());
}
}
}
$this->em->persist($investigation); $this->em->persist($investigation);
$this->em->flush(); $this->em->flush();
@@ -80,6 +90,31 @@ final class InvestigationController extends AbstractController
]); ]);
} }
private function handleAttachments($form, Question $question): void
{
// Récupérer le champ attachments du formulaire
$attachmentsForm = $form->get('attachments');
// Parcourir tous les attachments du formulaire
foreach ($attachmentsForm as $attachmentForm) {
/** @var Attachment $attachment */
$attachment = $attachmentForm->getData();
// Récupérer le fichier uploadé
$file = $attachmentForm->get('file')->getData();
if ($file) {
// Définir le fichier sur l'entité Attachment
$attachment->setFile($file);
// Associer l'attachment à la question
if (!$attachment->getQuestion()) {
$attachment->setQuestion($question);
}
}
}
}
#[Route('/show/{id}', name: 'app_investigation_show')] #[Route('/show/{id}', name: 'app_investigation_show')]
public function show(Investigation $investigation): Response public function show(Investigation $investigation): Response
{ {

174
src/Entity/Attachment.php Normal file
View File

@@ -0,0 +1,174 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[Vich\Uploadable]
class Attachment
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[Vich\UploadableField(
mapping: 'attachments',
fileNameProperty: 'fileName',
size: 'fileSize',
mimeType: 'mimeType',
originalName: 'originalName',
dimensions: 'dimensions'
)]
private ?File $file = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $fileName = null;
#[ORM\Column(nullable: true)]
private ?int $fileSize = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $mimeType = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $originalName = null;
#[ORM\Column(type: 'simple_array', nullable: true)]
private ?array $dimensions = null;
#[ORM\ManyToOne(targetEntity: Question::class, inversedBy: 'attachments')]
#[ORM\JoinColumn(nullable: false)]
private ?Question $question = null;
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $updatedAt = null;
public function getId(): ?int
{
return $this->id;
}
public function setFile(?File $file = null): void
{
$this->file = $file;
if (null !== $file) {
$this->updatedAt = new \DateTimeImmutable();
}
}
public function getFile(): ?File
{
return $this->file;
}
public function setFileName(?string $fileName): void
{
$this->fileName = $fileName;
}
public function getFileName(): ?string
{
return $this->fileName;
}
public function setFileSize(?int $fileSize): void
{
$this->fileSize = $fileSize;
}
public function getFileSize(): ?int
{
return $this->fileSize;
}
public function setMimeType(?string $mimeType): void
{
$this->mimeType = $mimeType;
}
public function getMimeType(): ?string
{
return $this->mimeType;
}
public function setOriginalName(?string $originalName): void
{
$this->originalName = $originalName;
}
public function getOriginalName(): ?string
{
return $this->originalName;
}
public function setDimensions(?array $dimensions): void
{
$this->dimensions = $dimensions;
}
public function getDimensions(): ?array
{
return $this->dimensions;
}
public function getQuestion(): ?Question
{
return $this->question;
}
public function setQuestion(?Question $question): void
{
$this->question = $question;
}
public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeImmutable $updatedAt): void
{
$this->updatedAt = $updatedAt;
}
// Méthode utilitaire pour afficher la taille en format lisible
public function getReadableFileSize(): string
{
$bytes = $this->fileSize;
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= (1 << (10 * $pow));
return round($bytes, 2) . ' ' . $units[$pow];
}
public function __toString(): string
{
return $this->originalName ?? $this->fileName ?? '';
}
// Si vous utilisez les dimensions (pour les images)
public function setDimensionsFromFile(File $file): void
{
if (str_starts_with($file->getMimeType(), 'image/')) {
try {
$size = getimagesize($file->getPathname());
if ($size) {
$this->dimensions = [$size[0], $size[1]];
}
} catch (\Exception $e) {
// Ignorer l'erreur si ce n'est pas une image
}
}
}
}

View File

@@ -56,6 +56,12 @@ class Question
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
private ?Investigation $investigation = null; private ?Investigation $investigation = null;
/**
* @var Collection<int, Attachment>
*/
#[ORM\OneToMany(targetEntity: Attachment::class, mappedBy: 'question', cascade: ['persist', 'remove'], orphanRemoval: true)]
private Collection $attachments;
public function __construct() public function __construct()
{ {
@@ -64,6 +70,7 @@ class Question
$this->choice = false; $this->choice = false;
$this->amount = 0; $this->amount = 0;
$this->max_participant = 0; $this->max_participant = 0;
$this->attachments = new ArrayCollection();
} }
public function __clone() public function __clone()
@@ -254,6 +261,36 @@ class Question
return $this; return $this;
} }
/**
* @return Collection<int, Attachment>
*/
public function getAttachments(): Collection
{
return $this->attachments;
}
public function addAttachment(Attachment $attachment): static
{
if (!$this->attachments->contains($attachment)) {
$this->attachments->add($attachment);
$attachment->setQuestion($this);
}
return $this;
}
public function removeAttachment(Attachment $attachment): static
{
if ($this->attachments->removeElement($attachment)) {
// set the owning side to null (unless already changed)
if ($attachment->getQuestion() === $this) {
$attachment->setQuestion(null);
}
}
return $this;
}
} }

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Form;
use App\Entity\Attachment;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType; // Ajoutez cette ligne
class AttachmentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('file', VichFileType::class, [ // Utilisez VichFileType au lieu de FileType
'label' => 'Fichier',
'required' => false,
'allow_delete' => true,
'delete_label' => 'Supprimer',
'download_label' => 'Télécharger',
'download_uri' => true,
'asset_helper' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Attachment::class,
]);
}
}

View File

@@ -12,6 +12,8 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class QuestionType extends AbstractType class QuestionType extends AbstractType
{ {
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
@@ -59,6 +61,17 @@ class QuestionType extends AbstractType
'prototype' => true, 'prototype' => true,
'by_reference' => false 'by_reference' => false
)) ))
->add('attachments', CollectionType::class, [
'label' => 'Pièces jointes',
'entry_type' => AttachmentType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'prototype' => true,
'prototype_name' => '__attachment_prot__',
'required' => false,
]);
; ;
} }

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\Attachment;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Attachment>
*/
class AttachmentRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Attachment::class);
}
// /**
// * @return Attachment[] Returns an array of Attachment objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('a')
// ->andWhere('a.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('a.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Attachment
// {
// return $this->createQueryBuilder('a')
// ->andWhere('a.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -189,5 +189,17 @@
"config/packages/web_profiler.yaml", "config/packages/web_profiler.yaml",
"config/routes/web_profiler.yaml" "config/routes/web_profiler.yaml"
] ]
},
"vich/uploader-bundle": {
"version": "2.9",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "1.13",
"ref": "1b3064c2f6b255c2bc2f56461aaeb76b11e07e36"
},
"files": [
"config/packages/vich_uploader.yaml"
]
} }
} }

View File

@@ -42,7 +42,7 @@
<div class="az-content-label mg-b-5"></div> <div class="az-content-label mg-b-5"></div>
<p class="mg-b-20">Créer une nouvelle enquête</p> <p class="mg-b-20">Créer une nouvelle enquête</p>
{{ form_start(form) }} {{ form_start(form, {'attr': {'enctype': 'multipart/form-data'}}) }}
<div class="tab-vertical mb-3" id="investigation-information"> <div class="tab-vertical mb-3" id="investigation-information">
<ul class="nav nav-tabs" id="myTab3" role="tablist"> <ul class="nav nav-tabs" id="myTab3" role="tablist">
<li class="nav-item"> <li class="nav-item">
@@ -162,6 +162,7 @@
</div> </div>
</div> </div>
{# modalities #}
<h4 class="card-title mt-4">Modalité</h4> <h4 class="card-title mt-4">Modalité</h4>
<div class="row mt-3"> <div class="row mt-3">
<div class="col-md-6"> <div class="col-md-6">
@@ -242,9 +243,38 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div class="card-body">
<div class="attachments-container" data-prototype="{{ form_widget(formQuestion.attachments.vars.prototype)|e('html_attr') }}" data-index="{{ formQuestion.attachments|length }}" >
{% for attachment in formQuestion.attachments %}
<div class="attachment-item" id="attachment-{{ loop.index0 }}">
{{ form_row(attachment.file, { 'row_attr': {'class': 'mb-0'} }) }}
<button type="button" class="btn btn-outline-danger btn-sm remove-attachment mt-2">
<i class="fas fa-trash"></i> Supprimer
</button>
</div>
{% else %}
<p class="text-muted">Aucun fichier ajouté</p>
{% endfor %}
</div>
</div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<button type="button" class="btn btn-primary add-modality">Ajouter une modalité</button> <button type="button" class="btn btn-primary add-modality mb-2">Ajouter une modalité</button>
<button type="button" class="btn btn-outline-primary add-attachment mt-3"><i class="fas fa-plus"></i> Ajouter un fichier</button>
</div> </div>
</div> </div>
</div> </div>
@@ -337,6 +367,50 @@
$(document).on('click', '.remove-modality', function() { $(document).on('click', '.remove-modality', function() {
$(this).closest('.modality-item').remove(); $(this).closest('.modality-item').remove();
}); });
// 1. Ajouter un fichier
$(document).on('click', '.add-attachment', function(e) {
e.preventDefault();
// On remonte au parent pour trouver le conteneur spécifique à cette question
var $container = $(this).closest('.row').find('.attachments-container');
var prototype = $container.data('prototype');
var index = $container.data('index');
// On remplace le prototype par l'index actuel
var newForm = prototype.replace(/__attachment_prot__/g, index);
// On crée l'élément HTML
var $newItem = $('<div class="attachment-item mb-2"></div>').append(newForm);
// On ajoute un bouton de suppression au nouvel élément
$newItem.append('<button type="button" class="btn btn-outline-danger btn-sm remove-attachment mt-2"><i class="fas fa-trash"></i> Supprimer</button>');
// On ajoute au DOM et on incrémente l'index
$container.append($newItem);
$container.data('index', index + 1);
// Supprimer le message "Aucun fichier" s'il existe
$container.find('p.text-muted').remove();
});
// 2. Supprimer un fichier (existant ou nouveau)
$(document).on('click', '.remove-attachment', function(e) {
e.preventDefault();
$(this).closest('.attachment-item').remove();
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -168,10 +168,29 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div class="card-body">
<div class="attachments-container" data-prototype="{{ form_widget(formQtn.attachments.vars.prototype)|e('html_attr') }}" data-index="{{ formQtn.attachments|length }}" >
{% for attachment in formQtn.attachments %}
<div class="attachment-item" id="attachment-{{ loop.index0 }}">
{{ form_row(attachment.file, { 'row_attr': {'class': 'mb-0'} }) }}
<button type="button" class="btn btn-outline-danger btn-sm remove-attachment mt-2">
<i class="fas fa-trash"></i> Supprimer
</button>
</div>
{% else %}
<p class="text-muted">Aucun fichier ajouté</p>
{% endfor %}
</div>
</div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<button type="button" class="btn btn-primary add-modality">Ajouter une modalité</button> <button type="button" class="btn btn-primary add-modality">Ajouter une modalité</button>
<button type="button" class="btn btn-outline-primary add-attachment mt-3"><i class="fas fa-plus"></i> Ajouter un fichier</button>
</div> </div>
</div> </div>