git: d339f7c79a44 - 2026Q3 - finance/frontaccounting: Update 2.4.12 => 2.4.20

From: Vladimir Druzenko <vvd_at_FreeBSD.org>
Date: Mon, 07 Sep 2026 18:30:39 UTC
The branch 2026Q3 has been updated by vvd:

URL: https://cgit.FreeBSD.org/ports/commit/?id=d339f7c79a44c5ab64b219ab26f56db5ef0e5a93

commit d339f7c79a44c5ab64b219ab26f56db5ef0e5a93
Author:     James Elstone <james@elstone.net>
AuthorDate: 2026-09-07 18:12:44 +0000
Commit:     Vladimir Druzenko <vvd@FreeBSD.org>
CommitDate: 2026-09-07 18:30:33 +0000

    finance/frontaccounting: Update 2.4.12 => 2.4.20
    
    Changelog:
    https://frontaccounting.com/wbt/pages/posts/release-2.4.13-230.php
    https://frontaccounting.com/wbt/pages/posts/release-2.4.14-231.php
    https://frontaccounting.com/wbt/pages/posts/release-2.4.15-232.php
    https://frontaccounting.com/wbt/pages/posts/release-2.4.15-232.php
    https://frontaccounting.com/wbt/pages/posts/release-2.4.16-233.php
    https://frontaccounting.com/wbt/pages/posts/release-2.4.17-234.php
    https://frontaccounting.com/wbt/pages/posts/release-2.4.18-235.php
    https://frontaccounting.com/wbt/pages/posts/release-2.4.19-236.php
    https://frontaccounting.com/wbt/pages/posts/release-2.4.20-237.php
    
    - Replaced the old FreeBSD patch that enabled the late ini_set() calls.
    - Upstream corrected the assignment to $deliver_to.
    - Added a port patch for PDF parser and page rotation
    - Add two FPDI fixes still required in upstream 2.4.20: declare
      $actual_obj and handle missing page rotation without accessing an
      array offset on false.
    - Added .htaccess protection for .bak, .orig and .rej files.
    - Correct the NGINX option conditional (PORT_OPTIONS:MNGINX).
    - Replaced %%APACHE_VERSION%% substitutions with literal 2.4 filenames.
    - Corrected the application path and session guidance; added backup
      advice.
    - Remove redundant DISTNAME and DISTVERSIONSUFFIX settings.
    - Regenerated all four patches using make makepatch.
    - Handles empty stock-history results without PHP warnings during
      invoice/delivery voiding.
    - Replaces unsupported MariaDB 10.6 with 10.11.
    - Allows remote database connections without installing a local database
      client/server (Makes clear which choices install local packages.)
    - Keeps mysqli unconditional and removes its duplicate conditional
      declaration.
    - Fix for iconv dependency issues when sending emails.
    - Use OPTIONS_SINGLE for database connectivity, requiring exactly one
      selection.
    - Declare mysqli once, unconditionally.
    - Replace USES+= php:cgi php:cli with USES+= php:cgi,cli. Verified that
      both arguments reach the PHP framework when no web-server option is
      selected.
    - Add an optional company-specific email-template hook. Company-specific
      wording and configuration are not included
      (create company_path() . '/reporting/email_template.php).
    - Replace PORTVERSION with DISTVERSION.
    - Remove unnecessary MKDIR in do-install.
    
    Retained changes include the 2.4.20 update, session and FPDI fixes, empty-stock-history handling, iconv dependency, MariaDB 10.11 and remote-database options, source-backup protection, packing-list corrections and installation guidance.
    
    PR:             298278
    Sponsored by:   UNIS Labs (vvd)
    Co-authored-by: Vladimir Druzenko <vvd@FreeBSD.org>
    MFH:            2026Q3
    
    (cherry picked from commit 7b541db13e036981477a5d4e7997c432a918da8a)
---
 finance/frontaccounting/Makefile                   |  40 ++---
 finance/frontaccounting/distinfo                   |   6 +-
 finance/frontaccounting/files/email_template.php   | 197 +++++++++++++++++++++
 finance/frontaccounting/files/patch-.htaccess      |   9 +
 .../files/patch-includes_db_inventory__db.inc      |  20 +++
 .../files/patch-includes_session.inc               |  23 +--
 ...h-reporting_includes_fpdi_fpdi__pdf__parser.php |  11 ++
 .../patch-reporting_includes_fpdi_pdf__parser.php  |  10 ++
 .../files/patch-reporting_includes_pdf__report.inc |  30 ++++
 finance/frontaccounting/files/pkg-message.in       |  23 ++-
 finance/frontaccounting/pkg-plist                  |   9 +-
 11 files changed, 330 insertions(+), 48 deletions(-)

diff --git a/finance/frontaccounting/Makefile b/finance/frontaccounting/Makefile
index ded158e42543..ad838d58f9f9 100644
--- a/finance/frontaccounting/Makefile
+++ b/finance/frontaccounting/Makefile
@@ -1,10 +1,7 @@
 PORTNAME=		frontaccounting
-PORTVERSION=		2.4.12
-PORTREVISION=		1
-DISTVERSIONSUFFIX=	${PORTVERSION}
+DISTVERSION=		2.4.20
 CATEGORIES=		finance www
-MASTER_SITES=		SF/${DIST_SUBDIR}/FrontAccounting%202.4/${PORTVERSION}
-DISTNAME=		frontaccounting-${PORTVERSION}
+MASTER_SITES=		SF/${DIST_SUBDIR}/FrontAccounting%202.4/${DISTVERSION}
 DIST_SUBDIR=		frontaccounting
 
 MAINTAINER=		james@elstone.net
@@ -15,7 +12,7 @@ LICENSE=		GPLv3+
 LICENSE_FILE=		${WRKSRC}/doc/license.txt
 
 USES=			cpe
-USE_PHP+=		mysqli session ctype zlib
+USE_PHP=		ctype iconv mysqli session zlib
 
 NO_BUILD=		yes
 SUB_FILES=		pkg-deinstall pkg-message
@@ -23,21 +20,22 @@ SUB_FILES=		pkg-deinstall pkg-message
 WRKSRC=			${WRKDIR}/${DIST_SUBDIR}
 
 OPTIONS_DEFINE=		APACHE LIGHTTPD NGINX NLS DOCS
-OPTIONS_DEFAULT=	APACHE MYSQL_SERVER
-OPTIONS_RADIO=		RG1
-OPTIONS_RADIO_RG1=	MARIADB_CLIENT MYSQL_CLIENT MYSQL_SERVER
+OPTIONS_DEFAULT=	APACHE REMOTE_DB
+OPTIONS_SINGLE=		RG1
+OPTIONS_SINGLE_RG1=	REMOTE_DB MARIADB_CLIENT MYSQL_CLIENT MYSQL_SERVER
 
-MARIADB_CLIENT_DESC=	MariaDB client connector support
-MYSQL_CLIENT_DESC=	MySQL client connector support
-MYSQL_SERVER_DESC=	MySQL server support
+MARIADB_CLIENT_DESC=	Install MariaDB 10.11 client
+MYSQL_CLIENT_DESC=	Install MySQL client
+MYSQL_SERVER_DESC=	Install MySQL server
 NGINX_DESC=		NGINX web server
-RG1_DESC=		Database Connectivity
+REMOTE_DB_DESC=		Use remote database (PHP mysqli only)
+RG1_DESC=		Database connectivity
 
 APACHE_USES=		apache:run php:web
-DOCS_VARS_OFF=	PORTDOCSARGS='-not -path "./doc*"'
+DOCS_VARS_OFF=		PORTDOCSARGS='-not -path "./doc*"'
 LIGHTTPD_RUN_DEPENDS=	lighttpd:www/lighttpd
 LIGHTTPD_USES=		php:web
-MARIADB_CLIENT_USES=	mysql:106m
+MARIADB_CLIENT_USES=	mysql:1011m,client
 MYSQL_CLIENT_USES=	mysql:client
 MYSQL_SERVER_USES=	mysql:server
 NGINX_RUN_DEPENDS=	nginx:www/nginx
@@ -46,17 +44,15 @@ NLS_USES=		gettext
 
 .include <bsd.port.options.mk>
 
-.if !${PORT_OPTIONS:MAPACHE} && !${PORT_OPTIONS:MLIGHTTPD} && !{PORT_OPTIONS:MNGINX}
-USES+=			php:cgi php:cli
-.endif
-
-.if ${PORT_OPTIONS:MMYSQL_SERVER} || ${PORT_OPTIONS:MMARIADB_CLIENT}
-USE_PHP+=		mysqli
+.if !${PORT_OPTIONS:MAPACHE} && !${PORT_OPTIONS:MLIGHTTPD} && !${PORT_OPTIONS:MNGINX}
+USES+=			php:cgi,cli
 .endif
 
 do-install:
-	@${MKDIR} ${STAGEDIR}${WWWDIR}
 	(cd ${WRKSRC} && ${COPYTREE_SHARE} . \
 		${STAGEDIR}${WWWDIR} ${PORTDOCSARGS})
+	@${MKDIR} ${STAGEDIR}${WWWDIR}/company/examples/reporting
+	${INSTALL_DATA} ${FILESDIR}/email_template.php \
+		${STAGEDIR}${WWWDIR}/company/examples/reporting/email_template.php
 
 .include <bsd.port.mk>
diff --git a/finance/frontaccounting/distinfo b/finance/frontaccounting/distinfo
index 7bd9710bf637..64f781ac1676 100644
--- a/finance/frontaccounting/distinfo
+++ b/finance/frontaccounting/distinfo
@@ -1,3 +1,3 @@
-TIMESTAMP = 1703926379
-SHA256 (frontaccounting/frontaccounting-2.4.12.tar.gz) = 6e4a0eaf388550655dcb696668bac977a2cb2f5f3e78ee14b7d61879b7c3e808
-SIZE (frontaccounting/frontaccounting-2.4.12.tar.gz) = 1876122
+TIMESTAMP = 1788631819
+SHA256 (frontaccounting/frontaccounting-2.4.20.tar.gz) = 01a7ed6b83e3305d40e3c37eb23db0dddd60870dc0215c72135834694693f336
+SIZE (frontaccounting/frontaccounting-2.4.20.tar.gz) = 1890935
diff --git a/finance/frontaccounting/files/email_template.php b/finance/frontaccounting/files/email_template.php
new file mode 100644
index 000000000000..76a9ed452553
--- /dev/null
+++ b/finance/frontaccounting/files/email_template.php
@@ -0,0 +1,197 @@
+<?php
+/*
+ * Example for the optional company email-template hook.
+ * Copy to company/<company-id>/reporting/email_template.php.
+ * Requires the pdf_report.inc email-template hook patch; this is not a
+ * standard feature of unpatched FrontAccounting 2.4.20.
+ *
+ * The file must return a callable. It is loaded separately for each contact.
+ * Return array() to keep the standard email. Optional return keys:
+ *   body   - introductory text, ending with two newlines
+ *   sender - signature details (without a closing greeting)
+ * FrontAccounting adds payment links, "Kindest regards", and the signature.
+ * Recipients, subject and attachments are handled by FrontAccounting.
+ * Do not echo, send mail, or modify database records here.
+ *
+ * CALLBACK FIELD REFERENCE -- FrontAccounting 2.4.20 + this port's hook
+ * ==================================================================
+ * Use $contact['email'], $formData['reference'], etc.; array keys are NOT
+ * separate variables. Names are case-sensitive (including DebtorName and
+ * StockItemName). Use ?? or isset(): fields can be absent, null or empty.
+ * Database numbers/flags can be strings. Dates normally use YYYY-MM-DD;
+ * format them for display rather than assuming they are localized.
+ *
+ * These are the standard source fields, not a guarantee that every report
+ * supplies every key. Custom reports, company headers and extensions can
+ * add/replace fields. In particular, $company is an extensible preference
+ * map, so no fixed list can describe every installation.
+ *
+ * $contact -- the current email recipient (one callback per contact)
+ * ----------------------------------------------------------------
+ * Standard crm_persons fields:
+ *   id          Contact/person record ID (not an invoice/customer ID).
+ *   ref         Contact reference/short name.
+ *   name        First contact name field.
+ *   name2       Second contact name field; may be empty.
+ *   address     Contact postal address.
+ *   phone       Primary contact phone number.
+ *   phone2      Alternative contact phone number.
+ *   fax         Contact fax number.
+ *   email       Recipient email address; changing this argument does not
+ *               change the address used by the renderer to send the mail.
+ *   lang        Contact language code.
+ *   notes       Internal contact notes; do not include by default.
+ *   inactive    Contact inactivity flag.
+ * Depending on which standard contact query the report uses, also:
+ *   type        Relationship class, e.g. customer, cust_branch, supplier.
+ *   action      Contact purpose, e.g. invoice, order, delivery, general.
+ *   ext_type    Combined type.action (branch contact queries).
+ *   contact_id  Relationship record ID (get_crm_persons queries).
+ *   description, system
+ *               Contact-category description and system-category flag
+ *               (get_crm_persons joins crm_categories with crm_persons).
+ * Numeric SQL-result indexes may also exist; use named keys instead.
+ *
+ * $formData -- document/header data, NOT the complete transaction row
+ * -----------------------------------------------------------------
+ * Prepared by SetCommonData and the standard doctext.inc header:
+ *   doctype         Document type; sales invoice = 10. This is distinct
+ *                   from the source transaction's type field below.
+ *   document_name   Localized label, e.g. "Invoice No.".
+ *   document_number Display number/reference selected by report settings.
+ *   document_date   Document date selected by the header.
+ *   document_amount Initially ov_amount + ov_freight + ov_gst. Do not
+ *                   assume a universally valid payable balance: headers
+ *                   can overwrite it (stock sales-order header does so).
+ *   recipient_name  Name selected for the document's primary address.
+ *   rep_lang        Report language, taken from the first supplied contact;
+ *                   may differ from this recipient's $contact['lang'].
+ *   domicile        Company domicile, added for invoices/statements.
+ *
+ * All source-row keys copied by SetCommonData, only when present/non-null:
+ *   Identifiers/references:
+ *     order_no, reference, id, trans_no, type, order_, wo_ref, debtor_ref,
+ *     requisition_no
+ *   Dates:
+ *     ord_date, date_, tran_date, delivery_date, due_date, required_by
+ *   Party/location/item data:
+ *     name, location_name, delivery_address, supp_name, address,
+ *     DebtorName, supp_account_no, tax_id, contact, StockItemName
+ *   Amounts/settings:
+ *     ov_freight, ov_gst, ov_amount, prepaid, payment_terms, curr_code,
+ *     units_issued
+ *   payment_terms is an ID, not the payment-terms description.
+ *   prepaid may be converted to "partial" or "final" by the invoice header.
+ *
+ * Additional keys copied from the other SetCommonData arguments:
+ *   Customer branch: br_address, br_name, salesman, disable_branch
+ *   Sales order:     deliver_to, delivery_address, customer_ref
+ *   Bank account:    bank_name, bank_account_number, payment_service
+ *   salesman is an ID, not the salesperson's name. A bank account or
+ *   payment service is only present when the report supplies that data.
+ *   Later arguments can overwrite an earlier key such as delivery_address.
+ *
+ * $company -- current company's get_company_prefs() / sys_prefs values
+ * -----------------------------------------------------------------
+ * Common fields for customer-facing text:
+ *   coy_name        Company name.
+ *   postal_address  Company postal address (may contain line breaks).
+ *   phone, fax, email
+ *                   Company contact details.
+ *   coy_no          Company registration number.
+ *   gst_no          Company tax/VAT registration number.
+ *   domicile        Registered domicile text.
+ *   coy_logo        Logo filename, not an image attachment or URL.
+ *   curr_default    Company base currency code; use formData.curr_code for
+ *                   the document currency when available.
+ *   legal_text      Configured legal/footer text.
+ *   bcc_email       Internal BCC setting; not customer-facing text.
+ *
+ * Other preference keys seeded by the stock 2.4.20 en_US-new.sql database
+ * (settings/account codes, not automatically formatted descriptions):
+ *   tax_prd, tax_last, use_dimension, f_year, shortname_name_in_list,
+ *   no_item_list, no_customer_list, no_supplier_list, base_sales, time_zone,
+ *   add_pct, round_to, login_tout, past_due_days, profit_loss_year_act,
+ *   retained_earnings_act, bank_charge_act, exchange_diff_act, tax_algorithm,
+ *   default_credit_limit, accumulate_shipping, freight_act, debtors_act,
+ *   default_sales_act, default_sales_discount_act, default_prompt_payment_act,
+ *   default_delivery_required, default_receival_required, default_quote_valid_days,
+ *   default_dim_required, pyt_discount_act, creditors_act, po_over_receive,
+ *   po_over_charge, allow_negative_stock, default_inventory_act, default_cogs_act,
+ *   default_adj_act, default_inv_sales_act, default_wip_act,
+ *   default_workorder_required, version_id, auto_curr_reval, grn_clearing_act,
+ *   deferred_income_act, gl_closing_date, alternative_tax_include_on_docs,
+ *   no_zero_lines_amount, show_po_item_codes, accounts_alpha, loc_notification,
+ *   print_invoice_no, allow_negative_prices, print_item_images_on_quote,
+ *   suppress_tax_rates, company_logo_report, barcodes_on_stock,
+ *   print_dialog_direct, ref_no_auto_increase, default_loss_on_asset_disposal_act,
+ *   depreciation_period, use_manufacturing, dim_on_recurrent_invoice,
+ *   long_description_invoice, max_days_in_docs, use_fixed_assets,
+ *   company_logo_on_views
+ * An upgraded database can have additional preferences, and custom charts
+ * of accounts can seed different values. Use ?? for optional preferences.
+ *
+ * $user -- string: display name of the logged-in user generating the report.
+ * It is not a user object, username, email address or database connection.
+ *
+ * Not supplied as callback arguments:
+ *   PDF bytes/path, invoice line items, attachments, email subject, mail
+ *   object, full customer record, passwords or database connection details.
+ *   $this is unavailable in this static callback. The renderer's local
+ *   variables do not become callback variables automatically.
+ *
+ * Variables created by this example below (not additional FA input fields):
+ *   $name, $reference, $greeting, $document, $body, $address, $addressLines,
+ *   $signature. The nested array_filter callback uses its own $line argument.
+ *
+ * To inspect a custom installation, examine array_keys($contact),
+ * array_keys($formData) and array_keys($company) in a private test harness.
+ * Do not echo/debug-dump them into a customer email or a public web page.
+ *
+ * Source references in the stock 2.4.20 archive:
+ *   reporting/includes/pdf_report.inc: constructor, SetCommonData, End
+ *   reporting/includes/doctext.inc
+ *   sales/includes/db/branches_db.inc: get_branch_contacts
+ *   includes/db/crm_contacts_db.inc: get_crm_persons
+ *   admin/db/company_db.inc: get_company_prefs
+ *   sql/en_US-new.sql: crm_persons, crm_categories, sys_prefs
+ */
+return static function ($contact, $formData, $company, $user) {
+    // FrontAccounting transaction type 10 is a sales invoice.
+    // Leave other document types unchanged.
+    if (($formData['doctype'] ?? null) != 10) {
+        return array();
+    }
+
+    $name = trim(($contact['name'] ?? '') . ' ' . ($contact['name2'] ?? ''));
+
+    $reference = (string) ($formData['reference'] ?? $formData['document_number'] ?? '');
+
+    $greeting = $name !== '' ? 'Hello ' . $name . ',' : 'Hello,';
+
+    $document = $reference !== '' ? 'invoice ' . $reference : 'your invoice';
+
+    // Replace these paragraphs with your preferred wording.
+    $body = $greeting . "\n\n";
+    $body .= "Thank you for your business.\n\n";
+    $body .= 'Please find attached ' . $document . " in PDF format.\n\n";
+    $body .= "If you have any questions, please reply to this email.\n\n";
+
+    // Use this installation's own settings, with one address line per line.
+    // No organization names, addresses or contact details are embedded here.
+    $address = str_replace(array("\r\n", "\r"), "\n", $company['postal_address'] ?? '');
+
+    $addressLines = array_filter(array_map('trim', explode("\n", $address)),
+        static function ($line) { return $line !== ''; });
+
+    $signature = array_merge(
+        array($user, $company['coy_name'] ?? ''),
+        array_values($addressLines),
+        array($company['email'] ?? '', $company['phone'] ?? '')
+    );
+    
+    $signature = array_filter(array_map('trim', $signature),
+        static function ($line) { return $line !== ''; });
+
+    return array('body' => $body, 'sender' => implode("\n", $signature));
+};
diff --git a/finance/frontaccounting/files/patch-.htaccess b/finance/frontaccounting/files/patch-.htaccess
new file mode 100644
index 000000000000..abd77fed9d03
--- /dev/null
+++ b/finance/frontaccounting/files/patch-.htaccess
@@ -0,0 +1,9 @@
+--- .htaccess.orig	2026-04-28 11:25:37 UTC
++++ .htaccess
+@@ -1,5 +1,5 @@
+ # Protect sensitive files.
+-<FilesMatch "\.(inc|po|sh|.*sql|log)$">
++<FilesMatch "\.(inc|po|sh|.*sql|log|bak|orig|rej)$">
+ 	Order allow,deny
+ 	Deny from All
+ 	Satisfy All
diff --git a/finance/frontaccounting/files/patch-includes_db_inventory__db.inc b/finance/frontaccounting/files/patch-includes_db_inventory__db.inc
new file mode 100644
index 000000000000..79a4c7406db9
--- /dev/null
+++ b/finance/frontaccounting/files/patch-includes_db_inventory__db.inc
@@ -0,0 +1,20 @@
+--- includes/db/inventory_db.inc.orig	2026-04-28 11:25:37 UTC
++++ includes/db/inventory_db.inc
+@@ -232,7 +232,7 @@ function get_deliveries_from_trans($stock_id, $move_id
+         WHERE stock_id=".db_escape($stock_id)." AND qty < 0 AND
+             trans_id>='$move_id' GROUP BY stock_id";
+ 	$result = db_query($sql, "The deliveries could not be updated");
+-	$row = db_fetch_row($result);
++	$row = db_fetch_row($result) ?: array(0, 0);
+ 	
+     $sql = "SELECT IF(type=".ST_SUPPRECEIVE." OR type=".ST_SUPPCREDIT.", price, standard_cost)
+         FROM ".TB_PREF."stock_moves
+@@ -247,7 +247,7 @@ function get_deliveries_from_trans($stock_id, $move_id
+ 		WHERE stock_id=".db_escape($stock_id)." AND
+ 			trans_id<'$move_id' GROUP BY stock_id";
+ 	$result = db_query($sql, "The deliveries could not be updated");
+-	$qoh = db_fetch_row($result);
++	$qoh = db_fetch_row($result) ?: array(0);
+ 
+ 	$qty = $row[0] - $qoh[0]; //QOH prior to -ve stock is subtracted
+ 	$final_cost = $row[1] - $qoh[0]*$cost[0];
diff --git a/finance/frontaccounting/files/patch-includes_session.inc b/finance/frontaccounting/files/patch-includes_session.inc
index 74af7895836a..0515dc1a78f1 100644
--- a/finance/frontaccounting/files/patch-includes_session.inc
+++ b/finance/frontaccounting/files/patch-includes_session.inc
@@ -1,14 +1,15 @@
---- includes/session.inc.orig	2022-07-13 06:00:09 UTC
+--- includes/session.inc.orig	2026-04-28 11:25:37 UTC
 +++ includes/session.inc
-@@ -432,9 +432,8 @@ if ($SysPrefs->error_logfile != '') {
- 	to avoid unexpeced session timeouts.
- 	Make sure this directory exists and is writable!
- */
--// ini_set('session.save_path', VARLIB_PATH.'/');
--
--// ini_set('session.gc_maxlifetime', 36000); // 10hrs - moved to before session_manager
-+ini_set('session.save_path', VARLIB_PATH.'/');
-+ini_set('session.gc_maxlifetime', 36000); // 10hrs - moved to before session_manager
+@@ -428,9 +428,9 @@ if ($SysPrefs->error_logfile != '') {
+ }
  
- hook_session_start(@$_POST["company_login_name"]);
+ /*
+-	Uncomment the setting below when using FA on shared hosting
+-	to avoid unexpeced session timeouts.
+-	Make sure this directory exists and is writable!
++	Configure session settings before SessionManager::sessionStart().
++	The examples below must remain disabled here: the session is active.
++	Any custom session directory must exist and be writable.
+ */
+ // ini_set('session.save_path', VARLIB_PATH.'/');
  
diff --git a/finance/frontaccounting/files/patch-reporting_includes_fpdi_fpdi__pdf__parser.php b/finance/frontaccounting/files/patch-reporting_includes_fpdi_fpdi__pdf__parser.php
new file mode 100644
index 000000000000..e71debc43e6e
--- /dev/null
+++ b/finance/frontaccounting/files/patch-reporting_includes_fpdi_fpdi__pdf__parser.php
@@ -0,0 +1,11 @@
+--- reporting/includes/fpdi/fpdi_pdf_parser.php.orig	2026-04-28 11:25:37 UTC
++++ reporting/includes/fpdi/fpdi_pdf_parser.php
+@@ -333,6 +333,8 @@ class fpdi_pdf_parser extends pdf_parser {
+     			return false;
+     		} else {
+                 $res = $this->_getPageRotation($obj[1][1]['/Parent']);
++                if ($res === false)
++                    return false;
+                 if ($res[0] == PDF_TYPE_OBJECT)
+                     return $res[1];
+                 return $res;
diff --git a/finance/frontaccounting/files/patch-reporting_includes_fpdi_pdf__parser.php b/finance/frontaccounting/files/patch-reporting_includes_fpdi_pdf__parser.php
new file mode 100644
index 000000000000..a4b833152d84
--- /dev/null
+++ b/finance/frontaccounting/files/patch-reporting_includes_fpdi_pdf__parser.php
@@ -0,0 +1,10 @@
+--- reporting/includes/fpdi/pdf_parser.php.orig	2026-04-28 11:25:37 UTC
++++ reporting/includes/fpdi/pdf_parser.php
+@@ -48,6 +48,7 @@ class pdf_parser {
+ require_once("wrapper_functions.php");
+ 
+ class pdf_parser {
++    var $actual_obj;
+ 	
+ 	/**
+      * Filename
diff --git a/finance/frontaccounting/files/patch-reporting_includes_pdf__report.inc b/finance/frontaccounting/files/patch-reporting_includes_pdf__report.inc
new file mode 100644
index 000000000000..1040182ab493
--- /dev/null
+++ b/finance/frontaccounting/files/patch-reporting_includes_pdf__report.inc
@@ -0,0 +1,30 @@
+--- reporting/includes/pdf_report.inc.orig	2026-04-28 11:25:37 UTC
++++ reporting/includes/pdf_report.inc
+@@ -1008,6 +1008,16 @@
+     					$msg = _("Dear") . " " . $contact['name2'] . ",\n\n" 
+     						. _("Attached you will find ") . " " . $subject ."\n\n";
+ 
++						$custom_email = array();
++						$email_template = company_path() . '/reporting/email_template.php';
++						if (is_file($email_template)) {
++							$format_email = include($email_template);
++							if (is_callable($format_email))
++								$custom_email = $format_email($contact, $this->formData, $this->company, $this->user);
++							if (isset($custom_email['body']))
++								$msg = $custom_email['body'];
++						}
++
+ 						if (isset($this->formData['payment_service']))
+ 						{
+ 							$amt = number_format($this->formData['document_amount'], user_price_dec());
+@@ -1024,7 +1034,9 @@
+ 
+ 	    				$msg .= _("Kindest regards") . "\n\n";
+     					$sender = $this->user . "\n" . $coy_name . "\n" . $this->company['postal_address'] . "\n" . $this->company['email'] . "\n" . $this->company['phone'];
+-    					$mail->to(str_replace(",", "", $contact['name'].' '.$contact['name2']), $contact['email']);
++    					if (isset($custom_email['sender']))
++							$sender = $custom_email['sender'];
++						$mail->to(str_replace(",", "", $contact['name'].' '.$contact['name2']), $contact['email']);
+     					$try++;
+     					$mail->subject($subject);
+     					$mail->text($msg . $sender);
diff --git a/finance/frontaccounting/files/pkg-message.in b/finance/frontaccounting/files/pkg-message.in
index 9fc84eace640..6bb83eb7cb7f 100644
--- a/finance/frontaccounting/files/pkg-message.in
+++ b/finance/frontaccounting/files/pkg-message.in
@@ -11,8 +11,8 @@
  directory (/usr/local/etc/apache24/Includes) containing the 
  following:
 
-        Alias /frontaccounting /usr/local/www/frontaccounting24/
-        <Directory /usr/local/www/frontaccounting24/>
+        Alias /frontaccounting %%WWWDIR%%/
+        <Directory %%WWWDIR%%/>
                 Require all granted
                 DirectoryIndex index.php
         </Directory>
@@ -20,12 +20,19 @@
         Adjust the "Require all granted" statement to suit your 
         security needs in your environment.
 
- Session Data will be stored by default for this port within:
-   %%WWWDIR%%/tmp
- 
- Adjust the session settings as you require beginning on line 434 
- in:
-     %%WWWDIR%%/tmp/session.inc
+ Before upgrading an existing installation, back up its database,
+ configuration, company directories, extensions and custom report files.
+ Reconcile local changes to package-owned files before reinstalling.
+
+ Session storage follows the PHP session.save_path configuration.
+ If overriding it for this application, configure it before the session
+ starts; do not enable late ini_set calls in includes/session.inc.
+
+ An example invoice email template is installed at:
+   %%WWWDIR%%/company/examples/reporting/email_template.php
+ Copy it to company/<company-id>/reporting/email_template.php and customize
+ the copy to enable it for that company. Back up any existing template first.
+ The examples directory is not a company and is not loaded automatically.
 
 EOM
 }
diff --git a/finance/frontaccounting/pkg-plist b/finance/frontaccounting/pkg-plist
index 6b5737386d2c..1c0c44b1f6c0 100644
--- a/finance/frontaccounting/pkg-plist
+++ b/finance/frontaccounting/pkg-plist
@@ -73,6 +73,7 @@
 %%WWWDIR%%/company/0/js_cache/index.php
 %%WWWDIR%%/company/0/pdf_files/index.php
 %%WWWDIR%%/company/0/reporting/index.php
+%%WWWDIR%%/company/examples/reporting/email_template.php
 %%WWWDIR%%/company/index.php
 %%WWWDIR%%/config.default.php
 %%WWWDIR%%/dimensions/dimension_entry.php
@@ -540,10 +541,10 @@
 %%WWWDIR%%/sql/alter2.3.php
 %%WWWDIR%%/sql/alter2.3.sql
 %%WWWDIR%%/sql/alter2.3rc.sql
-%%WWWDIR%%/sql/alter%%APACHE_VERSION%%.php
-%%WWWDIR%%/sql/alter%%APACHE_VERSION%%.sql
-%%WWWDIR%%/sql/alter%%APACHE_VERSION%%rc1.php
-%%WWWDIR%%/sql/alter%%APACHE_VERSION%%rc1.sql
+%%WWWDIR%%/sql/alter2.4.php
+%%WWWDIR%%/sql/alter2.4.sql
+%%WWWDIR%%/sql/alter2.4rc1.php
+%%WWWDIR%%/sql/alter2.4rc1.sql
 %%WWWDIR%%/sql/alter2.sql
 %%WWWDIR%%/sql/en_US-demo.sql
 %%WWWDIR%%/sql/en_US-new.sql