Prestashop : Modifier la liste d’un « AdminController » grâce au hook « action’ . $this->controller_name . ‘ListingResultsModifier »

Après avoir vu comment créer les base d’un module CRUD prestashop, puis quelques astuces pour adapter les fonctions en provenance du « ModuleAdminControllerCore » penchons nous à présent sur un besoin plus précis : Modifier le rendu « liste » de ce même module.

L’objet « ModuleTestTableTest » de notre module contient un champ « champ_int_test » qui comme son non l’indique va contenir un nombre entier. Comment faire si ce nombre correspond à la description d’un état (« En attente », »Traitement en cours », « Finalisé ») et que l’on veux afficher cette description compréhensible dans la liste plutôt que le nombre brut ? C’est un exemple simple qui permet de comprendre la façon de modifier ces listes, ensuite tout est possible !

A chaque fois qu’une liste est rendu (appel de la fonction « getList » du « AdminController » natif de prestashop, le hook suivant est exécuté : « ‘action’ . $this->controller_name . ‘ListingResultsModifier' » donc dans le cas de notre « moduleTest » et particulièrement de son Controller « AdminModuleTestController » il s’agira du hook « ActionAdminModuleTestListingResultsModifier ». Nous déclarons donc ce hook dans la fonction « __construct » de notre fichier « /moduletest/moduletest.php », à la suite de ce que l’on à déja :

        //hooks
        //modifications de la liste du "AdminModuleTest"
        $this->registerHook('ActionAdminModuleTestListingResultsModifier');

Nous devons ensuite créer dans ce même fichier la fonction qui va gérer ce hook. Voici son code ci-dessous. Elle reçoit un tableau en paramètre, ce tableau contient en particulier une clé « list » qui contient lui même un tableau de toutes les lignes de la liste. On va ainsi pouvoir boucler dessus et les modifier. Il contient aussi une clé « list_total » contenant ne nombre total de ligne, nous ne l’utilisons pas dans cette exemple.

    public function hookActionAdminModuletestListingResultsModifier($params) {
        //pour chaque ligne de la liste
        foreach ($params['list'] as $key => $row) {
            //si le numéro "champ_int_test" est inconnu dans la liste on ne change rien pour garder l'affichage du numéro d'origine
            //par contre si le numero correspond a une description de notre tableau "list_descriptions_int_test" on remplace le numéro par cette description
            if (isset(ModuleTestTableTest::$list_descriptions_int_test[$row['champ_int_test']])) {
                $params['list'][$key]['champ_int_test'] = ModuleTestTableTest::$list_descriptions_int_test[$row['champ_int_test']];
            }
        }
    }

Bien sur pour ne pas avoir d’erreur il nous faut créer dans notre fichier « /moduletest/classes/ModuleTestTableTest.php » la liste des descriptions correspondant au numéros. Rien de plus simple :

    public static $list_descriptions_int_test = array(
        0 => 'Initialisation',
        1 => 'En attente',
        2 => 'En traitement',
        3 => 'Terminé',
        100 => 'Echec'
    );

Prestashop : Comment modifier l’affichage et les actions d’un « ModuleAdminControllerCore »

Nous avons vu dans l’article précédent les bases d’un module Prestashop. Nous allons voir dans cet article comment faire des choses un peu plus poussé en modifiant notre controller qui étend « ModuleAdminControllerCore ». En continuant sur le module de test précédent nous seront donc situé dans le fichier « moduletest/controller/admin/AdminModuleTest.php »

Ajouter des boutons dans la toolbar
Par défaut la « header toolbar » contient seulement le bouton « aide », voici comment y ajouter des boutons. Nous ajoutons ici un bouton vers notre formulaire d’ajout (un bouton existe déjà dans la « toolbar » de la liste, mais il sera plus visible à ce niveau).

    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('Ajouter un test'),
            'icon' => 'process-icon-new'
        );
        parent::initPageHeaderToolbar();
    }

Bloquer une action existante par défaut
Par défaut différentes actions existe sur votre objet (ajouter, modifier, supprimer) même si les bouton ne sont pas présent les pages existent et sont accessible. Voici comment bloquer les droits d’accès :

    public function access($action, $disable = false) {
        //suppression des droit d'accés à la page d'ajout / edition
        if (in_array($action, array('add', 'edit'))) {
            return false;
        }
        return parent::access($action, $disable);
    }

Et maintenant la petite astuce pour ne pas afficher le bouton d’ajout dans la « toolbar » de la liste

    //suppression du bouton "ajouter"
    public function initToolbar() {
        parent::initToolbar();
        unset($this->toolbar_btn['new']);
    }

Permettre d’ajouter des case à cocher pour supprimer des lignes « en masse »
Dans notre fonction « __construct » il faut ajouter :

        //corps de la fonction
        //...
        //ajout de l'option suppression de masse
        $this->bulk_actions = array(
            'delete' => array(
                'text' => $this->l('Delete selected'),
                'icon' => 'icon-trash',
                'confirm' => $this->l('Delete selected items?'),
            ),
        );

Modifier le comportement d’une action
Il est facile de modifier le comportement des actions existantes (ajout, modification, suppression) en remplaçant (override) leur méthode. Ci dessous par exemple on modifie l’action de « suppression d’une ligne » pour supprimer aussi un fichier ayant l’id de la ligne si il existe.

    public function processDelete() {
        //suppression classique
        $res = parent::processDelete();
        //on appelle notre fonction de suppression du fichier
        $this->deleteFile($res);
        return $res;
    }

    //la fonction de suppression du fichier
    private function deleteFile($obj) {
        //si pas d'erreur
        if ($obj) {
            if (file_exists('/chemin/du/fichier/' . $obj->id)) {
                unlink('/chemin/du/fichier/' . $obj->id);
            }
        }
    }

Attention si vous voulez que l’action « suppression en masse » réalise aussi les suppression de fichiers, il faut alors adapter sa méthode.

    protected function processBulkDelete() {
        //pour chaque ligne à supprimer
        foreach ($this->boxes as $id) {
            //je récupère l’objet correspondant et j'utilise ma fonction de suppression du fichier
            $obj = new $this->className($id);
            $this->deleteFile($obj);
        }
        //action d'origine
        return parent::processBulkDelete();
    }

Ajouter du contenu au dessus ou en dessous de la liste
Pour personnaliser la page, par exemple ajouter un bloc de contenu avec une variable personnalisé et un bouton au dessus de la liste, nous allons utiliser un template. Il faut créer le fichier « moduletest/views/templates/admin/prev-content.tpl » qui contiendra ce code :

<div class="panel col-lg-12">
    <label>Variable custom :</label> {$variable_custom}
    <hr/>
    <button class="btn btn-primary">Bouton d'exemple</button>
</div>

Puis adapter dans notre controller la fonction « initContent » :

    public function initContent() {
        parent::initContent();
        //on charge notre template custom auquel on assigne une "variable_custom"
        $this->context->smarty->assign(array('variable_custom' => 'nanani-nanana'));
        $prevContent = $this->context->smarty->fetch(_PS_MODULE_DIR_ . 'moduletest/views/templates/admin/prev-content.tpl');
        //que l'on place devant le contenu pr défaut
        $this->context->smarty->assign(array('content' => $prevContent . $this->content));
    }

On peux aussi faire une vue complètement personnalisé, par exemple en utilisant le page « details ». En modifiant notre fonction « renderList » on ajoute un bouton « détails » pour chaque ligne.

    public function renderList() {
        //bouton détails et suppression
        $this->addRowAction('details');
        $this->addRowAction('delete');
        return parent::renderList();
    }

Et on adapte « initContent » qui devient :

    public function initContent() {
        if ($this->display == 'details') {
            //chargement de l'objet
            $this->object = $this->loadObject();
            //que l'on transmet au template
            $this->context->smarty->assign(array('object' => $this->object));
            //récupération du template perso
            $content = $this->context->smarty->fetch(_PS_MODULE_DIR_ . 'moduletest/views/templates/admin/details.tpl');
            $this->context->smarty->assign(array('content' => $content));
        } else {
            parent::initContent();
            //on charge notre template custom auquel on assigne une "variable_custom"
            $this->context->smarty->assign(array('variable_custom' => 'nanani-nanana'));
            $prevContent = $this->context->smarty->fetch(_PS_MODULE_DIR_ . 'moduletest/views/templates/admin/prev-content.tpl');
            //que l'on place devant le contenu pr défaut
            $this->context->smarty->assign(array('content' => $prevContent . $this->content));
        }
    }

De la même façon que précédemment on créer le template « moduletest/views/templates/admin/details.tpl » et pour l’exemple ici on y affiche de façon brute notre objet :

<div class="panel col-lg-12">
    <pre>{$object|print_r}</pre>
</div>

Et ce sera pas mal pour commencer.

Prestashop : Les bases d’un module CRUD

Cet article va présenter comment créer un « module de test » pour prestashop. L’objectif étant d’avoir une base de code sur laquelle on pourra partir pour ensuite répondre à des demandes plus concrètes. Ce module va créer une table dans la base de donnée et ajouter un menu dans le tableau d’administration de prestashop. Lors du clic sur ce menu on aura la possibilité de lire la table correspondante, y insérer/modifier ou supprimer des lignes. (CRUD)

Cet article contiendra peu d’explications (en dehors des commentaires dans le code) car il va surtout me servir de référence pour de prochains articles. Vous pouvez aussi consulter cet artice (en anglais) un peu plus détaillé : https://www.amauri.eng.br/en/blog/2016/03/developing-a-simple-module-with-crud-for-prestashop/

Pour commencer nous créons dans le dossier « modules » de prestashop notre dossier « moduletest » et dans ce dossier le fichier « moduletest.php » suivant :

//pour éviter un accès direct à ce fichier.
if (!defined('_PS_VERSION_')) {
    exit;
}

//on appelle le fichier de ce module "/classes/ModuleTestTableTest.php" que l'on va créer dans la partie suivante.
require_once dirname(__FILE__) . '/classes/ModuleTestTableTest.php';

class Moduletest extends Module
{
    //constructeur du module avec les informations à personnaliser.
    public function __construct(){
        $this->name = 'moduletest';
        $this->tab = 'administration';
        $this->version = '0.1.0';
        $this->author = 'auteur de test';
        $this->need_instance = 0;
        $this->ps_versions_compliancy = array('min' => '1.7', 'max' => _PS_VERSION_);

        parent::__construct();

        $this->displayName = $this->l('Nom du module de test');
        $this->description = $this->l('Description du module de test.');
    }

    //fonction d'installation du module
    public function install(){
        return parent::install() && $this->installSql() && $this->installTab();
    }

    //fonction de désinstallation du module
    public function uninstall(){
        return parent::uninstall() && $this->uninstallSql() && $this->uninstallTab();
    }

    //création de la table dans la base de données.
    protected function installSql(){
        $sqlCreate = "CREATE TABLE `" . _DB_PREFIX_ . ModuleTestTableTest::$definition["table"] . "` (
                `"
. ModuleTestTableTest::$definition["primary"] . "` int(11) unsigned NOT NULL AUTO_INCREMENT,
                `champ_varchar_test` varchar(255) DEFAULT NULL,
                `champ_date_test` DATETIME NOT NULL,
                `champ_int_test` int(11) unsigned NOT NULL,
                PRIMARY KEY (`"
. ModuleTestTableTest::$definition["primary"] . "`)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
;
        return Db::getInstance()->execute($sqlCreate);
    }

    //suppression de la table dans la base de données
    protected function uninstallSql(){
        $sql = "DROP TABLE " . _DB_PREFIX_ . ModuleTestTableTest::$definition["table"];
        return Db::getInstance()->execute($sql);
    }

    //création de l'onglet dans le menu de l'administration
    protected function installTab(){
        $tab = new Tab();
        $tab->class_name = 'AdminModuleTest';
        $tab->module = $this->name;
        $tab->icon = 'settings_applications';
        $tab->id_parent = (int) Tab::getIdFromClassName('DEFAULT');
        //
        $languages = Language::getLanguages();
        foreach ($languages as $lang) {
            $tab->name[$lang['id_lang']] = $this->displayName;
        }
        try {
            $tab->save();
        } catch (Exception $e) {
            echo $e->getMessage();
            return false;
        }

        return true;
    }

    //suppression de l'onglet dans le menu de l'admnistration.
    protected function uninstallTab(){
        $idTab = (int) Tab::getIdFromClassName('AdminModuleTest');
        if ($idTab) {
            $tab = new Tab($idTab);
            try {
                $tab->delete();
            } catch (Exception $e) {
                echo $e->getMessage();
                return false;
            }
        }
        return true;
    }
}

Créons maintenant le fichiers « /classes/ModuleTestTableTest.php » appelé au début du fichiers précédent. Ce fichier va hériter de « ObjectModel » de prestashop afin que l’on puisse gérer les données de façon « native » par la suite. Il va simplement servir à définir la table et les champs que notre module va ajouter à la base de données, et permettre ensuite de manipuler ces données.

//on définis les champs correspondant à ceux utilisé dans la fonction "installSql" du fichier "moduletest.php"
class ModuleTestTableTest extends ObjectModel {
    public $id;
    public $champ_varchar_test;
    public $champ_date_test;
    public $champ_int_test;
    public static $definition = array(
        'table' => 'module_test',
        'primary' => 'id_module_test',
        'multilang' => false,
        'fields' => array(
            'champ_varchar_test' => array(
                'type' => self::TYPE_STRING,
                'required' => true
            ),
            'champ_date_test' => array(
                'type' => self::TYPE_DATE,
                'required' => true
            ),
            'champ_int_test' => array(
                'type' => self::TYPE_INT,
                'required' => true
            ),
        )
    );
}

Il nous faut maintenant créer le « ModuleAdminControllerCore » nous permettant de gérer le données du module. Il nous faut créer le fichier « /controllers/admin/AdminModuleTest.php » (voir le nom indiqué dans les fonctions « installTab » et « uninstallTab » du fichier « moduletest.php ». Ce fichier hérite de « ModuleAdminControllerCore » et il nous reste peu de chose à faire pour que tout fonctionne :

//on appelle ici aussi notre classe "ObjectModel" que l'on va utiliser.
require_once _PS_MODULE_DIR_ . 'moduletest/classes/ModuleTestTableTest.php';

class AdminModuleTestController extends ModuleAdminControllerCore {

    //configuration de l'objet a utilisé et des champ à affiché
    public function __construct() {
        $this->bootstrap = true; //Gestion de l'affichage en mode bootstrap
        $this->table = ModuleTestTableTest::$definition['table']; //Table de l'objet
        $this->identifier = ModuleTestTableTest::$definition['primary']; //Clé primaire de l'objet
        $this->className = ModuleTestTableTest::class; //Classe de l'objet
        $this->lang = false; //Flag pour dire si utilisation de langues ou non
        $this->_defaultOrderBy = ModuleTestTableTest::$definition['primary'];
        //Appel de la fonction parente
        parent::__construct();
        //Liste des champs de l'objet à afficher dans la liste
        $this->fields_list = array(
            'id_module_test' => array(//nom du champ sql
                'title' => $this->module->l('ID'), //Titre
                'align' => 'center', // Alignement
                'class' => 'fixed-width-xs', //classe css de l'élément
            ),
            'champ_varchar_test' => array(
                'title' => $this->module->l('Texte'),
                'align' => 'left',
            ),
            'champ_date_test' => array(
                'title' => $this->module->l('Date'),
                'align' => 'left',
            ),
            'champ_int_test' => array(
                'title' => $this->module->l('Numéro'),
                'align' => 'left',
            ),
        );
    }

    //configuration du formulaire d'ajout/edition d'une ligne de la tabler
    //utiliser l'URL de votre admin + "index.php?controller=AdminPatterns" pour a liste des champs disponibles
    public function renderForm() {
        $this->fields_form = [
            'legend' => [
                'title' => $this->l('General Information'),
            ],
            'input' => [
                [
                    'type' => 'text',
                    'label' => $this->l('Texte'),
                    'name' => 'champ_varchar_test',
                    'required' => true,
                ],
                [
                    'type' => 'datetime',
                    'label' => $this->l('Date'),
                    'name' => 'champ_date_test',
                    'required' => true,
                ],
                [
                    'type' => 'text',
                    'label' => $this->l('Numéro'),
                    'name' => 'champ_int_test',
                    'required' => true,
                ],
            ],
            'submit' => [
                'title' => $this->l('Save'),
            ],
        ];
        return parent::renderForm();
    }

    //permet d'ajouter le bouton de suppression pour chaque ligne
    public function renderList() {
        $this->addRowAction('delete');
        return parent::renderList();
    }

}

Le module de type CRUD est maintenant opérationnel, on va regarder comment améliorer tout ça dans des articles à venir.

Prestashop (1.7) : Modifier des champs du formulaire de contact

Prestashop propose une page contenant un formulaire de contact afin que les visiteurs et utilisateurs de votre boutique puissent communiquer avec vous.
Cependant ce formulaire est peu personnalisable. Nous allons voir ci-dessous comment personnaliser ce formulaire. Notre exemple consistera en l’ajout d’un champ supplémentaire, mais la procédure permet permet de comprendre comment faire pour également supprimer ou modifier des champs existants.

Premièrement nous allons modifier le template du formulaire de contact afin de lui ajouter un champ.
Pour cela nous allons récupérer le template qui se situe dans votre thème (thème parent) ou bien directement dans le module « contactform » de prestashop.
Donc soit dans :

/themes/votre-theme-parent/modules/contactform/views/templates/widget/contactform.tpl

Soit dans

/modules/contactform/views/templates/widget/contactform.tpl

Et copier ce fichier dans votre theme enfant pour pouvoir le modifier proprement. C’est à dire dans :

/themes/votre-theme-enfant/modules/contactform/views/templates/widget/contactform.tpl

Vous pouvez maintenant faire vos modifications dans ce fichier. Par exemple pour ajouter un champ « Téléphone » (avec bien sur le html à adapter en fonction de votre thème et de la présentation souhaité) :

<div class="form-group row">
    <div class="col-md-12">
        <input class="form-control" name="phone" type="text" placeholder="Téléphone" value="{$contact.phone}">
    </div>
</div>

La seconde étape consiste à overrider le module contactform en étendant la classe « ContactForm » que vous trouverez dans le fichier :

/modules/contactform/contactform.php

Pour cela nous créons le fichiers suivant :

/override/modules/contactform/contactform.php

qui contiendra le code suivant (pour commencer) :

<?php

if (!defined('_PS_VERSION_')) {
    exit;
}

class ContactFormOverride extends Contactform {

//code à venir

}

On va à présent remplacer la ligne « //code à venir » en copiant telles quelles les 2 fonctions « getWidgetVariables » et « sendMessage » présentent dans le fichier d’origine :

/modules/contactform/contactform.php

On adapter les ligne de la fonction « getWidgetVariables » pour ajouter notre champs :

//...
//ajout de notre champ
$this->contact['phone'] = html_entity_decode(Tools::getValue('phone'));
//code d'origine
$this->contact['contacts'] = $this->getTemplateVarContact();
$this->contact['message'] = html_entity_decode(Tools::getValue('message'));
//etc...

Passons à la fonction « sendMessage » ou il va falloir modifier plusieurs partie du code, mais ces parties sont assez facile à repérer.
Il s’agit des 3 suivantes :

//debut de la fonction
$extension = array('.txt', '.rtf', '.doc', '.docx', '.pdf', '.zip', '.png', '.jpeg', '.gif', '.jpg');
$file_attachment = Tools::fileAttachment('fileUpload');
$message = trim(Tools::getValue('message'));
//ajout de notre champs
$phone = trim(Tools::getValue('phone'));
//...
//controle des différentes champs du formulaire
} elseif (!Validate::isCleanHtml($message)) {
    $this->context->controller->errors[] = $this->trans('Invalid message', array(), 'Shop.Notifications.Error');
} elseif (!Validate::isCleanHtml($phone)) {//contrôle de notre champ supplémentaire.
    $this->context->controller->errors[] = $this->trans('Téléphone invalide', array(), 'Shop.Notifications.Error');
}//....
$var_list = [
   '{order_name}' => '-',
   '{attached_file}' => '-',
   '{message}' => Tools::nl2br(stripslashes($message)),
   '{email}' => $from,
   '{product_name}' => '',
   '{phone}' => Tools::nl2br(stripslashes($phone)),//notre ajout
 ];

Il nous reste une dernière étape, il s’agit de modifier les templates correspondant à l’email transmis par le formulaire.
Vous pouvez retrouver ces template dans

/mails/fr/contact.html
/mails/fr/contact.txt

en adaptant la partie /fr/ selon votre language.
Vous devez copier ces fichier dans votre thème enfant :

/themes/votre-theme-enfant/mails/fr/contact.html
...

Et modifier leur contenus en ajoutant les informations concernant le nouveau champ.
Par exemple :

<span style="color:#333"><strong>Téléphone du client :</strong></span> {phone}<br /><br />

Normalement tout est bon !