From 7698277368a232cfbcfd2cc349c7086daadfa834 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Wed, 12 May 2021 12:04:03 +0200 Subject: [PATCH] backports murph-skeleton --- core/Controller/Admin/AdminController.php | 2 +- core/Controller/Admin/Crud/CrudController.php | 172 ++++++++++++++++++ core/Controller/Auth/AuthController.php | 2 +- .../Setting/SettingAdminController.php | 4 +- .../Site/NavigationAdminController.php | 3 +- core/Controller/Site/PageAdminController.php | 37 +--- core/Controller/Site/PageController.php | 1 - core/Controller/Site/TreeAdminController.php | 3 +- core/Controller/User/UserAdminController.php | 2 - core/Crud/CrudConfiguration.php | 164 +++++++++++++++++ .../Exception/CrudConfigurationException.php | 12 ++ core/Crud/Field/ButtonField.php | 29 +++ core/Crud/Field/DateField.php | 25 +++ core/Crud/Field/DatetimeField.php | 25 +++ core/Crud/Field/Field.php | 64 +++++++ core/Crud/Field/TextField.php | 24 +++ .../EntityManagerEventSubscriber.php | 2 +- .../Site/MenuEventSubscriber.php | 2 +- .../Site/SiteEventSubscriber.php | 3 +- core/Factory/FactoryInterface.php | 12 ++ core/Factory/SettingFactory.php | 2 +- core/Factory/Site/MenuFactory.php | 3 +- core/Factory/Site/NavigationFactory.php | 3 +- core/Factory/Site/NodeFactory.php | 3 +- core/Factory/Site/Page/PageFactory.php | 3 +- core/Factory/UserFactory.php | 2 +- core/Form/Site/NavigationType.php | 2 +- core/Form/Site/Page/Filter/PageFilterType.php | 11 +- core/Repository/RepositoryQuery.php | 42 ++--- .../Site/Page/PageRepositoryQuery.php | 23 +-- core/Resources/translations/messages.fr.yaml | 5 + .../views/admin/crud/_form.html.twig | 6 + .../views/admin/crud/_show.html.twig | 1 + .../Resources/views/admin/crud/edit.html.twig | 88 +++++++++ .../views/admin/crud/field/button.html.twig | 1 + .../views/admin/crud/field/date.html.twig | 1 + .../views/admin/crud/field/text.html.twig | 1 + .../views/admin/crud/filter.html.twig | 21 +++ .../views/admin/crud/index.html.twig | 127 +++++++++++++ core/Resources/views/admin/crud/new.html.twig | 47 +++++ .../Resources/views/admin/crud/show.html.twig | 44 +++++ core/Setting/SettingManager.php | 2 +- core/String/StringBuilder.php | 5 +- core/Twig/Extension/CrudExtension.php | 44 +++++ src/Factory/Blog/CategoryFactory.php | 3 +- src/Factory/Blog/CommentFactory.php | 3 +- src/Factory/Blog/PostFactory.php | 3 +- 47 files changed, 989 insertions(+), 95 deletions(-) create mode 100644 core/Controller/Admin/Crud/CrudController.php create mode 100644 core/Crud/CrudConfiguration.php create mode 100644 core/Crud/Exception/CrudConfigurationException.php create mode 100644 core/Crud/Field/ButtonField.php create mode 100644 core/Crud/Field/DateField.php create mode 100644 core/Crud/Field/DatetimeField.php create mode 100644 core/Crud/Field/Field.php create mode 100644 core/Crud/Field/TextField.php create mode 100644 core/Factory/FactoryInterface.php create mode 100644 core/Resources/views/admin/crud/_form.html.twig create mode 100644 core/Resources/views/admin/crud/_show.html.twig create mode 100644 core/Resources/views/admin/crud/edit.html.twig create mode 100644 core/Resources/views/admin/crud/field/button.html.twig create mode 100644 core/Resources/views/admin/crud/field/date.html.twig create mode 100644 core/Resources/views/admin/crud/field/text.html.twig create mode 100644 core/Resources/views/admin/crud/filter.html.twig create mode 100644 core/Resources/views/admin/crud/index.html.twig create mode 100644 core/Resources/views/admin/crud/new.html.twig create mode 100644 core/Resources/views/admin/crud/show.html.twig create mode 100644 core/Twig/Extension/CrudExtension.php diff --git a/core/Controller/Admin/AdminController.php b/core/Controller/Admin/AdminController.php index f7cce81..799ae7a 100644 --- a/core/Controller/Admin/AdminController.php +++ b/core/Controller/Admin/AdminController.php @@ -3,8 +3,8 @@ namespace App\Core\Controller\Admin; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\HttpFoundation\Response; abstract class AdminController extends AbstractController { diff --git a/core/Controller/Admin/Crud/CrudController.php b/core/Controller/Admin/Crud/CrudController.php new file mode 100644 index 0000000..abba5ea --- /dev/null +++ b/core/Controller/Admin/Crud/CrudController.php @@ -0,0 +1,172 @@ + + */ +abstract class CrudController extends AdminController +{ + protected array $filters = []; + + abstract protected function getConfiguration(): CrudConfiguration; + + protected function doIndex(int $page = 1, RepositoryQuery $query, Request $request, Session $session): Response + { + $configuration = $this->getConfiguration(); + + $this->updateFilters($request, $session); + + $pager = $query + ->useFilters($this->filters) + ->paginate($page, $configuration->getMaxPerPage('index')) + ; + + return $this->render($this->getConfiguration()->getView('index'), [ + 'configuration' => $configuration, + 'pager' => $pager, + 'filters' => [ + 'show' => null !== $configuration->getForm('filter'), + 'isEmpty' => empty($this->filters), + ], + ]); + } + + protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request): Response + { + $configuration = $this->getConfiguration(); + $form = $this->createForm($configuration->getForm('new'), $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $entityManager->create($entity); + $this->addFlash('success', 'The data has been saved.'); + + return $this->redirectToRoute($configuration->getPageRoute('edit'), [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'The form is not valid.'); + } + + return $this->render($configuration->getView('new'), [ + 'form' => $form->createView(), + 'configuration' => $configuration, + 'entity' => $entity, + ]); + } + + protected function doShow(EntityInterface $entity): Response + { + $configuration = $this->getConfiguration(); + + return $this->render($configuration->getView('show'), [ + 'entity' => $entity, + 'configuration' => $configuration, + ]); + } + + protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request): Response + { + $configuration = $this->getConfiguration(); + $form = $this->createForm($configuration->getForm('edit'), $entity); + + if ($request->isMethod('POST')) { + $form->handleRequest($request); + + if ($form->isValid()) { + $entityManager->update($entity); + $this->addFlash('success', 'The data has been saved.'); + + return $this->redirectToRoute($configuration->getPageRoute('edit'), [ + 'entity' => $entity->getId(), + ]); + } + $this->addFlash('warning', 'The form is not valid.'); + } + + return $this->render($configuration->getView('edit'), [ + 'form' => $form->createView(), + 'configuration' => $configuration, + 'entity' => $entity, + ]); + } + + protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request): Response + { + $configuration = $this->getConfiguration(); + + if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) { + $entityManager->delete($entity); + + $this->addFlash('success', 'The data has been removed.'); + } + + return $this->redirectToRoute($configuration->getPageRoute('index')); + } + + protected function doFilter(Session $session): Response + { + $configuration = $this->getConfiguration(); + $type = $configuration->getForm('filter'); + + if (null === $type) { + throw $this->createNotFoundException(); + } + + $form = $this->createForm($type); + $form->submit($session->get($form->getName(), [])); + + return $this->render($configuration->getView('filter'), [ + 'form' => $form->createView(), + 'configuration' => $configuration, + ]); + } + + protected function updateFilters(Request $request, Session $session) + { + $configuration = $this->getConfiguration(); + $type = $configuration->getForm('filter'); + + if (null === $type) { + return; + } + + $form = $this->createForm($type); + + if ($request->query->has($form->getName())) { + $filters = $request->query->get($form->getName()); + + if ('0' === $filters) { + $filters = []; + } + } elseif ($session->has($form->getName())) { + $filters = $session->get($form->getName()); + } else { + $filters = []; + } + + $form->submit($filters); + + if (empty($filters)) { + $this->filters = $filters; + $session->set($form->getName(), $filters); + } elseif ($form->isValid()) { + $this->filters = $form->getData(); + $session->set($form->getName(), $filters); + } + } +} diff --git a/core/Controller/Auth/AuthController.php b/core/Controller/Auth/AuthController.php index 2aa4881..054ce3a 100644 --- a/core/Controller/Auth/AuthController.php +++ b/core/Controller/Auth/AuthController.php @@ -6,6 +6,7 @@ use App\Core\Event\Account\PasswordRequestEvent; use App\Core\Manager\EntityManager; use App\Repository\UserRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -14,7 +15,6 @@ use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use ZxcvbnPhp\Zxcvbn; -use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; class AuthController extends AbstractController { diff --git a/core/Controller/Setting/SettingAdminController.php b/core/Controller/Setting/SettingAdminController.php index e466193..1cb6ef9 100644 --- a/core/Controller/Setting/SettingAdminController.php +++ b/core/Controller/Setting/SettingAdminController.php @@ -5,7 +5,6 @@ namespace App\Core\Controller\Setting; use App\Core\Controller\Admin\AdminController; use App\Core\Entity\Setting as Entity; use App\Core\Event\Setting\SettingEvent; -use App\Core\Factory\SettingFactory as EntityFactory; use App\Core\Manager\EntityManager; use App\Core\Repository\SettingRepositoryQuery as RepositoryQuery; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -47,8 +46,7 @@ class SettingAdminController extends AdminController EntityManager $entityManager, EventDispatcherInterface $eventDispatcher, Request $request - ): Response - { + ): Response { $builder = $this->createFormBuilder($entity); $eventDispatcher->dispatch(new SettingEvent([ diff --git a/core/Controller/Site/NavigationAdminController.php b/core/Controller/Site/NavigationAdminController.php index 59b4270..816edf6 100644 --- a/core/Controller/Site/NavigationAdminController.php +++ b/core/Controller/Site/NavigationAdminController.php @@ -24,7 +24,8 @@ class NavigationAdminController extends AdminController { $pager = $query ->orderBy('.label, .domain') - ->paginate($page); + ->paginate($page) + ; return $this->render('@Core/site/navigation_admin/index.html.twig', [ 'pager' => $pager, diff --git a/core/Controller/Site/PageAdminController.php b/core/Controller/Site/PageAdminController.php index 0a825c6..b363b08 100644 --- a/core/Controller/Site/PageAdminController.php +++ b/core/Controller/Site/PageAdminController.php @@ -5,17 +5,15 @@ namespace App\Core\Controller\Site; use App\Core\Controller\Admin\AdminController; use App\Core\Entity\Site\Page\Page as Entity; use App\Core\Factory\Site\Page\PageFactory as EntityFactory; -use App\Core\Form\Site\Page\PageType as EntityType; use App\Core\Form\Site\Page\Filter\PageFilterType as FilterType; +use App\Core\Form\Site\Page\PageType as EntityType; use App\Core\Manager\EntityManager; -use App\Core\Page\FooPage; -use App\Core\Page\SimplePage; use App\Core\Repository\Site\Page\PageRepositoryQuery as RepositoryQuery; use App\Core\Site\PageLocator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Routing\Annotation\Route; /** * @Route("/admin/site/page") @@ -31,7 +29,8 @@ class PageAdminController extends AdminController $pager = $query ->useFilters($this->filters) - ->paginate($page); + ->paginate($page) + ; return $this->render('@Core/site/page_admin/index.html.twig', [ 'pager' => $pager, @@ -39,24 +38,6 @@ class PageAdminController extends AdminController ]); } - /** - * @Route("/new", name="admin_site_page_new") - */ - public function new(EntityFactory $factory, EntityManager $entityManager): Response - { - // $entity = $factory->create(FooPage::class); - $entity = $factory->create(SimplePage::class); - $entity->setName('Page de test '.mt_rand()); - - $entityManager->create($entity); - - $this->addFlash('success', 'The data has been saved.'); - - return $this->redirectToRoute('admin_site_page_edit', [ - 'entity' => $entity->getId(), - ]); - } - /** * @Route("/edit/{entity}", name="admin_site_page_edit") */ @@ -122,6 +103,11 @@ class PageAdminController extends AdminController ]); } + public function getSection(): string + { + return 'site_page'; + } + protected function updateFilters(Request $request, Session $session) { if ($request->query->has('page_filter')) { @@ -147,9 +133,4 @@ class PageAdminController extends AdminController $session->set('page_filter', $filters); } } - - public function getSection(): string - { - return 'site_page'; - } } diff --git a/core/Controller/Site/PageController.php b/core/Controller/Site/PageController.php index 6a1a3fb..81a88e4 100644 --- a/core/Controller/Site/PageController.php +++ b/core/Controller/Site/PageController.php @@ -5,7 +5,6 @@ namespace App\Core\Controller\Site; use App\Core\Site\SiteRequest; use App\Core\Site\SiteStore; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class PageController extends AbstractController diff --git a/core/Controller/Site/TreeAdminController.php b/core/Controller/Site/TreeAdminController.php index bb8f512..876e4b9 100644 --- a/core/Controller/Site/TreeAdminController.php +++ b/core/Controller/Site/TreeAdminController.php @@ -56,7 +56,8 @@ class TreeAdminController extends AdminController ): Response { $navigations = $navigationQuery->create() ->orderBy('.label, .domain') - ->find(); + ->find() + ; $session->set('site_tree_last_navigation', $navigation->getId()); diff --git a/core/Controller/User/UserAdminController.php b/core/Controller/User/UserAdminController.php index 31cf1f3..6096cc0 100644 --- a/core/Controller/User/UserAdminController.php +++ b/core/Controller/User/UserAdminController.php @@ -13,7 +13,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; -use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; /** * @Route("/admin/user") @@ -38,7 +37,6 @@ class UserAdminController extends AdminController public function new( EntityFactory $factory, EntityManager $entityManager, - UserPasswordEncoderInterface $encoder, Request $request ): Response { $entity = $factory->create($this->getUser()); diff --git a/core/Crud/CrudConfiguration.php b/core/Crud/CrudConfiguration.php new file mode 100644 index 0000000..7206c52 --- /dev/null +++ b/core/Crud/CrudConfiguration.php @@ -0,0 +1,164 @@ + + */ +class CrudConfiguration +{ + protected array $pageTitles = []; + protected array $pageRoutes = []; + protected array $actions = []; + protected array $actionTitles = []; + protected array $forms = []; + protected array $formOptions = []; + protected array $views = []; + protected array $fields = []; + protected array $maxPerPage = []; + + /* -- */ + + public function setPageTitle(string $page, string $title): self + { + $this->pageTitles[$page] = $title; + + return $this; + } + + public function getPageTitle(string $page, ?string $default = null): ?string + { + return $this->pageTitles[$page] ?? $default; + } + + /* -- */ + + public function setPageRoute(string $page, string $route): self + { + $this->pageRoutes[$page] = $route; + + return $this; + } + + public function getPageRoute(string $page): ?string + { + return $this->pageRoutes[$page]; + } + + /* -- */ + + public function setForm(string $context, string $form, array $options = []): self + { + $this->forms[$context] = $form; + + return $this; + } + + public function getForm(string $context): ?string + { + return $this->forms[$context] ?? null; + } + + public function setFormOptions(string $context, array $options = []): self + { + $this->formOptions[$context] = $options; + + return $this; + } + + public function getFormOptions(string $context): array + { + return $this->formOptions[$context] ?? []; + } + + /* -- */ + + public function setAction(string $page, string $action, bool $enabled): self + { + if (!isset($this->actions[$page])) { + $this->actions[$page] = []; + } + + $this->actions[$page][$action] = $enabled; + + return $this; + } + + public function getAction(string $page, string $action, bool $default = true) + { + return $this->actions[$page][$action] ?? $default; + } + + /* -- */ + + public function setActionTitle(string $page, string $action, string $title): self + { + if (!isset($this->actionTitles[$page])) { + $this->actionTitles[$page] = []; + } + + $this->actions[$page][$action] = $title; + + return $this; + } + + public function getActionTitle(string $page, string $action, ?string $default = null): ?string + { + return $this->actionTitles[$page][$action] ?? $default; + } + + /* -- */ + + public function setView(string $context, string $view): self + { + $this->views[$context] = $view; + + return $this; + } + + public function getView(string $context, ?string $default = null) + { + if (null === $default) { + $default = sprintf('@Core/admin/crud/%s.html.twig', $context); + } + + return $this->views[$context] ?? $default; + } + + /* -- */ + + public function setField(string $context, string $label, string $field, array $options): self + { + if (!isset($this->fields[$context])) { + $this->fields[$context] = []; + } + + $this->fields[$context][$label] = [ + 'field' => $field, + 'options' => $options, + ]; + + return $this; + } + + public function getFields(string $context): array + { + return $this->fields[$context] ?? []; + } + + /* -- */ + + public function setMaxPerPage(string $page, int $max) + { + $this->maxPerPage[$page] = $max; + + return $this; + } + + public function getMaxPerPage(string $page, int $default = 20) + { + return $this->maxPerPage[$page] ?? $default; + } +} diff --git a/core/Crud/Exception/CrudConfigurationException.php b/core/Crud/Exception/CrudConfigurationException.php new file mode 100644 index 0000000..17990c7 --- /dev/null +++ b/core/Crud/Exception/CrudConfigurationException.php @@ -0,0 +1,12 @@ + + */ +class CrudConfigurationException extends \Exception +{ +} diff --git a/core/Crud/Field/ButtonField.php b/core/Crud/Field/ButtonField.php new file mode 100644 index 0000000..65e986e --- /dev/null +++ b/core/Crud/Field/ButtonField.php @@ -0,0 +1,29 @@ + + */ +class ButtonField extends Field +{ + public function configureOptions(OptionsResolver $resolver): OptionsResolver + { + parent::configureOptions($resolver); + + $resolver->setDefaults([ + 'view' => '@Core/admin/crud/field/button.html.twig', + 'button_attr' => [], + 'button_tag' => 'button', + ]); + + $resolver->setAllowedTypes('button_attr', ['array']); + $resolver->setAllowedTypes('button_tag', ['string']); + + return $resolver; + } +} diff --git a/core/Crud/Field/DateField.php b/core/Crud/Field/DateField.php new file mode 100644 index 0000000..f97da96 --- /dev/null +++ b/core/Crud/Field/DateField.php @@ -0,0 +1,25 @@ + + */ +class DateField extends Field +{ + public function configureOptions(OptionsResolver $resolver): OptionsResolver + { + parent::configureOptions($resolver); + + $resolver->setDefaults([ + 'view' => '@Core/admin/crud/field/date.html.twig', + 'format' => 'Y-m-d', + ]); + + return $resolver; + } +} diff --git a/core/Crud/Field/DatetimeField.php b/core/Crud/Field/DatetimeField.php new file mode 100644 index 0000000..99aa739 --- /dev/null +++ b/core/Crud/Field/DatetimeField.php @@ -0,0 +1,25 @@ + + */ +class DatetimeField extends Field +{ + public function configureOptions(OptionsResolver $resolver): OptionsResolver + { + parent::configureOptions($resolver); + + $resolver->setDefaults([ + 'view' => '@Core/admin/crud/field/date.html.twig', + 'format' => 'Y-m-d H:i:s', + ]); + + return $resolver; + } +} diff --git a/core/Crud/Field/Field.php b/core/Crud/Field/Field.php new file mode 100644 index 0000000..70a8049 --- /dev/null +++ b/core/Crud/Field/Field.php @@ -0,0 +1,64 @@ + + */ +abstract class Field +{ + public function buildView(Environment $twig, $entity, array $options) + { + return $twig->render($this->getView($options), [ + 'value' => $this->getValue($entity, $options), + 'options' => $options, + ]); + } + + public function configureOptions(OptionsResolver $resolver): OptionsResolver + { + $resolver->setDefaults([ + 'property' => null, + 'property_builder' => null, + 'view' => null, + 'attr' => [], + ]); + + $resolver->setRequired('view'); + $resolver->setAllowedTypes('property', ['null', 'string']); + $resolver->setAllowedTypes('view', 'string'); + $resolver->setAllowedTypes('attr', 'array'); + $resolver->setAllowedTypes('property_builder', ['null', 'callable']); + + return $resolver; + } + + protected function getValue($entity, array $options) + { + $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder() + ->getPropertyAccessor() + ; + + if (null !== $options['property']) { + $value = $propertyAccessor->getValue($entity, $options['property']); + } elseif (null !== $options['property_builder']) { + $value = call_user_func($options['property_builder'], $entity, $options); + } else { + throw new CrudConfigurationException('Unable to get the value. One of "property" and "property_builder" is required.'); + } + + return $value; + } + + protected function getView(array $options) + { + return $options['view']; + } +} diff --git a/core/Crud/Field/TextField.php b/core/Crud/Field/TextField.php new file mode 100644 index 0000000..50366b2 --- /dev/null +++ b/core/Crud/Field/TextField.php @@ -0,0 +1,24 @@ + + */ +class TextField extends Field +{ + public function configureOptions(OptionsResolver $resolver): OptionsResolver + { + parent::configureOptions($resolver); + + $resolver->setDefaults([ + 'view' => '@Core/admin/crud/field/text.html.twig', + ]); + + return $resolver; + } +} diff --git a/core/EventSuscriber/EntityManagerEventSubscriber.php b/core/EventSuscriber/EntityManagerEventSubscriber.php index 8d663dc..9ba7a91 100644 --- a/core/EventSuscriber/EntityManagerEventSubscriber.php +++ b/core/EventSuscriber/EntityManagerEventSubscriber.php @@ -12,7 +12,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; */ abstract class EntityManagerEventSubscriber implements EventSubscriberInterface { - static protected int $priority = 0; + protected static int $priority = 0; public static function getSubscribedEvents() { diff --git a/core/EventSuscriber/Site/MenuEventSubscriber.php b/core/EventSuscriber/Site/MenuEventSubscriber.php index 56ab531..1edc129 100644 --- a/core/EventSuscriber/Site/MenuEventSubscriber.php +++ b/core/EventSuscriber/Site/MenuEventSubscriber.php @@ -2,6 +2,7 @@ namespace App\Core\EventSuscriber\Site; +use App\Core\Cache\SymfonyCacheManager; use App\Core\Entity\EntityInterface; use App\Core\Entity\Site\Menu; use App\Core\Event\EntityManager\EntityManagerEvent; @@ -10,7 +11,6 @@ use App\Core\Factory\Site\NodeFactory; use App\Core\Manager\EntityManager; use App\Core\Repository\Site\NodeRepository; use App\Core\Slugify\CodeSlugify; -use App\Core\Cache\SymfonyCacheManager; /** * class MenuEventSubscriber. diff --git a/core/EventSuscriber/Site/SiteEventSubscriber.php b/core/EventSuscriber/Site/SiteEventSubscriber.php index db9d5e6..7655cc8 100644 --- a/core/EventSuscriber/Site/SiteEventSubscriber.php +++ b/core/EventSuscriber/Site/SiteEventSubscriber.php @@ -2,15 +2,14 @@ namespace App\Core\EventSuscriber\Site; +use App\Core\Cache\SymfonyCacheManager; use App\Core\Entity\EntityInterface; use App\Core\Entity\Site\Menu; use App\Core\Entity\Site\Navigation; use App\Core\Entity\Site\Node; use App\Core\Event\EntityManager\EntityManagerEvent; use App\Core\EventSuscriber\EntityManagerEventSubscriber; -use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\KernelInterface; -use App\Core\Cache\SymfonyCacheManager; /** * class SiteEventSubscriber. diff --git a/core/Factory/FactoryInterface.php b/core/Factory/FactoryInterface.php new file mode 100644 index 0000000..e31ca9e --- /dev/null +++ b/core/Factory/FactoryInterface.php @@ -0,0 +1,12 @@ + + */ +interface FactoryInterface +{ +} diff --git a/core/Factory/SettingFactory.php b/core/Factory/SettingFactory.php index ceeb0dd..f2e766c 100644 --- a/core/Factory/SettingFactory.php +++ b/core/Factory/SettingFactory.php @@ -9,7 +9,7 @@ use App\Core\Entity\Setting; * * @author Simon Vieille */ -class SettingFactory +class SettingFactory implements FactoryInterface { public function create(string $code): Setting { diff --git a/core/Factory/Site/MenuFactory.php b/core/Factory/Site/MenuFactory.php index c388e8c..48e2c44 100644 --- a/core/Factory/Site/MenuFactory.php +++ b/core/Factory/Site/MenuFactory.php @@ -4,13 +4,14 @@ namespace App\Core\Factory\Site; use App\Core\Entity\Site\Menu; use App\Core\Entity\Site\Navigation; +use App\Core\Factory\FactoryInterface; /** * class MenuFactory. * * @author Simon Vieille */ -class MenuFactory +class MenuFactory implements FactoryInterface { public function create(?Navigation $navigation = null): Menu { diff --git a/core/Factory/Site/NavigationFactory.php b/core/Factory/Site/NavigationFactory.php index 52b2d66..a4196db 100644 --- a/core/Factory/Site/NavigationFactory.php +++ b/core/Factory/Site/NavigationFactory.php @@ -3,13 +3,14 @@ namespace App\Core\Factory\Site; use App\Core\Entity\Site\Navigation; +use App\Core\Factory\FactoryInterface; /** * class NavigationFactory. * * @author Simon Vieille */ -class NavigationFactory +class NavigationFactory implements FactoryInterface { public function create(): Navigation { diff --git a/core/Factory/Site/NodeFactory.php b/core/Factory/Site/NodeFactory.php index 288c0ba..587d38a 100644 --- a/core/Factory/Site/NodeFactory.php +++ b/core/Factory/Site/NodeFactory.php @@ -4,13 +4,14 @@ namespace App\Core\Factory\Site; use App\Core\Entity\Site\Menu; use App\Core\Entity\Site\Node; +use App\Core\Factory\FactoryInterface; /** * class NodeFactory. * * @author Simon Vieille */ -class NodeFactory +class NodeFactory implements FactoryInterface { public function create(?Menu $menu = null, string $url = null): Node { diff --git a/core/Factory/Site/Page/PageFactory.php b/core/Factory/Site/Page/PageFactory.php index 2ecdd07..c408d31 100644 --- a/core/Factory/Site/Page/PageFactory.php +++ b/core/Factory/Site/Page/PageFactory.php @@ -3,13 +3,14 @@ namespace App\Core\Factory\Site\Page; use App\Core\Entity\Site\Page\Page; +use App\Core\Factory\FactoryInterface; /** * class PageFactory. * * @author Simon Vieille */ -class PageFactory +class PageFactory implements FactoryInterface { public function create(string $className, string $name): Page { diff --git a/core/Factory/UserFactory.php b/core/Factory/UserFactory.php index ed21651..9d4d4bc 100644 --- a/core/Factory/UserFactory.php +++ b/core/Factory/UserFactory.php @@ -11,7 +11,7 @@ use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; * * @author Simon Vieille */ -class UserFactory +class UserFactory implements FactoryInterface { protected TokenGeneratorInterface $tokenGenerator; protected UserPasswordEncoderInterface $encoder; diff --git a/core/Form/Site/NavigationType.php b/core/Form/Site/NavigationType.php index 09f551f..291c7a0 100644 --- a/core/Form/Site/NavigationType.php +++ b/core/Form/Site/NavigationType.php @@ -7,8 +7,8 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; class NavigationType extends AbstractType { diff --git a/core/Form/Site/Page/Filter/PageFilterType.php b/core/Form/Site/Page/Filter/PageFilterType.php index fc3cc0f..8eebd68 100644 --- a/core/Form/Site/Page/Filter/PageFilterType.php +++ b/core/Form/Site/Page/Filter/PageFilterType.php @@ -2,18 +2,13 @@ namespace App\Core\Form\Site\Page\Filter; -use App\Core\Entity\Site\Page\Page; +use App\Core\Entity\Site\Navigation; +use Doctrine\ORM\EntityRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Validator\Constraints\Image; -use Symfony\Component\Validator\Constraints\NotBlank; -use Symfony\Bridge\Doctrine\Form\Type\EntityType; -use App\Core\Entity\Site\Navigation; -use Doctrine\ORM\EntityRepository; class PageFilterType extends AbstractType { diff --git a/core/Repository/RepositoryQuery.php b/core/Repository/RepositoryQuery.php index 8022548..2792efb 100644 --- a/core/Repository/RepositoryQuery.php +++ b/core/Repository/RepositoryQuery.php @@ -74,6 +74,27 @@ abstract class RepositoryQuery return $this->repository; } + public function useFilters(array $filters) + { + foreach ($filters as $name => $value) { + if (null === $value) { + continue; + } + + if (is_int($value) || is_bool($value)) { + $this->andWhere('.'.$name.' = :'.$name); + $this->setParameter(':'.$name, $value); + } elseif (is_string($value)) { + $this->andWhere('.'.$name.' LIKE :'.$name); + $this->setParameter(':'.$name, '%'.$value.'%'); + } else { + $this->filterHandler($name, $value); + } + } + + return $this; + } + protected function populateDqlId(&$data) { if (is_string($data)) { @@ -98,25 +119,4 @@ abstract class RepositoryQuery protected function filterHandler(string $name, $value) { } - - public function useFilters(array $filters) - { - foreach ($filters as $name => $value) { - if (null === $value) { - continue; - } - - if (is_int($value) || is_bool($value)) { - $this->andWhere('.'.$name.' = :'.$name); - $this->setParameter(':'.$name, $value); - } elseif (is_string($value)) { - $this->andWhere('.'.$name.' LIKE :'.$name); - $this->setParameter(':'.$name, '%'.$value.'%'); - } else { - $this->filterHandler($name, $value); - } - } - - return $this; - } } diff --git a/core/Repository/Site/Page/PageRepositoryQuery.php b/core/Repository/Site/Page/PageRepositoryQuery.php index 3746684..efd9145 100644 --- a/core/Repository/Site/Page/PageRepositoryQuery.php +++ b/core/Repository/Site/Page/PageRepositoryQuery.php @@ -2,9 +2,9 @@ namespace App\Core\Repository\Site\Page; +use App\Core\Entity\Site\Navigation; use App\Core\Repository\RepositoryQuery; use Knp\Component\Pager\PaginatorInterface; -use App\Core\Entity\Site\Navigation; /** * class PageRepositoryQuery. @@ -18,15 +18,6 @@ class PageRepositoryQuery extends RepositoryQuery parent::__construct($repository, 'p', $paginator); } - protected function filterHandler(string $name, $value) - { - if ($name === 'navigation') { - return $this->filterByNavigation($value); - } else { - return parent::filterHandler($name, $value); - } - } - public function filterByNavigation(Navigation $navigation) { return $this @@ -34,7 +25,8 @@ class PageRepositoryQuery extends RepositoryQuery ->leftJoin('node.menu', 'menu') ->leftJoin('menu.navigation', 'navigation') ->where('navigation.id = :navigationId') - ->setParameter(':navigationId', $navigation->getId()); + ->setParameter(':navigationId', $navigation->getId()) + ; } public function filterById($id) @@ -46,4 +38,13 @@ class PageRepositoryQuery extends RepositoryQuery return $this; } + + protected function filterHandler(string $name, $value) + { + if ('navigation' === $name) { + return $this->filterByNavigation($value); + } + + return parent::filterHandler($name, $value); + } } diff --git a/core/Resources/translations/messages.fr.yaml b/core/Resources/translations/messages.fr.yaml index 23f95f1..c5a6ae6 100644 --- a/core/Resources/translations/messages.fr.yaml +++ b/core/Resources/translations/messages.fr.yaml @@ -86,6 +86,7 @@ "Add a menu": "Ajouter un menu" "Actions": "Actions" "Remove": "Supprimer" +"Delete": "Supprimer" "Move": "Déplacer" "Hidden": "Caché" "Show": "Voir" @@ -126,9 +127,13 @@ "Name": "Nom" "Authentication": "Authentification" "Anyway": "Peu importe" +"Reset": "Réinitialiser" "Yes": "Oui" "No": "Non" +"yes": "oui" +"no": "non" "Locale": "Langue" "Settings": "Paramètres" "Setting": "Paramètre" "Section": "Section" +"Filter": "Filtrer" diff --git a/core/Resources/views/admin/crud/_form.html.twig b/core/Resources/views/admin/crud/_form.html.twig new file mode 100644 index 0000000..796d8a5 --- /dev/null +++ b/core/Resources/views/admin/crud/_form.html.twig @@ -0,0 +1,6 @@ +
+
+ {{ form_widget(form) }} +
+
+ diff --git a/core/Resources/views/admin/crud/_show.html.twig b/core/Resources/views/admin/crud/_show.html.twig new file mode 100644 index 0000000..a769790 --- /dev/null +++ b/core/Resources/views/admin/crud/_show.html.twig @@ -0,0 +1 @@ +

{{ '{__toString}'|build_string(entity) }}

diff --git a/core/Resources/views/admin/crud/edit.html.twig b/core/Resources/views/admin/crud/edit.html.twig new file mode 100644 index 0000000..0893338 --- /dev/null +++ b/core/Resources/views/admin/crud/edit.html.twig @@ -0,0 +1,88 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block title %}{{ configuration.pageTitle('edit')|trans|build_string(entity) }} - {{ parent() }}{% endblock %} + +{% block body %} + {% block header %} +
+
+ {% block header_title %} +

{{ configuration.pageTitle('edit')|trans|build_string(entity) }}

+ {% endblock %} + + {% block header_actions %} +
+
+ {% if configuration.action('edit', 'back', true) %} + + + {{ configuration.actionTitle('edit', 'back', 'Back to the list')|trans }} + + {% endif %} + + {% if configuration.action('edit', 'show', true) %} + + + {{ configuration.actionTitle('edit', 'show', 'Show')|trans|build_string(entity) }} + + {% endif %} + + + + {% block dropdownMenu %} + {% set menu = '' %} + + {% if configuration.action('edit', 'delete', true) %} + {% set item %} + + {% endset %} + + {% set menu = menu ~ item %} + {% endif %} + + {% if menu %} + + + {{ menu|raw }} + {% endif %} + {% endblock %} + +
+
+ {% endblock %} +
+
+ {% endblock %} + + {% block form %} +
+
+
+
+ {{ include(configuration.view('editForm', '@Core/admin/crud/_form.html.twig')) }} +
+
+
+ + {{ form_rest(form) }} +
+ {% endblock %} + + {% if configuration.action('edit', 'delete', true) %} +
+ + +
+ {% endif %} +{% endblock %} diff --git a/core/Resources/views/admin/crud/field/button.html.twig b/core/Resources/views/admin/crud/field/button.html.twig new file mode 100644 index 0000000..ab9cb02 --- /dev/null +++ b/core/Resources/views/admin/crud/field/button.html.twig @@ -0,0 +1 @@ +<{{ options.button_tag }} {% for k, v in options.button_attr %}{{ k }}="{{ v }}"{% endfor %}>{{ value }} diff --git a/core/Resources/views/admin/crud/field/date.html.twig b/core/Resources/views/admin/crud/field/date.html.twig new file mode 100644 index 0000000..449180d --- /dev/null +++ b/core/Resources/views/admin/crud/field/date.html.twig @@ -0,0 +1 @@ +{{ value|date(options.format) }} diff --git a/core/Resources/views/admin/crud/field/text.html.twig b/core/Resources/views/admin/crud/field/text.html.twig new file mode 100644 index 0000000..40cf72a --- /dev/null +++ b/core/Resources/views/admin/crud/field/text.html.twig @@ -0,0 +1 @@ +{{ value }} diff --git a/core/Resources/views/admin/crud/filter.html.twig b/core/Resources/views/admin/crud/filter.html.twig new file mode 100644 index 0000000..aca97ad --- /dev/null +++ b/core/Resources/views/admin/crud/filter.html.twig @@ -0,0 +1,21 @@ + diff --git a/core/Resources/views/admin/crud/index.html.twig b/core/Resources/views/admin/crud/index.html.twig new file mode 100644 index 0000000..315bbb9 --- /dev/null +++ b/core/Resources/views/admin/crud/index.html.twig @@ -0,0 +1,127 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block title %}{{ configuration.pageTitle('index')|trans }} - {{ parent() }}{% endblock %} + +{% block body %} + {% block header %} +
+
+ {% block header_title %} +

{{ configuration.pageTitle('index')|trans }}

+ {% endblock %} + + {% block header_actions %} +
+
+ {% if configuration.action('index', 'new', true) %} + + + {{ configuration.actionTitle('index', 'new', 'New')|trans }} + + {% endif %} +
+
+ {% endblock %} +
+ + {% block header_filter_pager %} + {% if filters.show %} +
+
+ +
+
+ {{ knp_pagination_render(pager) }} +
+
+ {% else %} + {{ knp_pagination_render(pager) }} + {% endif %} + {% endblock %} +
+ {% endblock %} + + {% block list %} +
+ + {% block list_header %} + + + {% for label, config in configuration.fields('index') %} + {% set attr = config.options.attr is defined ? config.options.attr : [] %} + + + {% endfor %} + + + + {% endblock %} + + {% block list_items %} + + {% for item in pager %} + {% block list_item %} + + {% for config in configuration.fields('index') %} + {% set attr = config.options.attr is defined ? config.options.attr : [] %} + {% set action = config.options.action is defined ? config.options.action : null %} + + + {% endfor %} + + {% if configuration.action('index', 'edit', true) or configuration.action('index', 'delete', true) %} + + {% endif %} + + {% endblock %} + {% else %} + + + + {% endfor %} + + {% endblock %} +
+ {{ label|trans }} + Actions
+ {% if action == 'show' %} + + {{ render_field(item, config) }} + + {% elseif action == 'edit' %} + + {{ render_field(item, config) }} + + {% else %} + {{ render_field(item, config) }} + {% endif %} + + {% if configuration.action('index', 'edit', true) %} + + + + {% endif %} + + {% if configuration.action('index', 'delete', true) %} + + +
+ + +
+ {% endif %} +
+
+ +
+
+ Aucun résultat +
+
+
+ {% endblock %} +{% endblock %} diff --git a/core/Resources/views/admin/crud/new.html.twig b/core/Resources/views/admin/crud/new.html.twig new file mode 100644 index 0000000..e9fa50f --- /dev/null +++ b/core/Resources/views/admin/crud/new.html.twig @@ -0,0 +1,47 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block title %}{{ configuration.pageTitle('new')|trans|build_string(entity) }} - {{ parent() }}{% endblock %} + +{% block body %} + {% block header %} +
+
+ {% block header_title %} +

{{ configuration.pageTitle('new')|trans|build_string(entity) }}

+ {% endblock %} + + {% block header_actions %} +
+
+ {% if configuration.action('new', 'back', true) %} + + + {{ configuration.actionTitle('new', 'back', 'Back to the list')|trans }} + + {% endif %} + + +
+
+ {% endblock %} +
+
+ {% endblock %} + + {% block form %} +
+
+
+
+ {{ include(configuration.view('newForm', '@Core/admin/crud/_form.html.twig')) }} +
+
+
+ + {{ form_rest(form) }} +
+ {% endblock %} +{% endblock %} diff --git a/core/Resources/views/admin/crud/show.html.twig b/core/Resources/views/admin/crud/show.html.twig new file mode 100644 index 0000000..dba911f --- /dev/null +++ b/core/Resources/views/admin/crud/show.html.twig @@ -0,0 +1,44 @@ +{% extends '@Core/admin/layout.html.twig' %} + +{% block title %}{{ configuration.pageTitle('show')|trans|build_string(entity) }} - {{ parent() }}{% endblock %} + +{% block body %} + {% block header %} +
+
+ {% block header_title %} +

{{ configuration.pageTitle('show')|trans|build_string(entity) }}

+ {% endblock %} + + {% block header_actions %} +
+
+ {% if configuration.action('show', 'back', true) %} + + + {{ configuration.actionTitle('show', 'back', 'Back to the list')|trans }} + + {% endif %} + + {% if configuration.action('show', 'edit', true) %} + + + + {{ configuration.actionTitle('show', 'edit', 'Edit')|trans|build_string(entity) }} + + {% endif %} +
+
+ {% endblock %} +
+
+ {% endblock %} + + {% block show %} +
+
+ {{ include(configuration.view('showEntity', '@Core/admin/crud/_show.html.twig')) }} +
+
+ {% endblock %} +{% endblock %} diff --git a/core/Setting/SettingManager.php b/core/Setting/SettingManager.php index f6018ec..01d2937 100644 --- a/core/Setting/SettingManager.php +++ b/core/Setting/SettingManager.php @@ -2,10 +2,10 @@ namespace App\Core\Setting; +use App\Core\Entity\Setting; use App\Core\Factory\SettingFactory; use App\Core\Manager\EntityManager; use App\Core\Repository\SettingRepositoryQuery; -use App\Core\Entity\Setting; /** * class SettingManager. diff --git a/core/String/StringBuilder.php b/core/String/StringBuilder.php index 5164286..b749e6d 100644 --- a/core/String/StringBuilder.php +++ b/core/String/StringBuilder.php @@ -18,6 +18,7 @@ class StringBuilder public function __construct() { $this->propertyAccessor = PropertyAccess::createPropertyAccessorBuilder() + ->disableExceptionOnInvalidPropertyPath() ->getPropertyAccessor() ; } @@ -33,12 +34,12 @@ class StringBuilder return $format; } - preg_match_all('/\{([a-zA-Z0-9\.]+)\}/i', $format, $matches, PREG_SET_ORDER); + preg_match_all('/\{([a-zA-Z0-9\._]+)\}/i', $format, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $propertyValue = $this->propertyAccessor->getValue($object, $match[1]); - $format = u($format)->replace($match[0], $propertyValue); + $format = u($format)->replace($match[0], (string) $propertyValue); } return $format; diff --git a/core/Twig/Extension/CrudExtension.php b/core/Twig/Extension/CrudExtension.php new file mode 100644 index 0000000..480d421 --- /dev/null +++ b/core/Twig/Extension/CrudExtension.php @@ -0,0 +1,44 @@ +propertyAccessor = PropertyAccess::createPropertyAccessorBuilder() + ->getPropertyAccessor() + ; + + $this->twig = $twig; + } + + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return [ + new TwigFunction('render_field', [$this, 'renderField'], ['is_safe' => ['html']]), + ]; + } + + public function renderField($entity, array $config): string + { + $field = $config['field']; + $instance = new $field(); + $resolver = $instance->configureOptions(new OptionsResolver()); + + return $instance->buildView($this->twig, $entity, $resolver->resolve($config['options'])); + } +} diff --git a/src/Factory/Blog/CategoryFactory.php b/src/Factory/Blog/CategoryFactory.php index eb21c80..603178c 100644 --- a/src/Factory/Blog/CategoryFactory.php +++ b/src/Factory/Blog/CategoryFactory.php @@ -2,6 +2,7 @@ namespace App\Factory\Blog; +use App\Core\Factory\FactoryInterface; use App\Entity\Blog\Category; /** @@ -9,7 +10,7 @@ use App\Entity\Blog\Category; * * @author Simon Vieille */ -class CategoryFactory +class CategoryFactory implements FactoryInterface { public function create(): Category { diff --git a/src/Factory/Blog/CommentFactory.php b/src/Factory/Blog/CommentFactory.php index 9ce5cbb..b713546 100644 --- a/src/Factory/Blog/CommentFactory.php +++ b/src/Factory/Blog/CommentFactory.php @@ -2,6 +2,7 @@ namespace App\Factory\Blog; +use App\Core\Factory\FactoryInterface; use App\Entity\Blog\Comment; use App\Entity\Blog\Post; @@ -10,7 +11,7 @@ use App\Entity\Blog\Post; * * @author Simon Vieille */ -class CommentFactory +class CommentFactory implements FactoryInterface { public function create(Post $post): Comment { diff --git a/src/Factory/Blog/PostFactory.php b/src/Factory/Blog/PostFactory.php index 1c8887e..1c17ee4 100644 --- a/src/Factory/Blog/PostFactory.php +++ b/src/Factory/Blog/PostFactory.php @@ -2,6 +2,7 @@ namespace App\Factory\Blog; +use App\Core\Factory\FactoryInterface; use App\Entity\Blog\Post; /** @@ -9,7 +10,7 @@ use App\Entity\Blog\Post; * * @author Simon Vieille */ -class PostFactory +class PostFactory implements FactoryInterface { public function create(): Post {