274 lines
8.4 KiB
PHP
274 lines
8.4 KiB
PHP
<?php
|
|
/* Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* \file core/substitutions/functions_epcqr.lib.php
|
|
* \ingroup epcqr
|
|
* \brief Substitutionsfunktionen für QR-Codes und Bilder in Dokumenten
|
|
*/
|
|
|
|
/**
|
|
* Füllt Substitutionsarray mit Bildpfaden und QR-Code-Daten
|
|
*
|
|
* @param array &$substitutionarray Array mit Substitutionswerten
|
|
* @param Translate $langs Sprachobjekt
|
|
* @param CommonObject $object Dolibarr-Objekt (Rechnung, Angebot, etc.)
|
|
* @param string $outputlangs Ausgabesprache
|
|
* @return void
|
|
*/
|
|
function epcqr_completesubstitutionarray(&$substitutionarray, $langs, $object, $outputlangs = null)
|
|
{
|
|
global $conf, $db;
|
|
|
|
// Prüfen ob Objekt gültig ist
|
|
if (!is_object($object) || empty($object->id)) {
|
|
dol_syslog("EPCQR: Object ist null oder hat keine ID - überspringe", LOG_DEBUG);
|
|
return;
|
|
}
|
|
|
|
dol_syslog("EPCQR: START completesubstitutionarray für ".get_class($object), LOG_DEBUG);
|
|
|
|
// QR-Code Pfad aus Extra-Feldern
|
|
$qrCodePath = '';
|
|
|
|
// Prüfen ob Extra-Felder vorhanden sind
|
|
if (isset($object->array_options['options_qrcodepath'])) {
|
|
$qrCodePath = $object->array_options['options_qrcodepath'];
|
|
dol_syslog("EPCQR: QR-Code Pfad gefunden: ".$qrCodePath, LOG_DEBUG);
|
|
}
|
|
|
|
// Substitution für QR-Code-Bildpfad
|
|
$substitutionarray['qrcode_path'] = $qrCodePath;
|
|
|
|
// Für ODT-Dokumente: Spezielle Marker für Bildoperationen
|
|
// Diese werden später von der ODT-Erweiterung verarbeitet
|
|
$substitutionarray['__IMAGE_qrcode__'] = $qrCodePath;
|
|
|
|
// Allgemeine Bildfunktion: Jedes Extrafeld das auf "_imagepath" endet
|
|
// wird als Bildpfad interpretiert
|
|
if (!empty($object->array_options)) {
|
|
foreach ($object->array_options as $key => $value) {
|
|
if (preg_match('/^options_(.+)_imagepath$/', $key, $matches)) {
|
|
$fieldname = $matches[1];
|
|
$substitutionarray['__IMAGE_'.$fieldname.'__'] = $value;
|
|
dol_syslog("EPCQR: Bildpfad gefunden für ".$fieldname.": ".$value, LOG_DEBUG);
|
|
}
|
|
}
|
|
}
|
|
|
|
dol_syslog("EPCQR: ENDE completesubstitutionarray", LOG_DEBUG);
|
|
}
|
|
|
|
/**
|
|
* Fügt Bilder in ODT-Dokumente ein
|
|
*
|
|
* Diese Funktion wird nach der ODT-Generierung aufgerufen und
|
|
* ersetzt Bild-Marker durch tatsächliche Bilder im ODT
|
|
*
|
|
* @param string $odfFilePath Pfad zur generierten ODT-Datei
|
|
* @param array $imageData Array mit Bildinformationen
|
|
* @return bool true bei Erfolg, false bei Fehler
|
|
*/
|
|
function epcqr_insertImagesIntoODT($odfFilePath, $imageData)
|
|
{
|
|
global $conf;
|
|
|
|
dol_syslog("EPCQR: START insertImagesIntoODT für ".$odfFilePath, LOG_DEBUG);
|
|
|
|
if (!file_exists($odfFilePath)) {
|
|
dol_syslog("EPCQR: ODT-Datei existiert nicht: ".$odfFilePath, LOG_ERR);
|
|
return false;
|
|
}
|
|
|
|
// Temporäres Verzeichnis erstellen
|
|
$tmpDir = sys_get_temp_dir() . '/odt_epcqr_' . uniqid();
|
|
if (!mkdir($tmpDir)) {
|
|
dol_syslog("EPCQR: Konnte temporäres Verzeichnis nicht erstellen", LOG_ERR);
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// 1. ODT entpacken (ODT ist ZIP)
|
|
$zip = new ZipArchive();
|
|
if ($zip->open($odfFilePath) !== true) {
|
|
dol_syslog("EPCQR: Konnte ODT nicht öffnen: ".$odfFilePath, LOG_ERR);
|
|
epcqr_deleteDir($tmpDir);
|
|
return false;
|
|
}
|
|
$zip->extractTo($tmpDir);
|
|
$zip->close();
|
|
|
|
// 2. Pictures-Verzeichnis erstellen falls nicht vorhanden
|
|
if (!is_dir($tmpDir.'/Pictures')) {
|
|
mkdir($tmpDir.'/Pictures');
|
|
}
|
|
|
|
// 3. Bilder kopieren und XML vorbereiten
|
|
$manifest = file_get_contents($tmpDir.'/META-INF/manifest.xml');
|
|
$content = file_get_contents($tmpDir.'/content.xml');
|
|
$imageReplacements = array();
|
|
|
|
foreach ($imageData as $marker => $imagePath) {
|
|
if (empty($imagePath) || !file_exists($imagePath)) {
|
|
dol_syslog("EPCQR: Bild existiert nicht: ".$imagePath." für Marker ".$marker, LOG_WARNING);
|
|
continue;
|
|
}
|
|
|
|
// Bildinfo ermitteln
|
|
$imageInfo = @getimagesize($imagePath);
|
|
if ($imageInfo === false) {
|
|
dol_syslog("EPCQR: Konnte Bildinfo nicht lesen: ".$imagePath, LOG_WARNING);
|
|
continue;
|
|
}
|
|
|
|
$extension = strtolower(pathinfo($imagePath, PATHINFO_EXTENSION));
|
|
$mimeType = $imageInfo['mime'];
|
|
$imageWidth = $imageInfo[0];
|
|
$imageHeight = $imageInfo[1];
|
|
|
|
// Eindeutigen Bildnamen generieren
|
|
$imageFilename = 'img_'.md5($marker).'.'.$extension;
|
|
$imageDest = $tmpDir.'/Pictures/'.$imageFilename;
|
|
|
|
// Bild kopieren
|
|
if (!copy($imagePath, $imageDest)) {
|
|
dol_syslog("EPCQR: Konnte Bild nicht kopieren: ".$imagePath, LOG_ERR);
|
|
continue;
|
|
}
|
|
|
|
// Manifest.xml aktualisieren
|
|
if (strpos($manifest, 'Pictures/'.$imageFilename) === false) {
|
|
$newEntry = '<manifest:file-entry manifest:media-type="'.$mimeType.'" manifest:full-path="Pictures/'.$imageFilename.'"/>';
|
|
$manifest = str_replace('</manifest:manifest>', $newEntry."\n</manifest:manifest>", $manifest);
|
|
}
|
|
|
|
// Bildgröße in cm berechnen (Annahme: 96 DPI)
|
|
$widthCm = round(($imageWidth / 96) * 2.54, 2);
|
|
$heightCm = round(($imageHeight / 96) * 2.54, 2);
|
|
|
|
// Maximale Größe begrenzen
|
|
$maxWidth = 6; // cm
|
|
$maxHeight = 6; // cm
|
|
|
|
if ($widthCm > $maxWidth || $heightCm > $maxHeight) {
|
|
$ratio = min($maxWidth / $widthCm, $maxHeight / $heightCm);
|
|
$widthCm = round($widthCm * $ratio, 2);
|
|
$heightCm = round($heightCm * $ratio, 2);
|
|
}
|
|
|
|
// ODF draw:frame XML-Element erstellen
|
|
$imageXml = '<draw:frame draw:name="'.htmlspecialchars($marker).'" '
|
|
.'text:anchor-type="as-char" '
|
|
.'svg:width="'.$widthCm.'cm" '
|
|
.'svg:height="'.$heightCm.'cm">'
|
|
.'<draw:image xlink:href="Pictures/'.$imageFilename.'" '
|
|
.'xlink:type="simple" '
|
|
.'xlink:show="embed" '
|
|
.'xlink:actuate="onLoad"/>'
|
|
.'</draw:frame>';
|
|
|
|
$imageReplacements['{'.$marker.'}'] = $imageXml;
|
|
|
|
dol_syslog("EPCQR: Bild vorbereitet: ".$marker." -> ".$imageFilename, LOG_DEBUG);
|
|
}
|
|
|
|
// 4. Marker in content.xml ersetzen
|
|
foreach ($imageReplacements as $marker => $xml) {
|
|
$content = str_replace($marker, $xml, $content);
|
|
}
|
|
|
|
// 5. Dateien zurückschreiben
|
|
file_put_contents($tmpDir.'/META-INF/manifest.xml', $manifest);
|
|
file_put_contents($tmpDir.'/content.xml', $content);
|
|
|
|
// 6. Neue ODT erstellen
|
|
$zip = new ZipArchive();
|
|
if ($zip->open($odfFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
|
dol_syslog("EPCQR: Konnte neue ODT nicht erstellen", LOG_ERR);
|
|
epcqr_deleteDir($tmpDir);
|
|
return false;
|
|
}
|
|
|
|
// mimetype MUSS zuerst und unkomprimiert
|
|
$zip->addFile($tmpDir.'/mimetype', 'mimetype');
|
|
$zip->setCompressionName('mimetype', ZipArchive::CM_STORE);
|
|
|
|
// Restliche Dateien hinzufügen
|
|
$files = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator($tmpDir, RecursiveDirectoryIterator::SKIP_DOTS),
|
|
RecursiveIteratorIterator::LEAVES_ONLY
|
|
);
|
|
|
|
foreach ($files as $file) {
|
|
if (!$file->isFile()) {
|
|
continue;
|
|
}
|
|
$filePath = $file->getRealPath();
|
|
$relativePath = substr($filePath, strlen($tmpDir) + 1);
|
|
|
|
// mimetype überspringen (bereits hinzugefügt)
|
|
if ($relativePath === 'mimetype') {
|
|
continue;
|
|
}
|
|
|
|
// Backslashes zu Forward-Slashes
|
|
$relativePath = str_replace('\\', '/', $relativePath);
|
|
|
|
$zip->addFile($filePath, $relativePath);
|
|
}
|
|
|
|
$zip->close();
|
|
|
|
// 7. Cleanup
|
|
epcqr_deleteDir($tmpDir);
|
|
|
|
dol_syslog("EPCQR: ENDE insertImagesIntoODT - Erfolgreich", LOG_DEBUG);
|
|
return true;
|
|
} catch (Exception $e) {
|
|
dol_syslog("EPCQR: Exception in insertImagesIntoODT: ".$e->getMessage(), LOG_ERR);
|
|
if (is_dir($tmpDir)) {
|
|
epcqr_deleteDir($tmpDir);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hilfsfunktion: Löscht Verzeichnis rekursiv
|
|
*
|
|
* @param string $dir Verzeichnispfad
|
|
* @return void
|
|
*/
|
|
function epcqr_deleteDir($dir)
|
|
{
|
|
if (!is_dir($dir)) {
|
|
return;
|
|
}
|
|
$items = scandir($dir);
|
|
foreach ($items as $item) {
|
|
if ($item === '.' || $item === '..') {
|
|
continue;
|
|
}
|
|
$path = $dir.'/'.$item;
|
|
if (is_dir($path)) {
|
|
epcqr_deleteDir($path);
|
|
} else {
|
|
unlink($path);
|
|
}
|
|
}
|
|
rmdir($dir);
|
|
}
|