Notice: You are browsing the documentation for PrestaShop 9, which is currently in development.
You might want to read the documentation for the current version, PrestaShop 8. Read the current version of this page
On the PrestaShop back office, the links on the side menu are linked to AdminControllers and ModuleAdminController classes. The first ones come from the PrestaShop Core, but the second ones are defined by the modules. If you want to add a link to your ModuleAdminControllers in the back office sidebar, this guide is for you.
In order to register new links, open your main module class.
We will now use a property called $tabs
, storing an array of link details. Each of them contains a class (= link) to add in the side menu.
Depending on the options you provide, your links won’t be displayed the same way:
Property | Required | Type | Description | Example |
---|---|---|---|---|
class_name |
Yes | String | Class called when the user will click on your link. This is the class name without the Controller part. |
"AdminGamification" |
route_name |
No | String | 1.7.7 Symfony route name, if your controller is Symfony-based | "gamification_configuration" |
name |
No | String|String[] | Label displayed in the menu. If not provided, the class name is shown instead. | "Merchant Expertise" |
parent_class_name |
No | String | The parent menu, if you want to display it in a subcategory. Go farther in this document to see available values. | |
icon |
No | String | Icon name to use, if any | "shopping_basket" |
visible |
No | Boolean | Whether you want to display the tab or not. Hidden tabs are used when you don’t need a menu item but you still need to handle access rights. | true |
wording |
No | String | 1.7.8 The translation key to use to translate the menu label. | "Merchant Expertise" |
wording_domain |
No | String | 1.7.8 The translation domain to use to translate the menu label. | "Modules.Gamification.Admin" |
By default, your tab will be displayed in the menu with its class name. If you want to use a different label, you can set the name
property. This label can be translated in two ways.
If you want to add the same name to all available and active languages available on the shop, just set the ‘name’ key with a single string:
<?php
class mymodule extends Module
{
public $tabs = [
[
'name' => 'Merchant Expertise', // One name for all langs
'class_name' => 'AdminGamification',
'visible' => true,
'parent_class_name' => 'ShopParameters',
],
];
// ...
}
Since
1.7.8
you can declare a wording
and wording_domain
to leverage the translation system. This will allow the menu to be translated automatically using either the provided translation catalogues or the translation interface.
<?php
class mymodule extends Module
{
public $tabs = [
[
'name' => 'Merchant Expertise', // Fallback when the translation is unavailable
'class_name' => 'AdminGamification',
'parent_class_name' => 'ShopParameters',
'wording' => 'Merchant Expertise', // Translation key
'wording_domain' => 'Modules.Gamification.Admin', // Translation domain
],
];
// ...
}
In previous PrestaShop versions, wording
and wording_domain
will be ignored. In that case, you will have to provide translations for every language upfront.
You can add your translations per locale (ex.: fr-FR
) or per language (ex.: fr
) – both are valid.
<?php
class mymodule extends Module
{
public $tabs = [
[
'name' => [
'en' => 'Merchant Expertise', // Fallback value
'fr' => 'Expertise PrestaShop',
...
],
'class_name' => 'AdminGamification',
'parent_class_name' => 'ShopParameters',
'wording' => 'Merchant Expertise', // Ignored in PS < 1.7.8
'wording_domain' => 'Modules.Gamification.Admin', // Ignored in PS < 1.7.8
],
];
// ...
}
Here is the default structure of the side-menu from PrestaShop at the moment this page is written. You can choose an element from this list to use as a parent.
Once you’re done, just install (or reset) your module.
The $tabs
property will be read from PrestaShop and the tabs will be automatically displayed on the side menu. They will stay as long as your module is installed.
When you create a new Tab
it automatically creates the appropriate roles in Tab::initAccess
based on the class_name
. For example using AdminLinkWidget
as the class name will create the following roles:
ROLE_MOD_TAB_ADMINLINKWIDGET_CREATE
ROLE_MOD_TAB_ADMINLINKWIDGET_DELETE
ROLE_MOD_TAB_ADMINLINKWIDGET_READ
ROLE_MOD_TAB_ADMINLINKWIDGET_UPDATE
These roles will allow you to manage detailed permission in your controllers, you can read this documentation if you need more details about Controller Security.
They are automatically added to the SUPER_ADMIN
group, and the group of the Employee installing the module, but you can then edit privileges for other Employee groups.
Hidden Tabs
Tabs are usually visible and accessible in the menu, but there are also invisible tabs, they are only created for permissions to manage Security. All the controllers present in controllers/admin
in your module are automatically added as hidden Tabs (if no visible Tab exists).
When you disable a module, all its related Tabs will be automatically hidden from the Back Office menu.
Tabs are kept in database with their enabled
field is set to false
. Once the module is enabled again all its Tabs are automatically enabled as well.
If you created a modern controller using Symfony controllers and routing you can’t create a Tab as is because the system is
based on legacy controllers identified through their class names. But you can still trick it using the _legacy_link
property
in the routing (more details about this feature in the Controller and Routing page).
Let’s assume you already defined your Symfony route:
# modules/your-module/config/routes.yml
your_route_name:
path: your-module/demo
methods: [GET]
defaults:
_controller: 'MyModule\Controller\DemoController::demoAction'
What you need to do then is add the _legacy_controller
and _legacy_link
parameters:
# modules/your-module/config/routes.yml
your_route_name:
path: your-module/demo
methods: [GET]
defaults:
_controller: 'MyModule\Controller\DemoController::demoAction'
_legacy_controller: 'MyModuleDemoController'
_legacy_link: 'MyModuleDemoController'
So now any call in the menu system to Link::getAdminLink('MyModuleDemoController')'
will return your controller url your-module/demo
But since the MyModuleDemoController
class actually doesn’t exist, the automatic tab registration based on the $tabs
property won’t work.
So you need to insert your tab manually during your module installation:
<?php
use Language;
class Example_module_mailtheme extends Module
{
public function install()
{
return parent::install()
&& $this->installTab()
;
}
public function uninstall()
{
return parent::uninstall()
&& $this->uninstallTab()
;
}
public function enable($force_all = false)
{
return parent::enable($force_all)
&& $this->installTab()
;
}
public function disable($force_all = false)
{
return parent::disable($force_all)
&& $this->uninstallTab()
;
}
private function installTab()
{
$tabId = (int) Tab::getIdFromClassName('MyModuleDemoController');
if (!$tabId) {
$tabId = null;
}
$tab = new Tab($tabId);
$tab->active = 1;
$tab->class_name = 'MyModuleDemoController';
// Only since 1.7.7, you can define a route name
$tab->route_name = 'admin_my_symfony_routing';
$tab->name = array();
foreach (Language::getLanguages() as $lang) {
$tab->name[$lang['id_lang']] = $this->trans('My Module Demo', array(), 'Modules.MyModule.Admin', $lang['locale']);
}
$tab->id_parent = (int) Tab::getIdFromClassName('ShopParameters');
$tab->module = $this->name;
return $tab->save();
}
private function uninstallTab()
{
$tabId = (int) Tab::getIdFromClassName('MyModuleDemoController');
if (!$tabId) {
return true;
}
$tab = new Tab($tabId);
return $tab->delete();
}
}
And now you have your menu link directing to your Symfony controller with a nice url.
Modern controllers can also be registered via the $tabs
property. You don’t need to manually create the Tab object in this case, and you can take full advantage of the Symfony routing (no need for _legacy_link
).
Here is an example with a Symfony controller (example comes from the ps_linklist module). Nothing specific in this controller but notice the security annotation @AdminSecurity
that uses request.get('_legacy_controller')
which will make the link between this controller and the routing configuration.
<?php
// yourmodule/src/Controller/Admin/Improve/Design
namespace PrestaShop\Module\LinkList\Controller\Admin\Improve\Design;
use PrestaShopBundle\Security\Annotation\AdminSecurity;
// (...)
/**
* Class LinkBlockController.
*
* @ModuleActivated(moduleName="ps_linklist", redirectRoute="admin_module_manage")
*/
class LinkBlockController extends FrameworkBundleAdminController
{
/**
* @AdminSecurity("is_granted('read', request.get('_legacy_controller'))", message="Access denied.")
*
* @param Request $request
*
* @return Response
*/
public function listAction(Request $request)
{
// (...)
}
}
Now here is the routing configuration. We can see the _legacy_controller
option is present with a value of AdminLinkWidget
. This will be used for the AdminSecurity
annotation, but also as our Tab’s class_name.
# yourmodule/config/routes.yml
admin_link_block_list:
path: /link-widget/list
methods: [GET]
defaults:
_controller: 'PrestaShop\Module\LinkList\Controller\Admin\Improve\Design\LinkBlockController::listAction'
# _legacy_controller is used to manage permissions
_legacy_controller: AdminLinkWidget
# No need for _legacy_link in this case
Finally, here is the $tabs
property used for automatic registration. It still requires a class_name
field: it will be used to create the default AUTHORIZATION_ROLES
related to this class_name, and later to check for those permissions.
<?php
// yourmodule/ps_linklist.php
use Language;
class Ps_Linklist extends Module
{
public function __construct() {
// ...
$tabNames = [];
foreach (Language::getLanguages(true) as $lang) {
$tabNames[$lang['locale']] = $this->trans('Link List', array(), 'Modules.Linklist.Admin', $lang['locale']);
}
$this->tabs = [
[
'route_name' => 'admin_link_block_list',
'class_name' => 'AdminLinkWidget',
'visible' => true,
'name' => $tabNames,
'parent_class_name' => 'AdminParentThemes',
'wording' => 'Link List',
'wording_domain' => 'Modules.Linklist.Admin'
],
];
// ...
}
}
Hidden Tabs
Since 1.7.7, when you create a Symfony route with the _legacy_controller
if no visible Tab has been created an invisible one is automatically created so the permissions will be correctly handled.