[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"theme-db":3,"$frG4CgyAhd40U_jwaKKpPROwGJtO5_5RGr0erABcktB8":22,"megamenu":62,"$fKnz2vuX4bZz1LbUTiuFsvSZ3e07l5_5fqNYp4Tzdhi8":131,"$fM2ZB7SBDMxu6xNmLvMJ7FkuuBnpT1ORjjuql4FnWwRk":199,"header-db":213,"footer-db":226},{"theme":4},{"colors":5,"typography":13,"ui":17,"defaultColorMode":21},{"primary":6,"secondary":7,"background":8,"foreground":9,"muted":10,"headerBg":11,"footerBg":12,"topBarBg":9,"topBarText":11},"#4F46E5","#0D9488","#F9FAFB","#111827","#6B7280","#ffffff","#020617",{"fontFamily":14,"fontUrl":15,"baseFontSize":16},"Inter, system-ui, sans-serif","https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Inter:wght@400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;0,800;0,900;1,400;1,700&display=swap","16px",{"borderRadius":18,"contentWidth":19,"shadow":20},"lg","7xl",true,"light",{"title":23,"slug":24,"metaDescription":25,"category":26,"tags":27,"difficulty":33,"psVersions":34,"content":37,"faq":38,"tldr":57,"readingTime":58,"generatedAt":59,"publishDate":59,"relatedArticles":60,"sourceCategory":61},"Créer un module dashboard PrestaShop avec requêtes SQL personnalisées","module-dashboard-prestashop-requete-sql-personnalisee","Apprenez à développer un module PrestaShop qui affiche des données SQL personnalisées dans le tableau de bord : hook dashboardZoneTwo, requêtes optimisées et templates Smarty.","developpement",[28,29,30,31,32],"module prestashop","dashboard","requête SQL","hook dashboardZoneTwo","back-office","intermediaire",[35,36],"1.7","8.x","\u003Ch2>Pourquoi créer un widget dashboard personnalisé\u003C\u002Fh2>\n\u003Cp>Le tableau de bord PrestaShop affiche par défaut des indicateurs standards : chiffre d'affaires, commandes, visiteurs. Mais en tant que marchand ou développeur, vous avez souvent besoin d'y intégrer \u003Cstrong>vos propres données métier\u003C\u002Fstrong> — par exemple, le nombre de clients en attente de réapprovisionnement pour un produit, les produits les plus demandés en rupture de stock, ou tout autre indicateur issu d'une requête SQL spécifique.\u003C\u002Fp>\n\u003Cp>Dans cet article, nous allons construire un module complet qui s'intègre au tableau de bord via le hook \u003Ccode>dashboardZoneTwo\u003C\u002Fcode> et affiche les résultats d'une requête SQL dans un tableau propre et fonctionnel.\u003C\u002Fp>\n\u003Ch2>Architecture du module\u003C\u002Fh2>\n\u003Cp>Notre module suivra la structure standard PrestaShop :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-\">\nmy_dashboardwidget\u002F\n├── my_dashboardwidget.php\n├── views\u002F\n│   └── templates\u002F\n│       └── hook\u002F\n│           └── dashboard_zone_two.tpl\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Étape 1 : Le squelette du module\u003C\u002Fh2>\n\u003Cp>Commençons par la classe principale du module avec toutes les bonnes pratiques PrestaShop 8.x :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\n&lt;?php\n\nif (!defined('_PS_VERSION_')) {\n    exit;\n}\n\nclass My_DashboardWidget extends Module\n{\n    public function __construct()\n    {\n        $this-&gt;name = 'my_dashboardwidget';\n        $this-&gt;tab = 'dashboard';\n        $this-&gt;version = '1.0.0';\n        $this-&gt;author = 'Votre nom';\n        $this-&gt;need_instance = 0;\n        $this-&gt;bootstrap = true;\n\n        parent::__construct();\n\n        $this-&gt;displayName = $this-&gt;l('Widget Dashboard Personnalisé');\n        $this-&gt;description = $this-&gt;l('Affiche des données SQL personnalisées dans le tableau de bord.');\n        $this-&gt;ps_versions_compliancy = [\n            'min' =&gt; '1.7.0.0',\n            'max' =&gt; '8.99.99',\n        ];\n    }\n\n    public function install()\n    {\n        return parent::install()\n            && $this-&gt;registerHook('dashboardZoneTwo');\n    }\n\n    public function uninstall()\n    {\n        return parent::uninstall();\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\u003Cp>\u003Cstrong>Note PrestaShop 8.x :\u003C\u002Fstrong> La structure reste identique à la 1.7, mais veillez à utiliser la syntaxe array courte \u003Ccode>[]\u003C\u002Fcode> et à déclarer la compatibilité jusqu'à \u003Ccode>8.99.99\u003C\u002Fcode>.\u003C\u002Fp>\u003C\u002Fblockquote>\n\u003Ch2>Étape 2 : Le hook dashboardZoneTwo\u003C\u002Fh2>\n\u003Cp>PrestaShop expose plusieurs hooks pour le tableau de bord :\u003C\u002Fp>\n\u003Ctr>\u003Cth>Hook\u003C\u002Fth>\u003Cth>Position\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>`dashboardZoneOne`\u003C\u002Fth>\u003Cth>Colonne principale (haut)\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>`dashboardZoneTwo`\u003C\u002Fth>\u003Cth>Colonne principale (milieu)\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>`dashboardTop`\u003C\u002Fth>\u003Cth>Bandeau supérieur\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>`dashboardData`\u003C\u002Fth>\u003Cth>Données dynamiques (AJAX)\u003C\u002Fth>\u003C\u002Ftr>\n\u003Cp>Le hook \u003Ccode>dashboardZoneTwo\u003C\u002Fcode> est idéal pour un widget de type tableau de données.\u003C\u002Fp>\n\u003Ch2>Étape 3 : Écrire des requêtes SQL propres\u003C\u002Fh2>\n\u003Cp>Voici le point crucial. Prenons un cas concret : afficher les produits en rupture de stock les plus demandés par les clients (table \u003Ccode>ps_mailalert_customer_oos\u003C\u002Fcode>).\u003C\u002Fp>\n\u003Ch3>L'erreur classique : tout dans une seule requête\u003C\u002Fh3>\n\u003Cp>Beaucoup de développeurs tentent de tout récupérer en une seule requête complexe avec des sous-requêtes imbriquées. C'est souvent source de bugs et de problèmes de performance.\u003C\u002Fp>\n\u003Ch3>L'approche recommandée : décomposer\u003C\u002Fh3>\n\u003Cp>Décomposez en deux requêtes simples et lisibles :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\npublic function getOutOfStockAlerts()\n{\n    \u002F\u002F 1. Récupérer les produits distincts avec le nombre de demandes\n    $sql = new DbQuery();\n    $sql-&gt;select('ma.id_product, COUNT(ma.id_customer) AS nb_demandes');\n    $sql-&gt;from('mailalert_customer_oos', 'ma');\n    $sql-&gt;groupBy('ma.id_product');\n    $sql-&gt;orderBy('nb_demandes DESC');\n    $sql-&gt;limit(20);\n\n    $results = Db::getInstance(_PS_USE_SQL_SLAVE_)-&gt;executeS($sql);\n\n    if (empty($results)) {\n        return [];\n    }\n\n    \u002F\u002F 2. Enrichir chaque ligne avec les infos produit\n    $data = [];\n    $id_lang = (int) Context::getContext()-&gt;language-&gt;id;\n\n    foreach ($results as $row) {\n        $id_product = (int) $row['id_product'];\n\n        $sqlProduct = new DbQuery();\n        $sqlProduct-&gt;select('p.id_product, p.reference, pl.name');\n        $sqlProduct-&gt;from('product', 'p');\n        $sqlProduct-&gt;leftJoin(\n            'product_lang',\n            'pl',\n            'p.id_product = pl.id_product AND pl.id_lang = ' . $id_lang\n        );\n        $sqlProduct-&gt;where('p.id_product = ' . $id_product);\n\n        $product = Db::getInstance(_PS_USE_SQL_SLAVE_)-&gt;getRow($sqlProduct);\n\n        if ($product) {\n            $data[] = [\n                'id_product'  =&gt; $product['id_product'],\n                'reference'   =&gt; $product['reference'],\n                'name'        =&gt; $product['name'],\n                'nb_demandes' =&gt; (int) $row['nb_demandes'],\n            ];\n        }\n    }\n\n    return $data;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Version optimisée avec une seule requête JOIN\u003C\u002Fh3>\n\u003Cp>Si la performance est critique (catalogue volumineux), une requête unique avec \u003Ccode>JOIN\u003C\u002Fcode> et \u003Ccode>GROUP BY\u003C\u002Fcode> sera plus efficace :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\npublic function getOutOfStockAlertsOptimized()\n{\n    $id_lang = (int) Context::getContext()-&gt;language-&gt;id;\n    $id_shop = (int) Context::getContext()-&gt;shop-&gt;id;\n\n    $sql = new DbQuery();\n    $sql-&gt;select('p.id_product, p.reference, pl.name, COUNT(ma.id_customer) AS nb_demandes');\n    $sql-&gt;from('mailalert_customer_oos', 'ma');\n    $sql-&gt;innerJoin('product', 'p', 'ma.id_product = p.id_product');\n    $sql-&gt;innerJoin(\n        'product_lang',\n        'pl',\n        'p.id_product = pl.id_product AND pl.id_lang = ' . $id_lang . ' AND pl.id_shop = ' . $id_shop\n    );\n    $sql-&gt;groupBy('p.id_product, p.reference, pl.name');\n    $sql-&gt;orderBy('nb_demandes DESC');\n    $sql-&gt;limit(20);\n\n    return Db::getInstance(_PS_USE_SQL_SLAVE_)-&gt;executeS($sql);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\u003Cp>\u003Cstrong>Bonne pratique :\u003C\u002Fstrong> Utilisez toujours \u003Ccode>DbQuery\u003C\u002Fcode> plutôt que des requêtes SQL brutes. La classe gère automatiquement le préfixe des tables et offre une meilleure lisibilité. Utilisez \u003Ccode>_PS_USE_SQL_SLAVE_\u003C\u002Fcode> pour les requêtes en lecture afin de ne pas surcharger le serveur principal.\u003C\u002Fp>\u003C\u002Fblockquote>\n\u003Ch2>Étape 4 : Le hook complet avec template Smarty\u003C\u002Fh2>\n\u003Cp>Intégrons maintenant la récupération de données dans le hook :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\npublic function hookDashboardZoneTwo()\n{\n    $data = $this-&gt;getOutOfStockAlertsOptimized();\n\n    $this-&gt;context-&gt;smarty-&gt;assign([\n        'oos_alerts'   =&gt; $data,\n        'module_name'  =&gt; $this-&gt;displayName,\n        'admin_link'   =&gt; $this-&gt;context-&gt;link-&gt;getAdminLink('AdminProducts'),\n    ]);\n\n    return $this-&gt;display(__FILE__, 'views\u002Ftemplates\u002Fhook\u002Fdashboard_zone_two.tpl');\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Étape 5 : Le template Smarty\u003C\u002Fh2>\n\u003Cp>Créez le fichier \u003Ccode>views\u002Ftemplates\u002Fhook\u002Fdashboard_zone_two.tpl\u003C\u002Fcode> :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-smarty\">\n&lt;section id=\"dashboardwidget_oos\" class=\"panel widget\"&gt;\n    &lt;header class=\"panel-heading\"&gt;\n        &lt;i class=\"icon-bell\"&gt;&lt;\u002Fi&gt; {$module_name} — Produits les plus demandés en rupture\n    &lt;\u002Fheader&gt;\n    &lt;div class=\"panel-body\"&gt;\n        {if $oos_alerts|count &gt; 0}\n            &lt;table class=\"table table-striped table-hover\"&gt;\n                &lt;thead&gt;\n                    &lt;tr&gt;\n                        &lt;th&gt;ID&lt;\u002Fth&gt;\n                        &lt;th&gt;Référence&lt;\u002Fth&gt;\n                        &lt;th&gt;Nom du produit&lt;\u002Fth&gt;\n                        &lt;th class=\"text-center\"&gt;Demandes&lt;\u002Fth&gt;\n                        &lt;th&gt;Action&lt;\u002Fth&gt;\n                    &lt;\u002Ftr&gt;\n                &lt;\u002Fthead&gt;\n                &lt;tbody&gt;\n                    {foreach $oos_alerts as $alert}\n                        &lt;tr&gt;\n                            &lt;td&gt;{$alert.id_product}&lt;\u002Ftd&gt;\n                            &lt;td&gt;&lt;code&gt;{$alert.reference|escape:'html'}&lt;\u002Fcode&gt;&lt;\u002Ftd&gt;\n                            &lt;td&gt;{$alert.name|escape:'html'}&lt;\u002Ftd&gt;\n                            &lt;td class=\"text-center\"&gt;\n                                &lt;span class=\"badge badge-warning\"&gt;{$alert.nb_demandes}&lt;\u002Fspan&gt;\n                            &lt;\u002Ftd&gt;\n                            &lt;td&gt;\n                                &lt;a href=\"{$admin_link}&id_product={$alert.id_product}&updateproduct\"\n                                   class=\"btn btn-default btn-xs\"&gt;\n                                    &lt;i class=\"icon-pencil\"&gt;&lt;\u002Fi&gt; Éditer\n                                &lt;\u002Fa&gt;\n                            &lt;\u002Ftd&gt;\n                        &lt;\u002Ftr&gt;\n                    {\u002Fforeach}\n                &lt;\u002Ftbody&gt;\n            &lt;\u002Ftable&gt;\n        {else}\n            &lt;div class=\"alert alert-info\"&gt;\n                Aucune alerte de rupture de stock en cours.\n            &lt;\u002Fdiv&gt;\n        {\u002Fif}\n    &lt;\u002Fdiv&gt;\n&lt;\u002Fsection&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Étape 6 : Ajouter le rafraîchissement AJAX (optionnel)\u003C\u002Fh2>\n\u003Cp>Pour que votre widget se mette à jour dynamiquement quand on change la plage de dates du dashboard, implémentez le hook \u003Ccode>dashboardData\u003C\u002Fcode> :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\npublic function install()\n{\n    return parent::install()\n        && $this-&gt;registerHook('dashboardZoneTwo')\n        && $this-&gt;registerHook('dashboardData');\n}\n\npublic function hookDashboardData($params)\n{\n    \u002F\u002F Récupérer les dates du filtre dashboard\n    $date_from = $params['date_from'] ?? date('Y-m-d', strtotime('-30 days'));\n    $date_to = $params['date_to'] ?? date('Y-m-d');\n\n    $data = $this-&gt;getAlertsByDateRange($date_from, $date_to);\n\n    return [\n        'data_value' =&gt; [\n            'oos_total' =&gt; count($data),\n        ],\n        'data_chart' =&gt; [],\n    ];\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Bonnes pratiques et pièges à éviter\u003C\u002Fh2>\n\u003Ch3>Sécurité\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>**Ne jamais injecter de variables utilisateur** directement dans vos requêtes. Utilisez `(int)` pour les identifiants et `pSQL()` pour les chaînes.\u003C\u002Fli>\n\u003Cli>Le hook dashboard est exécuté dans le contexte back-office, mais un module mal sécurisé pourrait exposer des données sensibles via AJAX.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Performance\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>Limitez toujours vos résultats avec `$sql->limit()`. Un tableau de 10 000 lignes dans le dashboard va tuer l'expérience utilisateur.\u003C\u002Fli>\n\u003Cli>Utilisez `_PS_USE_SQL_SLAVE_` pour les lectures.\u003C\u002Fli>\n\u003Cli>Si vos données changent peu, envisagez un cache avec `Cache::getInstance()->set()`.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Compatibilité PrestaShop 8.x\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>Les hooks dashboard fonctionnent de manière identique en 1.7 et 8.x.\u003C\u002Fli>\n\u003Cli>En PrestaShop 8.x, Symfony est plus intégré. Pour un dashboard plus avancé, vous pouvez créer un **AdminController Symfony** avec des composants Vue.js ou React via le nouveau système de composants du back-office.\u003C\u002Fli>\n\u003Cli>La classe `DbQuery` reste pleinement supportée en 8.x et constitue la méthode recommandée.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>Code complet du module\u003C\u002Fh2>\n\u003Cp>Voici le fichier principal complet, prêt à installer :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\n&lt;?php\n\nif (!defined('_PS_VERSION_')) {\n    exit;\n}\n\nclass My_DashboardWidget extends Module\n{\n    public function __construct()\n    {\n        $this-&gt;name = 'my_dashboardwidget';\n        $this-&gt;tab = 'dashboard';\n        $this-&gt;version = '1.0.0';\n        $this-&gt;author = 'Votre nom';\n        $this-&gt;need_instance = 0;\n        $this-&gt;bootstrap = true;\n\n        parent::__construct();\n\n        $this-&gt;displayName = $this-&gt;l('Widget Dashboard Personnalisé');\n        $this-&gt;description = $this-&gt;l('Affiche des données SQL personnalisées dans le tableau de bord.');\n        $this-&gt;ps_versions_compliancy = [\n            'min' =&gt; '1.7.0.0',\n            'max' =&gt; '8.99.99',\n        ];\n    }\n\n    public function install()\n    {\n        return parent::install()\n            && $this-&gt;registerHook('dashboardZoneTwo');\n    }\n\n    public function uninstall()\n    {\n        return parent::uninstall();\n    }\n\n    public function getOutOfStockAlerts()\n    {\n        $id_lang = (int) Context::getContext()-&gt;language-&gt;id;\n        $id_shop = (int) Context::getContext()-&gt;shop-&gt;id;\n\n        $sql = new DbQuery();\n        $sql-&gt;select('p.id_product, p.reference, pl.name, COUNT(ma.id_customer) AS nb_demandes');\n        $sql-&gt;from('mailalert_customer_oos', 'ma');\n        $sql-&gt;innerJoin('product', 'p', 'ma.id_product = p.id_product');\n        $sql-&gt;innerJoin(\n            'product_lang',\n            'pl',\n            'p.id_product = pl.id_product AND pl.id_lang = ' . $id_lang . ' AND pl.id_shop = ' . $id_shop\n        );\n        $sql-&gt;groupBy('p.id_product, p.reference, pl.name');\n        $sql-&gt;orderBy('nb_demandes DESC');\n        $sql-&gt;limit(20);\n\n        return Db::getInstance(_PS_USE_SQL_SLAVE_)-&gt;executeS($sql);\n    }\n\n    public function hookDashboardZoneTwo()\n    {\n        $data = $this-&gt;getOutOfStockAlerts();\n\n        $this-&gt;context-&gt;smarty-&gt;assign([\n            'oos_alerts'  =&gt; $data,\n            'module_name' =&gt; $this-&gt;displayName,\n            'admin_link'  =&gt; $this-&gt;context-&gt;link-&gt;getAdminLink('AdminProducts'),\n        ]);\n\n        return $this-&gt;display(__FILE__, 'views\u002Ftemplates\u002Fhook\u002Fdashboard_zone_two.tpl');\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>",[39,42,45,48,51,54],{"q":40,"a":41},"Quel hook utiliser pour afficher un widget dans le tableau de bord PrestaShop ?","PrestaShop propose plusieurs hooks pour le dashboard : dashboardZoneOne (haut de page), dashboardZoneTwo (zone centrale), dashboardTop (bandeau supérieur) et dashboardData (pour les données dynamiques en AJAX). Le hook dashboardZoneTwo est le plus courant pour afficher un tableau de données personnalisé. Il suffit de l'enregistrer dans la méthode install() de votre module avec registerHook('dashboardZoneTwo'), puis d'implémenter la méthode hookDashboardZoneTwo() qui retourne le HTML à afficher.",{"q":43,"a":44},"Comment exécuter une requête SQL personnalisée dans un module PrestaShop ?","Utilisez la classe DbQuery pour construire vos requêtes de manière sécurisée. Exemple : $sql = new DbQuery(); $sql->select('p.id_product, pl.name'); $sql->from('product', 'p'); puis $results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); pour récupérer un tableau de résultats. Utilisez getRow() pour une seule ligne, getValue() pour une seule valeur. Ne concaténez jamais de variables utilisateur directement : castez les entiers avec (int) et échappez les chaînes avec pSQL().",{"q":46,"a":47},"Comment afficher les produits en rupture de stock les plus demandés dans PrestaShop ?","Le module mailalerts de PrestaShop stocke les demandes de notification dans la table ps_mailalert_customer_oos. Pour identifier les produits les plus demandés, faites un GROUP BY sur id_product avec un COUNT des clients, puis joignez avec ps_product et ps_product_lang pour obtenir le nom et la référence. Affichez le résultat dans un widget dashboard via le hook dashboardZoneTwo. Limitez toujours les résultats (LIMIT 20) pour ne pas surcharger l'interface.",{"q":49,"a":50},"Quelle est la différence entre Db::getInstance() et Db::getInstance(_PS_USE_SQL_SLAVE_) ?","Db::getInstance() se connecte au serveur de base de données principal (master), utilisé pour les écritures (INSERT, UPDATE, DELETE). Db::getInstance(_PS_USE_SQL_SLAVE_) se connecte au serveur de lecture (slave) si votre infrastructure en dispose. En pratique, sur une installation mono-serveur, les deux pointent vers la même base. Cependant, utiliser _PS_USE_SQL_SLAVE_ pour vos requêtes SELECT est une bonne pratique : si un jour vous mettez en place une réplication, votre code sera déjà optimisé.",{"q":52,"a":53},"Mon widget dashboard PrestaShop ne s'affiche pas, que vérifier ?","Vérifiez ces points dans l'ordre : 1) Le module est bien installé et actif dans le back-office. 2) Le hook est correctement enregistré (vérifiez dans Apparence > Positions que votre module apparaît sur dashboardZoneTwo). 3) La méthode hookDashboardZoneTwo() retourne bien une chaîne HTML (et non void). 4) Le fichier template .tpl existe au bon chemin (views\u002Ftemplates\u002Fhook\u002F). 5) Videz le cache PrestaShop. Si le hook n'apparaît pas dans Positions, réinitialisez-le manuellement : allez dans Modules, désinstallez puis réinstallez le module, ou utilisez $this->registerHook('dashboardZoneTwo') via un script.",{"q":55,"a":56},"Comment rendre un widget dashboard compatible PrestaShop 1.7 et 8.x ?","Les hooks dashboard (dashboardZoneOne, dashboardZoneTwo, dashboardData) fonctionnent de manière identique sur PrestaShop 1.7 et 8.x. Pour assurer la compatibilité, déclarez ps_versions_compliancy avec min 1.7.0.0 et max 8.99.99, utilisez la classe DbQuery plutôt que des requêtes SQL brutes, et restez sur les templates Smarty pour le rendu. Évitez les fonctions dépréciées comme Tools::jsonEncode() (remplacée par json_encode natif en 8.x) et testez sur les deux versions avant de distribuer votre module.","Créez un module PrestaShop qui affiche des données SQL personnalisées dans le tableau de bord via le hook dashboardZoneTwo, en utilisant DbQuery pour des requêtes sécurisées et un template Smarty pour le rendu.",6,"2026-03-21T13:47:19.000Z",[],"PrestaShop pour les développeurs",{"items":63},[64,75,83,91,99,108,116,123],{"id":65,"type":66,"label":67,"href":69,"icon":70,"description":70,"badge":70,"groupTitle":70,"style":70,"gridColumns":70,"cssClass":70,"psCategoryId":70,"showPsChildren":71,"position":72,"children":73,"psChildren":74},41,"link",{"fr":68},"Expertise","\u002Fexpertise",null,false,0,[],[],{"id":76,"type":66,"label":77,"href":79,"icon":70,"description":70,"badge":70,"groupTitle":70,"style":70,"gridColumns":70,"cssClass":70,"psCategoryId":70,"showPsChildren":71,"position":80,"children":81,"psChildren":82},42,{"fr":78},"Blog","\u002Fblog",1,[],[],{"id":84,"type":66,"label":85,"href":87,"icon":70,"description":70,"badge":70,"groupTitle":70,"style":70,"gridColumns":70,"cssClass":70,"psCategoryId":70,"showPsChildren":71,"position":88,"children":89,"psChildren":90},43,{"fr":86},"Modules PrestaShop","\u002Fmodules",2,[],[],{"id":92,"type":66,"label":93,"href":95,"icon":70,"description":70,"badge":70,"groupTitle":70,"style":70,"gridColumns":70,"cssClass":70,"psCategoryId":70,"showPsChildren":71,"position":96,"children":97,"psChildren":98},44,{"fr":94},"Outils IA","\u002Foutils-ia",3,[],[],{"id":100,"type":66,"label":101,"href":103,"icon":70,"description":70,"badge":70,"groupTitle":70,"style":104,"gridColumns":70,"cssClass":70,"psCategoryId":70,"showPsChildren":71,"position":105,"children":106,"psChildren":107},45,{"fr":102},"Offre Starter ✨","\u002Foffre-starter",{"highlight":20},4,[],[],{"id":109,"type":66,"label":110,"href":112,"icon":70,"description":70,"badge":70,"groupTitle":70,"style":70,"gridColumns":70,"cssClass":70,"psCategoryId":70,"showPsChildren":71,"position":113,"children":114,"psChildren":115},46,{"fr":111},"Academy","\u002Facademy",5,[],[],{"id":117,"type":66,"label":118,"href":120,"icon":70,"description":70,"badge":70,"groupTitle":70,"style":70,"gridColumns":70,"cssClass":70,"psCategoryId":70,"showPsChildren":71,"position":58,"children":121,"psChildren":122},47,{"fr":119},"À propos","\u002Fa-propos",[],[],{"id":124,"type":66,"label":125,"href":127,"icon":70,"description":70,"badge":70,"groupTitle":70,"style":70,"gridColumns":70,"cssClass":70,"psCategoryId":70,"showPsChildren":71,"position":128,"children":129,"psChildren":130},48,{"fr":126},"Contact","\u002Fcontact",7,[],[],{"columns":132},[133,145,175,191],{"title":134,"links":135},"Plateforme",[136,138,141,142],{"label":137,"href":103,"external":71},"Offre Starter (2 500 €)",{"label":139,"href":140,"external":71},"Devenir Ambassadeur","\u002Fambassadeur",{"label":86,"href":87,"external":71},{"label":143,"href":144,"external":20},"CodeMyShop.com","https:\u002F\u002Fcodemyshop.com",{"title":146,"links":147},"Le Synedre",[148,151,154,157,160,163,166,169,172],{"label":149,"href":150,"external":71},"L'histoire","\u002Fsynedre",{"label":152,"href":153,"external":71},"Constitution","\u002Fsynedre\u002Fconstitution",{"label":155,"href":156,"external":71},"L'équipe","\u002Fequipe",{"label":158,"href":159,"external":71},"Le réacteur en direct","\u002Freacteur",{"label":161,"href":162,"external":71},"Le Drill (entraînement)","\u002Fdrill",{"label":164,"href":165,"external":71},"Protocole de réunion","\u002Fsynedre\u002Freunion",{"label":167,"href":168,"external":71},"Les agents IA","\u002Fagents-ia",{"label":170,"href":171,"external":71},"La Conduite","\u002Fsynedre\u002Fconduite",{"label":173,"href":174,"external":71},"Charte plateforme","\u002Fsynedre\u002Fcharte",{"title":176,"links":177},"Ressources",[178,179,180,183,185,188],{"label":78,"href":79,"external":71},{"label":111,"href":112,"external":71},{"label":181,"href":182,"external":71},"Dictionnaire","\u002Fdictionnaire",{"label":184,"href":69,"external":71},"Expertise PrestaShop",{"label":186,"href":187,"external":71},"Flywheel","\u002Fflywheel",{"label":189,"href":190,"external":71},"Manifeste","\u002Fmanifeste",{"title":119,"links":192},[193,195,198],{"label":194,"href":120,"external":71},"Alexandre Carette",{"label":196,"href":197,"external":71},"Dossier de presse","\u002Fpresse",{"label":126,"href":127,"external":71},{"academy":200,"blog":201,"expertise":212},[],[202,206,209],{"title":203,"url":204,"score":80,"type":205},"PrestaShop headless avec Nuxt 3 : pourquoi séparer back et front","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-separation-front-back","blog",{"title":207,"url":208,"score":80,"type":205},"PrestaShop headless : Nuxt 3, pas Next.js — le choix souverain","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-nextjs-souverainete",{"title":210,"url":211,"score":80,"type":205},"Sylius rachète PrestaShop : ce que ça change pour vous","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fsylius-rachat-prestashop-headless-souverainete",[],{"header":214},{"logo":215,"topBar":220,"contactEmail":223,"features":224,"navBar":70},{"src":216,"alt":217,"text":194,"href":218,"class":219},"\u002Flogo-ac.svg","Alexandre Carette — Architecte E-commerce Souverain","\u002F","h-10 w-10",{"message":70,"showLanguages":71,"align":221,"languages":222},"left",[],"contact@alexandrecarette.fr",{"showSearch":71,"showWishlist":71,"showLogin":20,"showContact":71,"showCart":71,"stickyHeader":20,"headerLayout":225},"inline",{"footer":227},{"theme":228,"description":70,"hours":70,"logo":229,"contact":230,"social":231,"bottomBar":241},"dark",{"src":216,"href":218,"alt":194},{"email":70,"phone":70,"address":70,"cta":70},[232,235,238],{"platform":233,"href":234,"label":233},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-carette\u002F",{"platform":236,"href":237,"label":236},"malt","https:\u002F\u002Fwww.malt.fr\u002Fprofile\u002Falexandrecarette",{"platform":239,"href":240,"label":239},"github","https:\u002F\u002Fgithub.com\u002Fprest4cafe",{"copyright":70}]