Sonata est une suite de bundle pour Symfony 4. L’un d’entre eux permet de gérer du contenu à la manière de WordPress.
Vous trouverez assez peu de documentations sur le net, et pour cause, l’installation est bugué, et les rares personnes qui se sont frotté au bugtracker sur github, se sont vu répondre un RTFM bien en règle.
Rester sur ce genre d’échec n’est pas vraiment mon style.
Voici donc comment installer et configurer Sonata-Page-Bundle, avec l’installation officiel, puis les procédure de contournement de ces F###ing bugs.
1iere étape, on récupère notre skeleton de symfony 4 qui comprends notre base fonctionnelle du framework agrémenté de Sonata + FosUser + Media.
Déjà on économise une bonne journée ^^
En gros, il faut suivre la documentation : https://sonata-project.org/bundles/page/3-x/doc/reference/installation.html
Installer tous les Bundles, et suivre scrupuleusement la documentation.
La première difficulté c’est l’installation via composer. Je n’ai pas réussi à tout installer pour php 7.1. Ni pour php 7.2 d’ailleurs. Il y a toujours un package en conflit.
L’astuce dans composer, c’est de sortir du stable via l’instruction minimum-stability dans votre composer.json et là, au bout de 3 ou 4 heures à tester les versions, on arrive à tout installer.
Voici mon composer.json
{
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/doctrine-migrations-bundle": "^3.0",
"jms/serializer-bundle": "^3.7",
"liip/imagine-bundle": "^2.3",
"nelmio/api-doc-bundle": "^3.6",
"phpoffice/phpspreadsheet": "^1.14",
"pixassociates/sortable-behavior-bundle": "^1.5",
"prodigious/sonata-menu-bundle": "^3.0",
"sensio/framework-extra-bundle": "^5.5",
"sonata-project/block-bundle": "^3.20",
"sonata-project/cache-bundle": "^2.4",
"sonata-project/core-bundle": "^3.20",
"sonata-project/doctrine-orm-admin-bundle": "^3.20",
"sonata-project/formatter-bundle": "^4.2",
"sonata-project/intl-bundle": "^2.7",
"sonata-project/media-bundle": "^3.25",
"sonata-project/notification-bundle": "^3.8",
"sonata-project/page-bundle": "^3.17",
"sonata-project/seo-bundle": "^2.11",
"sonata-project/user-bundle": "^4.6",
"symfony/apache-pack": "^1.0",
"symfony/console": "4.*",
"symfony/debug-pack": "^1.0",
"symfony/dotenv": "4.4.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "4.4.*",
"symfony/serializer-pack": "^1.0",
"symfony/swiftmailer-bundle": "^3.4",
"symfony/templating": "4.4.*",
"symfony/translation": "4.4.*",
"symfony/var-dumper": "4.4.*",
"symfony/yaml": "4.*",
"twig/twig": "^2.9.0"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3",
"symfony/maker-bundle": "^1.19"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "4.4.*"
}
}
,
"minimum-stability": "dev",
"prefer-stable": true
}
Une fois les packages installés, et configurés, n’oubliez pas d’installer Ckeditor, et les assets. Et accessoirement, votre user super admin.
php bin/console assets:install
php bin/console ckeditor:install
mkdir -p public/uploads/media
php bin/console cache:clear
php bin/console fos:user:create admin tbourdin@partitech.com admin --super-admin
Il reste à créer le premier site :
php app/console sonata:page:create-site
Please define a value for Site.name : Skeletton CMS
Please define a value for Site.host : www.skeleton-cms.test
Please define a value for Site.relativePath : /
Please define a value for Site.enabled : true
Please define a value for Site.locale : -
Please define a value for Site.enabledFrom : now
Please define a value for Site.enabledTo : +10 years
Please define a value for Site.default : true
Creating website with the following information :
name : www.skeleton-cms.test
site : http(s)://www.skeleton-cms.test
enabled : Tue, 10 Jul 2030 16:12:08 +0100 => Mon, 10 Jan 2022 16:12:08 +0100
Confirm site creation ?y
Site created !
You can now create the related pages and snapshots by running the followings commands:
php app/console sonata:page:update-core-routes --site=1
php app/console sonata:page:create-snapshots --site=1
Le premier site est maintenant opérationnel. Enfin presque.
Dans l’interface de page, il faut créer notre première page

La configuration par défaut ne contiens rien. Il faut ensuite organiser le contenu de cette page via un éditeur, qui permet d’organiser notre page sous forme de blocks, affectés à des zones de notre templates.
Dans notre configuration du bundle, nous avons déclaré un template par défaut. Et c’est cette configuration, qui nous permet de modéliser l’interface de gestion des blocks
default_template: default
templates:
default:
path: '@SonataPage/layout.html.twig'
name: 'default'
containers:
header:
name: Header
content_top:
name: Top content
content:
name: Main content
content_bottom:
name: Bottom content
footer:
name: Footer
matrix:
layout: |
HHHHHHHH
HHHHHHHH
TTTTTTTT
TTTTTTTT
CCCCCCCC
CCCCCCCC
BBBBBBBB
BBBBBBBB
FFFFFFFF
FFFFFFFF
mapping:
H: header
T: content_top
C: content
B: content_bottom
F: footer

Dans notre exemple plus haut, nous avons inséré 2 blocs dans notre zone.
Le premier est un texte simple, le second est un menu (un classique menu footer).
Pour insérer un block on se positionne sur une zone a gauche, et on click sur le bouton + à droite pour sélectionner le type de block après l’avoir sélectionné dans la liste.
Toute la gestion des pages se fait de cette manière.
Ce qui veux dire que nous allons devoir créer une série de blocks selon les besoins de notre application.
Et pour créer un bloc, c’est hyper simple. Nous allons prendre l’exemple de notre block de menu.
Les menus sont géré via le package Sonata-Menu-Bundle qui est compatible avec Sonata-Page-Bundle. Mais il n’est pas fournis avec un block par défaut. C’est con, parce qu’un menu, c’est un peu le truc basique.
Donc on va créer notre block dans /src/Block/SonataMenuBlockService.php
<?php
namespace App\Block;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Block\Service\AbstractAdminBlockService;
use Sonata\BlockBundle\Meta\Metadata;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\Form\Type\ImmutableArrayType;
use Sonata\PageBundle\Model\Page;
use Sonata\PageBundle\Model\PageManagerInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Sonata\AdminBundle\Admin\Pool;
use Sonata\BlockBundle\Block\BaseBlockService;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;
use Sonata\CoreBundle\Validator\ErrorElement;
class SonataMenuBlockService extends AbstractAdminBlockService
{
/**
* @var PageManagerInterface
*/
protected $pageManager;
/**
* @param string $name
*/
public function __construct($name, EngineInterface $templating, Pool $pool, EntityManager $em , PageManagerInterface $pageManager)
{
parent::__construct($name, $templating);
$this->pool = $pool;
$this->em = $em;
$this->pageManager = $pageManager;
}
public function getName()
{
return 'Partitech Menu Block';
}
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
$menu_list=$this->pool->getContainer()->get('doctrine')->getManager()->getRepository('Prodigious\Sonata\MenuBundle\Entity\Menu')->findAll();
if(!empty($menu_list))
{
$option_list=array();
foreach($menu_list as $m)
{
$option_list[$m->getAlias()]=$m->getName();
}
}
$formMapper->add('settings', ImmutableArrayType::class, [
'keys' => [
/* ['title', TextType::class, [
'label' => 'form.label_title',
'required' => false,
]],*/
['menu', ChoiceType::class, [
'label' => 'Menu',
'choices' => array_flip($option_list),
]],
['template', TextType::class, [
'label' => 'template',
'required' => false,
]],
],
'translation_domain' => 'SonataPageBundle',
]);
}
public function execute(BlockContextInterface $blockContext, ?Response $response = null)
{
$settings=$blockContext->getSettings();
$systemElements = $this->pageManager->findBy([
'url' => null,
'parent' => null,
]);
$menu_list=$this->pool->getContainer()->get('doctrine')->getManager()->getRepository('Prodigious\Sonata\MenuBundle\Entity\Menu')->findOneBy(['alias'=> $settings['menu']]);
if(!empty($menu_list)){
$menu=$this->pool->getContainer()->get('doctrine')->getManager()->getRepository('Prodigious\Sonata\MenuBundle\Entity\MenuItem');
$mm = $this->pool->getContainer()->get('prodigious_sonata_menu.manager');
$menu = $mm->load($menu_list->getId());
// $status = true (Get enabled menu items)
// $status = false (Get disabled menu items)
// getMenuItems($menu, $root = MenuManager::ITEM_CHILD, $status = MenuManager::STATUS_ALL)
$menuItems = $mm->getMenuItems($menu, true, true);
}else{
$menuItems=false;
}
;
return $this->renderResponse($blockContext->getTemplate(), [
'context' => $blockContext,
'block' => $blockContext->getBlock(),
'settings' => $blockContext->getSettings(),
'systemElements' => $systemElements,
'menuItems'=>$menuItems
], $response);
}
public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults([
'menu'=>'menu',
'template' => '/Block/Menu/menu.html.twig',
]);
}
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (null !== $code ? $code : $this->getName()), false, 'SonataPageBundle', [
'class' => 'fa fa-list-ul',
]);
}
}
On ajoute nos templates dans /Block/Menu/menu.html.twig et le fichier macro des menu dans /Block/Menu/menu_tree_macro.html.twig.
C’est ni plus ni moins que les fichiers par défaut du bundle entouré d’une div qui contient des informations du block pour l’édition en front.
{#/Block/Menu/menu.html.twig#}
{% if block.name|length %}
{% set name=block.name%}
{% else %}
{% set name=block.settings.menu %}
{% endif %}
<div id="cms-block-25" class="cms-block cms-block-element ui-sortable-handle" data-id="{{ block.id }}" data-name="{{ name }}" data-role="block" data-page-id="{{ block.page.id }}">
{% if menuItems is not null and menuItems|length > 0 %}
{% import 'Block/Menu/menu_tree_macro.html.twig' as tree %}
{% set currentPath = app.request.requestUri %}
{{ tree.menu(menuItems, currentPath) }}
{% endif %}
</div>
{#/Block/Menu/menu_tree_macro.html.twig#}
{% macro menu(items, currentPath) %}
{% import _self as self %}
<ul>
{% for menuItem in items %}
{% set url = menuItem.url %}
{% set attributes = "menu-item" %}
{% if menuItem.classAttribute %}
{% set attributes = attributes ~ ' ' ~ menuItem.classAttribute %}
{% endif %}
{% if menuItem.hasChild() %}
{% set attributes = attributes ~ ' has-child' %}
{% for childItem in menuItem.children %}
{% set childUrl = childItem.url %}
{% if childUrl == currentPath %}
{% set attributes = attributes ~ ' current-parent' %}
{% endif %}
{% endfor %}
{% endif %}
<li class="{{ attributes }}" role="menu-item">
{% if menuItem.hasChild() %}
<a href="{{ url }}" class="parent" {% if currentPath == url %} class="current"{% endif %}" {% if menuItem.target %} target="_blank"{% endif %}>{{ menuItem.name }}</a>
{{ self.menu(menuItem.children, currentPath) }}
{% else %}
<a href="{{ url }}" {% if currentPath == url %} class="current"{% endif %}" {% if menuItem.target %} target="_blank"{% endif %}>{{ menuItem.name }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
On référence notre block dans nos services
services:
sonata.block.service.sonata_menu:
class: App\Block\SonataMenuBlockService
arguments:
- "sonata.block.service.sonata_menu"
- "@templating"
- "@sonata.admin.pool"
- "@doctrine.orm.entity_manager"
- "@sonata.page.manager.page"
tags:
- { name: sonata.block }
Et nous avons ensuite notre nouveau block qui est disponible dans notre liste de blocks. On peux faire en gros tout ce que l’on veux dans un block, à la manière d’un controller.

On peu donc créer notre menu footer

Et l’utiliser dans note block

Voici le rendu de notre page

En fait, non. Ça c’est le rendu avec un template corrigé. Sinon le rendu ressemble a ceci :

On va devoir donc dupliquer le template de base dans notre projet et corriger ce qui ne vas pas.
On créé notre base_layout et notre template de test test-layout dans le répertoire Layouts/
{#/templates/Layouts/base_layout.html.twig#}
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{%- block sonata_page_html_tag -%}
<!DOCTYPE html>
<html {{ sonata_seo_html_attributes() }}>
{% endblock %}
{% block sonata_page_head %}
<head {{ sonata_seo_head_attributes() }}>
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
{{ sonata_seo_title() }}
{{ sonata_seo_metadatas() }}
{% block sonata_page_stylesheets %}
{% block page_stylesheets %} {# Deprecated block #}
{% for stylesheet in sonata_page.assets.stylesheets %}
<link rel="stylesheet" href="{{ asset(stylesheet) }}" media="all">
{% endfor %}
{% endblock %}
{% endblock %}
{% block sonata_page_javascripts %}
{% block page_javascripts %} {# Deprecated block #}
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
{% for js in sonata_page.assets.javascripts %}
<script src="{{ asset(js) }}"></script>
{% endfor %}
{% endblock %}
{% endblock %}
</head>
{% endblock %}
{% block sonata_page_body_tag %}
<body class="sonata-bc">
{% endblock %}
{% block sonata_page_top_bar %}
{% block page_top_bar %} {# Deprecated block #}
{% if sonata_page.isEditor or ( app.user and is_granted('ROLE_PREVIOUS_ADMIN') ) %}
{% if sonata_page.isEditor and sonata_page.isInlineEditionOn %}
<!-- CMS specific variables -->
<script>
jQuery(document).ready(function() {
Sonata.Page.init({
url: {
block_save_position: '{{ sonata_admin.objectUrl('sonata.page.admin.page', 'edit', page) }}',
block_edit: '{{ sonata_admin.url('sonata.page.admin.page|sonata.page.admin.block', 'edit', {'id': 'BLOCK_ID'}) }}'
}
});
});
</script>
{% endif %}
<header class="sonata-bc sonata-page-top-bar navbar navbar-inverse navbar-fixed-top" role="banner"
{% if sonata_page.isEditor and sonata_page.isInlineEditionOn %}
data-page-editor='{{ {
url: {
block_save_position: sonata_admin.objectUrl('sonata.page.admin.page', 'edit', page),
block_edit: sonata_admin.url('sonata.page.admin.page|sonata.page.admin.block', 'edit', {'id': 'BLOCK_ID'})
}
}|json_encode()|raw }}'
{% endif %}>
<div class="container">
<ul class="nav navbar-nav">
{% if app.user and is_granted('ROLE_SONATA_ADMIN') %}
<li><a href="{{ path('sonata_admin_dashboard') }}">{{ "header.sonata_admin_dashboard"|trans({}, 'SonataPageBundle') }}</a></li>
{% endif %}
{% if sonata_page.isEditor %}
{% set sites = sonata_page.siteavailables %}
{% if sites|length > 1 and site is defined %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ site.name }} <span class="caret"></span></a>
<ul class="dropdown-menu">
{% for site in sites %}
<li><a href="{{ site.url }}">{{ site.name }}</a></li>
{% endfor %}
</ul>
</li>
{% endif %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Page <span class="caret"></span></a>
<ul class="dropdown-menu">
{% if page is defined %}
<li><a href="{{ sonata_admin.objectUrl('sonata.page.admin.page', 'edit', page) }}" target="_new">{{ "header.edit_page"|trans({}, 'SonataPageBundle') }}</a></li>
<li><a href="{{ sonata_admin.objectUrl('sonata.page.admin.page|sonata.page.admin.snapshot', 'list', page) }}" target="_new">{{ "header.create_snapshot"|trans({}, 'SonataPageBundle') }}</a></li>
<li class="divider"></li>
{% endif %}
<li><a href="{{ sonata_admin.url('sonata.page.admin.page', 'list') }}" target="_new">{{ "header.view_all_pages"|trans({}, 'SonataPageBundle') }}</a></li>
{% if error_codes is defined and error_codes|length %}
<li class="divider"></li>
<li><a href="{{ path('sonata_page_exceptions_list') }}" target="_new">{{ "header.view_all_exceptions"|trans({}, 'SonataPageBundle') }}</a></li>
{% endif %}
</ul>
</li>
{% if page is defined %}
<li>
<a href="{{ sonata_admin.url('sonata.page.admin.page', 'compose', {'id': page.id}) }}">
<i class="fa fa-magic"></i>
{{ 'header.compose_page'|trans({}, 'SonataPageBundle')}}
</a>
</li>
{% endif %}
{% if page is defined and not page.enabled %}
<li><span style="padding-left: 20px; background: red;"><strong><em>{{ 'header.page_is_disabled'|trans([], 'SonataPageBundle') }}</em></strong></span></li>
{% endif %}
{% if sonata_page.isInlineEditionOn and page is defined %}
<li>
<form class="form-inline" style="margin: 0px">
<label class="checkbox inline" for="page-action-enabled-edit"><input type="checkbox" id="page-action-enabled-edit">{{ 'header.show_zone'|trans({}, 'SonataPageBundle') }}</label>
<input type="submit" class="btn" value="{{ 'btn_save_position'|trans({}, 'SonataPageBundle') }}" id="page-action-save-position">
</form>
</li>
{% endif %}
{% endif %}
{% if app.user and is_granted('ROLE_PREVIOUS_ADMIN') %}
<li><a href="{{ url('homepage', {'_switch_user': '_exit'}) }}">{{ "header.switch_user_exit"|trans({}, 'SonataPageBundle')}}</a></li>
{% endif %}
</ul>
</div>
</header>
{% endif %}
{% endblock %}
{% endblock %}
{% block sonata_page_container %}
{% block page_container %}{% endblock %} {# Deprecated block #}
{% endblock %}
{% block sonata_page_asset_footer %}
{% block page_asset_footer %} {# Deprecated block #}
{% if page is defined %}
{% if page.javascript is not empty %}
<script>
{{ page.javascript|raw }}
</script>
{% endif %}
{% if page.stylesheet is not empty %}
<style>
{{ page.stylesheet|raw }}
</style>
{% endif %}
{% endif %}
{#
These includes can be done only at this point as all blocks are loaded,
Limition : this does not work if a global page is loaded from an ESI tag inside a container block
#}
{{ sonata_block_include_stylesheets('screen', app.request.basePath) }}
{{ sonata_block_include_javascripts('screen', app.request.basePath) }}
{% endblock %}
{% endblock %}
<!-- monitoring:3e9fda56df2cdd3b039f189693ab7844fbb2d4f6 -->
</body>
</html>
et notre template
{#/templates/Layouts/test-layout.html.twig#}
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends '/Layouts/base_layout.html.twig' %}
{% block sonata_page_container %}
<div class="container">
<div class="content">
<div class="row page-header">
{{ sonata_page_render_container('header', 'global') }}
</div>
<!----> {% block sonata_page_breadcrumb %}
<div class="row">
{{ sonata_block_render_event('breadcrumb', { 'context': 'page', 'current_uri': app.request.requestUri }) }}
</div>
{% endblock %}
{% if page is defined %}
<div class="row">
{% if page.name != 'global'%}
{{ sonata_page_render_container('content_top', 'global') }}
{% endif %}
{{ sonata_page_render_container('content_top', page) }}
</div>
{% endif %}
<div class="row">
{% block page_content %}
{% if content is defined %}
{{ content|raw }}
{% else %}
{% set content = block('content') is defined ? block('content') : '' %}
{% if content|length > 0 %}
{{ content|raw }}
{% elseif page is defined %}
{{ sonata_page_render_container('content', page) }}
{% endif %}
{% endif %}
{% endblock %}
</div>
{% if page is defined %}
<div class="row">
{{ sonata_page_render_container('content_bottom', page) }}
{% if page.name != 'global'%}
{{ sonata_page_render_container('content_bottom', 'global') }}
{% endif %}
</div>
{% endif %}
</div>
<footer class="row">
{{ sonata_page_render_container('footer', 'global') }}
</footer>
</div>
{% endblock %}
On le référence ensuite dans le fichier de configuration du bundle
sonata_page:
test:
path: 'Layouts/test-layout.html.twig'
name: 'test'
containers:
header:
name: Header
content_top:
name: Top content
content:
name: Main content
content_bottom:
name: Bottom content
footer:
name: Footer
matrix:
layout: |
HHHHHHHH
HHHHHHHH
TTTTTTTT
TTTTTTTT
CCCCCCCC
CCCCCCCC
BBBBBBBB
BBBBBBBB
FFFFFFFF
FFFFFFFF
mapping:
H: header
T: content_top
C: content
B: content_bottom
F: footer
Notre édition en front est presque fonctionnelle.
Nous avons bien une barre de navigation dans notre page. Lorsque l’on active l’édition des zones, tout s’affiche correctement. Si on double clique sur une zone de bloc, une fenêtre s’affiche avec l’édition du block dans l’admin. Et lorsque l’on bouge les blocks d’une zone a une autre, ça les déplace bien. Mais si on regarde la page, rien n’est sauvegardé.
Nous allons devoir créer une méthode dédié de note coté pour sauver les paramètres.

Nous allons donc créer un contrôleur dans notre bundle étendu, pour gérer la mise à jour de la donnée.
On créé notre controller UpdatePositionController avec notre méthode sonata_page_update_position()
<?php
namespace App\Application\Sonata\PageBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Sonata\MediaBundle\Entity\MediaManager;
use Sonata\MediaBundle\Provider\FileProvider;
use Symfony\Component\HttpKernel\KernelInterface;
use Psr\Container\ContainerInterface;
class UpdatePositionController extends AbstractController
{
/**
* @Route("/sonata-page-update-position", methods={"GET","POST"}, name="sonata_page_update_position")
*
*
* @return array
*/
public function sonata_page_update_position(Request $request)
{
$keyword=$request->request->all();
if(!empty($keyword['disposition']))
{
foreach($keyword['disposition'] as $i=>$v)
{
$id=$keyword['disposition'][$i]['id'];
$position=$keyword['disposition'][$i]['position'];
$parent_id=$keyword['disposition'][$i]['parent_id'];
$page_id=$keyword['disposition'][$i]['page_id'];
$connection = $this->getDoctrine()->getConnection();
$statement = $connection->prepare("UPDATE `page__block` SET `parent_id` = :parent_id, `position` = :position, `page_id`=:page_id WHERE `page__block`.`id` = :id ");
$statement->bindValue('id', $id);
$statement->bindValue('page_id', $page_id);
$statement->bindValue('position', $page_id);
$statement->bindValue('parent_id', $parent_id);
$statement->execute();
}
}
return new JsonResponse( (object) [
'result' => 'ok'
]);
}
}
?>
Nous devons ensuite le référencer dans nos services :
services:
App\Application\Sonata\PageBundle\Controller\:
resource: '../src/Application/Sonata/PageBundle/Controller'
tags: ['controller.service_arguments']
On doit ensuite le référencer dans les annotations pour que la route soit automatiquement géré via Symfony :
#config/routes/annotations.yaml
controllers:
resource: ../../src/Controller/
type: annotation
controllers_sonata_pages:
resource: ../../src/Application/Sonata/PageBundle/Controller/
type: annotation
Et pour finir, nous devons modifier le lien de mise à jour de l’ordre des blocs dans notre template qui doit être : block_save_position: ‘{{ path(‘sonata_page_update_position’, {}) }}’,
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{%- block sonata_page_html_tag -%}
<!DOCTYPE html>
<html {{ sonata_seo_html_attributes() }}>
{% endblock %}
{% block sonata_page_head %}
<head {{ sonata_seo_head_attributes() }}>
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
{{ sonata_seo_title() }}
{{ sonata_seo_metadatas() }}
{% block sonata_page_stylesheets %}
{% block page_stylesheets %} {# Deprecated block #}
{% for stylesheet in sonata_page.assets.stylesheets %}
<link rel="stylesheet" href="{{ asset(stylesheet) }}" media="all">
{% endfor %}
{% endblock %}
{% endblock %}
{% block sonata_page_javascripts %}
{% block page_javascripts %} {# Deprecated block #}
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
{% for js in sonata_page.assets.javascripts %}
<script src="{{ asset(js) }}"></script>
{% endfor %}
<script src="/bundles/pixsortablebehavior/js/jquery-ui.min.js"></script>
{% endblock %}
{% endblock %}
</head>
{% endblock %}
{% block sonata_page_body_tag %}
<body class="sonata-bc">
{% endblock %}
{% block sonata_page_top_bar %}
{% block page_top_bar %} {# Deprecated block #}
{% if sonata_page.isEditor or ( app.user and is_granted('ROLE_PREVIOUS_ADMIN') ) %}
{% if sonata_page.isEditor and sonata_page.isInlineEditionOn %}
<!-- CMS specific variables -->
<script>
jQuery(document).ready(function() {
Sonata.Page.init({
url: {
block_save_position: '{{ path('sonata_page_update_position', {}) }}',
block_edit: '{{ sonata_admin.url('sonata.page.admin.page|sonata.page.admin.block', 'edit', {'id': page.id , 'childId': 'BLOCK_ID'}) }}'
}
});
});
</script>
{% endif %}
<header class="sonata-bc sonata-page-top-bar navbar navbar-inverse navbar-fixed-top" role="banner"
{% if sonata_page.isEditor and sonata_page.isInlineEditionOn %}
data-page-editor='{{ {
url: {
block_save_position: path('sonata_page_update_position', {}),
block_edit: sonata_admin.url('sonata.page.admin.page|sonata.page.admin.block', 'edit', {'id': page.id , 'childId': 'BLOCK_ID'})
}
}|json_encode()|raw }}'
{% endif %}>
<div class="container">
<ul class="nav navbar-nav">
{% if app.user and is_granted('ROLE_SONATA_ADMIN') %}
<li><a href="{{ path('sonata_admin_dashboard') }}">{{ "header.sonata_admin_dashboard"|trans({}, 'SonataPageBundle') }}</a></li>
{% endif %}
{% if sonata_page.isEditor %}
{% set sites = sonata_page.siteavailables %}
{% if sites|length > 1 and site is defined %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ site.name }} <span class="caret"></span></a>
<ul class="dropdown-menu">
{% for site in sites %}
<li><a href="{{ site.url }}">{{ site.name }}</a></li>
{% endfor %}
</ul>
</li>
{% endif %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Page <span class="caret"></span></a>
<ul class="dropdown-menu">
{% if page is defined %}
<li><a href="{{ sonata_admin.objectUrl('sonata.page.admin.page', 'edit', page) }}" target="_new">{{ "header.edit_page"|trans({}, 'SonataPageBundle') }}</a></li>
<li><a href="{{ sonata_admin.objectUrl('sonata.page.admin.page|sonata.page.admin.snapshot', 'list', page) }}" target="_new">{{ "header.create_snapshot"|trans({}, 'SonataPageBundle') }}</a></li>
<li class="divider"></li>
{% endif %}
<li><a href="{{ sonata_admin.url('sonata.page.admin.page', 'list') }}" target="_new">{{ "header.view_all_pages"|trans({}, 'SonataPageBundle') }}</a></li>
{% if error_codes is defined and error_codes|length %}
<li class="divider"></li>
<li><a href="{{ path('sonata_page_exceptions_list') }}" target="_new">{{ "header.view_all_exceptions"|trans({}, 'SonataPageBundle') }}</a></li>
{% endif %}
</ul>
</li>
{% if page is defined %}
<li>
<a href="{{ sonata_admin.url('sonata.page.admin.page', 'compose', {'id': page.id}) }}">
<i class="fa fa-magic"></i>
{{ 'header.compose_page'|trans({}, 'SonataPageBundle')}}
</a>
</li>
{% endif %}
{% if page is defined and not page.enabled %}
<li><span style="padding-left: 20px; background: red;"><strong><em>{{ 'header.page_is_disabled'|trans([], 'SonataPageBundle') }}</em></strong></span></li>
{% endif %}
{% if sonata_page.isInlineEditionOn and page is defined %}
<li>
<form class="form-inline" style="margin: 0px">
<label class="checkbox inline" for="page-action-enabled-edit"><input type="checkbox" id="page-action-enabled-edit">{{ 'header.show_zone'|trans({}, 'SonataPageBundle') }}</label>
<input type="submit" class="btn" value="{{ 'btn_save_position'|trans({}, 'SonataPageBundle') }}" id="page-action-save-position">
</form>
</li>
{% endif %}
{% endif %}
{% if app.user and is_granted('ROLE_PREVIOUS_ADMIN') %}
<li><a href="{{ url('homepage', {'_switch_user': '_exit'}) }}">{{ "header.switch_user_exit"|trans({}, 'SonataPageBundle')}}</a></li>
{% endif %}
</ul>
</div>
</header>
{% endif %}
{% endblock %}
{% endblock %}
{% block sonata_page_container %}
{% block page_container %}{% endblock %} {# Deprecated block #}
{% endblock %}
{% block sonata_page_asset_footer %}
{% block page_asset_footer %} {# Deprecated block #}
{% if page is defined %}
{% if page.javascript is not empty %}
<script>
{{ page.javascript|raw }}
</script>
{% endif %}
{% if page.stylesheet is not empty %}
<style>
{{ page.stylesheet|raw }}
</style>
{% endif %}
{% endif %}
{#
These includes can be done only at this point as all blocks are loaded,
Limition : this does not work if a global page is loaded from an ESI tag inside a container block
#}
{{ sonata_block_include_stylesheets('screen', app.request.basePath) }}
{{ sonata_block_include_javascripts('screen', app.request.basePath) }}
{% endblock %}
{% endblock %}
<!-- monitoring:3e9fda56df2cdd3b039f189693ab7844fbb2d4f6 -->
</body>
</html>
Voici le résultat :