*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/**
* \file class/actions_exportzugferd.class.php
* \ingroup exportzugferd
* \brief Hook class for ZUGFeRD/XRechnung export
*/
/**
* Class ActionsExportzugferd
* Handles hooks for ZUGFeRD export functionality
*/
class ActionsExportzugferd
{
/**
* @var DoliDB Database handler
*/
private $db;
/**
* @var string[] Errors
*/
public $errors = array();
/**
* @var string Error message
*/
public $error = '';
/**
* @var string Result for hooks
*/
public $resprints = '';
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
/**
* Hook executed after PDF/ODT creation
* Creates ZUGFeRD XML file alongside the document and optionally embeds it in the PDF
*
* @param array $parameters Hook parameters
* @param object $object Object (not the invoice!)
* @param string $action Action
* @return int 0=OK, <0=Error
*/
public function afterPDFCreation($parameters, &$object, &$action)
{
global $conf, $langs;
dol_syslog("ExportZugferd Hook: afterPDFCreation called", LOG_INFO);
// Get the PDF file path from parameters
$pdfFile = isset($parameters['file']) ? $parameters['file'] : '';
if (empty($pdfFile)) {
dol_syslog("ExportZugferd Hook: No file in parameters", LOG_INFO);
return 0;
}
dol_syslog("ExportZugferd Hook: PDF file = " . $pdfFile, LOG_INFO);
// Get the invoice from parameters
$invoice = isset($parameters['object']) ? $parameters['object'] : null;
// Load Facture class if needed
if (!class_exists('Facture')) {
require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
}
// Check if it's a customer invoice
if (!is_object($invoice)) {
dol_syslog("ExportZugferd Hook: parameters[object] is not an object", LOG_INFO);
return 0;
}
dol_syslog("ExportZugferd Hook: Object class = " . get_class($invoice), LOG_INFO);
if (!($invoice instanceof Facture)) {
dol_syslog("ExportZugferd Hook: Not a customer invoice (Facture), skipping. Class: " . get_class($invoice), LOG_INFO);
return 0;
}
// Check if auto-generation is enabled
$autoGenerate = getDolGlobalInt('EXPORTZUGFERD_AUTO_GENERATE');
dol_syslog("ExportZugferd Hook: EXPORTZUGFERD_AUTO_GENERATE = " . $autoGenerate, LOG_INFO);
if (!$autoGenerate) {
dol_syslog("ExportZugferd Hook: Auto-generation disabled", LOG_INFO);
return 0;
}
dol_syslog("ExportZugferd Hook: Generating ZUGFeRD XML for invoice " . $invoice->ref, LOG_INFO);
// Generate ZUGFeRD XML and optionally embed into PDF
return $this->generateZugferdXML($invoice, $pdfFile);
}
/**
* Hook executed after ODT creation
*
* @param array $parameters Hook parameters
* @param object $object Object
* @param string $action Action
* @return int 0=OK, <0=Error
*/
public function afterODTCreation($parameters, &$object, &$action)
{
global $conf;
dol_syslog("ExportZugferd Hook: afterODTCreation called", LOG_INFO);
// Get the invoice from parameters
$invoice = isset($parameters['object']) ? $parameters['object'] : null;
// Load Facture class if needed
if (!class_exists('Facture')) {
require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
}
// Check if it's a customer invoice
if (!is_object($invoice) || !($invoice instanceof Facture)) {
dol_syslog("ExportZugferd Hook: afterODTCreation - Not a Facture object", LOG_DEBUG);
return 0;
}
// Check if auto-generation is enabled
if (!getDolGlobalInt('EXPORTZUGFERD_AUTO_GENERATE')) {
dol_syslog("ExportZugferd Hook: afterODTCreation - Auto-generation disabled", LOG_DEBUG);
return 0;
}
// Get the file path from parameters (ODT or PDF depending on MAIN_ODT_AS_PDF setting)
$file = isset($parameters['file']) ? $parameters['file'] : '';
dol_syslog("ExportZugferd Hook: afterODTCreation - file parameter = " . $file, LOG_INFO);
// Find PDF file - either the passed file is already PDF, or we need to find it in the directory
$pdfFile = '';
if (!empty($file) && pathinfo($file, PATHINFO_EXTENSION) === 'pdf') {
$pdfFile = $file;
} else {
// Search for PDF in invoice directory
$dir = $conf->facture->dir_output . '/' . dol_sanitizeFileName($invoice->ref);
if (is_dir($dir)) {
$files = glob($dir . '/*.pdf');
if (!empty($files)) {
// Get the most recently modified PDF
usort($files, function($a, $b) {
return filemtime($b) - filemtime($a);
});
$pdfFile = $files[0];
}
}
}
dol_syslog("ExportZugferd Hook: afterODTCreation - PDF file = " . $pdfFile, LOG_INFO);
// Generate ZUGFeRD XML and embed into PDF
return $this->generateZugferdXML($invoice, $pdfFile);
}
/**
* Generate ZUGFeRD XML for an invoice and optionally embed it into PDF
*
* @param Facture $invoice Invoice object
* @param string $pdfFile Optional path to PDF file for embedding
* @return int 0=OK, <0=Error
*/
private function generateZugferdXML($invoice, $pdfFile = '')
{
global $conf;
require_once __DIR__ . '/zugferdgenerator.class.php';
$generator = new ZugferdGenerator($this->db);
// Generate XML
$xml = $generator->generateFromInvoice($invoice);
if ($xml === false) {
$this->error = $generator->error;
$this->errors = $generator->errors;
dol_syslog("ExportZugferd: Error generating XML: " . $this->error, LOG_ERR);
return -1;
}
// Save XML file
$filepath = $generator->saveXML($invoice, $xml);
if ($filepath === false) {
$this->error = $generator->error;
dol_syslog("ExportZugferd: Error saving XML: " . $this->error, LOG_ERR);
return -1;
}
dol_syslog("ExportZugferd: XML generated successfully: " . $filepath, LOG_INFO);
// Embed XML into PDF if enabled and PDF file exists
$embedEnabled = getDolGlobalInt('EXPORTZUGFERD_EMBED_IN_PDF');
dol_syslog("ExportZugferd: EXPORTZUGFERD_EMBED_IN_PDF = " . $embedEnabled . ", pdfFile = " . $pdfFile, LOG_INFO);
if ($embedEnabled && !empty($pdfFile) && file_exists($pdfFile)) {
dol_syslog("ExportZugferd: Starting PDF embedding...", LOG_INFO);
$result = $this->embedXmlInPdf($pdfFile, $filepath);
if ($result < 0) {
dol_syslog("ExportZugferd: Warning - Could not embed XML in PDF: " . $this->error, LOG_WARNING);
// Don't return error, XML file was created successfully
} else {
dol_syslog("ExportZugferd: XML successfully embedded in PDF", LOG_INFO);
// Delete XML file after embedding if option is enabled
if (getDolGlobalInt('EXPORTZUGFERD_DELETE_XML_AFTER_EMBED')) {
@unlink($filepath);
dol_syslog("ExportZugferd: XML file deleted after embedding: " . $filepath, LOG_INFO);
}
}
} elseif ($embedEnabled && empty($pdfFile)) {
dol_syslog("ExportZugferd: Embedding enabled but no PDF file path provided", LOG_WARNING);
} elseif ($embedEnabled && !file_exists($pdfFile)) {
dol_syslog("ExportZugferd: Embedding enabled but PDF file does not exist: " . $pdfFile, LOG_WARNING);
}
return 0;
}
/**
* Embed XML file into PDF
*
* @param string $pdfFile Path to PDF file
* @param string $xmlFile Path to XML file
* @return int 0=OK, <0=Error
*/
private function embedXmlInPdf($pdfFile, $xmlFile)
{
require_once __DIR__ . '/pdfembedder.class.php';
$embedder = new ZugferdPdfEmbedder();
$profile = getDolGlobalString('EXPORTZUGFERD_PROFILE', 'EN16931');
if (!$embedder->embedXmlInPdf($pdfFile, $xmlFile, $profile)) {
$this->error = $embedder->error;
$this->errors = $embedder->errors;
return -1;
}
dol_syslog("ExportZugferd: XML embedded into PDF successfully: " . $pdfFile, LOG_INFO);
return 0;
}
/**
* Hook to add buttons/actions on invoice card
* Now disabled - buttons are shown in document area via formObjectOptions
*
* @param array $parameters Hook parameters
* @param object $object Invoice object
* @param string $action Action
* @return int 0=OK
*/
public function addMoreActionsButtons($parameters, &$object, &$action)
{
// Buttons are now shown in the document area (formObjectOptions) to reduce clutter
return 0;
}
/**
* Hook to add ZUGFeRD info and mini buttons in invoice info area
*
* @param array $parameters Hook parameters
* @param object $object Object
* @param string $action Action
* @return int 0=OK
*/
public function formObjectOptions($parameters, &$object, &$action)
{
global $conf, $langs, $user;
// Only for invoice card context
$contexts = explode(':', $parameters['context']);
if (!in_array('invoicecard', $contexts)) {
return 0;
}
// Check if it's a Facture
if (!($object instanceof Facture) || empty($object->ref)) {
return 0;
}
// Check permissions
$hasRight = $user->hasRight('exportzugferd', 'read') || $user->hasRight('exportzugferd', 'export') || $user->hasRight('facture', 'lire');
if (!$hasRight) {
return 0;
}
$langs->load('exportzugferd@exportzugferd');
$dir = $conf->facture->dir_output . '/' . dol_sanitizeFileName($object->ref);
$zugferdFile = $dir . '/factur-x.xml';
$xrechnungFile = $dir . '/xrechnung-' . $object->ref . '.xml';
$xmlFile = null;
$xmlExists = false;
if (file_exists($zugferdFile)) {
$xmlFile = $zugferdFile;
$xmlExists = true;
} elseif (file_exists($xrechnungFile)) {
$xmlFile = $xrechnungFile;
$xmlExists = true;
}
// Build the ZUGFeRD row with mini action buttons
print '
';
print '| ZUGFeRD/XRechnung | ';
print '';
if ($xmlExists) {
// XML exists - show file info and action buttons
print '';
print '';
print basename($xmlFile);
print '';
print ' (' . dol_print_size(filesize($xmlFile)) . ')';
// Mini buttons
print ' ';
print '';
print '';
print '';
print '';
print '';
print '';
print '';
print '';
print '';
} else {
// XML does not exist - show generate button
print '';
print '' . $langs->trans('NoZugferdXML') . '';
// Generate button
print ' ';
print '';
print ' ';
print '' . $langs->trans('GenerateZugferdXML') . '';
print '';
}
print ' | ';
print '
';
return 0;
}
/**
* Hook to add ZUGFeRD XML to email attachments (getFormMail hook)
*
* @param array $parameters Hook parameters
* @param object $object FormMail object
* @param string $action Action
* @return int 0=OK
*/
public function getFormMail($parameters, &$object, &$action)
{
global $conf;
// Check if auto-attach is enabled
if (!getDolGlobalInt('EXPORTZUGFERD_ATTACH_TO_EMAIL')) {
return 0;
}
// Check if this is for an invoice (facture trackid)
$trackid = isset($parameters['trackid']) ? $parameters['trackid'] : '';
if (strpos($trackid, 'inv') !== 0) {
return 0;
}
// Get invoice ID from parameters
$invoiceId = isset($object->param['id']) ? $object->param['id'] : 0;
if (empty($invoiceId)) {
return 0;
}
// Load invoice to get ref
require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
$invoice = new Facture($this->db);
if ($invoice->fetch($invoiceId) <= 0) {
return 0;
}
// Find ZUGFeRD XML file
$xmlFile = $this->getZugferdXmlPath($invoice);
if ($xmlFile && file_exists($xmlFile)) {
// Add XML file to fileinit array
if (!isset($object->param['fileinit'])) {
$object->param['fileinit'] = array();
}
if (!is_array($object->param['fileinit'])) {
$object->param['fileinit'] = array($object->param['fileinit']);
}
// Only add if not already in the list
if (!in_array($xmlFile, $object->param['fileinit'])) {
$object->param['fileinit'][] = $xmlFile;
dol_syslog("ExportZugferd: Added ZUGFeRD XML to email attachments: " . $xmlFile, LOG_DEBUG);
}
}
return 0;
}
/**
* Get the path to the ZUGFeRD XML file for an invoice
*
* @param Facture $invoice Invoice object
* @return string|null Path to XML file or null if not found
*/
private function getZugferdXmlPath($invoice)
{
global $conf;
if (empty($invoice->ref)) {
return null;
}
$dir = $conf->facture->dir_output . '/' . dol_sanitizeFileName($invoice->ref);
$zugferdFile = $dir . '/factur-x.xml';
$xrechnungFile = $dir . '/xrechnung-' . $invoice->ref . '.xml';
if (file_exists($zugferdFile)) {
return $zugferdFile;
} elseif (file_exists($xrechnungFile)) {
return $xrechnungFile;
}
return null;
}
}