backports murph-skeleton

This commit is contained in:
Simon Vieille 2021-05-16 19:55:23 +02:00
parent 138115ed1a
commit 5e669869a5
12 changed files with 351 additions and 70 deletions

View file

@ -41,6 +41,7 @@ class NodeAdminController extends AdminController
$entity = $factory->create($node->getMenu());
$form = $this->createForm(EntityType::class, $entity, [
'pages' => $pageLocator->getPages(),
'navigation' => $node->getMenu()->getNavigation(),
]);
if ($request->isMethod('POST')) {
@ -108,6 +109,7 @@ class NodeAdminController extends AdminController
): Response {
$form = $this->createForm(EntityType::class, $entity, [
'pages' => $pageLocator->getPages(),
'navigation' => $entity->getMenu()->getNavigation(),
]);
if ($request->isMethod('POST')) {
@ -269,15 +271,22 @@ class NodeAdminController extends AdminController
$page = $pageFactory->create($pageType, $entity->getLabel());
$page->setTemplate($pageConfiguration->getTemplates()[0]['file']);
$entity->setPage($page);
$entity
->setPage($page)
->setAliasNode(null);
} elseif ('existing' === $pageAction) {
if ($pageEntity) {
$entity->setPage($pageEntity);
} else {
$this->addFlash('info', 'Aucun changement de page effectué.');
}
} elseif ('none' === $pageAction) {
$entity->setAliasNode(null);
} elseif ('alias' === $pageAction) {
$entity->setPage(null);
} elseif ('none' === $pageAction) {
$entity
->setPage(null)
->setAliasNode(null);
}
}
}

View file

@ -123,9 +123,20 @@ class Node implements EntityInterface
*/
private $sitemapParameters = [];
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="aliasNodes")
*/
private $aliasNode;
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="aliasNode")
*/
private $aliasNodes;
public function __construct()
{
$this->children = new ArrayCollection();
$this->aliasNodes = new ArrayCollection();
}
public function getId(): ?int
@ -324,6 +335,10 @@ class Node implements EntityInterface
public function getPage(): ?Page
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getPage();
}
return $this->page;
}
@ -336,11 +351,19 @@ class Node implements EntityInterface
public function getRouteName(): string
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getRouteName();
}
return $this->getMenu()->getRouteName().'_'.($this->getCode() ? $this->getCode() : $this->getId());
}
public function getCode(): ?string
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getCode();
}
return $this->code;
}
@ -353,6 +376,10 @@ class Node implements EntityInterface
public function getParameters(): ?array
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getParameters();
}
if (!is_array($this->parameters)) {
$this->parameters = [];
}
@ -385,6 +412,10 @@ class Node implements EntityInterface
public function getController(): ?string
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getController();
}
return $this->controller;
}
@ -397,6 +428,10 @@ class Node implements EntityInterface
public function getSitemapParameters(): ?array
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getSitemapParameters();
}
if (!is_array($this->sitemapParameters)) {
$this->sitemapParameters = [
'isVisible' => false,
@ -414,4 +449,46 @@ class Node implements EntityInterface
return $this;
}
public function getAliasNode(): ?self
{
return $this->aliasNode;
}
public function setAliasNode(?self $aliasNode): self
{
$this->aliasNode = $aliasNode;
return $this;
}
/**
* @return Collection|self[]
*/
public function getAliasNodes(): Collection
{
return $this->aliasNodes;
}
public function addAliasNode(self $aliasNode): self
{
if (!$this->aliasNodes->contains($aliasNode)) {
$this->aliasNodes[] = $aliasNode;
$aliasNode->setAliasNode($this);
}
return $this;
}
public function removeAliasNode(self $aliasNode): self
{
if ($this->aliasNodes->removeElement($aliasNode)) {
// set the owning side to null (unless already changed)
if ($aliasNode->getAliasNode() === $this) {
$aliasNode->setAliasNode(null);
}
}
return $this;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
/**
* @ORM\Entity
*/
class CollectionBlock extends Block
{
public function getValue()
{
return json_decode(parent::getValue(), true);
}
public function setValue($value): self
{
return parent::setValue(json_encode($value));
}
}

View file

@ -62,66 +62,82 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber
if ($node->getDisableUrl()) {
$node->setUrl(null);
return;
}
if ($node->getUrl()) {
$generatedUrl = $node->getUrl();
} else {
$path = [];
$parent = $node->getParent();
if ($node->getUrl()) {
$generatedUrl = $node->getUrl();
} else {
$path = [];
$parent = $node->getParent();
if ($parent && $parent->getUrl()) {
$pPath = trim($parent->getUrl(), '/');
if ($parent && $parent->getUrl()) {
$pPath = trim($parent->getUrl(), '/');
if ($pPath) {
$path[] = $pPath;
if ($pPath) {
$path[] = $pPath;
}
}
$path[] = $this->slugify->slugify($node->getLabel());
$generatedUrl = '/'.implode('/', $path);
}
if ('/' !== $generatedUrl) {
$generatedUrl = rtrim($generatedUrl, '/');
}
$parameters = $node->getParameters();
$routeParameters = [];
foreach ($parameters as $key => $parameter) {
$parameter['name'] = $this->routeParameterSlugify->slugify($parameter['name']);
$routeParameter = sprintf('{%s}', $parameter['name']);
$regex = '/'.preg_quote($routeParameter).'/';
$routeParameters[] = $parameter['name'];
if (!preg_match($regex, $generatedUrl)) {
$generatedUrl .= '/'.$routeParameter;
}
$parameters[$key] = $parameter;
}
preg_match_all('/\{(.*)\}/isU', $generatedUrl, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
if (!in_array($match[1], $routeParameters)) {
$parameters[] = [
'name' => $this->routeParameterSlugify->slugify($match[1]),
'defaultValue' => null,
'requirement' => null,
];
}
}
$path[] = $this->slugify->slugify($node->getLabel());
$generatedUrl = '/'.implode('/', $path);
}
if ('/' !== $generatedUrl) {
$generatedUrl = rtrim($generatedUrl, '/');
}
$parameters = $node->getParameters();
$routeParameters = [];
foreach ($parameters as $key => $parameter) {
$parameter['name'] = $this->routeParameterSlugify->slugify($parameter['name']);
$routeParameter = sprintf('{%s}', $parameter['name']);
$regex = '/'.preg_quote($routeParameter).'/';
$routeParameters[] = $parameter['name'];
if (!preg_match($regex, $generatedUrl)) {
$generatedUrl .= '/'.$routeParameter;
if (!u($generatedUrl)->startsWith('https://') && !u($generatedUrl)->startsWith('http://')) {
$generatedUrl = str_replace('//', '/', $generatedUrl);
}
$parameters[$key] = $parameter;
}
$node->setParameters($parameters);
preg_match_all('/\{(.*)\}/isU', $generatedUrl, $matches, PREG_SET_ORDER);
$urlExists = $this->nodeRepository->urlExists($generatedUrl, $node);
foreach ($matches as $match) {
if (!in_array($match[1], $routeParameters)) {
$parameters[] = [
'name' => $this->routeParameterSlugify->slugify($match[1]),
'defaultValue' => null,
'requirement' => null,
];
if ($urlExists) {
$number = 1;
while ($this->nodeRepository->urlExists($generatedUrl.'-'.$number, $node)) {
++$number;
}
$generatedUrl = $generatedUrl.'-'.$number;
}
}
if (!u($generatedUrl)->startsWith('https://') && !u($generatedUrl)->startsWith('http://')) {
$generatedUrl = str_replace('//', '/', $generatedUrl);
}
if (!u($generatedUrl)->startsWith('/')) {
$generatedUrl = '/'.$generatedUrl;
}
$node->setParameters($parameters);
$node->setUrl($generatedUrl);
}
$attributes = $node->getAttributes();
$realAttributes = [];
@ -131,24 +147,6 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber
}
$node->setAttributes($realAttributes);
$urlExists = $this->nodeRepository->urlExists($generatedUrl, $node);
if ($urlExists) {
$number = 1;
while ($this->nodeRepository->urlExists($generatedUrl.'-'.$number, $node)) {
++$number;
}
$generatedUrl = $generatedUrl.'-'.$number;
}
if (!u($generatedUrl)->startsWith('/')) {
$generatedUrl = '/'.$generatedUrl;
}
$node->setUrl($generatedUrl);
}
public function onDelete(EntityManagerEvent $event)

View file

@ -90,6 +90,7 @@ class NodeType extends AbstractType
$actions = [
'New page' => 'new',
'Use an existing page' => 'existing',
'Alias element' => 'alias',
'No page' => 'none',
];
@ -153,6 +154,38 @@ class NodeType extends AbstractType
]
);
$builder->add(
'aliasNode',
EntityType::class,
[
'label' => false,
'required' => true,
'mapped' => true,
'class' => Node::class,
'choice_label' => 'label',
'choices' => call_user_func(function() use ($options, $builder) {
$nodes = [];
foreach ($options['navigation']->getMenus() as $menu) {
$nodes = array_merge(
$nodes,
$menu->getRootNode()->getAllChildren()->toArray()
);
}
foreach ($nodes as $k => $value) {
if ($value->getId() === $builder->getData()->getId()) {
unset($nodes[$k]);
}
}
return $nodes;
}),
'constraints' => [
],
]
);
$builder->add(
'parameters',
CollectionType::class,
@ -213,6 +246,7 @@ class NodeType extends AbstractType
$resolver->setDefaults([
'data_class' => Node::class,
'pages' => [],
'navigation' => null,
]);
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Core\Form\Site\Page;
use App\Core\Entity\Site\Page\Block;
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\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\CallbackTransformer;
class CollectionBlockType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'value',
CollectionType::class,
array_merge([
'required' => false,
'label' => false,
], $options['options']),
);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace($view->vars, [
'collection_name' => $options['collection_name'],
'label_add' => $options['label_add'],
'label_delete' => $options['label_delete'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Block::class,
'collection_name' => '',
'label_add' => 'Add',
'label_delete' => 'Delete',
'options' => [],
]);
}
}

View file

@ -71,6 +71,8 @@
"New menu": "Nouveau menu"
"New": "Nouveau"
"No action": "Aucune action"
"Alias element": "Élement alias"
"This element is an alias so the next configuration is ignored.": "Cet élement est un alias donc cette configuration sera ignorée"
"Route name: <code>%routeName%</code>": "Nom de la route : <code>%routeName%</code>"
"Content": "Contenu"
"Routing": "Routage"

View file

@ -36,3 +36,42 @@
</div>
</div>
{% endblock %}
{% block collection_block_widget %}
<div data-collection="collection-{{ collection_name }}">
{% for item in form.value %}
<div data-collection-item="{{ loop.index }}">
{% for child in item %}
{{ form_row(child) }}
{% endfor %}
</div>
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger">
<span data-collection-delete="{{ loop.index }}">
{{ label_delete|trans }}
</span>
</span>
</div>
{% endfor %}
</div>
<div data-collection-add="collection-{{ collection_name }}" class="collection-add">
<span class="btn btn-sm btn-primary" data-collection-add="collection-{{ collection_name }}">
{{ label_add|trans }}
</span>
</div>
<template type="text/template" id="collection-{{ collection_name }}">
<div data-collection-item="__name__">
{{ form_rest(form.value.vars.prototype) }}
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger">
{{ label_delete|trans }}
</span>
</div>
</div>
</template>
{% endblock %}

View file

@ -96,14 +96,39 @@
</div>
<div id="form-node-page-action-none" class="collapse" data-parent="#node-page-action">
<div class="card-body">
Aucune action
{{ form_row(form.aliasNode) }}
</div>
</div>
</div>
<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-alias">
{{ action.vars.label|trans }}
</label>
<div class="d-none">
{{ form_row(action, options) }}
</div>
</h2>
</div>
<div id="form-node-page-action-alias" class="collapse" data-parent="#node-page-action">
<div class="card-body">
{{ 'No action'|trans }}
</div>
</div>
</div>
{% if entity.id %}
<div class="card">
{% set action = form.pageAction[3] %}
{% set action = form.pageAction[4] %}
{% set options = {'attr': {'checked': 'checked'}} %}
<div class="card-header p-0">
@ -138,6 +163,12 @@
</div>
{% endif %}
{% if entity.aliasNode %}
<div class="alert alert-warning">
{{ 'This element is an alias so the next configuration is ignored.'|trans }}
</div>
{% endif %}
{{ form_row(form.url) }}
<div class="pb-1">
@ -215,6 +246,12 @@
</div>
</div>
<div class="tab-pane {% if tab == 'sitemap' %}show active{% endif %}" id="form-node-edit-sitemap">
{% if entity.aliasNode %}
<div class="alert alert-warning">
{{ 'This element is an alias so the next configuration is ignored.'|trans }}
</div>
{% endif %}
{{ form_row(form.sitemapParameters) }}
</div>
</div>

View file

@ -79,7 +79,11 @@
{{ node.label }}
<span class="ml-3 btn-group d-none d-md-inline-block">
{% if node.hasExternalUrl %}
{% if node.aliasNode %}
<span title="{{ node.aliasNode.label }}" data-toggle="tooltip" data-placement="top" class="btn btn-sm border border-secondary btn-light">
<span class="fa fa-file-import"></span>
</span>
{% elseif node.hasExternalUrl %}
<a href="{{ safe_node_url(node) }}" class="btn btn-sm border border-secondary btn-light">
<span class="fa fa-sign-out-alt text-muted"></span>
</a>

View file

@ -57,6 +57,10 @@ class SiteRouteLoader extends Loader
continue;
}
if (null !== $node->getAliasNode()) {
continue;
}
$requirements = [];
$defaults = [

View file

@ -46,6 +46,10 @@ class SitemapBuilder
continue;
}
if ($node->getAliasNode()) {
continue;
}
$nodeItems = [];
foreach ($this->getNodeUrls($node) as $url) {