diff --git a/core/Controller/Admin/Crud/CrudController.php b/core/Controller/Admin/Crud/CrudController.php new file mode 100644 index 0000000..37d0153 --- /dev/null +++ b/core/Controller/Admin/Crud/CrudController.php @@ -0,0 +1,156 @@ + + */ +abstract class CrudController extends AdminController +{ + abstract protected function getConfiguration(): CrudConfiguration; + + protected function doIndex(int $page = 1, RepositoryQuery $query, Request $request, Session $session): Response + { + /*$this->updateFilters($options['request'], $options['session']);*/ + + $pager = $query + //->useFilters($this->filters) + ->paginate($page) + ; + + /*$viewOptions = array_merge([ + 'pager' => $pager, + 'hasFilters' => !empty($this->filters), + ], $options['viewOptions']);*/ + + $configuration = $this->getConfiguration(); + + return $this->render($this->getConfiguration()->getView('index'), [ + 'configuration' => $configuration, + 'pager' => $pager, + ]); + } + + protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request): Response + { + $form = $this->createForm($this->forms['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(), + '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 + { + 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 + { + $form = $this->createForm($this->forms['filters']); + $form->submit($session->get($this->filterRequestId, [])); + + return $this->render($this->getView('filters'), [ + 'form' => $form->createView(), + ]); + } + + protected function updateFilters(Request $request, Session $session) + { + if ($request->query->has($this->filterRequestId)) { + $filters = $request->query->get($this->filterRequestId); + + if ('0' === $filters) { + $filters = []; + } + } elseif ($session->has($this->filterRequestId)) { + $filters = $session->get($this->filterRequestId); + } else { + $filters = []; + } + + if (isset($this->forms['filters'])) { + $form = $this->createForm($this->forms['filters']); + $form->submit($filters); + } else { + $form = null; + } + + if (empty($filters)) { + $this->filters = $filters; + $session->set($this->filterRequestId, $filters); + } elseif (null !== $form && $form->isValid()) { + $this->filters = $form->getData(); + $session->set($this->filterRequestId, $filters); + } + } +} diff --git a/core/Crud/CrudConfiguration.php b/core/Crud/CrudConfiguration.php new file mode 100644 index 0000000..1fc24bf --- /dev/null +++ b/core/Crud/CrudConfiguration.php @@ -0,0 +1,149 @@ + + */ +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 = []; + + /* -- */ + + 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]; + } + + 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] ?? []; + } +} 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/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/index.html.twig b/core/Resources/views/admin/crud/index.html.twig new file mode 100644 index 0000000..12dd48e --- /dev/null +++ b/core/Resources/views/admin/crud/index.html.twig @@ -0,0 +1,112 @@ +{% 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 %} +
+ + {{ knp_pagination_render(pager) }} +
+ {% 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/Twig/Extension/CrudExtension.php b/core/Twig/Extension/CrudExtension.php new file mode 100644 index 0000000..46800fd --- /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'])); + } +}