add node attributes and node paramters

This commit is contained in:
Simon Vieille 2021-03-22 12:42:48 +01:00
parent 310689eae5
commit 633e16771a
19 changed files with 579 additions and 118 deletions

View file

@ -342,7 +342,6 @@ table.table-fixed, .table-fixed > table {
*[data-collection-delete-container] {
cursor: pointer;
margin-top: 35px;
}
*[data-collection-add] {

View file

@ -44,13 +44,13 @@ const FormCollection = () => {
CollectionInitilizedAndUpdated
);
$('*[data-collection]').on(
$('body').on(
'click',
'*[data-collection-delete], *[data-collection-delete-container]',
DeleteHandler
);
$('*[data-collection-add]').click((e) => {
$('body').on('click', '*[data-collection-add]', (e) => {
e.stopPropagation()
const collectionId = $(e.target).attr('data-collection-add')

View file

@ -97,6 +97,21 @@ class Node implements EntityInterface
*/
private $code;
/**
* @ORM\Column(type="array", nullable=true)
*/
private $parameters = [];
/**
* @ORM\Column(type="array", nullable=true)
*/
private $attributes = [];
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $controller;
public function __construct()
{
$this->children = new ArrayCollection();
@ -305,4 +320,48 @@ class Node implements EntityInterface
return $this;
}
public function getParameters(): ?array
{
if (!is_array($this->parameters)) {
$this->parameters = [];
}
return $this->parameters;
}
public function setParameters(array $parameters): self
{
$this->parameters = $parameters;
return $this;
}
public function getAttributes(): ?array
{
if (!is_array($this->attributes)) {
$this->attributes = [];
}
return $this->attributes;
}
public function setAttributes(array $attributes): self
{
$this->attributes = $attributes;
return $this;
}
public function getController(): ?string
{
return $this->controller;
}
public function setController(?string $controller): self
{
$this->controller = $controller;
return $this;
}
}

View file

@ -59,7 +59,7 @@ class Block
return $this->value;
}
public function setValue(string $value): self
public function setValue($value): self
{
$this->value = $value;

View file

@ -11,6 +11,7 @@ use App\Core\Manager\EntityManager;
use App\Core\Repository\Site\NodeRepository;
use App\Core\Slugify\Slugify;
use Symfony\Component\HttpKernel\KernelInterface;
use App\Core\Slugify\CodeSlugify;
/**
* class NodeEventSubscriber.
@ -23,17 +24,20 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber
protected EntityManager $entityManager;
protected KernelInterface $kernel;
protected Slugify $slugify;
protected CodeSlugify $codeSlugify;
public function __construct(
NodeFactory $nodeFactory,
NodeRepository $nodeRepository,
EntityManager $entityManager,
Slugify $slugify
Slugify $slugify,
CodeSlugify $codeSlugify
) {
$this->nodeFactory = $nodeFactory;
$this->nodeRepository = $nodeRepository;
$this->entityManager = $entityManager;
$this->slugify = $slugify;
$this->codeSlugify = $codeSlugify;
}
public function support(EntityInterface $entity)
@ -49,6 +53,8 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber
$node = $event->getEntity();
$node->setCode($this->codeSlugify->slugify($node->getCode()));
if ($node->getUrl()) {
$generatedUrl = $node->getUrl();
} else {
@ -68,6 +74,17 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber
$generatedUrl = '/'.implode('/', $path);
}
$generatedUrl = rtrim($generatedUrl, '/');
foreach ($node->getParameters() as $parameter) {
$routeParameter = sprintf('{%s}', $parameter['name']);
$regex = '/'.preg_quote($routeParameter).'/';
if (!preg_match($regex, $generatedUrl)) {
$generatedUrl.= '/'.$routeParameter;
}
}
$urlExists = $this->nodeRepository->urlExists($generatedUrl, $node);
if ($urlExists) {

View file

@ -0,0 +1,58 @@
<?php
namespace App\Core\EventSuscriber\Site\Page;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Page\Block;
use App\Core\Event\EntityManager\EntityManagerEvent;
use App\Core\EventSuscriber\EntityManagerEventSubscriber;
use App\Core\Form\FileUploadHandler;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* class BlockEventSubscriber.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class BlockEventSubscriber extends EntityManagerEventSubscriber
{
protected FileUploadHandler $fileUpload;
public function __construct(FileUploadHandler $fileUpload)
{
$this->fileUpload = $fileUpload;
}
public function support(EntityInterface $entity)
{
return $entity instanceof Block;
}
public function onPreUpdate(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
return;
}
$block = $event->getEntity();
if (!$block->getValue() instanceof UploadedFile) {
return;
}
$directory = 'uploads/page/block';
$fileUpload->handleForm(
$block->getValue(),
$directory,
function ($filename) use ($block) {
$block->setValue($directory.'/'.$filename);
}
);
}
public function onPreCreate(EntityManagerEvent $event)
{
return $this->onPreUpdate($event);
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Core\Form\Site;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\NotBlank;
class NodeAttributeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'label',
TextType::class,
[
'label' => 'Libellé',
'required' => true,
'attr' => [
],
'constraints' => [
new NotBlank(),
],
]
);
$builder->add(
'value',
TextType::class,
[
'label' => 'Valeur',
'required' => false,
'attr' => [
],
'constraints' => [
],
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace App\Core\Form\Site;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\NotBlank;
class NodeParameterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'name',
TextType::class,
[
'label' => 'Nom',
'required' => true,
'attr' => [
],
'constraints' => [
new NotBlank(),
],
]
);
$builder->add(
'defaultValue',
TextType::class,
[
'label' => 'Valeur par défaut',
'required' => false,
'attr' => [
],
'constraints' => [
],
]
);
$builder->add(
'requirement',
TextType::class,
[
'label' => 'Éxigence',
'required' => false,
'attr' => [
],
'constraints' => [
],
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}

View file

@ -12,6 +12,7 @@ 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\Form\Extension\Core\Type\CollectionType;
class NodeType extends AbstractType
{
@ -59,6 +60,20 @@ class NodeType extends AbstractType
]
);
$builder->add(
'controller',
TextType::class,
[
'label' => 'Contrôleur',
'required' => false,
'help' => 'Laisser vide pour utiliser celui par défaut. Notation : App\\Controller\\FooController::barAction',
'attr' => [
],
'constraints' => [
],
]
);
$actions = [
'Nouvelle page' => 'new',
'Associer à une page existante' => 'existing',
@ -125,6 +140,30 @@ class NodeType extends AbstractType
]
);
$builder->add(
'parameters',
CollectionType::class,
[
'entry_type' => NodeParameterType::class,
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
]
);
$builder->add(
'attributes',
CollectionType::class,
[
'entry_type' => NodeAttributeType::class,
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
]
);
if (null === $builder->getData()->getId()) {
$builder->add(
'position',

View file

@ -42,13 +42,20 @@ class SiteRouteLoader extends Loader
$requirements = [];
$defaults = [
'_controller' => PageController::class.'::show',
'_controller' => $node->getController() ?? PageController::class.'::show',
'_node' => $node->getId(),
'_menu' => $menu->getId(),
'_page' => $node->getPage() ? $node->getPage()->getId() : null,
'_navigation' => $navigation->getId(),
];
foreach ($node->getParameters() as $parameter) {
$name = $parameter['name'];
$requirements[$name] = $parameter['requirement'];
$defaults[$name] = $parameter['defaultValue'];
}
$route = new Route($node->getUrl(), $defaults, $requirements);
$route->setHost($navigation->getDomain());

View file

@ -11,6 +11,14 @@ use Cocur\Slugify\Slugify as BaseSlugify;
*/
class CodeSlugify extends Slugify
{
public function slugify($data): ? string
{
$slug = parent::slugify($data);
$slug = preg_replace('/[^\w]+/', '', $slug);
return $slug;
}
protected function create(): BaseSlugify
{
$slugify = new BaseSlugify([
@ -19,7 +27,6 @@ class CodeSlugify extends Slugify
]);
$slugify->activateRuleSet('french');
$slugify->addRule("'", '');
return $slugify;
}

View file

@ -11,7 +11,7 @@ use Cocur\Slugify\Slugify as BaseSlugify;
*/
class Slugify
{
public function slugify($data)
public function slugify($data): ?string
{
return $this->create()->slugify($data);
}

View file

@ -69,11 +69,13 @@ class PostAdminController extends AdminController
$form->handleRequest($request);
if ($form->isValid()) {
$directory = 'uploads/post/'.date('Y');
$fileUpload->handleForm(
$form->get('image')->getData(),
'uploads/post/'.date('Y'),
$directory,
function ($filename) use ($entity) {
$entity->setImage('post/'.date('Y').'/'.$filename);
$entity->setImage($directory.'/'.$filename);
}
);

View file

@ -4,10 +4,11 @@ namespace App\Entity\Page;
use App\Core\Entity\Site\Page\Block;
use App\Core\Entity\Site\Page\Page;
use App\Form\Site\Page\TextareaBlockType;
use App\Form\Site\Page\TextBlockType;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Form\FormBuilderInterface;
use App\Core\Form\Site\Page\FileBlockType;
use App\Core\Form\Site\Page\TextBlockType;
use App\Core\Form\Site\Page\TextareaBlockType;
/**
* @ORM\Entity
@ -44,6 +45,20 @@ class SimplePage extends Page
],
]
);
// $builder->add(
// 'file',
// FileBlockType::class,
// [
// 'label' => 'Fichier',
// 'options' => [
// 'attr' => [
// ],
// 'constraints' => [
// ],
// ],
// ]
// );
}
public function setTitle(Block $block)
@ -65,4 +80,14 @@ class SimplePage extends Page
{
return $this->getBlock('content');
}
public function setFile(Block $block)
{
return $this->setBlock($block);
}
public function getFile()
{
return $this->getBlock('file');
}
}

View file

@ -37,7 +37,7 @@
<tr>
<td class="col-6">
{% if item.image %}
{% set image = asset('uploads/' ~ item.image) %}
{% set image = asset(item.image) %}
{% else %}
{% set image = asset('build/images/no-image.png') %}
{% endif %}

View file

@ -68,7 +68,7 @@
{% if entity.image %}
<figure>
<img src="{{ asset('uploads/' ~ entity.image) }}" alt="{{ entity.imageCaption }}" title="{{ entity.imageCaption }}" class="img-fluid">
<img src="{{ asset(entity.image) }}" alt="{{ entity.imageCaption }}" title="{{ entity.imageCaption }}" class="img-fluid">
<figcaption>
{{ entity.imageCaption }}

View file

@ -1,108 +1,248 @@
{{ form_row(form.label) }}
{{ form_row(form.url) }}
<ul class="nav nav-pills" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" data-toggle="tab" href="#form-node-edit-content">Contenu</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-toggle="tab" href="#form-node-edit-routing">Routage</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-toggle="tab" href="#form-node-edit-attributes">Attributs</a>
</li>
</ul>
{% if form.position is defined %}
{{ form_row(form.position) }}
{% endif %}
<div class="tab-content pt-4">
<div class="tab-pane show active" id="form-node-edit-content">
{{ form_row(form.label) }}
<div class="accordion mb-3" id="node-page-action">
<div class="card">
{% set action = form.pageAction[0] %}
{% set options = not entity.id ? {'attr': {'checked': 'checked'}} : {} %}
{% if form.position is defined %}
{{ form_row(form.position) }}
{% endif %}
<div class="card-header p-0">
<h2 class="mb-0">
<label class="btn btn-link btn-block text-left"
for="{{ action.vars.id }}"
data-toggle="collapse"
data-target="#form-node-page-action-new">
{{ action.vars.label }}
</label>
<div class="accordion mb-3" id="node-page-action">
<div class="card">
{% set action = form.pageAction[0] %}
{% set options = not entity.id ? {'attr': {'checked': 'checked'}} : {} %}
<div class="d-none">
{{ form_row(action, options) }}
<div class="card-header p-0">
<h2 class="mb-0">
<label class="btn btn-link btn-block text-left"
for="{{ action.vars.id }}"
data-toggle="collapse"
data-target="#form-node-page-action-new">
{{ action.vars.label }}
</label>
<div class="d-none">
{{ form_row(action, options) }}
</div>
</h2>
</div>
</h2>
</div>
<div id="form-node-page-action-new" class="collapse {% if not entity.id %}show{% endif %}" data-parent="#node-page-action">
<div class="card-body">
{{ form_row(form.pageType) }}
</div>
</div>
</div>
<div class="card">
{% set action = form.pageAction[1] %}
<div class="card-header p-0">
<h2 class="mb-0">
<label class="btn btn-link btn-block text-left"
for="{{ action.vars.id }}"
data-toggle="collapse"
data-target="#form-node-page-action-existing">
{{ action.vars.label }}
</label>
<div class="d-none">
{{ form_row(action) }}
</div>
</h2>
</div>
<div id="form-node-page-action-existing" class="collapse" data-parent="#node-page-action">
<div class="card-body">
{{ form_row(form.pageEntity) }}
</div>
</div>
</div>
<div class="card">
{% set action = form.pageAction[2] %}
<div class="card-header p-0">
<h2 class="mb-0">
<label class="btn btn-link btn-block text-left"
for="{{ action.vars.id }}"
data-toggle="collapse"
data-target="#form-node-page-action-none">
{{ action.vars.label }}
</label>
<div class="d-none">
{{ form_row(action) }}
</div>
</h2>
</div>
<div id="form-node-page-action-none" class="collapse" data-parent="#node-page-action">
<div class="card-body">
Aucune action
</div>
</div>
</div>
{% if entity.id %}
<div class="card">
{% set action = form.pageAction[3] %}
{% set options = {'attr': {'checked': 'checked'}} %}
<div class="card-header p-0">
<h2 class="mb-0">
<label class="btn btn-link btn-block text-left"
for="{{ action.vars.id }}"
data-toggle="collapse"
data-target="#form-node-page-action-keep">
{{ action.vars.label }}
</label>
<div class="d-none">
{{ form_row(action, options) }}
<div id="form-node-page-action-new" class="collapse {% if not entity.id %}show{% endif %}" data-parent="#node-page-action">
<div class="card-body">
{{ form_row(form.pageType) }}
</div>
</h2>
</div>
<div id="form-node-page-action-keep" class="collapse show" data-parent="#node-page-action">
<div class="card-body">
Aucune action
</div>
</div>
<div class="card">
{% set action = form.pageAction[1] %}
<div class="card-header p-0">
<h2 class="mb-0">
<label class="btn btn-link btn-block text-left"
for="{{ action.vars.id }}"
data-toggle="collapse"
data-target="#form-node-page-action-existing">
{{ action.vars.label }}
</label>
<div class="d-none">
{{ form_row(action) }}
</div>
</h2>
</div>
<div id="form-node-page-action-existing" class="collapse" data-parent="#node-page-action">
<div class="card-body">
{{ form_row(form.pageEntity) }}
</div>
</div>
</div>
<div class="card">
{% set action = form.pageAction[2] %}
<div class="card-header p-0">
<h2 class="mb-0">
<label class="btn btn-link btn-block text-left"
for="{{ action.vars.id }}"
data-toggle="collapse"
data-target="#form-node-page-action-none">
{{ action.vars.label }}
</label>
<div class="d-none">
{{ form_row(action) }}
</div>
</h2>
</div>
<div id="form-node-page-action-none" class="collapse" data-parent="#node-page-action">
<div class="card-body">
Aucune action
</div>
</div>
</div>
{% if entity.id %}
<div class="card">
{% set action = form.pageAction[3] %}
{% set options = {'attr': {'checked': 'checked'}} %}
<div class="card-header p-0">
<h2 class="mb-0">
<label class="btn btn-link btn-block text-left"
for="{{ action.vars.id }}"
data-toggle="collapse"
data-target="#form-node-page-action-keep">
{{ action.vars.label }}
</label>
<div class="d-none">
{{ form_row(action, options) }}
</div>
</h2>
</div>
<div id="form-node-page-action-keep" class="collapse show" data-parent="#node-page-action">
<div class="card-body">
Aucune action
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
</div>
<div class="tab-pane" id="form-node-edit-routing">
{{ form_row(form.url) }}
<div class="pb-1">
Nom de la route : <code>{{ entity.routeName }}</code>
</div>
{{ form_row(form.code) }}
{{ form_row(form.controller) }}
<div class="accordion mb-3" data-collection="collection-node-parameters" id="form-node-edit-parameters-collection">
{% for item in form.parameters %}
<div class="card" data-collection-item="{{ loop.index }}">
<div class="card-header p-0">
<span class="btn btn-link btn-block text-left" data-toggle="collapse" data-target="#form-node-parameter-{{ loop.index }}">
{{ item.vars.data.name }}
</span>
</div>
<div class="collapse" data-parent="#form-node-edit-parameters-collection" id="form-node-parameter-{{ loop.index }}">
<div class="card-body">
{{ form_row(item.name) }}
{{ form_row(item.defaultValue) }}
{{ form_row(item.requirement) }}
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger">
<span data-collection-delete="{{ loop.index }}" class="fa fa-trash"></span>
</span>
</div>
{{ form_rest(item) }}
</div>
</div>
</div>
{% endfor %}
</div>
<div data-collection-add="collection-node-parameters" class="collection-add">
<span class="btn btn-primary" data-collection-add="collection-node-parameters">
<span class="fa fa-plus"></span>
Ajouter un paramètre
</span>
</div>
</div>
<div class="tab-pane" id="form-node-edit-attributes">
<div class="accordion mb-3" data-collection="collection-node-attributes" id="form-node-edit-attributes-collection">
{% for item in form.attributes %}
<div class="card" data-collection-item="{{ loop.index }}">
<div class="card-header p-0">
<span class="btn btn-link btn-block text-left" data-toggle="collapse" data-target="#form-node-attribute-{{ loop.index }}">
{{ item.vars.data.label }}
</span>
</div>
<div class="collapse" data-parent="#form-node-edit-attributes-collection" id="form-node-attribute-{{ loop.index }}">
<div class="card-body">
{{ form_row(item.label) }}
{{ form_row(item.value) }}
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger">
<span data-collection-delete="{{ loop.index }}" class="fa fa-trash"></span>
</span>
</div>
{{ form_rest(item) }}
</div>
</div>
</div>
{% endfor %}
</div>
<div data-collection-add="collection-node-attributes" class="collection-add">
<span class="btn btn-primary" data-collection-add="collection-node-attributes">
<span class="fa fa-plus"></span>
Ajouter un attribut
</span>
</div>
</div>
</div>
<template type="text/template" id="collection-node-parameters">
<div class="card" data-collection-item="__name__">
<div class="card-header p-0">
<span class="btn btn-link btn-block text-left" data-toggle="collapse" data-target="#form-node-parameter-__name__">
Nouveau paramètre
</span>
</div>
<div class="collapse show" id="form-node-parameter-__name__" data-parent="#form-node-edit-parameters-collection">
<div class="card-body">
{{ form_row(form.parameters.vars.prototype.name) }}
{{ form_row(form.parameters.vars.prototype.defaultValue) }}
{{ form_row(form.parameters.vars.prototype.requirement) }}
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger"></span>
</div>
{{ form_rest(form.parameters.vars.prototype) }}
</div>
</div>
</div>
</template>
<template type="text/template" id="collection-node-attributes">
<div class="card" data-collection-item="__name__">
<div class="card-header p-0">
<span class="btn btn-link btn-block text-left" data-toggle="collapse" data-target="#form-node-attribute-__name__">
Nouveau attribut
</span>
</div>
<div class="collapse show" id="form-node-attribute-__name__" data-parent="#form-node-edit-attributes-collection">
<div class="card-body">
{{ form_row(form.attributes.vars.prototype.label) }}
{{ form_row(form.attributes.vars.prototype.value) }}
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger"></span>
</div>
{{ form_rest(form.attributes.vars.prototype) }}
</div>
</div>
</div>
</template>
<div class="d-none">
{{ form_rest(form) }}
</div>
{{ form_rest(form) }}

View file

@ -10,10 +10,6 @@
<form action="{{ path('admin_site_node_edit', {entity: entity.id}) }}" id="form-node-edit" method="POST">
{{ include('site/node_admin/_form.html.twig') }}
</form>
<div class="p1">
Nom de la route : <code>{{ entity.routeName }}</code>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>

View file

@ -113,10 +113,10 @@
{{ node.label }}
{% if node.url %}
<span class="ml-3 btn-group">
<a href="{{ absolute_url(path(node.routeName)) }}" target="_blank" class="btn btn-sm border border-secondary btn-light">
<span class="ml-3">
<span class="btn btn-sm border border-secondary btn-light">
{{ node.url }}
</a>
</span>
</span>
{% endif %}
</div>