Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions controllers/front/AbstractOpcJsonFrontController.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ protected function getTechnicalErrorResponseExtra(): array
return [];
}

/**
* Render a module-owned Smarty template by relative path under views/templates/front/.
* Goes through the `module:` Smarty resource so theme overrides under
* `themes/<active>/modules/ps_onepagecheckout/...` still take precedence.
*
* @param array<string,mixed> $params
*/
protected function renderModuleTemplate(string $relativePath, array $params): string
{
$this->context->smarty->assign($params);

return $this->context->smarty->fetch(
'module:ps_onepagecheckout/views/templates/front/' . ltrim($relativePath, '/') . '.tpl'
);
}

/**
* @param array<string,mixed> $response
*/
Expand Down
4 changes: 2 additions & 2 deletions controllers/front/addresseslist.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ protected function handleOpcRequest(): array
return [
'success' => true,
'address_count' => (int) ($response['address_count'] ?? 0),
'delivery_html' => $this->render(
'delivery_html' => $this->renderModuleTemplate(
'checkout/_partials/one-page-checkout/address-list',
[
'customer' => $response['customer'] ?? [],
'prefix' => '',
'selected_address' => (int) ($response['selected_delivery_address'] ?? 0),
]
),
'billing_html' => $this->render(
'billing_html' => $this->renderModuleTemplate(
'checkout/_partials/one-page-checkout/address-list',
[
'customer' => $response['customer'] ?? [],
Expand Down
2 changes: 1 addition & 1 deletion controllers/front/addressform.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected function handleOpcRequest(): array
$templateVariables = $handler->getTemplateVariables(Tools::getAllValues());

return [
'addresses_section' => $this->render(
'addresses_section' => $this->renderModuleTemplate(
'checkout/_partials/one-page-checkout/addresses-section',
$templateVariables
),
Expand Down
2 changes: 1 addition & 1 deletion controllers/front/carriers.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected function handleOpcRequest(): array
$response = $handler->handle(Tools::getAllValues());

if (!empty($response['success'])) {
$response['carriers_html'] = $this->render(
$response['carriers_html'] = $this->renderModuleTemplate(
'checkout/_partials/one-page-checkout/carriers',
[
'delivery_options' => $response['delivery_options'] ?? [],
Expand Down
2 changes: 1 addition & 1 deletion controllers/front/paymentmethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected function handleOpcRequest(): array
$response = $handler->handle(Tools::getAllValues());

if (!empty($response['success'])) {
$response['payment_html'] = $this->render(
$response['payment_html'] = $this->renderModuleTemplate(
'checkout/_partials/one-page-checkout/payment-methods',
[
'payment_options' => $response['payment_options'] ?? [],
Expand Down
6 changes: 1 addition & 5 deletions docs/CORE_PORTING_PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ When a Core checkout event already exists, document separately:
- listener compatibility already expected by the existing runtime,
- emitter ownership once the module becomes responsible for triggering the event.

Put a change in Hummingbird when it is:
- template structure,
- section placeholders,
- visual states,
- style-only behavior.
Put a change in the active theme only when it is page chrome unrelated to OPC step content (the surrounding `checkout.tpl` wrapper, breadcrumb, notifications, cart-summary panel). All OPC-step templates, partials, styles, and runtime JS live in this module.

Put a change in Core only when:
- the module would otherwise need an override,
Expand Down
2 changes: 1 addition & 1 deletion docs/RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Both entry points must render the same module-owned configuration flow (no redir
1. Triage every future checkout change using the playbook before coding.
2. Correct existing parity gaps in the module before porting new Core behavior.
3. Keep Core changes to the minimal no-override surface only.
4. Keep checkout business logic in the module and DOM/visual ownership in Hummingbird.
4. Keep all OPC step DOM, styles, and runtime JS inside the module. The theme owns only its own page chrome (checkout page wrapper, cart summary, page-level notifications).

## Test workflow

Expand Down
18 changes: 18 additions & 0 deletions ps_onepagecheckout.php
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ public function hookActionFrontControllerSetMedia(): void
]);

$this->registerOpcJavascriptAssets();
$this->registerOpcStylesheets();
}

public function hookActionFrontControllerSetVariables(array $params): void
Expand Down Expand Up @@ -346,6 +347,7 @@ protected function registerOpcJavascriptAssets(): void
['module-ps-onepagecheckout-select-carrier', 'views/public/opc-carrier-select.bundle.js', 154],
['module-ps-onepagecheckout-payment-methods', 'views/public/opc-payment-list.bundle.js', 155],
['module-ps-onepagecheckout-select-payment', 'views/public/opc-payment-select.bundle.js', 156],
['module-ps-onepagecheckout-cart-summary-state', 'views/public/opc-cart-summary-state.bundle.js', 157],
] as [$id, $path, $priority]) {
$this->context->controller->registerJavascript(
$id,
Expand All @@ -363,6 +365,22 @@ protected function addOpcJavascriptDefinition(array $javascriptDefinition): void
Media::addJsDef($javascriptDefinition);
}

protected function registerOpcStylesheets(): void
{
if (!isset($this->context->controller)) {
return;
}

$this->context->controller->registerStylesheet(
'module-ps-onepagecheckout',
'modules/' . $this->name . '/views/public/one-page-checkout.css',
[
'media' => 'all',
'priority' => 200,
]
);
}

protected function installInParent(): bool
{
return parent::install();
Expand Down
2 changes: 1 addition & 1 deletion src/Checkout/CheckoutOnePageStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

class CheckoutOnePageStep extends \AbstractCheckoutStep
{
protected $template = 'checkout/_partials/steps/one-page-checkout.tpl';
protected $template = 'module:ps_onepagecheckout/views/templates/front/checkout/_partials/steps/one-page-checkout.tpl';

/**
* @var OnePageCheckoutForm
Expand Down
2 changes: 1 addition & 1 deletion src/Checkout/PaymentSelectionKeyBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function buildSelectionKey(array $option): string
return sprintf(
'%s:%s',
$moduleName !== '' ? $moduleName : 'unknown',
substr(hash('sha256', json_encode($signature)), 0, 24)
substr(hash('sha256', (string) json_encode($signature, JSON_INVALID_UTF8_SUBSTITUTE)), 0, 24)
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Form/BackOfficeConfigurationForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,11 @@ private function buildTemplateVariables(): array
'confirm_modal_title' => $this->trans('Change checkout appearance', 'Modules.PsOnePageCheckout.Admin'),
'confirm_modal_compatibility_title' => $this->trans('Check theme compatibility', 'Modules.PsOnePageCheckout.Admin'),
'confirm_modal_description' => $this->trans('You\'re about to update the Checkout appearance of your store.', 'Modules.PsOnePageCheckout.Admin'),
'confirm_modal_compatibility_description' => $this->trans('One-page checkout layout requires a Hummingbird-compatible theme.', 'Modules.PsOnePageCheckout.Admin'),
'confirm_modal_compatibility_description' => $this->trans('One-page checkout layout requires a Bootstrap 5-compatible theme.', 'Modules.PsOnePageCheckout.Admin'),
'confirm_modal_checklist_title' => $this->trans('Before you proceed, please make sure that:', 'Modules.PsOnePageCheckout.Admin'),
'confirm_modal_checklist' => [
$this->trans('You are NOT using the "Classic" theme: This layout is incompatible with the legacy Classic theme and will break your storefront.', 'Modules.PsOnePageCheckout.Admin'),
$this->trans('Your theme is Hummingbird-based: Ensure your custom or third-party theme supports this new checkout architecture.', 'Modules.PsOnePageCheckout.Admin'),
$this->trans('Your theme is compatible with the one-page checkout DOM and Bootstrap 5: Custom or third-party themes must support this new checkout architecture.', 'Modules.PsOnePageCheckout.Admin'),
$this->trans('Your checkout modules are compatible. Some older modules may not be compatible with a one-page layout.', 'Modules.PsOnePageCheckout.Admin'),
],
'cancel_button_label' => $this->trans('Cancel', 'Admin.Actions'),
Expand Down
5 changes: 5 additions & 0 deletions tests/php/Unit/Controller/AddressFormControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ protected function render($template, array $parameters = [])
{
return sprintf('rendered:%s', (string) $template);
}

protected function renderModuleTemplate(string $relativePath, array $params): string
{
return sprintf('rendered:%s', $relativePath);
}
}

class EnabledPsOnepagecheckoutModuleForAddressForm extends \Ps_Onepagecheckout
Expand Down
68 changes: 68 additions & 0 deletions views/js/opc-cart-summary-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import OPC_EVENTS from './events';

(function psOpcCartSummaryStateRuntime() {
const prestashop = window.prestashop || null;

if (!prestashop || typeof prestashop.on !== 'function') {
return;
}

// Bootstrap accordion items inside the cart summary collapse on every DOM
// replacement. Capture which ones were open before the update and reopen them
// after — no-op when the theme doesn't use Bootstrap accordions.
let openCollapseIds = [];

prestashop.on(OPC_EVENTS.opcCartSummaryBeforeUpdate, ({selector}) => {
if (typeof selector !== 'string' || selector === '') {
return;
}

openCollapseIds = Array.from(document.querySelectorAll(`${selector} .accordion-collapse.show`))
.map((el) => el.id)
.filter(Boolean);
});

prestashop.on(OPC_EVENTS.opcCartSummaryUpdated, () => {
openCollapseIds.forEach((id) => {
const el = document.getElementById(id);

if (!el) {
return;
}

el.classList.add('show');

const btn = document.querySelector(`[data-bs-target="#${id}"]`);

if (btn) {
btn.classList.remove('collapsed');
btn.setAttribute('aria-expanded', 'true');
}
});
openCollapseIds = [];
});
})();
Loading
Loading