Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
integrate knpMenu into sonata Admin
  • Loading branch information
Amine Zaghdoudi committed Feb 18, 2015
1 parent 41ecba8 commit df42796
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 48 deletions.
51 changes: 51 additions & 0 deletions Admin/Pool.php
Expand Up @@ -13,6 +13,9 @@

use Symfony\Component\DependencyInjection\ContainerInterface;

use Knp\Menu\MenuFactory;
use Knp\Menu\ItemInterface;

class Pool
{
protected $container = null;
Expand Down Expand Up @@ -330,4 +333,52 @@ public function getOption($name, $default = null)

return $default;
}

/**
* Create and return a knp MenuItem
*
* @return ItemInterface
*/
public function getMenu()
{
$menuFactory = new MenuFactory();
$menu = $menuFactory
->createItem('root')
->setExtra('request', $this->container->get('request'))
;

foreach ($this->getAdminGroups() as $name => $group) {
$menu
->addChild($name, array('label' => $group['label']))
->setAttributes(
array(
'icon' => $group['icon']
)
)
->setExtra('roles', $group['roles'])
;

foreach ($group['items'] as $item) {
if (array_key_exists('admin', $item) && $item['admin'] != null) {
$admin = $this->getInstance($item['admin']);
$label = $admin->getLabel();
$route = $admin->generateUrl('list');
$translationDomain = $admin->getTranslationDomain();
} else {
$label = $item['label'];
$route = $this->container->get('router')->generate($item['route'], $item['route_params']);
$translationDomain = null;
$admin = null;
}

$menu[$name]
->addChild($label, array('uri' => $route))
->setExtra('translationdomain', $translationDomain)
->setExtra('admin', $admin)
;
}
}

return $menu;
}
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,9 @@
CHANGELOG
=========

## 2015-02-18
* [BC BREAK] Integration of KNPMenu for the admin menu. This integration is resetted when the standard layout ``standard_layout.html.twig`` is overrided. The KNPMenu is available in ``sonata_menu.html.twig`` template.

### 2015-02-15
* [BC BREAK] added ``getFieldOption``, ``setFieldOption`` methods to the FilterInterface
* [BC BREAK] added the ``getFilterFieldDescription`` method to the AdminInterface
Expand Down
Expand Up @@ -336,6 +336,7 @@ public function fixTemplates(ContainerBuilder $container, Definition $definition
'pager_links' => 'SonataAdminBundle:Pager:links.html.twig',
'pager_results' => 'SonataAdminBundle:Pager:results.html.twig',
'tab_menu_template' => 'SonataAdminBundle:Core:tab_menu_template.html.twig',
'knp_menu_template' => 'SonataAdminBundle:Menu:sonata_menu.html.twig',
'outer_list_rows_mosaic' => 'SonataAdminBundle:CRUD:list_outer_rows_mosaic.html.twig',
'outer_list_rows_list' => 'SonataAdminBundle:CRUD:list_outer_rows_list.html.twig',
'outer_list_rows_tree' => 'SonataAdminBundle:CRUD:list_outer_rows_tree.html.twig',
Expand Down
39 changes: 38 additions & 1 deletion DependencyInjection/Configuration.php
Expand Up @@ -103,7 +103,43 @@ public function getConfigTreeBuilder()
->scalarNode('label_catalogue')->end()
->scalarNode('icon')->defaultValue('<i class="fa fa-folder"></i>')->end()
->arrayNode('items')
->prototype('scalar')->end()
->beforeNormalization()
->ifArray()
->then(function($items) {
foreach ($items as $key => $item) {
if (is_array($item)) {
if (!array_key_exists('label', $item) || !array_key_exists('route', $item)) {
throw new \InvalidArgumentException('Expected either parameters "route" and "label" for array items');
}

if (!array_key_exists('route_params', $item)){
$items[$key]['route_params'] = array();
}

$items[$key]['admin'] = '';
} else {
$items[$key] = array(
'admin' => $item,
'label' => '',
'route' => '',
'route_params' => array()
);
}
}

return $items;
})
->end()
->prototype('array')
->children()
->scalarNode('admin')->end()
->scalarNode('label')->end()
->scalarNode('route')->end()
->arrayNode('route_params')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->arrayNode('item_adds')
->prototype('scalar')->end()
Expand Down Expand Up @@ -211,6 +247,7 @@ public function getConfigTreeBuilder()
->scalarNode('pager_links')->defaultValue('SonataAdminBundle:Pager:links.html.twig')->cannotBeEmpty()->end()
->scalarNode('pager_results')->defaultValue('SonataAdminBundle:Pager:results.html.twig')->cannotBeEmpty()->end()
->scalarNode('tab_menu_template')->defaultValue('SonataAdminBundle:Core:tab_menu_template.html.twig')->cannotBeEmpty()->end()
->scalarNode('knp_menu_template')->defaultValue('SonataAdminBundle:Menu:sonata_menu.html.twig')->cannotBeEmpty()->end()
->end()
->end()

Expand Down
1 change: 1 addition & 0 deletions Resources/doc/index.rst
Expand Up @@ -25,6 +25,7 @@ Reference Guide
reference/dashboard
reference/search
reference/select2
reference/knp_menu
reference/routing
reference/action_list
reference/action_create_edit
Expand Down
62 changes: 62 additions & 0 deletions Resources/doc/reference/knp_menu.rst
@@ -0,0 +1,62 @@
KnpMenu
=======

The admin comes with `KnpMenu <https://github.com/KnpLabs/KnpMenu>`_ integration
It integrates a menu with the KnpMenu library. This menu can be a SonataAdmin service or a route of a custom controller.

Add a custom controller entry in the menu
-----------------------------------------

To add a custom controller entry in the admin menu:

Create your controller

.. code-block:: php
/**
* @Route("/blog", name="blog_home")
*/
public function blogAction()
{
// ...
}
/**
* @Route("/blog/article/{articleId}", name="blog_article")
*/
public function ArticleAction($articleId)
{
// ...
}
Add the controller route as an item of the menu

.. code-block:: yaml
# Default configuration for "SonataAdminBundle"
sonata_admin:
dashboard:
groups:
news:
label: ~
label_catalogue: ~
items:
- sonata.news.admin.post
- route: blog_home
label: Blog
- route: blog_article
route_params: { articleId: 3 }
label: Article
...
Also you can override the template of knp_menu used by sonata. The default one is `SonataAdminBundle:Menu:sonata_menu.html.twig`:

.. code-block:: yaml
# Default configuration for "SonataAdminBundle"
sonata_admin:
templates:
knp_menu_template: ApplicationAdminBundle:Menu:custom_knp_menu.html.twig
...
And voilà, now you have a new menu group which contains an entry to sonata_admin_id, to your blog and to a specific article.
69 changes: 69 additions & 0 deletions Resources/views/Menu/sonata_menu.html.twig
@@ -0,0 +1,69 @@
{% extends 'knp_menu.html.twig' %}

{% block root %}
{%- set listAttributes = item.childrenAttributes|merge({'class': 'sidebar-menu'}) %}
{%- set request = item.getExtra('request') %}
{{ block('list') -}}
{% endblock %}

{% block item %}
{%- if item.displayed %}
{#- check role of the group #}
{%- set display = (item.getExtra('roles') is empty or is_granted('ROLE_SUPER_ADMIN') ) %}
{%- for role in item.getExtra('roles') if not display %}
{%- set display = is_granted(role) %}
{%- endfor %}
{%- endif %}

{%- if item.displayed and display|default %}
{%- set active = false %}
{%- if item.getExtra('admin') is not empty and item.getExtra('admin').hasroute('list') and item.getExtra('admin').isGranted('LIST') and request.get('_sonata_admin') == item.getExtra('admin').code %}
{%- set active = true %}
{%- elseif item.route is defined and request.get('_route') == item.route %}
{%- set active = true %}
{%- else %}
{%- for child in item.children if not active %}
{%- if child.getExtra('admin') is not empty and child.getExtra('admin').hasroute('list') and child.getExtra('admin').isGranted('LIST') and request.get('_sonata_admin') == child.getExtra('admin').code %}
{%- set active = true %}
{%- elseif child.route is defined and request.get('_route') == child.route %}
{%- set active = true %}
{%- endif %}
{%- endfor %}
{%- endif %}

{%- if item.level == 1%}
{%- do item.setAttribute('class', (item.attribute('class')~' treeview')|trim) %}
{%- endif %}
{%- if active %}
{%- do item.setAttribute('class', (item.attribute('class')~' active')|trim) %}
{%- do item.setChildrenAttribute('class', (item.childrenAttribute('class')~' active')|trim) %}
{%- endif %}

{%- do item.setChildrenAttribute('class', (item.childrenAttribute('class')~' treeview-menu')|trim) %}
{{ parent() }}
{% endif %}
{% endblock %}

{% block linkElement %}
{% spaceless %}
{% set translation_domain = item.getExtra('translationdomain', 'messages') %}
{% set icon = item.level > 1 ? '<i class="fa fa-angle-double-right"></i>' : '' %}
{% set is_link = true %}
{{ parent() }}
{% endspaceless %}
{% endblock %}

{% block spanElement %}
{% spaceless %}

<a href="#">
{% set translation_domain = item.attribute('label_catalogue') %}
{% set icon = item.attribute('icon')|default ? item.attribute('icon') : '' %}
{{ icon|default|raw }}
{{ parent() }}
<i class="fa pull-right fa-angle-left"></i>
</a>
{% endspaceless %}
{% endblock %}

{% block label %}{% if is_link is defined and is_link %}{{ icon|default|raw }}{% endif %}{% if options.allow_safe_labels and item.getExtra('safe_label', false) %}{{ item.label|raw }}{% else %}{{ item.label|trans({}, translation_domain|default('messages')) }}{% endif %}{% endblock %}
42 changes: 1 addition & 41 deletions Resources/views/standard_layout.html.twig
Expand Up @@ -210,47 +210,7 @@ file that was distributed with this source code.
{% block side_bar_before_nav %} {% endblock %}
{% block side_bar_nav %}
{% if app.security.token and is_granted('ROLE_SONATA_ADMIN') %}
<ul class="sidebar-menu">
{% for group in admin_pool.dashboardgroups %}
{% set display = (group.roles is empty or is_granted('ROLE_SUPER_ADMIN') ) %}
{% for role in group.roles if not display %}
{% set display = is_granted(role) %}
{% endfor %}

{# Do not display the group label if no item in group is available #}
{% set item_count = 0 %}
{% if display %}
{% for admin in group.items if item_count == 0 %}
{% if admin.hasroute('list') and admin.isGranted('LIST') %}
{% set item_count = item_count+1 %}
{% endif %}
{% endfor %}
{% endif %}

{% if display and (item_count > 0) %}
{% set active = false %}
{% for admin in group.items %}
{% if admin.hasroute('list') and admin.isGranted('LIST') and app.request.get('_sonata_admin') == admin.code %}
{% set active = true %}
{% endif %}
{% endfor %}
<li class="treeview{% if active %} active{% endif %}">
<a href="#">
{% if group.icon|default() %}{{ group.icon|raw }}{% endif %}
<span>{{ group.label|trans({}, group.label_catalogue) }}</span>
<i class="fa pull-right fa-angle-left"></i>
</a>
<ul class="treeview-menu{% if active %} active{% endif %}">
{% for admin in group.items %}
{% if admin.hasroute('list') and admin.isGranted('LIST') %}
<li{% if app.request.get('_sonata_admin') == admin.code %} class="active"{% endif %}><a href="{{ admin.generateUrl('list')}}"><i class="fa fa-angle-double-right"></i> {{ admin.label|trans({}, admin.translationdomain) }}</a></li>
{% endif %}
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
{{ knp_menu_render(admin_pool.menu, {'template' : admin_pool.getTemplate('knp_menu_template')}) }}
{% endif %}
{% endblock side_bar_nav %}
{% block side_bar_after_nav %}
Expand Down
46 changes: 46 additions & 0 deletions Tests/Admin/PoolTest.php
Expand Up @@ -254,6 +254,52 @@ public function testOptionDefault()
$this->assertEquals(array(), $this->pool->getOption('nonexistantarray', array()));
}

public function testGetMenu()
{
$containerMock = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
$routerMock = $this->getMock('Symfony\Component\Routing\RouterInterface');

$containerMock->expects($this->any())
->method('get')
->will($this->returnValueMap(
array(
array('router', 1, $routerMock),
array('request', 1, null)
)
));

$pool = new Pool($containerMock, 'Sonata Admin', '/path/to/pic.png', array('foo'=>'bar'));

$adminGroups = array(
"bar" => array(
"label" => "foo",
"icon" => '<i class="fa fa-edit"></i>',
"items" => array(
array(
"admin" => "",
"label" => "fooLabel",
"route" => "FooRoute",
"route_params" => array("foo" => "bar"),
)
),
"item_adds" => array(),
"roles" => array()

)
);
$pool->setAdminGroups($adminGroups);
$menu = $pool->getMenu();

$this->assertInstanceOf('Knp\Menu\ItemInterface', $menu);
$this->assertArrayHasKey('bar', $menu->getChildren());

foreach ($menu->getChildren() as $key => $child) {
$this->assertInstanceOf('Knp\Menu\MenuItem', $child);
$this->assertEquals("bar", $child->getName());
$this->assertEquals($adminGroups["bar"]["label"], $child->getLabel());
}
}

/**
* @return Symfony\Component\DependencyInjection\ContainerInterface - the mock of container interface
*/
Expand Down

0 comments on commit df42796

Please sign in to comment.