Prestashop : créer un admin controller pour un module

Ce tutoriel est compatible avec les versions de Prestashop suivantes :
1.5 1.6 1.7 1.7.5 1.7.6 1.7.7 1.7.8 8.0 8.1 +
Cet article est assez ancien, malgré toute l'attention que j' apporte à mes contenus il est possible que celui-ci ne soit plus d'actualité.
N'hésitez pas à me le signaler si nécessaire via le formulaire de contact.

Nous allons voir les différentes étapes pour réaliser facilement un controller admin basique lié à un module Prestashop.
Puis nous verrons ensuite les caractéristiques de base d’un controller admin, cet article sera complété par d’autres articles pour voir les fonctionnalités avancées 😉
Vous pouvez consulter l’ensemble des informations sur cette page  : https://www.h-hennes.fr/blog/2019/01/30/prestashop-utilisation-avancee-admincontroller/

Note : A partir de la version 1.7.5 de Prestashop il est possible d’utiliser d’utiliser le framework symfony et de suivre le même fonctionnement que les nouveaux controller admin de prestashop, je ferais sans doute un article ultérieurement.
Ce tutoriel reste tout de même valide sur cette version et les suivantes pour l’instant.

Pour cela nous allons créer un module « samplemodule ».
Le code complet est visible sur github : https://github.com/nenes25/prestashop_samplemodule/tree/admin

Les prérequis suivants sont nécessaires pour réaliser un controller admin pour votre module :

  • Création d’une « Tab »
  • Utilisation d’un objet Prestashop héritant de la classe ObjectModel ( et de la base de données qui lui est associée )

Pour l’objet Prestashop nous partirons sur un objet fictif « Sample » avec les propriétés suivantes :

  • name : nom de l’objet
  • code : code de l’objet
  • email : email de l’objet
  • title : titre de l’objet ( différent en fonction de la langue )
  • description : description de l’objet ( différent en fonction de la langue )

Le code suivant sera dans le fichier classes/Sample.php du module

class Sample extends ObjectModel
{
 
    public $id;
    public $name;
    public $code;
    public $email;
    public $title;
    public $description;
 
    public static $definition = [
        'table' => 'hh_sample',
        'primary' => 'id_sample',
        'multilang' => true,
        'fields' => [
            // Champs Standards
            'name' => ['type' => self::TYPE_STRING, 'validate' => 'isName', 'size' => 255, 'required' => true],
            'code' => ['type' => self::TYPE_STRING, 'validate' => 'isLinkRewrite', 'size' => 255, 'required' => true],
            'email' => ['type' => self::TYPE_STRING, 'validate' => 'isEmail', 'size' => 255, 'required' => true],
            //Champs langue
            'title' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml', 'size' => 255,],
            'description' => ['type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml',],
        ],
    ];
}

 

Pour la création de la tab et de la base de donnée c’est le module qui se charge de gérer tout cela lors de son installation.

//Inclusion du modèle Sample
require_once _PS_MODULE_DIR_ . '/samplemodule/classes/Sample.php';
 
class SampleModule extends Module
{
    public function __construct()
    {
        $this->author = 'hhennes';
        $this->name = 'samplemodule';
        $this->tab = 'hhennes';
        $this->version = '0.1.1';
        $this->need_instance = 0;
 
        parent::__construct();
 
        $this->displayName = $this->l('Prestashop sample Module');
        $this->description = $this->l('Prestashop sample Module with front controller');
    }
 
    /**
     * Installation du module
     * @return boolean
     */
    public function install()
    {
        return parent::install() && $this->_installSql() && $this->_installTab();
    }
 
    /**
     * Désinstallation du module
     * @return boolean
     */
    public function uninstall()
    {
        return parent::uninstall() && $this->_uninstallSql() && $this->_uninstallTab();
    }
 
    /**
     * Création de la base de donnée
     * @return boolean
     */
    protected function _installSql()
    {
        $sqlCreate = "CREATE TABLE `" . _DB_PREFIX_ . Sample::$definition['table'] . "` (
                `id_sample` int(11) unsigned NOT NULL AUTO_INCREMENT,
                `name` varchar(255) DEFAULT NULL,
                `code` varchar(255) DEFAULT NULL,
                `email` varchar(255) DEFAULT NULL,
                `date_add` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                `date_upd` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (`id_sample`)
                ) ENGINE=InnoDB DEFAULT CHARSET=latin1;";
 
        $sqlCreateLang = "CREATE TABLE `" . _DB_PREFIX_ . Sample::$definition['table'] . "_lang` (
              `id_sample` int(11) unsigned NOT NULL AUTO_INCREMENT,
              `id_lang` int(11) NOT NULL,
              `title` varchar(255) DEFAULT NULL,
              `description` text,
              PRIMARY KEY (`id_sample`,`id_lang`)
            ) ENGINE=InnoDB DEFAULT CHARSET=latin1;";
 
        return Db::getInstance()->execute($sqlCreate) && Db::getInstance()->execute($sqlCreateLang);
    }
 
    /**
     * Installation du controller dans le backoffice
     * @return boolean
     */
    protected function _installTab()
    {
        $tab = new Tab();
        $tab->class_name = 'AdminHhSample';
        $tab->module = $this->name;
        $tab->id_parent = (int)Tab::getIdFromClassName('DEFAULT');
        $tab->icon = 'settings_applications';
        $languages = Language::getLanguages();
        foreach ($languages as $lang) {
            $tab->name[$lang['id_lang']] = $this->l('HH Sample Admin controller');
        }
        try {
            $tab->save();
        } catch (Exception $e) {
            echo $e->getMessage();
            return false;
        }
 
        return true;
    }
 
    /**
     * Désinstallation du controller admin
     * @return boolean
     */
    protected function _uninstallTab()
    {
        $idTab = (int)Tab::getIdFromClassName('AdminHhSample');
        if ($idTab) {
            $tab = new Tab($idTab);
            try {
                $tab->delete();
            } catch (Exception $e) {
                echo $e->getMessage();
                return false;
            }
        }
        return true;
    }
 
    /**
     * Suppression de la base de données
     */
    protected function _uninstallSql()
    {
        $sql = "DROP TABLE ". _DB_PREFIX_ .Sample::$definition['table'].",". _DB_PREFIX_ .Sample::$definition['table']."_lang";
        return Db::getInstance()->execute($sql);
    }
}

Création du controller admin

Celui-ci doit être créé dans le dossier /controllers/admin/ de votre module.
Pour l’exemple celui-ci se nommera AdminHhSample et le fichier AdminHhSample.php

Je rappelle que les fonctions basiques d’un controller admin dans le cadre d’un module sont :

  • d’afficher une liste d’objets Prestashop
  • d’ajouter et d’éditer ces objets

Voici le code basique de ce fichier

require_once _PS_MODULE_DIR_ . '/samplemodule/classes/Sample.php';
class AdminHhSampleController extends ModuleAdminController
{
}

Le nom de classe du controller est nomdufichierController et il doit étendre la classe ModuleAdminController
Dans celui-ci on inclus également le fichier php contenant la classe de l’objet que nous allons gérer.

 

Liste des objets :

La fonction par défaut du controller est d’afficher une liste des objets.
Pour cela il faut renseigner toutes les informations nécessaires dans la fonction __construct() de la classe
J’ai commenté les différents paramètres pour expliquer leur fonctionnement.

A noter que les champs $this->fields_list sont ensuite utilisés dans une liste généré via la classe HelperList, si vous souhaitez visualiser toutes les possibilités.

   /**
     * Instanciation de la classe
     * Définition des paramètres basiques obligatoires
     */
    public function __construct()
    {
        $this->bootstrap = true; //Gestion de l'affichage en mode bootstrap 
        $this->table = Sample::$definition['table']; //Table de l'objet
        $this->identifier = Sample::$definition['primary']; //Clé primaire de l'objet
        $this->className = Sample::class; //Classe de l'objet
        $this->lang = true; //Flag pour dire si utilisation de langues ou non
 
        //Appel de la fonction parente pour pouvoir utiliser la traduction ensuite
        parent::__construct();
 
        //Liste des champs de l'objet à afficher dans la liste
        $this->fields_list = [
            'id_sample' => [ //nom du champ sql
                'title' => $this->module->l('ID'), //Titre
                'align' => 'center', // Alignement
                'class' => 'fixed-width-xs' //classe css de l'élément
            ],
            'name' => [
                'title' => $this->module->l('name'),
                'align' => 'left',
            ],
            'code' => [
                'title' => $this->module->l('code'),
                'align' => 'left',
            ],
            'email' => [
                'title' => $this->module->l('email'),
                'align' => 'left',
            ],
            'title' => [
                'title' => $this->module->l('title'),
                'lang' => true, //Flag pour dire d'utiliser la langue
                'align' => 'left',
            ]
        ];
 
        //Ajout d'actions sur chaque ligne
        $this->addRowAction('edit');
        $this->addRowAction('delete');
    }

Avec ce code nous aurons l’affichage suivant :

blog listing admin

Ajout / Édition d’un objet

Pour l’ajout ou l’édition d’un objet le controller doit implémenter la fonction renderForm avec le contenu suivant :
Pour voir l’ensemble des champs possibles vous pouvez visualiser le controller natif de AdminPatterns via l’url : http://www.votre-site/admin/index.php?controller=AdminPatterns

/**
     * Affichage du formulaire d'ajout / création de l'objet
     * @return string
     * @throws SmartyException
     */
    public function renderForm()
    {
        //Définition du formulaire d'édition
        $this->fields_form = [
            //Entête
            'legend' => [
                'title' => $this->module->l('Edit Sample'),
                'icon' => 'icon-cog'
            ],
            //Champs
            'input' => [
                [
                    'type' => 'text', //Type de champ
                    'label' => $this->module->l('name'), //Label
                    'name' => 'name', //Nom
                    'class' => 'input fixed-width-sm', //classes css
                    'size' => 50, //longueur maximale du champ
                    'required' => true, //Requis ou non
                    'empty_message' => $this->l('Please fill the postcode'), //Message d'erreur si vide
                    'hint' => $this->module->l('Enter sample name') //Indication complémentaires de saisie
                ],
                [
                    'type' => 'text',
                    'label' => $this->module->l('code'),
                    'name' => 'code',
                    'class' => 'input fixed-width-sm',
                    'size' => 5,
                    'required' => true,
                    'empty_message' => $this->module->l('Please fill the code'),
                ],
                [
                    'type' => 'text',
                    'label' => $this->module->l('email'),
                    'name' => 'email',
                    'class' => 'input fixed-width-sm',
                    'size' => 5,
                    'required' => true,
                    'empty_message' => $this->module->l('Please fill email'),
                ],
                [
                    'type' => 'text',
                    'label' => $this->module->l('Title'),
                    'name' => 'title',
                    'class' => 'input fixed-width-sm',
                    'lang' => true, //Flag pour utilisation des langues
                    'required' => true,
                    'empty_message' => $this->l('Please fill the title'),
                ],
                [
                    'type' => 'textarea',
                    'label' => $this->module->l('Description'),
                    'name' => 'description',
                    'lang' => true,
                    'autoload_rte' => true, //Flag pour éditeur Wysiwyg
                ],
            ],
            //Boutton de soumission
            'submit' => [
                'title' => $this->l('Save'), //On garde volontairement la traduction de l'admin par défaut
            ]
        ];
        return parent::renderForm();
    }

Le rendu sera ensuite le suivant :

Blog édition sample

 

Avec ces 2 éléments vous avez à présent un controller admin fonctionnel pour votre module qui permet de gérer facilement votre entité Prestashop 🙂

Bonus : Un bouton d’ajout dans la toolbar

Le bouton d’ajout d’un élément est relativement petit par défaut, voici comment un ajouter un plus visible
Ajouter la fonction initPageHeaderToolbar() dans votre controller avec le contenu suivant :

  /**
     * Gestion de la toolbar
     */
    public function initPageHeaderToolbar()
    {
 
        //Bouton d'ajout
        $this->page_header_toolbar_btn['new'] = array(
            'href' => self::$currentIndex . '&add' . $this->table . '&token=' . $this->token,
            'desc' => $this->module->l('Add new Sample'),
            'icon' => 'process-icon-new'
        );
 
        parent::initPageHeaderToolbar();
    }

Bouton Controller

23 réflexions sur “Prestashop : créer un admin controller pour un module”

  1. merci pour le tutoriel, j’ai pu suivre vos instructions et realiser ce module example, et jai egalement trouver les petits piege que vous avez mi dans le code pour vous assurer que nous sommes éveillé, merci pour le petit test, je suis egalement un jeunne developpeur, et je m’interesse déjà à prestashop depuis deja 7 mois, et je prends deja gout, je developpais deja des modules graces a la devdoc de prestashop et à vos tutoriels, mais j’avoue que ce que vous faite dans ce tutoriel etait le manque que j’avais encore, maintenant je peux garantir a mes modules de nouveaux back office autres que ceux standardisement utilisé, merci pour le tutoriel, j’aimerai maintenant savoir comment je pourrais dans mon admin page géré plusieurs rubrique unpeu comme plusieurs classes, et les scindés dans la page admin de mon module unpeu comme ce qui est présenté dans internationnalisation avec pays zone et etats !!! Merci déjà pour ce tutoriel, je reste ouvert à vous si vous voulez travaillez avec moi

    1. Bonjour Yannick,

      Pouvez-vous me donner les « pièges » que vous avez trouvé dans cet article, je ne crois pas avoir fait de coquille lors de sa rédaction ^^.
      La gestion des admincontroller est nativement très puissante et permets de faire beaucoup de choses assez facilement et avec peu de code.
      Le problème restant comme d’habitude que ce n’est pas documenté.
      Je rédige actuellement un article sur les options avancées des admincontroller mais le sujet est très vaste je ne pourrais sans doute pas tout évoquer.
      Pour ce qui concerne votre demande je ne saurais pas vous dire directement comment la faire, le plus simple reste d’étudier le code du controller concerné 😉

      Cordialement,
      Hervé

  2. Bonjour hervé, quand je parlais de piège je faisais allusion à certains petits détails, dans le depot gitHub, la classe Sample.php n’a pas d’attribut public $name; ce qui empeche l’insertion lors de la soumission du formulaire, il m’a fallu 20 minutes pour me rendre compte que l’insertion de fonctionnait pas correctement à cause de l’attribut $name dans le fichier sample.php, deuxiement quand j’ai essayer de désinstaller le module une erreur s’est produite, je me suis rendu compte que la fonction uninstall sql ne faisai plus reference au table crée par defaut, au lieu de referencer prefix_hh_sample elle referençait plutot hh_sample, du coup j’ai ajouté le prefix sur le code de la fucntion unisntall pour que la désinstallation de la base de donnée réussie

    1. Vous avez totalement raison.
      Cette erreur n’est pas volontaire j’avais changé 2/3 points lors de la rédaction de l’article et je n’avais pas testé à nouveau le module.
      J’ai corrigé les erreurs que vous relevez, et j’en ai identifié une autre tout est corrigé sur github.
      Les coquilles ont également été corrigées dans l’article.
      Merci de votre vigilance 🙂

  3. Okay supe!! c’est un plaisir pour moi de prendre connaissance de vos articles. je suis entrain d’améliorer l’admin controller du module afin de prendre en compte beaucoup de configuration native , lorsque je fini je vous soumet cela pour approbation

  4. Bonjour Hervé, super tuto, je m’en serais bien inspiré mais visiblement prestashop a encore modifié le core car lorsque je veux, pour tester, installer votre module récupéré sur GIT … et voilà le joli message que me sort presta : Ce fichier ne semble pas être un fichier .zip de module valide.
    A moins que ce module de démo ne soit pas fonctionnel à la base … ce qui m’étonnerai fort !
    Une idée du pourquoi du comment de la chose ???
    Au plaisir de vous lire et continuez, vos articles sont très intéressants 😉

    1. Bonjour Xavier,
      Pour le coup je plaide coupable cela n’a rien à voir avec prestashop, mais avec comment j’ai structuré mon dépot github 😀 ( qui me servais à la base uniquement de source pour les articles )
      Pour installer le module il faut créer un dossier samplemodule dans le dossier modules et y envoyer les fichiers en ftp ( ou faire ça en local et en faire une archive )

      Cordialement,
      Hervé

  5. Bonjour Hervé,

    un grand merci pour ce tuto qui m’a dépanné (et ce n’est pas le premier) ! J’ai cependant une question (2 en fait) :

    – comment fusionner le contenu de 2 colonnes à l’affichage de la liste ? (par exemple mettre le nom et prénom dans la même colonne) sachant que ces 2 champs sont bien distincts dans la base.

    – comment récupère-t-on les données d’une autre table via une clé étrangère ?

    D’avance merci pour votre réponse 🙂

    1. Bonjour Gaëlle,
      Un bonne exemple de comment gérer votre problématique est visible ici : https://github.com/PrestaShop/PrestaShop/blob/1.7.5.x/controllers/admin/AdminAddressesController.php#L103
      Les informations sont récupérées tous simplement via une requête sql
      Vous pouvez rajouter vos jointures via $this->_join ,
      Pareil pour votre groupement de champ il vous suffit d’ajouter une clause dans le $this->_select avec par ex CONCAT(firstname,’ ‘,lastname) AS fullname et dans votre fieldlist vous mettez le nom de champ « fullname »

      Cordialement,
      Hervé

  6. Bonjour Hervé,
    J’ai une question, si je souhaite avoir deux colonnes qui traitent la même donnée comment faire?

    exemple:

    ‘expiry_date’ => array(
    ‘title’ => $this->l(‘Expire Date’),
    ‘align’ => ‘center’,
    ‘type’ => ‘date’,
    ),
    ‘expiry_date’ => array(
    ‘title’ => $this->l(‘Check’),
    ‘align’ => ‘center’,
    ‘class’ => ‘fixed-width-xs’,
    ‘callback’ => ‘checkSendDoc’,
    ),

    J’ai expiry_date qui affiche la date d’expiration et je veux faire une colonne qui prend la date d’expiration et retour un true ou false si cela expire ce mois-ci.

    Je peux afficher une ou l’autre colonne mais pas les deux en même temps car c’est basé sur le même nom de variable.

    Peux tu m’aider? Merci d’avance

    1. Bonjour,

      J’ai fait un test rapide qui semble fonctionnel.
      L’astuce est de rajouter la colonne une 2ème fois mais avec un autre alias.

      $this->_select= ' expiry_date as other_expiry_date';

      Cordialement,
      Hervé

  7. Bonjour,
    J’essaie de créer un module avec la même base que celui de ce tuto sans succès.
    J’ai beau avoir un code très très similaire, impossible d’afficher la liste (vide) comme sur le screen juste avant Ajout/Edition d’un objet.
    Je laisse le lien vers un topic que j’ai lancé sur le sujet (avec mon module téléchargeable en l’état actuel): https://www.prestashop.com/forums/topic/1047470-module-qui-naffiche-rien-admincontroller-en-tord
    Merci d’avance à ceux qui jetteront un coup d’oeil à mon problème =)

  8. Bonjour Hervé j’ai essayé de mon coté et même recreer un dosier en y ajoutant vos fichier mais rien n’y fait. Je n’arrive pas a trouver justement cette affichage.
    De plus j’aurais une question. Souhaitant créer un module que j’ai actuellement accroché au hookDisplayAdminCustomers. Dans celui ci j’affiche un tpl qui est juste un block avec un input d’email et un bouton valider. Cependant je voudrais que cette email aille dans la BDD que j’ai créé. Donc pour cela j »ai besoin d’un js qui envoi les données avec de l’ajax a mon controller. Cependant j’épluche le net pour savoir comment je puisse envoyer ça à mon controller.. Sans succès.. Pourtant j’y arrive bien hors module car j’y ai fait des choses similaire.. J’aimerai savoir si tu aurais une idée ou un lien afin de m’aider de comment je puisse faire pour que quand j’appuie sur le bouton validé l’email dans l’input et l’id du customer soit envoyé a fonction ajax de mon controller qui j’arrive pas du tout a le lié de même pour le js.
    Merci
    CDT

    1. Bonjour Jéremy,

      Votre première question est assez floue.
      Concernant votre module et le hook displayAdminCustomers, quelle est la version de prestashop du développement ?
      En fonction l’approche n’est pas tout à fait la même ( si c’est encore un controller non symfony c’est plus simple )
      Il est assez facile de rajouter des js dans les anciens controllers admin via le hook actionAdminControllerSetMedia

      Cordialement,
      Hervé

      1. Bonjour Herve
        Merci de votre réponse, ma version de prestashop est la version 1.7.6.9.
        Et pour ma première question c’est que dans détails je n’arrive pas à avoir l’onglet du module tout simplement..
        Et par rapport a mon module c’est parce que dans mon hook displayAdminCustomers je n’ai que un appel de tpl
        « return $this->display(__FILE__, ‘customer.tpl’);  »
        il ne comporte que ça actuellement. Et dans customer.tpl j’ai mon html qui comporte juste un input pour la zone de texte (ou l’utilisateur doit saisir un texte )et un bouton valider. Sauf que j’ai beau cliquer sur mon bouton le js n’est pas executé même une fonction basique tel que une alert. Ca peut et doit provenir de mon code surement.
        CDT
        Jérémy

        1. Et j’ai une autre requete qui n’a rien a voir avec le module .
          Sur une version presta 1.7.7.4 j’ai un override de la classe product qui contient une fonction qui affiche le résultat d’une requette sql. je souhaite l’afficher dans un fichier qui est un fichier twig avec cette 1.7.7. Avant j’allais juste dans la view de order dans admins/template… et je faisais {Product::mafunction()}. Sauf que en twig le ficher src/PrestaShopBundle/Resources/views/Admin/Sell/Order/Order/Blocks/View/product.html.twig j’ai cru comprendre que nous ne pouvions pas faire d’appel de fonction php fin pas directement
          https://stackoverflow.com/questions/32920217/how-to-call-static-function-in-symfony2-twig-template#46636220
          Ce lien dit que je dois ajouter
          $viewVars[‘Product’] = new Product();
          $html = $twig->render(‘product.html.twig’, $viewVars); et dans mon twig un appel {{ Product.mafunction() }}
          Cependant je n’ai pas compris la partie avec les deux lignes car je créer un objet product pour que j’ajoute a la variable $twig qui n’est pas déclarer (ce qui me retourne une erreur) donc je voudrais savoir si vous avez une idée ou solution afin que je puisse appeler ma fonction php dans twig.
          Merci d’avance
          CDT
          Jérémy

          1. Bonjour Jérémy,
            Désolé mais cette demande reste floue.
            Pourquoi est-ce nécessaire de passer nécessairement pas des fichiers twig ?
            Il existe des hooks dans l’édition de la fiche produit qui vous permettent d’insérer vos propre contenus.
            Par ailleurs essayez de faire des commentaires en rapport avec les articles on est assez loin du sujet initial la.

            Cordialement,
            Hervé

  9. Bonjour Hervé, avez-vous réalisé un nouvel article pour la nouvelle façon de gérer les contrôleurs admin avec symfony pour la dernière version ? Merci pour votre réponse !

Répondre à herve Annuler la réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *