<?php
namespace App\Controller\Website;
use App\Form\DepartementType;
use App\Form\TerritoryType;
use App\Form\ThematiquesType;
use App\Message\NewExperience;
use Sulu\Bundle\CommunityBundle\Controller\AbstractController;
use Sulu\Bundle\MediaBundle\Collection\Manager\CollectionManager;
use Sulu\Bundle\MediaBundle\Media\Manager\MediaManagerInterface;
use Symfony\Component\Form\Event\PreSubmitEvent;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Twig\Environment;
class SendExperienceController extends AbstractController
{
private Environment $twig;
private MediaManagerInterface $mediaManager;
private Security $security;
private RouterInterface $router;
private CollectionManager $collectionManager;
private CsrfTokenManagerInterface $csrfTokenManager;
/**
* @var MessageBusInterface $bus
*/
private MessageBusInterface $bus;
public function __construct(
Environment $twig,
MediaManagerInterface $mediaManager,
MessageBusInterface $bus,
Security $security,
RouterInterface $router,
CollectionManager $collectionManager,
CsrfTokenManagerInterface $csrfTokenManager
) {
$this->twig = $twig;
$this->mediaManager = $mediaManager;
$this->bus = $bus;
$this->security = $security;
$this->router = $router;
$this->collectionManager = $collectionManager;
$this->csrfTokenManager = $csrfTokenManager;
}
public function getExperienceForm(Request $request): Response
{
// Cette action est appelée en sous-requête via render(controller()) depuis
// pages/send-experience.html.twig. FragmentHandler::deliver() rejette toute
// réponse non-2xx : renvoyer une redirection ici lève une RuntimeException
// ("Error when rendering ... (Status code is 302)"). On rend donc une
// invitation à se connecter, en 200.
if (!$this->security->getUser()) {
return new Response($this->twig->render('blocks/send-experience-login-required.html.twig', [
'loginPath' => $this->router->generate('sulu_community.login'),
]));
}
$form = $this->createFormBuilder(null, ['allow_extra_fields' => true, 'csrf_protection' => false])
// first block
->add('title', TextType::class, [
'required' => true,
'label' => 'Titre de l\'expérience*',
'attr' => [
'placeholder' => 'Titre de l\'expérience',
'maxlength' => 150
]
])
->add('departement_type', DepartementType::class, ['required' => true, 'label' => ' '])
->add('departement', HiddenType::class)
//{{ form_errors(form.thematiques_type) }}
// à mettre pour le futur twig du formulaire
->add('territory_type', TerritoryType::class, [
'required' => false,
'label' => 'Choisir les territoires',
])
->add('territory_select', HiddenType::class, ['mapped' => true])
->add('file', FileType::class, [
'label' => 'Importer le visuel principal* <span>(PNG, JPG max. 10MB)</span>',
'label_html' => true,
'required' => true,
'constraints' => [
new Assert\File(
[
'maxSize' => '10M',
'mimeTypes' => [
'image/jpg',
'image/jpeg',
'image/png'
]
],
),
]
])
->add('header_img', HiddenType::class, ['mapped' => true])
->add('sub_thematiques', TextType::class, [
'required' => false,
'label' => 'Sous-thématiques',
'attr' => [
'placeholder' => 'Sous-thématiques'
]
])
->add('structure', TextType::class, [
'required' => true,
'label' => 'Nom de la structure porteuse*',
'attr' => [
'placeholder' => 'Structure'
]
])
->add('adresse_input', TextType::class, [
'required' => true,
'label' => 'Adresse*',
'attr' => [
'placeholder' => 'Adresse'
]
])
->add('adresse', HiddenType::class)
->add('long', HiddenType::class)
->add('lat', HiddenType::class)
// à ajouter
->add('phone', TelType::class, [
'required' => true,
'label' => 'Téléphone*',
'attr' => [
'placeholder' => 'Téléphone'
]
])
->add('email', EmailType::class, [
'label' => 'Adresse email*',
'required' => true,
'attr' => [
'placeholder' => 'email@exemple.com'
]
])
->add('personne_rsc', TextType::class, [
'required' => false,
'label' => 'Nom de la personne ressource',
'attr' => [
'placeholder' => 'Structure'
]
])
->add('desc', TextareaType::class, [
'required' => true,
'label' => 'Résumé de l\'initiative* (600 carac. max)',
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '600'
]
])
// second block
->add('thematiques_type', ThematiquesType::class, [
'label' => 'Thématiques*',
'required' => true,
'constraints' => [
new Assert\Callback(function ($data, $context) {
if (empty($data['name'])) {
$context->buildViolation('Veuillez sélectionner au moins une option.')
->addViolation();
}
}),
],
])
->add('thematiques', HiddenType::class)
// à ajouter
->add('status_juridique', TextareaType::class, [
'label' => 'Statut juridique (50 carac. max)',
'attr' => [
'placeholder' => 'Statut juridique',
'maxlength' => '50'
],
'required' => false
])
// à ajouter
->add('influence', ChoiceType::class, [
'label' => 'Aire d\'influence*',
'multiple' => true,
'expanded' => true,
'required' => true,
'choices' => [
'Quartier' => 'Quartier',
'Commune' => 'Commune',
'Intercommunalité' => 'Intercommunalité',
'PNR' => 'PNR',
'Pays/PETR' => 'Pays/PETR',
'Département' => 'Département',
'Région' => 'Région',
'Autre' => 'Autre'
],
'constraints' => [
new Assert\Count([
'min' => 1,
'minMessage' => 'Veuillez sélectionner au moins une option.',
]),
],
])
->add('date_start', DateType::class, [
'label' => 'Date de lancement*',
'required' => true,
])
// à ajouter
->add('status', ChoiceType::class, [
'label' => 'État d\'achèvement*',
'required' => true,
'expanded' => true,
'choices' => [
'En cours' => 'En cours',
'Terminé' => 'Terminé'
]
])
// à ajouter
->add('cost', TextType::class, [
'label' => 'Coût du projet',
'attr' => [
'placeholder' => 'Coût du projet'
],
'required' => false
])
// à ajouter
->add('cible', ChoiceType::class, [
'label' => 'Public cible*',
'multiple' => true,
'expanded' => true,
'required' => true,
'choices' => [
'Tout public' => 'Tout public',
'Citoyens' => 'Citoyens',
'Enfants' => 'Enfants',
'Adolescents' => 'Adolescents',
'Etudiants' => 'Etudiants',
'Seniors' => 'Seniors',
'Public en insertion' => 'Public en insertion',
'Personnes vulnérables' => 'Personnes vulnérables',
'Demandeurs d\'emploi' => 'Demandeurs d\'emploi',
'Professionnels' => 'Professionnels',
'Agriculteurs' => 'Agriculteurs',
'Elus' => 'Elus',
'Elus locaux' => 'Elus locaux',
'Collectivités territoriales' => 'Collectivités territoriales',
'Associations' => 'Associations',
'Autre' => 'Autre'
],
'constraints' => [
new Assert\Count([
'min' => 1,
'minMessage' => 'Veuillez sélectionner au moins une option.',
]),
],
])
// à ajouter
->add('cible_autre', TextareaType::class, [
'label' => 'Précisez votre public cible si vous le souhaitez (non obligatoire/100 caractères max)',
'required' => false,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '100'
]
])
// à ajouter
->add('partenaires_techniques', TextareaType::class, [
'label' => 'Partenaires techniques (200 carac. max)',
'required' => false,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '200'
]
])
// à ajouter
->add('partenaires_financiers', TextareaType::class, [
'label' => 'Partenaires financiers (200 carac. max)',
'required' => false,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '200'
]
])
// third block
->add('compo_equipe', TextareaType::class, [
'label' => 'Composition de l\'équipe rattachée au projet (élus, équipe technique, etc.) (200 carac. max)',
'required' => false,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '200'
]
])
// à ajouter
->add('contexte', TextareaType::class, [
'label' => 'Contexte de l\'expérience* (1000 carac. max)',
'required' => true,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '1000'
]
])
->add('objectif', TextareaType::class, [
'label' => 'Objectif de l\'expérience* (1000 carac. max)',
'required' => true,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '1000'
]
])
// à ajouter
->add('mise_oeuvre', TextareaType::class, [
'label' => 'Mise en œuvre des actions* (1000 carac. max)',
'required' => true,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '1000'
]
])
// à ajouter
->add('resultats', TextareaType::class, [
'label' => 'Résultats (1000 carac. max)',
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '1000'
],
'required' => false
])
// à ajouter
->add('facteurs_reussite', TextareaType::class, [
'label' => 'Facteurs de réussite* (1000 carac. max)',
'required' => true,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '1000'
]
])
// à ajouter
->add('freins', TextareaType::class, [
'label' => 'Freins* (1000 carac. max)',
'required' => true,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '1000'
]
])
// à ajouter
->add('enseignements', TextareaType::class, [
'label' => 'Enseignements* (1000 carac. max)',
'required' => true,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '1000'
]
])
// à ajouter
->add('perspectives', TextareaType::class, [
'label' => 'Perspectives (1000 carac. max)',
'required' => false,
'attr' => [
'placeholder' => 'Réponse',
'maxlength' => '1000'
]
])
// à ajouter
->add('link_to_resource', UrlType::class, [
'label' => 'Lien vers le site web relatif à la ressource',
'attr' => [
'placeholder' => 'Lien vers le site web relatif à la ressource'
],
'required' => false,
])
// à ajouter
->add('other_link', UrlType::class, [
'label' => 'Autre lien',
'attr' => [
'placeholder' => 'Autre lien'
],
'required' => false,
])
// à ajouter
->add('other_file_input', FileType::class, [
'label' => 'Importer un document <span>(PNG, JPG, PDF max. 10MB)</span>',
'label_html' => true,
'required' => false,
'constraints' => [
new Assert\File(
[
'maxSize' => '10M',
'mimeTypes' => [
'image/jpg',
'image/jpeg',
'image/png',
'application/pdf'
]
],
),
]
])
->add('other_file', HiddenType::class, ['mapped' => true])
->add('user', HiddenType::class)
->add('send', SubmitType::class, ['label' => 'Envoyer'])
->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event): void {
$data = $event->getData();
$file = $data['file'];
$other_file = $data['other_file_input'];
if (!array_key_exists('name', $data['thematiques_type'])) {
return;
}
if ($file) {
$collectionId = null;
$collections = $this->collectionManager->get('fr');
// je boucle sur mes collections et je regarde si il y a une collection avec le titre du document
$title = 'Gallerie des médias expériences';
foreach ($collections as $collection) {
if ($title === $collection->getTitle()) {
$collectionId = $collection->getId();
}
}
if ($collectionId === null) {
$collectionData = [
"id" => null,
"title" => $title,
"locale" => 'fr',
'type' => ['id' => 1],
'parent' => null,
'key' => null,
];
$currentCollection = $this->collectionManager->save($collectionData, null, true);
$collectionId = $currentCollection->getId();
}
$savedFile = $this->mediaManager->save($file, [
'locale' => 'fr',
'title' => $file->getClientOriginalName(),
'collection' => $collectionId
], null);
$fileId = $savedFile->getId();
$data['header_img'] = json_encode(["displayOption" => null, "id" => $fileId]);
if ($other_file) {
$savedFile = $this->mediaManager->save($other_file, [
'locale' => 'fr',
'title' => $other_file->getClientOriginalName(),
'collection' => $collectionId
], null);
$other_fileId = $savedFile->getId();
$data['other_file'] = json_encode(["displayOption" => null, "id" => $other_fileId]);
}
} else {
$this->addFlash('error', 'Vous devez renseigner un fichier image de moins de 10M');
return;
}
$data['departement'] = $data['departement_type']['name'];
if (isset($data['territory_type']) && array_key_exists('name', $data['territory_type'])) {
$data['territory_select'] = json_encode($data['territory_type']['name']);
}
$data['thematiques'] = json_encode($data['thematiques_type']['name']);
$data['adresse'] = json_encode([
$data['adresse_input'],
$data['lat'],
$data['long']
]);
$data['user'] = $this->security->getUser()->getContact()->getId(); //marqué comme erreur, mais la fonction est bien présente
$event->setData($data);
})
->getForm();
sleep(3);
$form->handleRequest($request);
$data = null;
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData(); // Récupérer les données du formulaire si soumis
$data['thematiques'] = json_decode($data['thematiques']);
$data['header_img'] = json_decode($data['header_img']);
$data['other_file'] = json_decode($data['other_file']);
$data['adresse'] = json_decode($data['adresse']);
// convertie les ID en INT
if (isset($data['territory_select']) && null !== $data['territory_select']) {
$data['territory_select'] = array_map(
function ($value) {
return (int)$value;
},
json_decode($data['territory_select'])
);
}
unset($data['file']);
unset($data['departement_type']);
unset($data['territory_type']);
unset($data['thematiques_type']);
unset($data['adresse_input']);
unset($data['long']);
unset($data['lat']);
$this->bus->dispatch(new NewExperience(json_encode(["data" => $this->secureTextData($data)])));
return new Response($this->twig->render("blocks/send-experience-form.html.twig", [
'form' => $form->createView(), // Passez la vue du formulaire
]));
}
return new Response($this->twig->render("blocks/send-experience-form.html.twig", [
'form' => $form->createView(), // Passez la vue du formulaire
'data' => $data, // Données soumises (optionnel)
]));
}
private function secureTextData(array $datas): array
{
foreach ($datas as $key => $data) {
if (is_string($data) && strlen($data) > 0) {
$datas[$key] = htmlspecialchars(strip_tags($data), ENT_NOQUOTES | ENT_HTML401, 'UTF-8');
}
}
return $datas;
}
}