Skip to content

Feed the Symfony dashboard's Zone Two hook with product tables, add a settings page - #78

Open
nicosomb wants to merge 3 commits into
devfrom
feature/41971-zone-two-poc
Open

Feed the Symfony dashboard's Zone Two hook with product tables, add a settings page#78
nicosomb wants to merge 3 commits into
devfrom
feature/41971-zone-two-poc

Conversation

@nicosomb

Copy link
Copy Markdown
Contributor
Questions Answers
Description? Adds hookDisplayAdminDashboardZoneTwo(), the modern counterpart of hookDashboardZoneTwo(), so this module also feeds the migrated (Symfony) Back Office Dashboard. Reuses hookDashboardData() as-is (recent orders, best sellers, most viewed, top search tables) and renders them as plain server-side Twig — no JSON/JS contract needed for tables, unlike charts. Legacy hooks are untouched. Version bumped 2.2.1 → 2.3.0 (minor, backward compatible with 8.2.0+).
Type? new feature
BC breaks? no
Deprecations? no
Fixed ticket? Fixes PrestaShop/PrestaShop#41971.
How to test? 1. On a shop with PrestaShop/PrestaShop#42431 and the dashboard feature flag enabled, install/reset this module. 2. Open the BO Dashboard: 4 tables render in Zone Two (recent orders, best sellers, most viewed, top search).

Draft PoC for the #41971 spike — the legacy data_table shape is reused almost verbatim, only the transport changes.

@ps-jarvis

ps-jarvis commented Aug 28, 2026

Copy link
Copy Markdown

This pull request seems to contain new translation strings. I have summarized them below to ease up review:

  • Modules.Dashproducts.Admin
    • Recent orders
    • Best sellers
    • Most viewed products
    • Top search
  • Admin.Actions
    • Products dashboard settings
  • Admin.Global
    • No data

(Note: this is an automated message, but answering it will reach a real human)

@nicosomb
nicosomb force-pushed the feature/41971-zone-two-poc branch from 4977473 to c75d3ce Compare September 3, 2026 09:05
@nicosomb nicosomb changed the title PoC: feed the migrated Symfony dashboard with real product tables Feed the Symfony dashboard's Zone Two hook with product tables, add a settings page Sep 3, 2026
@nicosomb
nicosomb marked this pull request as ready for review September 3, 2026 11:58
@github-project-automation github-project-automation Bot moved this to Ready for review in PR Dashboard Sep 3, 2026

@mattgoud mattgoud left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally against PrestaShop/PrestaShop#42431 with the dashboard flag on. The four tables render in zone two, and the fallback branches work correctly (the "No data" case on Best sellers and Top search, and the raw string body on Most viewed products showing the "Save global page views" notice). Reusing hookDashboardData() rather than duplicating the queries is the right call, and rendering tables server-side instead of pushing them through the JSON contract makes sense.

Two blockers and a few smaller points.

Blocker 1: no upgrade script, so no existing shop gets the hook or the tab

install() only runs on a fresh install. On a shop that already has dashproducts 2.2.1, neither displayAdminDashboardZoneTwo nor the AdminDashproductsConfiguration tab is ever created.

Reproduced locally: old version installed, 2.3.0 files dropped on disk, then the upgrade path replayed (ModuleManager::upgradeMigration() minus the download step):

dashproducts   db=2.2.1   disk=2.3.0   needUpgrade=NO

No hook row, no tab, and Module::upgradeModuleVersion() is never called so ps_module.version stays on 2.2.1 forever.

autoupgrade behaves the same way: ModuleMigration::needMigration() returns false when upgrade/*.php is empty, it logs "Module does not need to be migrated", then calls saveVersionInDb() anyway. After a core upgrade the shop reports 2.3.0 while the hook is still missing, with no way back other than uninstall/reinstall (losing the configuration). autoupgrade cannot fix this on its own: core upgrade SQL never inserts into ps_hook_module, it only cleans orphans.

The module already has the pattern (upgrade/upgrade-2.1.2.php), so it needs an upgrade/upgrade-2.3.0.php that registers the hook and creates the tab, sharing the tab creation with install() rather than duplicating it.

Blocker 2: the settings controller has no ACL

indexAction() carries no #[AdminSecurity]. The admin firewall (app/config/admin/security.yml) only requires IS_AUTHENTICATED, and AdminSecurityListener does nothing when the attribute is absent, so any logged-in employee can open and save these settings regardless of their profile, and the hidden tab created by install() is doing no work today.

90 of the 92 core admin controllers carry the attribute (the two exceptions are Login and Security), and ps_linklist does too:

#[AdminSecurity("is_granted('read', request.get('_legacy_controller'))")]
public function indexAction(Request $request): Response

Once the attribute is there, the id_parent = -1 tab only grants the roles to SuperAdmin by default, so the other profiles will need their permissions initialised.

Two rendering issues visible in the browser

  • The details button in Recent orders renders as an empty square. getTableRecentOrders() builds it with <i class="icon-search"></i>, which is a legacy theme icon class that does not exist in the new BO theme (it uses Material icons). On the legacy dashboard it worked; here it needs <i class="material-icons">search</i>.

    recent-orders-missing-icons
  • dashproducts.php:211 has 'wrapper_end' => '<span>' instead of </span>, which now renders a stray <span></span> in the amount column (€66.80<span></span>). Pre-existing module bug, but the new template surfaces it. Worth fixing while you are here. Side note: the new template reading wrapper_end is correct, and it incidentally fixes a long-standing legacy bug where js/admin/dashboard.js:129 reads wrapper_stop and therefore never closes these wrappers.

No data is a new string in a core catalogue

ps-jarvis flagged it, and it is indeed absent from translations/en-US/AdminGlobal.en-US.xlf. A module cannot add entries to core catalogues, so it will stay untranslated. It belongs in Modules.Dashproducts.Admin.

Smaller points

  • {{ cell.value|raw }} carries unescaped customer input: getTableTop10MostSearch() puts $term['keywords'] straight into value. The legacy dashboard has the same hole (js/admin/dashboard.js:128 injects body.value as HTML), so this is not a regression, but it is exactly the "untyped arrays / raw HTML, XSS surface" pain point listed in PrestaShop/PrestaShop#41971. Since the transport is being redone anyway, splitting a plain-text value from an explicit link would close it instead of carrying it over.
  • FrameworkBundleAdminController is @deprecated since 9.0 in favour of PrestaShopAdminController. Worth deciding explicitly given the announced 8.2 compatibility.
  • The token in getConfigUrl() is dead weight. router is aliased to prestashop.router which already appends _token, and in PS 9 Tools::getAdminToken() ignores its argument and returns that same CSRF token, so the link carries the identical value twice. Verified in the browser on this branch.
  • configUrl only on loop.first. It works, but tying the gear to whichever table happens to come first is a bit arbitrary; worth confirming with UX.
  • config.xml churn. Reindented from tabs to spaces, <limited_countries> dropped, trailing newline gone. Unrelated to the feature.
  • No index.php guard files in the new config/, src/, src/Controller, src/Type directories, while every other directory in the module has one.
  • Form labels are hardcoded strings resolved through translation_domain, so the translation extractor will not pick them up.

The screenshot is attached to this review (GitHub-hosted). An archive copy also lives in mattgoud/qa-assets/dashproducts-78.

'DASHPRODUCT_NBR_SHOW_TOP_SEARCH',
];

public function indexAction(Request $request): Response

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No ACL check here. The admin firewall only requires IS_AUTHENTICATED and AdminSecurityListener is a no-op without the attribute, so any logged-in employee can read and save these settings whatever their profile. The hidden tab created in install() is not consulted by anything today.

#[AdminSecurity("is_granted('read', request.get('_legacy_controller'))")]
public function indexAction(Request $request): Response

{% for row in entry.table.body %}
<tr>
{% for cell in row %}
<td class="{{ cell.class }}">{{ cell.wrapper_start|default('')|raw }}{{ cell.value|raw }}{{ cell.wrapper_end|default('')|raw }}</td>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cell.value is raw customer input for the Top search table (getTableTop10MostSearch() passes $term['keywords'] through unescaped). The legacy dashboard has the same hole so this is not a regression, but it is the exact pain point the spike lists. Since the transport is being rebuilt anyway, a plain-text value plus an explicit link field would close it rather than carry it forward.

Also worth noting dashproducts.php:211 sets 'wrapper_end' => '<span>' instead of </span>, which now shows up as a stray <span></span> after the amount.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

Dashboard Migration - Dynamic data refacto

3 participants