diff --git a/.gitignore b/.gitignore
index 281da24..dc07d31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,6 @@
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###
+
+/public/uploads/
+!/public/uploads/.gitkeep
diff --git a/assets/css/admin.scss b/assets/css/admin.scss
index 9186bef..42b932e 100644
--- a/assets/css/admin.scss
+++ b/assets/css/admin.scss
@@ -114,7 +114,7 @@ tr.table-primary-light {
.fa {
font-size: 1.2rem;
margin-right: 5px;
- min-width: 20px;
+ min-width: 30px;
color: #fff;
}
@@ -352,3 +352,78 @@ 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;
+ }
+ }
+ }
+}
+
+fieldset.form-group {
+ margin-bottom: 0;
+}
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..2afb83c 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -16,4 +16,6 @@ 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],
+ App\Bundle\AppBundle::class => ['all' => true],
];
diff --git a/config/packages/app.yaml b/config/packages/app.yaml
new file mode 100644
index 0000000..52aad8e
--- /dev/null
+++ b/config/packages/app.yaml
@@ -0,0 +1,8 @@
+app:
+ site:
+ pages:
+ App\Site\Page\SimplePage:
+ name: 'Page simple'
+ templates:
+ - {name: "Template 1", file: "site/page/simple/page.html.twig"}
+ - {name: "Template 2", file: "site/page/simple/page2.html.twig"}
diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml
index c319176..0019ee1 100644
--- a/config/packages/doctrine.yaml
+++ b/config/packages/doctrine.yaml
@@ -10,9 +10,20 @@ doctrine:
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
- App:
+ App\Entity:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
- alias: App
+ alias: App\Entity
+ App\Site\Page:
+ is_bundle: false
+ type: annotation
+ dir: '%kernel.project_dir%/src/Site/Page'
+ prefix: 'App\Site\Page'
+ 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/routes.yaml b/config/routes.yaml
index 3a94441..05e7257 100644
--- a/config/routes.yaml
+++ b/config/routes.yaml
@@ -2,11 +2,10 @@
# path: /
# controller: App\Controller\DefaultController::index
-#https://symfony.com/doc/current/routing/custom_route_loader.html
-#admin_routes:
-# resource: 'admin_route_loader::loadRoutes'
-# type: service
-#
+site_route:
+ resource: 'site.route_loader::loadRoutes'
+ type: extra
+
2fa_login:
path: /2fa
defaults:
diff --git a/config/services.yaml b/config/services.yaml
index c7296dd..343eac5 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -27,5 +27,16 @@ services:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
+ site.route_loader:
+ class: App\Router\SiteRouteLoader
+ tags: [routing.loader]
+
+ 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/public/uploads/post/2021/20210317-035213ovh_feu1.jpg b/public/uploads/post/2021/20210317-035213ovh_feu1.jpg
deleted file mode 100644
index 0e4e0e8..0000000
Binary files a/public/uploads/post/2021/20210317-035213ovh_feu1.jpg and /dev/null differ
diff --git a/src/Bundle/AppBundle.php b/src/Bundle/AppBundle.php
new file mode 100644
index 0000000..06b408c
--- /dev/null
+++ b/src/Bundle/AppBundle.php
@@ -0,0 +1,23 @@
+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 93ffdbf..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;
@@ -97,7 +96,7 @@ class PostAdminController extends AdminController
/**
* @Route("/show/{entity}", name="admin_blog_post_show")
*/
- public function show(Entity $entity, PostRepositoryQuery $postQuery): Response
+ public function show(Entity $entity): Response
{
return $this->render('blog/post_admin/show.html.twig', [
'entity' => $entity,
@@ -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..3ec69e7
--- /dev/null
+++ b/src/Controller/Site/NodeAdminController.php
@@ -0,0 +1,263 @@
+create($node->getMenu());
+ $form = $this->createForm(EntityType::class, $entity, [
+ 'pages' => $pageLocator->getPages(),
+ ]);
+
+ 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);
+ } else {
+ if ('after' === $position) {
+ $nodeRepository->persistAsNextSiblingOf($entity, $node);
+ } elseif ('before' === $position) {
+ $nodeRepository->persistAsPrevSiblingOf($entity, $node);
+ }
+ }
+
+ $this->handlePageAssociation(
+ $form->get('pageAction')->getData(),
+ $form->get('pageEntity')->getData(),
+ $form->get('pageType')->getData(),
+ $entity,
+ $pageFactory,
+ $pageLocator
+ );
+
+ $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' => $node->getMenu()->getNavigation()->getId(),
+ ]);
+ }
+
+ return $this->render('site/node_admin/new.html.twig', [
+ 'form' => $form->createView(),
+ 'node' => $node,
+ 'entity' => $entity,
+ ]);
+ }
+
+ /**
+ * @Route("/edit/{entity}", name="admin_site_node_edit")
+ */
+ public function edit(
+ Entity $entity,
+ EntityManager $entityManager,
+ PageFactory $pageFactory,
+ PageLocator $pageLocator,
+ Request $request
+ ): Response {
+ $form = $this->createForm(EntityType::class, $entity, [
+ 'pages' => $pageLocator->getPages(),
+ ]);
+
+ if ($request->isMethod('POST')) {
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $this->handlePageAssociation(
+ $form->get('pageAction')->getData(),
+ $form->get('pageEntity')->getData(),
+ $form->get('pageType')->getData(),
+ $entity,
+ $pageFactory,
+ $pageLocator
+ );
+
+ $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->getMenu()->getNavigation()->getId(),
+ ]);
+ }
+
+ return $this->render('site/node_admin/edit.html.twig', [
+ 'form' => $form->createView(),
+ '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 '';
+ }
+
+ protected function handlePageAssociation(
+ string $pageAction,
+ ?Page $pageEntity,
+ string $pageType,
+ Entity $entity,
+ PageFactory $pageFactory,
+ PageLocator $pageLocator
+ ) {
+ if ('new' === $pageAction) {
+ $pageConfiguration = $pageLocator->getPage($pageType);
+ $page = $pageFactory->create($pageType, $entity->getLabel());
+ $page->setTemplate($pageConfiguration->getTemplates()[0]['file']);
+
+ $entity->setPage($page);
+ } elseif ('existing' === $pageAction) {
+ if ($pageEntity) {
+ $entity->setPage($pageEntity);
+ } else {
+ $this->addFlash('info', 'Aucun changement de page effectué.');
+ }
+ } elseif ('none' === $pageAction) {
+ $entity->setPage(null);
+ }
+ }
+}
diff --git a/src/Controller/Site/PageAdminController.php b/src/Controller/Site/PageAdminController.php
new file mode 100644
index 0000000..364fb0f
--- /dev/null
+++ b/src/Controller/Site/PageAdminController.php
@@ -0,0 +1,109 @@
+paginate($page);
+
+ return $this->render('site/page_admin/index.html.twig', [
+ 'pager' => $pager,
+ ]);
+ }
+
+ /**
+ * @Route("/new", name="admin_site_page_new")
+ */
+ public function new(EntityFactory $factory, EntityManager $entityManager): Response
+ {
+ // $entity = $factory->create(FooPage::class);
+ $entity = $factory->create(SimplePage::class);
+ $entity->setName('Page de test '.mt_rand());
+
+ $entityManager->create($entity);
+
+ $this->addFlash('success', 'Donnée enregistrée.');
+
+ return $this->redirectToRoute('admin_site_page_edit', [
+ 'entity' => $entity->getId(),
+ ]);
+ }
+
+ /**
+ * @Route("/edit/{entity}", name="admin_site_page_edit")
+ */
+ public function edit(
+ int $entity,
+ EntityFactory $factory,
+ EntityManager $entityManager,
+ RepositoryQuery $repositoryQuery,
+ PageLocator $pageLocator,
+ Request $request
+ ): Response {
+ $entity = $repositoryQuery->filterById($entity)->findOne();
+ $form = $this->createForm(EntityType::class, $entity, [
+ 'pageConfiguration' => $pageLocator->getPage(get_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_page_edit', [
+ 'entity' => $entity->getId(),
+ ]);
+ }
+
+ $this->addFlash('warning', 'Le formulaire est invalide.');
+ }
+
+ return $this->render('site/page_admin/edit.html.twig', [
+ 'form' => $form->createView(),
+ 'entity' => $entity,
+ ]);
+ }
+
+ /**
+ * @Route("/delete/{entity}", name="admin_site_page_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_page_index');
+ }
+
+ public function getSection(): string
+ {
+ return 'site_page';
+ }
+}
diff --git a/src/Controller/Site/PageController.php b/src/Controller/Site/PageController.php
new file mode 100644
index 0000000..f02bcd7
--- /dev/null
+++ b/src/Controller/Site/PageController.php
@@ -0,0 +1,25 @@
+getPage()) {
+ throw $this->createNotFoundException();
+ }
+
+ return $this->render($siteRequest->getPage()->getTemplate(), [
+ '_node' => $siteRequest->getNode(),
+ '_page' => $siteRequest->getPage(),
+ '_menu' => $siteRequest->getMenu(),
+ '_navigation' => $siteRequest->getNavigation(),
+ ]);
+ }
+}
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/DependencyInjection/AppExtension.php b/src/DependencyInjection/AppExtension.php
new file mode 100644
index 0000000..664ea17
--- /dev/null
+++ b/src/DependencyInjection/AppExtension.php
@@ -0,0 +1,28 @@
+getConfiguration($configs, $container);
+ $config = $this->processConfiguration($configuration, $configs);
+
+ $container->setParameter('app', $config);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfiguration(array $configs, ContainerBuilder $container)
+ {
+ return new Configuration();
+ }
+}
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
new file mode 100644
index 0000000..b710766
--- /dev/null
+++ b/src/DependencyInjection/Configuration.php
@@ -0,0 +1,44 @@
+getRootNode()
+ ->children()
+ ->arrayNode('site')
+ ->children()
+ ->arrayNode('pages')
+ ->prototype('array')
+ ->children()
+ ->scalarNode('name')
+ ->isRequired()
+ ->cannotBeEmpty()
+ ->end()
+ ->arrayNode('templates')
+ ->prototype('array')
+ ->children()
+ ->scalarNode('name')
+ ->cannotBeEmpty()
+ ->end()
+ ->scalarNode('file')
+ ->cannotBeEmpty()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end();
+
+ return $treeBuilder;
+ }
+}
diff --git a/src/Entity/Blog/Category.php b/src/Entity/Blog/Category.php
index a2ab1ff..8d6dced 100644
--- a/src/Entity/Blog/Category.php
+++ b/src/Entity/Blog/Category.php
@@ -2,6 +2,7 @@
namespace App\Entity\Blog;
+use App\Doctrine\Timestampable;
use App\Entity\EntityInterface;
use App\Repository\Blog\CategoryRepository;
use Doctrine\Common\Collections\ArrayCollection;
@@ -14,6 +15,8 @@ use Doctrine\ORM\Mapping as ORM;
*/
class Category implements EntityInterface
{
+ use Timestampable;
+
/**
* @ORM\Id
* @ORM\GeneratedValue
diff --git a/src/Entity/Site/Menu.php b/src/Entity/Site/Menu.php
new file mode 100644
index 0000000..1434a28
--- /dev/null
+++ b/src/Entity/Site/Menu.php
@@ -0,0 +1,141 @@
+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..b0b08f4
--- /dev/null
+++ b/src/Entity/Site/Node.php
@@ -0,0 +1,282 @@
+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());
+ }
+
+ public function getPage(): ?Page
+ {
+ return $this->page;
+ }
+
+ public function setPage(?Page $page): self
+ {
+ $this->page = $page;
+
+ return $this;
+ }
+}
diff --git a/src/Entity/Site/Page/Block.php b/src/Entity/Site/Page/Block.php
new file mode 100644
index 0000000..902c580
--- /dev/null
+++ b/src/Entity/Site/Page/Block.php
@@ -0,0 +1,80 @@
+id;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): self
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ public function getValue(): ?string
+ {
+ return $this->value;
+ }
+
+ public function setValue(string $value): self
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ public function getPage(): ?Page
+ {
+ return $this->page;
+ }
+
+ public function setPage(?Page $page): self
+ {
+ $this->page = $page;
+
+ return $this;
+ }
+}
diff --git a/src/Entity/Site/Page/Page.php b/src/Entity/Site/Page/Page.php
new file mode 100644
index 0000000..6111f2f
--- /dev/null
+++ b/src/Entity/Site/Page/Page.php
@@ -0,0 +1,248 @@
+blocks = new ArrayCollection();
+ $this->nodes = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): self
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ public function getTemplate(): ?string
+ {
+ return $this->template;
+ }
+
+ public function setTemplate(?string $template): self
+ {
+ $this->template = $template;
+
+ return $this;
+ }
+
+ /**
+ * @return Collection|Block[]
+ */
+ public function getBlocks(): Collection
+ {
+ return $this->blocks;
+ }
+
+ public function addBlock(Block $block): self
+ {
+ if (!$this->blocks->contains($block)) {
+ $this->blocks[] = $block;
+ $block->setPage($this);
+ }
+
+ return $this;
+ }
+
+ public function removeBlock(Block $block): self
+ {
+ if ($this->blocks->removeElement($block)) {
+ // set the owning side to null (unless already changed)
+ if ($block->getPage() === $this) {
+ $block->setPage(null);
+ }
+ }
+
+ return $this;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ }
+
+ public function getBlock($name)
+ {
+ foreach ($this->getBlocks() as $block) {
+ if ($block->getName() === $name) {
+ return $block;
+ }
+ }
+
+ $block = new Block();
+ $block->setName($name);
+ $block->setPage($this);
+
+ return $block;
+ }
+
+ public function setBlock(Block $block): self
+ {
+ foreach ($this->blocks->toArray() as $key => $value) {
+ if ($value->getName() === $block->getName()) {
+ $this->blocks->remove($key);
+ $this->blocks->add($block);
+
+ return $this;
+ }
+ }
+
+ $this->blocks->add($block);
+
+ return $this;
+ }
+
+ public function getMetaTitle(): ?string
+ {
+ return $this->metaTitle;
+ }
+
+ public function setMetaTitle(?string $metaTitle): self
+ {
+ $this->metaTitle = $metaTitle;
+
+ return $this;
+ }
+
+ public function getMetaDescrition(): ?string
+ {
+ return $this->metaDescrition;
+ }
+
+ public function setMetaDescrition(?string $metaDescrition): self
+ {
+ $this->metaDescrition = $metaDescrition;
+
+ return $this;
+ }
+
+ public function getOgTitle(): ?string
+ {
+ return $this->ogTitle;
+ }
+
+ public function setOgTitle(?string $ogTitle): self
+ {
+ $this->ogTitle = $ogTitle;
+
+ return $this;
+ }
+
+ public function getOgDescription(): ?string
+ {
+ return $this->ogDescription;
+ }
+
+ public function setOgDescription(?string $ogDescription): self
+ {
+ $this->ogDescription = $ogDescription;
+
+ 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->setPage($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->getPage() === $this) {
+ $node->setPage(null);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/src/Event/EntityManager/EntityManagerEvent.php b/src/Event/EntityManager/EntityManagerEvent.php
index 29cf9e3..b7a3518 100644
--- a/src/Event/EntityManager/EntityManagerEvent.php
+++ b/src/Event/EntityManager/EntityManagerEvent.php
@@ -15,6 +15,9 @@ class EntityManagerEvent extends Event
const CREATE_EVENT = 'entity_manager_event.create';
const UPDATE_EVENT = 'entity_manager_event.update';
const DELETE_EVENT = 'entity_manager_event.delete';
+ const PRE_CREATE_EVENT = 'entity_manager_event.pre_create';
+ const PRE_UPDATE_EVENT = 'entity_manager_event.pre_update';
+ const PRE_DELETE_EVENT = 'entity_manager_event.pre_delete';
protected EntityInterface $entity;
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 79%
rename from src/EventSuscriber/BlogPostEventSubscriber.php
rename to src/EventSuscriber/Blog/PostEventSubscriber.php
index f653f83..2a4f634 100644
--- a/src/EventSuscriber/BlogPostEventSubscriber.php
+++ b/src/EventSuscriber/Blog/PostEventSubscriber.php
@@ -1,21 +1,21 @@
*/
-class BlogPostEventSubscriber implements EventSubscriberInterface
+class PostEventSubscriber extends EntityManagerEventSubscriber
{
protected Filesystem $filesystem;
protected PostRepositoryQuery $query;
@@ -26,14 +26,6 @@ class BlogPostEventSubscriber implements EventSubscriberInterface
$this->query = $query;
}
- public static function getSubscribedEvents()
- {
- return [
- EntityManagerEvent::UPDATE_EVENT => 'onUpdate',
- EntityManagerEvent::DELETE_EVENT => 'onDelete',
- ];
- }
-
public function support(EntityInterface $entity)
{
return $entity instanceof Post;
diff --git a/src/EventSuscriber/EntityManagerEventSubscriber.php b/src/EventSuscriber/EntityManagerEventSubscriber.php
index ef819f1..856360d 100644
--- a/src/EventSuscriber/EntityManagerEventSubscriber.php
+++ b/src/EventSuscriber/EntityManagerEventSubscriber.php
@@ -10,18 +10,17 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*
* @author Simon Vieille
*/
-class EntityManagerEventSubscriber implements EventSubscriberInterface
+abstract class EntityManagerEventSubscriber implements EventSubscriberInterface
{
- public function __construct()
- {
- }
-
public static function getSubscribedEvents()
{
return [
EntityManagerEvent::CREATE_EVENT => 'onCreate',
EntityManagerEvent::UPDATE_EVENT => 'onUpdate',
EntityManagerEvent::DELETE_EVENT => 'onDelete',
+ EntityManagerEvent::PRE_CREATE_EVENT => 'onPreCreate',
+ EntityManagerEvent::PRE_UPDATE_EVENT => 'onPreUpdate',
+ EntityManagerEvent::PRE_DELETE_EVENT => 'onPreDelete',
];
}
@@ -36,4 +35,16 @@ class EntityManagerEventSubscriber implements EventSubscriberInterface
public function onDelete(EntityManagerEvent $event)
{
}
+
+ public function onPreCreate(EntityManagerEvent $event)
+ {
+ }
+
+ public function onPreUpdate(EntityManagerEvent $event)
+ {
+ }
+
+ public function onPreDelete(EntityManagerEvent $event)
+ {
+ }
}
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..f4e7ce5
--- /dev/null
+++ b/src/EventSuscriber/Site/NodeEventSubscriber.php
@@ -0,0 +1,127 @@
+
+ */
+class NodeEventSubscriber extends EntityManagerEventSubscriber
+{
+ protected NodeFactory $nodeFactory;
+ protected EntityManager $entityManager;
+ protected KernelInterface $kernel;
+ protected Slugify $slugify;
+
+ public function __construct(
+ NodeFactory $nodeFactory,
+ NodeRepository $nodeRepository,
+ EntityManager $entityManager,
+ KernelInterface $kernel,
+ Slugify $slugify
+ ) {
+ $this->nodeFactory = $nodeFactory;
+ $this->nodeRepository = $nodeRepository;
+ $this->entityManager = $entityManager;
+ $this->kernel = $kernel;
+ $this->slugify = $slugify;
+ }
+
+ public function support(EntityInterface $entity)
+ {
+ return $entity instanceof Node;
+ }
+
+ public function onPreUpdate(EntityManagerEvent $event)
+ {
+ if (!$this->support($event->getEntity())) {
+ return;
+ }
+
+ $node = $event->getEntity();
+
+ if ($node->getUrl()) {
+ $generatedUrl = $node->getUrl();
+ } else {
+ $path = [];
+ $parent = $node->getParent();
+
+ if ($parent && $parent->getUrl()) {
+ $pPath = trim($parent->getUrl(), '/');
+
+ if ($pPath) {
+ $path[] = $pPath;
+ }
+ }
+
+ $path[] = $this->slugify->slugify($node->getLabel());
+
+ $generatedUrl = '/'.implode('/', $path);
+ }
+
+ $urlExists = $this->nodeRepository->urlExists($generatedUrl, $node);
+
+ if ($urlExists) {
+ $number = 1;
+
+ while ($this->nodeRepository->urlExists($generatedUrl.'-'.$number, $node)) {
+ ++$number;
+ }
+
+ $generatedUrl = $generatedUrl.'-'.$number;
+ }
+
+ $node->setUrl($generatedUrl);
+ }
+
+ public function onUpdate(EntityManagerEvent $event)
+ {
+ $application = new Application($this->kernel);
+ $application->setAutoExit(false);
+
+ $input = new ArrayInput([
+ 'command' => 'cache:clear',
+ ]);
+
+ $output = new BufferedOutput();
+ $application->run($input, $output);
+ }
+
+ 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/Factory/Site/Page/PageFactory.php b/src/Factory/Site/Page/PageFactory.php
new file mode 100644
index 0000000..64e3725
--- /dev/null
+++ b/src/Factory/Site/Page/PageFactory.php
@@ -0,0 +1,21 @@
+
+ */
+class PageFactory
+{
+ public function create(string $className, string $name): Page
+ {
+ $entity = new $className();
+ $entity->setName($name);
+
+ 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..66fb167
--- /dev/null
+++ b/src/Form/Site/NodeType.php
@@ -0,0 +1,144 @@
+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' => [
+ ],
+ ]
+ );
+
+ $actions = [
+ 'Nouvelle page' => 'new',
+ 'Associer à une page existante' => 'existing',
+ 'Aucune page' => 'none',
+ ];
+
+ if ($builder->getData()->getId()) {
+ $actions['Garder la configuration actuelle'] = 'keep';
+ }
+
+ $builder->add(
+ 'pageAction',
+ ChoiceType::class,
+ [
+ 'label' => false,
+ 'required' => true,
+ 'expanded' => true,
+ 'mapped' => false,
+ 'choices' => $actions,
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'pageType',
+ ChoiceType::class,
+ [
+ 'label' => false,
+ 'required' => true,
+ 'mapped' => false,
+ 'choices' => call_user_func(function () use ($options) {
+ $choices = [];
+
+ foreach ($options['pages'] as $page) {
+ $choices[$page->getName()] = $page->getClassName();
+ }
+
+ return $choices;
+ }),
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'pageEntity',
+ EntityType::class,
+ [
+ 'label' => false,
+ 'required' => true,
+ 'mapped' => false,
+ 'class' => Page::class,
+ 'choice_label' => 'name',
+ 'query_builder' => function (EntityRepository $repo) {
+ return $repo->createQueryBuilder('p')
+ ->orderBy('p.name', 'ASC')
+ ;
+ },
+ 'constraints' => [
+ ],
+ ]
+ );
+
+ if (null === $builder->getData()->getId()) {
+ $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,
+ 'pages' => [],
+ ]);
+ }
+}
diff --git a/src/Form/Site/Page/PageType.php b/src/Form/Site/Page/PageType.php
new file mode 100644
index 0000000..0cb31ba
--- /dev/null
+++ b/src/Form/Site/Page/PageType.php
@@ -0,0 +1,116 @@
+add(
+ 'name',
+ TextType::class,
+ [
+ 'label' => 'Nom',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'metaTitle',
+ TextType::class,
+ [
+ 'label' => 'Titre',
+ 'required' => false,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'metaDescrition',
+ TextType::class,
+ [
+ 'label' => 'Description',
+ 'required' => false,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'ogTitle',
+ TextType::class,
+ [
+ 'label' => 'Titre',
+ 'required' => false,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'ogDescription',
+ TextType::class,
+ [
+ 'label' => 'Description',
+ 'required' => false,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'template',
+ ChoiceType::class,
+ [
+ 'label' => 'Rendu',
+ 'required' => true,
+ 'choices' => call_user_func(function () use ($options) {
+ $choices = [];
+
+ foreach ($options['pageConfiguration']->getTemplates() as $template) {
+ $choices[$template['name']] = $template['file'];
+ }
+
+ return $choices;
+ }),
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new NotBlank(),
+ ],
+ ]
+ );
+
+ $builder->getData()->buildForm($builder);
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => Page::class,
+ 'pageConfiguration' => null,
+ ]);
+ }
+}
diff --git a/src/Form/Site/Page/TextBlockType.php b/src/Form/Site/Page/TextBlockType.php
new file mode 100644
index 0000000..483ccac
--- /dev/null
+++ b/src/Form/Site/Page/TextBlockType.php
@@ -0,0 +1,32 @@
+add(
+ 'value',
+ TextType::class,
+ array_merge([
+ 'required' => false,
+ 'label' => false,
+ ], $options['options']),
+ );
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => Block::class,
+ 'options' => [],
+ ]);
+ }
+}
diff --git a/src/Form/Site/Page/TextareaBlockType.php b/src/Form/Site/Page/TextareaBlockType.php
new file mode 100644
index 0000000..b539c4c
--- /dev/null
+++ b/src/Form/Site/Page/TextareaBlockType.php
@@ -0,0 +1,21 @@
+add(
+ 'value',
+ TextareaType::class,
+ array_merge([
+ 'required' => false,
+ 'label' => false,
+ ], $options['options']),
+ );
+ }
+}
diff --git a/src/Manager/EntityManager.php b/src/Manager/EntityManager.php
index 8216aab..7c67882 100644
--- a/src/Manager/EntityManager.php
+++ b/src/Manager/EntityManager.php
@@ -25,28 +25,48 @@ class EntityManager
$this->entityManager = $entityManager;
}
- public function create(EntityInterface $entity): self
+ public function create(EntityInterface $entity, bool $dispatchEvent = true): self
{
+ 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
{
+ 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
{
+ 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;
}
@@ -65,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..b2821d0
--- /dev/null
+++ b/src/Repository/Site/NodeRepository.php
@@ -0,0 +1,35 @@
+getClassMetadata(Node::class));
+ }
+
+ public function urlExists($url, Node $node)
+ {
+ $query = $this->createQueryBuilder('n')
+ ->where('n.url = :url')
+ ->setParameter(':url', $url)
+ ;
+
+ if ($node->getId()) {
+ $query
+ ->andWhere('n.id != :id')
+ ->setParameter(':id', $node->getId())
+ ;
+ }
+
+ return $query->getQuery()
+ ->setMaxResults(1)
+ ->getOneOrNullResult()
+ ;
+ }
+}
diff --git a/src/Repository/Site/Page/BlockRepository.php b/src/Repository/Site/Page/BlockRepository.php
new file mode 100644
index 0000000..3cd4648
--- /dev/null
+++ b/src/Repository/Site/Page/BlockRepository.php
@@ -0,0 +1,15 @@
+
+ */
+class BlockRepositoryQuery extends RepositoryQuery
+{
+ public function __construct(BlockRepository $repository, PaginatorInterface $paginator)
+ {
+ parent::__construct($repository, 'b', $paginator);
+ }
+}
diff --git a/src/Repository/Site/Page/PageRepository.php b/src/Repository/Site/Page/PageRepository.php
new file mode 100644
index 0000000..5fd82b7
--- /dev/null
+++ b/src/Repository/Site/Page/PageRepository.php
@@ -0,0 +1,15 @@
+
+ */
+class PageRepositoryQuery extends RepositoryQuery
+{
+ public function __construct(PageRepository $repository, PaginatorInterface $paginator)
+ {
+ parent::__construct($repository, 'p', $paginator);
+ }
+
+ public function filterById($id)
+ {
+ $this
+ ->where('.id = :id')
+ ->setParameter(':id', $id)
+ ;
+
+ return $this;
+ }
+}
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/src/Router/SiteRouteLoader.php b/src/Router/SiteRouteLoader.php
new file mode 100644
index 0000000..9dd6507
--- /dev/null
+++ b/src/Router/SiteRouteLoader.php
@@ -0,0 +1,65 @@
+
+ */
+class SiteRouteLoader extends Loader
+{
+ protected NavigationRepositoryQuery $navigationQuery;
+ protected $isLoaded = false;
+
+ public function __construct(NavigationRepositoryQuery $navigationQuery)
+ {
+ $this->navigationQuery = $navigationQuery;
+ }
+
+ public function load($resource, ?string $type = null)
+ {
+ if (true === $this->isLoaded) {
+ throw new \RuntimeException('Do not add the "extra" loader twice');
+ }
+
+ $routes = new RouteCollection();
+ $navigations = $this->navigationQuery->find();
+
+ foreach ($navigations as $navigation) {
+ foreach ($navigation->getMenus() as $menu) {
+ foreach ($menu->getRootNode()->getAllChildren() as $node) {
+ $requirements = [];
+
+ $defaults = [
+ '_controller' => PageController::class.'::show',
+ '_node' => $node->getId(),
+ '_menu' => $menu->getId(),
+ '_page' => $node->getPage() ? $node->getPage()->getId() : null,
+ '_navigation' => $navigation->getId(),
+ ];
+
+ $route = new Route($node->getUrl(), $defaults, $requirements);
+ $route->setHost($navigation->getDomain());
+
+ $routes->add('site_page_'.$node->getId(), $route);
+ }
+ }
+ }
+
+ $this->isLoaded = true;
+
+ return $routes;
+ }
+
+ public function supports($resource, string $type = null)
+ {
+ return 'extra' === $type;
+ }
+}
diff --git a/src/Site/Page/SimplePage.php b/src/Site/Page/SimplePage.php
new file mode 100644
index 0000000..7652d98
--- /dev/null
+++ b/src/Site/Page/SimplePage.php
@@ -0,0 +1,68 @@
+add(
+ 'title',
+ TextBlockType::class,
+ [
+ 'label' => 'Titre',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ ],
+ ]
+ );
+
+ $builder->add(
+ 'content',
+ TextareaBlockType::class,
+ [
+ 'label' => 'Content',
+ 'options' => [
+ 'attr' => [
+ 'data-tinymce' => '',
+ 'rows' => '18',
+ ],
+ 'constraints' => [
+ ],
+ ],
+ ]
+ );
+ }
+
+ public function setTitle(Block $block)
+ {
+ return $this->setBlock($block);
+ }
+
+ public function getTitle()
+ {
+ return $this->getBlock('title');
+ }
+
+ public function setContent(Block $block)
+ {
+ return $this->setBlock($block);
+ }
+
+ public function getContent()
+ {
+ return $this->getBlock('content');
+ }
+}
diff --git a/src/Site/PageConfiguration.php b/src/Site/PageConfiguration.php
new file mode 100644
index 0000000..1d97fc4
--- /dev/null
+++ b/src/Site/PageConfiguration.php
@@ -0,0 +1,51 @@
+
+ */
+class PageConfiguration
+{
+ protected string $className;
+ protected string $name;
+ protected array $templates;
+
+ public function setClassName(string $className): self
+ {
+ $this->className = $className;
+
+ return $this;
+ }
+
+ public function getClassName(): string
+ {
+ return $this->className;
+ }
+
+ public function setName(string $name): self
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setTemplates(array $templates): self
+ {
+ $this->templates = $templates;
+
+ return $this;
+ }
+
+ public function getTemplates(): array
+ {
+ return $this->templates;
+ }
+}
diff --git a/src/Site/PageLocator.php b/src/Site/PageLocator.php
new file mode 100644
index 0000000..7ff736f
--- /dev/null
+++ b/src/Site/PageLocator.php
@@ -0,0 +1,48 @@
+
+ */
+class PageLocator
+{
+ protected array $params;
+ protected array $pages;
+
+ public function __construct(ParameterBagInterface $bag)
+ {
+ $this->params = $bag->get('app');
+ $this->loadPages();
+ }
+
+ public function getPages(): array
+ {
+ return $this->pages;
+ }
+
+ public function getPage($className)
+ {
+ return $this->pages[$className] ?? null;
+ }
+
+ protected function loadPages(): void
+ {
+ $params = $this->params['site']['pages'] ?? [];
+
+ foreach ($params as $className => $conf) {
+ $pageConfiguration = new PageConfiguration();
+ $pageConfiguration
+ ->setClassName($className)
+ ->setName($conf['name'])
+ ->setTemplates($conf['templates'])
+ ;
+
+ $this->pages[$className] = $pageConfiguration;
+ }
+ }
+}
diff --git a/src/Site/SiteRequest.php b/src/Site/SiteRequest.php
new file mode 100644
index 0000000..cd41954
--- /dev/null
+++ b/src/Site/SiteRequest.php
@@ -0,0 +1,69 @@
+
+ */
+class SiteRequest
+{
+ protected RequestStack $requestStack;
+ protected NodeRepository $nodeRepository;
+ protected NavigationRepositoryQuery $navigationRepositoryQuery;
+ protected PageRepositoryQuery $pageRepositoryQuery;
+
+ public function __construct(RequestStack $requestStack, NodeRepository $nodeRepository)
+ {
+ $this->requestStack = $requestStack;
+ $this->nodeRepository = $nodeRepository;
+ }
+
+ public function getNode(): ?Node
+ {
+ $request = $this->requestStack->getCurrentRequest();
+
+ if ($request->attributes->has('_node')) {
+ return $this->nodeRepository->findOneBy([
+ 'id' => $request->attributes->get('_node'),
+ ]);
+ }
+
+ return null;
+ }
+
+ public function getPage(): ?Page
+ {
+ $node = $this->getNode();
+
+ if ($node && $node->getPage()) {
+ return $node->getPage();
+ }
+
+ return null;
+ }
+
+ public function getMenu(): ?Menu
+ {
+ $node = $this->getNode();
+
+ return null !== $node ? $node->getMenu() : null;
+ }
+
+ public function getNavigation(): ?Navigation
+ {
+ $menu = $this->getMenu();
+
+ return null !== $menu ? $menu->getNavigation() : null;
+ }
+}
diff --git a/src/Slugify/Slugify.php b/src/Slugify/Slugify.php
new file mode 100644
index 0000000..76976b3
--- /dev/null
+++ b/src/Slugify/Slugify.php
@@ -0,0 +1,31 @@
+
+ */
+class Slugify
+{
+ public function slugify($data)
+ {
+ return $this->create()->slugify($data);
+ }
+
+ protected function create(): BaseSlugify
+ {
+ $slugify = new BaseSlugify([
+ 'separator' => '-',
+ 'lowercase' => false,
+ ]);
+
+ $slugify->activateRuleSet('french');
+ $slugify->addRule("'", '');
+
+ return $slugify;
+ }
+}
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 d13618e..73a5e1f 100644
--- a/templates/admin/layout.html.twig
+++ b/templates/admin/layout.html.twig
@@ -50,20 +50,44 @@
- {% if is_granted('ROLE_WRITER') %}
+ {% if is_granted('ROLE_ADMIN') %}
+ {% endif %}
+ {% if is_granted('ROLE_WRITER') %}
+
+
+
-
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/_form.html.twig b/templates/site/node_admin/_form.html.twig
new file mode 100644
index 0000000..49be5c6
--- /dev/null
+++ b/templates/site/node_admin/_form.html.twig
@@ -0,0 +1,108 @@
+{{ form_row(form.label) }}
+{{ form_row(form.url) }}
+
+{% if form.position is defined %}
+ {{ form_row(form.position) }}
+{% endif %}
+
+
+
+ {% set action = form.pageAction[0] %}
+ {% set options = not entity.id ? {'attr': {'checked': 'checked'}} : {} %}
+
+
+
+
+ {{ form_row(form.pageType) }}
+
+
+
+
+ {% set action = form.pageAction[1] %}
+
+
+
+
+ {{ form_row(form.pageEntity) }}
+
+
+
+
+ {% set action = form.pageAction[2] %}
+
+
+
+
+
+ {% if entity.id %}
+
+ {% set action = form.pageAction[3] %}
+ {% set options = {'attr': {'checked': 'checked'}} %}
+
+
+
+
+ {% endif %}
+
+
+{{ form_rest(form) }}
diff --git a/templates/site/node_admin/edit.html.twig b/templates/site/node_admin/edit.html.twig
new file mode 100644
index 0000000..598e586
--- /dev/null
+++ b/templates/site/node_admin/edit.html.twig
@@ -0,0 +1,20 @@
+
+
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..2b9515c
--- /dev/null
+++ b/templates/site/node_admin/new.html.twig
@@ -0,0 +1,19 @@
+
diff --git a/templates/site/page/simple/page.html.twig b/templates/site/page/simple/page.html.twig
new file mode 100644
index 0000000..6ab1d64
--- /dev/null
+++ b/templates/site/page/simple/page.html.twig
@@ -0,0 +1,8 @@
+template 1
+
+Node : {{ _node.label }}
+Menu : {{ _menu.label }}
+Navigation : {{ _navigation.label }}
+
+Page : {{ _page.title.value }}
+Content : {{ _page.content.value|raw }}
diff --git a/templates/site/page/simple/page2.html.twig b/templates/site/page/simple/page2.html.twig
new file mode 100644
index 0000000..ffb52bc
--- /dev/null
+++ b/templates/site/page/simple/page2.html.twig
@@ -0,0 +1,8 @@
+template 2
+
+Node : {{ _node.label }}
+Menu : {{ _menu.label }}
+Navigation : {{ _navigation.label }}
+
+Page : {{ _page.title.value }}
+Content : {{ _page.content.value|raw }}
diff --git a/templates/site/page_admin/_form.html.twig b/templates/site/page_admin/_form.html.twig
new file mode 100644
index 0000000..514278d
--- /dev/null
+++ b/templates/site/page_admin/_form.html.twig
@@ -0,0 +1,51 @@
+{% set formMetas %}
+ {% for item in ['metaTitle', 'metaDescrition'] %}
+ {{ form_row(form[item]) }}
+ {% endfor %}
+{% endset %}
+
+{% set formOpenGraph %}
+ {% for item in ['ogTitle', 'ogDescription'] %}
+ {{ form_row(form[item]) }}
+ {% endfor %}
+{% endset %}
+
+{% set formOthers %}
+ {% for item in ['name', 'template'] %}
+ {{ form_row(form[item]) }}
+ {% endfor %}
+{% endset %}
+
+{% set formSitemap %}
+{% endset %}
+
+
+
+ {{ form_widget(form) }}
+
+
+
+
+
+
+ {{ formMetas|raw }}
+
+
+ {{ formOpenGraph|raw }}
+
+
+ {{ formOthers|raw }}
+
+
+
+
diff --git a/templates/site/page_admin/edit.html.twig b/templates/site/page_admin/edit.html.twig
new file mode 100644
index 0000000..2e4b6e5
--- /dev/null
+++ b/templates/site/page_admin/edit.html.twig
@@ -0,0 +1,48 @@
+{% extends 'admin/layout.html.twig' %}
+
+{% block body %}
+
+
+
+
{{ entity.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/templates/site/page_admin/index.html.twig b/templates/site/page_admin/index.html.twig
new file mode 100644
index 0000000..65fce23
--- /dev/null
+++ b/templates/site/page_admin/index.html.twig
@@ -0,0 +1,59 @@
+{% extends 'admin/layout.html.twig' %}
+
+{% block body %}
+
+
+
+
+
+ Nom |
+ Actions |
+
+
+
+ {% for item in pager %}
+ {% set edit = path('admin_site_page_edit', {entity: item.id}) %}
+
+
+
+
+ {{ item.name }}
+
+ |
+
+
+
+
+
+
+
+ |
+
+ {% else %}
+
+
+
+
+
+
+ Aucun résultat
+
+ |
+
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/templates/site/tree_admin/navigation.html.twig b/templates/site/tree_admin/navigation.html.twig
new file mode 100644
index 0000000..68a9ede
--- /dev/null
+++ b/templates/site/tree_admin/navigation.html.twig
@@ -0,0 +1,200 @@
+{% 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 edit = path('admin_site_node_edit', {entity: node.id}) %}
+ {% set new = path('admin_site_node_new', {node: node.id}) %}
+
+
+
+ {% if node.page %}
+
+
+ Page
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endfor %}
+ {% endif %}
+
+
+ {% else %}
+
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+
+
+ {% for menuId, form in forms.menus %}
+
+
+
+ {% endfor %}
+{% endblock %}
diff --git a/templates/user/user_admin/edit.html.twig b/templates/user/user_admin/edit.html.twig
index 7bb320b..afd3168 100644
--- a/templates/user/user_admin/edit.html.twig
+++ b/templates/user/user_admin/edit.html.twig
@@ -30,7 +30,7 @@