From 349e282b264c0b65d87462f596f106609f8aa10d Mon Sep 17 00:00:00 2001 From: mattgoud Date: Fri, 17 Jul 2026 16:03:06 +0200 Subject: [PATCH 1/3] Add dashexample module demonstrating the new Symfony dashboard hooks Registers displayAdminDashboardZoneOne, displayAdminDashboardZoneTwo and displayAdminDashboardToolbar, rendering content through module Twig templates (no Smarty, no HelperForm, no Db::getInstance()) and loading its own assets from its hook output (no actionAdminControllerSetMedia / class-name detection). Serves as living documentation of the new integration contract and as a validation vehicle for the migrated dashboard (PrestaShop/PrestaShop#41968). --- README.md | 1 + dashexample/.gitignore | 3 + dashexample/README.md | 88 ++++++++++++++ dashexample/composer.json | 17 +++ dashexample/config.xml | 11 ++ dashexample/dashexample.php | 111 ++++++++++++++++++ dashexample/views/css/dashexample.css | 25 ++++ dashexample/views/js/dashexample.js | 13 ++ .../views/templates/admin/toolbar.html.twig | 19 +++ .../views/templates/admin/zone_one.html.twig | 22 ++++ .../views/templates/admin/zone_two.html.twig | 24 ++++ 11 files changed, 334 insertions(+) create mode 100644 dashexample/.gitignore create mode 100644 dashexample/README.md create mode 100644 dashexample/composer.json create mode 100644 dashexample/config.xml create mode 100644 dashexample/dashexample.php create mode 100644 dashexample/views/css/dashexample.css create mode 100644 dashexample/views/js/dashexample.js create mode 100644 dashexample/views/templates/admin/toolbar.html.twig create mode 100644 dashexample/views/templates/admin/zone_one.html.twig create mode 100644 dashexample/views/templates/admin/zone_two.html.twig diff --git a/README.md b/README.md index 2bbda467..9cf3017a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Most of these modules have been optimized and made compatible for PrestaShop v9. | Module name | Description | Minimum version | |----------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| | [api_module](https://github.com/PrestaShop/example-modules/tree/master/api_module) | This example module demonstrates how to modify PrestaShop's new API. | v9.0.0 | +| [dashexample](https://github.com/PrestaShop/example-modules/tree/master/dashexample) | This module demonstrates how to integrate with the migrated Symfony dashboard page through its new dedicated hooks. | v9.2.0 | | [demo_grid](https://github.com/PrestaShop/example-modules/tree/master/demo_grid) | This module demonstrates how to use Grid in PrestaShop | v9.0.0 | | [democonsolecommand](https://github.com/PrestaShop/example-modules/tree/master/democonsolecommand) | Example module showing how to implement a Symfony console command | v9.0.0 | | [democontrollertabs](https://github.com/PrestaShop/example-modules/tree/master/democontrollertabs) | Demo Creating modern Controllers and associate Tabs to them | v9.0.0 | diff --git a/dashexample/.gitignore b/dashexample/.gitignore new file mode 100644 index 00000000..61064cbd --- /dev/null +++ b/dashexample/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +.idea diff --git a/dashexample/README.md b/dashexample/README.md new file mode 100644 index 00000000..886876a6 --- /dev/null +++ b/dashexample/README.md @@ -0,0 +1,88 @@ +# Dashboard example (`dashexample`) + +Demonstration module for the **migrated (Symfony) Back Office Dashboard** and its new dedicated hook family. + +It is both living documentation of the integration contract and a validation vehicle: once installed with the `dashboard` feature flag enabled, it renders content in Zone One, Zone Two and the toolbar area of the new dashboard page. + +## What it demonstrates + +- Registering on the **new** dashboard hooks: `displayAdminDashboardZoneOne`, `displayAdminDashboardZoneTwo`, `displayAdminDashboardToolbar`. +- Rendering hook content through **module Twig templates** (`views/templates/admin/*.html.twig`) — no Smarty, no `HelperForm`, no `Db::getInstance()`. +- Passing and using hook **parameters** (`date_from` / `date_to`, the employee stats date range). +- Loading the module's **own CSS/JS assets from its hook output** (see `toolbar.html.twig`) — no `actionAdminControllerSetMedia`, no `get_class($this->context->controller)` detection. + +## Requirements + +- PrestaShop **9.2.0** or later (the version that introduces the migrated dashboard and its hooks). +- The **`dashboard` feature flag** enabled: *Advanced Parameters > New & Experimental Features > Dashboard page*. + +## Install + +```bash +# from the PrestaShop root +php bin/console prestashop:module install dashexample +``` + +Then enable the `dashboard` feature flag and open the Dashboard. You should see the module's blocks in the two zones and a marker in the toolbar area. + +## New vs legacy hook families + +The migrated dashboard deliberately uses a **new hook family**, distinct from the legacy one. A module knows which architecture it is integrating with purely from **which hook is called** — there is no version detection to do on the module side. + +| New (Symfony dashboard) | Legacy (legacy dashboard) | Area | +|---|---|---| +| `displayAdminDashboardZoneOne` | `dashboardZoneOne` | Left column | +| `displayAdminDashboardZoneTwo` | `dashboardZoneTwo` | Center column | +| `displayAdminDashboardZoneThree` | `dashboardZoneThree` | Right column | +| `displayAdminDashboardTop` | `displayDashboardTop` | Top area | +| `displayAdminDashboardToolbar` | `displayDashboardToolbarTopMenu` | Toolbar | + +The legacy hooks are untouched and keep working on the legacy page (flag off). + +## Supporting both PrestaShop versions in one module + +To render on **both** the legacy and the migrated dashboard from a single module, register on **both** hook families and let each callback render with the matching architecture. The core calls only the hook that belongs to the currently displayed page, so the two never collide. + +```php +public function install(): bool +{ + return parent::install() + && $this->registerHook([ + // Migrated (Symfony) dashboard — Twig, no legacy helpers + 'displayAdminDashboardZoneOne', + 'displayAdminDashboardZoneTwo', + 'displayAdminDashboardToolbar', + // Legacy dashboard — Smarty / HelperForm as before + 'dashboardZoneOne', + 'dashboardZoneTwo', + 'displayDashboardToolbarTopMenu', + ]); +} + +// Called only on the migrated page +public function hookDisplayAdminDashboardZoneOne(array $params): string +{ + return $this->get('twig')->render('@Modules/mymodule/views/templates/admin/zone_one.html.twig', $params); +} + +// Called only on the legacy page +public function hookDashboardZoneOne(array $params): string +{ + // legacy Smarty rendering + $this->smarty->assign($params); + + return $this->display(__FILE__, 'views/templates/hook/zone_one.tpl'); +} +``` + +This module registers **only the new hooks** on purpose, so it also serves as a focused test of the new contract. + +## Files + +| File | Role | +|---|---| +| `dashexample.php` | Module class: hook registration + hook callbacks rendering Twig | +| `views/templates/admin/zone_one.html.twig` | Zone One block | +| `views/templates/admin/zone_two.html.twig` | Zone Two block | +| `views/templates/admin/toolbar.html.twig` | Toolbar block + asset loading | +| `views/css/dashexample.css`, `views/js/dashexample.js` | Module-owned assets | diff --git a/dashexample/composer.json b/dashexample/composer.json new file mode 100644 index 00000000..4bc4ce0f --- /dev/null +++ b/dashexample/composer.json @@ -0,0 +1,17 @@ +{ + "name": "prestashop/dashexample", + "description": "Demonstration of the new dedicated hooks of the migrated Symfony dashboard page.", + "license": "AFL-3.0", + "authors": [ + { + "name": "PrestaShop Core Team" + } + ], + "require": { + "php": ">=8.1" + }, + "config": { + "prepend-autoloader": false + }, + "type": "prestashop-module" +} diff --git a/dashexample/config.xml b/dashexample/config.xml new file mode 100644 index 00000000..759aaae4 --- /dev/null +++ b/dashexample/config.xml @@ -0,0 +1,11 @@ + + + dashexample + + + + + + 0 + 0 + diff --git a/dashexample/dashexample.php b/dashexample/dashexample.php new file mode 100644 index 00000000..23678228 --- /dev/null +++ b/dashexample/dashexample.php @@ -0,0 +1,111 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +declare(strict_types=1); + +use Twig\Environment; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Demonstrates how a module integrates with the migrated (Symfony) Back Office Dashboard. + * + * The Symfony dashboard dispatches a dedicated hook family (`displayAdminDashboard*`), distinct + * from the legacy `dashboardZone*` / `displayDashboard*` hooks. A module therefore knows which + * architecture it is integrating with purely from which hook is called — no version detection, + * no `get_class($controller)` check, no Smarty, no `HelperForm`, no `Db::getInstance()`. + * + * See README.md for how to stay compatible with the legacy dashboard at the same time. + */ +class DashExample extends Module +{ + public function __construct() + { + $this->name = 'dashexample'; + $this->author = 'PrestaShop'; + $this->version = '1.0.0'; + $this->ps_versions_compliancy = ['min' => '9.2.0', 'max' => '9.99.99']; + $this->need_instance = 0; + + parent::__construct(); + + $this->displayName = $this->l('Dashboard example'); + $this->description = $this->l('Demonstration of the new dedicated hooks of the migrated Symfony dashboard page.'); + } + + public function install(): bool + { + return parent::install() + && $this->registerHook([ + 'displayAdminDashboardZoneOne', + 'displayAdminDashboardZoneTwo', + 'displayAdminDashboardToolbar', + ]); + } + + /** + * Renders a block in the first (left) column of the Symfony dashboard. + * Receives the employee date range selected on the page. + */ + public function hookDisplayAdminDashboardZoneOne(array $params): string + { + return $this->render('zone_one.html.twig', [ + 'dateFrom' => $params['date_from'] ?? null, + 'dateTo' => $params['date_to'] ?? null, + ]); + } + + /** + * Renders a block in the second (center) column of the Symfony dashboard. + */ + public function hookDisplayAdminDashboardZoneTwo(array $params): string + { + return $this->render('zone_two.html.twig', [ + 'dateFrom' => $params['date_from'] ?? null, + 'dateTo' => $params['date_to'] ?? null, + ]); + } + + /** + * Renders content in the toolbar area of the Symfony dashboard. + * + * This hook is rendered once at the top of the page, so the module loads its own + * assets from here (via its hook output) instead of `actionAdminControllerSetMedia`. + */ + public function hookDisplayAdminDashboardToolbar(array $params): string + { + return $this->render('toolbar.html.twig', [ + 'moduleUri' => $this->getPathUri(), + ]); + } + + /** + * Render one of this module's admin Twig templates. + */ + private function render(string $template, array $params = []): string + { + /** @var Environment $twig */ + $twig = $this->get('twig'); + + return $twig->render(sprintf('@Modules/%s/views/templates/admin/%s', $this->name, $template), $params); + } +} diff --git a/dashexample/views/css/dashexample.css b/dashexample/views/css/dashexample.css new file mode 100644 index 00000000..e51c5f68 --- /dev/null +++ b/dashexample/views/css/dashexample.css @@ -0,0 +1,25 @@ +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0). + * It is also available through the world-wide-web at this URL: https://opensource.org/licenses/AFL-3.0 + */ + +.dashexample-toolbar { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 14px; + margin-bottom: 15px; + border-radius: 4px; + background-color: #e6f0ff; + color: #25b9d7; + font-weight: 600; +} + +.dashexample-card .panel-heading { + display: flex; + align-items: center; + gap: 6px; +} diff --git a/dashexample/views/js/dashexample.js b/dashexample/views/js/dashexample.js new file mode 100644 index 00000000..cc5fe884 --- /dev/null +++ b/dashexample/views/js/dashexample.js @@ -0,0 +1,13 @@ +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0). + * It is also available through the world-wide-web at this URL: https://opensource.org/licenses/AFL-3.0 + */ + +document.addEventListener('DOMContentLoaded', () => { + // The module manages its own assets: this file is loaded from the module's hook output + // (see views/templates/admin/toolbar.html.twig), not via actionAdminControllerSetMedia. + console.log('[dashexample] Loaded on the migrated Symfony dashboard page.'); +}); diff --git a/dashexample/views/templates/admin/toolbar.html.twig b/dashexample/views/templates/admin/toolbar.html.twig new file mode 100644 index 00000000..615042dd --- /dev/null +++ b/dashexample/views/templates/admin/toolbar.html.twig @@ -0,0 +1,19 @@ +{#** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0). + * It is also available through the world-wide-web at this URL: https://opensource.org/licenses/AFL-3.0 + *#} + +{% trans_default_domain 'Module.Dashexample.Admin' %} + +{# The module loads its own assets from its hook output — no actionAdminControllerSetMedia, + no get_class($controller) detection. #} + + + +
+ extension + {{ 'Dashboard example module is active.'|trans }} +
diff --git a/dashexample/views/templates/admin/zone_one.html.twig b/dashexample/views/templates/admin/zone_one.html.twig new file mode 100644 index 00000000..8d5b854f --- /dev/null +++ b/dashexample/views/templates/admin/zone_one.html.twig @@ -0,0 +1,22 @@ +{#** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0). + * It is also available through the world-wide-web at this URL: https://opensource.org/licenses/AFL-3.0 + *#} + +{% trans_default_domain 'Module.Dashexample.Admin' %} + +
+

+ widgets + {{ 'Zone one'|trans }} +

+
+

{{ 'This block is rendered by the dashexample module through the displayAdminDashboardZoneOne hook.'|trans }}

+

+ {{ 'Selected range: %from% → %to%'|trans({'%from%': dateFrom, '%to%': dateTo}) }} +

+
+
diff --git a/dashexample/views/templates/admin/zone_two.html.twig b/dashexample/views/templates/admin/zone_two.html.twig new file mode 100644 index 00000000..d18b6b80 --- /dev/null +++ b/dashexample/views/templates/admin/zone_two.html.twig @@ -0,0 +1,24 @@ +{#** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0). + * It is also available through the world-wide-web at this URL: https://opensource.org/licenses/AFL-3.0 + *#} + +{% trans_default_domain 'Module.Dashexample.Admin' %} + +
+

+ insights + {{ 'Zone two'|trans }} +

+
+

{{ 'This block is rendered by the dashexample module through the displayAdminDashboardZoneTwo hook.'|trans }}

+
    +
  • {{ 'Rendered with a module Twig template (no Smarty).'|trans }}
  • +
  • {{ 'No HelperForm, no Db::getInstance().'|trans }}
  • +
  • {{ 'Range received as hook parameters: %from% → %to%.'|trans({'%from%': dateFrom, '%to%': dateTo}) }}
  • +
+
+
From 2f5b5ad4693c938fc5aff13f1b6ae802ccdb9e0d Mon Sep 17 00:00:00 2001 From: mattgoud Date: Thu, 23 Jul 2026 16:41:42 +0200 Subject: [PATCH 2/3] dashexample: add displayAdminDashboardTop example Register and render the top-area hook (top.html.twig) too, so the module demonstrates the full new dashboard hook family. --- dashexample/README.md | 3 ++- dashexample/dashexample.php | 13 +++++++++++++ dashexample/views/templates/admin/top.html.twig | 15 +++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 dashexample/views/templates/admin/top.html.twig diff --git a/dashexample/README.md b/dashexample/README.md index 886876a6..454fdba1 100644 --- a/dashexample/README.md +++ b/dashexample/README.md @@ -6,7 +6,7 @@ It is both living documentation of the integration contract and a validation veh ## What it demonstrates -- Registering on the **new** dashboard hooks: `displayAdminDashboardZoneOne`, `displayAdminDashboardZoneTwo`, `displayAdminDashboardToolbar`. +- Registering on the **new** dashboard hooks: `displayAdminDashboardZoneOne`, `displayAdminDashboardZoneTwo`, `displayAdminDashboardTop`, `displayAdminDashboardToolbar`. - Rendering hook content through **module Twig templates** (`views/templates/admin/*.html.twig`) — no Smarty, no `HelperForm`, no `Db::getInstance()`. - Passing and using hook **parameters** (`date_from` / `date_to`, the employee stats date range). - Loading the module's **own CSS/JS assets from its hook output** (see `toolbar.html.twig`) — no `actionAdminControllerSetMedia`, no `get_class($this->context->controller)` detection. @@ -84,5 +84,6 @@ This module registers **only the new hooks** on purpose, so it also serves as a | `dashexample.php` | Module class: hook registration + hook callbacks rendering Twig | | `views/templates/admin/zone_one.html.twig` | Zone One block | | `views/templates/admin/zone_two.html.twig` | Zone Two block | +| `views/templates/admin/top.html.twig` | Top area block | | `views/templates/admin/toolbar.html.twig` | Toolbar block + asset loading | | `views/css/dashexample.css`, `views/js/dashexample.js` | Module-owned assets | diff --git a/dashexample/dashexample.php b/dashexample/dashexample.php index 23678228..a1010a9a 100644 --- a/dashexample/dashexample.php +++ b/dashexample/dashexample.php @@ -58,6 +58,7 @@ public function install(): bool && $this->registerHook([ 'displayAdminDashboardZoneOne', 'displayAdminDashboardZoneTwo', + 'displayAdminDashboardTop', 'displayAdminDashboardToolbar', ]); } @@ -85,6 +86,18 @@ public function hookDisplayAdminDashboardZoneTwo(array $params): string ]); } + /** + * Renders a full-width block in the top area of the Symfony dashboard. + * Receives the employee date range selected on the page. + */ + public function hookDisplayAdminDashboardTop(array $params): string + { + return $this->render('top.html.twig', [ + 'dateFrom' => $params['date_from'] ?? null, + 'dateTo' => $params['date_to'] ?? null, + ]); + } + /** * Renders content in the toolbar area of the Symfony dashboard. * diff --git a/dashexample/views/templates/admin/top.html.twig b/dashexample/views/templates/admin/top.html.twig new file mode 100644 index 00000000..109f8c79 --- /dev/null +++ b/dashexample/views/templates/admin/top.html.twig @@ -0,0 +1,15 @@ +{#** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0). + * It is also available through the world-wide-web at this URL: https://opensource.org/licenses/AFL-3.0 + *#} + +{% trans_default_domain 'Module.Dashexample.Admin' %} + +
+ campaign + {{ 'This banner is rendered by the dashexample module through the displayAdminDashboardTop hook.'|trans }} + {{ 'Selected range: %from% → %to%'|trans({'%from%': dateFrom, '%to%': dateTo}) }} +
From a99d8c6fdd543aaf450c9bbc6c49776d46c7a23c Mon Sep 17 00:00:00 2001 From: mattgoud Date: Thu, 23 Jul 2026 16:56:23 +0200 Subject: [PATCH 3/3] dashexample: add displayAdminDashboardZoneThree example Fill zone three as well so the module demonstrates and exercises the full dashboard hook family (the migrated page hides empty zones, so an unused zone three would otherwise never render). --- dashexample/README.md | 3 ++- dashexample/dashexample.php | 13 +++++++++++ .../templates/admin/zone_three.html.twig | 22 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 dashexample/views/templates/admin/zone_three.html.twig diff --git a/dashexample/README.md b/dashexample/README.md index 454fdba1..576750cc 100644 --- a/dashexample/README.md +++ b/dashexample/README.md @@ -6,7 +6,7 @@ It is both living documentation of the integration contract and a validation veh ## What it demonstrates -- Registering on the **new** dashboard hooks: `displayAdminDashboardZoneOne`, `displayAdminDashboardZoneTwo`, `displayAdminDashboardTop`, `displayAdminDashboardToolbar`. +- Registering on the **new** dashboard hooks: `displayAdminDashboardZoneOne`, `displayAdminDashboardZoneTwo`, `displayAdminDashboardZoneThree`, `displayAdminDashboardTop`, `displayAdminDashboardToolbar`. - Rendering hook content through **module Twig templates** (`views/templates/admin/*.html.twig`) — no Smarty, no `HelperForm`, no `Db::getInstance()`. - Passing and using hook **parameters** (`date_from` / `date_to`, the employee stats date range). - Loading the module's **own CSS/JS assets from its hook output** (see `toolbar.html.twig`) — no `actionAdminControllerSetMedia`, no `get_class($this->context->controller)` detection. @@ -84,6 +84,7 @@ This module registers **only the new hooks** on purpose, so it also serves as a | `dashexample.php` | Module class: hook registration + hook callbacks rendering Twig | | `views/templates/admin/zone_one.html.twig` | Zone One block | | `views/templates/admin/zone_two.html.twig` | Zone Two block | +| `views/templates/admin/zone_three.html.twig` | Zone Three block | | `views/templates/admin/top.html.twig` | Top area block | | `views/templates/admin/toolbar.html.twig` | Toolbar block + asset loading | | `views/css/dashexample.css`, `views/js/dashexample.js` | Module-owned assets | diff --git a/dashexample/dashexample.php b/dashexample/dashexample.php index a1010a9a..e388041e 100644 --- a/dashexample/dashexample.php +++ b/dashexample/dashexample.php @@ -58,6 +58,7 @@ public function install(): bool && $this->registerHook([ 'displayAdminDashboardZoneOne', 'displayAdminDashboardZoneTwo', + 'displayAdminDashboardZoneThree', 'displayAdminDashboardTop', 'displayAdminDashboardToolbar', ]); @@ -86,6 +87,18 @@ public function hookDisplayAdminDashboardZoneTwo(array $params): string ]); } + /** + * Renders a block in the third (right) column of the Symfony dashboard. + * Receives the employee date range selected on the page. + */ + public function hookDisplayAdminDashboardZoneThree(array $params): string + { + return $this->render('zone_three.html.twig', [ + 'dateFrom' => $params['date_from'] ?? null, + 'dateTo' => $params['date_to'] ?? null, + ]); + } + /** * Renders a full-width block in the top area of the Symfony dashboard. * Receives the employee date range selected on the page. diff --git a/dashexample/views/templates/admin/zone_three.html.twig b/dashexample/views/templates/admin/zone_three.html.twig new file mode 100644 index 00000000..1b0666e0 --- /dev/null +++ b/dashexample/views/templates/admin/zone_three.html.twig @@ -0,0 +1,22 @@ +{#** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0). + * It is also available through the world-wide-web at this URL: https://opensource.org/licenses/AFL-3.0 + *#} + +{% trans_default_domain 'Module.Dashexample.Admin' %} + +
+

+ bookmark + {{ 'Zone three'|trans }} +

+
+

{{ 'This block is rendered by the dashexample module through the displayAdminDashboardZoneThree hook.'|trans }}

+

+ {{ 'Selected range: %from% → %to%'|trans({'%from%': dateFrom, '%to%': dateTo}) }} +

+
+