SWJB Posted March 3, 2019 Share Posted March 3, 2019 Hi, I'm building a custom module for PS 1.7.5 and need to create an admin page that is accessible from the side menu. I have a custom namespaced controller, action, twig template and routing.yml all working fine (composer.json in the module directory to setup the psr4 namespace mapping). Where I'm having issue is adding a link to the side bar under the Administration category. From the docs, I read about the $this->tabs array of the main module class which takes the "class_name" array key. The tab registration process kicks off fine when I add a tab to the array, but during the registration process, it seems like it only works with the legacy controller structure and looking for the class: "module_name/controllers/admin/[class_name]Controller.php . [REF: src/Adapter/Module/Tab/ModuleTabRegister.php::checkIsValid line:179]. Is there not a way to add a side bar link to an action of a new FrameworkBundleAdminController instance OR adding the link to a route i have configured in "module_name/config/routes.yml" Any help would be greatly appreciated. Thanks. 1 Link to comment Share on other sites More sharing options...
Rolige Posted March 4, 2019 Share Posted March 4, 2019 Hello, Its not clear whats the problem if you know the code to create a new tab link in the left menu, could you tell us exactly what you need? Regards! Link to comment Share on other sites More sharing options...
SWJB Posted March 4, 2019 Author Share Posted March 4, 2019 (edited) Hi, thanks for your reply My problem is that the code generates a link based on the old style (previous to 1.7.5) of menu controller that doesn't use the Symfony based controller. So it assumes a [class_name]Controller.php file is in <module_name>/controller/admin/ . It doesn't allow me to specified the fully qualified class name or specify an action within that controller. I have a controller that extends FrameworkBundleAdminController: <?php namespace MyNamespace\Controller\Admin; use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; class MyDashboardController extends FrameworkBundleAdminController { /** Controller Code **/ public function dashboardAction() { return $this->render('@Modules/my_module/views/admin/dashboard.html.twig'); } } I want to add a link in the admin side bar to the dashboard action of MyDashboardController. the problem is that in my main module class where I create the tabs array like this: <?php /** Somewhere in main module class constructor **/ $this->tabs = [ [ 'name' => [ 'en' => 'My Dashbord', 'fr' => 'Mon Table de bord', ], 'class_name' => 'MyDashboard', //Want to use: MyNamespace\Controllers\Admin\MyDashboardController:dashboardAction 'visible' => true, 'parent_class_name' => 'IMPROVE', ], ]; It won't work with the new way to develop modules based on the symfony system. The prestashop functions that register the tabs using the class_name element of the tabs array make the assumtion that the class_name is the old style, here is the code where it loads the class: This is in /src/Adapter/Module/Tab/ModuleTabRegister.php[line:172]. We can see that the class name passed through the tabs array is used to concatenate to 'Controller.php' and then validated if it exists in the module admin controllers found, which looks in : $modulePath = _PS_ROOT_DIR_ . '/' . basename(_PS_MODULE_DIR_) . '/' . $moduleName . '/controllers/admin/'; This is in /src/Adapter/Module/Tab/ModuleTabRegister.php:getModuleAdminControllers()[line:203]. This would not be so bad if I could also configure which action to use inside the controller, I could always reconfigure my module's PSR-4 autoloading to use the needed folder structure, but since I cannot say which action to use, the default run() function of the old style module controllers is called by default. An ideal solutions would be to be able to pass MyNamespace\Controllers\Admin\MyDashboardController:dashboardAction to the tabs array class_name, this could easily be parsed to class name and method name by exploding the string on ":", A quick validation of class_exists could be done first and if so, execute the method of the class, if not go on and try the old style style loading. Another better solution could be to pass the route that i configured in <my_module>/config/routes.yml. The tab loader would check the route configured which points to the correct controller and action: my_dashbaord: path: my_module/dashboard methods: [GET] defaults: _controller: 'MyNamespace\Controller\Admin\MyDashboardController::dashboardAction' Am I completely missing something here ? The way it is i don't see any way to implement an admin sidebar tab for a new module controller built using the official docs for 1.7.* that shows us to extend PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController and use custom namespaces? Edited March 4, 2019 by SWJB (see edit history) Link to comment Share on other sites More sharing options...
Rolige Posted March 4, 2019 Share Posted March 4, 2019 I see, but at this point PS doesn't have option to create a tab menu link pointed to an Admin controller in the new architecture, or at least not that I know, a possible solution is changing the link by JavasScript. Link to comment Share on other sites More sharing options...
SWJB Posted March 4, 2019 Author Share Posted March 4, 2019 Ok thanks for your reply, so i'll stop banging my head against the wall for this. Would you be able to point me in the right direction to adding some custom javascript for my module in the admin ? thanks again, your help is much appreciated. Link to comment Share on other sites More sharing options...
Rolige Posted March 4, 2019 Share Posted March 4, 2019 56 minutes ago, SWJB said: Ok thanks for your reply, so i'll stop banging my head against the wall for this. Would you be able to point me in the right direction to adding some custom javascript for my module in the admin ? thanks again, your help is much appreciated. Just load a JS file in the Back Office Header: $('id-or-class-here').attr('href', '#your-new-link-here'); Link to comment Share on other sites More sharing options...
mickaelandrieu Posted March 4, 2019 Share Posted March 4, 2019 Hi @SWJB, take a look at this community module => https://github.com/friends-of-prestashop/masterclass/blob/master/masterclass.php#L107 Regards, Link to comment Share on other sites More sharing options...
SWJB Posted March 4, 2019 Author Share Posted March 4, 2019 (edited) Hi @mickaelandrieu Thanks, it was the : _legacy_controller _legacy_link That i was missing in my routing. This feels pretty hacky though. I can only assume that this will be updated in future versions as Symfony is more and more integrated into the core. Thanks Edited March 4, 2019 by SWJB just because (see edit history) 1 Link to comment Share on other sites More sharing options...
mickaelandrieu Posted March 11, 2019 Share Posted March 11, 2019 On 3/4/2019 at 7:13 PM, SWJB said: This feels pretty hacky though. I can only assume that this will be updated in future versions as Symfony is more and more integrated into the core. Once the back office will be entirely migrated to Symfony, maybe! For now, the Symfony controllers rely on the old Security system of back office, so on the core team have to match the new controllers to the old ones to not break security permissions when doing an auto upgrade. This is hacky, but doing a progressive migration is hacky 😀 Link to comment Share on other sites More sharing options...
Bella961 Posted March 30, 2019 Share Posted March 30, 2019 Attributes column in administration disappears when I delete one of languages (Czech and Slovak languages are both installed at one time). When Czech is deleted, attributes column disappear. Link to comment Share on other sites More sharing options...
muspi Posted July 1, 2019 Share Posted July 1, 2019 Hello, I'm facing same problem. Can tou tell me please what informations you set in _legacy_controller and _legacy_link please? Link to comment Share on other sites More sharing options...
Mickaël Andrieu Posted July 1, 2019 Share Posted July 1, 2019 Hi, the docs are available and a pull request has been done to support tabs property with modern controllers on 1.7.7. https://devdocs.prestashop.com/1.7/development/architecture/migration-guide/controller-routing/#routing-in-prestashop Cheers Link to comment Share on other sites More sharing options...
muspi Posted July 2, 2019 Share Posted July 2, 2019 7 hours ago, Mickaël Andrieu said: Hi, the docs are available and a pull request has been done to support tabs property with modern controllers on 1.7.7. https://devdocs.prestashop.com/1.7/development/architecture/migration-guide/controller-routing/#routing-in-prestashop Cheers Thank you. Indeed, my shop is in 1.7.5, and i do not want to upgrade now. Is there any alternative to make it work with _legacy route parameters? Link to comment Share on other sites More sharing options...
SWJB Posted July 2, 2019 Author Share Posted July 2, 2019 @mickaelandrieu - Here is what i have and working in 1.7.5: Create your new controller in your module. I put it in [module_folder] / lib / Controllers / Admin / CognetifContracts.php. Here is what it looks like (i've removed everything except just what you would need to get it working): <?php namespace Cognetif\Contracts\Controllers\Admin; class CognetifContracts extends FrameworkBundleAdminController { public function __construct() { parent::__construct(); } /** * List all contracts * @return Response * @throws Exception */ public function listAction() { return $this->render('@Modules/cg_contracts/views/templates/admin/list-contracts.html.twig', [ 'contracts' => [1, 2, 3], 'tab' => 'list', 'layoutTitle' => $this->trans('Contract List', $this->module) ]); } I'm using a custom namespace so that needs to be configured in the [ module_folder ] / composer.json file: { ... "autoload": { "psr-4": { "Cognetif\\Contracts\\": "lib/" } } } afterwards you need to execute the $ composer install command from the terminal within your module directory. This will have composer resolve your namespace to the correct file location. In my case [ module_folder ] / lib . where i keep all my psr-4 classes. In my main module file [module_name].php, you need to require the composer autoloading file at the top of the file before your module class definition: <?php if (!defined('_PS_VERSION_')) { exit; } require_once(__DIR__ . '/vendor/autoload.php'); class Cg_Contracts extends \Module { //... } Create a routing file in : [module_folder] / config / routes.yml Create a route like the following that defines an action within a controller. Here i'm using my custom namespace 'Cognetif\Contracts\Controllers\Admin' . The name of the controller is 'CognetifContracts' and the name of the action or function is 'listAction'. I tell it that i allow the GET method to the action. If you want multiple methods, separate them with commas within the [ ] array such as: [ GET, POST]. cognetif_contracts_list: path: cg_contracts/contracts methods: [GET] defaults: _controller: 'Cognetif\Contracts\Controllers\Admin\CognetifContracts::listAction' _legacy_controller: 'CognetifContractsList' _legacy_link: CognetifContractsList The path cg_contracts/contracts will be seen in the url from the PS backend when accessing that page. But as i mentioned in the original post, the legacy functions are not able to access this page using the Namespaced controller and action names. So if you add a unique string that will reference the controller and action in the _legacy_controller and _legacy_link properties, you'll then be able to pass this as the controller to those legacy functions. Here is an example of how I use it to add the Tab to the PS backend: TabHelper::AddTab('CognetifContractsList', [1 => 'Contracts', 2 => 'Contrats'], $this->name, 'Improve'); and the TabHelper class, I found somewhere and adapted... it might be referenced in one of the previous comments on this thread: <?php namespace Cognetif\Helpers; use Tab; use Language; class TabHelper { public static function AddTab($className, $tabName, $moduleName, $parentClassName, $icon = null) { $tab = new Tab(); $tab->active = 1; $tab->class_name = $className; $tab->name = []; foreach (Language::getLanguages(true) as $lang) { if (is_array($tabName) && array_key_exists($lang['id_lang'], $tabName)) { $tab->name[$lang['id_lang']] = $tabName[$lang['id_lang']]; } else { $tab->name[$lang['id_lang']] = $tabName; } } $tab->id_parent = (int)Tab::getIdFromClassName($parentClassName); $tab->module = $moduleName; if (!is_null($icon)) { $tab->icon = $icon; } $tab->add(); return $tab; } public static function removeTab($className) { $id_tab = (int)Tab::getIdFromClassName($className); $tab = new Tab($id_tab); if ($tab->name !== '') { $tab->delete(); } return true; } } And here is an example that had been redacted from the CognetifContracts.php Symfony admin controller that redirects to the an action within the same controller. You can see it references the route name that was configured in the routes.yml and not the _legacy_controller or _legacy_link return $this->redirectToRoute('cognetif_contracts_list'); So with this information you should be able to setup a new symfony based FrameworkBundleAdminController class, create a route in the routes.yml and use the configured route as a controller/action reference in the legacy code via _legacy_controller or _legacy_link and use the route name in the new symfony base redirects. If you think I may have forgotten anything or have any questions on how this works or anything else please don't hesitate to contact me via PM or contact me via my website: cognetif.com Good luck. @muspi Quote Hi, the docs are available and a pull request has been done to support tabs property with modern controllers on 1.7.7. https://devdocs.prestashop.com/1.7/development/architecture/migration-guide/controller-routing/#routing-in-prestashop The docs are really not clear for someone who is starting out with this. Comments like this implying that you simply need to read the docs, don't actually help people. Especially with the state of the PS docs lack of examples and a hybrid framework that is transitioning between a legacy codebase and Symfony. 1 Link to comment Share on other sites More sharing options...
muspi Posted July 2, 2019 Share Posted July 2, 2019 (edited) Thanks a lot, your TabHelper is a goo solution!!! You saved my day!!! - I set addTab() method in install() module function - removeTab() method in uninstall() module function Works like a charm Edited July 2, 2019 by muspi (see edit history) Link to comment Share on other sites More sharing options...
Mickaël Andrieu Posted July 2, 2019 Share Posted July 2, 2019 (edited) Hi, > docs are not clear enough [...] but it's the truth: reading the docs is clear enough in this case! > The path cg_contracts/contracts will be seen in the URL from the PS backend when accessing that page. Not exactly: all URLs are prefixed by <your-admin-folder>/modules, you can get rid of this constraint if you're able to enable Annotations into your controllers. Thanks for noticing the TabHelper (directly extracted and adapted from https://github.com/friends-of-prestashop/masterclass/blob/master/src/Utils/TabManager.php, you're welcome by the way ). Be careful with this TabManager: there is a good reason we didn't contribute it directly on the Core: you will corrupt your table "role_authorization" if you reset or uninstall your module. @mupi if you're looking for modules you can take a look at the "Friends of PrestaShop" organization on GitHub, most of my modules are available for free (and open source). Cheers Edited July 2, 2019 by Mickaël Andrieu tried to reduce the size of the quote (see edit history) Link to comment Share on other sites More sharing options...
SWJB Posted July 2, 2019 Author Share Posted July 2, 2019 I guess we'll have to agree to disagree about the docs :-) I find them extremely general and don't document the majority of the properties or available functionality. Just the easy safe path which doesn't give you an indication on what you can actually do. Thanks for providing the original link to the tab manager. Is there a better way if its not considered safe ? How else should we be getting a tab into the back end ? Can you provide a better solution ? it would be much appreciated! Link to comment Share on other sites More sharing options...
mickaelandrieu Posted August 21, 2019 Share Posted August 21, 2019 Hello @SWJB, sadly I was working on support of modern controllers but it was closed in favor of an issue managed by the Core team. The best you can do is to comment the issue so the team is aware how important this issue is for people. I may reopen my contribution if needed. Cheers ! Link to comment Share on other sites More sharing options...
ZiedDams Posted August 31, 2021 Share Posted August 31, 2021 SOLUTION THAT WORKED FOR ME: $tab->class_name = "something"; in your routes.yml after the _controller: 'your-module\Controller\ControllerClass::YourAction' add this _legacy_controller: 'something' //(same thing that you write in the $tab->class_name with quotes) _legacy_link: something //(same thing that you write in the $tab->class_name without quotes) HOPE THIS HELP SOMEONE Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now