* * 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 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; dol_syslog("EPCQR: START completesubstitutionarray für ".get_class($object), LOG_DEBUG); // 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; } // 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 = str_replace('', $newEntry."\n", $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 = '' .'' .''; $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); }