Jump to content

PS 1.7.5 Admin link for FrameworkBundleAdminController:action


Recommended Posts

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.

 

  • Like 1
Link to comment
Share on other sites

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:

 

image.png.2b52acd4667ec8d37102b968f8c3aa3b.png

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 by SWJB (see edit history)
Link to comment
Share on other sites

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

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

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

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 by SWJB
just because (see edit history)
  • Like 1
Link to comment
Share on other sites

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

  • 3 weeks later...
  • 3 months later...
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

@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.

 

 

  • Like 1
Link to comment
Share on other sites

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 by Mickaël Andrieu
tried to reduce the size of the quote (see edit history)
Link to comment
Share on other sites

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

  • 1 month later...
  • 2 years later...

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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...