From 8a3331f9d9c1a81919ac96143a4a052d776c7741 Mon Sep 17 00:00:00 2001
From: objecttothis <17935339+objecttothis@users.noreply.github.com>
Date: Fri, 23 Oct 2026 12:39:19 +0400
Subject: [PATCH] feat(export): gate XLSX export behind ext-zip availability
Move ExportPdfWriter out of export_helper.php into its own class
file under App\Libraries so the PDF writer can be autoloaded and
reused independently of the helper functions.
- Add runtime detection of the PHP zip extension and surface it to
the frontend via window.ospos_ext_zip_enabled (app/Views/partial/lang_lines.php)
- Disable the XLSX export menu item in manage_tables.js and
report_tables.js when ext-zip is missing, with a tooltip
explaining why (new Common.export_xlsx_disabled_tooltip lang line)
- Show Zip extension status on the system info page
(app/Views/configs/system_info.php)
- Document the new php-zip requirement and its optional nature for
Excel/XLSX export in docs/BUILD.md and docs/INSTALL.md
- Add explicit namespaces to helper test files
(export_helperTest.php, tabular_helperTest.php, tax_helperTest.php)
for consistency with PSR-4 autoloading
This keeps the app fully functional without ext-zip, only disabling
the Excel export feature that depends on openspout/openspout.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
---
app/Helpers/export_helper.php | 28 ++-------------------------
app/Language/en/Common.php | 1 +
app/Libraries/ExportPdfWriter.php | 29 ++++++++++++++++++++++++++++
app/Views/configs/system_info.php | 3 ++-
app/Views/partial/lang_lines.php | 5 ++++-
docs/BUILD.md | 2 ++
docs/INSTALL.md | 2 +-
public/js/manage_tables.js | 20 +++++++++++++++++++
public/js/report_tables.js | 20 +++++++++++++++++++
tests/Helpers/export_helperTest.php | 2 ++
tests/Helpers/tabular_helperTest.php | 2 ++
tests/Helpers/tax_helperTest.php | 2 ++
12 files changed, 87 insertions(+), 29 deletions(-)
create mode 100644 app/Libraries/ExportPdfWriter.php
diff --git a/app/Helpers/export_helper.php b/app/Helpers/export_helper.php
index 96e969e08..d2645d09c 100644
--- a/app/Helpers/export_helper.php
+++ b/app/Helpers/export_helper.php
@@ -1,5 +1,7 @@
SetFont('DejaVuSans', 'B', EXPORT_PDF_FONT_SIZE);
-
- foreach ($this->exportHeaderValues as $index => $value) {
- $width = $this->exportColumnWidths[$index];
- $text = exportPdfTruncateCellText($this, $value, $width);
- $this->Cell($width, EXPORT_PDF_ROW_HEIGHT, $text, 1, 0, 'C');
- }
- $this->Ln();
-
- $this->SetFont('DejaVuSans', '', EXPORT_PDF_FONT_SIZE);
- }
-}
-
// Columns rendered centered rather than left-aligned.
const EXPORT_PDF_CENTERED_COLUMNS = ['category', 'cost_price', 'unit_price', 'quantity', 'tax_percents'];
diff --git a/app/Language/en/Common.php b/app/Language/en/Common.php
index 7c56f705e..188222d00 100644
--- a/app/Language/en/Common.php
+++ b/app/Language/en/Common.php
@@ -28,6 +28,7 @@ return [
'export_all' => 'Export All',
'export_page' => 'Export Page',
'export_scope_toggle' => 'Export all rows or current page only',
+ 'export_xlsx_disabled_tooltip' => 'ext-zip is required in PHP for Excel export',
'fields_required_message' => 'Fields in red are required',
'fields_required_message_unique' => '',
'first_name' => 'First Name',
diff --git a/app/Libraries/ExportPdfWriter.php b/app/Libraries/ExportPdfWriter.php
new file mode 100644
index 000000000..2ad382e00
--- /dev/null
+++ b/app/Libraries/ExportPdfWriter.php
@@ -0,0 +1,29 @@
+SetFont('DejaVuSans', 'B', EXPORT_PDF_FONT_SIZE);
+
+ foreach ($this->exportHeaderValues as $index => $value) {
+ $width = $this->exportColumnWidths[$index];
+ $text = exportPdfTruncateCellText($this, $value, $width);
+ $this->Cell($width, EXPORT_PDF_ROW_HEIGHT, $text, 1, 0, 'C');
+ }
+ $this->Ln();
+
+ $this->SetFont('DejaVuSans', '', EXPORT_PDF_FONT_SIZE);
+ }
+}
diff --git a/app/Views/configs/system_info.php b/app/Views/configs/system_info.php
index 22c4447cc..fb12e2285 100644
--- a/app/Views/configs/system_info.php
+++ b/app/Views/configs/system_info.php
@@ -28,7 +28,8 @@ use Config\OSPOS;
echo "» Multibyte String: ", extension_loaded('mbstring') ? '✓ Enabled' : '✗ Disabled', '
';
echo "» cURL: ", extension_loaded('curl') ? '✓ Enabled' : '✗ Disabled', '
';
echo "» JSON: ", extension_loaded('json') ? '✓ Enabled' : '✗ Disabled', '
';
- echo "» XML: ", extension_loaded('xml') ? '✓ Enabled' : '✗ Disabled', '
';
+ echo "» XML: ", extension_loaded('xml') ? '✓ Enabled' : '✗ Disabled', '
';
+ echo "» Zip: ", extension_loaded('zip') ? '✓ Enabled' : '✗ Disabled', '
';
?>
diff --git a/app/Views/partial/lang_lines.php b/app/Views/partial/lang_lines.php
index c82b11aba..9b657aa23 100644
--- a/app/Views/partial/lang_lines.php
+++ b/app/Views/partial/lang_lines.php
@@ -6,7 +6,8 @@
'common_close': "= lang('Common.close') ?>",
'common_export_all': "= lang('Common.export_all') ?>",
'common_export_page': "= lang('Common.export_page') ?>",
- 'common_export_scope_toggle': "= lang('Common.export_scope_toggle') ?>"
+ 'common_export_scope_toggle': "= lang('Common.export_scope_toggle') ?>",
+ 'common_export_xlsx_disabled_tooltip': "= lang('Common.export_xlsx_disabled_tooltip') ?>"
};
$.extend(lang, {
@@ -16,4 +17,6 @@
});
})(window.lang = window.lang || {}, jQuery);
+
+ window.ospos_ext_zip_enabled = = extension_loaded('zip') ? 'true' : 'false' ?>;
diff --git a/docs/BUILD.md b/docs/BUILD.md
index 95c71c46b..c5e693099 100644
--- a/docs/BUILD.md
+++ b/docs/BUILD.md
@@ -32,6 +32,8 @@ That's all there is to it.
Note: If you receive messages similar to 'codeigniter4/framework v4.3.1 requires ext-intl', this is an indicator that you do not have intl enabled in php.ini
+Note: If you receive messages similar to 'openspout/openspout requires ext-zip', enable the `zip` extension in php.ini. It is only needed for Excel/XLSX export; the rest of the application builds and runs fine without it, but Excel export will be disabled at runtime.
+
After the build tasks are complete, if you have the database set up and a preconfigured copy of .env, just drop the .env file into the root of the working folder. You should be ready to go.
If you do not have an existing (and upgraded) database, then you will need to continue from this point forward with the standard installation instructions, but at this point you have a runnable version of OSPOS.
diff --git a/docs/INSTALL.md b/docs/INSTALL.md
index 8b41f5ed1..72b2fd58e 100644
--- a/docs/INSTALL.md
+++ b/docs/INSTALL.md
@@ -1,6 +1,6 @@
## Server Requirements
-- PHP version `8.2.35` to `8.4.27` are supported (minimum patch levels required for CVE-2026-109618 / CVE-2026-109619 security fixes). PHP version `≤ 8.1` is NOT supported. Please note that PHP needs to have the extensions `php-json`, `php-gd`, `php-bcmath`, `php-intl`, `php-openssl`, `php-mbstring`, `php-curl` and `php-xml` installed and enabled. An unstable master build can be downloaded in the releases section.
+- PHP version `8.2.35` to `8.4.27` are supported (minimum patch levels required for CVE-2026-109618 / CVE-2026-109619 security fixes). PHP version `≤ 8.1` is NOT supported. Please note that PHP needs to have the extensions `php-json`, `php-gd`, `php-bcmath`, `php-intl`, `php-openssl`, `php-mbstring`, `php-curl`, `php-xml` and `php-zip` installed and enabled. `php-zip` is required for Excel/XLSX export; without it the application still runs normally, but the Excel export option is disabled. An unstable master build can be downloaded in the releases section.
- MySQL `5.7` is supported, also MariaDB replacement `10.x` is supported and might offer better performance.
- Apache `2.4` is supported. Nginx should work fine too, see [wiki page here](https://github.com/opensourcepos/opensourcepos/wiki/Local-Deployment-using-LEMP).
- Raspberry PI based installations proved to work, see [wiki page here]().
diff --git a/public/js/manage_tables.js b/public/js/manage_tables.js
index 2edb50568..472275ae4 100644
--- a/public/js/manage_tables.js
+++ b/public/js/manage_tables.js
@@ -273,9 +273,29 @@
toggle_column_visibility();
init_export_all_toggle();
init_export_all_intercept();
+ init_xlsx_export_gate();
dialog_support.init("button.modal-dlg");
};
+ const init_xlsx_export_gate = function () {
+ if (window.ospos_ext_zip_enabled) {
+ return;
+ }
+
+ const $xlsxItem = $('.export [data-type="xlsx"], .export[data-type="xlsx"]');
+
+ $xlsxItem
+ .addClass('disabled')
+ .attr('aria-disabled', 'true')
+ .attr('title', lang.line('common_export_xlsx_disabled_tooltip'))
+ .off('click')
+ .on('click', function(event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ })
+ .tooltip({ container: 'body' });
+ };
+
let export_all_enabled = false;
const init_export_all_intercept = function () {
diff --git a/public/js/report_tables.js b/public/js/report_tables.js
index 673d4690e..d3b4943ac 100644
--- a/public/js/report_tables.js
+++ b/public/js/report_tables.js
@@ -62,9 +62,29 @@ const report_export_toggle = (function () {
}, true);
};
+ const init_xlsx_export_gate = function () {
+ if (window.ospos_ext_zip_enabled) {
+ return;
+ }
+
+ const $xlsxItem = $('.export [data-type="xlsx"], .export[data-type="xlsx"]');
+
+ $xlsxItem
+ .addClass('disabled')
+ .attr('aria-disabled', 'true')
+ .attr('title', lang.line('common_export_xlsx_disabled_tooltip'))
+ .off('click')
+ .on('click', function(event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ })
+ .tooltip({ container: 'body' });
+ };
+
const init = function () {
init_export_all_toggle();
init_export_all_intercept();
+ init_xlsx_export_gate();
};
return { init };
diff --git a/tests/Helpers/export_helperTest.php b/tests/Helpers/export_helperTest.php
index 9ab20f9a9..dd8a2350a 100644
--- a/tests/Helpers/export_helperTest.php
+++ b/tests/Helpers/export_helperTest.php
@@ -1,5 +1,7 @@