<?php
declare(strict_types=1);
namespace App\Admin;
use App\Entity\Lot;
use App\Admin\LotAdmin;
use App\Entity\Documents;
use App\Form\BlockType;
use App\Form\ImageType;
use App\Entity\Programme;
use App\Form\FichierType;
use App\Security\ProgrammeVoter;
use App\Service\ImagePdf;
use App\Entity\PieceJointe;
use App\Entity\Utilisateur;
use App\Form\VilleAutocompleteType;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Form\FormMapper;
use App\Repository\PieceJointeRepository;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Route\RouteCollectionInterface;
use Symfony\Component\Validator\Constraints\File;
use Sonata\AdminBundle\Datagrid\DatagridInterface;
use Sonata\AdminBundle\FieldDescription\FieldDescriptionInterface;
use Sonata\Form\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use function Sodium\add;
final class ProgrammeAdmin extends AbstractAdmin
{
protected $datagridValues = [
'_page' => 1,
'_sort_order' => 'DESC',
'_sort_by' => 'id',
'_per_page' => 250,
];
private $tokenStorage;
private $originalLivraison;
public function setSecurityTokenStorage($tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function getUser()
{
$token = $this->tokenStorage->getToken();
if (null !== $token) {
return $token->getUser();
}
return null;
}
protected function configureDefaultSortValues(array &$sortValues): void
{
$sortValues[DatagridInterface::PAGE] = 1;
$sortValues[DatagridInterface::SORT_ORDER] = 'DESC';
$sortValues[DatagridInterface::SORT_BY] = 'id';
$sortValues[DatagridInterface::PER_PAGE] = 250;
}
protected function configureRoutes(RouteCollectionInterface $collection): void
{
$collection->add('requete_supp', $this->getRouterIdParameter().'/requete_supp');
$collection->remove('delete');
}
public function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
{
$queryBuilder = $query->getQueryBuilder();
$user = $this->getUser();
if (!$user instanceof Utilisateur || !in_array('ROLE_MASTER', $user->getRoles())) {
if ($user->getEntreprise() !== null) {
$queryBuilder
->innerJoin('o.entreprise', 'e')
->andWhere('e.id = :entrepriseId')
->setParameter('entrepriseId', $user->getEntreprise()->getId());
}
if (in_array('ROLE_PARTENAIRE', $user->getRoles())) {
if (!$user->getallProgrammes()) {
$programmes = $user->getPrescripteurProgrammes();
if($programmes->count()) {
$queryBuilder->andWhere('o.id IN (' . implode(',', array_map(function($p){ return $p->getId(); }, $programmes->toArray())) . ')');
} else {
$queryBuilder->andWhere('o.id IN (0)');
}
}
}
$query->andWhere('o.demandeSuppression IS NULL');
}
return $query;
}
protected function configureBatchActions(array $actions): array
{
$actions['requete_supp'] = [
'label' => 'Requête de suppression',
'ask_confirmation' => true,
'confirmation_message' => 'Êtes-vous sûr de vouloir supprimer ces éléments ?',
];
return $actions;
}
protected function configureTabMenu(MenuItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
{
if (!$childAdmin && !in_array($action, ['edit', 'show'])) {
return;
}
$admin = $this->isChild() ? $this->getParent() : $this;
$id = $admin->getRequest()->get('id');
$user = $this->getUser();
if ($this->isGranted('EDIT') && !($childAdmin && in_array($action, ['edit', 'show', 'create'])) && $action != 'edit') {
if ($user && $user->peutModifierContenu()) {
$menu->addChild('Modifier le programme', $admin->generateMenuUrl('edit', ['id' => $id]));
} else {
$menu->addChild('Voir le programme', $admin->generateMenuUrl('edit', ['id' => $id]));
}
} elseif ($this->isGranted('EDIT') && !($childAdmin && $action == 'edit')) {
$url = $admin->getRouteGenerator()->generate('admin_app_programme_lot_list', [
'id' => $admin->getRequest()->get('id')
]);
$menu->addChild('Voir les lots du programme', [
'uri' => $url
]);
}
}
public function prePersist(object $programme): void
{
$files = $this->getRequest()->files->get($this->getForm()->getName());
if(isset($files['_image'])) $this->manageImage($files['_image'], $programme);
if(isset($files['_brochure'])) $this->manageBrochure($files['_brochure'], $programme);
if(isset($files['_plan_masse'])) $this->managePlanMasse($files['_plan_masse'], $programme);
$user = $this->getUser();
if ($user instanceof Utilisateur && $user->getEntreprise() !== null) {
$programme->setEntreprise($user->getEntreprise());
}
if(isset($files['pjs'])) $this->managePiecesJointes($files['pjs'], $programme);
if(isset($files['documents'])) $this->manageDocuments($files['documents'], $programme);
}
public function preUpdate(object $programme): void
{
// Stocker l'ancienne valeur de livraison avant la modification
$this->originalLivraison = $this->getOriginalEntityData($programme, 'livraison');
$files = $this->getRequest()->files->get($this->getForm()->getName());
$fields = $this->getRequest()->request->get($this->getForm()->getName());
if(isset($files['_image'])) $this->manageImage($files['_image'], $programme);
if(isset($files['_brochure'])) $this->manageBrochure($files['_brochure'], $programme);
if(isset($files['_plan_masse'])) $this->managePlanMasse($files['_plan_masse'], $programme);
if(isset($fields['_image']) && $fields['_image'] == '__delete__') $this->manageImage(null, $programme);
if(isset($fields['_brochure']) && $fields['_brochure'] == '__delete__') $this->manageBrochure(null, $programme);
if(isset($fields['_plan_masse']) && $fields['_plan_masse'] == '__delete__') $this->managePlanMasse(null, $programme);
$this->saveLotsCascade($programme);
$pjs = isset($files['pjs']) ? $files['pjs'] : [];
$this->managePiecesJointes($pjs, $programme);
if(isset($files['documents'])) $this->manageDocuments($files['documents'], $programme);
$this->manageHeritagePiecesJointes($programme);
}
/**
* Récupère la valeur originale d'un champ avant modification
*/
private function getOriginalEntityData($entity, $field)
{
$em = $this->getModelManager()->getEntityManager($entity);
$uow = $em->getUnitOfWork();
$originalData = $uow->getOriginalEntityData($entity);
return isset($originalData[$field]) ? $originalData[$field] : null;
}
private function manageHeritagePiecesJointes($programme) {
foreach ($programme->getLots() as $lot) {
foreach ($lot->getPjs() as $pj) {
$mere = $pj->getPieceJointeMere();
if ($mere) {
if ($programme->getPjs()->contains($mere)) {
$pj->setFilename($mere->getFilename());
$pj->setDescriptionCourte($mere->getDescriptionCourte());
} else {
$lot->removePj($pj);
}
}
}
$idsMeresPjs = [];
foreach ($lot->getPjs() as $pj) {
$mere = $pj->getPieceJointeMere();
if ($mere) {
$idsMeresPjs[] = $mere->getId();
}
}
foreach ($programme->getPjs() as $programmePj) {
if (!in_array($programmePj->getId(), $idsMeresPjs)) {
$fille = clone $programmePj;
$fille->setPieceJointeMere($programmePj);
$fille->setProgramme(null);
$fille->setOrdre(0);
$lot->addPj($fille);
}
}
}
}
private function manageDocuments($docs, $programme) {
$programmeDocs = $programme->getDocuments()->toArray();
foreach($docs as $n => $doc) {
if($doc['fichier'] && isset($programmeDocs[$n])) {
$fichier = $doc['fichier'];
$originalFilename = pathinfo($fichier->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $this->slugify($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $fichier->guessExtension();
try {
$fichier->move(Documents::SAVE_PATH, $newFilename);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$programme->getDocuments()[$n]->setFilename($newFilename);
}
}
}
private function managePiecesJointes($pjs, $programme) {
$programmePjs = $programme->getPjs()->toArray();
foreach($pjs as $n => $pj) {
if($pj['fichier'] && isset($programmePjs[$n])) {
$fichier = $pj['fichier'];
$originalFilename = pathinfo($fichier->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $this->slugify($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $fichier->guessExtension();
try {
$fichier->move(PieceJointe::SAVE_PATH, $newFilename);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
if(strpos($newFilename, '.pdf') !== false) {
$newFilename = ImagePdf::convertToImage(PieceJointe::SAVE_PATH.$newFilename);
}
$programme->getPjs()[$n]->setFilename($newFilename);
}
}
}
private function manageImage($imageFile, $programme)
{
if($imageFile == null) {
$programme->setImage(null);
} else {
$originalFilename = pathinfo($imageFile->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $this->slugify($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $imageFile->guessExtension();
try {
$imageFile->move(Lot::IMAGE_SAVE_PATH, $newFilename);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$programme->setImage($newFilename);
}
}
private function manageBrochure($brochureFile, $programme)
{
if($brochureFile == null) {
$programme->setBrochure(null);
} else {
$originalFilename = pathinfo($brochureFile->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $this->slugify($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $brochureFile->guessExtension();
try {
$brochureFile->move(Lot::BROCHURE_SAVE_PATH, $newFilename);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$programme->setBrochure($newFilename);
}
}
private function managePlanMasse($planMasseFile, $programme)
{
if ($planMasseFile == null) {
$programme->setPlanMasse(null);
} else {
$originalFilename = pathinfo($planMasseFile->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $this->slugify($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $planMasseFile->guessExtension();
try {
$planMasseFile->move(Lot::PLAN_MASSE_SAVE_PATH, $newFilename);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
if (strpos($newFilename, '.pdf') !== false) {
$newFilename = ImagePdf::convertToImage(Lot::PLAN_MASSE_SAVE_PATH . $newFilename);
}
$programme->setPlanMasse($newFilename);
}
}
private function saveLotsCascade($programme)
{
foreach($programme->getLots() as $lot) {
$lot->setAdresse($programme->getAdresse());
$lot->setVille($programme->getVille());
$lot->setCodePostal($programme->getCodePostal());
$lot->setLatitude($programme->getLatitude());
$lot->setLongitude($programme->getLongitude());
$lotLivraison = $lot->getLivraison();
$originalLivraison = $this->originalLivraison;
$lotLivraisonUpper = $lotLivraison ? strtoupper(trim($lotLivraison)) : null;
$originalLivraisonUpper = $originalLivraison ? strtoupper(trim($originalLivraison)) : null;
if ($lotLivraisonUpper === $originalLivraisonUpper ||
$lotLivraison === null ||
trim($lotLivraison) === '') {
$lot->setLivraison($programme->getLivraison());
}
$lot->setProgramme($programme->getNom());
$lot->setImage($programme->getImage());
$lot->setBrochure($programme->getBrochure());
$lot->setPlanMasse($programme->getPlanMasse());
$lot->setPromoteur($programme->getPromoteur());
}
}
protected function configureDatagridFilters(DatagridMapper $filter): void
{
$filter
->add('adresse')
->add('ville', null, [
'show_filter' => true
])
->add('codePostal')
->add('dateLivraison')
->add('latitude')
->add('longitude')
->add('livraison')
->add('promoteur')
->add('nom', null, [
'show_filter' => true,
'label' => 'Nom du programme'
])
->add('image')
->add('brochure')
->add('planMasse')
;
}
protected function configureListFields(ListMapper $list): void
{
$list
->addIdentifier('nom', FieldDescriptionInterface::TYPE_STRING, [
'template' => 'sonata/list_fields/list_programme_child_identifier.html.twig'
])
->add('lots', Lot::class, [
'template' => 'sonata/list_fields/list_programme_lots.html.twig'
])
->addIdentifier('promoteur', FieldDescriptionInterface::TYPE_STRING, [
'template' => 'sonata/list_fields/list_programme_child_identifier.html.twig'
])
->addIdentifier('livraison', FieldDescriptionInterface::TYPE_STRING, [
'template' => 'sonata/list_fields/list_programme_child_identifier.html.twig'
])
->addIdentifier('ville', FieldDescriptionInterface::TYPE_STRING, [
'template' => 'sonata/list_fields/list_programme_child_identifier.html.twig'
])
->addIdentifier('codePostal', FieldDescriptionInterface::TYPE_STRING, [
'template' => 'sonata/list_fields/list_programme_child_identifier.html.twig'
])
->add('_Suppression', 'actions', [
'actions' => [
'requete_supp' => [
'template' => 'sonata/Button/list__action_requete_suppression.html.twig',
],
],
]);
}
protected function configureFormFields(FormMapper $form): void
{
$user = $this->getUser();
$isSuperAdmin = $user->peutModifierContenu();
$canModifySensitiveSections = in_array('ROLE_SUPERADMIN', $user->getRoles()) ||
in_array('ROLE_ADMIN', $user->getRoles()) ||
in_array('ROLE_DIRECTEUR', $user->getRoles()) ||
in_array('ROLE_SAISIE', $user->getRoles());
$form
->tab('Programme')
->with('Description du programme', ['box_class' => 'inline-fields', 'class' => 'col-md-6' . ($isSuperAdmin ? '' : ' unauthorized-docs')])
->add('nom', null, [
'required' => true,
'attr' => [
'placeholder' => 'Ex: Garden park'
]
])
// ->add('type', null, [
// 'required' => true,
// 'attr' => [
// 'style' => 'width:60%',
// 'placeholder' => 'Ex: T4'
// ]
// ])
->add('promoteur', null, [
'required' => false,
'attr' => [
'placeholder' => 'Ex: Vinci'
]
])
->add('livraison', null, [
'required' => false,
'attr' => [
'placeholder' => 'Ex: 4T '.date('Y')
]
])
->add('visuLivraison', CheckboxType::class, [
'label' => 'Visibilité Livraison',
'required' => false,
'help' => 'Cocher pour afficher la livraison sur l\'espace public',
])
->add('adresse')
->add('ville', VilleAutocompleteType::class, [
'code_postal_field' => 'codePostal'
])
->add('codePostal', null, [
'attr' => [
'style' => 'width:50%;margin-left:auto;'
]
])
->add('_geocodeAdresse', BlockType::class, [
'mapped' => false,
'template' => 'sonata/Block/geocode_adresse.html.twig',
'params' => [
'fields' => ['adresse', 'ville', 'codePostal']
]
])
->add('latitude')
->add('longitude')
->add('typePlan', ChoiceType::class, [
'choices' => array_flip(Programme::TYPES_PLANS),
'multiple' => false,
'expanded' => false,
'label' => 'Type de plan'
])
->add('zoomMap', ChoiceType::class, [
'choices' => array_flip(Programme::ZOOMS_MAP),
'multiple' => false,
'expanded' => false,
'label' => 'Zoom map'
])
->add('noteGlobale', TextareaType::class, [
'label' => 'Notes Globales',
'required' => false,
'attr' => ['rows' => '2']
])
->end()
->with('Image de couverture', ['box_class' => '', 'class' => 'col-md-6 file' . ($isSuperAdmin ? '' : ' unauthorized-docs')])
->add('_image', ImageType::class, [// unmapped means that this field is not associated to any entity property
'mapped' => false,
'save_path' => Lot::IMAGE_SAVE_PATH,
'attr' => [
'accept' => 'image/jpeg,image/gif,image/png',
],
'label' => 'Fichier pour l\'image de couverture',
'help' => 'Formats d\'image acceptés : JPG, PNG et GIF',
// make it optional so you don't have to re-upload the PDF file
// every time you edit the Product details
'required' => false,
// unmapped fields can't define their validation using annotations
// in the associated entity, so you can use the PHP constraint classes
'constraints' => [
new File([
'maxSize' => '32768k',
'mimeTypes' => [
'image/jpeg',
'image/png',
'image/gif',
],
'mimeTypesMessage' => 'Vous devez choisir un fichier au format JPG, PNG ou GIF.',
])
]
])
->end()
->with('Brochure', ['box_class' => '', 'class' => 'col-md-6 file' . ($isSuperAdmin ? '' : ' unauthorized-docs')])
->add('_brochure', FichierType::class, [// unmapped means that this field is not associated to any entity property
'mapped' => false,
'save_path' => Lot::BROCHURE_SAVE_PATH,
'attr' => [
'accept' => 'application/pdf,application/x-pdf,image/jpeg,image/gif,image/png',
],
'label' => 'Fichier pour la brochure',
'help' => 'Formats de fichier acceptés : PDF, JPG, PNG et GIF',
// make it optional so you don't have to re-upload the PDF file
// every time you edit the Product details
'required' => false,
// unmapped fields can't define their validation using annotations
// in the associated entity, so you can use the PHP constraint classes
'constraints' => [
new File([
'maxSize' => '32768k',
'mimeTypes' => [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'application/x-pdf',
],
'mimeTypesMessage' => 'Vous devez choisir un fichier au format PDF, JPG, PNG ou GIF.',
])
]
])
->add('libelleBrochure', null, [
'help' => 'Intitulé du bouton de téléchargement de l\'espace public (Laissez vide pour afficher l\'intitulé par défaut "Brochure")',
'attr' => [
'class' => 'little-input-brochure',
'placeholder' => 'Ex: Télécharger la brochure'
],
'label' => 'Libellé brochure'
])
->end()
->with('Plan Masse', ['box_class' => '', 'class' => 'col-md-6 file' . ($isSuperAdmin ? '' : ' unauthorized-docs')])
->add('_plan_masse', FichierType::class, [// unmapped means that this field is not associated to any entity property
'mapped' => false,
'save_path' => Lot::PLAN_MASSE_SAVE_PATH,
'attr' => [
'accept' => 'application/pdf,application/x-pdf,image/jpeg,image/gif,image/png',
],
'label' => 'Fichier pour le plan masse',
'help' => 'Formats de fichier acceptés : PDF, JPG, PNG et GIF',
// make it optional so you don't have to re-upload the PDF file
// every time you edit the Product details
'required' => false,
// unmapped fields can't define their validation using annotations
// in the associated entity, so you can use the PHP constraint classes
'constraints' => [
new File([
'maxSize' => '32768k',
'mimeTypes' => [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'application/x-pdf',
],
'mimeTypesMessage' => 'Vous devez choisir un fichier au format PDF, JPG, PNG ou GIF.',
])
]
])
->end()
->end()
->tab('Vivier slides')
->with('Vivier slides', ['box_class' => 'no-header', 'class' => 'col-md-12 programme-slides' . ($canModifySensitiveSections ? '' : ' unauthorized-docs')])
->add('pjs', CollectionType::class, [
'label_attr' => [
'class' => 'hide'
],
'by_reference' => false,
'type_options' => [
'delete' => true,
'delete_options' => [
'type' => ButtonType::class,
'type_options' => [
'label' => 'Retirer',
'attr' => [
'class' => 'btn btn-danger btn-delete-elements',
'onclick' => "
if(confirm('Voulez-vous vraiment effacer cette ligne ? N\'oubliez pas d\'enregistrer vos modifications.')) {
$(this).closest('tr').remove();
}
"
]
]
]
]
], [
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'ordre'
])
->end()
->end()
->tab('Documents')
->with('Documents', ['box_class' => 'no-header', 'class' => 'col-md-12' . ($canModifySensitiveSections ? '' : ' unauthorized-docs')])
->add('documents', CollectionType::class, [
'label_attr' => [
'class' => 'hide'
],
'by_reference' => false,
'type_options' => [
'delete' => true,
'delete_options' => [
'type' => ButtonType::class,
'type_options' => [
'label' => 'Retirer',
'attr' => [
'class' => 'btn btn-danger btn-delete-elements',
'onclick' => "
if(confirm('Voulez-vous vraiment effacer cette ligne ? N\'oubliez pas d\'enregistrer vos modifications.')) {
$(this).closest('tr').remove();
}
"
]
]
]
]
], [
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'ordre'
])
->end()
->end()
;
}
}