epcqr/lib/qrcode.class.php
2026-01-29 20:37:59 +01:00

881 lines
26 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 lib/qrcode.class.php
* \ingroup epcqr
* \brief QR-Code Generator mit lokalem Caching und Styling
*/
/**
* QRCode Generator Klasse
* Generiert QR-Codes mit optionalen Styling-Optionen (Farben, Logo)
*/
class QRCodeGenerator
{
private $db;
private $cacheDir;
// Styling-Optionen
private $fgColor = array(0, 0, 0); // Vordergrundfarbe (schwarz)
private $bgColor = array(255, 255, 255); // Hintergrundfarbe (weiß)
private $bgTransparent = false; // Transparenter Hintergrund
private $logoPath = ''; // Pfad zum Logo
private $logoSize = 20; // Logo-Größe in Prozent (20% der QR-Code-Größe)
private $pixelSize = 8; // Pixel-Größe für Module
private $moduleStyle = 'square'; // Modul-Stil: square, rounded, dots, diamond
private $borderStyle = 'none'; // Rahmen-Stil: none, simple, rounded, double, dashed
private $borderWidth = 3; // Rahmenbreite in Pixeln
private $borderPadding = 8; // Abstand zwischen QR-Code und Rahmen
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
global $conf;
$this->db = $db;
// Cache-Verzeichnis für QR-Codes
$this->cacheDir = $conf->epcqr->dir_output.'/qrcodes';
// Verzeichnis erstellen falls nicht vorhanden
if (!is_dir($this->cacheDir)) {
dol_mkdir($this->cacheDir);
}
// Styling aus Konfiguration laden
$this->loadStyleFromConfig();
}
/**
* Lädt Styling-Einstellungen aus der Dolibarr-Konfiguration
*/
private function loadStyleFromConfig()
{
// Vordergrundfarbe (Hex -> RGB)
$fgHex = getDolGlobalString('EPCQR_FG_COLOR', '#000000');
$this->fgColor = $this->hexToRgb($fgHex);
// Hintergrundfarbe (Hex -> RGB) oder transparent
$bgHex = getDolGlobalString('EPCQR_BG_COLOR', '#FFFFFF');
if (strtolower($bgHex) === 'transparent' || $bgHex === '') {
$this->bgTransparent = true;
$this->bgColor = array(255, 255, 255); // Fallback für Logo-Hintergrund
} else {
$this->bgTransparent = false;
$this->bgColor = $this->hexToRgb($bgHex);
}
// Logo-Pfad
$this->logoPath = getDolGlobalString('EPCQR_LOGO_PATH', '');
// Logo-Größe (Prozent)
$this->logoSize = getDolGlobalInt('EPCQR_LOGO_SIZE', 20);
if ($this->logoSize < 5) $this->logoSize = 5;
if ($this->logoSize > 30) $this->logoSize = 30; // Max 30% wegen Fehlerkorrektur
// Modul-Stil
$this->moduleStyle = getDolGlobalString('EPCQR_MODULE_STYLE', 'square');
if (!in_array($this->moduleStyle, array('square', 'rounded', 'dots', 'diamond'))) {
$this->moduleStyle = 'square';
}
// Rahmen-Stil
$this->borderStyle = getDolGlobalString('EPCQR_BORDER_STYLE', 'none');
if (!in_array($this->borderStyle, array('none', 'simple', 'rounded', 'double', 'dashed'))) {
$this->borderStyle = 'none';
}
}
/**
* Konvertiert Hex-Farbe zu RGB-Array
*
* @param string $hex Hex-Farbcode (z.B. #FF0000)
* @return array RGB-Array [r, g, b]
*/
private function hexToRgb($hex)
{
$hex = ltrim($hex, '#');
if (strlen($hex) == 3) {
$hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
}
return array(
hexdec(substr($hex, 0, 2)),
hexdec(substr($hex, 2, 2)),
hexdec(substr($hex, 4, 2))
);
}
/**
* Generiert einen EPC-QR-Code für SEPA-Überweisungen
*
* @param string $accountHolder Kontoinhaber Name
* @param string $iban IBAN
* @param string $bic BIC (optional)
* @param float $amount Betrag
* @param string $reference Verwendungszweck
* @return string|false Pfad zur generierten QR-Code-Datei oder false bei Fehler
*/
public function generateEPCQRCode($accountHolder, $iban, $bic, $amount, $reference)
{
// Eindeutigen Dateinamen generieren basierend auf Parametern UND Styling
$styleHash = md5(implode('', $this->fgColor).implode('', $this->bgColor).($this->bgTransparent ? 'T' : 'F').$this->logoPath.$this->logoSize.$this->moduleStyle.$this->borderStyle);
$hash = md5($accountHolder.$iban.$bic.$amount.$reference.$styleHash);
$filename = 'epc_'.$hash.'.png';
$filepath = $this->cacheDir.'/'.$filename;
// Prüfen ob QR-Code bereits cached ist
if (file_exists($filepath)) {
dol_syslog("QRCodeGenerator: QR-Code aus Cache geladen: ".$filepath, LOG_DEBUG);
return $filepath;
}
// QR-Code generieren
$qrData = $this->generateEPCData($accountHolder, $iban, $bic, $amount, $reference);
$result = $this->generateQRImage($qrData, $filepath);
if ($result) {
dol_syslog("QRCodeGenerator: QR-Code generiert: ".$filepath, LOG_DEBUG);
return $filepath;
}
dol_syslog("QRCodeGenerator: Fehler beim Generieren des QR-Codes", LOG_ERR);
return false;
}
/**
* Generiert einen generischen QR-Code aus beliebigem Text
*
* @param string $data Daten für QR-Code
* @param string $prefix Präfix für Dateinamen (default: 'qr')
* @return string|false Pfad zur generierten QR-Code-Datei oder false bei Fehler
*/
public function generateQRCode($data, $prefix = 'qr')
{
// Eindeutigen Dateinamen generieren
$styleHash = md5(implode('', $this->fgColor).implode('', $this->bgColor).($this->bgTransparent ? 'T' : 'F').$this->logoPath.$this->logoSize.$this->moduleStyle.$this->borderStyle);
$hash = md5($data.$styleHash);
$filename = $prefix.'_'.$hash.'.png';
$filepath = $this->cacheDir.'/'.$filename;
// Prüfen ob QR-Code bereits cached ist
if (file_exists($filepath)) {
dol_syslog("QRCodeGenerator: QR-Code aus Cache geladen: ".$filepath, LOG_DEBUG);
return $filepath;
}
// QR-Code generieren
$result = $this->generateQRImage($data, $filepath);
if ($result) {
dol_syslog("QRCodeGenerator: QR-Code generiert: ".$filepath, LOG_DEBUG);
return $filepath;
}
dol_syslog("QRCodeGenerator: Fehler beim Generieren des QR-Codes", LOG_ERR);
return false;
}
/**
* Generiert EPC-Datenstring für SEPA-QR-Codes
*
* @param string $accountHolder Kontoinhaber Name
* @param string $iban IBAN
* @param string $bic BIC
* @param float $amount Betrag
* @param string $reference Verwendungszweck
* @return string EPC-Datenstring
*/
private function generateEPCData($accountHolder, $iban, $bic, $amount, $reference)
{
// EPC QR-Code Format (GiroCode Standard)
$epcData = array(
'BCD', // Service Tag
'002', // Version
'1', // Character set (1 = UTF-8)
'SCT', // Identification (SEPA Credit Transfer)
$bic, // BIC
$accountHolder, // Empfänger Name
$iban, // Empfänger IBAN
'EUR'.number_format($amount, 2, '.', ''), // Betrag
'', // Purpose (optional)
$reference, // Verwendungszweck
'' // Beneficiary to originator information (optional)
);
return implode("\n", $epcData);
}
/**
* Generiert QR-Code-Bild aus Daten mit Styling
*
* @param string $data Daten für QR-Code
* @param string $filepath Zielpfad für PNG-Datei
* @return bool true bei Erfolg, false bei Fehler
*/
private function generateQRImage($data, $filepath)
{
// TCPDF 2D Barcode Bibliothek laden
require_once DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf_barcodes_2d.php';
try {
// QR-Code mit hoher Fehlerkorrektur erstellen (H = 30% - notwendig für Logo)
$errorLevel = !empty($this->logoPath) && file_exists($this->logoPath) ? 'H' : 'M';
$barcodeobj = new TCPDF2DBarcode($data, 'QRCODE,'.$errorLevel);
// PNG-Daten generieren
$imageData = $barcodeobj->getBarcodePngData($this->pixelSize, $this->pixelSize, $this->fgColor);
if ($imageData === false) {
dol_syslog("QRCodeGenerator: TCPDF konnte kein PNG generieren", LOG_ERR);
return false;
}
// Bild aus String erstellen für weitere Verarbeitung
$image = imagecreatefromstring($imageData);
if ($image === false) {
// Fallback: Direkt speichern ohne Styling
$result = file_put_contents($filepath, $imageData);
return ($result !== false);
}
// Modul-Stil anwenden (rounded, dots, diamond) oder Standard-Verarbeitung
if ($this->moduleStyle !== 'square') {
$image = $this->applyModuleStyle($image);
} else {
// Standard: Hintergrund anwenden (transparent oder Farbe)
if ($this->bgTransparent) {
$image = $this->applyTransparentBackground($image);
} elseif ($this->bgColor != array(255, 255, 255)) {
$image = $this->applyBackgroundColor($image);
}
}
// Logo hinzufügen (wenn konfiguriert)
if (!empty($this->logoPath) && file_exists($this->logoPath)) {
$image = $this->addLogo($image);
}
// Rahmen hinzufügen (wenn konfiguriert)
if ($this->borderStyle !== 'none') {
$image = $this->addBorder($image);
}
// Bild speichern
$result = imagepng($image, $filepath);
imagedestroy($image);
return $result;
} catch (Exception $e) {
dol_syslog("QRCodeGenerator: Exception bei QR-Generierung: ".$e->getMessage(), LOG_ERR);
return false;
}
}
/**
* Wendet Hintergrundfarbe auf das Bild an
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild
*/
private function applyBackgroundColor($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Neues Bild mit Hintergrundfarbe erstellen
$newImage = imagecreatetruecolor($width, $height);
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
imagefill($newImage, 0, 0, $bgColorAlloc);
// Vordergrundfarbe einmal allokieren
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// QR-Code-Module (dunkle Pixel) kopieren
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$rgb = imagecolorat($image, $x, $y);
// Farbwerte extrahieren (funktioniert für indexed und truecolor)
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
// Helligkeit berechnen (0 = schwarz, 255 = weiß)
$brightness = ($r + $g + $b) / 3;
// Dunkle Pixel (QR-Code-Module) = Vordergrundfarbe setzen
if ($brightness < 128) {
imagesetpixel($newImage, $x, $y, $fgColorAlloc);
}
}
}
imagedestroy($image);
return $newImage;
}
/**
* Wendet transparenten Hintergrund auf das Bild an
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit Transparenz
*/
private function applyTransparentBackground($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Neues Bild mit Alpha-Kanal erstellen
$newImage = imagecreatetruecolor($width, $height);
// Alpha-Blending deaktivieren und Alpha speichern aktivieren
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
// Transparente Farbe erstellen und füllen
$transparent = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
imagefill($newImage, 0, 0, $transparent);
// Für das Zeichnen Alpha-Blending aktivieren
imagealphablending($newImage, true);
// Vordergrundfarbe einmal allokieren
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// QR-Code-Module (dunkle Pixel) erkennen und kopieren
// Verwendet Helligkeitswert statt exakte Farbprüfung für bessere Kompatibilität
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$rgb = imagecolorat($image, $x, $y);
// Farbwerte extrahieren (funktioniert für indexed und truecolor)
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
// Helligkeit berechnen (0 = schwarz, 255 = weiß)
$brightness = ($r + $g + $b) / 3;
// Dunkle Pixel (QR-Code-Module) = Helligkeit < 128
// Helle Pixel (Hintergrund) = transparent lassen
if ($brightness < 128) {
imagesetpixel($newImage, $x, $y, $fgColorAlloc);
}
}
}
// Alpha speichern nochmal sicherstellen
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
imagedestroy($image);
return $newImage;
}
/**
* Wendet Modul-Stil auf das QR-Code-Bild an (rounded, dots, diamond)
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit gestylten Modulen
*/
private function applyModuleStyle($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Modul-Matrix extrahieren (welche Module sind dunkel)
$moduleMatrix = $this->extractModuleMatrix($image);
$moduleCount = count($moduleMatrix);
if ($moduleCount == 0) {
return $image;
}
// Neue Bildgröße berechnen (etwas Padding hinzufügen)
$moduleSize = $this->pixelSize;
$newWidth = $moduleCount * $moduleSize;
$newHeight = $moduleCount * $moduleSize;
// Neues Bild erstellen
$newImage = imagecreatetruecolor($newWidth, $newHeight);
// Alpha für transparenten Hintergrund
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
$bgColorAlloc = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
} else {
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
imagefill($newImage, 0, 0, $bgColorAlloc);
if ($this->bgTransparent) {
imagealphablending($newImage, true);
}
// Vordergrundfarbe
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// Module zeichnen
for ($row = 0; $row < $moduleCount; $row++) {
for ($col = 0; $col < $moduleCount; $col++) {
if (!empty($moduleMatrix[$row][$col])) {
$x = $col * $moduleSize;
$y = $row * $moduleSize;
switch ($this->moduleStyle) {
case 'rounded':
$this->drawRoundedModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
case 'dots':
$this->drawDotModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
case 'diamond':
$this->drawDiamondModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
default:
imagefilledrectangle($newImage, $x, $y, $x + $moduleSize - 1, $y + $moduleSize - 1, $fgColorAlloc);
}
}
}
}
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
}
imagedestroy($image);
return $newImage;
}
/**
* Extrahiert die Modul-Matrix aus dem QR-Code-Bild
*
* @param resource $image GD-Bild-Ressource
* @return array 2D-Array mit true/false für jedes Modul
*/
private function extractModuleMatrix($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Anzahl der Module berechnen
$moduleCount = (int)($width / $this->pixelSize);
$matrix = array();
for ($row = 0; $row < $moduleCount; $row++) {
$matrix[$row] = array();
for ($col = 0; $col < $moduleCount; $col++) {
// Mitte des Moduls samplen
$x = ($col * $this->pixelSize) + (int)($this->pixelSize / 2);
$y = ($row * $this->pixelSize) + (int)($this->pixelSize / 2);
if ($x < $width && $y < $height) {
$rgb = imagecolorat($image, $x, $y);
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
$brightness = ($r + $g + $b) / 3;
$matrix[$row][$col] = ($brightness < 128);
} else {
$matrix[$row][$col] = false;
}
}
}
return $matrix;
}
/**
* Zeichnet ein abgerundetes Modul
*/
private function drawRoundedModule($image, $x, $y, $size, $color)
{
$radius = (int)($size * 0.3); // 30% Radius für Rundung
// Gefülltes Rechteck mit abgerundeten Ecken simulieren
// Hauptrechteck (ohne Ecken)
imagefilledrectangle($image, $x + $radius, $y, $x + $size - $radius - 1, $y + $size - 1, $color);
imagefilledrectangle($image, $x, $y + $radius, $x + $size - 1, $y + $size - $radius - 1, $color);
// Ecken als gefüllte Kreise
imagefilledellipse($image, $x + $radius, $y + $radius, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $size - $radius - 1, $y + $radius, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $radius, $y + $size - $radius - 1, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $size - $radius - 1, $y + $size - $radius - 1, $radius * 2, $radius * 2, $color);
}
/**
* Zeichnet ein kreisförmiges Modul (Punkt)
*/
private function drawDotModule($image, $x, $y, $size, $color)
{
$centerX = $x + (int)($size / 2);
$centerY = $y + (int)($size / 2);
$diameter = $size - 2; // Etwas kleiner für Abstand
imagefilledellipse($image, $centerX, $centerY, $diameter, $diameter, $color);
}
/**
* Zeichnet ein rautenförmiges Modul (Diamant)
*/
private function drawDiamondModule($image, $x, $y, $size, $color)
{
$centerX = $x + (int)($size / 2);
$centerY = $y + (int)($size / 2);
$half = (int)($size / 2) - 1;
$points = array(
$centerX, $y + 1, // Oben
$x + $size - 2, $centerY, // Rechts
$centerX, $y + $size - 2, // Unten
$x + 1, $centerY // Links
);
imagefilledpolygon($image, $points, $color);
}
/**
* Fügt Logo in die Mitte des QR-Codes ein
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild
*/
private function addLogo($image)
{
// Logo laden
$logoInfo = getimagesize($this->logoPath);
if ($logoInfo === false) {
dol_syslog("QRCodeGenerator: Logo konnte nicht gelesen werden: ".$this->logoPath, LOG_WARNING);
return $image;
}
$logoType = $logoInfo[2];
$logo = null;
switch ($logoType) {
case IMAGETYPE_PNG:
$logo = imagecreatefrompng($this->logoPath);
break;
case IMAGETYPE_JPEG:
$logo = imagecreatefromjpeg($this->logoPath);
break;
case IMAGETYPE_GIF:
$logo = imagecreatefromgif($this->logoPath);
break;
default:
dol_syslog("QRCodeGenerator: Nicht unterstütztes Logo-Format", LOG_WARNING);
return $image;
}
if ($logo === false) {
return $image;
}
$qrWidth = imagesx($image);
$qrHeight = imagesy($image);
$logoOrigWidth = imagesx($logo);
$logoOrigHeight = imagesy($logo);
// Logo-Größe berechnen (Prozent der QR-Code-Größe)
$logoNewWidth = (int)($qrWidth * $this->logoSize / 100);
$logoNewHeight = (int)($logoOrigHeight * ($logoNewWidth / $logoOrigWidth));
// Logo-Position (Mitte)
$logoX = (int)(($qrWidth - $logoNewWidth) / 2);
$logoY = (int)(($qrHeight - $logoNewHeight) / 2);
// Hintergrund für Logo-Bereich (mit etwas Padding)
// Nur wenn NICHT transparent - bei transparentem Hintergrund kein Rechteck zeichnen
$padding = 4;
if (!$this->bgTransparent) {
$bgColorAlloc = imagecolorallocate($image, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
imagefilledrectangle(
$image,
$logoX - $padding,
$logoY - $padding,
$logoX + $logoNewWidth + $padding,
$logoY + $logoNewHeight + $padding,
$bgColorAlloc
);
} else {
// Bei transparentem Hintergrund: Bereich mit Transparenz füllen
imagealphablending($image, false);
$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
imagefilledrectangle(
$image,
$logoX - $padding,
$logoY - $padding,
$logoX + $logoNewWidth + $padding,
$logoY + $logoNewHeight + $padding,
$transparent
);
imagealphablending($image, true);
}
// Logo skaliert einfügen
imagecopyresampled(
$image,
$logo,
$logoX,
$logoY,
0,
0,
$logoNewWidth,
$logoNewHeight,
$logoOrigWidth,
$logoOrigHeight
);
imagedestroy($logo);
return $image;
}
/**
* Fügt einen Rahmen um den QR-Code hinzu
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit Rahmen
*/
private function addBorder($image)
{
$origWidth = imagesx($image);
$origHeight = imagesy($image);
// Neue Bildgröße mit Padding und Rahmen
$totalPadding = $this->borderPadding + $this->borderWidth;
$newWidth = $origWidth + ($totalPadding * 2);
$newHeight = $origHeight + ($totalPadding * 2);
// Neues Bild erstellen
$newImage = imagecreatetruecolor($newWidth, $newHeight);
// Alpha für transparenten Hintergrund
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
$bgColorAlloc = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
} else {
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
imagefill($newImage, 0, 0, $bgColorAlloc);
if ($this->bgTransparent) {
imagealphablending($newImage, true);
}
// Rahmenfarbe (Vordergrundfarbe)
$borderColor = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// Rahmen zeichnen je nach Stil
switch ($this->borderStyle) {
case 'simple':
$this->drawSimpleBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'rounded':
$this->drawRoundedBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'double':
$this->drawDoubleBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'dashed':
$this->drawDashedBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
}
// Original-Bild in die Mitte kopieren
imagecopy($newImage, $image, $totalPadding, $totalPadding, 0, 0, $origWidth, $origHeight);
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
}
imagedestroy($image);
return $newImage;
}
/**
* Zeichnet einen einfachen Rahmen
*/
private function drawSimpleBorder($image, $width, $height, $color)
{
imagesetthickness($image, $this->borderWidth);
$offset = (int)($this->borderWidth / 2);
imagerectangle($image, $offset, $offset, $width - $offset - 1, $height - $offset - 1, $color);
}
/**
* Zeichnet einen abgerundeten Rahmen
*/
private function drawRoundedBorder($image, $width, $height, $color)
{
$radius = 15;
$thickness = $this->borderWidth;
// Dicke Linien für die geraden Seiten
imagesetthickness($image, $thickness);
$offset = (int)($thickness / 2);
// Obere Linie (ohne Ecken)
imageline($image, $radius, $offset, $width - $radius, $offset, $color);
// Untere Linie
imageline($image, $radius, $height - $offset - 1, $width - $radius, $height - $offset - 1, $color);
// Linke Linie
imageline($image, $offset, $radius, $offset, $height - $radius, $color);
// Rechte Linie
imageline($image, $width - $offset - 1, $radius, $width - $offset - 1, $height - $radius, $color);
// Abgerundete Ecken als Bögen
imagesetthickness($image, $thickness);
// Oben-links
imagearc($image, $radius, $radius, $radius * 2, $radius * 2, 180, 270, $color);
// Oben-rechts
imagearc($image, $width - $radius - 1, $radius, $radius * 2, $radius * 2, 270, 360, $color);
// Unten-links
imagearc($image, $radius, $height - $radius - 1, $radius * 2, $radius * 2, 90, 180, $color);
// Unten-rechts
imagearc($image, $width - $radius - 1, $height - $radius - 1, $radius * 2, $radius * 2, 0, 90, $color);
}
/**
* Zeichnet einen doppelten Rahmen
*/
private function drawDoubleBorder($image, $width, $height, $color)
{
$gap = 4; // Abstand zwischen den Linien
imagesetthickness($image, 2);
// Äußerer Rahmen
imagerectangle($image, 1, 1, $width - 2, $height - 2, $color);
// Innerer Rahmen
imagerectangle($image, 1 + $gap, 1 + $gap, $width - 2 - $gap, $height - 2 - $gap, $color);
}
/**
* Zeichnet einen gestrichelten Rahmen
*/
private function drawDashedBorder($image, $width, $height, $color)
{
$dashLength = 8;
$gapLength = 4;
// Transparente Farbe für Lücken
if ($this->bgTransparent) {
$gapColor = imagecolorallocatealpha($image, 0, 0, 0, 127);
} else {
$gapColor = imagecolorallocate($image, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
// Strichmuster erstellen
$style = array();
for ($i = 0; $i < $dashLength; $i++) {
$style[] = $color;
}
for ($i = 0; $i < $gapLength; $i++) {
$style[] = $gapColor;
}
imagesetstyle($image, $style);
imagesetthickness($image, $this->borderWidth);
$offset = (int)($this->borderWidth / 2);
// Rahmen mit Stil zeichnen
imageline($image, $offset, $offset, $width - $offset, $offset, IMG_COLOR_STYLED);
imageline($image, $width - $offset - 1, $offset, $width - $offset - 1, $height - $offset, IMG_COLOR_STYLED);
imageline($image, $width - $offset, $height - $offset - 1, $offset, $height - $offset - 1, IMG_COLOR_STYLED);
imageline($image, $offset, $height - $offset, $offset, $offset, IMG_COLOR_STYLED);
}
/**
* Löscht gecachte QR-Codes die älter als X Tage sind
*
* @param int $days Anzahl Tage (default: 30)
* @return int Anzahl gelöschter Dateien
*/
public function cleanCache($days = 30)
{
$deleted = 0;
$threshold = time() - ($days * 86400);
if (!is_dir($this->cacheDir)) {
return 0;
}
$files = scandir($this->cacheDir);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
$filepath = $this->cacheDir.'/'.$file;
if (is_file($filepath) && filemtime($filepath) < $threshold) {
if (unlink($filepath)) {
$deleted++;
}
}
}
dol_syslog("QRCodeGenerator: ".$deleted." alte QR-Codes gelöscht", LOG_INFO);
return $deleted;
}
/**
* Löscht alle gecachten QR-Codes (für Style-Änderungen)
*
* @return int Anzahl gelöschter Dateien
*/
public function clearCache()
{
return $this->cleanCache(0);
}
}