Browse Source

Premier commit projet SovBundle (Administration de La clic )

tags/0.1
Fab 3 years ago
commit
4ef072db13
65 changed files with 2818 additions and 0 deletions
  1. +1
    -0
      .gitignore
  2. +106
    -0
      Authenticator/LoginFormAuthenticator.php
  3. +125
    -0
      Command/CreateUserCommand.php
  4. +0
    -0
      Controller/.gitignore
  5. +64
    -0
      Controller/Admin/AbstractCrudController.php
  6. +60
    -0
      Controller/Admin/DashboardController.php
  7. +72
    -0
      Controller/Admin/SecurityController.php
  8. +31
    -0
      DependencyInjection/Configuration.php
  9. +34
    -0
      DependencyInjection/SovExtension.php
  10. +19
    -0
      Entity/Translation/EntityTranslation.php
  11. +30
    -0
      Event/EntityManager/EntityManagerEvent.php
  12. +63
    -0
      EventSubscriber/Translation/TranslationEasyAdminSubscriber.php
  13. +41
    -0
      Field/CKEditorField.php
  14. +79
    -0
      Field/CollectionField.php
  15. +50
    -0
      Field/FileManagerField.php
  16. +85
    -0
      Field/GalleryManagerField.php
  17. +85
    -0
      Form/Type/FileManagerType.php
  18. +9
    -0
      IModel/Cms/BlameableInterface.php
  19. +8
    -0
      IModel/Cms/DevAliasInterface.php
  20. +9
    -0
      IModel/Cms/FileInterface.php
  21. +10
    -0
      IModel/Cms/SeoInterface.php
  22. +8
    -0
      IModel/Cms/SluggableInterface.php
  23. +8
    -0
      IModel/Cms/SortableInterface.php
  24. +8
    -0
      IModel/Cms/StatusInterface.php
  25. +9
    -0
      IModel/Cms/TimestampableInterface.php
  26. +22
    -0
      IModel/Cms/TreeInterface.php
  27. +7
    -0
      IModel/EntityInterface.php
  28. +9
    -0
      IModel/Translation/TranslatableInterface.php
  29. +8
    -0
      IModel/User/UserInterface.php
  30. +102
    -0
      Manager/EntityManager.php
  31. +69
    -0
      Model/Cms/AbstractDocument.php
  32. +51
    -0
      Model/Cms/BlameableTrait.php
  33. +27
    -0
      Model/Cms/DevAliasTrait.php
  34. +73
    -0
      Model/Cms/File.php
  35. +63
    -0
      Model/Cms/SeoTrait.php
  36. +27
    -0
      Model/Cms/SluggableTrait.php
  37. +34
    -0
      Model/Cms/SortableTrait.php
  38. +25
    -0
      Model/Cms/StatusTrait.php
  39. +46
    -0
      Model/Cms/TimestampableTrait.php
  40. +11
    -0
      Model/Cms/TreeTrait.php
  41. +53
    -0
      Model/Translation/TranslatableTrait.php
  42. +155
    -0
      Model/User/User.php
  43. +241
    -0
      README.md
  44. +0
    -0
      Repository/.gitignore
  45. +137
    -0
      Repository/BaseRepository.php
  46. +15
    -0
      Repository/BaseRepositoryInterface.php
  47. +20
    -0
      Repository/Cms/FileRepository.php
  48. +68
    -0
      Repository/User/UserRepository.php
  49. +0
    -0
      Resources/config/services.yml
  50. +93
    -0
      Resources/public/js/form-type-collection.js
  51. +29
    -0
      Resources/public/js/form-type-file-manager.js
  52. +13
    -0
      Resources/public/js/jquery-ui/jquery-ui.min.js
  53. +57
    -0
      Resources/public/js/utils.js
  54. +21
    -0
      Resources/views/crud/action/translatable.html.twig
  55. +8
    -0
      Resources/views/crud/edit.html.twig
  56. +11
    -0
      Resources/views/crud/field/collection.html.twig
  57. +10
    -0
      Resources/views/crud/field/gallery.html.twig
  58. +13
    -0
      Resources/views/crud/field/image.html.twig
  59. +18
    -0
      Resources/views/crud/filemanager/file-manager-modal.html.twig
  60. +100
    -0
      Resources/views/crud/form_theme.html.twig
  61. +9
    -0
      Resources/views/crud/new.html.twig
  62. +9
    -0
      Resources/views/dashboard.html.twig
  63. +15
    -0
      SovBundle.php
  64. +111
    -0
      Twig/TwigExtension.php
  65. +24
    -0
      composer.json

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
/.idea/*

+ 106
- 0
Authenticator/LoginFormAuthenticator.php View File

@@ -0,0 +1,106 @@
<?php

namespace Lc\SovBundle\Authenticator;

use Lc\SovBundle\IModel\User\UserInterface;
use Lc\SovBundle\Manager\EntityManager;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface as SfUserInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;

public const LOGIN_ROUTE = 'login';

private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;

public function __construct(EntityManager $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}

public function supports(Request $request)
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $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'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['email']
);

return $credentials;
}

public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}

$user = $this->entityManager->getRepository(UserInterface::class)->findOneBy(['email' => $credentials['email']]);

if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Email could not be found.');
}

return $user;
}

public function checkCredentials($credentials, SfUserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}

/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function getPassword($credentials): ?string
{
return $credentials['password'];
}

public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}

return new RedirectResponse($this->urlGenerator->generate('lc_admin_dashboard'));
}

protected function getLoginUrl()
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}

+ 125
- 0
Command/CreateUserCommand.php View File

@@ -0,0 +1,125 @@
<?php

namespace Lc\SovBundle\Command;


use Lc\SovBundle\IModel\User\UserInterface;
use Lc\SovBundle\Manager\EntityManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class CreateUserCommand extends Command
{
private $passwordEncoder;
private $em;

public function __construct(UserPasswordEncoderInterface $passwordEncoder, EntityManager $entityManager)
{
parent::__construct();

$this->passwordEncoder = $passwordEncoder;
$this->em = $entityManager;
}

/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('user:create')
->setDescription('Create a user.')
->setDefinition(array(
new InputArgument('email', InputArgument::REQUIRED, 'The email'),
new InputArgument('password', InputArgument::REQUIRED, 'The password'),
new InputArgument('role', InputArgument::REQUIRED, 'ROLE'),
))
->setHelp('');
}

/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{

$email = $input->getArgument('email');
$password = $input->getArgument('password');
$role = $input->getArgument('role');

$user = $this->em->new(UserInterface::class);
$user->setEmail($email);
$user->setRoles(array($role));

$password = $this->passwordEncoder->encodePassword($user, $password);

$user->setPassword($password);

$this->em->create($user);
$this->em->flush();

$output->writeln(sprintf('Created user <comment>%s</comment>', $email));
return Command::SUCCESS;
}

/**
* {@inheritdoc}
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
$questions = array();

if (!$input->getArgument('email')) {
$question = new Question('Email : ');
$question->setValidator(function ($password) {
if (empty($password)) {
throw new \Exception('Email can not be empty');
}

return $password;
});
$questions['email'] = $question;
}

if (!$input->getArgument('password')) {
$question = new Question('Password : ');
$question->setValidator(function ($password) {
if (empty($password)) {
throw new \Exception('Password can not be empty');
}

return $password;
});
$question->setHidden(true);
$questions['password'] = $question;
}

if (!$input->getArgument('role')) {
$question = new Question('Rôle [admin/superadmin/user] : ');
$question->setValidator(function ($role) {
if ($role == 'admin') {
$role = 'ROLE_ADMIN';
} else if ($role == 'superadmin') {
$role = 'ROLE_SUPER_ADMIN';
} else if ($role == 'user') {
$role = 'ROLE_USER';
} else {
throw new \Exception('Choose a role');

}
return $role;
});
$questions['role'] = $question;
}


foreach ($questions as $name => $question) {
$answer = $this->getHelper('question')->ask($input, $output, $question);
$input->setArgument($name, $answer);
}
}
}

+ 0
- 0
Controller/.gitignore View File


+ 64
- 0
Controller/Admin/AbstractCrudController.php View File

@@ -0,0 +1,64 @@
<?php

namespace Lc\SovBundle\Controller\Admin;

use App\Entity\Page;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Assets;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController as EaAbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Lc\SovBundle\IModel\Translation\TranslatableInterface;

abstract class AbstractCrudController extends EaAbstractCrudController
{

public function configureActions(Actions $actions): Actions
{
if (in_array(TranslatableInterface::class, class_implements($this->getEntityFqcn()))) {
$actions->update(Crud::PAGE_INDEX, Action::EDIT, function (Action $action) {
return $action->setTemplatePath('@Sov/crud/action/translatable.html.twig');
});
}

return $actions;
}

public function configureCrud(Crud $crud): Crud
{
return $crud
->overrideTemplates([
'crud/edit' => '@Sov/crud/edit.html.twig',
'crud/new' => '@Sov/crud/new.html.twig'
])
// don't forget to add EasyAdmin's form theme at the end of the list
// (otherwise you'll lose all the styles for the rest of form fields)
->setFormThemes(['@Sov/crud/form_theme.html.twig', '@FOSCKEditor/Form/ckeditor_widget.html.twig']);
}


public function configureAssets(Assets $assets): Assets
{
return $assets
// adds the CSS and JS assets associated to the given Webpack Encore entry
// it's equivalent to calling encore_entry_link_tags('...') and encore_entry_script_tags('...')
//->addWebpackEncoreEntry('admin-app')

// the argument of these methods is passed to the asset() Twig function
// CSS assets are added just before the closing </head> element
// and JS assets are added just before the closing </body> element
->addJsFile('bundles/lcadmin/js/utils.js');
}

/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
}

+ 60
- 0
Controller/Admin/DashboardController.php View File

@@ -0,0 +1,60 @@
<?php

namespace Lc\SovBundle\Controller\Admin;

use App\Entity\Page;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DashboardController extends AbstractDashboardController
{

public function index(): Response
{
return $this->render('@Sov/dashboard.html.twig');
}

public function configureDashboard(): Dashboard
{
return Dashboard::new()
// the name visible to end users
->setTitle('LA CLIC !')
// you can include HTML contents too (e.g. to link to an image)
->setTitle('<img src="assets/img/laclic.png" width="100px">')
// the path defined in this method is passed to the Twig asset() function
->setFaviconPath('favicon.svg')
// the domain used by default is 'messages'
->setTranslationDomain('lcadmin');
}

public function configureCrud(): Crud
{
$crud = Crud::new();
return $crud
->addFormTheme('@FOSCKEditor/Form/ckeditor_widget.html.twig');
}

public function configureMenuItems(): iterable
{
return [
MenuItem::linkToDashboard('Tableau de bord', 'fa fa-home'),
MenuItem::linkToCrud('Pages', 'fa fa-tags', Page::class),


/*
MenuItem::section('Blog'),
MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class),
MenuItem::linkToCrud('Blog Posts', 'fa fa-file-text', BlogPost::class),

MenuItem::section('Users'),
MenuItem::linkToCrud('Comments', 'fa fa-comment', Comment::class),
MenuItem::linkToCrud('Users', 'fa fa-user', User::class),*/

//MenuItem::linkToLogout('Déconnexion', 'fa fa-exit'),
];
}
}

+ 72
- 0
Controller/Admin/SecurityController.php View File

@@ -0,0 +1,72 @@
<?php

namespace Lc\SovBundle\Controller\Admin;


use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{

public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('lc_');
}

// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();

return $this->render('@EasyAdmin/page/login.html.twig', [
// parameters usually defined in Symfony login forms
'error' => $error,
'last_username' => $lastUsername,

// OPTIONAL parameters to customize the login form:

// the translation_domain to use (define this option only if you are
// rendering the login template in a regular Symfony controller; when
// rendering it from an EasyAdmin Dashboard this is automatically set to
// the same domain as the rest of the Dashboard)
'translation_domain' => 'admin',

// the title visible above the login form (define this option only if you are
// rendering the login template in a regular Symfony controller; when rendering
// it from an EasyAdmin Dashboard this is automatically set as the Dashboard title)
'page_title' => '<img src="assets/img/laclic.png" >',

// the string used to generate the CSRF token. If you don't define
// this parameter, the login form won't include a CSRF token
'csrf_token_intention' => 'authenticate',

// the URL users are redirected to after the login (default: '/admin')
'target_path' => $this->generateUrl('lc_admin_dashboard'),

// the label displayed for the username form field (the |trans filter is applied to it)
'username_label' => 'Your username',

// the label displayed for the password form field (the |trans filter is applied to it)
'password_label' => 'Your password',

// the label displayed for the Sign In form button (the |trans filter is applied to it)
'sign_in_label' => 'Log in',

// the 'name' HTML attribute of the <input> used for the username field (default: '_username')
'username_parameter' => 'email',

// the 'name' HTML attribute of the <input> used for the password field (default: '_password')
'password_parameter' => 'password',
]);
}


public function logout()
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}

+ 31
- 0
DependencyInjection/Configuration.php View File

@@ -0,0 +1,31 @@
<?php

namespace Lc\SovBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
* This is the class that validates and merges configuration from your app/config files
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('lc_admin');
$rootNode = $treeBuilder->getRootNode();

$rootNode
->children()
->scalarNode('dashboardDefault')->defaultValue('App\Controller\Admin\DashboardController')->end()
->end();


return $treeBuilder;
}
}

+ 34
- 0
DependencyInjection/SovExtension.php View File

@@ -0,0 +1,34 @@
<?php

namespace Lc\SovBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;


class SovExtension extends Extension implements PrependExtensionInterface
{


public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

foreach ($config as $parameter => $value)
$container->setParameter(sprintf('lc_admin.%s', $parameter), $value);

$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}

public function prepend(ContainerBuilder $container)
{
/*$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/easy_admin'));
$loader->load('base.yaml');
$loader->load('entities/merchant.yaml');*/
}
}

+ 19
- 0
Entity/Translation/EntityTranslation.php View File

@@ -0,0 +1,19 @@
<?php
namespace Lc\SovBundle\Entity\Translation;


use Doctrine\ORM\Mapping as ORM;
use Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation;

/**
* @ORM\Table(name="lc_translations_entity", indexes={
* @ORM\Index(name="entity_translation_idx", columns={"locale", "object_class", "field", "foreign_key"})
* })
* @ORM\Entity(repositoryClass="Lc\SovBundle\Repository\BaseRepository")
*/

class EntityTranslation extends AbstractTranslation
{
//put your code here
}

+ 30
- 0
Event/EntityManager/EntityManagerEvent.php View File

@@ -0,0 +1,30 @@
<?php

namespace Lc\SovBundle\Event\EntityManager;

use Lc\SovBundle\IModel\EntityInterface;
use Symfony\Contracts\EventDispatcher\Event;

/**
* class EntityEvent.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class EntityManagerEvent extends Event
{
const CREATE_EVENT = 'entity_manager_event.create';
const UPDATE_EVENT = 'entity_manager_event.update';
const DELETE_EVENT = 'entity_manager_event.delete';

protected EntityInterface $entity;

public function __construct(EntityInterface $entity)
{
$this->entity = $entity;
}

public function getEntity(): EntityInterface
{
return $this->entity;
}
}

+ 63
- 0
EventSubscriber/Translation/TranslationEasyAdminSubscriber.php View File

@@ -0,0 +1,63 @@
<?php

namespace Lc\SovBundle\EventSubscriber\Translation;

use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
use Gedmo\Translatable\TranslatableListener;
use Lc\SovBundle\IModel\Translation\TranslatableInterface;
use Lc\SovBundle\Manager\EntityManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function Symfony\Component\Translation\t;

class TranslationEasyAdminSubscriber implements EventSubscriberInterface
{
protected $em;

public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}

public static function getSubscribedEvents()
{
return [
BeforeCrudActionEvent::class => ['setTranslatableLocale']
];
}

public function setTranslatableLocale(BeforeCrudActionEvent $event)
{

$entity = $event->getAdminContext()->getEntity()->getInstance();
$_locale = $event->getAdminContext()->getRequest()->get('_locale');

if($entity instanceof TranslatableInterface){

//parcours les champs association pour détecter un champ de association qui est traduisible
foreach ($this->em->getClassMetadata(get_class($entity))->getAssociationMappings() as $associationMapping){
if (in_array(TranslatableInterface::class, class_implements($associationMapping["targetEntity"]))){
if($entity->__get($associationMapping['fieldName']) instanceof PersistentCollection){
foreach ($entity->__get($associationMapping['fieldName']) as $item){
$item->setTranslatableLocale($_locale);
$this->em->refresh($item);
}
}else{
if($entity->__get($associationMapping['fieldName'])) {
$entity->__get($associationMapping['fieldName'])->setTranslatableLocale($_locale);
$this->em->refresh($entity->__get($associationMapping['fieldName']));
}
}

}
}
$entity->setTranslatableLocale($_locale);
$this->em->refresh($entity);
}
}


}

+ 41
- 0
Field/CKEditorField.php View File

@@ -0,0 +1,41 @@
<?php

namespace Lc\SovBundle\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use FOS\CKEditorBundle\Form\Type\CKEditorType;

/**
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
final class CKEditorField implements FieldInterface
{
use FieldTrait;

public const OPTION_NUM_OF_ROWS = 'numOfRows';

public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplateName('crud/field/text_editor')
->setFormType(CKEditorType::class)
->addCssClass('field-text_editor')
->addCssFiles('bundles/easyadmin/form-type-text-editor.css')
->addJsFiles('bundles/easyadmin/form-type-text-editor.js')
->setCustomOption(self::OPTION_NUM_OF_ROWS, null);
}

public function setNumOfRows(int $rows): self
{
if ($rows < 1) {
throw new \InvalidArgumentException(sprintf('The argument of the "%s()" method must be 1 or higher (%d given).', __METHOD__, $rows));
}

$this->setCustomOption(self::OPTION_NUM_OF_ROWS, $rows);

return $this;
}
}

+ 79
- 0
Field/CollectionField.php View File

@@ -0,0 +1,79 @@
<?php

namespace Lc\SovBundle\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Lc\SovBundle\Form\Type\FileManagerType;
use Lc\SovBundle\Form\Type\GalleryManagerType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;


class CollectionField implements FieldInterface
{
use FieldTrait;

public const OPTION_ALLOW_ADD = 'allowAdd';
public const OPTION_ALLOW_DELETE = 'allowDelete';
public const OPTION_ENTRY_IS_COMPLEX = 'entryIsComplex';
public const OPTION_ENTRY_TYPE = 'entryType';
public const OPTION_SHOW_ENTRY_LABEL = 'showEntryLabel';

public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplatePath('@Sov/crud/field/collection.html.twig')
->setFormType(CollectionType::class)
->addCssClass('field-collection')
->addJsFiles('bundles/lcadmin/js/form-type-collection-array.js')
->setFormTypeOption('allow_add', true)
->setFormTypeOption('allow_delete', true)
->setFormTypeOption('entry_options', array('label' => false))
->setFormTypeOption('attr', array('class' => 'field-collection-group'))

//Fixe le bug easyadmin lors de la gestion d'un champ de type array, laisser a false pour une entité
->setFormTypeOption('row_attr', array('data-reindex-key' => true));

}

public function allowAdd(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_ADD, $allow);

return $this;
}

public function allowDelete(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_DELETE, $allow);

return $this;
}

/**
* Set this option to TRUE if the collection items are complex form types
* composed of several form fields (EasyAdmin applies a special rendering to make them look better).
*/
public function setEntryIsComplex(bool $isComplex): self
{
$this->setCustomOption(self::OPTION_ENTRY_IS_COMPLEX, $isComplex);

return $this;
}

public function setEntryType(string $formTypeFqcn): self
{
$this->setCustomOption(self::OPTION_ENTRY_TYPE, $formTypeFqcn);

return $this;
}

public function showEntryLabel(bool $showLabel = true): self
{
$this->setCustomOption(self::OPTION_SHOW_ENTRY_LABEL, $showLabel);

return $this;
}
}

+ 50
- 0
Field/FileManagerField.php View File

@@ -0,0 +1,50 @@
<?php

namespace Lc\SovBundle\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Lc\SovBundle\Form\Type\FileManagerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

/**
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
final class FileManagerField implements FieldInterface
{
use FieldTrait;

public const OPTION_MAX_LENGTH = 'maxLength';
public const OPTION_RENDER_AS_HTML = 'renderAsHtml';

public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplatePath('@Sov/crud/field/image.html.twig')
->addJsFiles('bundles/lcadmin/js/form-type-file-manager.js')
->setFormType(FileManagerType::class)
->addCssClass('field-text')
->setCustomOption(self::OPTION_MAX_LENGTH, null)
->setCustomOption(self::OPTION_RENDER_AS_HTML, false);
}

public function setMaxLength(int $length): self
{
if ($length < 1) {
throw new \InvalidArgumentException(sprintf('The argument of the "%s()" method must be 1 or higher (%d given).', __METHOD__, $length));
}

$this->setCustomOption(self::OPTION_MAX_LENGTH, $length);

return $this;
}

public function renderAsHtml(bool $asHtml = true): self
{
$this->setCustomOption(self::OPTION_RENDER_AS_HTML, $asHtml);

return $this;
}
}

+ 85
- 0
Field/GalleryManagerField.php View File

@@ -0,0 +1,85 @@
<?php

namespace Lc\SovBundle\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Lc\SovBundle\Form\Type\FileManagerType;
use Lc\SovBundle\Form\Type\GalleryManagerType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;


class GalleryManagerField implements FieldInterface
{
use FieldTrait;

public const OPTION_ALLOW_ADD = 'allowAdd';
public const OPTION_ALLOW_DELETE = 'allowDelete';
public const OPTION_ENTRY_IS_COMPLEX = 'entryIsComplex';
public const OPTION_ENTRY_TYPE = 'entryType';
public const OPTION_SHOW_ENTRY_LABEL = 'showEntryLabel';

public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplatePath('@Sov/crud/field/collection.html.twig')
->setFormType(CollectionType::class)
->addCssClass('field-collection')
->addJsFiles('bundles/lcadmin/js/form-type-collection.js')
->addJsFiles('bundles/lcadmin/js/form-type-file-manager.js')
->addJsFiles('bundles/lcadmin/js/jquery-ui/jquery-ui.min.js')
->setFormTypeOption('allow_add', true)
->setFormTypeOption('allow_delete', true)
->setFormTypeOption('entry_options', array('label'=> false))
->setFormTypeOption('entry_type', FileManagerType::class)
->setFormTypeOption('attr', array('class'=> 'field-collection-group'))
->setFormTypeOption('row_attr', array('data-sortable'=> true))
->hideOnIndex();

//->setEntryType(FileManagerType::class);

//->setFormTypeOption('show_entry_label', false);

}

public function allowAdd(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_ADD, $allow);

return $this;
}

public function allowDelete(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_DELETE, $allow);

return $this;
}

/**
* Set this option to TRUE if the collection items are complex form types
* composed of several form fields (EasyAdmin applies a special rendering to make them look better).
*/
public function setEntryIsComplex(bool $isComplex): self
{
$this->setCustomOption(self::OPTION_ENTRY_IS_COMPLEX, $isComplex);

return $this;
}

public function setEntryType(string $formTypeFqcn): self
{
$this->setCustomOption(self::OPTION_ENTRY_TYPE, $formTypeFqcn);

return $this;
}

public function showEntryLabel(bool $showLabel = true): self
{
$this->setCustomOption(self::OPTION_SHOW_ENTRY_LABEL, $showLabel);

return $this;
}
}

+ 85
- 0
Form/Type/FileManagerType.php View File

@@ -0,0 +1,85 @@
<?php

namespace Lc\SovBundle\Form\Type;

use Lc\SovBundle\DataTransformer\FileManagerTypeToDataTransformer;
use Lc\SovBundle\Entity\File\File;
use Lc\SovBundle\IModel\Cms\FileInterface;
use Lc\SovBundle\Manager\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class FileManagerType extends AbstractType implements DataTransformerInterface
{
protected $em;

public function __construct(EntityManager $entityManager){
$this->em = $entityManager;
}


public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('path', HiddenType::class, array(
'block_prefix' => 'file_manager_image',
'label' => false
));
$builder->add('legend', TextType::class, array(
'block_prefix' => 'file_manager_legend',
'attr'=> array(
"placeholder"=> 'Légende'
),
'label' => false
));
$builder->add('position', HiddenType::class, array(
'block_prefix' => 'file_manager_position',
'empty_data'=> 0,
'required' => true,
'attr' => array(
'class' => 'field-position'
),
'label' => false
));
}

/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => $this->em->getEntityName(FileInterface::class),
'compound' => true,
]);
}

/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'file_manager';
}

/**
* {@inheritdoc}
*/
public function transform($data)
{
// Model data should not be transformed
return $data;
}

/**
* {@inheritdoc}
*/
public function reverseTransform($data)
{
return null === $data ? '' : $data;
}
}

+ 9
- 0
IModel/Cms/BlameableInterface.php View File

@@ -0,0 +1,9 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface BlameableInterface
{

}

+ 8
- 0
IModel/Cms/DevAliasInterface.php View File

@@ -0,0 +1,8 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface DevAliasInterface
{

}

+ 9
- 0
IModel/Cms/FileInterface.php View File

@@ -0,0 +1,9 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface FileInterface
{

}

+ 10
- 0
IModel/Cms/SeoInterface.php View File

@@ -0,0 +1,10 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface SeoInterface
{



}

+ 8
- 0
IModel/Cms/SluggableInterface.php View File

@@ -0,0 +1,8 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface SluggableInterface
{

}

+ 8
- 0
IModel/Cms/SortableInterface.php View File

@@ -0,0 +1,8 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface SortableInterface
{

}

+ 8
- 0
IModel/Cms/StatusInterface.php View File

@@ -0,0 +1,8 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface StatusInterface
{

}

+ 9
- 0
IModel/Cms/TimestampableInterface.php View File

@@ -0,0 +1,9 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface TimestampableInterface
{


}

+ 22
- 0
IModel/Cms/TreeInterface.php View File

@@ -0,0 +1,22 @@
<?php

namespace Lc\SovBundle\IModel\Cms;

interface TreeInterface
{
/**
* Retourne le parent d'une entité
*
* @return entity
*/
public function getParent();


/**
* Retourne les enfants d'une entité
*
* @return entity
*/

public function getChildrens();
}

+ 7
- 0
IModel/EntityInterface.php View File

@@ -0,0 +1,7 @@
<?php

namespace Lc\SovBundle\IModel;

interface EntityInterface
{
}

+ 9
- 0
IModel/Translation/TranslatableInterface.php View File

@@ -0,0 +1,9 @@
<?php

namespace Lc\SovBundle\IModel\Translation;

interface TranslatableInterface
{

}

+ 8
- 0
IModel/User/UserInterface.php View File

@@ -0,0 +1,8 @@
<?php

namespace Lc\SovBundle\IModel\User;

interface UserInterface
{

}

+ 102
- 0
Manager/EntityManager.php View File

@@ -0,0 +1,102 @@
<?php

namespace Lc\SovBundle\Manager;

use Doctrine\ORM\EntityManager as DoctrineEntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Lc\SovBundle\Event\EntityManager\EntityManagerEvent;
use Lc\SovBundle\IModel\EntityInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
* class EntityManager.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class EntityManager
{
protected EventDispatcherInterface $eventDispatcher;

protected DoctrineEntityManager $entityManager;

public function __construct(EventDispatcherInterface $eventDispatcher, EntityManagerInterface $entityManager)
{
$this->eventDispatcher = $eventDispatcher;
$this->entityManager = $entityManager;
}

public function getRepository($className)
{
return $this->entityManager->getRepository($this->getEntityName($className));
}

public function new($className)
{
$entityName = $this->getEntityName($className);
return new $entityName;
}

public function create(EntityInterface $entity): self
{
$this->persist($entity);
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::CREATE_EVENT);

return $this;
}

public function update(EntityInterface $entity): self
{
$this->persist($entity);
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::UPDATE_EVENT);

return $this;
}

public function delete(EntityInterface $entity): self
{
$this->remove($entity);
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT);

return $this;
}

public function flush(): self
{
$this->entityManager->flush();

return $this;
}

public function clear(): self
{
$this->entityManager->clear();

return $this;
}

public function refresh(EntityInterface $entity): self
{
$this->entityManager->refresh($entity);

return $this;
}

protected function persist(EntityInterface $entity)
{
$this->entityManager->persist($entity);
}

public function getClassMetadata($className){
return $this->entityManager->getClassMetadata($className);
}

public function getEntityName($className)
{
if (substr($className, -9) === 'Interface') {
return $this->entityManager->getClassMetadata($className)->getName();
}else{
return $className;
}

}
}

+ 69
- 0
Model/Cms/AbstractDocument.php View File

@@ -0,0 +1,69 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Lc\SovBundle\IModel\Cms\BlameableInterface;
use Lc\SovBundle\IModel\Cms\DevAliasInterface;
use Lc\SovBundle\IModel\Cms\SeoInterface;
use Lc\SovBundle\IModel\Cms\SluggableInterface;
use Lc\SovBundle\IModel\Cms\SortableInterface;
use Lc\SovBundle\IModel\Cms\TimestampableInterface;
use Lc\SovBundle\IModel\Cms\StatusInterface;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Lc\SovBundle\IModel\EntityInterface;

/**
* @ORM\MappedSuperclass
*/
abstract class AbstractDocument implements BlameableInterface, SeoInterface, SluggableInterface, SortableInterface, StatusInterface, TimestampableInterface, DevAliasInterface, EntityInterface
{

use BlameableTrait;
use SeoTrait;
use SluggableTrait;
use SortableTrait;
use StatusTrait;
use TimestampableTrait;
use DevAliasTrait;

/**
* @Gedmo\Translatable
* @ORM\Column(type="string", length=255)
*/
protected $title;

/**
* @Gedmo\Translatable
* @ORM\Column(type="text", nullable=true)
*/
protected $description;


public function getTitle(): ?string
{
return $this->title;
}

public function setTitle(string $title): self
{
$this->title = $title;

return $this;
}


public function getDescription(): ?string
{
return $this->description;
}

public function setDescription(?string $description): self
{
$this->description = $description;

return $this;
}


}

+ 51
- 0
Model/Cms/BlameableTrait.php View File

@@ -0,0 +1,51 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Lc\SovBundle\IModel\User\UserInterface;

trait BlameableTrait
{

/**
* @Gedmo\Blameable(on="create")
* @ORM\ManyToOne(targetEntity="App\Entity\User")
* @ORM\JoinColumn(nullable=false)
*/
protected $createdBy;

/**
* @Gedmo\Blameable(on="update")
* @ORM\ManyToOne(targetEntity="App\Entity\User")
* @ORM\JoinColumn(nullable=false)
*/
protected $updatedBy;


public function getCreatedBy(): ?UserInterface
{
return $this->createdBy;
}

public function setCreatedBy(?UserInterface $createdBy): self
{
$this->createdBy = $createdBy;

return $this;
}

public function getUpdatedBy(): ?UserInterface
{
return $this->updatedBy;
}

public function setUpdatedBy(?UserInterface $updatedBy): self
{
$this->updatedBy = $updatedBy;

return $this;
}

}

+ 27
- 0
Model/Cms/DevAliasTrait.php View File

@@ -0,0 +1,27 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

trait DevAliasTrait
{

/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $devAlias;

public function getDevAlias(): ?string
{
return $this->devAlias;
}

public function setDevAlias(?string $devAlias): self
{
$this->devAlias = $devAlias;

return $this;
}
}

+ 73
- 0
Model/Cms/File.php View File

@@ -0,0 +1,73 @@
<?php
namespace Lc\SovBundle\Model\Cms;


use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Lc\SovBundle\IModel\Cms\BlameableInterface;
use Lc\SovBundle\IModel\Cms\DevAliasInterface;
use Lc\SovBundle\IModel\Cms\SortableInterface;
use Lc\SovBundle\IModel\Cms\TimestampableInterface;
use Lc\SovBundle\IModel\EntityInterface;
use Lc\SovBundle\IModel\Translation\TranslatableInterface;
use Lc\SovBundle\Model\Cms\BlameableTrait;
use Lc\SovBundle\Model\Cms\DevAliasTrait;
use Lc\SovBundle\Model\Cms\SortableTrait;
use Lc\SovBundle\Model\Cms\TimestampableTrait;
use Lc\SovBundle\Model\Translation\TranslatableTrait;
use Lc\SovBundle\Entity\Translation\EntityTranslation;

/**
* @Gedmo\TranslationEntity (class=EntityTranslation::class)
*/

abstract class File implements SortableInterface, BlameableInterface, TimestampableInterface, TranslatableInterface, DevAliasInterface, EntityInterface
{
use DevAliasTrait;
use BlameableTrait;
use TimestampableTrait;
use TranslatableTrait;
use SortableTrait;

/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $path;

/**
* @Gedmo\Translatable
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $legend;


public function __toString(){
return $this->getLegend();
}

public function getPath(): ?string
{
return $this->path;
}

public function setPath(?string $path): self
{
$this->path = $path;

return $this;
}


public function getLegend(): ?string
{
return $this->legend;
}

public function setLegend(?string $legend): self
{
$this->legend = $legend;

return $this;
}
}

+ 63
- 0
Model/Cms/SeoTrait.php View File

@@ -0,0 +1,63 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Doctrine\ORM\Mapping as ORM;

trait SeoTrait
{
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $metaTitle;


/**
* @ORM\Column(type="text", nullable=true)
*/
protected $metaDescription;


/**
* @var array
* @ORM\Column(type="array", nullable=true)
*/
protected $oldUrls;


public function getMetaTitle(): ?string
{
return $this->metaTitle;
}

public function setMetaTitle(?string $metaTitle): self
{
$this->metaTitle = $metaTitle;

return $this;
}

public function getMetaDescription(): ?string
{
return $this->metaDescription;
}

public function setMetaDescription(?string $metaDescription): self
{
$this->metaDescription = $metaDescription;

return $this;
}

public function setOldUrls($oldUrls): self
{
$this->oldUrls = $oldUrls;

return $this;
}

public function getOldUrls(): ?array
{
return $this->oldUrls;
}
}

+ 27
- 0
Model/Cms/SluggableTrait.php View File

@@ -0,0 +1,27 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

trait SluggableTrait
{
/**
* @ORM\Column(type="string", length=255)
* @Gedmo\Slug(fields={"title"})
*/
protected $slug;

public function getSlug(): ?string
{
return $this->slug;
}

public function setSlug(?string $slug): self
{
$this->slug = $slug;

return $this;
}
}

+ 34
- 0
Model/Cms/SortableTrait.php View File

@@ -0,0 +1,34 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;


trait SortableTrait
{
/**
* @var string
* @ORM\Column(type="float")
*/
protected $position = 0;

/**
* @return float
*/
public function getPosition(): float
{
return $this->position;
}

/**
* @param float $position
* @return $this
*/
public function setPosition(float $position): self
{
$this->position = $position;
return $this;
}
}

+ 25
- 0
Model/Cms/StatusTrait.php View File

@@ -0,0 +1,25 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Doctrine\ORM\Mapping as ORM;

trait StatusTrait
{
/**
* @ORM\Column(type="float")
*/
protected $status;

public function getStatus(): ?float
{
return $this->status;
}

public function setStatus(float $status): self
{
$this->status = $status;

return $this;
}
}

+ 46
- 0
Model/Cms/TimestampableTrait.php View File

@@ -0,0 +1,46 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

trait TimestampableTrait
{
/**
* @ORM\Column(type="datetime")
* @Gedmo\Timestampable(on="create")
*/
protected $createdAt;

/**
* @ORM\Column(type="datetime")
* @Gedmo\Timestampable(on="update")
*/
protected $updatedAt;

public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}

public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;

return $this;
}

public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}

public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;

return $this;
}

}

+ 11
- 0
Model/Cms/TreeTrait.php View File

@@ -0,0 +1,11 @@
<?php

namespace Lc\SovBundle\Model\Cms;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

trait TreeTrait
{

}

+ 53
- 0
Model/Translation/TranslatableTrait.php View File

@@ -0,0 +1,53 @@
<?php

namespace Lc\SovBundle\Model\Translation;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Lc\SovBundle\Entity\Translation\EntityTranslation;


/**
* @Gedmo\TranslationEntity(class=EntityTranslation::class)
*/
trait TranslatableTrait
{
/**
* Post locale
* Used locale to override Translation listener's locale
* @Gedmo\Locale
*/
protected $locale;

/**
* @ORM\Column(type="array", nullable=true)
*/
protected $localesEnabled = [];

public function __get($name)
{
if(isset($this->{$name})) {
return $this->{$name};
}else{
return null;
}

}

public function setTranslatableLocale($locale)
{
$this->locale = $locale;
}

public function getLocalesEnabled(): ?array
{
return $this->localesEnabled;
}

public function setLocalesEnabled(?array $localesEnabled): self
{
$this->localesEnabled = $localesEnabled;

return $this;
}
}

+ 155
- 0
Model/User/User.php View File

@@ -0,0 +1,155 @@
<?php

namespace Lc\SovBundle\Model\User;

use Doctrine\ORM\Mapping as ORM;
use Lc\SovBundle\IModel\EntityInterface;
use Symfony\Component\Security\Core\User\UserInterface;

/**
* @ORM\MappedSuperclass()
*/
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 isVerified(): bool
{
return $this->isVerified;
}

public function setIsVerified(bool $isVerified): self
{
$this->isVerified = $isVerified;

return $this;
}
}

+ 241
- 0
README.md View File

@@ -0,0 +1,241 @@
# Laclic SovBundle

Administration basé sur EasyAdmin 3. Développé par <a href="https://www.laclic.fr">la Clic!</a>

Require :

- Symfony > 5.3
- EasyAdmin > 5.3
- FOS/Ckeditor-bundle: "^2.2",

## Instalation en version développement sur un projet :

Si tu démarres un nouveau projet il te suffit de cloner le projet : https://gitea.laclic.fr/Laclic/DefaultProjectSf5

- À la racine de ton projet créer un dossier Lc/AdminBundle/
- Dans le dossier Lc/AdminBundle :
```
git clone https://gitea.laclic.fr/Laclic/AdminBundle.git .
```

- Initialisation :
- Création des entités de base, créer une entité User.php et File.php, ajouter dans la configuration de doctrine les lignes suivantes
```
doctrine:
orm:
resolve_target_entities:
Lc\SovBundle\IModel\User\UserInterface: App\Entity\User
Lc\SovBundle\IModel\Cms\FileInterface: App\Entity\File
#...
```
- Instalation ckeditor
`php bin/console ckeditor:install`
`php bin/console assets:install public
- Créer la config fos_ckeditor.yaml
```
fos_ck_editor:
input_sync: true
default_config: base_config
configs:
base_config:
filebrowserBrowseRoute: file_manager
filebrowserBrowseRouteParameters:
conf: default
module: ckeditor
format_tags: 'p;h2;h3'
toolbar:
- { name: "styles", items: ['Bold', 'Italic','Underline', 'Strike']}
- { name: "paragraph", items: ['Format', 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote'] }
- { name: "link", items: [ 'Link', 'Unlink'] }
- {name: 'insert', items: [ 'Image' ]}
- {name: 'document', items: [ 'Source' ]}
simple_config:
toolbar:
- { items: ['Bold', 'Italic', 'Underline', 'Colors', 'Source'] }
twig:
form_themes:
- '@FOSCKEditor/Form/ckeditor_widget.html.twig'


```
- Créer la config artgris_file_manager.yaml
```
artgris_file_manager:
conf:
default:
dir: "../public/uploads"
type: 'image'
```

- Enjoy !

### Pour les projet Aquarium, Laclic, Le plat pentu (et peut être d'autres) suit cette préocédure :

- Compare ton .gitignore avec le gitignore de DefaultProjectSf5 et ajoute les lignes manquantes

- Modifier composer.json dans “autoload” ajouter :
````
"autoload": {
"psr-4": {
"App\\": "src/",
"Lc\\": "Lc/"
}
},
````
- Dans le dossier `config` de ton projet :
- Modifier `bundles.php` et ajouter :
````
Lc\SovBundle\SovBundle::class => ['all' => true],
````
- Modifier `services.yaml` et ajouter :
````
Lc\SovBundle\:
resource: '../Lc/AdminBundle/'
exclude:
- '../Lc/AdminBundle/DependencyInjection/'
- '../Lc/AdminBundle/Entity/'
- '../Lc/AdminBundle/Kernel.php'
- '../Lc/AdminBundle/Tests/'
````
et
````
Lc\SovBundle\Controller\:
resource: '../Lc/AdminBundle/Controller/'
tags: ['controller.service_arguments']
````
- Modifier `packages/security.yml`
````
guard:
authenticators:
- Lc\SovBundle\Authenticator\LoginFormAuthenticator
````
- Modifier `packages/doctrine.yaml`
````
resolve_target_entities:
Lc\SovBundle\IModel\User\UserInterface: App\Entity\User
````
- Modifier `routes.yaml`
````
login:
path: /login
controller: Lc\SovBundle\Controller\Admin\SecurityController::login
logout:
path: /logout
controller: Lc\SovBundle\Admin\SecurityController::logout
````
- Dans le dossier `src/` de ton projet :
- Supprimer `Controller/Admin/SecurityController.php`.
- Supprimer `Controller/Admin/AbstractCrudController.php`.
- Modifier l'en tête de `Controller/Admin/DashboardController.php`.
````
use Lc\SovBundle\Controller\Admin\DashboardController as LcDashboardController;
class DashboardController extends LcDashboardController
{
// ... le code spécifique
}
````

- Modifier l'en tête des controllers. Exemple `Controller/Admin/PageCrudController.php`.
````
use Lc\SovBundle\Controller\Admin\AbstractCrudController;

class PageCrudController extends AbstractCrudController
{
// ... le code spécifique
}
````
- Modifier `Entity/User.php`.
````
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Lc\SovBundle\IModel\User\UserInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Lc\SovBundle\Model\User\User as LcUserModel;
/**
* @ORM\Entity(repositoryClass="Lc\SovBundle\Repository\User\UserRepository")
* @UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class User extends LcUserModel implements UserInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
public function getId(): ?int
{
return $this->id;
}
}
````
- Modifier les entités pour que les Use pointe dans AdminBundle exemple avec `Entity/Page.php`.
````
use Lc\SovBundle\IModel\Cms\ImageInterface;
use Lc\SovBundle\Model\Cms\AbstractDocument;
use Lc\SovBundle\Model\Cms\ImageTrait;
/**
* @ORM\Entity(repositoryClass=PageRepository::class)
*/
class Page extends AbstractDocument implements ImageInterface
{
use ImageTrait;
// ... le code spécifique
}
````

- Modifier les repository pour qu'il fonctionne avec AdminBundle exemple avec `Repository/PageRepository.php.php`.
````
<?php
namespace App\Repository;
use App\Entity\Page;
use Lc\SovBundle\Repository\BaseRepository;
/**
* @method Page|null find($id, $lockMode = null, $lockVersion = null)
* @method Page|null findOneBy(array $criteria, array $orderBy = null)
* @method Page[] findAll()
* @method Page[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class PageRepository extends BaseRepository
{
public function getInterfaceClass()
{
return Page::class;
}
// ... le code spécifique
}

````
- Supprimer `Repository/UserRepository.php.php`.
Sauf si il contient des fonctions autres que `__contruct()` et `upgradePassword-()`
Si c'est le cas le réécrire en faisant un extends de UserRepository dans AdminBundle
- Faire un `composer install`
- Tester l'application connectes toi à l'admin, édites un doc ou deux
- Si tout est ok tu peux supprimer les dossiers :
````
src/IModel/
src/Model/
src/Security/
src/Command/

+ 0
- 0
Repository/.gitignore View File


+ 137
- 0
Repository/BaseRepository.php View File

@@ -0,0 +1,137 @@
<?php

namespace Lc\SovBundle\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Gedmo\Translatable\TranslatableListener;
use Lc\SovBundle\IModel\Cms\StatusInterface;

abstract class BaseRepository extends EntityRepository implements ServiceEntityRepositoryInterface, BaseRepositoryInterface
{

protected $defaultLocale;

public function setDefaultLocale($locale)
{
$this->defaultLocale = $locale;
}

public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct($entityManager, $entityManager->getClassMetadata($this->getInterfaceClass()));
}

public function findOneByOldUrl($url)
{
$qb = $this->createQueryBuilder('entity')
->where('entity.oldUrls LIKE :oldUrl')
->andWhere('entity.status = 1')
->setParameter(':oldUrl', '%'.$url.'%');

return $qb->getQuery()->getOneOrNullResult();
}

public function getOneOrNullResult(QueryBuilder $qb, $locale = null, $hydrationMode = null)
{
return $this->getTranslatedQuery($qb, $locale)->getOneOrNullResult($hydrationMode);
}


public function getResult(QueryBuilder $qb, $locale = null, $hydrationMode = AbstractQuery::HYDRATE_OBJECT)
{
return $this->getTranslatedQuery($qb, $locale)->getResult($hydrationMode);
}


public function getArrayResult(QueryBuilder $qb, $locale = null)
{
return $this->getTranslatedQuery($qb, $locale)->getArrayResult();
}


public function getSingleResult(QueryBuilder $qb, $locale = null, $hydrationMode = null)
{
return $this->getTranslatedQuery($qb, $locale)->getSingleResult($hydrationMode);
}


public function getScalarResult(QueryBuilder $qb, $locale = null)
{
return $this->getTranslatedQuery($qb, $locale)->getScalarResult();
}


public function getSingleScalarResult(QueryBuilder $qb, $locale = null)
{
return $this->getTranslatedQuery($qb, $locale)->getSingleScalarResult();
}

protected function getTranslatedQuery(QueryBuilder $qb, $locale = null)
{
$locale = null === $locale ? $this->defaultLocale : $locale;

$query = $qb->getQuery();

$query->setHint(
Query::HINT_CUSTOM_OUTPUT_WALKER,
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
);

$query->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale);

return $query;
}


public function findAll()
{
return $this->findBy(array());
}

public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
$className = $this->getClassMetadata()->getName();
$entity = new $className;

if($entity instanceof StatusInterface){
if (!isset($criteria['status'])) $criteria['status'] = 1;
if ($criteria['status'] === false) unset($criteria['status']);
}

return parent::findBy($criteria, $orderBy, $limit, $offset);

}

public function findOneBy(array $criteria, array $orderBy = null)
{

$className = $this->getClassMetadata()->getName();
$entity = new $className;

if($entity instanceof StatusInterface){
if (!isset($criteria['status'])) $criteria['status'] = 1;
if ($criteria['status'] === false) unset($criteria['status']);
}

return parent::findOneBy($criteria, $orderBy);
}

public function findSimilarSlug($merchant){
$qb = $this->createQueryBuilder('entity')
->select('entity.id, COUNT(entity.slug) as niche')
->andWhere('entity.status>=0')
->andWhere('entity.merchant= :merchant')
->setParameter('merchant', $merchant)

->groupBy('entity.slug')
->having('COUNT(entity.slug) >1');

return $qb->getQuery()->getResult();
}

}

+ 15
- 0
Repository/BaseRepositoryInterface.php View File

@@ -0,0 +1,15 @@
<?php

namespace Lc\SovBundle\Repository;


interface BaseRepositoryInterface
{

/**
* Retourne la class ou l'interface correspondant à ce repository
*
* @return string
*/
public function getInterfaceClass();
}

+ 20
- 0
Repository/Cms/FileRepository.php View File

@@ -0,0 +1,20 @@
<?php

namespace Lc\SovBundle\Repository\Cms;

use Lc\SovBundle\IModel\Cms\FileInterface;
use Lc\SovBundle\Repository\BaseRepository;
/**
* @method FileInterface|null find($id, $lockMode = null, $lockVersion = null)
* @method FileInterface|null findOneBy(array $criteria, array $orderBy = null)
* @method FileInterface[] findAll()
* @method FileInterface[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class FileRepository extends BaseRepository
{
public function getInterfaceClass()
{
return FileInterface::class;
}

}

+ 68
- 0
Repository/User/UserRepository.php View File

@@ -0,0 +1,68 @@
<?php

namespace Lc\SovBundle\Repository\User;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Lc\SovBundle\IModel\User\UserInterface;
use Lc\SovBundle\Repository\BaseRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface as SfUserInterface;

/**
* @method UserInterface|null find($id, $lockMode = null, $lockVersion = null)
* @method UserInterface|null findOneBy(array $criteria, array $orderBy = null)
* @method UserInterface[] findAll()
* @method UserInterface[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends BaseRepository implements PasswordUpgraderInterface
{
public function getInterfaceClass()
{
return UserInterface::class;
}

/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function upgradePassword(SfUserInterface $user, string $newEncodedPassword): void
{
if (!$user instanceof SfUserInterface) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
}

$user->setPassword($newEncodedPassword);
$this->_em->persist($user);
$this->_em->flush();
}

// /**
// * @return User[] Returns an array of User objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->orderBy('u.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/

/*
public function findOneBySomeField($value): ?User
{
return $this->createQueryBuilder('u')
->andWhere('u.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

+ 0
- 0
Resources/config/services.yml View File


+ 93
- 0
Resources/public/js/form-type-collection.js View File

@@ -0,0 +1,93 @@
jQuery(document).ready(function () {
initCollectionWidget();
});


function initCollectionWidget() {
$('.field-collection[data-prototype]').each(function (i, collectionWidget) {
setCollectionWidgetSortable($(collectionWidget));
reindexKeyCollectionWidget($(collectionWidget));

setCollectionWidgetAdd($(collectionWidget));
setCollectionWidgetDelete($(collectionWidget));
});
}

function setCollectionWidgetAdd($collectionWidget) {

if ($collectionWidget.data('allow-add')) {
$collectionWidget.find('.field-collection-add').on('click', function (e) {

// grab the prototype template
var newWidget = $collectionWidget.attr('data-prototype');
// replace the "__name__" used in the id and name of the prototype
// with a number that's unique to your emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/__name__/g, getNumItems($collectionWidget));

// create a new list element and add it to the list
$collectionWidget.find('.form-widget-compound .field-collection-group').append(newWidget);
$collectionWidget.find('.field-collection-item:last').find('.field-position').val(getNumItems($collectionWidget));

reindexKeyCollectionWidget($collectionWidget);
setCollectionWidgetDelete($collectionWidget);
initFileManager();
$collectionWidget.data('num-items', $collectionWidget.data('num-items') + 1);
$collectionWidget.find('.collection-empty').hide();
});
}
}

function setCollectionWidgetDelete($collectionWidget) {
if ($collectionWidget.data('allow-delete')) {
$collectionWidget.find('.field-collection-delete').off('click');
$collectionWidget.find('.field-collection-delete').on('click', function () {
$(this).parents('.form-group:first').remove();
reindexKeyCollectionWidget($collectionWidget);
if(getNumItems($collectionWidget)==0)$collectionWidget.find('.collection-empty').show();
});
}
}

function getNumItems($collectionWidget) {
if ($collectionWidget.data('reindex-key')) {
return $collectionWidget.find('.field-collection-item').length;
} else {
return $collectionWidget.data('num-items');
}
}

function reindexKeyCollectionWidget($collectionWidget) {
if ($collectionWidget.data('reindex-key')) {
$collectionWidget.find('.field-collection-item').each(function (i, item) {
$(item).find('.input[type="text"]').each(function (y, field) {
$field = $(field);
//Chanegment ID
posId = indexOfLastDigit($field.prop('id'));
idPrefix = $field.prop('id').substr(0, posId);
idSuffix = $field.prop('id').substr(posId + 1);
$field.prop('id', idPrefix + i + idSuffix);

//Chanegment Name
posName = indexOfLastDigit($field.prop('name'));
namePrefix = $field.prop('name').substr(0, posName);
nameSuffix = $field.prop('name').substr(posName + 1);
$field.prop('name', namePrefix + i + nameSuffix);
});
});
}
}

function setCollectionWidgetSortable($collectionWidget) {
if ($collectionWidget.data('sortable')) {
$collectionWidget.find('.field-collection-group').sortable({
"handle" : '.lc-btn-sortable',
cancel: ''
});
$collectionWidget.find('.field-collection-group').on("sortupdate", function (event, ui) {
$collectionWidget.find('.field-collection-group>div').each(function (index, item) {
$(item).find('.field-position').val(index);
});
});
}
}

+ 29
- 0
Resources/public/js/form-type-file-manager.js View File

@@ -0,0 +1,29 @@
jQuery(document).ready(function () {
initFileManager();
});


function initFileManager() {
$('.lc-filemanager-delete').off('click');
$('.lc-filemanager-delete').on('click', function (e) {
let $field = $(this);
$('#' + $field.data('id')).val("");
$('#' + $field.data('id') + '_preview').prop('src',"");
});
$('.lc-filemanager-open').off('click');
$('.lc-filemanager-open').on('click', function (e) {
let $field = $(this);
$('#lc-filemanager-frame').off('load');
$("#lc-filemanager-frame").on('load', function () {
$('#lc-filemanager-frame').contents().on('click', '.select', function () {
var path = $(this).attr('data-path')
$('#' + $field.data('id')).val(path);
$('#' + $field.data('id') + '_preview').prop('src',path);
$('#lc-filemanager-modal').modal('hide')
});
});
$("#lc-filemanager-frame").prop('src', $field.data('target'));
$('#lc-filemanager-modal').modal('show');
});

}

+ 13
- 0
Resources/public/js/jquery-ui/jquery-ui.min.js
File diff suppressed because it is too large
View File


+ 57
- 0
Resources/public/js/utils.js View File

@@ -0,0 +1,57 @@

function arrayRemove(arr, value) { return arr.filter(function(ele){ return ele != value; });}

const scratchDiv = document.createElement('div');
function toPlainText(html) {
scratchDiv.innerHTML = html;
return scratchDiv.textContent;
}

function getDateFormatted(date, separator) {
if(date) {
var date = new Date(date);
var _d = date.getDate(),
d = _d > 9 ? _d : '0' + _d,
_m = date.getMonth() + 1,
m = _m > 9 ? _m : '0' + _m,
formatted = d + separator + m + separator + date.getFullYear();
return formatted;
}else{
return '';
}
}

function log(msg) {
try {
console.log(msg);
} catch (e) {

}
}

var getUrlParameter = function getUrlParameter(sParam) {
var sPageURL = window.location.search.substring(1),
sURLVariables = sPageURL.split('&'),
sParameterName,
i;

for (i = 0; i < sURLVariables.length; i++) {
sParameterName = sURLVariables[i].split('=');

if (sParameterName[0] === sParam) {
return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1]);
}
}
};

function indexOfFirstDigit(input) {
let i = 0;
for (; input[i] < '0' || input[i] > '9'; i++) ;
return i == input.length ? -1 : i;
}

function indexOfLastDigit(input) {
let i = input.length - 1;
for (; input[i] < '0' || input[i] > '9'; i--) ;
return i == input.length ? -1 : i;
}

+ 21
- 0
Resources/views/crud/action/translatable.html.twig View File

@@ -0,0 +1,21 @@
{# @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 %}

+ 8
- 0
Resources/views/crud/edit.html.twig View File

@@ -0,0 +1,8 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{% extends '@EasyAdmin/crud/edit.html.twig' %}

{% block body_javascript %}
{{ parent() }}
{% include '@Sov/crud/filemanager/file-manager-modal.html.twig' %}
{% endblock %}

+ 11
- 0
Resources/views/crud/field/collection.html.twig View File

@@ -0,0 +1,11 @@
{# @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 %}

+ 10
- 0
Resources/views/crud/field/gallery.html.twig View File

@@ -0,0 +1,10 @@
{# @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 %}

+ 13
- 0
Resources/views/crud/field/image.html.twig View File

@@ -0,0 +1,13 @@
{# @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 %}

+ 18
- 0
Resources/views/crud/filemanager/file-manager-modal.html.twig View File

@@ -0,0 +1,18 @@
<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">&times;</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>

+ 100
- 0
Resources/views/crud/form_theme.html.twig View File

@@ -0,0 +1,100 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{% use '@EasyAdmin/crud/form_theme.html.twig' %}


{% 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 %}


+ 9
- 0
Resources/views/crud/new.html.twig View File

@@ -0,0 +1,9 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{% extends '@EasyAdmin/crud/new.html.twig' %}

{% block body_javascript %}
{{ parent() }}
{% include '@Sov/crud/filemanager/file-manager-modal.html.twig' %}
{% endblock %}


+ 9
- 0
Resources/views/dashboard.html.twig View File

@@ -0,0 +1,9 @@
{% extends '@EasyAdmin/page/content.html.twig' %}

{% block page_title 'SovBundle' %}

{% block page_content %}

Bien sur votre tableau de bord
{% endblock %}


+ 15
- 0
SovBundle.php View File

@@ -0,0 +1,15 @@
<?php

namespace Lc\SovBundle;

use Lc\SovBundle\DependencyInjection\SovExtension;
use Symfony\Component\HttpKernel\Bundle\Bundle;


class SovBundle extends Bundle
{
public function getContainerExtension()
{
return new SovExtension();
}
}

+ 111
- 0
Twig/TwigExtension.php View File

@@ -0,0 +1,111 @@
<?php

namespace Lc\SovBundle\Twig;


use App\Entity\Page;

use Doctrine\ORM\EntityManagerInterface;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class TwigExtension extends AbstractExtension
{
protected $em;
protected $kernel;
protected $parameterBag;
protected $cacheManager;
protected $requestStack;
protected $router;

public function __construct(KernelInterface $kernel, ParameterBagInterface $parameterBag, CacheManager $cacheManager, EntityManagerInterface $entityManager, RequestStack $requestStack,UrlGeneratorInterface $router)
{
$this->kernel = $kernel;
$this->parameterBag = $parameterBag;
$this->cacheManager = $cacheManager;
$this->em = $entityManager;
$this->requestStack = $requestStack;
$this->router = $router;
}

public function getFunctions()
{
return array(
new TwigFunction('lc_liip', [$this, 'lcLiip']),
new TwigFunction('page_by_dev_alias', [$this, 'getPageByDevAlias']),
new TwigFunction('translated_urls', [$this, 'getTranslatedUrls'])
);
}


public function getFilters()
{
return [
new TwigFilter('lc_cache', [$this, 'lcCache']),
];
}

public function lcCache($file)
{
$cacheTime = filemtime($this->kernel->getProjectDir() . '/public' . $file);
if ($cacheTime) {
return $file . '?c=' . $cacheTime;
} else {
return $file . "?c=0";
}
}

public function lcLiip($path, $thumb = 'tile', $default = 'default.jpg')
{
if (substr($path, 0, 1) === '/') $path = substr($path, 1);

if ($path) {
$fileManagerFolder = substr($this->getFileManagerFolder(), 1) ;

if (strpos($path, $fileManagerFolder) === false) {
$path = $fileManagerFolder . '/' . $path;
}

if (file_exists($path)) {
return $this->cacheManager->getBrowserPath($path, $thumb);
}
}

return $this->cacheManager->getBrowserPath($this->getFileManagerFolder() . '/' . $default, $thumb);
}

function getTranslatedUrls()
{
$ret = array();
$langs = $this->parameterBag->get('app.locales');
$currentRoute = $this->requestStack->getCurrentRequest()->get('_route');

$params = array_merge((array)$this->requestStack->getCurrentRequest()->get('_route_params'), $_GET);

if ($currentRoute) {
foreach($langs as $lg) {
$ret[$lg] = $this->router->generate($currentRoute, array_merge($params, array('_locale'=>$lg)));
}
}

return $ret;
}

public function getFileManagerFolder()
{
return $this->parameterBag->get('app.path_uploads');
}

public function getPageByDevAlias($devAlias)
{

return $this->em->getRepository(Page::class)->findOneByDevAlias($devAlias);
}

}

+ 24
- 0
composer.json View File

@@ -0,0 +1,24 @@
{
"name": "lc/admin-bundle",
"type": "symfony-bundle",
"description": "Administration for Symfony applications based on easyadmin",
"keywords": ["backend", "admin", "generator"],
"homepage": "https://gitea.laclic.fr/Laclic/AdminBundle",
"license": "MIT",
"authors": [
{
"name": "La clic !",
"homepage": "laclic.fr/"
}
],
"require": {
"php": ">=7.2.5",
"easycorp/easyadmin-bundle": "^3.0",
"artgris/filemanager-bundle": "^2.2",
"friendsofsymfony/ckeditor-bundle": "^2.2",
"stof/doctrine-extensions-bundle": "^1.5"
},
"config": {
"sort-packages": true
}
}

Loading…
Cancel
Save