Skip to content

Extending eZ Platform UI

Back-end interface

The back-end interface is produced by the ezplatform-admin-ui Bundle together with ezplatform-admin-ui-modules, which contains React modules that handle specific parts of the application. This interface is accessible in your browser at http://[uri_of_platform]/admin.

General extensibility

eZ Platform's back-end interface uses React-based modules that make each part of the UI easily extensible. The interface uses Bootstrap, which facilitates adapting and styling the interface to your needs.

Available extensibility points:

Menus in eZ Platform are based on the KnpMenuBundle and are easily extensible. For a general idea on how to use MenuBuilder, refer to the official documentation.

Menus are extensible using event subscribers/listeners. You can hook into the following events:

  • ConfigureMenuEvent::MAIN_MENU
  • ConfigureMenuEvent::USER_MENU
  • ConfigureMenuEvent::CONTENT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::CONTENT_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::CONTENT_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::CONTENT_SIDEBAR_LEFT
  • ConfigureMenuEvent::TRASH_SIDEBAR_RIGHT
  • ConfigureMenuEvent::SECTION_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::SECTION_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::POLICY_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::POLICY_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::ROLE_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::ROLE_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::USER_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::USER_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::ROLE_ASSIGNMENT_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::LANGUAGE_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::LANGUAGE_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::CONTENT_TYPE_GROUP_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::CONTENT_TYPE_GROUP_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::CONTENT_TYPE_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::CONTENT_TYPE_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::URL_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::USER_PASSWORD_CHANGE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::OBJECT_STATE_GROUP_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::OBJECT_STATE_GROUP_EDIT_SIDEBAR_RIGHT
  • ConfigureMenuEvent::OBJECT_STATE_CREATE_SIDEBAR_RIGHT
  • ConfigureMenuEvent::OBJECT_STATE_EDIT_SIDEBAR_RIGHT

An event subscriber can be implemented as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
namespace EzSystems\EzPlatformAdminUi\EventListener;

use EzSystems\EzPlatformAdminUi\Menu\Event\ConfigureMenuEvent;
use EzSystems\EzPlatformAdminUi\Menu\MainMenuBuilder;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class MenuListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            ConfigureMenuEvent::MAIN_MENU => ['onMenuConfigure', 0],
        ];
    }

    public function onMenuConfigure(ConfigureMenuEvent $event)
    {
        $menu = $event->getMenu();
        $factory = $event->getFactory();
        $options = $event->getOptions(); // options passed from the context (i.e. Content item in Content View)

        // your customizations
    }
}

Extending menu examples

Add a new menu item under "Content" with custom attributes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$menu[MainMenuBuilder::ITEM_CONTENT]->addChild(
    'form_manager',
    [
        'route' => '_ezpublishLocation',
        'routeParameters' => ['locationId' => 2],
        'linkAttributes' => [ // attributes directly on <a> element
            'class' => 'test_class another_class',
            'data-property' => 'value',
        ],
        'attributes' => [ // attributes on container <li> element
            'data-property' => 'value',
        ],
    ]
);

Remove the "Media" menu item from the Content tab

1
2
3
$menu[MainMenuBuilder::ITEM_CONTENT]->removeChild(
    MainMenuBuilder::ITEM_CONTENT__MEDIA
);

Add a top-level menu item with a child

1
2
3
4
5
6
7
8
$menu->addChild(
    'menu_item_1',
    ['label' => 'Menu Item 1', 'extras' => ['icon' => 'file']]
);
$menu['menu_item_1']->addChild(
    '2nd_level_menu_item',
    ['label' => '2nd level menu item', 'uri' => 'http://example.com']
);

Add an item depending on a condition

1
2
3
4
5
6
7
$condition = true;
if ($condition) {
    $menu->addChild(
        'menu_item_2',
        ['label' => 'Menu Item 2', 'extras' => ['icon' => 'article']]
    );
}

Add a top-level menu item with URL redirection

1
2
3
4
5
6
7
8
$menu->addChild(
    'menu_item_3',
    [
        'label' => 'Menu Item 3',
        'uri' => 'http://example.com',
        'extras' => ['icon' => 'article'],
    ]
);

Translatable labels

To have translatable labels, use translation.key from the messages domain:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$menu->addChild(
    'menu_item_3',
    [
        'label' => 'translation.key',
        'uri' => 'http://example.com',
        'extras' => [
            'icon' => 'article',
            'translation_domain' => 'messages',
        ],
    ]
);

Reorder menu items, i.e. reverse the order

1
2
3
$menu->reorderChildren(
    array_reverse(array_keys($menu->getChildren()))
);

Dashboard

To extend the Dashboard, make use of an event subscriber.

In the following example, the DasboardEventSubscriber.php reverses the order of sections of the Dashboard (in a default installation this makes the "Everyone" block appear above the "Me" block):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace AppBundle\EventListener;

use EzSystems\EzPlatformAdminUi\Component\Event\RenderGroupEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class DashboardEventSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            RenderGroupEvent::NAME => ['onRenderGroupEvent', 20],
        ];
    }

    public function onRenderGroupEvent(RenderGroupEvent $event)
    {
        if ($event->getGroupName() !== 'dashboard-blocks') {
            return;
        }

        $components = $event->getComponents();

        $reverseOrder = array_reverse($components);

        $event->setComponents($reverseOrder);

    }
}

Tabs

Many elements of the back-office interface, such as System Info or Location View, are built using tabs.

Tabs in System Information

You can extend existing tab groups with new tabs, or create your own tab groups.

Adding a new tab group

To create a custom tab group with additional logic you need to create it at the level of compiling the Symfony container, using a CompilerPass:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/AppBundle/DependencyInjection/Compiler/CustomTabGroupPass.php

namespace AppBundle\DependencyInjection\Compiler;

use EzSystems\EzPlatformAdminUi\Tab\TabGroup;
use EzSystems\EzPlatformAdminUi\Tab\TabRegistry;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

class CustomTabGroupPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition(TabRegistry::class)) {
            return;
        }

        $tabRegistry = $container->getDefinition(TabRegistry::class);
        $tabGroupDefinition = new Definition(
            TabGroup::class,  // or any class that extends TabGroup
            ['custom-tab-group']
        );
        $tabRegistry->addMethodCall('addTabGroup', [$tabGroupDefinition]);
    }
}

A shorter way that you can use when no custom logic is required, is to add your own tab with the new group. If a tab's group is not defined, it will be created automatically.

A new tab group can be rendered using the ez_platform_tabs Twig helper:

1
2
3
<div class="my-tabs">
    {{ ez_platform_tabs('custom-tab-group') }}
</div>

This will render the group and all tabs assigned to it.

Adding a new tab

Before you add a tab to a group you must create the tab's PHP class and define it as a Symfony service with the ezplatform.tab tag:

1
2
3
4
5
6
# services.yml

AppBundle\Custom\Tab:
    parent: EzSystems\EzPlatformAdminUi\Tab\AbstractTab
    tags:
        - { name: ezplatform.tab, group: 'custom-tab-group' }

This configuration also assigns the new tab to custom-tab-group.

Tip

If the custom-tab-group does not yet exist, it will be created automatically at this point.

The tab class can look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/AppBundle/Custom/Tab.php

namespace AppBundle\Custom;

use EzSystems\EzPlatformAdminUi\Tab\AbstractTab;

class Tab extends AbstractTab
{
    public function getIdentifier(): string
    {
        return 'custom-tab';
    }

    public function getName(): string
    {
        return /** @Desc("Custom Tab") */
            $this->translator->trans('custom.tab.name', [], 'some_translation_domain');
    }

    public function renderView(array $parameters): string
    {
        // Do rendering here

        return $this->twig->render('AppBundle:my_tabs/custom.html.twig', [
            'foo' => 'Bar!',
        ]);
    }
}

Beyond the AbstractTab used in the example above you can use two specializes tab types:

  • AbstractControllerBasedTab enables you to embed the results of a controller action in the tab.
  • AbstractRouteBasedTab embeds the results of the selected routing, passing applicable parameters.

Modifying tab display

You can order the tabs by making the tab implement OrderedTabInterface. The order will then depend on the numerical value returned by the getOrder method.

1
2
3
4
5
6
7
class Tab extends AbstractTab implements OrderedTabInterface
{
    public function getOrder(): int
    {
        return 100;
    }
}

The tabs will be displayed according to this value in ascending order.

Tip

It is good practice to reserve some distance between these values, for example to stagger them by step of 10. It may come useful if you later need to place something between the existing tabs.

You can also influence tab display (e.g. order tabs, remove or modify them, etc.) by using Event Listeners:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class TabEvents
{
    /**
     * Happens just before rendering a tab group.
     */
    const TAB_GROUP_PRE_RENDER = 'ezplatform.tab.group.pre_render';

    /**
     * Happens just before rendering a tab.
     */
    const TAB_PRE_RENDER = 'ezplatform.tab.pre_render';
}

As an example, see how OrderedTabInterface is implemented:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
namespace EzSystems\EzPlatformAdminUi\Tab\Event\Subscriber;

use EzSystems\EzPlatformAdminUi\Tab\Event\TabEvents;
use EzSystems\EzPlatformAdminUi\Tab\Event\TabGroupEvent;
use EzSystems\EzPlatformAdminUi\Tab\OrderedTabInterface;
use EzSystems\EzPlatformAdminUi\Tab\TabInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Reorders tabs according to their Order value (Tabs implementing OrderedTabInterface).
 * Tabs without order specified are pushed to the end of the group.
 *
 * @see OrderedTabInterface
 */
class OrderedTabSubscriber implements EventSubscriberInterface
{
    /**
     * @return array
     */
    public static function getSubscribedEvents(): array
    {
        return [
            TabEvents::TAB_GROUP_PRE_RENDER => ['onTabGroupPreRender'],
        ];
    }

    /**
     * @param TabGroupEvent $tabGroupEvent
     */
    public function onTabGroupPreRender(TabGroupEvent $tabGroupEvent)
    {
        $tabGroup = $tabGroupEvent->getData();
        $tabs = $tabGroup->getTabs();

        $tabs = $this->reorderTabs($tabs);

        $tabGroup->setTabs($tabs);
        $tabGroupEvent->setData($tabGroup);
    }

    /**
     * @param TabInterface[] $tabs
     *
     * @return array
     */
    private function reorderTabs($tabs): array
    {
        $orderedTabs = [];
        foreach ($tabs as $tab) {
            if ($tab instanceof OrderedTabInterface) {
                $orderedTabs[$tab->getIdentifier()] = $tab;
                unset($tabs[$tab->getIdentifier()]);
            }
        }

        uasort($orderedTabs, [$this, 'sortTabs']);

        return array_merge($orderedTabs, $tabs);
    }

    /**
     * @param OrderedTabInterface $tab1
     * @param OrderedTabInterface $tab2
     *
     * @return int
     */
    private function sortTabs(OrderedTabInterface $tab1, OrderedTabInterface $tab2): int
    {
        return $tab1->getOrder() <=> $tab2->getOrder();
    }
}

Further extensibility using Components

Components enable you to inject widgets (e.g. Dashboard blocks) and HTML code (e.g. a tag for loading JS or CSS files) into specific places in the Back Office.

A component is any class that implements the Renderable interface. It must be tagged as a service:

1
2
3
AppBundle\Component\MyNewComponent:
    tags:
        - { name: ezplatform.admin_ui.component, group: 'content-edit-form-before' }

group indicates where the widget will be displayed. The available groups are:

If you do not want to write your own class and only wish to inject a short element (e.g. render a Twig template or add a CSS link), you can make use of pre-existing base classes. In this case all you have to do is add a service definition (with proper parameters), for example:

1
2
3
4
5
6
7
8
9
appbundle.components.my_twig_component:
    parent: EzSystems\EzPlatformAdminUi\Component\TwigComponent
    arguments:
        $template: 'path/to/file.html.twig'
        $parameters:
            first_param: 'first_value'
            second_param: 'second_value'
    tags:
        - { name: ezplatform.admin_ui.component, group: 'dashboard-blocks' }

This renders the path/to/file.html.twig template with first_param and second_param as parameters.

There are three such base components:

  • TwigComponent renders a Twig template, like above
  • LinkComponent renders the HTML <link> tag:
1
2
3
4
5
6
appbundle.components.my_link_component:
   parent: EzSystems\EzPlatformAdminUi\Component\LinkComponent
   arguments:
       $href: 'http://address.of/file.css'
   tags:
       - { name: ezplatform.admin_ui.component, group: 'stylesheet-head' }
  • ScriptComponent renders the HTML <script> tag:
1
2
3
4
5
6
appbundle.components.my_script_component:
   parent: EzSystems\EzPlatformAdminUi\Component\ScriptComponent
   arguments:
       $src: 'http://address.of/file.js'
   tags:
       - { name: ezplatform.admin_ui.component, group: 'script-body' }
Read the Docs