From 4bf081dd669ee6f0dc533a3282aad276d565a87a Mon Sep 17 00:00:00 2001 From: Isabelle Date: Fri, 10 Jul 2026 15:05:59 +0200 Subject: [PATCH] fix(main): Fixing bugs --- ws_customerspecialty.zip | Bin 55021 -> 55223 bytes ws_customerspecialty/README.md | 90 ++++++++++++++---- ws_customerspecialty/config/command.yml | 6 -- ws_customerspecialty/config/provider.yml | 4 - ws_customerspecialty/config/routes.yml | 7 -- ws_customerspecialty/config/services.yml | 1 - ...eCustomerSpecialtyActiveCommandHandler.php | 35 ------- .../ToggleCustomerSpecialtyActiveCommand.php | 18 ---- ws_customerspecialty/src/Config/Config.php | 1 + .../CustomerSpecialtyController.php | 15 +-- ...CustomerSpecialtyGridDefinitionFactory.php | 8 +- .../src/Module/AdminHooks.php | 23 ++++- .../src/Module/DisplayHooks.php | 29 ++++-- .../views/templates/admin/configuration.tpl | 24 ++++- .../admin/grid/customerspecialty.html.twig | 24 ++++- 15 files changed, 158 insertions(+), 127 deletions(-) delete mode 100644 ws_customerspecialty/config/provider.yml delete mode 100644 ws_customerspecialty/src/Command/CommandHandler/ToggleCustomerSpecialtyActiveCommandHandler.php delete mode 100644 ws_customerspecialty/src/Command/ToggleCustomerSpecialtyActiveCommand.php diff --git a/ws_customerspecialty.zip b/ws_customerspecialty.zip index cd2c5aaa9639227fa380c55ad28e5dae8baaac2c..64b94ecf7230a6bff19d5f05072c4c113e9f2966 100644 GIT binary patch delta 15128 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 diff --git a/ws_customerspecialty/README.md b/ws_customerspecialty/README.md index 143aa31..45fdbe3 100644 --- a/ws_customerspecialty/README.md +++ b/ws_customerspecialty/README.md @@ -55,26 +55,76 @@ not something the module can safely do on your behalf without overriding core ma - No drag-and-drop reordering of specialty values. The grid has "Move up" / "Move down" row actions instead (`customerspecialty_move_up` / `_move_down` routes swapping `position` with the adjacent row). This avoids depending on PrestaShop's core Grid position/drag-reorder plumbing. -- The active/inactive toggle uses a `ToggleColumn` + `customerspecialty_toggle_active` route. If - that route ever misbehaves on a given PrestaShop version, active/inactive can always be changed - via the Edit form as a fallback — it isn't the only way to flip the flag. +- The active/inactive column is display-only (`Active`/`Inactive` text via a + `GridDataModifier` hook — `actionCustomerspecialtyGridDataModifier`). An earlier version used a + clickable `ToggleColumn` for instant AJAX toggling. Checked against PrestaShop's real core + source (`src/Core/Grid/Column/Type/Common/ToggleColumn.php`): the column's required options + (`field`, `primary_field`, `route`) were all correctly set, so that wasn't the bug. The actual + cause was on the controller side — `toggleActiveAction()` returned a full-page + `RedirectResponse`, but the toggle's AJAX click handler expects a JSON response to update its + own UI state, so the click likely persisted server-side with no visible feedback. Removed rather + than fixed, since the display-only column + existing Edit form covers the same functionality + with less surface area. Change active/inactive via the Edit form (pencil icon) instead. -Confirmed working live on PrestaShop 9: create, edit, and active/inactive toggle on the -Specialties grid. Three real bugs were found and fixed this way (not by static guessing) during -initial rollout: a wrong `FormDataProviderInterface` namespace, a missing `LinkRowAction` -`route_param_field` option, and `FrameworkBundleAdminController::trans()` taking `(key, domain, -params)` instead of `Module::trans()`'s `(key, params, domain)`. "Move up"/"Move down" reordering -has not been explicitly click-tested yet — verify before relying on it. +Confirmed working live on PrestaShop 9: create, edit, and (formerly) active/inactive toggle on the +Specialties grid. Several real bugs were found via live errors during rollout rather than caught +ahead of time: a wrong `FormDataProviderInterface` namespace, a missing `LinkRowAction` +`route_param_field` option, a wrong `FormField` namespace, and `FrameworkBundleAdminController::trans()` +taking `(key, domain, params)` instead of `Module::trans()`'s `(key, params, domain)`. "Move +up"/"Move down" reordering hasn't been explicitly click-tested, but checked against PrestaShop's +real `LinkRowAction` source (`src/Core/Grid/Action/Row/Type/LinkRowAction.php`): its only required +options are `route`, `route_param_name`, `route_param_field` — the exact same three already +confirmed working on the "Edit" row action, and "Move up"/"Move down" use the identical shape. Low +residual risk, but still worth a click to confirm. -## Biggest assumption to verify +## Bug caught by research before it hit production -The storefront "Specialty" field on registration/account-edit relies on `additionalCustomerFormFields` -already being called by core's own front-office `CustomerFormatter` (passing `fields` by reference). -The legacy module achieved this only by overriding `CustomerFormatter` to add that hook call itself — -an override intentionally not reproduced here (see the architecture note above). If the installed -PrestaShop version's `CustomerFormatter` does not already call this hook natively, the storefront -field will not appear (back office, grid, and mail-variable behavior are unaffected either way, -since none of those depend on this hook). Verify on a real 1.7 and 9.0 install; if the hook isn't -native, the fallback is to make the field mandatory server-side via a small first-party -`CustomerFormatter` override scoped to only adding that one `Hook::exec()` call — much smaller and -safer than the legacy module's full-method copy. +The `active`/`inactive` label transform used the hook name `actionCustomerspecialtyGridDataModifier` +with `$params['presented_grid']['data']['records']` — copied directly from this module's own +written conventions doc. Checked against real PrestaShop core source (confirmed identically on +1.7.8.11, 8.1.6, and 9.0.0): `GridFactory::getGrid()` dispatches `action{Id}GridDataModifier` with +`['data' => &$data]` (a raw `GridData` object, no `presented_grid` key at all) — the +`presented_grid` array shape actually belongs to a *different* hook, `action{Id}GridPresenterModifier`, +dispatched separately from `GridPresenter::present()`. The conventions doc has the hook name and +parameter shape from two different hooks crossed. Our hook name has been corrected to +`actionCustomerspecialtyGridPresenterModifier`; the body logic was already right for that shape. +This was caught via source research before ever being deployed, unlike most other bugs in this +module which surfaced as live fatals — worth flagging back if this doc gets used for future modules. + +## Confirmed live on PrestaShop 9 + +`additionalCustomerFormFields` **is** called natively by core's front-office `CustomerFormatter::getFormat()` +(confirmed via a live stack trace from the registration page) — no `CustomerFormatter` override is +needed, as originally hoped. This was the biggest unverified assumption in the initial build; it's +now settled. + +## Front-office vs back-office service container gotcha (found live, now fixed) + +The front office runs on a different, smaller Symfony container than the back office — it does +**not** have this module's own `config/*.yml`-registered services compiled into it, only core +services. A hook that may fire in front-office context (like `additionalCustomerFormFields`, fired +from the registration page) must not fetch custom module services via `$this->get('websmid.*')` — +it throws `ServiceNotFoundException` there even though the identical call works fine from +admin-only hooks. Fixed by instantiating `CustomerSpecialtyChoiceProvider` directly (`new +CustomerSpecialtyChoiceProvider()`) instead of routing it through the container — it has no real +dependencies, so this works identically in both contexts. `config/provider.yml` was removed since +the class is no longer registered as a DI service. + +**Update, confirmed live**: even `prestashop.core.command_bus` — a *core* service, not a +module-registered one — throws the same `ServiceNotFoundException` when called from front-office +hooks (`actionObjectCustomerAddAfter`/`UpdateAfter`, fired from registration/account-edit). The +front-office container is more limited than assumed; it's not just custom module services that are +missing. Fixed by having `persistFrontOfficeCustomerSpecialty()` write directly via +`Db::getInstance()` (an `INSERT ... ON DUPLICATE KEY UPDATE` against +`ws_customerspecialty_customer`) instead of dispatching `LinkCustomerSpecialtyCommand` through the +command bus. This also retroactively explains why `ws_giftdropdown`'s own front controller bypasses +its fully-wired Command/CommandHandler/Doctrine plumbing in favor of raw SQL — same limitation, +same workaround, already an established pattern in this codebase. + +**General rule going forward**: any hook that can fire in front-office context +(`additionalCustomerFormFields`, `actionObjectCustomerAddAfter`/`UpdateAfter`, and similar) must +stick to `Db::getInstance()`/legacy globals only — no `$this->get(...)` container service, custom +or core, and no Doctrine entity manager. Only hooks that are provably admin-only +(`actionCustomerFormBuilderModifier`, `actionAfterCreate/UpdateCustomerFormHandler`, +`actionCustomerGridDefinitionModifier`/`QueryBuilderModifier`) can safely use the command bus, +Doctrine, or custom DI services. diff --git a/ws_customerspecialty/config/command.yml b/ws_customerspecialty/config/command.yml index 40908e8..1cd4295 100644 --- a/ws_customerspecialty/config/command.yml +++ b/ws_customerspecialty/config/command.yml @@ -11,12 +11,6 @@ services: - { name: 'tactician.handler', command: 'Websmid\CustomerSpecialty\Command\MoveCustomerSpecialtyCommand' } - { name: 'messenger.message_handler' } - websmid.customerspecialty.command_handler.toggle_customer_specialty_active_handler: - class: 'Websmid\CustomerSpecialty\Command\CommandHandler\ToggleCustomerSpecialtyActiveCommandHandler' - tags: - - { name: 'tactician.handler', command: 'Websmid\CustomerSpecialty\Command\ToggleCustomerSpecialtyActiveCommand' } - - { name: 'messenger.message_handler' } - websmid.customerspecialty.command_handler.link_customer_specialty_handler: class: 'Websmid\CustomerSpecialty\Command\CommandHandler\LinkCustomerSpecialtyCommandHandler' tags: diff --git a/ws_customerspecialty/config/provider.yml b/ws_customerspecialty/config/provider.yml deleted file mode 100644 index ed49d1c..0000000 --- a/ws_customerspecialty/config/provider.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - websmid.customerspecialty.form.choice_provider.customer_specialty_choice_provider: - public: true - class: 'Websmid\CustomerSpecialty\Form\ChoiceProvider\CustomerSpecialtyChoiceProvider' diff --git a/ws_customerspecialty/config/routes.yml b/ws_customerspecialty/config/routes.yml index 1f6b2b7..347ef86 100644 --- a/ws_customerspecialty/config/routes.yml +++ b/ws_customerspecialty/config/routes.yml @@ -27,13 +27,6 @@ customerspecialty_edit: _controller: 'Websmid\CustomerSpecialty\Controller\CustomerSpecialtyController::editAction' _legacy_controller: AdminCustomerSpecialtyOverview -customerspecialty_toggle_active: - path: customerspecialty/toggle-active/{idCustomerSpecialty} - methods: [GET] - defaults: - _controller: 'Websmid\CustomerSpecialty\Controller\CustomerSpecialtyController::toggleActiveAction' - _legacy_controller: AdminCustomerSpecialtyOverview - customerspecialty_move_up: path: customerspecialty/move-up/{idCustomerSpecialty} methods: [GET] diff --git a/ws_customerspecialty/config/services.yml b/ws_customerspecialty/config/services.yml index 8f283e3..f949b46 100644 --- a/ws_customerspecialty/config/services.yml +++ b/ws_customerspecialty/config/services.yml @@ -2,7 +2,6 @@ imports: - { resource: form.yml } - { resource: grid.yml } - { resource: command.yml } - - { resource: provider.yml } services: websmid.customerspecialty.entity.table_prefix_subscriber: diff --git a/ws_customerspecialty/src/Command/CommandHandler/ToggleCustomerSpecialtyActiveCommandHandler.php b/ws_customerspecialty/src/Command/CommandHandler/ToggleCustomerSpecialtyActiveCommandHandler.php deleted file mode 100644 index f8d3b13..0000000 --- a/ws_customerspecialty/src/Command/CommandHandler/ToggleCustomerSpecialtyActiveCommandHandler.php +++ /dev/null @@ -1,35 +0,0 @@ -handle($command); - } - - public function handle(ToggleCustomerSpecialtyActiveCommand $command) - { - $entityManager = SymfonyContainer::getInstance()->get('doctrine.orm.entity_manager'); - $repository = $entityManager->getRepository(CustomerSpecialty::class); - - $customerSpecialty = $repository->find($command->getIdCustomerSpecialty()); - - if (!$customerSpecialty) { - throw new PrestaShopException('Customer specialty not found.'); - } - - $customerSpecialty->setActive(!$customerSpecialty->isActive()); - - $entityManager->persist($customerSpecialty); - $entityManager->flush(); - - return true; - } -} diff --git a/ws_customerspecialty/src/Command/ToggleCustomerSpecialtyActiveCommand.php b/ws_customerspecialty/src/Command/ToggleCustomerSpecialtyActiveCommand.php deleted file mode 100644 index 40f717c..0000000 --- a/ws_customerspecialty/src/Command/ToggleCustomerSpecialtyActiveCommand.php +++ /dev/null @@ -1,18 +0,0 @@ -idCustomerSpecialty = $idCustomerSpecialty; - } - - public function getIdCustomerSpecialty(): int - { - return $this->idCustomerSpecialty; - } -} diff --git a/ws_customerspecialty/src/Config/Config.php b/ws_customerspecialty/src/Config/Config.php index d980eaf..a3783ac 100644 --- a/ws_customerspecialty/src/Config/Config.php +++ b/ws_customerspecialty/src/Config/Config.php @@ -15,6 +15,7 @@ class Config 'actionCustomerGridQueryBuilderModifier', 'displayAdminCustomers', 'actionGetExtraMailTemplateVars', + 'actionCustomerspecialtyGridPresenterModifier', ]; const TABS = [ diff --git a/ws_customerspecialty/src/Controller/CustomerSpecialtyController.php b/ws_customerspecialty/src/Controller/CustomerSpecialtyController.php index ed78d80..790e42d 100644 --- a/ws_customerspecialty/src/Controller/CustomerSpecialtyController.php +++ b/ws_customerspecialty/src/Controller/CustomerSpecialtyController.php @@ -7,7 +7,6 @@ use PrestaShopBundle\Security\Voter\PageVoter; use Websmid\CustomerSpecialty\Grid\Definition\Factory\CustomerSpecialtyGridDefinitionFactory; use Websmid\CustomerSpecialty\Grid\Filters\CustomerSpecialtyFilters; use Websmid\CustomerSpecialty\Command\MoveCustomerSpecialtyCommand; -use Websmid\CustomerSpecialty\Command\ToggleCustomerSpecialtyActiveCommand; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -32,7 +31,7 @@ class CustomerSpecialtyController extends FrameworkBundleAdminController { $legacyContext = $this->get('prestashop.adapter.legacy.context')->getContext(); - return $legacyContext->link->getAdminLink('AdminModules') . '&configure=ws_customerspecialty'; + return $legacyContext->link->getAdminLink('AdminModules') . '&configure=ws_customerspecialty&viewsettings=1'; } public function searchAction(Request $request): RedirectResponse @@ -95,18 +94,6 @@ class CustomerSpecialtyController extends FrameworkBundleAdminController ]); } - public function toggleActiveAction(int $idCustomerSpecialty): RedirectResponse - { - try { - $this->getCommandBus()->handle(new ToggleCustomerSpecialtyActiveCommand($idCustomerSpecialty)); - $this->addFlash('success', $this->trans('Successful update', 'Admin.Notifications.Success')); - } catch (Exception $e) { - $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages())); - } - - return $this->redirectToRoute('customerspecialty_index'); - } - public function moveUpAction(int $idCustomerSpecialty): RedirectResponse { return $this->move($idCustomerSpecialty, MoveCustomerSpecialtyCommand::DIRECTION_UP); diff --git a/ws_customerspecialty/src/Grid/Definition/Factory/CustomerSpecialtyGridDefinitionFactory.php b/ws_customerspecialty/src/Grid/Definition/Factory/CustomerSpecialtyGridDefinitionFactory.php index 0678ad4..c57c26a 100644 --- a/ws_customerspecialty/src/Grid/Definition/Factory/CustomerSpecialtyGridDefinitionFactory.php +++ b/ws_customerspecialty/src/Grid/Definition/Factory/CustomerSpecialtyGridDefinitionFactory.php @@ -5,7 +5,6 @@ namespace Websmid\CustomerSpecialty\Grid\Definition\Factory; use PrestaShop\PrestaShop\Core\Grid\Definition\Factory\AbstractGridDefinitionFactory; use PrestaShop\PrestaShop\Core\Grid\Column\ColumnCollection; use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\DataColumn; -use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\ToggleColumn; use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\ActionColumn; use PrestaShop\PrestaShop\Core\Grid\Action\Row\RowActionCollection; use PrestaShop\PrestaShop\Core\Grid\Action\Row\Type\LinkRowAction; @@ -49,13 +48,10 @@ class CustomerSpecialtyGridDefinitionFactory extends AbstractGridDefinitionFacto ]) ) ->add( - (new ToggleColumn('active')) + (new DataColumn('active')) ->setName($this->trans('Active', [], 'Admin.Global')) ->setOptions([ - 'field' => 'active', - 'primary_field' => 'id_ws_customerspecialty', - 'route' => 'customerspecialty_toggle_active', - 'route_param_name' => 'idCustomerSpecialty', + 'field' => 'active', ]) ) ->add( diff --git a/ws_customerspecialty/src/Module/AdminHooks.php b/ws_customerspecialty/src/Module/AdminHooks.php index 4b2b34c..d5e1c8c 100644 --- a/ws_customerspecialty/src/Module/AdminHooks.php +++ b/ws_customerspecialty/src/Module/AdminHooks.php @@ -4,6 +4,7 @@ namespace Websmid\CustomerSpecialty\Module; use Websmid\CustomerSpecialty\Config\Config; use Websmid\CustomerSpecialty\Command\LinkCustomerSpecialtyCommand; +use Websmid\CustomerSpecialty\Form\ChoiceProvider\CustomerSpecialtyChoiceProvider; use Db; use Configuration; use Tools; @@ -11,6 +12,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\DataColumn; use PrestaShop\PrestaShop\Core\Grid\Filter\Filter; +use PrestaShop\PrestaShop\Core\Grid\Record\RecordCollection; trait AdminHooks { @@ -26,6 +28,9 @@ trait AdminHooks if (Tools::isSubmit('submitWsCustomerSpecialty')) { Configuration::updateValue(Config::FRONT_ENABLED_CONFIG_KEY, (bool) Tools::getValue('front_enabled')); $confirmation = $this->displayConfirmation($this->trans('Settings saved.', [], 'Admin.Notifications.Success')); + } elseif (!Tools::getValue('viewsettings')) { + header('Location: ' . $this->get('router')->generate('customerspecialty_index')); + exit; } $this->context->smarty->assign([ @@ -113,6 +118,20 @@ trait AdminHooks $this->applyCustomerSpecialtyFilter($searchCriteria, $searchQueryBuilder); } + public function hookActionCustomerspecialtyGridPresenterModifier(array $params): void + { + $activeLabel = $this->trans('Active', [], 'Admin.Global'); + $inactiveLabel = $this->trans('Inactive', [], 'Admin.Global'); + + $params['presented_grid']['data']['records'] = new RecordCollection( + array_map(static function (array $row) use ($activeLabel, $inactiveLabel) { + $row['active'] = $row['active'] ? $activeLabel : $inactiveLabel; + + return $row; + }, $params['presented_grid']['data']['records']->all()) + ); + } + public function hookDisplayAdminCustomers(array $params) { $title = $this->loadCustomerSpecialtyTitle((int) $params['id_customer']); @@ -200,8 +219,8 @@ trait AdminHooks return false !== $title && null !== $title ? (string) $title : null; } - private function getCustomerSpecialtyChoiceProvider() + private function getCustomerSpecialtyChoiceProvider(): CustomerSpecialtyChoiceProvider { - return $this->get('websmid.customerspecialty.form.choice_provider.customer_specialty_choice_provider'); + return new CustomerSpecialtyChoiceProvider(); } } diff --git a/ws_customerspecialty/src/Module/DisplayHooks.php b/ws_customerspecialty/src/Module/DisplayHooks.php index 0968b83..ff4c8ad 100644 --- a/ws_customerspecialty/src/Module/DisplayHooks.php +++ b/ws_customerspecialty/src/Module/DisplayHooks.php @@ -3,11 +3,10 @@ namespace Websmid\CustomerSpecialty\Module; use Websmid\CustomerSpecialty\Config\Config; -use Websmid\CustomerSpecialty\Command\LinkCustomerSpecialtyCommand; use Configuration; use Tools; use Db; -use PrestaShop\PrestaShop\Core\Form\FormField; +use FormField; trait DisplayHooks { @@ -17,7 +16,7 @@ trait DisplayHooks return; } - $choices = $this->get('websmid.customerspecialty.form.choice_provider.customer_specialty_choice_provider')->getChoices(); + $choices = $this->getCustomerSpecialtyChoiceProvider()->getChoices(); if (empty($choices)) { return; @@ -28,7 +27,17 @@ trait DisplayHooks ->setType('select') ->setLabel($this->trans('Specialty', [], 'Modules.Wscustomerspecialty.Shop')) ->setRequired(true) - ->setAvailableValues($choices); + ->setAvailableValues(array_flip($choices)); + + $idCustomer = (int) $this->context->customer->id; + + if ($idCustomer > 0) { + $currentSpecialization = $this->loadCustomerSpecialtyId($idCustomer); + + if ($currentSpecialization > 0) { + $specializationField->setValue($currentSpecialization); + } + } if (isset($params['fields'])) { $this->insertFieldAfterEmail($params['fields'], $specializationField); @@ -86,8 +95,16 @@ trait DisplayHooks return; } - $this->get('prestashop.core.command_bus')->handle( - new LinkCustomerSpecialtyCommand($idCustomer, $specialization) + $now = date('Y-m-d H:i:s'); + + Db::getInstance()->execute( + 'INSERT INTO `' . _DB_PREFIX_ . 'ws_customerspecialty_customer` + (`id_customer`, `id_ws_customerspecialty`, `date_add`, `date_upd`) + VALUES + (' . $idCustomer . ', ' . $specialization . ', \'' . pSQL($now) . '\', \'' . pSQL($now) . '\') + ON DUPLICATE KEY UPDATE + `id_ws_customerspecialty` = ' . $specialization . ', + `date_upd` = \'' . pSQL($now) . '\'' ); } diff --git a/ws_customerspecialty/views/templates/admin/configuration.tpl b/ws_customerspecialty/views/templates/admin/configuration.tpl index 8cae1db..cd6d81f 100644 --- a/ws_customerspecialty/views/templates/admin/configuration.tpl +++ b/ws_customerspecialty/views/templates/admin/configuration.tpl @@ -2,10 +2,26 @@ {$confirmation nofilter} {/if} - +
diff --git a/ws_customerspecialty/views/templates/admin/grid/customerspecialty.html.twig b/ws_customerspecialty/views/templates/admin/grid/customerspecialty.html.twig index 2e66da1..d06b0df 100644 --- a/ws_customerspecialty/views/templates/admin/grid/customerspecialty.html.twig +++ b/ws_customerspecialty/views/templates/admin/grid/customerspecialty.html.twig @@ -1,10 +1,26 @@ {% extends '@PrestaShop/Admin/layout.html.twig' %} {% block content %} - + {% include '@PrestaShop/Admin/Common/Grid/grid_panel.html.twig' with {'grid': customerSpecialtyGrid} %} {% endblock %}