Frontend orders, invoice, and search Layouts Bypass WebAssetManager and Break Under Joomla plg_system_httpheaders CSP Nonce / Script Hashes
- Product: JoomShaper EasyStore (
com_easystore) - Product Version:
3.1.1 - Joomla Version:
6.2.0(PHP8.4) - Files:
components/com_easystore/tmpl/orders/default.php(lines 56, 64, 75, 88)components/com_easystore/tmpl/order/invoice.php(lines 198-202)components/com_easystore/layouts/filter/search.php(lines 46-58)components/com_easystore/layouts/filter/basicfilter/search.php(lines 47-59)
What the Bug Is
When a Joomla site enables the built-in System - HTTP Headers (plg_system_httpheaders) plugin with Content Security Policy Nonce ({nonce}) and/or Script Hashes ({script-hashes}) and script-src configured without unsafe-inline, Joomla's ScriptsRenderer automatically attaches nonce attributes and computes SHA-256 hashes for scripts registered through WebAssetManager (EasyStoreHelper::wa()->addInlineScript(...)). However, four frontend EasyStore templates/layouts hardcode inline onclick / onchange attributes or raw <script> tags directly in the HTML body instead of using EasyStoreHelper::wa()->addInlineScript(...), causing the browser to block them under strict CSP:
components/com_easystore/tmpl/orders/default.php: Usesonclickon the search-clear button (line 56) andonchangeonfilter_payment_status,filter_order_status, andfilter_date(lines 64, 75, 88).components/com_easystore/tmpl/order/invoice.php: Outputs a raw<script>tag callingwindow.print()(lines 198-202) without anonceattribute instead of registering it viaEasyStoreHelper::wa()->addInlineScript(...).components/com_easystore/layouts/filter/search.phpandcomponents/com_easystore/layouts/filter/basicfilter/search.php: Output raw<script defer>tags (lines 46-58) without anonceattribute instead of registering them viaEasyStoreHelper::wa()->addInlineScript(...).
How to Reproduce It
- In Joomla Administrator, enable System - HTTP Headers (
plg_system_httpheaders) with Content-Security-Policy = Enabled, Nonce = Enabled, Script Hashes = Enabled, andscript-srcconfigured with{nonce} {script-hashes}(withoutunsafe-inline). - Visit the frontend customer Order History page (
index.php?option=com_easystore&view=orders) and change the Payment Status or Order Status filter dropdown, or click the search clear button. - Open the browser DevTools Console: the browser blocks the inline
onchange/onclickexecution under Content Security Policy, and raw<script>blocks inorder/invoice.phpandlayouts/filter/search.phpare similarly blocked because they lack the document'scsp_nonce.
Fix (Unified Patch Against Clean com_easystore 3.1.1)
Register the inline scripts via EasyStoreHelper::wa()->addInlineScript(...) and replace the 4 inline onclick/onchange attributes in tmpl/orders/default.php with standard DOM event listeners:
--- a/components/com_easystore/tmpl/orders/default.php
+++ b/components/com_easystore/tmpl/orders/default.php
@@ -22,2 +22,19 @@
->useStyle('com_easystore.profile.site')
- ->useStyle('com_easystore.orders.site');
+ ->useStyle('com_easystore.orders.site')
+ ->addInlineScript(
+ "document.addEventListener('DOMContentLoaded', function () {
+ var form = document.getElementById('adminForm');
+ if (!form) return;
+ form.querySelectorAll('.easystore-search-clear').forEach(function (btn) {
+ btn.addEventListener('click', function () {
+ if (btn.previousElementSibling) {
+ btn.previousElementSibling.value = '';
+ }
+ form.submit();
+ });
+ });
+ form.querySelectorAll('[name=\"filter_payment_status\"], [name=\"filter_order_status\"], [name=\"filter_date\"]').forEach(function (el) {
+ el.addEventListener('change', function () { form.submit(); });
+ });
+ });"
+ );
?>
@@ -46,3 +63,2 @@
aria-label="Clear search"
- onclick="this.previousElementSibling.value = ''; this.form.submit();"
>
@@ -54,3 +70,3 @@
<label><?php echo Text::_('COM_EASYSTORE_ORDERS_PAYMENT_STATUS'); ?></label>
- <select name="filter_payment_status" onchange="this.form.submit()">
+ <select name="filter_payment_status">
<option value=""><?php echo Text::_('JALL'); ?></option>
@@ -63,3 +79,3 @@
<label><?php echo Text::_('COM_EASYSTORE_ORDERS_STATUS'); ?></label>
- <select name="filter_order_status" onchange="this.form.submit()">
+ <select name="filter_order_status">
<option value=""><?php echo Text::_('JALL'); ?></option>
@@ -75,4 +91,3 @@
name="filter_date"
- value="<?php echo htmlspecialchars($this->state->get('filter.date', '')); ?>"
- onchange="this.form.submit()">
+ value="<?php echo htmlspecialchars($this->state->get('filter.date', '')); ?>">
</div>
--- a/components/com_easystore/tmpl/order/invoice.php
+++ b/components/com_easystore/tmpl/order/invoice.php
@@ -23,3 +23,4 @@
EasyStoreHelper::wa()
- ->useStyle('com_easystore.invoice');
+ ->useStyle('com_easystore.invoice')
+ ->addInlineScript("document.addEventListener('DOMContentLoaded', function () { window.print(); });");
@@ -32,8 +33,2 @@
?>
-
-<script>
-document.addEventListener('DOMContentLoaded', function () {
- window.print();
-});
-</script>
--- a/components/com_easystore/layouts/filter/search.php
+++ b/components/com_easystore/layouts/filter/search.php
@@ -32,2 +32,14 @@
}
+EasyStoreHelper::wa()->addInlineScript(
+ "window.addEventListener('DOMContentLoaded', () => {
+ const form = document.querySelector('[data-easystore-search]');
+ if (!form) return;
+ const queryElement = form.querySelector('[name=filter_query]');
+ if (!queryElement) return;
+ let previousValue = queryElement.value;
+ queryElement.addEventListener('input', (event) => {
+ if (event.target.value === '' && previousValue !== '') {
+ form.submit();
+ }
+ });
+ });"
+);
?>
@@ -43,15 +55,1 @@
</form>
-
-<script defer>
- window.addEventListener('DOMContentLoaded', () => {
- const form = document.querySelector('[data-easystore-search]');
- const queryElement = form.querySelector('[name=filter_query]');
- let previousValue = queryElement.value;
-
- queryElement.addEventListener('input', (event) => {
- if (event.target.value === '' && previousValue !== '') {
- form.submit();
- }
- });
- });
-</script>