From 286fbfddeda0a239619a0dfbb67106914e4a45a0 Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 19:56:51 -0700 Subject: [PATCH 01/13] src: XmlReader: replace deprecated SAX parser APIs (PHP 8.4 / 8.5) PHP 8.4 deprecated `xml_set_object()` and the use of string method names as callbacks for `xml_set_*_handler()`. PHP 8.5 additionally deprecated `xml_parser_free()` (which has been a no-op since PHP 8.0; the parser is freed automatically when its last reference goes out of scope). Loading any project xml file currently emits: Deprecated: Function xml_set_object() is deprecated since 8.4 ... Deprecated: xml_set_element_handler(): Passing non-callable strings is deprecated since 8.4 ... Deprecated: xml_set_character_data_handler(): Passing non-callable strings is deprecated since 8.4 ... Deprecated: Function xml_parser_free() is deprecated since 8.5 ... Replace the string callbacks with `[object, methodName]` array callables (also valid in PHP 7.4+), and remove the now-deprecated `xml_parser_free()` call. Behavior is unchanged. Co-authored-by: Cursor --- src/XmlReader.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/XmlReader.php b/src/XmlReader.php index 86dc161..028c0fb 100644 --- a/src/XmlReader.php +++ b/src/XmlReader.php @@ -397,9 +397,10 @@ public function __construct(&$query, $filename, $xmlstring = false, $search_tag $this->field_display["YTickLabelInterval"]["Type"] = "HIDE"; } - xml_set_object($this->parser, $this); - xml_set_element_handler($this->parser, 'startElement', 'endElement'); - xml_set_character_data_handler($this->parser, 'cdata'); + // PHP 8.4 deprecated xml_set_object() and string-name callbacks for xml_set_*_handler(). + // Pass [object, methodName] callables instead, which are also valid in PHP 7.4+. + xml_set_element_handler($this->parser, [$this, 'startElement'], [$this, 'endElement']); + xml_set_character_data_handler($this->parser, [$this, 'cdata']); xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false); // 1 = single field, 2 = array field, 3 = record container @@ -520,7 +521,8 @@ public function __construct(&$query, $filename, $xmlstring = false, $search_tag if ($x) { xml_parse($this->parser, $x); - xml_parser_free($this->parser); + // xml_parser_free() was deprecated in PHP 8.5 (no-op since 8.0). + // Parser is freed automatically when $this->parser goes out of scope. } //var_dump($this->data); From 36809dbc726abd2f17577edbec0f91bae18b5dbe Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 19:57:06 -0700 Subject: [PATCH 02/13] src: ReportHtml/Html2pdf: guard closeGroup() against false currentGroup (PHP 8.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `$this->currentGroup` is initialised to `false` and is only promoted to an array by `openGroup()`. When `closeGroup()` is called without a matching `openGroup()` (a common occurrence during certain report layouts/totals paths), PHP 8.1+ emits: Deprecated: Automatic conversion of false to array is deprecated in src/ReportHtml.php on line 348 Deprecated: Automatic conversion of false to array is deprecated in src/ReportHtml2pdf.php on line 347 …and the subsequent writes silently mutate `currentGroup` into a one-key array `[ "endrow" => ... ]`, hiding logic bugs. Both methods now early-return when `currentGroup` is not yet an array, preserving the previous effective no-op behaviour without the deprecation. Co-authored-by: Cursor --- src/ReportHtml.php | 7 +++++++ src/ReportHtml2pdf.php | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/ReportHtml.php b/src/ReportHtml.php index 3e2a140..4747792 100644 --- a/src/ReportHtml.php +++ b/src/ReportHtml.php @@ -343,6 +343,13 @@ public function openGroup() { */ public function closeGroup() { + // PHP 8.1+: writing to $this->currentGroup[...] when it is `false` triggers + // an "Automatic conversion of false to array" deprecation. Treat closeGroup() + // with no open group as a no-op, matching the behaviour of openGroup() init paths. + if (!is_array($this->currentGroup)) { + return; + } + $x= $this->line_count; $this->currentGroup["endrow"] = $this->line_count - 1; $this->jar["pages"][$this->page_count]["rows"][$this->line_count]["closerowsection"] = true; diff --git a/src/ReportHtml2pdf.php b/src/ReportHtml2pdf.php index 8ad6291..015f8d5 100644 --- a/src/ReportHtml2pdf.php +++ b/src/ReportHtml2pdf.php @@ -342,6 +342,13 @@ public function openGroup() { */ public function closeGroup() { + // PHP 8.1+: writing to $this->currentGroup[...] when it is `false` triggers + // an "Automatic conversion of false to array" deprecation. Treat closeGroup() + // with no open group as a no-op. + if (!is_array($this->currentGroup)) { + return; + } + $x= $this->line_count; $this->currentGroup["endrow"] = $this->line_count - 1; $this->jar["pages"][$this->page_count]["rows"][$this->line_count]["closerowsection"] = true; From 50e1d979bc278e8255cb3a5454e1f97563b75c31 Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 19:58:19 -0700 Subject: [PATCH 03/13] src: ReportTCPDF: declare properties + fix newReportPageLineByStyle signature (PHP 8.0 / 8.2) PDF report generation currently produces multiple fatal-level deprecations on PHP 8.2+: Deprecated: Creation of dynamic property Reportico\Engine\ReportTCPDF::$column_spacing is deprecated Deprecated: Creation of dynamic property Reportico\Engine\ReportTCPDF::$abs_row_right_margin is deprecated Deprecated: Creation of dynamic property Reportico\Engine\ReportTCPDF::$abs_col_right_margin is deprecated Deprecated: Creation of dynamic property Reportico\Engine\ReportTCPDF::$abs_row_width is deprecated Deprecated: Creation of dynamic property Reportico\Engine\ReportTCPDF::$abs_columns_width is deprecated and one PHP 8.0+ implicit-required-parameter deprecation: Deprecated: Reportico\Engine\ReportTCPDF::newReportPageLineByStyle(): Optional parameter $txt declared before required parameter $styles is implicitly treated as a required parameter This change: 1. Explicitly declares the five properties on the class so PHP 8.2 no-dynamic-properties rule is satisfied. $column_spacing is initialised to 0 to match what the constructor was lazily setting. 2. Removes the unused default = "" from $txt in newReportPageLineByStyle(); the next parameter is a required by-ref parameter, so $txt was already implicitly required and every call site already passes it. No behaviour change. Co-authored-by: Cursor --- src/ReportTCPDF.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ReportTCPDF.php b/src/ReportTCPDF.php index 6fbbc9b..f0b1dde 100644 --- a/src/ReportTCPDF.php +++ b/src/ReportTCPDF.php @@ -25,6 +25,12 @@ class ReportTCPDF extends Report public $abs_col_left_margin; public $abs_left_margin; public $abs_right_margin; + // PHP 8.2+: declare these explicitly to avoid Creation-of-dynamic-property deprecations. + public $abs_row_right_margin; + public $abs_col_right_margin; + public $abs_row_width; + public $abs_columns_width; + public $column_spacing = 0; public $abs_page_width = 0; public $abs_page_height = 0; public $abs_print_width = 0; @@ -3244,7 +3250,9 @@ public function newReportPageLine($txt = "") $this->newReportPageLineByStyle("LINEPAGE$txt", $this->mid_page_page_styles, false); } - public function newReportPageLineByStyle($txt = "", &$styles, $blankline = false) + // PHP 8.0+: optional parameters cannot precede required ones; $styles is by-ref/required, + // so $txt must also be required (callers always pass it explicitly). + public function newReportPageLineByStyle($txt, &$styles, $blankline = false) { // Line page wrapper $this->applyStyleTags("$txt", $styles); From a4c34803f7307e61bf852ae7d9d57597be01ec9d Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 19:58:40 -0700 Subject: [PATCH 04/13] src: ReporticoSession: guard $_SESSION[null] when namespace key unset (PHP 8.1) When `session_namespace_key` has not been set yet (e.g. very early in a fresh-session bootstrap, before initialise(), or when callers reach the isset helpers from an out-of-order code path), `ReporticoApp::get("session_namespace_key")` returns null. Indexing `$_SESSION` with that null then triggers, on PHP 8.1+: Deprecated: Using null as an array offset is deprecated, use an empty string instead in src/ReporticoSession.php on line 198 The same pattern exists in three helpers: - existsReporticoSession() - issetReporticoSessionParam() - unsetReporticoSessionParam() In all three the semantic answer when the key is null/empty is the same: no Reportico namespace exists yet, so existsReporticoSession() and issetReporticoSessionParam() should return false, and unsetReporticoSessionParam() should be a no-op. Add an explicit guard at the top of each helper that returns the appropriate "namespace does not exist" answer when the key is null or empty, before any $_SESSION array access. This eliminates the deprecation without changing observable behaviour. No callers of these helpers needed updating; they already treat a "namespace does not exist" return value as the absence of state. Co-authored-by: Cursor --- src/ReporticoSession.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/ReporticoSession.php b/src/ReporticoSession.php index 8c40c1c..d2da5ba 100644 --- a/src/ReporticoSession.php +++ b/src/ReporticoSession.php @@ -195,7 +195,13 @@ static function setUpReporticoSession($namespace) */ static function existsReporticoSession() { - if (isset($_SESSION[ReporticoApp::get("session_namespace_key")])) { + $key = ReporticoApp::get("session_namespace_key"); + // PHP 8.1+: $_SESSION[null] triggers "Using null as an array offset is deprecated". + // When session_namespace_key has not been set yet, the namespace by definition does not exist. + if ($key === null || $key === "") { + return false; + } + if (isset($_SESSION[$key])) { return true; } else { return false; @@ -269,7 +275,12 @@ static function issetReporticoSessionParam($param, $session_name = false) { if (!$session_name) $session_name = ReporticoApp::get("session_namespace_key"); - + + // PHP 8.1+: $_SESSION[null] triggers "Using null as an array offset is deprecated". + if ($session_name === null || $session_name === "") { + return false; + } + return isset($_SESSION[$session_name][$param]); } @@ -321,8 +332,13 @@ static function getReporticoSessionParam($param) */ static function unsetReporticoSessionParam($param) { - if (isset($_SESSION[ReporticoApp::get("session_namespace_key")][$param])) { - unset($_SESSION[ReporticoApp::get("session_namespace_key")][$param]); + $key = ReporticoApp::get("session_namespace_key"); + // PHP 8.1+: $_SESSION[null] triggers "Using null as an array offset is deprecated". + if ($key === null || $key === "") { + return; + } + if (isset($_SESSION[$key][$param])) { + unset($_SESSION[$key][$param]); } } From fdca21be0d09b5e009d3ccd1ebd15df79793d14e Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 20:14:44 -0700 Subject: [PATCH 05/13] src: replace `default;` with `default:` in switch statements (PHP 8.5) PHP has historically accepted both `:` and `;` as the terminator for switch case/default labels. PHP 8.5 deprecates the `;` form: Deprecated: Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead There are 5 occurrences of `default;` in the codebase. All five are in switch statements where the `default` arm shares a fall-through body with a `case "LINE":` arm (chart-engine type dispatch), or guards an "unhandled tag" fall-through (XmlReader). Replace `default;` with `default:` at each site. No behaviour change. Affected files: - src/XmlReader.php - src/ChartPchart3.php - src/ChartPchart.php - src/ChartJpgraph.php - dyngraph.php Co-authored-by: Cursor --- dyngraph.php | 2 +- src/ChartJpgraph.php | 2 +- src/ChartPchart.php | 2 +- src/ChartPchart3.php | 2 +- src/XmlReader.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dyngraph.php b/dyngraph.php index 4eb2bf7..026c33a 100755 --- a/dyngraph.php +++ b/dyngraph.php @@ -258,7 +258,7 @@ function convertSpecialChars($intext) $graph->Add($lplot[$lplotct]); break; case "LINE": - default; + default: if ( count($v["data"]) == 1 ) $v["data"][] = 0; $lplot[$lplotct]=new LinePlot($v["data"]); diff --git a/src/ChartJpgraph.php b/src/ChartJpgraph.php index a058f28..5ca74c8 100755 --- a/src/ChartJpgraph.php +++ b/src/ChartJpgraph.php @@ -575,7 +575,7 @@ function generateGraphImage () $graph->Add($lplot[$lplotct]); break; case "LINE": - default; + default: if ( count($v["data"]) == 1 ) $v["data"][] = 0; $lplot[$lplotct]=new LinePlot($v["data"]); diff --git a/src/ChartPchart.php b/src/ChartPchart.php index 7e3129a..e9300fe 100755 --- a/src/ChartPchart.php +++ b/src/ChartPchart.php @@ -828,7 +828,7 @@ public function generateGraphImage($outputfile) $image->writeValues($data->GetData(), $data->GetDataDescription(), $series2); break; case "LINE": - default; + default: if ($linedrawn) { break; } diff --git a/src/ChartPchart3.php b/src/ChartPchart3.php index 72e8583..ca72784 100644 --- a/src/ChartPchart3.php +++ b/src/ChartPchart3.php @@ -778,7 +778,7 @@ public function generateGraphImage($outputfile) break; case "LINE": - default; + default: if (count($v["data"]) == 1) { $v["data"][] = 0; diff --git a/src/XmlReader.php b/src/XmlReader.php index 028c0fb..b4e0b0a 100644 --- a/src/XmlReader.php +++ b/src/XmlReader.php @@ -2308,7 +2308,7 @@ public function &draw_add_button($in_tag, $in_value = false) break; case "mainqueroutppgft":$importtype = "IMPORT"; break; - default; + default: $importtype = false; } From 97540579e912a1112b59662cb3845266bd3ffade Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 20:47:04 -0700 Subject: [PATCH 06/13] src: widgets: declare date-range properties on Criteria and DatePicker (PHP 8.2) When a report with a DATE criteria is opened, PHP 8.2+ emits multiple "Creation of dynamic property" deprecations: Deprecated: Creation of dynamic property Reportico\Widgets\Criteria::$range_start is deprecated in src/widgets/Widget.php on line 242 Deprecated: Creation of dynamic property Reportico\Widgets\DatePicker::$range_name is deprecated in src/widgets/DatePicker.php on line 228 Deprecated: Creation of dynamic property Reportico\Widgets\DatePicker::$range_end_raw is deprecated in src/widgets/DatePicker.php on line 229 Deprecated: Creation of dynamic property Reportico\Widgets\DatePicker::$range_start_raw is deprecated in src/widgets/DatePicker.php on line 229 The locations: - Widget::handleUrlParameters() passes $this->range_start by reference into ReporticoLocale::convertDateRangeDefaultsToDates() when the engine criteria is of type DATE. $this is the surrounding widget (typically a Criteria). - DatePicker::deriveValue() initialises $this->range_name, $this->range_start_raw and $this->range_end_raw to "" before populating them from the request. DateRangePicker and TimeRangePicker already declare the equivalent fields explicitly; this commit aligns DatePicker (range_name, range_start_raw, range_end_raw) and adds the single field that Widget::handleUrlParameters() needs on Criteria (range_start). No behaviour change. Co-authored-by: Cursor --- src/widgets/Criteria.php | 4 ++++ src/widgets/DatePicker.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/widgets/Criteria.php b/src/widgets/Criteria.php index df913d6..6d4c2dc 100644 --- a/src/widgets/Criteria.php +++ b/src/widgets/Criteria.php @@ -30,6 +30,10 @@ class Criteria extends Widget public $buttonTypes = array(); public $formTypes = array(); + // PHP 8.2+: declared explicitly to avoid Creation-of-dynamic-property deprecation. + // Populated by Widget::handleUrlParameters() when criteria_type is "DATE" (passed by reference into ReporticoLocale::convertDateRangeDefaultsToDates()). + public $range_start = false; + public function __construct($engine, $load = false, $engineCriteria = false ) { diff --git a/src/widgets/DatePicker.php b/src/widgets/DatePicker.php index 94ebad1..faf2838 100644 --- a/src/widgets/DatePicker.php +++ b/src/widgets/DatePicker.php @@ -26,6 +26,11 @@ class DatePicker extends Widget public $range_raw = false; public $range_start = false; public $range_end = false; + // PHP 8.2+: declared explicitly to avoid Creation-of-dynamic-property deprecations + // (assigned in deriveValue()). Matches DateRangePicker / TimeRangePicker. + public $range_name = false; + public $range_start_raw = false; + public $range_end_raw = false; public $options = [ "Today" => [ From 470ec3ee43417e87d55d3309c126683d1a5a45c8 Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 20:59:29 -0700 Subject: [PATCH 07/13] src: ReporticoSession: harden remaining $_SESSION accesses against null namespace (PHP 8.1) Follow-up to a4c3480 ("guard $_SESSION[null] when namespace key unset"). That commit guarded the three read-only helpers (existsReporticoSession, issetReporticoSessionParam, unsetReporticoSessionParam), which is enough for the originally reported deprecation. Continued testing surfaced that three more sites in the same file still index $_SESSION by a key sourced from ReporticoApp::get("session_namespace_key"), and so can still emit: Deprecated: Using null as an array offset is deprecated, use an empty string instead in src/ReporticoSession.php The remaining sites are: - setReporticoSessionParam(): writes $_SESSION[$namespace][$param]; $namespace falls back to ReporticoApp::get("session_namespace_key") when not passed. - initializeReporticoNamespace(): reads $_SESSION[$namespace] and unsets it; $namespace is reassigned from ReporticoApp::get(...). (getReporticoSessionParam() is already safe because it short-circuits through the now-guarded issetReporticoSessionParam().) Add the same null/"" early-return guard at both sites so the deprecation cannot recur from any other call path. Behaviour is unchanged for callers that already have a valid namespace key. Co-authored-by: Cursor --- src/ReporticoSession.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ReporticoSession.php b/src/ReporticoSession.php index d2da5ba..1c1801c 100644 --- a/src/ReporticoSession.php +++ b/src/ReporticoSession.php @@ -299,6 +299,12 @@ static function setReporticoSessionParam($param, $value, $namespace = false, $ar if (!$namespace) $namespace = ReporticoApp::get("session_namespace_key"); + // PHP 8.1+: $_SESSION[null] triggers "Using null as an array offset is deprecated". + // If the namespace key is not yet set there is no session bucket to write to. + if ($namespace === null || $namespace === "") { + return; + } + //echo "Set $namespace:$param
"; if (!$array) { $_SESSION[$namespace][$param] = $value; @@ -386,7 +392,9 @@ static function reporticoNamespace() static function initializeReporticoNamespace($namespace = "reportico") { $namespace = ReporticoApp::get("session_namespace_key"); - if (isset($_SESSION[$namespace])) { + // PHP 8.1+: $_SESSION[null] triggers "Using null as an array offset is deprecated". + // Nothing to clear if the namespace key has not been set. + if ($namespace !== null && $namespace !== "" && isset($_SESSION[$namespace])) { unset($_SESSION[$namespace]); } From 79e87d3b1531e01bee14b347e44ff6f0fb86ac4d Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 21:12:23 -0700 Subject: [PATCH 08/13] composer: require Twig ^3.19 for PHP 8.4+ compatibility Twig 3.5.x triggers deprecations on PHP 8.4+ (implicitly nullable parameters in FilesystemLoader, Environment::getTemplateClass, etc.). Reportico's error handler treats those deprecations as fatal UI errors, which caused a white screen when opening reports that render Twig templates (e.g. Open Jobs criteria PREPARE). Raise the floor to Twig 3.19+ so installs on PHP 8.5 resolve to a current 3.x release with explicit nullable types. Run `composer update twig/twig` after merge to refresh the lockfile. Co-authored-by: Cursor --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1d74b1d..ac29a4c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "require": { "php": ">=8.0", "reportico/adodb-php": "^8.1", - "twig/twig": "^3.0", + "twig/twig": "^3.19", "tecnickcom/tcpdf": "^6.2", "szymach/c-pchart": "^3.0", "reportico/assetter": "^8.1", From 8695a6513fb403eff55f3c5734e08c943c2caecb Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 21:19:51 -0700 Subject: [PATCH 09/13] src: PostgreSQL string literals for CHAR criteria + remove DatePicker debug echo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgreSQL treats double-quoted tokens as identifiers. QueryColumn::getValueDelimiter() returned '"' for CHAR criteria, so expanded criteria like {wanteddate} became `... < "2026-05-13"`, which PostgreSQL reads as "column 2026-05-13" and throws SQLSTATE[42703] — an uncaught PDOException and a white screen on reports such as Open Jobs (lookup SQL uses the wanteddate criterion). Use single-quote delimiters when the ADOdb/PDO driver is PostgreSQL. Also remove a stray `echo $cls` in DatePicker::getCriteriaClause() left from debugging, which corrupted HTML/ajax responses. Co-authored-by: Cursor --- src/QueryColumn.php | 16 ++++++++++++++++ src/widgets/DatePicker.php | 1 - 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/QueryColumn.php b/src/QueryColumn.php index 89fc540..11d8c38 100644 --- a/src/QueryColumn.php +++ b/src/QueryColumn.php @@ -280,6 +280,22 @@ public function __call($method, $args) public function getValueDelimiter() { if (strtoupper($this->column_type) == "CHAR") { + // PostgreSQL (and the SQL standard) use single quotes for string literals. + // Double quotes delimit identifiers, so a date like "2026-05-13" is parsed as a + // column name and fails with "column does not exist" — uncaught PDOException → WSOD. + $driver = ""; + if ($this->datasource && !empty($this->datasource->_conn_driver)) { + $driver = strtolower((string) $this->datasource->_conn_driver); + } + if ( + $driver === "pdo_pgsql" + || $driver === "postgres" + || $driver === "pgsql" + || str_contains($driver, "pgsql") + ) { + return "'"; + } + return ("\""); } diff --git a/src/widgets/DatePicker.php b/src/widgets/DatePicker.php index faf2838..a7f2c13 100644 --- a/src/widgets/DatePicker.php +++ b/src/widgets/DatePicker.php @@ -204,7 +204,6 @@ public function getCriteriaClause($lhs = true, $operand = true, $rhs = true, $rh } } - echo $cls; $del = ""; if ($add_del) { From 1b5dca4e468d182965855f48b120e05b9975424f Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 21:19:51 -0700 Subject: [PATCH 10/13] src: ignore E_DEPRECATED in ReporticoApp::ErrorHandler Vendor and extension deprecations on PHP 8.x were classified as "Fatal Error" and appended to system_errors, which forces error-rendering paths and can yield blank or broken pages even when the underlying request would succeed. Return early for E_DEPRECATED and E_USER_DEPRECATED so PHP's default handling applies and Reportico does not treat them as blocking report errors. Co-authored-by: Cursor --- src/ReporticoApp.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ReporticoApp.php b/src/ReporticoApp.php index 094c04d..f28fd07 100644 --- a/src/ReporticoApp.php +++ b/src/ReporticoApp.php @@ -352,6 +352,12 @@ static function ErrorLogger($errno, $errstr, $errfile = false, $errline = false) // error handler function static function ErrorHandler($errno, $errstr, $errfile, $errline) { + // PHP 8.x: deprecations must not be stored as blocking "system errors" — Reportico treats + // anything in that list like a fatal in several execute paths (blank / broken pages). + if ($errno === E_DEPRECATED || $errno === E_USER_DEPRECATED) { + return true; + } + switch ($errno) { case E_ERROR: $errtype = ReporticoLang::translate("Error"); From ff07334f51119d19bffb219c96af3c1827620c85 Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Tue, 12 May 2026 21:25:01 -0700 Subject: [PATCH 11/13] start/run/partial/dyngraph: raise default PHP memory limit for large reports The previous hard cap of 100M is easy to hit on big result sets: assignment expressions are eval()'d per row (Reportico::assign), and PDF/HTML rendering buffers additional data. That produced: Allowed memory size of 104857600 bytes exhausted ... eval()'d code Default is now 512M. Optional override: set environment variable REPORTICO_MEMORY_LIMIT (e.g. `768M` or `1G`) before the web/PHP process if a specific deployment needs more. Co-authored-by: Cursor --- dyngraph.php | 2 +- partial.php | 2 +- run.php | 2 +- start.php | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dyngraph.php b/dyngraph.php index 026c33a..f82805a 100755 --- a/dyngraph.php +++ b/dyngraph.php @@ -12,7 +12,7 @@ */ - ini_set("memory_limit","100M"); + ini_set("memory_limit", getenv("REPORTICO_MEMORY_LIMIT") ?: "512M"); error_reporting(E_ALL); date_default_timezone_set(@date_default_timezone_get()); diff --git a/partial.php b/partial.php index c664912..5efcb65 100755 --- a/partial.php +++ b/partial.php @@ -18,7 +18,7 @@ error_reporting(E_ALL); date_default_timezone_set(@date_default_timezone_get()); - ini_set("memory_limit","100M"); + ini_set("memory_limit", getenv("REPORTICO_MEMORY_LIMIT") ?: "512M"); //ob_start(); require_once('reportico.php'); diff --git a/run.php b/run.php index 2d0923b..c85295b 100755 --- a/run.php +++ b/run.php @@ -25,7 +25,7 @@ date_default_timezone_set(@date_default_timezone_get()); // Reserver 100Mb for running -ini_set("memory_limit","100M"); +ini_set("memory_limit", getenv("REPORTICO_MEMORY_LIMIT") ?: "512M"); // Allow a good time for long reports to run. Set to 0 to allow unlimited time ini_set("max_execution_time","90"); diff --git a/start.php b/start.php index 142bfcc..7c788b8 100755 --- a/start.php +++ b/start.php @@ -22,7 +22,8 @@ date_default_timezone_set(@date_default_timezone_get()); // Reserver 100Mb for running -ini_set("memory_limit","100M"); +// Large reports (many rows × assignments / PDF / grids) can exceed 100M; allow more headroom. +ini_set("memory_limit", getenv("REPORTICO_MEMORY_LIMIT") ?: "512M"); // Allow a good time for long reports to run. Set to 0 to allow unlimited time ini_set("max_execution_time","90"); From 01aa984db9b58a2e5d4a86d8aee10385e9367b3c Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Sun, 24 May 2026 18:53:56 -0700 Subject: [PATCH 12/13] start/run: raise default max_execution_time to 300s for heavy reports Large PostgreSQL reports (e.g. Contact Phone Email Sales History) can exceed the previous 90s PHP limit after the SQL completes. Default is now 300s; override with REPORTICO_MAX_EXECUTION_TIME in the environment if needed. Co-authored-by: Cursor --- run.php | 2 +- start.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/run.php b/run.php index c85295b..9df6565 100755 --- a/run.php +++ b/run.php @@ -28,7 +28,7 @@ ini_set("memory_limit", getenv("REPORTICO_MEMORY_LIMIT") ?: "512M"); // Allow a good time for long reports to run. Set to 0 to allow unlimited time -ini_set("max_execution_time","90"); +ini_set("max_execution_time", getenv("REPORTICO_MAX_EXECUTION_TIME") ?: "300"); // Instantiate Reportico $q = new Reportico\Engine\Reportico(); diff --git a/start.php b/start.php index 7c788b8..4f38f93 100755 --- a/start.php +++ b/start.php @@ -26,7 +26,7 @@ ini_set("memory_limit", getenv("REPORTICO_MEMORY_LIMIT") ?: "512M"); // Allow a good time for long reports to run. Set to 0 to allow unlimited time -ini_set("max_execution_time","90"); +ini_set("max_execution_time", getenv("REPORTICO_MAX_EXECUTION_TIME") ?: "300"); // Only turn on output buffering if necessary, normally leave this commented //ob_start(); From c151deb56a904fa600dbc5a0f7dd4c8561e8ae02 Mon Sep 17 00:00:00 2001 From: DataBoost Devs Date: Mon, 22 Jun 2026 14:59:39 -0700 Subject: [PATCH 13/13] Fix report execution and menu scanning on PHP 8.x Harden XmlReader and findFileToInclude when report XML is missing, skip non-.xml files in project menus, pass project/xmlin on criteria execute, and declare TimeRangePicker::$range_name for PHP 8.2. Co-authored-by: Cursor --- src/Reportico.php | 2 +- src/ReporticoUtility.php | 5 +++++ src/XmlReader.php | 28 ++++++++++++++++------------ src/widgets/AdminMenu.php | 3 +++ src/widgets/CriteriaForm.php | 5 +++++ src/widgets/ProjectMenu.php | 4 ++++ src/widgets/TimeRangePicker.php | 1 + 7 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Reportico.php b/src/Reportico.php index bef0fc8..0f6a2f5 100755 --- a/src/Reportico.php +++ b/src/Reportico.php @@ -4963,7 +4963,7 @@ public function generateDropdownMenu(&$menu) } $filename = $proj_parent . "/" . $project . "/" . $menuitem["reportfile"]; - if (!preg_match("/\.xml/", $filename)) { + if (!preg_match("/\.xml$/i", $filename)) { $filename .= ".xml"; } diff --git a/src/ReporticoUtility.php b/src/ReporticoUtility.php index 054cefd..9a02b9f 100644 --- a/src/ReporticoUtility.php +++ b/src/ReporticoUtility.php @@ -159,6 +159,11 @@ static function backtrace() // Look for a file in the include path, or the path of the current source file static function findFileToInclude($file_path, &$new_file_path, &$rel_to_include = "") { + if (!$file_path || !is_string($file_path)) { + $new_file_path = $file_path; + return false; + } + // First look in path of current file static $_path_array = null; if (__DIR__) { diff --git a/src/XmlReader.php b/src/XmlReader.php index b4e0b0a..7cc09c4 100644 --- a/src/XmlReader.php +++ b/src/XmlReader.php @@ -468,7 +468,11 @@ public function __construct(&$query, $filename, $xmlstring = false, $search_tag echo "XML $filename
"; } if ($this->query) { - $readfile = $this->query->projects_folder . "/" . ReporticoApp::getConfig("project") . "/" . $filename; + if (!empty($this->query->reports_path)) { + $readfile = $this->query->reports_path . "/" . $filename; + } else { + $readfile = $this->query->projects_folder . "/" . ReporticoApp::getConfig("project") . "/" . $filename; + } $adminfile = $this->query->admin_projects_folder . "/admin/" . $filename; } else { $readfile = $filename; @@ -493,27 +497,27 @@ public function __construct(&$query, $filename, $xmlstring = false, $search_tag if ($readfile && is_file($readfile)) { $readfile = $readfile; } else { - if (!is_file($adminfile)) { + if ($adminfile && !is_file($adminfile)) { ReporticoUtility::findFileToInclude($adminfile, $readfile); - if (is_file($readfile)) { - $readfile = $readfile; - } - - } else { + } elseif ($adminfile && is_file($adminfile)) { $use_admin_xml = true; $readfile = $adminfile; } } - if ($readfile) { + if ($readfile && is_file($readfile)) { //if ( $use_admin_xml ) //Authenticator::flag("admin-report-selected"); - if ( !file_exists($readfile) ) { - ReporticoApp::backtrace(); - } $x = join("", file($readfile)); + } elseif ($this->search_tag) { + // Menu title lookup for a non-report path; skip without fatal error + $this->search_response = ""; + } elseif ($readfile) { + $report_path = ($this->query && $this->query->reports_path) ? $this->query->reports_path : $readfile; + trigger_error("Report Definition File " . $report_path . "/" . $filename . " Not Found", E_USER_ERROR); } else { - trigger_error("Report Definition File " . $this->query->reports_path . "/" . $filename . " Not Found", E_USER_ERROR); + $report_path = ($this->query && $this->query->reports_path) ? $this->query->reports_path : ""; + trigger_error("Report Definition File " . $report_path . "/" . $filename . " Not Found", E_USER_ERROR); } } diff --git a/src/widgets/AdminMenu.php b/src/widgets/AdminMenu.php index f93acbf..e040b2e 100644 --- a/src/widgets/AdminMenu.php +++ b/src/widgets/AdminMenu.php @@ -131,6 +131,9 @@ public function generateMenuList () if (is_dir(ReporticoApp::get("projpath"))) { if ($dh = opendir(ReporticoApp::get("projpath"))) { while (($file = readdir($dh)) !== false) { + if (!preg_match('/\.xml$/i', $file)) { + continue; + } $mtch = "/" . $menuitem["report"] . "/"; if (preg_match($mtch, $file)) { $repxml = new XmlReader($this->engine, $file, false, "ReportTitle"); diff --git a/src/widgets/CriteriaForm.php b/src/widgets/CriteriaForm.php index ae4ced7..00fcc15 100644 --- a/src/widgets/CriteriaForm.php +++ b/src/widgets/CriteriaForm.php @@ -98,9 +98,14 @@ public function render() } + $project = htmlspecialchars(ReporticoApp::getConfig("project", ""), ENT_QUOTES); + $xmlin = htmlspecialchars($this->engine->xmlinput ? $this->engine->xmlinput : "", ENT_QUOTES); + $sections["begin"] = "
+ + "; $sections["end"] = "
"; diff --git a/src/widgets/ProjectMenu.php b/src/widgets/ProjectMenu.php index 3c24fad..f7750b9 100644 --- a/src/widgets/ProjectMenu.php +++ b/src/widgets/ProjectMenu.php @@ -116,6 +116,10 @@ public function generateMenuList () if (is_dir(ReporticoApp::get("projpath"))) { if ($dh = opendir(ReporticoApp::get("projpath"))) { while (($file = readdir($dh)) !== false) { + // Only real report files (e.g. Report.xml), not *.xml.bak.* backups + if (!preg_match('/\.xml$/i', $file)) { + continue; + } $mtch = "/" . $menuitem["report"] . "/"; if (preg_match($mtch, $file)) { $repxml = new XmlReader($this->engine, $file, false, "ReportTitle"); diff --git a/src/widgets/TimeRangePicker.php b/src/widgets/TimeRangePicker.php index 991f83c..23786ea 100644 --- a/src/widgets/TimeRangePicker.php +++ b/src/widgets/TimeRangePicker.php @@ -29,6 +29,7 @@ class TimeRangePicker extends Widget public $range_end = false; public $range_raw = false; public $derived = false; + public $range_name = false; public $options = [ "Today" => [