* * 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; } }