@@ -24,15 +24,19 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P | |||
{ | |||
use TargetPathTrait; | |||
public const LOGIN_ROUTE = 'login'; | |||
public const LOGIN_ROUTE = 'sov_login'; | |||
private $entityManager; | |||
private $urlGenerator; | |||
private $csrfTokenManager; | |||
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->urlGenerator = $urlGenerator; | |||
$this->csrfTokenManager = $csrfTokenManager; | |||
@@ -42,19 +46,19 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P | |||
public function supports(Request $request) | |||
{ | |||
return self::LOGIN_ROUTE === $request->attributes->get('_route') | |||
&& $request->isMethod('POST'); | |||
&& $request->isMethod('POST'); | |||
} | |||
public function getCredentials(Request $request) | |||
{ | |||
$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( | |||
Security::LAST_USERNAME, | |||
$credentials['email'] | |||
Security::LAST_USERNAME, | |||
$credentials['email'] | |||
); | |||
return $credentials; | |||
@@ -67,7 +71,9 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P | |||
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) { | |||
// fail authentication with a custom error | |||
@@ -92,11 +98,15 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P | |||
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() |
@@ -67,6 +67,7 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
} | |||
); | |||
} | |||
/* Boutons des actions dans les listes */ | |||
$actionsArray[Crud::PAGE_INDEX] = [ | |||
Action::NEW => [ | |||
@@ -150,7 +151,7 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
} | |||
if (isset($button['icon'])) { | |||
$action->setIcon('fa fa-'.$button['icon']); | |||
$action->setIcon('fa fa-' . $button['icon']); | |||
} | |||
if (isset($button['label'])) { | |||
@@ -170,6 +171,15 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
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 | |||
{ | |||
$crud = parent::configureCrud($crud); | |||
@@ -183,7 +193,7 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
{ | |||
$entityClass = $this->getEntityFqcn(); | |||
$paramListMaxResults = 'listMaxResults'; | |||
$paramSessionListMaxResults = $entityClass.'-'.$paramListMaxResults; | |||
$paramSessionListMaxResults = $entityClass . '-' . $paramListMaxResults; | |||
$requestListMaxResults = $this->request->getCurrentRequest()->get($paramListMaxResults); | |||
if ($requestListMaxResults) { | |||
@@ -196,17 +206,36 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
$crud->setPaginatorPageSize($maxResults); | |||
} | |||
public function index(AdminContext $context) | |||
public function configureFields(string $pageName): iterable | |||
{ | |||
$responseParameters = parent::index($context); | |||
if (in_array(SeoInterface::class, class_implements($this->getEntityFqcn()))) { | |||
$seoPanel = [ | |||
FormField::addPanel('Seo'), | |||
TextField::new('metaTitle')->setLabel('Meta Title')->setHelp( | |||
'Affiché dans les résultats de recherche Google' | |||
)->hideOnIndex(), | |||
TextareaField::new('metaDescription')->setLabel('Meta description')->setHelp( | |||
'Affiché dans les résultats de recherche Google' | |||
)->hideOnIndex(), | |||
CollectionField::new('oldUrls') | |||
->setFormTypeOption('entry_type', TextType::class)->setLabel( | |||
'Anciennes urls du document' | |||
)->hideOnIndex(), | |||
]; | |||
} | |||
// Liste des fields | |||
$responseParameters->set('fields', $this->configureFields('index')); | |||
if (in_array(DevAliasInterface::class, class_implements($this->getEntityFqcn()))) { | |||
$confPanel = [ | |||
FormField::addPanel('Conf'), | |||
TextField::new('devAlias')->hideOnIndex(), | |||
]; | |||
} | |||
return $responseParameters; | |||
return array_merge($seoPanel, $confPanel); | |||
} | |||
public function sort(AdminContext $context) | |||
{ | |||
$event = new BeforeCrudActionEvent($context); | |||
@@ -303,34 +332,5 @@ abstract class AbstractCrudController extends EaAbstractCrudController | |||
return $responseParameters; | |||
} | |||
public function configureFields(string $pageName): iterable | |||
{ | |||
if (in_array(SeoInterface::class, class_implements($this->getEntityFqcn()))) { | |||
$seoPanel = [ | |||
FormField::addPanel('Seo'), | |||
TextField::new('metaTitle')->setLabel('Meta Title')->setHelp( | |||
'Affiché dans les résultats de recherche Google' | |||
)->hideOnIndex(), | |||
TextareaField::new('metaDescription')->setLabel('Meta description')->setHelp( | |||
'Affiché dans les résultats de recherche Google' | |||
)->hideOnIndex(), | |||
CollectionField::new('oldUrls') | |||
->setFormTypeOption('entry_type', TextType::class)->setLabel( | |||
'Anciennes urls du document' | |||
)->hideOnIndex(), | |||
]; | |||
} | |||
if (in_array(DevAliasInterface::class, class_implements($this->getEntityFqcn()))) { | |||
$confPanel = [ | |||
FormField::addPanel('Conf'), | |||
TextField::new('devAlias')->hideOnIndex(), | |||
]; | |||
} | |||
return array_merge($seoPanel, $confPanel); | |||
} | |||
} | |||
@@ -63,8 +63,8 @@ class DashboardController extends AbstractDashboardController | |||
->setMenuItems( | |||
[ | |||
//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') | |||
] | |||
); | |||
} |
@@ -2,7 +2,6 @@ | |||
namespace Lc\SovBundle\Controller\Admin; | |||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |||
use Symfony\Component\HttpFoundation\Response; | |||
use Symfony\Component\Routing\Annotation\Route; | |||
@@ -14,7 +13,7 @@ class SecurityController extends AbstractController | |||
public function login(AuthenticationUtils $authenticationUtils): Response | |||
{ | |||
if ($this->getUser()) { | |||
return $this->redirectToRoute('lc_'); | |||
return $this->redirectToRoute('admin_dashboard'); | |||
} | |||
// get the login error if there is one | |||
@@ -45,7 +44,7 @@ class SecurityController extends AbstractController | |||
'csrf_token_intention' => 'authenticate', | |||
// 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) | |||
'username_label' => 'Your username', |
@@ -0,0 +1,73 @@ | |||
<?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() | |||
] | |||
); | |||
} | |||
} |
@@ -2,16 +2,19 @@ | |||
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; | |||
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') | |||
]; | |||
} | |||
} |
@@ -22,7 +22,7 @@ class LcSovExtension extends Extension implements PrependExtensionInterface | |||
$container->setParameter(sprintf('lc_sov.%s', $parameter), $value); | |||
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); | |||
$loader->load('services.yml'); | |||
$loader->load('services.yaml'); | |||
} | |||
public function prepend(ContainerBuilder $container) |
@@ -0,0 +1,93 @@ | |||
<?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), | |||
] | |||
); | |||
} | |||
} |
@@ -0,0 +1,80 @@ | |||
<?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), | |||
] | |||
); | |||
} | |||
} |
@@ -11,150 +11,154 @@ use Symfony\Component\Security\Core\User\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; | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
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 | |||
@@ -0,0 +1,24 @@ | |||
services: | |||
_defaults: | |||
autowire: true # Automatically injects dependencies in your services. | |||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | |||
Lc\SovBundle\: | |||
resource: '../../' | |||
exclude: | |||
- '../../DependencyInjection/' | |||
- '../../Entity/' | |||
- '../../Kernel.php' | |||
- '../../Tests/' | |||
Lc\SovBundle\Controller\: | |||
resource: '../../Controller/' | |||
tags: [ 'controller.service_arguments' ] | |||
Lc\SovBundle\Form\Type\Crud\CrudFormType: | |||
decorates: EasyCorp\Bundle\EasyAdminBundle\Form\Type\CrudFormType | |||
arguments: ['@form.type_guesser.doctrine', '@.inner'] | |||
# EasyCorp\Bundle\EasyAdminBundle\Form\Type\CrudFormType: | |||
# class: Lc\SovBundle\Form\Type\CrudFormType |
@@ -1,24 +0,0 @@ | |||
services: | |||
_defaults: | |||
autowire: true # Automatically injects dependencies in your services. | |||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | |||
Lc\SovBundle\: | |||
resource: '../../' | |||
exclude: | |||
- '../../DependencyInjection/' | |||
- '../../Entity/' | |||
- '../../Kernel.php' | |||
- '../../Tests/' | |||
Lc\SovBundle\Controller\: | |||
resource: '../../Controller/' | |||
tags: [ 'controller.service_arguments' ] | |||
Lc\SovBundle\Form\Type\Crud\CrudFormType: | |||
decorates: EasyCorp\Bundle\EasyAdminBundle\Form\Type\CrudFormType | |||
arguments: ['@form.type_guesser.doctrine', '@.inner'] | |||
# EasyCorp\Bundle\EasyAdminBundle\Form\Type\CrudFormType: | |||
# class: Lc\SovBundle\Form\Type\CrudFormType |
@@ -35,7 +35,7 @@ | |||
target="{{ item.linkTarget }}" rel="{{ item.linkRel }}" | |||
referrerpolicy="origin-when-cross-origin"> | |||
{% if item.icon is not empty %} | |||
<i class="ta ta-{{ item.icon }}"></i> | |||
<i class="fa fa-{{ item.icon }}"></i> | |||
{% endif %} | |||
<span>{{ item.label }}</span> | |||
</a> |
@@ -1,21 +0,0 @@ | |||
{# @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 %} |
@@ -1,174 +0,0 @@ | |||
{# @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 %} |
@@ -1,11 +0,0 @@ | |||
{# @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 %} |
@@ -1,10 +0,0 @@ | |||
{# @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 %} |
@@ -1,13 +0,0 @@ | |||
{# @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 %} |
@@ -1,18 +0,0 @@ | |||
<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> |
@@ -1,114 +0,0 @@ | |||
{# @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 %} | |||
@@ -1,411 +0,0 @@ | |||
{# @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 %} |
@@ -1,189 +0,0 @@ | |||
{# @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 %} |
@@ -1,31 +0,0 @@ | |||
{# @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> | |||
@@ -1,10 +0,0 @@ | |||
{% extends '@LcSov/tabler/layout.html.twig' %} | |||
{% block content_title 'SovBundle' %} | |||
{% block main %} | |||
Bien sur votre tableau de bord | |||
Salut !!! | |||
{% endblock %} | |||
@@ -1,254 +0,0 @@ | |||
{# @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> |
@@ -1,60 +0,0 @@ | |||
{# @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> --> |
@@ -0,0 +1,19 @@ | |||
{% 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 %} |
@@ -0,0 +1,18 @@ | |||
{% 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 %} |
@@ -0,0 +1,5 @@ | |||
{% extends '@LcSov/adminlte/layout.html.twig' %} | |||
{% block main %} | |||
Demander un nouveau mot de passe | |||
{% endblock main %} |