{ | { | ||||
use TargetPathTrait; | use TargetPathTrait; | ||||
public const LOGIN_ROUTE = 'login'; | |||||
public const LOGIN_ROUTE = 'sov_login'; | |||||
private $entityManager; | private $entityManager; | ||||
private $urlGenerator; | private $urlGenerator; | ||||
private $csrfTokenManager; | private $csrfTokenManager; | ||||
private $passwordEncoder; | private $passwordEncoder; | ||||
public function __construct(EntityManager $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder) | |||||
{ | |||||
public function __construct( | |||||
EntityManager $entityManager, | |||||
UrlGeneratorInterface $urlGenerator, | |||||
CsrfTokenManagerInterface $csrfTokenManager, | |||||
UserPasswordEncoderInterface $passwordEncoder | |||||
) { | |||||
$this->entityManager = $entityManager; | $this->entityManager = $entityManager; | ||||
$this->urlGenerator = $urlGenerator; | $this->urlGenerator = $urlGenerator; | ||||
$this->csrfTokenManager = $csrfTokenManager; | $this->csrfTokenManager = $csrfTokenManager; | ||||
public function supports(Request $request) | public function supports(Request $request) | ||||
{ | { | ||||
return self::LOGIN_ROUTE === $request->attributes->get('_route') | return self::LOGIN_ROUTE === $request->attributes->get('_route') | ||||
&& $request->isMethod('POST'); | |||||
&& $request->isMethod('POST'); | |||||
} | } | ||||
public function getCredentials(Request $request) | public function getCredentials(Request $request) | ||||
{ | { | ||||
$credentials = [ | $credentials = [ | ||||
'email' => $request->request->get('email'), | |||||
'password' => $request->request->get('password'), | |||||
'csrf_token' => $request->request->get('_csrf_token'), | |||||
'email' => $request->request->get('email'), | |||||
'password' => $request->request->get('password'), | |||||
'csrf_token' => $request->request->get('_csrf_token'), | |||||
]; | ]; | ||||
$request->getSession()->set( | $request->getSession()->set( | ||||
Security::LAST_USERNAME, | |||||
$credentials['email'] | |||||
Security::LAST_USERNAME, | |||||
$credentials['email'] | |||||
); | ); | ||||
return $credentials; | return $credentials; | ||||
throw new InvalidCsrfTokenException(); | throw new InvalidCsrfTokenException(); | ||||
} | } | ||||
$user = $this->entityManager->getRepository(UserInterface::class)->findOneBy(['email' => $credentials['email']]); | |||||
$user = $this->entityManager->getRepository(UserInterface::class)->findOneBy( | |||||
['email' => $credentials['email']] | |||||
); | |||||
if (!$user) { | if (!$user) { | ||||
// fail authentication with a custom error | // fail authentication with a custom error | ||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey) | public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey) | ||||
{ | { | ||||
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { | |||||
return new RedirectResponse($targetPath); | |||||
$routeName = 'home'; | |||||
$email = $request->request->get('email'); | |||||
$user = $this->entityManager->getRepository(UserInterface::class)->findOneBy(['email' => $email]); | |||||
if ($user && ($user->hasRole('ROLE_ADMIN') || $user->hasRole('ROLE_SUPER_ADMIN'))) { | |||||
$routeName = 'admin_dashboard'; | |||||
} | } | ||||
return new RedirectResponse($this->urlGenerator->generate('lc_admin_dashboard')); | |||||
return new RedirectResponse($this->urlGenerator->generate($routeName)); | |||||
} | } | ||||
protected function getLoginUrl() | protected function getLoginUrl() |
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; | use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; | ||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Assets; | use EasyCorp\Bundle\EasyAdminBundle\Config\Assets; | ||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; | use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; | ||||
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore; | |||||
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; | use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; | ||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController as EaAbstractCrudController; | use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController as EaAbstractCrudController; | ||||
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; | use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; | ||||
return $actions; | return $actions; | ||||
} | } | ||||
public function configureResponseParameters(KeyValueStore $responseParameters): KeyValueStore | |||||
{ | |||||
if (Crud::PAGE_INDEX === $responseParameters->get('pageName')) { | |||||
$responseParameters->set('fields', $this->configureFields('index')); | |||||
} | |||||
return $responseParameters; | |||||
} | |||||
public function configureCrud(Crud $crud): Crud | public function configureCrud(Crud $crud): Crud | ||||
{ | { | ||||
$crud = parent::configureCrud($crud); | $crud = parent::configureCrud($crud); | ||||
$crud->setPaginatorPageSize($maxResults); | $crud->setPaginatorPageSize($maxResults); | ||||
} | } | ||||
public function index(AdminContext $context) | |||||
{ | |||||
$responseParameters = parent::index($context); | |||||
// Liste des fields | |||||
$responseParameters->set('fields', $this->configureFields('index')); | |||||
return $responseParameters; | |||||
} | |||||
} | } |
->setMenuItems( | ->setMenuItems( | ||||
[ | [ | ||||
//MenuItem::linkToRoute('My Profile', 'fa fa-id-card', '', ['...' => '...']), | //MenuItem::linkToRoute('My Profile', 'fa fa-id-card', '', ['...' => '...']), | ||||
//MenuItem::section(), | |||||
MenuItem::linkToLogout('Déconnexion', 'fa fa-sign-out'), | |||||
MenuItem::linkToLogout('Déconnexion', 'sign-out-alt'), | |||||
//MenuItem::linkToLogout('Déconnexion', 'sign-out-alt') | |||||
] | ] | ||||
); | ); | ||||
} | } |
namespace Lc\SovBundle\Controller\Admin; | namespace Lc\SovBundle\Controller\Admin; | ||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||||
use Symfony\Component\HttpFoundation\Response; | use Symfony\Component\HttpFoundation\Response; | ||||
use Symfony\Component\Routing\Annotation\Route; | use Symfony\Component\Routing\Annotation\Route; | ||||
public function login(AuthenticationUtils $authenticationUtils): Response | public function login(AuthenticationUtils $authenticationUtils): Response | ||||
{ | { | ||||
if ($this->getUser()) { | if ($this->getUser()) { | ||||
return $this->redirectToRoute('lc_'); | |||||
return $this->redirectToRoute('admin_dashboard'); | |||||
} | } | ||||
// get the login error if there is one | // get the login error if there is one | ||||
'csrf_token_intention' => 'authenticate', | 'csrf_token_intention' => 'authenticate', | ||||
// the URL users are redirected to after the login (default: '/admin') | // the URL users are redirected to after the login (default: '/admin') | ||||
'target_path' => $this->generateUrl('lc_admin_dashboard'), | |||||
'target_path' => $this->generateUrl('admin_dashboard'), | |||||
// the label displayed for the username form field (the |trans filter is applied to it) | // the label displayed for the username form field (the |trans filter is applied to it) | ||||
'username_label' => 'Your username', | 'username_label' => 'Your username', |
<?php | |||||
namespace Lc\SovBundle\Controller\Admin; | |||||
use Lc\SovBundle\Doctrine\EntityManager; | |||||
use Lc\SovBundle\Form\Type\User\ChangePasswordFormType; | |||||
use Lc\SovBundle\Form\Type\User\ProfileFormType; | |||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |||||
use Symfony\Component\HttpFoundation\Request; | |||||
use Symfony\Component\HttpFoundation\Response; | |||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; | |||||
class UserController extends AbstractController | |||||
{ | |||||
protected $em; | |||||
public function __construct(EntityManager $em) | |||||
{ | |||||
$this->em = $em; | |||||
} | |||||
public function profile(Request $request): Response | |||||
{ | |||||
$user = $this->getUser(); | |||||
$form = $this->createForm(ProfileFormType::class, $user); | |||||
$form->handleRequest($request); | |||||
if ($form->isSubmitted() && $form->isValid()) { | |||||
$user = $form->getData(); | |||||
$this->em->update($user); | |||||
$this->em->flush(); | |||||
} | |||||
return $this->render( | |||||
'@LcSov/user/profile.html.twig', | |||||
[ | |||||
'form' => $form->createView() | |||||
] | |||||
); | |||||
} | |||||
public function changePassword(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response | |||||
{ | |||||
$user = $this->getUser(); | |||||
$form = $this->createForm(ChangePasswordFormType::class, $user); | |||||
$form->handleRequest($request); | |||||
if ($form->isSubmitted() && $form->isValid()) { | |||||
$user = $form->getData(); | |||||
$plainPassword = $form->get('plainPassword')->getData(); | |||||
// @TODO : créer UserManager | |||||
$newPasswordEncoded = $passwordEncoder->encodePassword($user, $plainPassword); | |||||
$user->setPassword($newPasswordEncoded); | |||||
$this->em->update($user); | |||||
$this->em->flush(); | |||||
} | |||||
return $this->render( | |||||
'@LcSov/user/change_password.html.twig', | |||||
[ | |||||
'form' => $form->createView() | |||||
] | |||||
); | |||||
} | |||||
} |
namespace Lc\SovBundle\Controller\Admin; | namespace Lc\SovBundle\Controller\Admin; | ||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Action; | |||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; | |||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; | |||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; | use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; | ||||
abstract class UserCrudController extends AbstractCrudController | abstract class UserCrudController extends AbstractCrudController | ||||
{ | { | ||||
public function configureFields(string $pageName): iterable | |||||
{ | |||||
return [ | |||||
TextField::new('email') | |||||
]; | |||||
} | |||||
public function configureFields(string $pageName): iterable | |||||
{ | |||||
return [ | |||||
TextField::new('email') | |||||
]; | |||||
} | |||||
} | } |
$container->setParameter(sprintf('lc_sov.%s', $parameter), $value); | $container->setParameter(sprintf('lc_sov.%s', $parameter), $value); | ||||
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); | $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); | ||||
$loader->load('services.yml'); | |||||
$loader->load('services.yaml'); | |||||
} | } | ||||
public function prepend(ContainerBuilder $container) | public function prepend(ContainerBuilder $container) |
<?php | |||||
/* | |||||
* This file is part of the FOSUserBundle package. | |||||
* | |||||
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> | |||||
* | |||||
* For the full copyright and license information, please view the LICENSE | |||||
* file that was distributed with this source code. | |||||
*/ | |||||
namespace Lc\SovBundle\Form\Type\User; | |||||
use Lc\SovBundle\Doctrine\EntityManager; | |||||
use Lc\SovBundle\Model\User\UserInterface; | |||||
use Symfony\Component\Form\AbstractType; | |||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType; | |||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType; | |||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType; | |||||
use Symfony\Component\Form\FormBuilderInterface; | |||||
use Symfony\Component\OptionsResolver\OptionsResolver; | |||||
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword; | |||||
use Symfony\Component\Validator\Constraints\NotBlank; | |||||
class ChangePasswordFormType extends AbstractType | |||||
{ | |||||
protected $em ; | |||||
public function __construct(EntityManager $em) | |||||
{ | |||||
$this->em = $em ; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function buildForm(FormBuilderInterface $builder, array $options) | |||||
{ | |||||
$constraintsOptions = [ | |||||
'message' => 'Mot de passe invalide', | |||||
]; | |||||
if (!empty($options['validation_groups'])) { | |||||
$constraintsOptions['groups'] = [reset($options['validation_groups'])]; | |||||
} | |||||
$builder->add( | |||||
'current_password', | |||||
PasswordType::class, | |||||
[ | |||||
'label' => 'Mot de passe actuel', | |||||
'mapped' => false, | |||||
'constraints' => [ | |||||
new NotBlank(), | |||||
new UserPassword($constraintsOptions), | |||||
], | |||||
] | |||||
); | |||||
$builder->add( | |||||
'plainPassword', | |||||
RepeatedType::class, | |||||
[ | |||||
'type' => PasswordType::class, | |||||
'mapped' => false, | |||||
'first_options' => ['label' => 'Nouveau mot de passe'], | |||||
'second_options' => ['label' => 'Nouveau mot de passe (confirmation)'], | |||||
'invalid_message' => 'Les deux mots de passe ne correspondent pas.', | |||||
] | |||||
); | |||||
$builder->add( | |||||
'submit', | |||||
SubmitType::class, | |||||
array( | |||||
'label' => 'Sauvegarder' | |||||
) | |||||
); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function configureOptions(OptionsResolver $resolver) | |||||
{ | |||||
$resolver->setDefaults( | |||||
[ | |||||
'data_class' => $this->em->getEntityName(UserInterface::class), | |||||
] | |||||
); | |||||
} | |||||
} |
<?php | |||||
namespace Lc\SovBundle\Form\Type\User; | |||||
use Lc\SovBundle\DataTransformer\FileManagerTypeToDataTransformer; | |||||
use Lc\SovBundle\Doctrine\EntityManager; | |||||
use Lc\SovBundle\Entity\File\File; | |||||
use Lc\SovBundle\Doctrine\Extension\FileInterface; | |||||
use Lc\SovBundle\Model\User\UserInterface; | |||||
use Symfony\Component\Form\AbstractType; | |||||
use Symfony\Component\Form\Extension\Core\Type\EmailType; | |||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType; | |||||
use Symfony\Component\Form\Extension\Core\Type\TextType; | |||||
use Symfony\Component\Form\FormBuilderInterface; | |||||
use Symfony\Component\OptionsResolver\OptionsResolver; | |||||
class ProfileFormType extends AbstractType | |||||
{ | |||||
protected $em ; | |||||
public function __construct(EntityManager $em) | |||||
{ | |||||
$this->em = $em ; | |||||
} | |||||
public function buildForm(FormBuilderInterface $builder, array $options) | |||||
{ | |||||
$builder->add( | |||||
'firstname', | |||||
TextType::class, | |||||
array( | |||||
'label' => 'Prénom' | |||||
) | |||||
); | |||||
$builder->add( | |||||
'lastname', | |||||
TextType::class, | |||||
array( | |||||
'label' => 'Nom' | |||||
) | |||||
); | |||||
$builder->add( | |||||
'email', | |||||
EmailType::class, | |||||
array( | |||||
'label' => 'Email' | |||||
) | |||||
); | |||||
$builder->add( | |||||
'phone', | |||||
TextType::class, | |||||
array( | |||||
'label' => 'Téléphone' | |||||
) | |||||
); | |||||
$builder->add( | |||||
'submit', | |||||
SubmitType::class, | |||||
array( | |||||
'label' => 'Sauvegarder' | |||||
) | |||||
); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function configureOptions(OptionsResolver $resolver) | |||||
{ | |||||
$resolver->setDefaults( | |||||
[ | |||||
'data_class' => $this->em->getEntityName(UserInterface::class), | |||||
] | |||||
); | |||||
} | |||||
} |
*/ | */ | ||||
abstract class User implements EntityInterface, UserInterface | abstract class User implements EntityInterface, UserInterface | ||||
{ | { | ||||
/** | |||||
* @ORM\Column(type="string", length=180, unique=true) | |||||
*/ | |||||
protected $email; | |||||
/** | |||||
* @ORM\Column(type="json") | |||||
*/ | |||||
protected $roles = []; | |||||
/** | |||||
* @var string The hashed password | |||||
* @ORM\Column(type="string") | |||||
*/ | |||||
protected $password; | |||||
/** | |||||
* @ORM\Column(type="string", length=255, nullable=true) | |||||
*/ | |||||
protected $lastname; | |||||
/** | |||||
* @ORM\Column(type="string", length=255, nullable=true) | |||||
*/ | |||||
protected $firstname; | |||||
/** | |||||
* @ORM\Column(type="boolean") | |||||
*/ | |||||
protected $isVerified = false; | |||||
public function getEmail(): ?string | |||||
{ | |||||
return $this->email; | |||||
} | |||||
public function setEmail(string $email): self | |||||
{ | |||||
$this->email = $email; | |||||
return $this; | |||||
} | |||||
/** | |||||
* A visual identifier that represents this user. | |||||
* | |||||
* @see UserInterface | |||||
*/ | |||||
public function getUsername(): string | |||||
{ | |||||
return (string)$this->email; | |||||
} | |||||
/** | |||||
* @see UserInterface | |||||
*/ | |||||
public function getRoles(): array | |||||
{ | |||||
$roles = $this->roles; | |||||
// guarantee every user at least has ROLE_USER | |||||
$roles[] = 'ROLE_USER'; | |||||
return array_unique($roles); | |||||
} | |||||
public function setRoles(array $roles): self | |||||
{ | |||||
$this->roles = $roles; | |||||
return $this; | |||||
} | |||||
/** | |||||
* @see UserInterface | |||||
*/ | |||||
public function getPassword(): string | |||||
{ | |||||
return (string)$this->password; | |||||
} | |||||
public function setPassword(string $password): self | |||||
{ | |||||
$this->password = $password; | |||||
return $this; | |||||
} | |||||
/** | |||||
* @see UserIn | |||||
*/ | |||||
public function getSalt() | |||||
{ | |||||
// not needed when using the "bcrypt" algorithm in security.yaml | |||||
} | |||||
/** | |||||
* @see UserInterface | |||||
*/ | |||||
public function eraseCredentials() | |||||
{ | |||||
// If you store any temporary, sensitive data on the user, clear it here | |||||
// $this->plainPassword = null; | |||||
} | |||||
public function getLastname(): ?string | |||||
{ | |||||
return $this->lastname; | |||||
} | |||||
public function setLastname(?string $lastname): self | |||||
{ | |||||
$this->lastname = $lastname; | |||||
return $this; | |||||
} | |||||
public function getFirstname(): ?string | |||||
{ | |||||
return $this->firstname; | |||||
} | |||||
public function setFirstname(?string $firstname): self | |||||
{ | |||||
$this->firstname = $firstname; | |||||
return $this; | |||||
} | |||||
public function getName(): ?string | |||||
{ | |||||
return $this->getFirstname(). ' '.strtoupper($this->getLastname()); | |||||
} | |||||
public function isVerified(): bool | |||||
{ | |||||
return $this->isVerified; | |||||
} | |||||
public function setIsVerified(bool $isVerified): self | |||||
{ | |||||
$this->isVerified = $isVerified; | |||||
return $this; | |||||
} | |||||
/** | |||||
* @ORM\Column(type="string", length=180, unique=true) | |||||
*/ | |||||
protected $email; | |||||
/** | |||||
* @ORM\Column(type="json") | |||||
*/ | |||||
protected $roles = []; | |||||
/** | |||||
* @var string The hashed password | |||||
* @ORM\Column(type="string") | |||||
*/ | |||||
protected $password; | |||||
/** | |||||
* @ORM\Column(type="string", length=255, nullable=true) | |||||
*/ | |||||
protected $lastname; | |||||
/** | |||||
* @ORM\Column(type="string", length=255, nullable=true) | |||||
*/ | |||||
protected $firstname; | |||||
/** | |||||
* @ORM\Column(type="boolean") | |||||
*/ | |||||
protected $isVerified = false; | |||||
public function getEmail(): ?string | |||||
{ | |||||
return $this->email; | |||||
} | |||||
public function setEmail(string $email): self | |||||
{ | |||||
$this->email = $email; | |||||
return $this; | |||||
} | |||||
/** | |||||
* A visual identifier that represents this user. | |||||
* | |||||
* @see UserInterface | |||||
*/ | |||||
public function getUsername(): string | |||||
{ | |||||
return (string)$this->email; | |||||
} | |||||
/** | |||||
* @see UserInterface | |||||
*/ | |||||
public function getRoles(): array | |||||
{ | |||||
$roles = $this->roles; | |||||
// guarantee every user at least has ROLE_USER | |||||
$roles[] = 'ROLE_USER'; | |||||
return array_unique($roles); | |||||
} | |||||
public function setRoles(array $roles): self | |||||
{ | |||||
$this->roles = $roles; | |||||
return $this; | |||||
} | |||||
public function hasRole($role) | |||||
{ | |||||
return in_array(strtoupper($role), $this->getRoles(), true); | |||||
} | |||||
/** | |||||
* @see UserInterface | |||||
*/ | |||||
public function getPassword(): string | |||||
{ | |||||
return (string)$this->password; | |||||
} | |||||
public function setPassword(string $password): self | |||||
{ | |||||
$this->password = $password; | |||||
return $this; | |||||
} | |||||
/** | |||||
* @see UserIn | |||||
*/ | |||||
public function getSalt() | |||||
{ | |||||
// not needed when using the "bcrypt" algorithm in security.yaml | |||||
} | |||||
/** | |||||
* @see UserInterface | |||||
*/ | |||||
public function eraseCredentials() | |||||
{ | |||||
// If you store any temporary, sensitive data on the user, clear it here | |||||
// $this->plainPassword = null; | |||||
} | |||||
public function getLastname(): ?string | |||||
{ | |||||
return $this->lastname; | |||||
} | |||||
public function setLastname(?string $lastname): self | |||||
{ | |||||
$this->lastname = $lastname; | |||||
return $this; | |||||
} | |||||
public function getFirstname(): ?string | |||||
{ | |||||
return $this->firstname; | |||||
} | |||||
public function setFirstname(?string $firstname): self | |||||
{ | |||||
$this->firstname = $firstname; | |||||
return $this; | |||||
} | |||||
public function getName(): ?string | |||||
{ | |||||
return $this->getFirstname() . ' ' . strtoupper($this->getLastname()); | |||||
} | |||||
public function isVerified(): bool | |||||
{ | |||||
return $this->isVerified; | |||||
} | |||||
public function setIsVerified(bool $isVerified): self | |||||
{ | |||||
$this->isVerified = $isVerified; | |||||
return $this; | |||||
} | |||||
} | } |
sov_login: | |||||
path: /login | |||||
controller: Lc\SovBundle\Controller\Admin\SecurityController::login | |||||
sov_logout: | |||||
path: /logout | |||||
controller: Lc\SovBundle\Controller\Admin\SecurityController::logout | |||||
sov_admin_account_profile: | |||||
path: /admin/account/profile | |||||
controller: Lc\SovBundle\Controller\Admin\UserController::profile | |||||
sov_admin_account_password: | |||||
path: /admin/account/password | |||||
controller: Lc\SovBundle\Controller\Admin\UserController::changePassword | |||||
target="{{ item.linkTarget }}" rel="{{ item.linkRel }}" | target="{{ item.linkTarget }}" rel="{{ item.linkRel }}" | ||||
referrerpolicy="origin-when-cross-origin"> | referrerpolicy="origin-when-cross-origin"> | ||||
{% if item.icon is not empty %} | {% if item.icon is not empty %} | ||||
<i class="ta ta-{{ item.icon }}"></i> | |||||
<i class="fa fa-{{ item.icon }}"></i> | |||||
{% endif %} | {% endif %} | ||||
<span>{{ item.label }}</span> | <span>{{ item.label }}</span> | ||||
</a> | </a> |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{# @var action \EasyCorp\Bundle\EasyAdminBundle\Dto\ActionDto #} | |||||
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} | |||||
{% if 'a' == action.htmlElement %} | |||||
{% for _locale in app_locales %} | |||||
<a class="{{ isIncludedInDropdown|default(false) ? 'dropdown-item' }} {{ action.cssClass }}" | |||||
href="{{ action.linkUrl }}&_locale={{ _locale }}" | |||||
{% for name, value in action.htmlAttributes %}{{ name }}="{{ value|e('html_attr') }}" {% endfor %}> | |||||
{%- if action.icon %}<i class="action-icon {{ action.icon }}"></i> {% endif -%} | |||||
{%- if action.label is not empty -%}{{ action.label }} {{ _locale }}{%- endif -%} | |||||
</a> | |||||
{% endfor %} | |||||
{% elseif 'button' == action.htmlElement %} | |||||
<button class="{{ action.cssClass }}" {% for name, value in action.htmlAttributes %}{{ name }}="{{ value|e('html_attr') }}" {% endfor %}> | |||||
<span class="btn-label"> | |||||
{%- if action.icon %}<i class="action-icon {{ action.icon }}"></i> {% endif -%} | |||||
{%- if action.label is not empty -%}{{ action.label }}{%- endif -%} | |||||
</span> | |||||
</button> | |||||
{% endif %} |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} | |||||
{% extends ea.templatePath('layout') %} | |||||
{% form_theme edit_form with ea.crud.formThemes only %} | |||||
{% trans_default_domain ea.i18n.translationDomain %} | |||||
{% block body_id 'ea-edit-' ~ entity.name ~ '-' ~ entity.primaryKeyValue %} | |||||
{% block body_class 'ea-edit ea-edit-' ~ entity.name %} | |||||
{% block configured_head_contents %} | |||||
{{ parent() }} | |||||
{% for htmlContent in edit_form.vars.ea_crud_form.assets.headContents %} | |||||
{{ htmlContent|raw }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block configured_body_contents %} | |||||
{{ parent() }} | |||||
{% for htmlContent in edit_form.vars.ea_crud_form.assets.bodyContents %} | |||||
{{ htmlContent|raw }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block configured_stylesheets %} | |||||
{{ parent() }} | |||||
{% for css_asset in edit_form.vars.ea_crud_form.assets.cssFiles %} | |||||
<link rel="stylesheet" href="{{ asset(css_asset) }}"> | |||||
{% endfor %} | |||||
{% for webpack_encore_entry in edit_form.vars.ea_crud_form.assets.webpackEncoreEntries %} | |||||
{{ ea_call_function_if_exists('encore_entry_link_tags', webpack_encore_entry) }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block configured_javascripts %} | |||||
{{ parent() }} | |||||
{% for js_asset in edit_form.vars.ea_crud_form.assets.jsFiles %} | |||||
<script src="{{ asset(js_asset) }}"></script> | |||||
{% endfor %} | |||||
{% for webpack_encore_entry in edit_form.vars.ea_crud_form.assets.webpackEncoreEntries %} | |||||
{{ ea_call_function_if_exists('encore_entry_script_tags', webpack_encore_entry) }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block content_title %} | |||||
{%- apply spaceless -%} | |||||
{{ ea.crud.customPageTitle is null | |||||
? (ea.crud.defaultPageTitle|trans(ea.i18n.translationParameters, 'EasyAdminBundle'))|raw | |||||
: ea.crud.customPageTitle|trans(ea.i18n.translationParameters)|raw }} | |||||
{%- endapply -%} | |||||
{% endblock %} | |||||
{% block page_actions %} | |||||
{% for action in entity.actions %} | |||||
{{ include(action.templatePath, { action: action }, with_context = false) }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block main %} | |||||
<div class="card"> | |||||
<div class="card-body"> | |||||
{% block edit_form %} | |||||
{{ form(edit_form) }} | |||||
{% endblock edit_form %} | |||||
{% block delete_form %} | |||||
{{ include('@EasyAdmin/crud/includes/_delete_form.html.twig', { entity_id: entity.primaryKeyValue }, with_context = false) }} | |||||
{% endblock delete_form %} | |||||
</div> | |||||
</div> | |||||
{% endblock %} | |||||
{% block body_javascript %} | |||||
{{ parent() }} | |||||
<script type="text/javascript"> | |||||
$(function () { | |||||
$('.ea-edit-form').areYouSure({'message': '{{ 'form.are_you_sure'|trans({}, 'EasyAdminBundle')|e('js') }}'}); | |||||
const entityForm = document.querySelector('form.ea-edit-form'); | |||||
const inputFieldsSelector = 'input,select,textarea'; | |||||
// Adding visual feedback for invalid fields: any ".form-group" with invalid fields | |||||
// receives "has-error" class. The class is removed on click on the ".form-group" | |||||
// itself to support custom/complex fields. | |||||
entityForm.addEventListener('submit', function (submitEvent) { | |||||
entityForm.querySelectorAll(inputFieldsSelector).forEach(function (input) { | |||||
if (!input.validity.valid) { | |||||
const formGroup = input.closest('div.form-group'); | |||||
formGroup.classList.add('has-error'); | |||||
formGroup.addEventListener('click', function onFormGroupClick() { | |||||
formGroup.classList.remove('has-error'); | |||||
formGroup.removeEventListener('click', onFormGroupClick); | |||||
}); | |||||
} | |||||
}); | |||||
const eaEvent = new CustomEvent('ea.form.submit', { | |||||
cancelable: true, | |||||
detail: {page: 'edit', form: entityForm} | |||||
}); | |||||
const eaEventResult = document.dispatchEvent(eaEvent); | |||||
if (false === eaEventResult) { | |||||
submitEvent.preventDefault(); | |||||
submitEvent.stopPropagation(); | |||||
} | |||||
}); | |||||
// forms with tabs require some special treatment for errors. The problem | |||||
// is when the field with errors is included in a tab not currently visible. | |||||
// Browser shows this error "An invalid form control with name='...' is not focusable." | |||||
// So, the user clicks on Submit button, the form is not submitted and the error | |||||
// is not displayed. This JavaScript code ensures that each tab shows a badge with | |||||
// the number of errors in it. | |||||
entityForm.addEventListener('submit', function () { | |||||
const formTabPanes = entityForm.querySelectorAll('.tab-pane'); | |||||
if (0 === formTabPanes.length) { | |||||
return; | |||||
} | |||||
let firstNavTabItemWithError = null; | |||||
formTabPanes.forEach(function (tabPane) { | |||||
let tabPaneNumErrors = 0; | |||||
tabPane.querySelectorAll(inputFieldsSelector).forEach(function (input) { | |||||
if (!input.validity.valid) { | |||||
tabPaneNumErrors++; | |||||
} | |||||
}); | |||||
let navTabItem = entityForm.querySelector('.nav-item a[href="#' + tabPane.id + '"]'); | |||||
let existingErrorBadge = navTabItem.querySelector('span.badge.badge-danger'); | |||||
if (null !== existingErrorBadge) { | |||||
navTabItem.removeChild(existingErrorBadge); | |||||
} | |||||
if (tabPaneNumErrors > 0) { | |||||
let newErrorBadge = document.createElement('span'); | |||||
newErrorBadge.classList.add('badge', 'badge-danger'); | |||||
newErrorBadge.title = 'form.tab.error_badge_title'; | |||||
newErrorBadge.textContent = tabPaneNumErrors; | |||||
navTabItem.appendChild(newErrorBadge); | |||||
if (null === firstNavTabItemWithError) { | |||||
firstNavTabItemWithError = navTabItem; | |||||
} | |||||
} | |||||
}); | |||||
if (firstNavTabItemWithError) { | |||||
firstNavTabItemWithError.click(); | |||||
} | |||||
}); | |||||
$('.action-delete').on('click', function (e) { | |||||
e.preventDefault(); | |||||
const formAction = $(this).attr('formaction'); | |||||
$('#modal-delete').modal({backdrop: true, keyboard: true}) | |||||
.off('click', '#modal-delete-button') | |||||
.on('click', '#modal-delete-button', function () { | |||||
$('#delete-form').attr('action', formAction).trigger('submit'); | |||||
}); | |||||
}); | |||||
}); | |||||
</script> | |||||
< | |||||
{% endblock %} |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} | |||||
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} | |||||
{# this is a bit ugly, but Twig doesn't have a 'is numeric' test #} | |||||
{% if field.formattedValue is iterable %} | |||||
{% for item in field.formattedValue %} | |||||
<span class="badge badge-secondary">{{ item }}</span> | |||||
{% endfor %} | |||||
{% else %} | |||||
{{ field.formattedValue }} | |||||
{% endif %} |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} | |||||
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} | |||||
{# this is a bit ugly, but Twig doesn't have a 'is numeric' test #} | |||||
{% if field.formattedValue matches '/^\\d+$/' %} | |||||
<span class="badge badge-secondary">{{ field.formattedValue }}</span> | |||||
{% else %} | |||||
{{ field.formattedValue }} | |||||
{% endif %} |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} | |||||
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} | |||||
{% set html_id = 'ea-lightbox-' ~ field.uniqueId %} | |||||
{% if field.value is not null and field.value.path is not null %} | |||||
<a href="#" class="ea-lightbox-thumbnail" title="{{ field.value.legend }}" data-featherlight="#{{ html_id }}" data-featherlight-close-on-click="anywhere"> | |||||
<img src="{{ asset(field.value.path) }}" class="img-fluid"> | |||||
</a> | |||||
<div id="{{ html_id }}" class="ea-lightbox"> | |||||
<img src="{{ asset(field.value.path) }}"> | |||||
</div> | |||||
{% endif %} |
<div class="modal fade" id="lc-filemanager-modal" tabindex="-1" role="dialog" aria-labelledby="File manager modal"> | |||||
<div class="modal-dialog modal-lg" role="document"> | |||||
<div class="modal-content"> | |||||
<div class="modal-header"> | |||||
<h4 class="modal-title">Gestionaire de fichier</h4> | |||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |||||
<span aria-hidden="true">×</span> | |||||
</button> | |||||
</div> | |||||
<div class="modal-body"> | |||||
<iframe id="lc-filemanager-frame" src="" width="100%" height="500" frameborder="0"></iframe> | |||||
</div> | |||||
<div class="modal-footer"> | |||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{% use '@EasyAdmin/crud/form_theme.html.twig' %} | |||||
{% block form_label -%} | |||||
{% if label is not same as(false) -%} | |||||
{%- if compound is defined and compound -%} | |||||
{%- set element = 'legend' -%} | |||||
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} | |||||
{%- else -%} | |||||
{%- set label_attr = label_attr|merge({for: id, class: (label_attr.class|default('') ~ ' form-label')|trim}) -%} | |||||
{%- endif -%} | |||||
{{ parent() }} | |||||
{%- endif -%} | |||||
{%- endblock form_label %} | |||||
{% block gallery_manager_row %} | |||||
{{ block('collection_row') }} | |||||
{% endblock gallery_manager_row %} | |||||
{% block gallery_manager_widget %} | |||||
{{ block('collection_widget') }} | |||||
{% endblock gallery_manager_widget %} | |||||
{% block collection_row %} | |||||
{% if prototype is defined and not prototype.rendered %} | |||||
{% set row_attr = row_attr|merge({ 'data-prototype': form_row(prototype) }) %} | |||||
{% endif %} | |||||
{% set row_attr = row_attr|merge({ | |||||
'data-entry-is-complex': form.vars.ea_crud_form.ea_field and form.vars.ea_crud_form.ea_field.customOptions.get('entryIsComplex') ? 'true' : 'false', | |||||
'data-allow-add': allow_add ? 'true' : 'false', | |||||
'data-allow-delete': allow_delete ? 'true' : 'false', | |||||
'data-num-items': form.children|length, | |||||
'data-form-type-name-placeholder': prototype is defined ? prototype.vars.name : '', | |||||
}) %} | |||||
{{ block('form_row') }} | |||||
{% endblock collection_row %} | |||||
{% block collection_widget %} | |||||
{{ block('form_widget') }} | |||||
{% if allow_add|default(false) %} | |||||
<button type="button" class="btn btn-link field-collection-add"> | |||||
<i class="fa fa-plus pr-1"></i> | |||||
{{ 'action.add_new_item'|trans({}, 'EasyAdminBundle') }} | |||||
</button> | |||||
{% endif %} | |||||
{% endblock collection_widget %} | |||||
{% block collection_entry_widget %} | |||||
{% set is_complex = form_parent(form).vars.ea_crud_form.ea_field.customOptions.get('entryIsComplex') ?? false %} | |||||
<div class="field-collection-item {{ is_complex ? 'field-collection-item-complex' }}"> | |||||
{{ form_widget(form) }} | |||||
{% if form_parent(form).vars.allow_delete|default(false) %} | |||||
<button type="button" class="btn btn-link field-collection-delete" | |||||
title="{{ 'action.remove_item'|trans({}, 'EasyAdminBundle') }}"> | |||||
<i class="fas fa-times"></i> | |||||
</button> | |||||
{% endif %} | |||||
</div> | |||||
{% endblock collection_entry_widget %} | |||||
{% block file_manager_image_row %} | |||||
{{ form_widget(form) }} | |||||
{% endblock file_manager_image_row %} | |||||
{% block file_manager_legend_row %} | |||||
{{ form_widget(form) }} | |||||
{% endblock file_manager_legend_row %} | |||||
{% block file_manager_position_row %} | |||||
{{ form_widget(form) }} | |||||
{% endblock file_manager_position_row %} | |||||
{% block file_manager_widget %} | |||||
<div class="lc-filemanager col-xs-12"> | |||||
<div class="col-md-6 col-xs-12 nopadding" style="padding: 0px;"> | |||||
<img style="width: 200px; height: 150px; object-fit: contain; background: #ddd; " src="{{ form.path.vars.value }}" class="lc-filemenager-preview" id="{{ form.path.vars.id }}_preview" alt=""> | |||||
</div> | |||||
<div class="input-group"> | |||||
{{ form_widget(form) }} | |||||
<div class="input-group-append"> | |||||
<button type="button" class="btn btn-sm lc-filemanager-open" data-id="{{ form.path.vars.id }}" | |||||
data-target="{{ path('file_manager', {module:1, conf:'default'})|raw }}"> | |||||
<i class="fa fa-folder-open-o"></i> | |||||
</button> | |||||
{% if value %} | |||||
<button type="button" class="btn btn-sm lc-filemanager-delete" data-id="{{ form.path.vars.id }}"> | |||||
<i class="fa fa-trash-o"></i> | |||||
</button> | |||||
{% endif %} | |||||
{% if form.parent.vars['row_attr']['data-sortable'] is defined %} | |||||
<button type="button" class="btn btn-sm lc-btn-sortable" > | |||||
<i class="fa fa-arrows"></i> | |||||
</button> | |||||
{% endif %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% endblock file_manager_widget %} | |||||
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{# @var entities \EasyCorp\Bundle\EasyAdminBundle\Collection\EntityDtoCollection #} | |||||
{# @var paginator \EasyCorp\Bundle\EasyAdminBundle\Orm\EntityPaginator #} | |||||
{% extends ea.templatePath('layout') %} | |||||
{% trans_default_domain ea.i18n.translationDomain %} | |||||
{% block body_id entities|length > 0 ? 'ea-index-' ~ entities|first.name : '' %} | |||||
{% block body_class 'index' ~ (entities|length > 0 ? ' index-' ~ entities|first.name : '') %} | |||||
{% block content_title %} | |||||
{%- apply spaceless -%} | |||||
{% set default_title = ea.crud.defaultPageTitle('index')|trans(ea.i18n.translationParameters, 'EasyAdminBundle') %} | |||||
{{ ea.crud.customPageTitle is null ? default_title|raw : ea.crud.customPageTitle('index')|trans(ea.i18n.translationParameters)|raw }} | |||||
{%- endapply -%} | |||||
{% endblock %} | |||||
{% set has_batch_actions = batch_actions|length > 0 %} | |||||
{% block page_actions %} | |||||
{% block global_actions %} | |||||
<div class="global-actions"> | |||||
{% for action in global_actions %} | |||||
{{ include(action.templatePath, { action: action }, with_context = false) }} | |||||
{% endfor %} | |||||
</div> | |||||
{% endblock global_actions %} | |||||
{% block batch_actions %} | |||||
{% if has_batch_actions %} | |||||
<div class="batch-actions" style="display: none"> | |||||
{% for action in batch_actions %} | |||||
{{ include(action.templatePath, { action: action }, with_context = false) }} | |||||
{% endfor %} | |||||
</div> | |||||
{% endif %} | |||||
{% endblock %} | |||||
{% endblock page_actions %} | |||||
{% block main %} | |||||
{# sort can be multiple; let's consider the sorting field the first one #} | |||||
{% set sort_field_name = app.request.get('sort')|keys|first %} | |||||
{% set sort_order = app.request.get('sort')|first %} | |||||
{% set some_results_are_hidden = false %} | |||||
{% set has_footer = entities|length != 0 %} | |||||
{% set has_search = ea.crud.isSearchEnabled %} | |||||
{% set has_filters = filters|length > 0 %} | |||||
{% set has_datagrid_tools = has_search or has_filters %} | |||||
<div class="card"> | |||||
<div class="card-header"> | |||||
<div class="d-flex"> | |||||
<div class="text-muted"> | |||||
Show | |||||
<div class="mx-2 d-inline-block"> | |||||
<input type="text" class="form-control form-control-sm" value="8" size="3" | |||||
aria-label="Invoices count" data-cip-id="cIPJQ342845640"> | |||||
</div> | |||||
entries | |||||
</div> | |||||
<div class="ms-auto text-muted"> | |||||
Search: | |||||
<div class="ms-2 d-inline-block"> | |||||
<input type="text" class="form-control form-control-sm" aria-label="Search invoice" | |||||
data-cip-id="cIPJQ342845641"> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="card-body border-bottom py-3"> | |||||
</div> | |||||
<div class="table-responsive"> | |||||
<table class="table card-table table-vcenter text-nowrap table-mobile-md table-striped"> | |||||
<thead> | |||||
{% block table_head %} | |||||
<tr> | |||||
{% if has_batch_actions %} | |||||
<th class="w-1"><span><input type="checkbox" | |||||
class="form-check-input m-0 align-middle form-batch-checkbox-all"></span> | |||||
</th> | |||||
{% endif %} | |||||
{% set ea_sort_asc = constant('EasyCorp\\Bundle\\EasyAdminBundle\\Config\\Option\\SortOrder::ASC') %} | |||||
{% set ea_sort_desc = constant('EasyCorp\\Bundle\\EasyAdminBundle\\Config\\Option\\SortOrder::DESC') %} | |||||
{% for field in entities|first.fields ?? [] %} | |||||
{% set is_sorting_field = ea.search.isSortingField(field.property) %} | |||||
{% set next_sort_direction = is_sorting_field ? (ea.search.sortDirection(field.property) == ea_sort_desc ? ea_sort_asc : ea_sort_desc) : ea_sort_desc %} | |||||
{% set column_icon = is_sorting_field ? (next_sort_direction == ea_sort_desc ? 'fa-arrow-up' : 'fa-arrow-down') : 'fa-sort' %} | |||||
<th class="{{ is_sorting_field ? 'sorted' }} {{ field.isVirtual ? 'field-virtual' }} text-{{ field.textAlign }}" | |||||
dir="{{ ea.i18n.textDirection }}"> | |||||
{% if field.isSortable %} | |||||
<a href="{{ ea_url({ page: 1, sort: { (field.property): next_sort_direction } }).includeReferrer() }}"> | |||||
{{ field.label|raw }} <i class="fa fa-fw {{ column_icon }}"></i> | |||||
</a> | |||||
{% else %} | |||||
<span>{{ field.label|raw }}</span> | |||||
{% endif %} | |||||
</th> | |||||
{% endfor %} | |||||
<th {% if ea.crud.showEntityActionsAsDropdown %}width="10px"{% endif %} dir="{{ ea.i18n.textDirection }}"> | |||||
<span class="sr-only">{{ 'action.entity_actions'|trans(ea.i18n.translationParameters, 'EasyAdminBundle') }}</span> | |||||
</th> | |||||
</tr> | |||||
{% endblock table_head %} | |||||
</thead> | |||||
<tbody> | |||||
{% block table_body %} | |||||
{% for entity in entities %} | |||||
{% if not entity.isAccessible %} | |||||
{% set some_results_are_hidden = true %} | |||||
{% else %} | |||||
<tr data-id="{{ entity.primaryKeyValueAsString }}"> | |||||
{% if has_batch_actions %} | |||||
<td><input type="checkbox" class="form-batch-checkbox" | |||||
value="{{ entity.primaryKeyValue }}"></td> | |||||
{% endif %} | |||||
{% for field in entity.fields %} | |||||
<td class="{{ field.property == sort_field_name ? 'sorted' }} text-{{ field.textAlign }} {{ field.cssClass }}" | |||||
dir="{{ ea.i18n.textDirection }}"> | |||||
{{ include(field.templatePath, { field: field, entity: entity }, with_context = false) }} | |||||
</td> | |||||
{% endfor %} | |||||
{% block entity_actions %} | |||||
<td class="actions"> | |||||
{% if not ea.crud.showEntityActionsAsDropdown %} | |||||
{% for action in entity.actions %} | |||||
{{ include(action.templatePath, { action: action, entity: entity, isIncludedInDropdown: ea.crud.showEntityActionsAsDropdown }, with_context = false) }} | |||||
{% endfor %} | |||||
{% else %} | |||||
<div class="dropdown dropdown-actions"> | |||||
<a class="dropdown-toggle btn btn-secondary btn-sm" href="#" | |||||
role="button" data-toggle="dropdown" aria-haspopup="true" | |||||
aria-expanded="false"> | |||||
<i class="fa fa-fw fa-ellipsis-h"></i> | |||||
</a> | |||||
<div class="dropdown-menu dropdown-menu-right"> | |||||
{% for action in entity.actions %} | |||||
{{ include(action.templatePath, { action: action, isIncludedInDropdown: ea.crud.showEntityActionsAsDropdown }, with_context = false) }} | |||||
{% endfor %} | |||||
</div> | |||||
</div> | |||||
{% endif %} | |||||
</td> | |||||
{% endblock entity_actions %} | |||||
</tr> | |||||
{% endif %} | |||||
{% else %} | |||||
<tr> | |||||
<td class="no-results" colspan="100"> | |||||
{{ 'datagrid.no_results'|trans(ea.i18n.translationParameters, 'EasyAdminBundle') }} | |||||
</td> | |||||
</tr> | |||||
{% endfor %} | |||||
{% if some_results_are_hidden %} | |||||
<tr class="datagrid-row-empty"> | |||||
<td class="text-center" colspan="{{ entities|first.fields|length + 1 }}"> | |||||
<span class="datagrid-row-empty-message"><i | |||||
class="fa fa-lock mr-1"></i> {{ 'datagrid.hidden_results'|trans({}, 'EasyAdminBundle') }}</span> | |||||
</td> | |||||
</tr> | |||||
{% endif %} | |||||
{% endblock table_body %} | |||||
</tbody> | |||||
</table> | |||||
</div> | |||||
{% if entities|length > 0 %} | |||||
<div class="card-footer d-flex align-items-center"> | |||||
{% block paginator %} | |||||
{{ include(ea.templatePath('crud/paginator')) }} | |||||
{% endblock paginator %} | |||||
</div> | |||||
{% endif %} | |||||
</div> | |||||
{% block delete_form %} | |||||
{{ include('@EasyAdmin/crud/includes/_delete_form.html.twig', with_context = false) }} | |||||
{% endblock delete_form %} | |||||
{% if has_filters %} | |||||
{{ include('@EasyAdmin/crud/includes/_filters_modal.html.twig') }} | |||||
{% endif %} | |||||
{% if has_batch_actions %} | |||||
{{ include('@EasyAdmin/crud/includes/_batch_action_modal.html.twig', {}, with_context = false) }} | |||||
{% endif %} | |||||
{% endblock main %} | |||||
{% block body_javascript %} | |||||
{{ parent() }} | |||||
<script type="text/javascript"> | |||||
$(function () { | |||||
const customSwitches = document.querySelectorAll('td.field-boolean .custom-control.custom-switch input[type="checkbox"]'); | |||||
for (i = 0; i < customSwitches.length; i++) { | |||||
customSwitches[i].addEventListener('change', function () { | |||||
const customSwitch = this; | |||||
const newValue = this.checked; | |||||
const oldValue = !newValue; | |||||
const toggleUrl = this.getAttribute('data-toggle-url') + "&newValue=" + newValue.toString(); | |||||
let toggleRequest = $.ajax({type: "GET", url: toggleUrl, data: {}}); | |||||
toggleRequest.done(function (result) { | |||||
}); | |||||
toggleRequest.fail(function () { | |||||
// in case of error, restore the original value and disable the toggle | |||||
customSwitch.checked = oldValue; | |||||
customSwitch.disabled = true; | |||||
customSwitch.closest('.custom-switch').classList.add('disabled'); | |||||
}); | |||||
}); | |||||
} | |||||
$('.action-delete').on('click', function (e) { | |||||
e.preventDefault(); | |||||
const formAction = $(this).attr('formaction'); | |||||
$('#modal-delete').modal({backdrop: true, keyboard: true}) | |||||
.off('click', '#modal-delete-button') | |||||
.on('click', '#modal-delete-button', function () { | |||||
let deleteForm = $('#delete-form'); | |||||
deleteForm.attr('action', formAction); | |||||
deleteForm.submit(); | |||||
}); | |||||
}); | |||||
{% if filters|length > 0 %} | |||||
const filterModal = document.querySelector('#modal-filters'); | |||||
const removeFilter = function (field) { | |||||
field.closest('form').querySelectorAll('input[name^="filters[' + field.dataset.filterProperty + ']"]').forEach(hidden => { | |||||
hidden.remove(); | |||||
}); | |||||
field.remove(); | |||||
} | |||||
document.querySelector('#modal-clear-button').addEventListener('click', function () { | |||||
filterModal.querySelectorAll('.filter-field').forEach(filterField => { | |||||
removeFilter(filterField); | |||||
}); | |||||
filterModal.querySelector('form').submit(); | |||||
}); | |||||
document.querySelector('#modal-apply-button').addEventListener('click', function () { | |||||
filterModal.querySelectorAll('.filter-checkbox:not(:checked)').forEach(notAppliedFilter => { | |||||
removeFilter(notAppliedFilter.closest('.filter-field')); | |||||
}); | |||||
filterModal.querySelector('form').submit(); | |||||
}); | |||||
// HTML5 specifies that a <script> tag inserted with innerHTML should not execute | |||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#Security_considerations | |||||
// That's why we can't use just 'innerHTML'. See https://stackoverflow.com/a/47614491/2804294 | |||||
let setInnerHTML = function (element, htmlContent) { | |||||
element.innerHTML = htmlContent; | |||||
Array.from(element.querySelectorAll('script')).forEach(oldScript => { | |||||
const newScript = document.createElement('script'); | |||||
Array.from(oldScript.attributes) | |||||
.forEach(attr => newScript.setAttribute(attr.name, attr.value)); | |||||
newScript.appendChild(document.createTextNode(oldScript.innerHTML)); | |||||
oldScript.parentNode.replaceChild(newScript, oldScript); | |||||
}); | |||||
}; | |||||
let filterButton = document.querySelector('.action-filters-button'); | |||||
filterButton.addEventListener('click', function (event) { | |||||
let filterModal = document.querySelector(filterButton.dataset.modal); | |||||
let filterModalBody = filterModal.querySelector('.modal-body'); | |||||
$(filterModal).modal({backdrop: true, keyboard: true}); | |||||
filterModalBody.innerHTML = '<div class="fa-3x px-3 py-3 text-muted text-center"><i class="fas fa-circle-notch fa-spin"></i></div>'; | |||||
$.get(filterButton.getAttribute('href'), function (response) { | |||||
setInnerHTML(filterModalBody, response); | |||||
}); | |||||
event.preventDefault(); | |||||
event.stopPropagation(); | |||||
}); | |||||
filterButton.setAttribute('href', filterButton.getAttribute('data-href')); | |||||
filterButton.removeAttribute('data-href'); | |||||
filterButton.classList.remove('disabled'); | |||||
{% endif %} | |||||
{% if has_batch_actions %} | |||||
const titleContent = $('.content-header-title > .title').html(); | |||||
$(document).on('click', '.deselect-batch-button', function () { | |||||
$(this).closest('.content').find(':checkbox.form-batch-checkbox-all').prop('checked', false).trigger('change'); | |||||
}); | |||||
$(document).on('change', '.form-batch-checkbox-all', function () { | |||||
$(this).closest('.content').find(':checkbox.form-batch-checkbox').prop('checked', $(this).prop('checked')).trigger('change'); | |||||
}); | |||||
$(document).on('change', '.form-batch-checkbox', function () { | |||||
const $content = $(this).closest('.content'); | |||||
let $input = $content.find(':hidden#batch_form_entityIds'); | |||||
let ids = $input.val() ? $input.val().split(',') : []; | |||||
const id = $(this).val(); | |||||
if ($(this).prop('checked')) { | |||||
$(this).closest('tr').addClass('selected-row'); | |||||
if (-1 === ids.indexOf(id)) { | |||||
ids.push(id); | |||||
} | |||||
} else { | |||||
$(this).closest('tr').removeClass('selected-row'); | |||||
ids = ids.filter(function (value) { | |||||
return value !== id | |||||
}); | |||||
$content.find(':checkbox.form-batch-checkbox-all').prop('checked', false); | |||||
} | |||||
if (0 === ids.length) { | |||||
$content.find('.global-actions').show(); | |||||
$content.find('.batch-actions').hide(); | |||||
$content.find('table').removeClass('table-batch'); | |||||
} else { | |||||
$content.find('.batch-actions').show(); | |||||
$content.find('.global-actions').hide(); | |||||
$content.find('table').addClass('table-batch'); | |||||
} | |||||
$input.val(ids.join(',')); | |||||
$content.find('.content-header-title > .title').html(0 === ids.length ? titleContent : ''); | |||||
}); | |||||
let modalTitle = $('#batch-action-confirmation-title'); | |||||
const titleContentWithPlaceholders = modalTitle.text(); | |||||
$('[data-action-batch]').on('click', function (event) { | |||||
event.preventDefault(); | |||||
event.stopPropagation(); | |||||
let $actionElement = $(this); | |||||
const actionName = $actionElement.attr('data-action-name'); | |||||
const selectedItems = $('input[type="checkbox"].form-batch-checkbox:checked'); | |||||
modalTitle.text(titleContentWithPlaceholders | |||||
.replace('%action_name%', actionName) | |||||
.replace('%num_items%', selectedItems.length)); | |||||
$('#modal-batch-action').modal({backdrop: true, keyboard: true}) | |||||
.off('click', '#modal-batch-action-button') | |||||
.on('click', '#modal-batch-action-button', function () { | |||||
$actionElement.unbind('click'); | |||||
$form = document.createElement('form'); | |||||
$form.setAttribute('action', $actionElement.attr('data-action-url')); | |||||
$form.setAttribute('method', 'POST'); | |||||
$actionNameInput = document.createElement('input'); | |||||
$actionNameInput.setAttribute('type', 'hidden'); | |||||
$actionNameInput.setAttribute('name', 'batchActionName'); | |||||
$actionNameInput.setAttribute('value', $actionElement.attr('data-action-name')); | |||||
$form.appendChild($actionNameInput); | |||||
$entityFqcnInput = document.createElement('input'); | |||||
$entityFqcnInput.setAttribute('type', 'hidden'); | |||||
$entityFqcnInput.setAttribute('name', 'entityFqcn'); | |||||
$entityFqcnInput.setAttribute('value', $actionElement.attr('data-entity-fqcn')); | |||||
$form.appendChild($entityFqcnInput); | |||||
$actionUrlInput = document.createElement('input'); | |||||
$actionUrlInput.setAttribute('type', 'hidden'); | |||||
$actionUrlInput.setAttribute('name', 'batchActionUrl'); | |||||
$actionUrlInput.setAttribute('value', $actionElement.attr('data-action-url')); | |||||
$form.appendChild($actionUrlInput); | |||||
$csrfTokenInput = document.createElement('input'); | |||||
$csrfTokenInput.setAttribute('type', 'hidden'); | |||||
$csrfTokenInput.setAttribute('name', 'batchActionCsrfToken'); | |||||
$csrfTokenInput.setAttribute('value', $actionElement.attr('data-action-csrf-token')); | |||||
$form.appendChild($csrfTokenInput); | |||||
selectedItems.each((i, item) => { | |||||
$entityIdInput = document.createElement('input'); | |||||
$entityIdInput.setAttribute('type', 'hidden'); | |||||
$entityIdInput.setAttribute('name', `batchActionEntityIds[${i}]`); | |||||
$entityIdInput.setAttribute('value', item.value); | |||||
$form.appendChild($entityIdInput); | |||||
}); | |||||
document.body.appendChild($form); | |||||
//modalTitle.text(titleContentWithPlaceholders); | |||||
$form.submit(); | |||||
}); | |||||
}); | |||||
{% endif %} | |||||
}); | |||||
</script> | |||||
{% if app.request.get('query') is not empty %} | |||||
<script type="text/javascript"> | |||||
const search_query = "{{ ea.search.query|default('')|e('js') }}"; | |||||
// the original query is prepended to allow matching exact phrases in addition to single words | |||||
$('#main').find('table tbody td:not(.actions)').highlight($.merge([search_query], search_query.split(' '))); | |||||
</script> | |||||
{% endif %} | |||||
{% endblock %} |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} | |||||
{% extends ea.templatePath('layout') %} | |||||
{% form_theme new_form with ea.crud.formThemes only %} | |||||
{% trans_default_domain ea.i18n.translationDomain %} | |||||
{% block body_id 'ea-new-' ~ entity.name ~ '-' ~ entity.primaryKeyValue %} | |||||
{% block body_class 'ea-new ea-new-' ~ entity.name %} | |||||
{% block configured_head_contents %} | |||||
{{ parent() }} | |||||
{% for htmlContent in new_form.vars.ea_crud_form.assets.headContents %} | |||||
{{ htmlContent|raw }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block configured_stylesheets %} | |||||
{{ parent() }} | |||||
{% for css_asset in new_form.vars.ea_crud_form.assets.cssFiles %} | |||||
<link rel="stylesheet" href="{{ asset(css_asset) }}"> | |||||
{% endfor %} | |||||
{% for webpack_encore_entry in new_form.vars.ea_crud_form.assets.webpackEncoreEntries %} | |||||
{{ ea_call_function_if_exists('encore_entry_link_tags', webpack_encore_entry) }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block configured_javascripts %} | |||||
{{ parent() }} | |||||
{% for js_asset in new_form.vars.ea_crud_form.assets.jsFiles %} | |||||
<script src="{{ asset(js_asset) }}"></script> | |||||
{% endfor %} | |||||
{% for webpack_encore_entry in new_form.vars.ea_crud_form.assets.webpackEncoreEntries %} | |||||
{{ ea_call_function_if_exists('encore_entry_script_tags', webpack_encore_entry) }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block content_title %} | |||||
{# {%- apply spaceless -%} | |||||
{% set default_title = ea.crud.defaultPageTitle('new')|trans(ea.i18n.translationParameters, 'EasyAdminBundle') %} | |||||
{{ ea.crud.customPageTitle is null ? default_title|raw : ea.crud.customPageTitle('new')|trans(ea.i18n.translationParameters)|raw }} | |||||
{%- endapply -%}#} | |||||
{% endblock %} | |||||
{% block content_header_wrapper %} | |||||
{# {% for action in entity.actions %} | |||||
{{ include(action.templatePath, { action: action }, with_context = false) }} | |||||
{% endfor %}#} | |||||
{% endblock %} | |||||
{% block main %} | |||||
<div class="col-8"> | |||||
<div class="card"> | |||||
<div class="card-status-top bg-primary"></div> | |||||
<div class="card-header "> | |||||
{% set default_title = ea.crud.defaultPageTitle('new')|trans(ea.i18n.translationParameters, 'EasyAdminBundle') %} | |||||
<h2 class="card-title">{{ ea.crud.customPageTitle is null ? default_title|raw : ea.crud.customPageTitle('new')|trans(ea.i18n.translationParameters)|raw }}</h2> | |||||
</div> | |||||
<div class="card-body"> | |||||
<button type="button" data-bs-toggle="modal" data-bs-target="#niche">Launch modal</button> | |||||
<div class="modal-dialog" role="document" id="niche"> | |||||
<div class="modal-content"> | |||||
<div class="modal-header"> | |||||
<h5 class="modal-title">Modal title</h5> | |||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> | |||||
</div> | |||||
<div class="modal-body"> | |||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci animi beatae delectus deleniti dolorem eveniet facere fuga iste nemo nesciunt nihil odio perspiciatis, quia quis reprehenderit sit tempora totam unde. | |||||
</div> | |||||
<div class="modal-footer"> | |||||
<button type="button" class="btn me-auto" data-bs-dismiss="modal">Close</button> | |||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Save changes</button> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% block new_form %} | |||||
{{ form(new_form) }} | |||||
{% endblock new_form %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% endblock %} | |||||
{% block body_javascript %} | |||||
{{ parent() }} | |||||
{# <script type="text/javascript"> | |||||
$(function () { | |||||
$('.ea-new-form').areYouSure({'message': '{{ 'form.are_you_sure'|trans({}, 'EasyAdminBundle')|e('js') }}'}); | |||||
const entityForm = document.querySelector('form.ea-new-form'); | |||||
const inputFieldsSelector = 'input,select,textarea'; | |||||
// Adding visual feedback for invalid fields: any ".form-group" with invalid fields | |||||
// receives "has-error" class. The class is removed on click on the ".form-group" | |||||
// itself to support custom/complex fields. | |||||
entityForm.addEventListener('submit', function (submitEvent) { | |||||
entityForm.querySelectorAll(inputFieldsSelector).forEach(function (input) { | |||||
if (!input.validity.valid) { | |||||
const formGroup = input.closest('div.form-group'); | |||||
formGroup.classList.add('has-error'); | |||||
formGroup.addEventListener('click', function onFormGroupClick() { | |||||
formGroup.classList.remove('has-error'); | |||||
formGroup.removeEventListener('click', onFormGroupClick); | |||||
}); | |||||
} | |||||
}); | |||||
const eaEvent = new CustomEvent('ea.form.submit', { | |||||
cancelable: true, | |||||
detail: {page: 'new', form: entityForm} | |||||
}); | |||||
const eaEventResult = document.dispatchEvent(eaEvent); | |||||
if (false === eaEventResult) { | |||||
submitEvent.preventDefault(); | |||||
submitEvent.stopPropagation(); | |||||
} | |||||
}); | |||||
// forms with tabs require some special treatment for errors. The problem | |||||
// is when the field with errors is included in a tab not currently visible. | |||||
// Browser shows this error "An invalid form control with name='...' is not focusable." | |||||
// So, the user clicks on Submit button, the form is not submitted and the error | |||||
// is not displayed. This JavaScript code ensures that each tab shows a badge with | |||||
// the number of errors in it. | |||||
entityForm.addEventListener('submit', function () { | |||||
const formTabPanes = entityForm.querySelectorAll('.tab-pane'); | |||||
if (0 === formTabPanes.length) { | |||||
return; | |||||
} | |||||
let firstNavTabItemWithError = null; | |||||
formTabPanes.forEach(function (tabPane) { | |||||
let tabPaneNumErrors = 0; | |||||
tabPane.querySelectorAll(inputFieldsSelector).forEach(function (input) { | |||||
if (!input.validity.valid) { | |||||
tabPaneNumErrors++; | |||||
} | |||||
}); | |||||
let navTabItem = entityForm.querySelector('.nav-item a[href="#' + tabPane.id + '"]'); | |||||
let existingErrorBadge = navTabItem.querySelector('span.badge.badge-danger'); | |||||
if (null !== existingErrorBadge) { | |||||
navTabItem.removeChild(existingErrorBadge); | |||||
} | |||||
if (tabPaneNumErrors > 0) { | |||||
let newErrorBadge = document.createElement('span'); | |||||
newErrorBadge.classList.add('badge', 'badge-danger'); | |||||
newErrorBadge.title = 'form.tab.error_badge_title'; | |||||
newErrorBadge.textContent = tabPaneNumErrors; | |||||
navTabItem.appendChild(newErrorBadge); | |||||
if (null === firstNavTabItemWithError) { | |||||
firstNavTabItemWithError = navTabItem; | |||||
} | |||||
} | |||||
}); | |||||
if (firstNavTabItemWithError) { | |||||
firstNavTabItemWithError.click(); | |||||
} | |||||
}); | |||||
// prevent multiple form submissions to avoid creating duplicated entities | |||||
entityForm.addEventListener('submit', function () { | |||||
// this timeout is needed to include the disabled button into the submitted form | |||||
setTimeout(function () { | |||||
const submitButtons = entityForm.querySelectorAll('[type="submit"]'); | |||||
submitButtons.forEach(function (button) { | |||||
button.setAttribute('disabled', 'disabled'); | |||||
}); | |||||
}, 1); | |||||
}, false); | |||||
}); | |||||
</script> | |||||
{{ include('@EasyAdmin/crud/includes/_select2_widget.html.twig') }}#} | |||||
{% endblock %} |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{# @var paginator \EasyCorp\Bundle\EasyAdminBundle\Orm\EntityPaginator #} | |||||
{% trans_default_domain 'EasyAdminBundle' %} | |||||
<p class="m-0 text-muted"> | |||||
{{ 'paginator.results'|trans({'%count%': paginator.numResults})|raw }} | |||||
</p> | |||||
<ul class="pagination m-0 ms-auto"> | |||||
<li class="page-item {{ not paginator.hasPreviousPage ? 'disabled' }}"> | |||||
<a class="page-link" href="{{ not paginator.hasPreviousPage ? '#' : paginator.generateUrlForPage(paginator.previousPage) }}" {{ not paginator.hasPreviousPage ? 'aria-disabled="true"' }}> | |||||
<i class="ti ti-arrow-left"></i> <span class="btn-label">{{ 'paginator.previous'|trans }}</span> | |||||
</a> | |||||
</li> | |||||
{% for page in paginator.pageRange %} | |||||
<li class="page-item {{ page == paginator.currentPage ? 'active' }} {{ page is null ? 'disabled' }}"> | |||||
{% if page is null %} | |||||
<span class="page-link">…</span> | |||||
{% else %} | |||||
<a class="page-link" href="{{ paginator.generateUrlForPage(page) }}">{{ page }}</a> | |||||
{% endif %} | |||||
</li> | |||||
{% endfor %} | |||||
<li class="page-item {{ not paginator.hasNextPage ? 'disabled' }}"> | |||||
<a class="page-link" href="{{ not paginator.hasNextPage ? '#' : paginator.generateUrlForPage(paginator.nextPage) }}" {{ not paginator.hasNextPage ? 'aria-disabled="true"' }}> | |||||
<span class="btn-label">{{ 'paginator.next'|trans }}</span> <i class="ti ti-arrow-right"></i> | |||||
</a> | |||||
</li> | |||||
</ul> | |||||
{% extends '@LcSov/tabler/layout.html.twig' %} | |||||
{% block content_title 'SovBundle' %} | |||||
{% block main %} | |||||
Bien sur votre tableau de bord | |||||
Salut !!! | |||||
{% endblock %} | |||||
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{% trans_default_domain ea.i18n.translationDomain %} | |||||
<!DOCTYPE html> | |||||
<html lang="{{ ea.i18n.htmlLocale }}" dir="{{ ea.i18n.textDirection }}"> | |||||
<head> | |||||
{% block head_metas %} | |||||
<meta charset="utf-8"> | |||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||||
<meta name="robots" | |||||
content="noindex, nofollow, noarchive, nosnippet, noodp, noimageindex, notranslate, nocache"/> | |||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> | |||||
<meta name="generator" content="EasyAdmin"/> | |||||
{% endblock head_metas %} | |||||
<title>{% block page_title %}{{ block('content_title')|striptags|raw }}{% endblock %}</title> | |||||
{# {% block head_stylesheets %} | |||||
<link rel="stylesheet" href="{{ asset('bundles/easyadmin/app.css') }}"> | |||||
{% endblock %} #} | |||||
{% block configured_stylesheets %} | |||||
{% for css_asset in ea.assets.cssFiles ?? [] %} | |||||
<link rel="stylesheet" href="{{ asset(css_asset) }}"> | |||||
{% endfor %} | |||||
{% for webpack_encore_entry in ea.assets.webpackEncoreEntries ?? [] %} | |||||
{{ ea_call_function_if_exists('encore_entry_link_tags', webpack_encore_entry) }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block head_favicon %} | |||||
<link rel="shortcut icon" href="{{ asset(ea.dashboardFaviconPath) }}"> | |||||
{% endblock %} | |||||
{# {% block head_javascript %} | |||||
<script src="{{ asset('bundles/easyadmin/app.js') }}"></script> | |||||
{% endblock head_javascript %} #} | |||||
{# {% if 'rtl' == ea.i18n.textDirection %} | |||||
<link rel="stylesheet" href="{{ asset('bundles/easyadmin/app.rtl.css') }}"> | |||||
<link rel="stylesheet" href="{{ asset('bundles/easyadmin/app-custom-rtl.css') }}"> | |||||
{% endif %} #} | |||||
{% block configured_head_contents %} | |||||
{% for htmlContent in ea.assets.headContents ?? [] %} | |||||
{{ htmlContent|raw }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
</head> | |||||
{% block body %} | |||||
<body id="{% block body_id %}{% endblock %}" class="antialiased {% block body_class %}{% endblock %}"> | |||||
{% block javascript_page_layout %} | |||||
<script> | |||||
document.body.classList.add( | |||||
'ea-content-width-' + (localStorage.getItem('ea/content/width') || '{{ ea.crud.contentWidth ?? ea.dashboardContentWidth ?? 'normal' }}'), | |||||
'ea-sidebar-width-' + (localStorage.getItem('ea/sidebar/width') || '{{ ea.crud.sidebarWidth ?? ea.dashboardSidebarWidth ?? 'normal' }}') | |||||
); | |||||
</script> | |||||
{% endblock javascript_page_layout %} | |||||
{% block wrapper_wrapper %} | |||||
{% block flash_messages %} | |||||
{{ include(ea.templatePath('flash_messages')) }} | |||||
{% endblock flash_messages %} | |||||
<div class="wrapper"> | |||||
TABLER | |||||
{% block wrapper %} | |||||
{# <header class="main-header"> | |||||
{% block header %} | |||||
<nav class="navbar" role="navigation"> | |||||
{% block header_navbar %} | |||||
<button id="navigation-toggler" type="button" aria-label="Toggle navigation"> | |||||
<i class="fa fa-fw fa-bars"></i> | |||||
</button> | |||||
{% endblock header_navbar %} | |||||
</nav> | |||||
</header> #} | |||||
<aside class="navbar navbar-vertical navbar-expand-lg navbar-dark"> | |||||
<div class="container-fluid"> | |||||
{% block sidebar %} | |||||
{% block header_logo %} | |||||
<h1 class="navbar-brand navbar-brand-autodark"> | |||||
<a class="{{ ea.dashboardTitle|length > 14 ? 'logo-long' }}" | |||||
title="{{ ea.dashboardTitle|striptags }}" | |||||
href="{{ path(ea.dashboardRouteName) }}"> | |||||
{{ ea.dashboardTitle|raw }} | |||||
</a> | |||||
</h1> | |||||
{% endblock header_logo %} | |||||
<section class="sidebar"> | |||||
{% block main_menu_wrapper %} | |||||
{{ include(ea.templatePath('main_menu')) }} | |||||
{% endblock main_menu_wrapper %} | |||||
</section> | |||||
{% endblock sidebar %} | |||||
</div> | |||||
</aside> | |||||
<div class="page-wrapper"> | |||||
<div class="sticky-top"> | |||||
<header class="navbar navbar-expand-md navbar-light sticky-top d-print-none"> | |||||
<div class="container-xl"> | |||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" | |||||
data-bs-target="#navbar-menu"> | |||||
<span class="navbar-toggler-icon"></span> | |||||
</button> | |||||
<span class="push-menu"> | |||||
<i class="ti ti-antenna-bars-1"></i> | |||||
</span> | |||||
<div class="navbar-nav flex-row order-md-last"> | |||||
<div class="nav-item d-none d-md-flex me-3"> | |||||
<div class="btn-list"> | |||||
{#<a href="{{ path(homepage_route()) }}" class="btn btn-outline-white" target="_blank" rel="noreferrer"> | |||||
Afficher le site | |||||
</a>#} | |||||
</div> | |||||
</div> | |||||
<div class="nav-item dropdown"> | |||||
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" | |||||
data-bs-toggle="dropdown" | |||||
aria-label="Open user menu"> | |||||
<span class="ti ti-user"></span> | |||||
<div class="d-none d-xl-block ps-2"> | |||||
<div>{{ ea.user is null ? 'user.anonymous'|trans(domain = 'EasyAdminBundle') : ea.userMenu.name }}</div> | |||||
{% if is_granted('ROLE_SUPER_ADMIN') %} | |||||
<div class="mt-1 small text-muted">Superadmin</div> | |||||
{% elseif is_granted('ROLE_ADMIN') %} | |||||
<div class="mt-1 small text-muted">Admin</div> | |||||
{% else %} | |||||
<div class="mt-1 small text-muted">NC</div> | |||||
{% endif %} | |||||
</div> | |||||
</a> | |||||
{% block user_menu %} | |||||
{% if ea.userMenu.items|length > 0 %} | |||||
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow"> | |||||
{% for item in ea.userMenu.items %} | |||||
{% if item.isMenuSection %} | |||||
<hr class="m-0"/> | |||||
{% else %} | |||||
<a href="{{ item.linkUrl }}" | |||||
class="dropdown-item {{ item.cssClass }}" | |||||
target="{{ item.linkTarget }}" rel="{{ item.linkRel }}" | |||||
referrerpolicy="origin-when-cross-origin"> | |||||
{% if item.icon is not empty %} | |||||
<i class="ta ta-{{ item.icon }}"></i> | |||||
{% endif %} | |||||
<span>{{ item.label }}</span> | |||||
</a> | |||||
{% endif %} | |||||
{% endfor %} | |||||
</div> | |||||
{% endif %} | |||||
{% endblock user_menu %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</header> | |||||
</div> | |||||
{% block content %} | |||||
{% block content_header_wrapper %} | |||||
<div class="container-xl"> | |||||
<div class="page-header d-print-none"> | |||||
<div class="row align-items-center"> | |||||
{% set has_help_message = (ea.crud.helpMessage ?? '') is not empty %} | |||||
{% block content_header %} | |||||
<div class="col"> | |||||
<h2 class="page-title"> | |||||
{% block content_title %}{% endblock %} | |||||
</h2> | |||||
{% block content_help %} | |||||
{% if has_help_message %} | |||||
<div class="text-muted"> | |||||
{{ ea.crud.helpMessage|e('html_attr') }} | |||||
</div> | |||||
{% endif %} | |||||
{% endblock %} | |||||
</div> | |||||
{% block page_actions_wrapper %} | |||||
<div class="col-auto ms-auto d-print-none"> | |||||
<div class="btn-list"> | |||||
{% block page_actions %}{% endblock %} | |||||
</div> | |||||
</div> | |||||
{% endblock %} | |||||
{% endblock %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% endblock %} | |||||
<div class="page-body"> | |||||
<div class="container-xl"> | |||||
{% block main %}{% endblock %} | |||||
</div> | |||||
</div> | |||||
{% block content_footer_wrapper %} | |||||
{% set content_footer = block('content_footer') is defined ? block('content_footer') : '' %} | |||||
{% if content_footer is not empty %} | |||||
<footer class="footer footer-transparent d-print-none"> | |||||
<div class="container"> | |||||
<div class="row text-center align-items-center flex-row-reverse"> | |||||
{{ content_footer }} | |||||
</div> | |||||
</div> | |||||
</footer> | |||||
{% endif %} | |||||
{% endblock %} | |||||
{% endblock %} | |||||
</div> | |||||
{% endblock wrapper %} | |||||
</div> | |||||
{% endblock wrapper_wrapper %} | |||||
{% block configured_javascripts %} | |||||
{% for js_asset in ea.assets.jsFiles ?? [] %} | |||||
<script src="{{ asset(js_asset) }}"></script> | |||||
{% endfor %} | |||||
{% for webpack_encore_entry in ea.assets.webpackEncoreEntries ?? [] %} | |||||
{{ ea_call_function_if_exists('encore_entry_script_tags', webpack_encore_entry) }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
{% block body_javascript %}{% endblock body_javascript %} | |||||
{% block configured_body_contents %} | |||||
{% for htmlContent in ea.assets.bodyContents ?? [] %} | |||||
{{ htmlContent|raw }} | |||||
{% endfor %} | |||||
{% endblock %} | |||||
</body> | |||||
{% endblock body %} | |||||
</html> |
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} | |||||
{% macro render_menu_item(item, is_sub_item = false) %} | |||||
{% if item.isMenuSection %} | |||||
{% if item.icon is not empty %} | |||||
<span class="nav-link-icon d-md-none d-lg-inline-block"> | |||||
<i class="ti ti-{{ item.icon }}"></i> | |||||
</span> | |||||
{% endif %} | |||||
<span class="nav-link-title {{ item.cssClass }}">{{ item.label }}</span> | |||||
{% else %} | |||||
<a href="{{ item.linkUrl }}" class="{{ is_sub_item ? 'dropdown-item' : 'nav-link' }} {{ item.cssClass }} {{ item.hasSubItems ? 'dropdown-toggle' }}" | |||||
target="{{ item.linkTarget }}" rel="{{ item.linkRel }}" referrerpolicy="origin-when-cross-origin" | |||||
{{ item.hasSubItems ? 'data-bs-toggle="dropdown" role="button" aria-expanded="false"' }} > | |||||
{% if item.icon is not empty %} | |||||
<span class="nav-link-icon d-md-none d-lg-inline-block"> | |||||
<i class="ti ti-{{ item.icon }}"></i> | |||||
</span> | |||||
{% endif %} | |||||
<span class="nav-link-title">{{ item.label|raw }}</span> | |||||
{% if item.hasSubItems %}<i class="fa fa-fw fa-angle-right treeview-icon"></i>{% endif %} | |||||
</a> | |||||
{% endif %} | |||||
{% endmacro %} | |||||
{% block main_menu_before %}{% endblock %} | |||||
<div class="collapse navbar-collapse" id="navbar-menu"> | |||||
<ul class="navbar-nav pt-lg-3"> | |||||
{% block main_menu %} | |||||
{% for menuItem in ea.mainMenu.items %} | |||||
{% block menu_item %} | |||||
<li class="nav-item {{ menuItem.isMenuSection ? 'header' }} {{ menuItem.hasSubItems ? 'dropdown' }} {{ ea.mainMenu.isSelected(menuItem) ? 'active' }} {{ ea.mainMenu.isExpanded(menuItem) ? 'active submenu-active' }}"> | |||||
{{ _self.render_menu_item(menuItem) }} | |||||
{% if menuItem.hasSubItems %} | |||||
<div class="dropdown-menu"> | |||||
<div class="dropdown-menu-columns"> | |||||
<div class="dropdown-menu-column"> | |||||
{% for menuSubItem in menuItem.subItems %} | |||||
{% block menu_subitem %} | |||||
{{ _self.render_menu_item(menuSubItem, true) }} | |||||
{% endblock menu_subitem %} | |||||
{% endfor %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% endif %} | |||||
</li> | |||||
{% endblock menu_item %} | |||||
{% endfor %} | |||||
{% endblock main_menu %} | |||||
</ul> | |||||
</div> | |||||
{% block main_menu_after %}{% endblock %} | |||||
<!-- <a class="dropdown-item" href="./empty.html"> | |||||
Empty page | |||||
</a> --> |
{% extends '@LcSov/adminlte/layout.html.twig' %} | |||||
{% block content_title %} | |||||
Changer de mot de passe | |||||
{% endblock %} | |||||
{% block main %} | |||||
<div class="col-8"> | |||||
{% embed '@LcSov/adminlte/embed/card.html.twig' %} | |||||
{% block header_wrapper %}{% endblock %} | |||||
{% block body %} | |||||
{% form_theme form '@LcSov/adminlte/crud/form_theme.html.twig' %} | |||||
{{ form(form) }} | |||||
{% endblock %} | |||||
{% block footer_wrapper %}{% endblock %} | |||||
{% endembed %} | |||||
</div> | |||||
{% endblock main %} |
{% extends '@LcSov/adminlte/layout.html.twig' %} | |||||
{% block content_title %} | |||||
Mes informations personnelles | |||||
{% endblock %} | |||||
{% block main %} | |||||
<div class="col-8"> | |||||
{% embed '@LcSov/adminlte/embed/card.html.twig' %} | |||||
{% block header_wrapper %}{% endblock %} | |||||
{% block body %} | |||||
{% form_theme form '@LcSov/adminlte/crud/form_theme.html.twig' %} | |||||
{{ form(form) }} | |||||
{% endblock %} | |||||
{% block footer_wrapper %}{% endblock %} | |||||
{% endembed %} | |||||
</div> | |||||
{% endblock main %} |
{% extends '@LcSov/adminlte/layout.html.twig' %} | |||||
{% block main %} | |||||
Demander un nouveau mot de passe | |||||
{% endblock main %} |