Commit 560f9676 by Damien Moulard

WIP display informative modale to user, customable by admin

parent c715bd64
......@@ -339,6 +339,14 @@ sonata_admin:
icon: '<i class="fa fa-shopping-basket"></i>'
items:
- admin.productsFamily
sonata.admin.informationPopup:
keep_open: false
on_top: true
label: "Popup d'information"
label_catalogue: SonataAdminBundle
icon: '<i class="fa fa-exclamation-circle"></i>'
items:
- admin.informationPopup
sonata.admin.group.globalparameter:
keep_open: false
on_top: true
......
......@@ -680,6 +680,16 @@ services:
label: "Familles de Produits"
public: true
admin.informationPopup:
class: App\Admin\InformationPopupAdmin
arguments: [~, App\Entity\InformationPopup, ~]
tags:
- name: sonata.admin
manager_type: orm
group: "Popup d'information"
label: "Popup d'information"
public: true
sonata.media.provider.csv:
class: App\Admin\ImportProvider
tags:
......
<?php
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use FOS\CKEditorBundle\Form\Type\CKEditorType;
/**
* Administration des popups d'information.
* Ces popup sont à destination des utilisateurs (adhérents uniquement pour l'instant),
* et s'ouvrent automatiquement au lancement de l'application,
* tant que l'utilisateur n'a pas cliqué sur le bouton de confirmation
*
* TODO: make sure only one popup is active at a given time (use post save hook)
*
* KOHINOS : Outil de gestion de Monnaie Locale Complémentaire
*/
class InformationPopupAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $form): void
{
$form
->add('title', TextType::class, [
'label' => 'Titre',
'required' => true,
])
->add('content', CKEditorType::class, [
'label' => 'Contenu',
'required' => true,
])
->add('validationButtonText', TextType::class, [
'label' => 'Texte du bouton de validation',
'required' => true,
'attr' => [
'placeholder' => 'Par exemple : "J\'ai compris", "Ok"...',
]
])
->add('closingButtonText', TextType::class, [
'label' => 'Texte du bouton de fermeture',
'required' => true,
'attr' => [
'placeholder' => 'Par exemple : "Annuler", "Fermer pour l\'instant"...',
]
])
->add('enabled', null, [
'label' => 'Activé ?',
'help' => 'Une seule popup d\'information peut être active à la fois. Activer cette popup désactivera toutes les autres.'
]);
}
protected function configureDatagridFilters(DatagridMapper $datagrid): void
{
$datagrid
->add('title', null, [
'label' => 'Titre'
]);
}
protected function configureListFields(ListMapper $list): void
{
$list
->addIdentifier('title', null, [
'label' => 'Titre'
])
->add('enabled', null, [
'label' => 'Activé ?',
]);
}
}
\ No newline at end of file
......@@ -12,6 +12,8 @@ use App\Entity\Reconversion;
use App\Entity\TransactionPrestataireAdherent;
use App\Entity\User;
use App\Entity\TransactionAdherentPrestataire;
use App\Entity\InformationPopup;
use App\Entity\InformationPopupUser;
use App\Enum\MoyenEnum;
use App\Form\Type\CotiserFormType;
use App\Form\Type\DonAdherentFormType;
......@@ -561,4 +563,31 @@ class UserController extends AbstractController
return $this->render('@kohinos/tav/payment_done_page.html.twig', $templateData);
}
/**
* Register that the connected user (currently only adherents) has clicked on the popup validation button,
* So it's not shown to him/her again.
*
* @Route("/user/setUserValidatedInformationPopup", name="set_user_validated_information_popup")
* @IsGranted({"ROLE_ADHERENT"})
*/
public function setUserValidatedInformationPopupAction(Request $request)
{
$user = $this->security->getUser();
$activePopup = $this->em->getRepository(InformationPopup::class)->findOneBy(['enabled' => true]);
$activePopupUser = $this->em->getRepository(InformationPopupUser::class)->findOneBy(['informationPopup' => $activePopup, 'user' => $user]);
if (null === $activePopupUser) {
$activePopupUser = new InformationPopupUser();
$activePopupUser->setInformationPopup($activePopup);
$activePopupUser->setUser($user);
}
$activePopupUser->setHasValidated(true);
$this->em->persist($activePopupUser);
$this->em->flush();
return $this->redirectToRoute('index');
}
}
<?php
namespace App\Entity;
use App\Repository\InformationPopupRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Doctrine\UuidGenerator;
/**
* @ORM\Entity(repositoryClass=InformationPopupRepository::class)
*/
class InformationPopup
{
/**
* @var \Ramsey\Uuid\UuidInterface
*
* @ORM\Id
* @ORM\Column(type="uuid", unique=true)
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator(class=UuidGenerator::class)
*/
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $title;
/**
* @ORM\Column(type="text")
*/
private $content;
/**
* @ORM\Column(type="string", length=100)
*/
private $validationButtonText;
/**
* @ORM\Column(type="string", length=100)
*/
private $closingButtonText;
/**
* Don't use EnablableEntityTrait as it is true by default.
* Only one InformationPopup should be active at a time.
*
* @ORM\Column(type="boolean", options={"default": false})
*/
private $enabled = false;
/**
* @ORM\OneToMany(targetEntity=InformationPopupUser::class, mappedBy="informationPopup", orphanRemoval=true)
*/
private $informationPopupUsers;
public function __construct()
{
$this->informationPopupUsers = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getValidationButtonText(): ?string
{
return $this->validationButtonText;
}
public function setValidationButtonText(string $validationButtonText): self
{
$this->validationButtonText = $validationButtonText;
return $this;
}
public function getClosingButtonText(): ?string
{
return $this->closingButtonText;
}
public function setClosingButtonText(string $closingButtonText): self
{
$this->closingButtonText = $closingButtonText;
return $this;
}
public function getEnabled(): ?bool
{
return $this->enabled;
}
public function setEnabled(bool $enabled): self
{
$this->enabled = $enabled;
return $this;
}
/**
* @return Collection<int, InformationPopupUser>
*/
public function getInformationPopupUsers(): Collection
{
return $this->informationPopupUsers;
}
public function addInformationPopupUser(InformationPopupUser $informationPopupUser): self
{
if (!$this->informationPopupUsers->contains($informationPopupUser)) {
$this->informationPopupUsers[] = $informationPopupUser;
$informationPopupUser->setInformationPopup($this);
}
return $this;
}
public function removeInformationPopupUser(InformationPopupUser $informationPopupUser): self
{
if ($this->informationPopupUsers->removeElement($informationPopupUser)) {
// set the owning side to null (unless already changed)
if ($informationPopupUser->getInformationPopup() === $this) {
$informationPopupUser->setInformationPopup(null);
}
}
return $this;
}
}
<?php
namespace App\Entity;
use App\Repository\InformationPopupUserRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=InformationPopupUserRepository::class)
*/
class InformationPopupUser
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=InformationPopup::class, inversedBy="informationPopupUsers")
* @ORM\JoinColumn(nullable=false)
*/
private $informationPopup;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* @ORM\Column(type="boolean", options={"default": false})
*/
private $hasValidated = false;
public function getId(): ?int
{
return $this->id;
}
public function getInformationPopup(): ?InformationPopup
{
return $this->informationPopup;
}
public function setInformationPopup(?InformationPopup $informationPopup): self
{
$this->informationPopup = $informationPopup;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getHasValidated(): ?bool
{
return $this->hasValidated;
}
public function setHasValidated(bool $hasValidated): self
{
$this->hasValidated = $hasValidated;
return $this;
}
}
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250109101735 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE information_popup (id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', title VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, validation_button_text VARCHAR(100) NOT NULL, closing_button_text VARCHAR(100) NOT NULL, enabled TINYINT(1) DEFAULT \'0\' NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_general_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE information_popup_user (id INT AUTO_INCREMENT NOT NULL, information_popup_id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', user_id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', has_validated TINYINT(1) DEFAULT \'0\' NOT NULL, INDEX IDX_9AA7FF00FBB63E47 (information_popup_id), INDEX IDX_9AA7FF00A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_general_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE information_popup_user ADD CONSTRAINT FK_9AA7FF00FBB63E47 FOREIGN KEY (information_popup_id) REFERENCES information_popup (id)');
$this->addSql('ALTER TABLE information_popup_user ADD CONSTRAINT FK_9AA7FF00A76ED395 FOREIGN KEY (user_id) REFERENCES user (id)');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE information_popup_user DROP FOREIGN KEY FK_9AA7FF00FBB63E47');
$this->addSql('DROP TABLE information_popup');
$this->addSql('DROP TABLE information_popup_user');
}
}
<?php
namespace App\Repository;
use App\Entity\InformationPopup;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<InformationPopup>
*
* @method InformationPopup|null find($id, $lockMode = null, $lockVersion = null)
* @method InformationPopup|null findOneBy(array $criteria, array $orderBy = null)
* @method InformationPopup[] findAll()
* @method InformationPopup[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class InformationPopupRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, InformationPopup::class);
}
/**
* @throws ORMException
* @throws OptimisticLockException
*/
public function add(InformationPopup $entity, bool $flush = true): void
{
$this->_em->persist($entity);
if ($flush) {
$this->_em->flush();
}
}
/**
* @throws ORMException
* @throws OptimisticLockException
*/
public function remove(InformationPopup $entity, bool $flush = true): void
{
$this->_em->remove($entity);
if ($flush) {
$this->_em->flush();
}
}
// /**
// * @return InformationPopup[] Returns an array of InformationPopup objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('i')
->andWhere('i.exampleField = :val')
->setParameter('val', $value)
->orderBy('i.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?InformationPopup
{
return $this->createQueryBuilder('i')
->andWhere('i.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}
<?php
namespace App\Repository;
use App\Entity\InformationPopupUser;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<InformationPopupUser>
*
* @method InformationPopupUser|null find($id, $lockMode = null, $lockVersion = null)
* @method InformationPopupUser|null findOneBy(array $criteria, array $orderBy = null)
* @method InformationPopupUser[] findAll()
* @method InformationPopupUser[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class InformationPopupUserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, InformationPopupUser::class);
}
/**
* @throws ORMException
* @throws OptimisticLockException
*/
public function add(InformationPopupUser $entity, bool $flush = true): void
{
$this->_em->persist($entity);
if ($flush) {
$this->_em->flush();
}
}
/**
* @throws ORMException
* @throws OptimisticLockException
*/
public function remove(InformationPopupUser $entity, bool $flush = true): void
{
$this->_em->remove($entity);
if ($flush) {
$this->_em->flush();
}
}
// /**
// * @return InformationPopupUser[] Returns an array of InformationPopupUser objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('i')
->andWhere('i.exampleField = :val')
->setParameter('val', $value)
->orderBy('i.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?InformationPopupUser
{
return $this->createQueryBuilder('i')
->andWhere('i.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}
......@@ -23,6 +23,8 @@ use App\Entity\Prestataire;
use App\Entity\Rubrique;
use App\Entity\Siege;
use App\Entity\User;
use App\Entity\InformationPopup;
use App\Entity\InformationPopupUser;
use App\Enum\CurrencyEnum;
use App\Flux\AccountableInterface;
use App\Utils\CotisationUtils;
......@@ -109,6 +111,8 @@ class AppExtension extends AbstractExtension
new \Twig_SimpleFunction('parameter', function ($name) {
return $this->container->getParameter($name);
}),
new \Twig_SimpleFunction('showInformationModal', [$this, 'showInformationModal']),
new \Twig_SimpleFunction('getInformationPopupData', [$this, 'getInformationPopupData']),
];
}
......@@ -576,4 +580,38 @@ class AppExtension extends AbstractExtension
{
return $this->tavCotisationUtils->checkExistingRecurringPayment($userEmail);
}
/**
* Display an informative modale destined to users at app loading, if conditions are required.
*
* Conditions :
* - user is adherent (forced for now)
* - user hasn't clicked on the modal validation button yet (InformationPopupUser instance not created or exists with hasValidated property at false)
*/
public function showInformationModal()
{
$user = $this->security->getUser();
if (null != $user && $this->security->isGranted('ROLE_ADHERENT')) {
// There should be only one enabled popup at a given time
$activePopup = $this->em->getRepository(InformationPopup::class)->findOneBy(['enabled' => true]);
if (!is_null($activePopup)) {
$activePopupUser = $this->em->getRepository(InformationPopupUser::class)->findOneBy(['informationPopup' => $activePopup, 'user' => $user]);
if (null == $activePopupUser || false == $activePopupUser->getHasValidated()) {
return true;
}
}
}
return false;
}
/**
* If the information modale is shown, get its data
*/
public function getInformationPopupData()
{
return $this->em->getRepository(InformationPopup::class)->findOneBy(['enabled' => true]);
}
}
......@@ -66,6 +66,7 @@
{% include '@kohinos/common/modale_choix_groupe.html.twig' %}
{% include '@kohinos/common/modale_confirmation_transaction.html.twig' %}
{% include '@kohinos/common/modale_info_for_users.html.twig' %}
<!-- HEADER -->
{% block header %}
......@@ -100,6 +101,16 @@
});
</script>
{% endif %}
{# AFFICHAGE DE LA MODALE INFORMATIVE #}
{% if showInformationModal() %}
<script>
$(document).ready(function() {
$('#infoForUserModal').modal('show');
});
</script>
{% endif %}
{% if app.request.query.get('showmlcadhesionmodal') %}
<script>
$(document).ready(function() {
......
<!-- Modal -->
{% set informationPopupData = getInformationPopupData() %}
<div class="modal fade" id="infoForUserModal" tabindex="-1" role="dialog" aria-labelledby="infoForUserModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="infoForUserModalTitle">{{ informationPopupData.title }}</h5>
</div>
<div class="modal-body">
{{ informationPopupData.content|raw }}
</div>
<div class="modal-footer">
<a href='{{ path('set_user_validated_information_popup') }}' class="btn btn-primary">{{ informationPopupData.validationButtonText }}</a>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ informationPopupData.closingButtonText }}</button>
</div>
</div>
</div>
</div>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment