@@ -0,0 +1 @@ | |||
/.idea/* |
@@ -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); | |||
} | |||
} |
@@ -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 +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'), | |||
]; | |||
} | |||
*/ | |||
} |
@@ -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'), | |||
]; | |||
} | |||
} |
@@ -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.'); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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');*/ | |||
} | |||
} |
@@ -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 | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Cms; | |||
interface BlameableInterface | |||
{ | |||
} |
@@ -0,0 +1,8 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Cms; | |||
interface DevAliasInterface | |||
{ | |||
} |
@@ -0,0 +1,9 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Cms; | |||
interface FileInterface | |||
{ | |||
} |
@@ -0,0 +1,10 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Cms; | |||
interface SeoInterface | |||
{ | |||
} |
@@ -0,0 +1,8 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Cms; | |||
interface SluggableInterface | |||
{ | |||
} |
@@ -0,0 +1,8 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Cms; | |||
interface SortableInterface | |||
{ | |||
} |
@@ -0,0 +1,8 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Cms; | |||
interface StatusInterface | |||
{ | |||
} |
@@ -0,0 +1,9 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Cms; | |||
interface TimestampableInterface | |||
{ | |||
} |
@@ -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(); | |||
} |
@@ -0,0 +1,7 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel; | |||
interface EntityInterface | |||
{ | |||
} |
@@ -0,0 +1,9 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\Translation; | |||
interface TranslatableInterface | |||
{ | |||
} |
@@ -0,0 +1,8 @@ | |||
<?php | |||
namespace Lc\SovBundle\IModel\User; | |||
interface UserInterface | |||
{ | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
<?php | |||
namespace Lc\SovBundle\Model\Cms; | |||
use Doctrine\Common\Collections\ArrayCollection; | |||
use Doctrine\ORM\Mapping as ORM; | |||
trait TreeTrait | |||
{ | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 +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(); | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 +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); | |||
}); | |||
}); | |||
} | |||
} |
@@ -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'); | |||
}); | |||
} |
@@ -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; | |||
} |
@@ -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 %} |
@@ -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 %} |
@@ -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 %} |
@@ -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 %} |
@@ -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 %} |
@@ -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">×</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> |
@@ -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 %} | |||
@@ -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 %} | |||
@@ -0,0 +1,9 @@ | |||
{% extends '@EasyAdmin/page/content.html.twig' %} | |||
{% block page_title 'SovBundle' %} | |||
{% block page_content %} | |||
Bien sur votre tableau de bord | |||
{% endblock %} | |||
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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 | |||
} | |||
} |