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:
Controller
part.By default, your tab will be displayed in the menu with its class name. If you want to use something more explicit, you can set the name property.
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
public $tabs = array(
array(
'name' => 'Merchant Expertise', // One name for all langs
'class_name' => 'AdminGamification',
'visible' => true,
'parent_class_name' => 'ShopParameters',
));
You can also add your translations per locale (ex.: fr-FR) or per language (ex.: fr), both are valid.
If a language is installed on the shop but is not found in your translated names, it will be automatically associated to the first value of the array.
Hence, we advise you to define the English value first.
<?php
public $tabs = array(
array(
'name' => array(
'en' => 'Merchant Expertise', // Default value should be first
'fr' => 'Expertise PrestaShop',
...
),
'class_name' => 'AdminGamification',
'parent_class_name' => 'ShopParameters',
));
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',
],
];
...
}
}
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.