<?php /* * kohinos_cooperatic * Copyright (C) 2019-2020 ADML63 * Copyright (C) 2020- Cooperatic * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ namespace App\Admin; use App\Admin\UserAdmin; use App\Entity\Adherent; use App\Entity\ContactPrestataire; use App\Entity\Cotisation; use App\Entity\CotisationPrestataire; use App\Entity\EtatPrestataire; use App\Entity\Geoloc; use App\Entity\GlobalParameter; use App\Entity\Groupe; use App\Entity\Prestataire; use App\Entity\Rubrique; use App\Entity\User; use App\Entity\Usergroup; use App\Form\Type\ContactEntityFormType; use App\Form\Type\CotisationFormType; use App\Form\Type\GeolocPrestataireFormType; use App\Form\Type\UserWithPswdFormType; use App\Form\Type\UserInfosFormType; use App\Util\WordpressUtil; use Doctrine\ORM\Query; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; use FOS\CKEditorBundle\Form\Type\CKEditorType; use FOS\UserBundle\Model\UserManagerInterface; use Knp\Menu\ItemInterface as MenuItemInterface; use Sonata\AdminBundle\Admin\AbstractAdmin; use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Route\RouteCollection; use Sonata\AdminBundle\Show\ShowMapper; use Sonata\DoctrineORMAdminBundle\Filter\CallbackFilter; use Sonata\MediaBundle\Form\Type\MediaType; use Sonata\MediaBundle\Provider\Pool; use Sonata\MediaBundle\Provider\MediaProviderInterface; use Sonata\AdminBundle\Object\Metadata; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; use Symfony\Component\Form\Extension\Core\Type\PercentType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\UrlType; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Security; /** * Administration des prestataires * * KOHINOS : Outil de gestion de Monnaie Locale Complémentaire * @author Julien Jorry <julien.jorry@gmail.com> */ class PrestataireAdmin extends AbstractAdmin { protected $baseRouteName = 'prestataire'; protected $baseRoutePattern = 'prestataire'; protected $security; protected $pool; protected $wp; protected $datagridValues = [ // reverse order (default = 'ASC') '_sort_order' => 'DESC', // name of the ordered field (default = the model's id field, if any) '_sort_by' => 'raison', // '_page' => 1, // '_per_page' => 32 ]; public function setSecurity(Security $security) { $this->security = $security; } public function setPool(Pool $pool) { $this->pool = $pool; } public function setWordpressUtil(WordpressUtil $wp) { $this->wp = $wp; } public function configure() { parent::configure(); } /** * {@inheritdoc} */ public function createQuery($context = 'list') { $user = $this->security->getUser(); $query = parent::createQuery($context); if ($user->hasRole('ROLE_GESTION_GROUPE') || $user->hasRole('ROLE_CONTACT') || $user->isGranted('ROLE_TRESORIER')) { if (empty($this->getRequest()->getSession()->get('_groupegere'))) { $query->andWhere('false = true'); } else { $query ->andWhere($query->getRootAliases()[0].'.groupe = :group') ->setParameter('group', $this->getRequest()->getSession()->get('_groupegere')) ; } } return $query; } protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) { if (!$childAdmin && !in_array($action, ['edit', 'show'])) { return; } $user = $this->security->getUser(); $admin = $this->isChild() ? $this->getParent() : $this; $id = $admin->getRequest()->get('id'); $presta = $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(Prestataire::class)->findOneById($id); $users = $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(User::class)->findByPrestataire($id); if ($this->isGranted('EDIT') && !empty($users)) { // @TODO : si plusieurs utilisateurs, mettre un lien par user pour modifier celui-ci ? // $menu->addChild("Modifier l'utilisateur", [ // 'uri' => $this->getConfigurationPool ()->getContainer()->get('router')->generate('admin_app_user_edit', ['id' => $user->getId()], UrlGeneratorInterface::ABSOLUTE_URL) // ]); } // Le prestataire "Monnaie Locale" représentant l'asso recevant les cotisations n'a pas de cotisations lui même ! if (!$presta->isMlc()) { $menu->addChild("Ajouter une cotisation", [ 'uri' => $this->getConfigurationPool()->getContainer()->get('router')->generate('cotisation_prestataire_create', ['expediteur' => $id], UrlGeneratorInterface::ABSOLUTE_URL) ]); $menu->addChild("Voir les cotisations", [ 'uri' => $this->getConfigurationPool()->getContainer()->get('router')->generate('cotisation_prestataire_list', ['filter' => array('expediteur' => array('value' => $id))], UrlGeneratorInterface::ABSOLUTE_URL) ]); } } /** * {@inheritdoc} */ protected function configureFormFields(FormMapper $formMapper): void { // Initialize prestataire $presta = $this->getSubject(); // $user = $this->security->getUser(); $now = new \DateTime(); $cotisation = null; // get the current Image instance $imageHelp = null; if (!empty($presta) && !empty($presta->getMedia())) { $image = $presta->getMedia(); if ($image && ($webPath = $image->getWebPath())) { // get the container so the full path to the image can be set $container = $this->getConfigurationPool()->getContainer(); $fullPath = $container->get('request_stack')->getCurrentRequest()->getBasePath().'/'.$webPath; // add a 'help' option containing the preview's img tag $imageHelp = '<img src="'.$fullPath.'" class="admin-preview" />'; } } $formMapper ->tab('Prestataire') ->with('Prestataire', ['class' => 'col-md-6']) ->add('typeprestataire', null, array( 'label' => 'Type :', 'required' => true, 'expanded' => true )) ->add('groupe', ChoiceType::class, array( 'required' => true, 'label' => 'Groupe local :', 'choices' => $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(Groupe::class)->findAll(), 'choice_label' => 'name', 'placeholder' => 'Choisir un groupe', )) ->add('raison', TextType::class, array( 'label' => 'Raison :', 'required' => true )) ->add('statut', TextType::class, array( 'label' => 'Statut :', 'required' => false )) ->add('siret', TextType::class, array( 'label' => 'SIRET :', 'required' => true )) ->add('iban', TextType::class, array( 'label' => 'IBAN :', 'required' => false )) ->add('responsable', TextType::class, array( 'label' => 'Responsable :', 'required' => false )) ->add('metier', TextType::class, array( 'label' => 'Métier responsable :', 'required' => false )) ->add('horaires', TextType::class, array( 'label' => 'Horaires', 'required' => false )) ->add('web', UrlType::class, array( 'label' => 'Site Web', 'required' => false )) ->add('description', CKEditorType::class, array( 'label' => 'Description', 'required' => false )) ->add('tauxreconversion', PercentType::class, array( 'label' => 'Taux de reconversion', 'required' => false, 'type' => 'integer', 'help' => 'Si la valeur est nulle, par défaut le taux de reconversion sera de '.$this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(GlobalParameter::class)->val(GlobalParameter::RECONVERSION_PRESTATAIRE).' %' )) ->add('enabled', CheckboxType::class, array( 'label' => 'Activé ?' )) ->end() ->with('Contact(s)', ['class' => 'col-md-6']) ->add('contacts', CollectionType::class, array( 'entry_type' => ContactEntityFormType::class, 'entry_options' => array('label' => false, 'data_class' => ContactPrestataire::class), 'allow_add' => true, 'allow_delete' => true, 'by_reference' => false, 'label' => false )) ->end() ->with('Adresse(s)', ['class' => 'col-md-6']) ->add('geolocs', CollectionType::class, array( 'entry_type' => GeolocPrestataireFormType::class, 'entry_options' => array('label' => false), 'allow_add' => true, 'allow_delete' => true, 'by_reference' => false, 'label' => false )) ->end() ->with('Classification', ['class' => 'col-md-6']) ->add('etats', EntityType::class, array( 'class' => EtatPrestataire::class, 'multiple' => true, 'required' => false, 'by_reference' => false, 'choices' => $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(EtatPrestataire::class)->findBy(array('enabled' => true)), 'label' => 'Tags' )) ->add('rubriques', EntityType::class, array( 'class' => Rubrique::class, 'multiple' => true, 'required' => false, 'by_reference' => false, 'choices' => $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(Rubrique::class)->findBy(array('enabled' => true)), 'label' => 'Rubriques' )) ->end() ->with('Image', ['class' => 'col-md-6']) ->add('media', MediaType::class, array( 'provider' => 'sonata.media.provider.image', 'context' => 'prestataire', 'help' => $imageHelp, 'required' => false )) ->end() ->with('Gestionnaire(s)', ['class' => 'col-md-6']) ->add('users', EntityType::class, array( // 'mapped' => false, 'class' => User::class, 'multiple' => true, 'required' => false, 'label' => 'Associer à un(des) utilisateur(s) existant :', 'choices' => $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(User::class)->findAll(), 'choice_label' => 'username', 'placeholder' => 'Choisir un utilisateur', )) ->add('newusers', CollectionType::class, array( 'mapped' => false, 'required' => false, 'label' => 'Nouvel Utilisateur/Adhérent', 'entry_type' => UserWithPswdFormType::class, 'entry_options' => array( 'required' => false, 'label' => false), 'by_reference' => false, 'allow_add' => true, 'allow_delete' => true )) ->end() ->with('Caissier(s)', ['class' => 'col-md-6']) ->add('caissiers', EntityType::class, [ // 'mapped' => false, 'class' => User::class, 'multiple' => true, 'required' => false, 'label' => 'Associer à un(des) utilisateur(s) existant :', 'choices' => $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(User::class)->findAll(), 'choice_label' => 'username', 'placeholder' => 'Choisir un utilisateur', ]) ->add('newcaissiers', CollectionType::class, [ 'mapped' => false, 'required' => false, 'label' => 'Nouvel Utilisateur/Adhérent', 'entry_type' => UserWithPswdFormType::class, 'entry_options' => [ 'required' => false, 'label' => false, ], 'by_reference' => false, 'allow_add' => true, 'allow_delete' => true, ]) ->end() ->end() ; // @TODO : add tags model transformer if add new from text // ->get('etats') // ->addModelTransformer(new CallbackTransformer( // function ($tagsAsArray) { // // transform the array to a string // return $tagsAsArray; // }, // function ($tagsAsString) { // // transform the string back to an array // return explode(', ', $tagsAsString); // } // )) $em = $this->getConfigurationPool()->getContainer()->get('doctrine')->getManager(); $formMapper->getFormBuilder()->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($em) { $prestataire = $event->getData(); $users = null; /* Permet d'ajouter le nouvel utilisateur créé aux gestionnaires (newusers) et aux caissiers (newcaissiers) du presta (On crée un compte adhérent en même temps que le prestataire) */ if (null != $event->getForm()->get('users')->getData()) { $users = $event->getForm()->get('users')->getData(); $this->addUsersOrCaissers($users, $prestataire); } if (null != $event->getForm()->get('caissiers')->getData()) { $caissiers = $event->getForm()->get('caissiers')->getData(); $this->addUsersOrCaissers($caissiers, $prestataire, 'ROLE_CAISSIER_PRESTATAIRE'); } if (null != $event->getForm()->get('newusers')->getData()) { $newusers = $event->getForm()->get('newusers')->getData(); $collection = new ArrayCollection($newusers); $return = $this->addUsersOrCaissers($collection, $prestataire); if (false === $return) { $event->getForm()->get('newusers')->addError(new FormError('Gestionnaires : Courriel(s) déjà utilisé : ' . implode(', ', $return) . '!')); } } if (null != $event->getForm()->get('newcaissiers')->getData()) { $newcaissiers = $event->getForm()->get('newcaissiers')->getData(); $collection = new ArrayCollection($newcaissiers); $return = $this->addUsersOrCaissers($collection, $prestataire, 'ROLE_CAISSIER_PRESTATAIRE'); if (count($return) > 0) { $event->getForm()->get('newcaissiers')->addError(new FormError('Caissiers : Courriel(s) déjà utilisé : ' . implode(', ', $return) . '!')); } } }); parent::configureFormFields($formMapper); } private function addUsersOrCaissers(Collection $users, Prestataire $prestataire, string $role = 'ROLE_PRESTATAIRE') { $return = []; $em = $this->getConfigurationPool()->getContainer()->get('doctrine')->getManager(); foreach ($users as $user) { if (null == $user->getId()) { $emailExist = $em->getRepository(User::class)->findBy(['email' => $user->getEmail()]); if (count($emailExist) > 0) { $return[] = $emailExist; break; } $user->setUsername($user->getEmail()); $adh = new Adherent(); $user->setAdherent($adh); $groupeAdh = $em->getRepository(Usergroup::class)->findOneByName('Adherent'); $user->addPossiblegroup($groupeAdh); } if ('ROLE_PRESTATAIRE' == $role) { $prestataire->addUser($user); $group = $em->getRepository(Usergroup::class)->findOneByName('Prestataire'); } elseif ('ROLE_CAISSIER_PRESTATAIRE' == $role) { $prestataire->addCaissier($user); $group = $em->getRepository(Usergroup::class)->findOneByName('Caissier de Prestataire'); } $user->addPossiblegroup($group); $this->userManager->updateUser($user); $em->persist($user); $em->persist($prestataire); } $em->flush(); // TODO: remove goup (and possible groups?) when user is unselected from caissiers list (get caissiers before update & compare) return $return; } /** * {@inheritdoc} */ protected function configureDatagridFilters(DatagridMapper $datagridMapper): void { parent::configureDatagridFilters($datagridMapper); $datagridMapper ->add('full_text', CallbackFilter::class, [ 'callback' => [$this, 'getFullTextFilter'], 'field_type' => TextType::class, 'label' => "Recherche par nom", 'show_filter' => true, 'advanced_filter' => false ]) ->add('groupe', null, [ 'label' => "Groupe", 'show_filter' => true, 'advanced_filter' => false ]) ->add('etats', null, [ 'label' => "Tags", 'show_filter' => true, 'advanced_filter' => false ], null, ['expanded' => true, 'multiple' => true ]) ->add('typeprestataire', null, [ 'label' => "Type", 'advanced_filter' => false ]) ; } public function getFullTextFilter($queryBuilder, $alias, $field, $value) { if (!$value['value']) { return; } // Use `andWhere` instead of `where` to prevent overriding existing `where` conditions $queryBuilder->andWhere($queryBuilder->expr()->orX( $queryBuilder->expr()->like($alias.'.raison', $queryBuilder->expr()->literal('%' . $value['value'] . '%')), $queryBuilder->expr()->like($alias.'.statut', $queryBuilder->expr()->literal('%' . $value['value'] . '%')), $queryBuilder->expr()->like($alias.'.description', $queryBuilder->expr()->literal('%' . $value['value'] . '%')) )); return true; } public function getTemplate($name) { if ($name == 'edit') { return 'presta/block/base_edit_prestataires.html.twig'; } return parent::getTemplate($name); } /** * @param UserManagerInterface $userManager */ public function setUserManager(UserManagerInterface $userManager): void { $this->userManager = $userManager; } /** * @return UserManagerInterface */ public function getUserManager() { return $this->userManager; } public function getRouteShowOnFront($object) { return $this->routeGenerator->generate('show_prestataire', array('slug' => $object->getSlug())); } protected function configureListFields(ListMapper $listMapper): void { unset($this->listModes['mosaic']); $user = $this->security->getUser(); $actions = ['edit' => ['link_parameters' => ['full' => true]]]; if (false === $this->wp->isUsed()) { $actions['show'] = ['template' => '@SonataAdmin/CRUD/list__action_showonfront.html.twig']; } elseif ($this->wp->canSync()) { $actions['sync-wp'] = []; } $listMapper ->addIdentifier('raison') ->add('groupe', null, array( 'label' => 'Groupe', 'sortable' => true, 'sort_field_mapping' => array('fieldName' => 'name'), 'sort_parent_association_mappings' => array(array('fieldName' => 'groupe')) )) // ->addIdentifier('users', null, array('label' => 'Gestionnaires')) ->add('rubriques', null) ->add('etats', null, array( 'label' => 'Tags', // 'editable' => true, // 'class' => EtatPrestataire::class, // 'multiple' => true, // // 'required' => false, // // 'by_reference' => false, // 'choices' => $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository(EtatPrestataire::class)->findBy(array('enabled' => true)) )) ->add('enabled', null, array( 'label' => 'Activé', 'editable' => true )) ->add('updatedAt', null, array( 'label' => 'Mis à jour' )) // You may also specify the actions you want to be displayed in the list ->add('_action', null, [ 'actions' => $actions ]) ; } protected function configureRoutes(RouteCollection $collection) { $collection->remove('delete'); if ($this->wp->canSync()) { $collection->add('sync-wp', $this->getRouterIdParameter().'/sync-wp'); $collection->add('sync-all-wp'); } } public function configureActionButtons($action, $object = null) { $list = parent::configureActionButtons($action, $object); if ($this->wp->canSync()) { $list['sync-all-wp']['template'] = '@SonataAdmin/CRUD/sync-all-wp_button.html.twig'; } return $list; } public function getObjectMetadata($object) { if ($object->getMedia() != null) { $provider = $this->pool->getProvider($object->getMedia()->getProviderName()); $url = $provider->generatePublicUrl( $object->getMedia(), $provider->getFormatName($object->getMedia(), MediaProviderInterface::FORMAT_ADMIN) ); return new Metadata($object->getRaison(), strip_tags($object->getDescription()), $url); } return new Metadata($object->getRaison(), strip_tags($object->getDescription())); } public function configureBatchActions($actions) { unset($actions['delete']); if ($this->wp->canSync()) { $actions['sync-wp'] = [ 'ask_confirmation' => true, 'label' => 'batch_sync-wp', 'translation_domain' => 'SonataAdminBundle', ]; } return $actions; } /* @TODO : définir champ à exporter */ public function getExportFields() { return [ 'Id' => 'id', 'Raison' => 'raison', 'Groupe' => 'groupe.name', 'E-compte' => 'ecompte', 'Rubriques' => 'rubriquesString', 'Gestionnaires' => 'usersString', 'Tags' => 'etatsString', 'Activé ? (1=oui)' => 'enabled' ]; } /** * Synchronize with Wordpress here (rather than through Doctrine ORM events). */ public function postPersist($presta) { if ($this->wp->runSync($presta->getId())) { $this->getRequest()->getSession()->getFlashBag()->add( 'sonata_flash_success', 'Synchronisation vers Wordpress effectuée<br>' ); } } public function postUpdate($presta) { $this->postPersist($presta); } }