diff --git a/assets/css/admin.scss b/assets/css/admin.scss
index b3b08ac..f1350a4 100644
--- a/assets/css/admin.scss
+++ b/assets/css/admin.scss
@@ -352,3 +352,74 @@ table.table-fixed, .table-fixed > table {
.login-image {
width: 50%;
}
+
+.tree {
+ position: relative;
+ background: white;
+ color: #212529;
+
+ span {
+ font-style: italic;
+ letter-spacing: .4px;
+ color: #a8a8a8;
+ }
+
+ .fa-folder-open, .fa-folder {
+ color: #007bff;
+ }
+
+ .fa-html5 {
+ color: #f21f10;
+ }
+
+ ul {
+ padding-left: 5px;
+ list-style: none;
+ margin: 0;
+ padding-bottom: 0;
+
+ li {
+ position: relative;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-left: 15px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+
+ &:before {
+ position: absolute;
+ top: 15px;
+ left: 0;
+ width: 10px;
+ height: 1px;
+ margin: auto;
+ content: '';
+ background-color: #666;
+ }
+
+ &:after {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 1px;
+ height: 100%;
+ content: '';
+ background-color: #666;
+ }
+
+ &:last-child:after {
+ height: 15px;
+ }
+ }
+
+ a {
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+ }
+}
diff --git a/assets/js/addons/modal.js b/assets/js/addons/modal.js
index 8cc6dbe..5b007b4 100644
--- a/assets/js/addons/modal.js
+++ b/assets/js/addons/modal.js
@@ -5,9 +5,20 @@ module.exports = function() {
e.preventDefault();
e.stopPropagation();
- let id = $(e.target).attr('data-modal');
- let modal = $(id);
+ let container = $('#modal-container');
- modal.modal('toggle');
+ if (!container.length) {
+ container = $('
');
+
+ $('body').append(container);
+ }
+
+ container.html('');
+
+ const url = $(e.target).attr('data-modal');
+
+ container.load(url, function() {
+ $(container).modal('show');
+ });
});
}
diff --git a/composer.json b/composer.json
index d98caa8..3b566ec 100644
--- a/composer.json
+++ b/composer.json
@@ -19,6 +19,7 @@
"scheb/2fa-google-authenticator": "^5.7",
"scheb/2fa-qr-code": "^5.7",
"sensio/framework-extra-bundle": "^6.1",
+ "stof/doctrine-extensions-bundle": "^1.6",
"symfony/apache-pack": "^1.0",
"symfony/asset": "5.2.*",
"symfony/console": "5.2.*",
diff --git a/config/bundles.php b/config/bundles.php
index 6abf867..b1227e8 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -16,4 +16,5 @@ return [
Knp\Bundle\PaginatorBundle\KnpPaginatorBundle::class => ['all' => true],
Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
+ Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
];
diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml
index c319176..c8c6e32 100644
--- a/config/packages/doctrine.yaml
+++ b/config/packages/doctrine.yaml
@@ -16,3 +16,9 @@ doctrine:
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
+ gedmo_tree:
+ type: annotation
+ prefix: Gedmo\Tree\Entity
+ dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Tree/Entity"
+ alias: GedmoTree # (optional) it will default to the name set for the mapping
+ is_bundle: false
diff --git a/config/packages/stof_doctrine_extensions.yaml b/config/packages/stof_doctrine_extensions.yaml
new file mode 100644
index 0000000..c83f3b1
--- /dev/null
+++ b/config/packages/stof_doctrine_extensions.yaml
@@ -0,0 +1,4 @@
+# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
+# See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/
+stof_doctrine_extensions:
+ default_locale: en_US
diff --git a/config/services.yaml b/config/services.yaml
index c7296dd..c8fba38 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -27,5 +27,12 @@ services:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
+ gedmo.listener.tree:
+ class: Gedmo\Tree\TreeListener
+ tags:
+ - { name: doctrine.event_subscriber, connection: default }
+ calls:
+ - [ setAnnotationReader, [ "@annotation_reader" ] ]
+
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
diff --git a/src/Controller/Blog/CategoryAdminController.php b/src/Controller/Blog/CategoryAdminController.php
index 6d0b134..9430cf9 100644
--- a/src/Controller/Blog/CategoryAdminController.php
+++ b/src/Controller/Blog/CategoryAdminController.php
@@ -111,7 +111,7 @@ class CategoryAdminController extends AdminController
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
$entityManager->delete($entity);
- $this->addFlash('success', 'Données supprimées.');
+ $this->addFlash('success', 'Données supprimée..');
}
return $this->redirectToRoute('admin_blog_category_index');
diff --git a/src/Controller/Blog/PostAdminController.php b/src/Controller/Blog/PostAdminController.php
index 4e24d94..bb3c3a1 100644
--- a/src/Controller/Blog/PostAdminController.php
+++ b/src/Controller/Blog/PostAdminController.php
@@ -8,7 +8,6 @@ use App\Factory\Blog\PostFactory as EntityFactory;
use App\Form\Blog\PostType as EntityType;
use App\Form\FileUploadHandler;
use App\Manager\EntityManager;
-use App\Repository\Blog\PostRepositoryQuery;
use App\Repository\Blog\PostRepositoryQuery as RepositoryQuery;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -112,7 +111,7 @@ class PostAdminController extends AdminController
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
$entityManager->delete($entity);
- $this->addFlash('success', 'Données supprimées.');
+ $this->addFlash('success', 'Données supprimée..');
}
return $this->redirectToRoute('admin_blog_post_index');
diff --git a/src/Controller/Site/MenuAdminController.php b/src/Controller/Site/MenuAdminController.php
new file mode 100644
index 0000000..5cd60f4
--- /dev/null
+++ b/src/Controller/Site/MenuAdminController.php
@@ -0,0 +1,82 @@
+create($navigation);
+ $form = $this->createForm(EntityType::class, $entity);
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $entityManager->create($entity);
+
+ $this->addFlash('success', 'Donnée enregistrée.');
+ } else {
+ $this->addFlash('warning', 'Le formulaire est invalide.');
+ }
+
+ return $this->redirectToRoute('admin_site_tree_navigation', [
+ 'navigation' => $navigation->getId(),
+ ]);
+ }
+
+ /**
+ * @Route("/edit/{entity}", name="admin_site_menu_edit", methods={"POST"})
+ */
+ public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
+ {
+ $form = $this->createForm(EntityType::class, $entity);
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $entityManager->update($entity);
+ $this->addFlash('success', 'Donnée enregistrée.');
+ } else {
+ $this->addFlash('warning', 'Le formulaire est invalide.');
+ }
+
+ return $this->redirectToRoute('admin_site_tree_navigation', [
+ 'navigation' => $entity->getNavigation()->getId(),
+ ]);
+ }
+
+ /**
+ * @Route("/delete/{entity}", name="admin_site_menu_delete", methods={"DELETE"})
+ */
+ public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
+ {
+ if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
+ $entityManager->delete($entity);
+
+ $this->addFlash('success', 'Données supprimée..');
+ }
+
+ return $this->redirectToRoute('admin_site_tree_navigation', [
+ 'navigation' => $entity->getNavigation()->getId(),
+ ]);
+ }
+
+ public function getSection(): string
+ {
+ return '';
+ }
+}
diff --git a/src/Controller/Site/NavigationAdminController.php b/src/Controller/Site/NavigationAdminController.php
new file mode 100644
index 0000000..dbd7e60
--- /dev/null
+++ b/src/Controller/Site/NavigationAdminController.php
@@ -0,0 +1,116 @@
+paginate($page);
+
+ return $this->render('site/navigation_admin/index.html.twig', [
+ 'pager' => $pager,
+ ]);
+ }
+
+ /**
+ * @Route("/new", name="admin_site_navigation_new")
+ */
+ public function new(EntityFactory $factory, EntityManager $entityManager, Request $request): Response
+ {
+ $entity = $factory->create();
+ $form = $this->createForm(EntityType::class, $entity);
+
+ if ($request->isMethod('POST')) {
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $entityManager->create($entity);
+ $this->addFlash('success', 'Donnée enregistrée.');
+
+ return $this->redirectToRoute('admin_site_navigation_edit', [
+ 'entity' => $entity->getId(),
+ ]);
+ }
+ $this->addFlash('warning', 'Le formulaire est invalide.');
+ }
+
+ return $this->render('site/navigation_admin/new.html.twig', [
+ 'form' => $form->createView(),
+ 'entity' => $entity,
+ ]);
+ }
+
+ /**
+ * @Route("/edit/{entity}", name="admin_site_navigation_edit")
+ */
+ public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
+ {
+ $form = $this->createForm(EntityType::class, $entity);
+
+ if ($request->isMethod('POST')) {
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $entityManager->update($entity);
+ $this->addFlash('success', 'Donnée enregistrée.');
+
+ return $this->redirectToRoute('admin_site_navigation_edit', [
+ 'entity' => $entity->getId(),
+ ]);
+ }
+
+ $this->addFlash('warning', 'Le formulaire est invalide.');
+ }
+
+ return $this->render('site/navigation_admin/edit.html.twig', [
+ 'form' => $form->createView(),
+ 'entity' => $entity,
+ ]);
+ }
+
+ /**
+ * @Route("/show/{entity}", name="admin_site_navigation_show")
+ */
+ public function show(Entity $entity): Response
+ {
+ return $this->render('site/navigation_admin/show.html.twig', [
+ 'entity' => $entity,
+ ]);
+ }
+
+ /**
+ * @Route("/delete/{entity}", name="admin_site_navigation_delete", methods={"DELETE"})
+ */
+ public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
+ {
+ if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
+ $entityManager->delete($entity);
+
+ $this->addFlash('success', 'Données supprimée..');
+ }
+
+ return $this->redirectToRoute('admin_site_navigation_index');
+ }
+
+ public function getSection(): string
+ {
+ return 'site_navigation';
+ }
+}
diff --git a/src/Controller/Site/NodeAdminController.php b/src/Controller/Site/NodeAdminController.php
new file mode 100644
index 0000000..e06f3cb
--- /dev/null
+++ b/src/Controller/Site/NodeAdminController.php
@@ -0,0 +1,178 @@
+create($node->getMenu());
+ $form = $this->createForm(EntityType::class, $entity);
+
+ if ($request->isMethod('POST')) {
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $position = $form->get('position')->getData();
+
+ $parent = 'above' === $position ? $node : $node->getParent();
+ $entity->setParent($parent);
+
+ if ('above' === $position) {
+ $nodeRepository->persistAsLastChild($entity, $node);
+ $entityManager->flush();
+ } else {
+ if ('after' === $position) {
+ $nodeRepository->persistAsNextSiblingOf($entity, $node);
+ } elseif ('before' === $position) {
+ $nodeRepository->persistAsPrevSiblingOf($entity, $node);
+ }
+
+ $entityManager->flush();
+ }
+
+ $this->addFlash('success', 'Donnée enregistrée.');
+ } else {
+ $this->addFlash('warning', 'Le formulaire est invalide.');
+ }
+
+ return $this->redirectToRoute('admin_site_tree_navigation', [
+ 'navigation' => $node->getMenu()->getNavigation()->getId(),
+ ]);
+ }
+
+ return $this->render('site/node_admin/new.html.twig', [
+ 'form' => $form->createView(),
+ 'node' => $node,
+ 'entity' => $entity,
+ ]);
+ }
+
+ /**
+ * @Route("/move/{entity}", name="admin_site_node_move")
+ */
+ public function move(
+ Entity $entity,
+ EntityManager $entityManager,
+ NodeRepository $nodeRepository,
+ Request $request
+ ): Response {
+ $form = $this->createForm(NodeMoveType::class, null, [
+ 'menu' => $entity->getMenu(),
+ ]);
+
+ if ($request->isMethod('POST')) {
+ $form->handleRequest($request);
+
+ if ($form->get('node')->getData()->getId() === $entity->getId()) {
+ $form->get('node')->addError(new FormError('Élement de référence invalide.'));
+ }
+
+ if ($form->isValid()) {
+ $position = $form->get('position')->getData();
+ $node = $form->get('node')->getData();
+
+ $parent = 'above' === $position ? $node : $node->getParent();
+ $entity->setParent($parent);
+
+ if ('above' === $position) {
+ $nodeRepository->persistAsLastChild($entity, $node);
+ $entityManager->flush();
+ } else {
+ if ('after' === $position) {
+ $nodeRepository->persistAsNextSiblingOf($entity, $node);
+ } elseif ('before' === $position) {
+ $nodeRepository->persistAsPrevSiblingOf($entity, $node);
+ }
+
+ $entityManager->flush();
+ }
+
+ $this->addFlash('success', 'Donnée enregistrée.');
+ } else {
+ $this->addFlash('warning', 'Le formulaire est invalide.');
+ }
+
+ return $this->redirectToRoute('admin_site_tree_navigation', [
+ 'navigation' => $entity->getMenu()->getNavigation()->getId(),
+ ]);
+ }
+
+ return $this->render('site/node_admin/move.html.twig', [
+ 'form' => $form->createView(),
+ 'entity' => $entity,
+ ]);
+ }
+
+ /**
+ * @Route("/toggle/visibility/{entity}", name="admin_site_node_toggle_visibility", methods={"POST"})
+ */
+ public function toggleVisibility(Entity $entity, EntityManager $entityManager, Request $request): Response
+ {
+ if ($this->isCsrfTokenValid('toggle_visibility'.$entity->getId(), $request->request->get('_token'))) {
+ $entity->setIsVisible(!$entity->getIsVisible());
+
+ $entityManager->update($entity);
+
+ $this->addFlash('success', 'Donnée enregistrée.');
+ }
+
+ return $this->redirectToRoute('admin_site_tree_navigation', [
+ 'navigation' => $entity->getMenu()->getNavigation()->getId(),
+ ]);
+ }
+
+ /**
+ * @Route("/delete/{entity}", name="admin_site_node_delete", methods={"DELETE"})
+ */
+ public function delete(
+ Entity $entity,
+ NodeRepository $nodeRepository,
+ EventDispatcherInterface $eventDispatcher,
+ Request $request
+ ): Response {
+ if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
+ $eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_DELETE_EVENT);
+ $nodeRepository->removeFromTree($entity);
+ $nodeRepository->reorder($entity->getMenu()->getRootNode());
+ $eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT);
+
+ $this->addFlash('success', 'Donnée supprimée.');
+ }
+
+ return $this->redirectToRoute('admin_site_tree_navigation', [
+ 'navigation' => $entity->getMenu()->getNavigation()->getId(),
+ ]);
+ }
+
+ public function getSection(): string
+ {
+ return '';
+ }
+}
diff --git a/src/Controller/Site/TreeAdminController.php b/src/Controller/Site/TreeAdminController.php
new file mode 100644
index 0000000..bf34123
--- /dev/null
+++ b/src/Controller/Site/TreeAdminController.php
@@ -0,0 +1,72 @@
+create()
+ ->orderBy('.label')
+ ->findOne()
+ ;
+
+ if (null === $navigation) {
+ $this->addFlash('warning', 'Vous devez ajouter une navigation.');
+
+ return $this->redirectToRoute('admin_site_navigation_new');
+ }
+
+ return $this->redirectToRoute('admin_site_tree_navigation', [
+ 'navigation' => $navigation->getId(),
+ ]);
+ }
+
+ /**
+ * @Route("/navigation/{navigation}", name="admin_site_tree_navigation")
+ */
+ public function navigation(
+ Navigation $navigation,
+ NavigationRepositoryQuery $navigationQuery,
+ MenuFactory $menuFactory
+ ): Response {
+ $navigations = $navigationQuery->create()
+ ->orderBy('.label')
+ ->find()
+ ;
+
+ $forms = [
+ 'menu' => $this->createForm(MenuType::class, $menuFactory->create())->createView(),
+ 'menus' => [],
+ ];
+
+ foreach ($navigation->getMenus() as $menu) {
+ $forms['menus'][$menu->getId()] = $this->createForm(MenuType::class, $menu)->createView();
+ }
+
+ return $this->render('site/tree_admin/navigation.html.twig', [
+ 'navigation' => $navigation,
+ 'navigations' => $navigations,
+ 'forms' => $forms,
+ ]);
+ }
+
+ public function getSection(): string
+ {
+ return 'site_tree';
+ }
+}
diff --git a/src/Controller/User/UserAdminController.php b/src/Controller/User/UserAdminController.php
index 3a72a80..0c0b0b2 100644
--- a/src/Controller/User/UserAdminController.php
+++ b/src/Controller/User/UserAdminController.php
@@ -144,7 +144,7 @@ class UserAdminController extends AdminController
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
$entityManager->delete($entity);
- $this->addFlash('success', 'Données supprimées.');
+ $this->addFlash('success', 'Données supprimée..');
}
return $this->redirectToRoute('admin_user_index');
diff --git a/src/Entity/Site/Menu.php b/src/Entity/Site/Menu.php
new file mode 100644
index 0000000..8ffe88c
--- /dev/null
+++ b/src/Entity/Site/Menu.php
@@ -0,0 +1,140 @@
+nodes = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getLabel(): ?string
+ {
+ return $this->label;
+ }
+
+ public function setLabel(string $label): self
+ {
+ $this->label = $label;
+
+ return $this;
+ }
+
+ public function getCode(): ?string
+ {
+ return $this->code;
+ }
+
+ public function setCode(string $code): self
+ {
+ $this->code = $code;
+
+ return $this;
+ }
+
+ public function getNavigation(): ?Navigation
+ {
+ return $this->navigation;
+ }
+
+ public function setNavigation(?Navigation $navigation): self
+ {
+ $this->navigation = $navigation;
+
+ return $this;
+ }
+
+ /**
+ * @return Collection|Node[]
+ */
+ public function getNodes(): Collection
+ {
+ return $this->nodes;
+ }
+
+ public function addNode(Node $node): self
+ {
+ if (!$this->nodes->contains($node)) {
+ $this->nodes[] = $node;
+ $node->setMenu($this);
+ }
+
+ return $this;
+ }
+
+ public function removeNode(Node $node): self
+ {
+ if ($this->nodes->removeElement($node)) {
+ // set the owning side to null (unless already changed)
+ if ($node->getMenu() === $this) {
+ $node->setMenu(null);
+ }
+ }
+
+ return $this;
+ }
+
+ public function getRootNode(): ?Node
+ {
+ return $this->rootNode;
+ }
+
+ public function setRootNode(?Node $rootNode): self
+ {
+ $this->rootNode = $rootNode;
+
+ return $this;
+ }
+}
diff --git a/src/Entity/Site/Navigation.php b/src/Entity/Site/Navigation.php
new file mode 100644
index 0000000..421dbf3
--- /dev/null
+++ b/src/Entity/Site/Navigation.php
@@ -0,0 +1,122 @@
+menus = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getLabel(): ?string
+ {
+ return $this->label;
+ }
+
+ public function setLabel(string $label): self
+ {
+ $this->label = $label;
+
+ return $this;
+ }
+
+ public function getCode(): ?string
+ {
+ return $this->code;
+ }
+
+ public function setCode(string $code): self
+ {
+ $this->code = $code;
+
+ return $this;
+ }
+
+ public function getDomain(): ?string
+ {
+ return $this->domain;
+ }
+
+ public function setDomain(string $domain): self
+ {
+ $this->domain = $domain;
+
+ return $this;
+ }
+
+ /**
+ * @return Collection|Menu[]
+ */
+ public function getMenus(): Collection
+ {
+ return $this->menus;
+ }
+
+ public function addMenu(Menu $menu): self
+ {
+ if (!$this->menus->contains($menu)) {
+ $this->menus[] = $menu;
+ $menu->setNavigation($this);
+ }
+
+ return $this;
+ }
+
+ public function removeMenu(Menu $menu): self
+ {
+ if ($this->menus->removeElement($menu)) {
+ // set the owning side to null (unless already changed)
+ if ($menu->getNavigation() === $this) {
+ $menu->setNavigation(null);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/src/Entity/Site/Node.php b/src/Entity/Site/Node.php
new file mode 100644
index 0000000..9dd7e05
--- /dev/null
+++ b/src/Entity/Site/Node.php
@@ -0,0 +1,263 @@
+children = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getMenu(): ?Menu
+ {
+ return $this->menu;
+ }
+
+ public function setMenu(?Menu $menu): self
+ {
+ $this->menu = $menu;
+
+ return $this;
+ }
+
+ public function getTreeLeft(): ?int
+ {
+ return $this->treeLeft;
+ }
+
+ public function setTreeLeft(int $treeLeft): self
+ {
+ $this->treeLeft = $treeLeft;
+
+ return $this;
+ }
+
+ public function getTreeLevel(): ?int
+ {
+ return $this->treeLevel;
+ }
+
+ public function setTreeLevel(int $treeLevel): self
+ {
+ $this->treeLevel = $treeLevel;
+
+ return $this;
+ }
+
+ public function getTreeRight(): ?int
+ {
+ return $this->treeRight;
+ }
+
+ public function setTreeRight(int $treeRight): self
+ {
+ $this->treeRight = $treeRight;
+
+ return $this;
+ }
+
+ public function getTreeRoot(): ?self
+ {
+ return $this->treeRoot;
+ }
+
+ public function setTreeRoot(?self $treeRoot): self
+ {
+ $this->treeRoot = $treeRoot;
+
+ return $this;
+ }
+
+ public function getParent(): ?self
+ {
+ return $this->parent;
+ }
+
+ public function setParent(?self $parent): self
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * @return Collection|Node[]
+ */
+ public function getChildren(): Collection
+ {
+ return $this->children;
+ }
+
+ public function addChild(Node $child): self
+ {
+ if (!$this->children->contains($child)) {
+ $this->children[] = $child;
+ $child->setParent($this);
+ }
+
+ return $this;
+ }
+
+ public function removeChild(Node $child): self
+ {
+ if ($this->children->removeElement($child)) {
+ // set the owning side to null (unless already changed)
+ if ($child->getParent() === $this) {
+ $child->setParent(null);
+ }
+ }
+
+ return $this;
+ }
+
+ public function getAllChildren(): ArrayCollection
+ {
+ $children = [];
+
+ $getChildren = function (Node $node) use (&$children, &$getChildren) {
+ foreach ($node->getChildren() as $nodeChildren) {
+ $children[] = $nodeChildren;
+
+ $getChildren($nodeChildren);
+ }
+ };
+
+ $getChildren($this);
+
+ usort($children, function ($a, $b) {
+ return $a->getTreeLeft() < $b->getTreeLeft() ? -1 : 1;
+ });
+
+ return new ArrayCollection($children);
+ }
+
+ public function getLabel(): ?string
+ {
+ return $this->label;
+ }
+
+ public function setLabel(?string $label): self
+ {
+ $this->label = $label;
+
+ return $this;
+ }
+
+ public function getUrl(): ?string
+ {
+ return $this->url;
+ }
+
+ public function setUrl(?string $url): self
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ public function getIsVisible(): ?bool
+ {
+ return $this->isVisible;
+ }
+
+ public function setIsVisible(bool $isVisible): self
+ {
+ $this->isVisible = $isVisible;
+
+ return $this;
+ }
+
+ public function getTreeLabel()
+ {
+ $prefix = str_repeat('-', ($this->getTreeLevel() - 1) * 5);
+
+ return trim($prefix.' '.$this->getLabel());
+ }
+}
diff --git a/src/EventSuscriber/AccountPasswordRequestEventSubscriber.php b/src/EventSuscriber/Account/PasswordRequestEventSubscriber.php
similarity index 92%
rename from src/EventSuscriber/AccountPasswordRequestEventSubscriber.php
rename to src/EventSuscriber/Account/PasswordRequestEventSubscriber.php
index 3d81039..a4e18b8 100644
--- a/src/EventSuscriber/AccountPasswordRequestEventSubscriber.php
+++ b/src/EventSuscriber/Account/PasswordRequestEventSubscriber.php
@@ -1,6 +1,6 @@
*/
-class AccountPasswordRequestEventSubscriber implements EventSubscriberInterface
+class PasswordRequestEventSubscriber implements EventSubscriberInterface
{
protected MailNotifier $notifier;
protected UrlGeneratorInterface $urlGenerator;
diff --git a/src/EventSuscriber/BlogPostEventSubscriber.php b/src/EventSuscriber/Blog/PostEventSubscriber.php
similarity index 89%
rename from src/EventSuscriber/BlogPostEventSubscriber.php
rename to src/EventSuscriber/Blog/PostEventSubscriber.php
index d38dbf3..2a4f634 100644
--- a/src/EventSuscriber/BlogPostEventSubscriber.php
+++ b/src/EventSuscriber/Blog/PostEventSubscriber.php
@@ -1,21 +1,21 @@
*/
-class BlogPostEventSubscriber extends EntityManagerEventSubscriber
+class PostEventSubscriber extends EntityManagerEventSubscriber
{
protected Filesystem $filesystem;
protected PostRepositoryQuery $query;
diff --git a/src/EventSuscriber/Site/MenuEventSubscriber.php b/src/EventSuscriber/Site/MenuEventSubscriber.php
new file mode 100644
index 0000000..855b25f
--- /dev/null
+++ b/src/EventSuscriber/Site/MenuEventSubscriber.php
@@ -0,0 +1,72 @@
+
+ */
+class MenuEventSubscriber extends EntityManagerEventSubscriber
+{
+ protected NodeFactory $nodeFactory;
+ protected EntityManager $entityManager;
+
+ public function __construct(
+ NodeFactory $nodeFactory,
+ NodeRepository $nodeRepository,
+ EntityManager $entityManager
+ ) {
+ $this->nodeFactory = $nodeFactory;
+ $this->nodeRepository = $nodeRepository;
+ $this->entityManager = $entityManager;
+ }
+
+ public function support(EntityInterface $entity)
+ {
+ return $entity instanceof Menu;
+ }
+
+ public function onCreate(EntityManagerEvent $event)
+ {
+ if (!$this->support($event->getEntity())) {
+ return;
+ }
+
+ $menu = $event->getEntity();
+
+ if (0 !== count($menu->getNodes())) {
+ return;
+ }
+
+ $rootNode = $this->nodeFactory->create($menu);
+ $childNode = $this->nodeFactory->create($menu);
+ $childNode
+ ->setParent($rootNode)
+ ->setLabel('Premier élément')
+ ;
+
+ $menu->setRootNode($rootNode);
+
+ $this->entityManager->create($rootNode);
+ $this->entityManager->create($childNode);
+
+ $this->entityManager->getEntityManager()->persist($menu);
+ $this->entityManager->flush();
+
+ $this->nodeRepository->persistAsFirstChild($childNode, $rootNode);
+ }
+
+ public function onUpdate(EntityManagerEvent $event)
+ {
+ return $this->onCreate($event);
+ }
+}
diff --git a/src/EventSuscriber/Site/NodeEventSubscriber.php b/src/EventSuscriber/Site/NodeEventSubscriber.php
new file mode 100644
index 0000000..0d313b6
--- /dev/null
+++ b/src/EventSuscriber/Site/NodeEventSubscriber.php
@@ -0,0 +1,61 @@
+
+ */
+class NodeEventSubscriber extends EntityManagerEventSubscriber
+{
+ protected NodeFactory $nodeFactory;
+ protected EntityManager $entityManager;
+
+ public function __construct(
+ NodeFactory $nodeFactory,
+ NodeRepository $nodeRepository,
+ EntityManager $entityManager
+ ) {
+ $this->nodeFactory = $nodeFactory;
+ $this->nodeRepository = $nodeRepository;
+ $this->entityManager = $entityManager;
+ }
+
+ public function support(EntityInterface $entity)
+ {
+ return $entity instanceof Node;
+ }
+
+ public function onDelete(EntityManagerEvent $event)
+ {
+ if (!$this->support($event->getEntity())) {
+ return;
+ }
+
+ $menu = $event->getEntity()->getMenu();
+ $rootNode = $menu->getRootNode();
+
+ if (0 !== count($rootNode->getChildren())) {
+ return;
+ }
+
+ $childNode = $this->nodeFactory->create($menu);
+ $childNode
+ ->setParent($rootNode)
+ ->setLabel('Premier élément')
+ ;
+
+ $this->entityManager->update($rootNode, false);
+ $this->entityManager->create($childNode, false);
+ $this->nodeRepository->persistAsFirstChild($childNode, $rootNode);
+ }
+}
diff --git a/src/Factory/Site/MenuFactory.php b/src/Factory/Site/MenuFactory.php
new file mode 100644
index 0000000..214470f
--- /dev/null
+++ b/src/Factory/Site/MenuFactory.php
@@ -0,0 +1,25 @@
+
+ */
+class MenuFactory
+{
+ public function create(?Navigation $navigation = null): Menu
+ {
+ $entity = new Menu();
+
+ if (null !== $navigation) {
+ $entity->setNavigation($navigation);
+ }
+
+ return $entity;
+ }
+}
diff --git a/src/Factory/Site/NavigationFactory.php b/src/Factory/Site/NavigationFactory.php
new file mode 100644
index 0000000..a5ad448
--- /dev/null
+++ b/src/Factory/Site/NavigationFactory.php
@@ -0,0 +1,18 @@
+
+ */
+class NavigationFactory
+{
+ public function create(): Navigation
+ {
+ return new Navigation();
+ }
+}
diff --git a/src/Factory/Site/NodeFactory.php b/src/Factory/Site/NodeFactory.php
new file mode 100644
index 0000000..adac084
--- /dev/null
+++ b/src/Factory/Site/NodeFactory.php
@@ -0,0 +1,25 @@
+
+ */
+class NodeFactory
+{
+ public function create(?Menu $menu = null): Node
+ {
+ $entity = new Node();
+
+ if (null !== $menu) {
+ $entity->setMenu($menu);
+ }
+
+ return $entity;
+ }
+}
diff --git a/src/Form/Site/MenuType.php b/src/Form/Site/MenuType.php
new file mode 100644
index 0000000..4f02d55
--- /dev/null
+++ b/src/Form/Site/MenuType.php
@@ -0,0 +1,51 @@
+add(
+ 'label',
+ TextType::class,
+ [
+ 'label' => 'Libellé',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'code',
+ TextType::class,
+ [
+ 'label' => 'Code',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => Menu::class,
+ ]);
+ }
+}
diff --git a/src/Form/Site/NavigationType.php b/src/Form/Site/NavigationType.php
new file mode 100644
index 0000000..af70122
--- /dev/null
+++ b/src/Form/Site/NavigationType.php
@@ -0,0 +1,65 @@
+add(
+ 'label',
+ TextType::class,
+ [
+ 'label' => 'Libellé',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'code',
+ TextType::class,
+ [
+ 'label' => 'Code',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'domain',
+ TextType::class,
+ [
+ 'label' => 'Nom de domaine',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => Navigation::class,
+ ]);
+ }
+}
diff --git a/src/Form/Site/NodeMoveType.php b/src/Form/Site/NodeMoveType.php
new file mode 100644
index 0000000..c5a2cc4
--- /dev/null
+++ b/src/Form/Site/NodeMoveType.php
@@ -0,0 +1,62 @@
+add(
+ 'position',
+ ChoiceType::class,
+ [
+ 'label' => 'Position',
+ 'required' => true,
+ 'choices' => [
+ 'Après' => 'after',
+ 'Avant' => 'before',
+ 'En dessous' => 'above',
+ ],
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'node',
+ EntityType::class,
+ [
+ 'label' => 'Élement de référence',
+ 'class' => Node::class,
+ 'choices' => call_user_func(function () use ($options) {
+ return $options['menu']->getRootNode()->getAllChildren();
+ }),
+ 'choice_label' => 'treeLabel',
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => null,
+ 'menu' => null,
+ ]);
+ }
+}
diff --git a/src/Form/Site/NodeType.php b/src/Form/Site/NodeType.php
new file mode 100644
index 0000000..81480a6
--- /dev/null
+++ b/src/Form/Site/NodeType.php
@@ -0,0 +1,72 @@
+add(
+ 'label',
+ TextType::class,
+ [
+ 'label' => 'Libellé',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'url',
+ TextType::class,
+ [
+ 'label' => 'URL',
+ 'required' => false,
+ 'help' => 'Laisser vide pour une génération automatique',
+ 'attr' => [
+ ],
+ 'constraints' => [
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'position',
+ ChoiceType::class,
+ [
+ 'label' => 'Position',
+ 'required' => true,
+ 'mapped' => false,
+ 'choices' => [
+ 'Après' => 'after',
+ 'Avant' => 'before',
+ 'En dessous' => 'above',
+ ],
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => Node::class,
+ ]);
+ }
+}
diff --git a/src/Manager/EntityManager.php b/src/Manager/EntityManager.php
index cad899f..7c67882 100644
--- a/src/Manager/EntityManager.php
+++ b/src/Manager/EntityManager.php
@@ -25,36 +25,48 @@ class EntityManager
$this->entityManager = $entityManager;
}
- public function create(EntityInterface $entity): self
+ public function create(EntityInterface $entity, bool $dispatchEvent = true): self
{
- $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_CREATE_EVENT);
+ if ($dispatchEvent) {
+ $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_CREATE_EVENT);
+ }
$this->persist($entity);
- $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::CREATE_EVENT);
+ if ($dispatchEvent) {
+ $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::CREATE_EVENT);
+ }
return $this;
}
- public function update(EntityInterface $entity): self
+ public function update(EntityInterface $entity, bool $dispatchEvent = true): self
{
- $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_UPDATE_EVENT);
+ if ($dispatchEvent) {
+ $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_UPDATE_EVENT);
+ }
$this->persist($entity);
- $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::UPDATE_EVENT);
+ if ($dispatchEvent) {
+ $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::UPDATE_EVENT);
+ }
return $this;
}
- public function delete(EntityInterface $entity): self
+ public function delete(EntityInterface $entity, bool $dispatchEvent = true): self
{
- $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_DELETE_EVENT);
+ if ($dispatchEvent) {
+ $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_DELETE_EVENT);
+ }
$this->entityManager->remove($entity);
$this->flush();
- $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT);
+ if ($dispatchEvent) {
+ $this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT);
+ }
return $this;
}
@@ -73,6 +85,11 @@ class EntityManager
return $this;
}
+ public function getEntityManager(): EntityManagerInterface
+ {
+ return $this->entityManager;
+ }
+
protected function persist(EntityInterface $entity)
{
$this->entityManager->persist($entity);
diff --git a/src/Repository/Blog/CategoryRepository.php b/src/Repository/Blog/CategoryRepository.php
index b69b076..2703c9a 100644
--- a/src/Repository/Blog/CategoryRepository.php
+++ b/src/Repository/Blog/CategoryRepository.php
@@ -6,12 +6,6 @@ use App\Entity\Blog\Category;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
-/**
- * @method Category|null find($id, $lockMode = null, $lockVersion = null)
- * @method Category|null findOneBy(array $criteria, array $orderBy = null)
- * @method Category[] findAll()
- * @method Category[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
- */
class CategoryRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
diff --git a/src/Repository/Blog/PostRepository.php b/src/Repository/Blog/PostRepository.php
index 1882005..d6a3032 100644
--- a/src/Repository/Blog/PostRepository.php
+++ b/src/Repository/Blog/PostRepository.php
@@ -6,12 +6,6 @@ use App\Entity\Blog\Post;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
-/**
- * @method Post|null find($id, $lockMode = null, $lockVersion = null)
- * @method Post|null findOneBy(array $criteria, array $orderBy = null)
- * @method Post[] findAll()
- * @method Post[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
- */
class PostRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
diff --git a/src/Repository/RepositoryQuery.php b/src/Repository/RepositoryQuery.php
index 6324c32..2dca428 100644
--- a/src/Repository/RepositoryQuery.php
+++ b/src/Repository/RepositoryQuery.php
@@ -73,7 +73,10 @@ abstract class RepositoryQuery
public function findOne()
{
- return $this->query->getQuery()->getOneOrNullResult();
+ return $this->query->getQuery()
+ ->setMaxResults(1)
+ ->getOneOrNullResult()
+ ;
}
public function find()
@@ -85,4 +88,9 @@ abstract class RepositoryQuery
{
return $this->paginator->paginate($this->query->getQuery(), $page, $limit);
}
+
+ public function getRepository(): ServiceEntityRepository
+ {
+ return $this->repository;
+ }
}
diff --git a/src/Repository/Site/MenuRepository.php b/src/Repository/Site/MenuRepository.php
new file mode 100644
index 0000000..514e2a8
--- /dev/null
+++ b/src/Repository/Site/MenuRepository.php
@@ -0,0 +1,15 @@
+
+ */
+class MenuRepositoryQuery extends RepositoryQuery
+{
+ public function __construct(MenuRepository $repository, PaginatorInterface $paginator)
+ {
+ parent::__construct($repository, 'm', $paginator);
+ }
+}
diff --git a/src/Repository/Site/NavigationRepository.php b/src/Repository/Site/NavigationRepository.php
new file mode 100644
index 0000000..9900e60
--- /dev/null
+++ b/src/Repository/Site/NavigationRepository.php
@@ -0,0 +1,15 @@
+
+ */
+class NavigationRepositoryQuery extends RepositoryQuery
+{
+ public function __construct(NavigationRepository $repository, PaginatorInterface $paginator)
+ {
+ parent::__construct($repository, 'n', $paginator);
+ }
+}
diff --git a/src/Repository/Site/NodeRepository.php b/src/Repository/Site/NodeRepository.php
new file mode 100644
index 0000000..a6e9901
--- /dev/null
+++ b/src/Repository/Site/NodeRepository.php
@@ -0,0 +1,15 @@
+getClassMetadata(Node::class));
+ }
+}
diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php
index 21b3759..b7bead5 100644
--- a/src/Repository/UserRepository.php
+++ b/src/Repository/UserRepository.php
@@ -9,12 +9,6 @@ use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
-/**
- * @method User|null find($id, $lockMode = null, $lockVersion = null)
- * @method User|null findOneBy(array $criteria, array $orderBy = null)
- * @method User[] findAll()
- * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
- */
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
@@ -22,9 +16,6 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
parent::__construct($registry, User::class);
}
- /**
- * Used to upgrade (rehash) the user's password automatically over time.
- */
public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
{
if (!$user instanceof User) {
diff --git a/symfony.lock b/symfony.lock
index 6c634c2..5db99ec 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -5,6 +5,9 @@
"beberlei/assert": {
"version": "v3.3.0"
},
+ "behat/transliterator": {
+ "version": "v1.3.0"
+ },
"bjeavons/zxcvbn-php": {
"version": "1.2.0"
},
@@ -102,6 +105,9 @@
"friendsofphp/proxy-manager-lts": {
"version": "v1.0.3"
},
+ "gedmo/doctrine-extensions": {
+ "version": "v3.0.3"
+ },
"khanamiryan/qrcode-detector-decoder": {
"version": "1.0.4"
},
@@ -190,6 +196,18 @@
"spomky-labs/otphp": {
"version": "v10.0.1"
},
+ "stof/doctrine-extensions-bundle": {
+ "version": "1.2",
+ "recipe": {
+ "repo": "github.com/symfony/recipes-contrib",
+ "branch": "master",
+ "version": "1.2",
+ "ref": "6c1ceb662f8997085f739cd089bfbef67f245983"
+ },
+ "files": [
+ "config/packages/stof_doctrine_extensions.yaml"
+ ]
+ },
"swiftmailer/swiftmailer": {
"version": "v6.2.7"
},
diff --git a/templates/admin/layout.html.twig b/templates/admin/layout.html.twig
index e83d1e9..f169f87 100644
--- a/templates/admin/layout.html.twig
+++ b/templates/admin/layout.html.twig
@@ -65,7 +65,7 @@
-
Arborescence
@@ -80,14 +80,6 @@
- -
-
-
-
- Pages
-
-
-
-
diff --git a/templates/site/navigation_admin/_form.html.twig b/templates/site/navigation_admin/_form.html.twig
new file mode 100644
index 0000000..dcb7b82
--- /dev/null
+++ b/templates/site/navigation_admin/_form.html.twig
@@ -0,0 +1,12 @@
+
+
+
+ {% for item in ['label', 'code', 'domain'] %}
+
+ {{ form_row(form[item]) }}
+
+ {% endfor %}
+
+
+
+
diff --git a/templates/site/navigation_admin/edit.html.twig b/templates/site/navigation_admin/edit.html.twig
new file mode 100644
index 0000000..4339b20
--- /dev/null
+++ b/templates/site/navigation_admin/edit.html.twig
@@ -0,0 +1,57 @@
+{% extends 'admin/layout.html.twig' %}
+
+{% block body %}
+
+
+
+
{{ entity.label }}
+
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/templates/site/navigation_admin/index.html.twig b/templates/site/navigation_admin/index.html.twig
new file mode 100644
index 0000000..bd09d62
--- /dev/null
+++ b/templates/site/navigation_admin/index.html.twig
@@ -0,0 +1,77 @@
+{% extends 'admin/layout.html.twig' %}
+
+{% block body %}
+
+
+
+
+
+ Libellé |
+ Domaine |
+ Actions |
+
+
+
+ {% for item in pager %}
+ {% set edit = path('admin_site_navigation_edit', {entity: item.id}) %}
+ {% set show = path('admin_site_navigation_show', {entity: item.id}) %}
+
+
+
+
+ {{ item.label }}
+
+
+ {{ item.code }}
+ |
+
+
+ {{ item.domain }}
+
+ |
+
+
+
+
+
+
+
+ |
+
+ {% else %}
+
+
+
+
+
+
+ Aucun résultat
+
+ |
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/templates/site/navigation_admin/new.html.twig b/templates/site/navigation_admin/new.html.twig
new file mode 100644
index 0000000..3c87915
--- /dev/null
+++ b/templates/site/navigation_admin/new.html.twig
@@ -0,0 +1,39 @@
+{% extends 'admin/layout.html.twig' %}
+
+{% block body %}
+
+
+
+
Nouvelle navigation
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/templates/site/navigation_admin/show.html.twig b/templates/site/navigation_admin/show.html.twig
new file mode 100644
index 0000000..7013516
--- /dev/null
+++ b/templates/site/navigation_admin/show.html.twig
@@ -0,0 +1,48 @@
+{% extends 'admin/layout.html.twig' %}
+
+{% block body %}
+
+
+
+
{{ entity.label }}
+ :
+
+
+
+
+
+
+
+
+ -
+ Libellé
+
+ {{ entity.label }}
+
+ -
+ Code
+
+ {{ entity.code }}
+
+ -
+ Nom de domaine
+
+ {{ entity.domain }}
+
+
+
+
+{% endblock %}
diff --git a/templates/site/node_admin/move.html.twig b/templates/site/node_admin/move.html.twig
new file mode 100644
index 0000000..6750cc4
--- /dev/null
+++ b/templates/site/node_admin/move.html.twig
@@ -0,0 +1,19 @@
+
diff --git a/templates/site/node_admin/new.html.twig b/templates/site/node_admin/new.html.twig
new file mode 100644
index 0000000..bb86b73
--- /dev/null
+++ b/templates/site/node_admin/new.html.twig
@@ -0,0 +1,19 @@
+
diff --git a/templates/site/tree_admin/navigation.html.twig b/templates/site/tree_admin/navigation.html.twig
new file mode 100644
index 0000000..7db50bd
--- /dev/null
+++ b/templates/site/tree_admin/navigation.html.twig
@@ -0,0 +1,177 @@
+{% extends 'admin/layout.html.twig' %}
+
+{% block body %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% for menu in navigation.menus %}
+
+
+
+
+
+
+
+ {{ menu.code }}
+
+
+
+
+
+
+
+
+
+
+
+ {% set rootNode = menu.rootNode %}
+
+ {% if rootNode %}
+ {% for node in rootNode.allChildren %}
+ {% set move = path('admin_site_node_move', {entity: node.id}) %}
+ {% set new = path('admin_site_node_new', {node: node.id}) %}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ node.label }}
+
+
+
+
+
+
+ {% endfor %}
+ {% endif %}
+
+
+ {% else %}
+
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+
+
+ {% for menuId, form in forms.menus %}
+
+
+
+ {% endfor %}
+{% endblock %}