From 3078cc6f67f27929c736714bfd51b1f81121ca17 Mon Sep 17 00:00:00 2001 From: data Date: Thu, 29 Jan 2026 20:37:59 +0100 Subject: [PATCH] =?UTF-8?q?Stabile=20Version=202.0=20QR=20Code=20lokal=20g?= =?UTF-8?q?eneriert=20und=20ins=20odt=20eingef=C3=BCgt.=20Ver=C3=A4nderung?= =?UTF-8?q?en=20wie=20Design,=20Gr=C3=B6=C3=9Fe,=20Rahmen=20usw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 715 +++++++++-------- admin/setup.php | 862 +++++++++------------ class/actions_epcqr.class.php | 66 ++ core/modules/modEpcqr.class.php | 12 +- core/substitutions/functions_epcqr.lib.php | 4 +- langs/de_DE/epcqr.lang | 96 +++ langs/en_US/epcqr.lang | 84 +- lib/epcqr.lib.php | 84 +- lib/qrcode.class.php | 715 ++++++++++++++++- sql/dolibarr_allversions.sql | 26 +- sql/llx_epcqr_extrafields.sql | 104 +++ sql/update_1.5.0.sql | 6 +- test_qrcode.php | 12 +- 13 files changed, 1828 insertions(+), 958 deletions(-) create mode 100644 langs/de_DE/epcqr.lang create mode 100644 sql/llx_epcqr_extrafields.sql diff --git a/README.md b/README.md index 8657955..8f59708 100755 --- a/README.md +++ b/README.md @@ -1,150 +1,218 @@ -# EPCQR - QR-Code Generator für Dolibarr Rechnungen +# EPC QR-Code Modul für Dolibarr -**Version:** 1.5 -**Autor:** Eduard Wisch -**Dolibarr:** 13.x - 20.x +**Version:** 2.0 +**Autor:** DATA IT-Solutions (Eduard Wisch) +**Kompatibilität:** Dolibarr 19.0+ **Lizenz:** GPL-3.0+ --- ## Übersicht -Das **EPCQR Modul** generiert automatisch **EPC-QR-Codes** (European Payments Council) für Dolibarr-Rechnungen und bietet eine **wiederverwendbare Lösung** zum Einfügen von Bildern in ODT-Dokumentvorlagen. +Das **EPC QR-Code Modul** generiert automatisch **EPC/GiroCode QR-Codes** für SEPA-Überweisungen auf Dolibarr-Rechnungen. Kunden können den QR-Code mit ihrer Banking-App scannen und die Zahlung sofort ausführen - ohne manuelle Eingabe von IBAN, Betrag oder Verwendungszweck. -### Neu in Version 1.5 +### Neu in Version 2.0 -✅ **Lokale QR-Code-Generierung** mit Caching -✅ **Generisches Bildintegration-System** für ODT-Dokumente -✅ **{qrcode} Keyword** in ODT-Templates -✅ **Substitutionssystem** für beliebige Bilder -✅ **Hook-basierte ODT-Verarbeitung** +- **Admin-Center** mit allen Einstellungen an einem Ort +- **4 Modul-Stile:** Quadrat, Abgerundet, Punkte, Diamant +- **5 Rahmen-Stile:** Kein Rahmen, Einfach, Abgerundet, Doppelt, Gestrichelt +- **Transparenter Hintergrund** möglich +- **Logo-Einbettung** mit konfigurierbarer Größe (5-30%) +- **Individuelle Farben** für Vordergrund und Hintergrund +- **Cache-Verwaltung** direkt im Admin-Center +- **Extrafeld-Sichtbarkeit** konfigurierbar +- **Verbesserte ODT-Integration** ### Was sind EPC-QR-Codes? -EPC-QR-Codes enthalten alle relevanten Zahlungsinformationen im GiroCode-Format: +EPC-QR-Codes (auch GiroCode genannt) enthalten alle relevanten Zahlungsinformationen: - Empfänger (Kontoinhaber) - IBAN -- BIC +- BIC (optional) - Betrag - Verwendungszweck (Rechnungsnummer) ---- - -## Features - -### QR-Code Generierung -✅ **Automatische Generierung** bei Rechnungsfreigabe -✅ **Lokales Caching** (keine externe Service-Abhängigkeit mehr) -✅ **EPC/GiroCode-Standard** konform -✅ **Finale Rechnungsnummer** im QR-Code (keine PROV-Nummern) - -### Bildintegration in ODT -✅ **{qrcode} Keyword** für einfache Verwendung in Templates -✅ **Generisches System** für beliebige Bilder -✅ **Automatische Bildgrößen-Anpassung** -✅ **Wiederverwendbar** für andere Module - -### Technisch -✅ **Hook-System** für ODT-Verarbeitung -✅ **Substitutionsfunktionen** für Dokumente -✅ **Extra-Felder** mit `_imagepath` Suffix automatisch erkannt -✅ **Vollständige ODT-ZIP-Verarbeitung** +Diese QR-Codes sind mit allen europäischen Banking-Apps kompatibel. --- ## Installation -### 1. Modul aktivieren +### Voraussetzungen -1. Gehe zu: **Home → Setup → Modules/Applications** -2. Suche nach "EPCQR" -3. Klicke auf **Activate** +- Dolibarr 19.0 oder höher +- PHP 7.1 oder höher +- PHP GD-Extension (für Bildgenerierung) +- TCPDF-Bibliothek (in Dolibarr enthalten) -### 2. Extra-Felder werden automatisch erstellt +### Installationsschritte -Das Modul erstellt automatisch 3 Extra-Felder für Rechnungen: +1. **Modul-Ordner kopieren:** + ```bash + cp -r epcqr /path/to/dolibarr/htdocs/custom/ + ``` -| Feldname | Typ | Beschreibung | -|----------|-----|--------------| -| `qrcode` | HTML | QR-Code als `` Tag (Kompatibilität) | -| `qrcodepfad` | Varchar(255) | QR-Code URL via viewimage.php | -| `qrcodepath` | Varchar(255) | **NEU**: Lokaler Dateipfad für ODT-Integration | +2. **Modul aktivieren:** + - Einstellungen → Module/Anwendungen + - Kategorie "Finanzen" auswählen + - "EPC QR-Code" aktivieren + +3. **Bankdaten konfigurieren:** + - Einstellungen → Module → EPC QR-Code → Einstellungen + - Bankverbindung eingeben oder Systembankkonto auswählen --- ## Konfiguration -### Bankdaten anpassen +### Admin-Center -**WICHTIG:** Du musst deine Bankdaten im Code hinterlegen! +Das Admin-Center erreichen Sie unter: +**Einstellungen → Module → EPC QR-Code → Einstellungen** -**Datei:** `/custom/epcqr/class/actions_epcqr.class.php` +Es gibt drei Tabs: +- **Einstellungen** - Alle Konfigurationsoptionen +- **Debug** - QR-Code Testseite +- **Über** - Modul-Informationen -Suche nach dieser Stelle im Trigger `BILL_VALIDATE`: +### Bankverbindung -```php -// Bankdaten - HIER ANPASSEN! -$accountHolder = 'Peter Casimir'; -$iban = 'DE70217625500013438147'; -$bic = 'GENODEF1HUM'; +| Einstellung | Beschreibung | +|-------------|--------------| +| **Datenquelle** | "Systembankkonto verwenden" oder "Manuelle Eingabe" | +| **Kontoinhaber** | Name des Kontoinhabers (bei manueller Eingabe) | +| **IBAN** | Internationale Bankkontonummer | +| **BIC** | Bank Identifier Code (optional) | + +> **Tipp:** Bei "Systembankkonto verwenden" werden die Bankdaten automatisch aus dem in Dolibarr konfigurierten Bankkonto geladen. + +### QR-Code Größe + +| Einstellung | Beschreibung | +|-------------|--------------| +| **QR-Code Größe (Verhältnis)** | Größe in ODT-Dokumenten (0.01 - 2.0) | + +**Empfohlene Werte:** +| Wert | Größe | +|------|-------| +| 0.03 - 0.05 | sehr klein | +| 0.1 | klein | +| 0.3 | mittel (Standard) | +| 0.5 | groß | + +### Styling-Optionen + +#### Farben + +| Einstellung | Beschreibung | +|-------------|--------------| +| **Vordergrundfarbe** | Farbe der QR-Code-Module (Standard: #000000 schwarz) | +| **Hintergrundfarbe** | Hintergrundfarbe oder "Transparent" aktivieren | + +> **Hinweis:** Bei transparentem Hintergrund sollte die Vordergrundfarbe ausreichend Kontrast zum Dokumenthintergrund haben. + +#### Logo + +| Einstellung | Beschreibung | +|-------------|--------------| +| **Logo (optional)** | Absoluter Pfad zum Logo-Bild (PNG, JPG, GIF) | +| **Logo-Größe** | 5% - 30% der QR-Code-Größe (Standard: 20%) | + +**Beispiel-Pfade:** +``` +/var/www/dolibarr/documents/mycompany/logos/logo.png +/srv/http/dolibarr/htdocs/custom/epcqr/img/mylogo.png ``` -**Ändere diese Werte auf deine Daten:** +> **Wichtig:** Bei Verwendung eines Logos wird automatisch die höchste Fehlerkorrektur (H = 30%) verwendet, um die Scanbarkeit zu gewährleisten. -```php -$accountHolder = 'Dein Name / Firmenname'; -$iban = 'DE12345678901234567890'; -$bic = 'ABCDEFGH123'; -``` +#### Modul-Stil -**Speichern und fertig!** +| Stil | Beschreibung | +|------|--------------| +| **Quadrat** | Standard QR-Code-Module (klassisch) | +| **Abgerundet** | Module mit abgerundeten Ecken | +| **Punkte** | Kreisförmige Module | +| **Diamant** | Rautenförmige Module | + +#### Rahmen-Stil + +| Stil | Beschreibung | +|------|--------------| +| **Kein Rahmen** | Standard - ohne Rahmen | +| **Einfach** | Schlichte Linie um den QR-Code | +| **Abgerundet** | Rahmen mit runden Ecken | +| **Doppelt** | Zwei parallele Linien | +| **Gestrichelt** | Unterbrochene Linie | + +### Cache-Verwaltung + +QR-Codes werden lokal gecached für optimale Performance. Nach Änderung der Styling-Einstellungen: + +**Cache leeren** - Klicken Sie auf den Button, um alle gecachten QR-Codes zu löschen. Neue QR-Codes werden mit den aktuellen Einstellungen generiert. + +### Extrafeld-Sichtbarkeit + +Kontrollieren Sie, welche QR-Code-Felder im Rechnungsformular angezeigt werden: + +| Einstellung | Beschreibung | +|-------------|--------------| +| **QR-Code (HTML) ausblenden** | Versteckt das Bild-Feld | +| **QR-Code URL ausblenden** | Versteckt den URL-Link | +| **QR-Code Pfad ausblenden** | Versteckt den lokalen Dateipfad | --- ## Verwendung -### Automatische QR-Code Generierung +### Automatische Generierung -Der QR-Code wird **automatisch** generiert wenn du eine Rechnung freigibst: +Der QR-Code wird **automatisch generiert**, wenn: +1. Eine neue Rechnung erstellt wird +2. Eine bestehende Rechnung validiert wird -1. Rechnung erstellen (Status: Draft) -2. **Freigeben** (Validate) -3. QR-Code wird automatisch generiert -4. Extra-Felder werden befüllt +Der Trigger läuft beim Event `BILL_VALIDATE`. -### QR-Code in ODT-Templates einfügen +### Extrafelder -**NEU in Version 1.5: Verwenden Sie das einfache `{qrcode}` Keyword!** +Das Modul erstellt drei Extrafelder auf Rechnungen: -**Schritt 1:** ODT-Template öffnen (z.B. in LibreOffice) +| Feld | Typ | Inhalt | +|------|-----|--------| +| `qrcode` | HTML | ``-Tag zur Anzeige im Formular | +| `qrcodepfad` | HTML | URL zum QR-Code-Bild (klickbar) | +| `qrcodepath` | varchar | Lokaler Dateipfad (für ODT-Integration) | -**Schritt 2:** An gewünschter Stelle einfügen: +### ODT-Integration -``` -{qrcode} -``` +Um den QR-Code in ODT-Dokumentvorlagen einzufügen: -**Schritt 3:** Template speichern und hochladen +1. **Platzhalter einfügen:** + ``` + {qrcode} + ``` -**Schritt 4:** Rechnung freigeben → QR-Code wird automatisch generiert +2. **Der QR-Code wird automatisch** beim Generieren des Dokuments eingefügt. -**Schritt 5:** Dokument als ODT generieren → QR-Code wird eingefügt +3. **Größe anpassen:** Die Größe wird über die "QR-Code Größe (Verhältnis)" Einstellung im Admin-Center gesteuert. -**Ergebnis:** Der QR-Code erscheint automatisch als Bild im ODT-Dokument! - -**Hinweis:** Das alte Format `{invoice_options_qrcode}` funktioniert weiterhin für Kompatibilität. +> **Hinweis:** Der Platzhalter muss exakt `{qrcode}` lauten. ### Beispiel Template-Position ``` ┌─────────────────────────────────┐ -│ RECHNUNG IN2601-0001 │ +│ RECHNUNG RE2501-0001 │ │ │ -│ Gesamtbetrag: 1.234,56 € │ +│ Gesamtbetrag: 1.234,56 EUR │ │ │ -│ {invoice_options_qrcode} │ ← QR-Code hier +│ ┌──────────┐ │ +│ │ {qrcode} │ │ +│ └──────────┘ │ │ │ -│ Bitte überweisen Sie... │ +│ Bitte überweisen Sie den │ +│ Betrag unter Angabe der │ +│ Rechnungsnummer. │ └─────────────────────────────────┘ ``` @@ -152,327 +220,240 @@ Der QR-Code wird **automatisch** generiert wenn du eine Rechnung freigibst: ## Technische Details -### Wie funktioniert das Modul? +### EPC/GiroCode Format -#### 1. Trigger-System +Der generierte QR-Code enthält folgende Daten im EPC-Standard: -Das Modul nutzt den Dolibarr-Trigger **BILL_VALIDATE**, der bei Rechnungsfreigabe ausgeführt wird. - -**Problem:** Zu diesem Zeitpunkt hat das `$object` noch die provisorische Nummer (z.B. `PROV23`)! - -**Lösung:** Das Modul lädt das Rechnungsobjekt neu aus der Datenbank: - -```php -$invoice = new Facture($this->db); -$invoice->fetch($object->id); -// Jetzt hat $invoice->ref die finale Nummer (z.B. IN26-0001) +``` +BCD # Service Tag +002 # Version +1 # Character set (UTF-8) +SCT # SEPA Credit Transfer +[BIC] # Bank Identifier Code +[Kontoinhaber] # Empfänger Name +[IBAN] # Empfänger IBAN +EUR[Betrag] # Währung und Betrag + # Purpose (leer) +[Rechnungsnummer] # Verwendungszweck + # Info (leer) ``` -#### 2. QR-Code Service +### Dateispeicherung -Das Modul nutzt einen externen Service: - -**URL:** `https://qr.data-it-solution.de/epc` - -**Parameter:** -- `name` = Kontoinhaber -- `iban` = IBAN -- `bic` = BIC/SWIFT -- `amount` = Betrag (formatiert mit `price2num()`) -- `remittance` = Verwendungszweck (Rechnungsnummer) - -**Beispiel-URL:** +QR-Codes werden gespeichert unter: ``` -https://qr.data-it-solution.de/epc?name=Eduard+Wisch&iban=DE70217625500013438147&bic=GENODEF1HUM&amount=1234.56&remittance=IN26-0001 +/documents/epcqr/qrcodes/epc_[hash].png ``` -#### 3. Speicherung +Der Hash basiert auf: +- Bankdaten (Kontoinhaber, IBAN, BIC) +- Rechnungsdaten (Betrag, Referenz) +- Styling-Einstellungen (Farben, Logo, Modul-Stil, Rahmen-Stil) -Der QR-Code wird in 2 Extra-Feldern gespeichert: +### Hooks -```php -// Als HTML-Tag für ODT -$invoice->array_options['options_qrcode'] = ""; +Das Modul verwendet folgende Dolibarr-Hooks: -// Als URL für Backup/Referenz -$invoice->array_options['options_qrcodepfad'] = $qrurl; +| Hook | Funktion | +|------|----------| +| `invoicecard` | QR-Code bei Rechnungsvalidierung generieren | +| `odtgeneration` | QR-Code-Bild in ODT einfügen (beforeODTSave) | +| `pdfgeneration` | Substitutionsvariablen für PDF | + +### Substitutionsvariablen + +Für ODT/PDF-Dokumente stehen folgende Substitutionen zur Verfügung: + +| Variable | Inhalt | +|----------|--------| +| `{qrcode}` | QR-Code als Bild | +| `{options_qrcodepath}` | Lokaler Dateipfad | + +### Modul-Struktur + +``` +epcqr/ +├── admin/ +│ ├── setup.php # Einstellungen +│ └── about.php # Über-Seite +├── class/ +│ └── actions_epcqr.class.php # Hook-Aktionen +├── core/ +│ ├── modules/ +│ │ └── modEpcqr.class.php # Modul-Deskriptor +│ ├── substitutions/ +│ │ └── functions_epcqr.lib.php # Substitutionen +│ └── triggers/ +│ └── interface_99_modEpcqr_EpcqrTriggers.class.php +├── langs/ +│ ├── de_DE/ +│ │ └── epcqr.lang # Deutsche Übersetzung +│ └── en_US/ +│ └── epcqr.lang # Englische Übersetzung +├── lib/ +│ ├── epcqr.lib.php # Bibliotheksfunktionen +│ └── qrcode.class.php # QR-Code Generator +├── sql/ +│ ├── llx_epcqr_extrafields.sql # Extrafelder +│ └── dolibarr_allversions.sql # Upgrade-Script +├── test_qrcode.php # Debug-Seite +└── README.md # Diese Dokumentation ``` --- -## Workflow-Diagramm +## Fehlerbehebung -``` -┌─────────────────────┐ -│ Rechnung erstellen │ -│ (Status: DRAFT) │ -└──────────┬──────────┘ - │ - ▼ -┌─────────────────────┐ -│ Freigeben (Validate)│ -└──────────┬──────────┘ - │ - ▼ -┌─────────────────────────────┐ -│ 1. Datenbank Update │ -│ → Finale Nummer vergeben │ -└──────────┬──────────────────┘ - │ - ▼ -┌─────────────────────────────┐ -│ 2. Trigger BILL_VALIDATE │ -│ → Modul wird aufgerufen │ -└──────────┬──────────────────┘ - │ - ▼ -┌─────────────────────────────┐ -│ 3. Object neu laden │ -│ → Finale Nummer holen │ -└──────────┬──────────────────┘ - │ - ▼ -┌─────────────────────────────┐ -│ 4. QR-URL generieren │ -│ → Mit finaler Nummer │ -└──────────┬──────────────────┘ - │ - ▼ -┌─────────────────────────────┐ -│ 5. Extra-Felder speichern │ -│ → options_qrcode │ -│ → options_qrcodepfad │ -└─────────────────────────────┘ -``` +### QR-Code wird nicht generiert ---- +1. **Bankdaten prüfen:** Sind Kontoinhaber und IBAN konfiguriert? +2. **Modul aktiv?** Ist das EPC QR-Code Modul aktiviert? +3. **Rechnung validiert?** QR-Codes werden erst bei Validierung generiert. +4. **Logs prüfen:** Einstellungen → Logs auf Fehlermeldungen prüfen. + - Suche nach "EPCQR" in den Logs -## Troubleshooting +### QR-Code nicht scannbar -### Problem: QR-Code zeigt PROV-Nummer +1. **Größe erhöhen:** Mindestens 2x2 cm für zuverlässiges Scannen. +2. **Kontrast prüfen:** Dunkle Vordergrundfarbe auf hellem Hintergrund. +3. **Logo verkleinern:** Maximale Logo-Größe 30% (besser 20% oder weniger). +4. **Modul-Stil:** Bei Problemen "Quadrat" (Standard) verwenden. -**Symptom:** QR-Code enthält "PROV23" statt "IN26-0001" +### ODT: QR-Code erscheint nicht -**Ursache:** Object wurde nicht neu geladen im Trigger +1. **Platzhalter korrekt?** Muss exakt `{qrcode}` sein. +2. **Pfad existiert?** Prüfen ob die Datei unter `qrcodepath` existiert. +3. **Cache leeren:** Nach Änderungen den Cache leeren. +4. **Extrafeld vorhanden?** `qrcodepath` muss als Extrafeld existieren. -**Lösung:** Prüfe in `actions_epcqr.class.php`: +### Styling-Änderungen haben keine Wirkung -```php -// FALSCH: -$ref = $object->ref; +Nach Änderung der Styling-Einstellungen: +1. **Cache leeren** im Admin-Center klicken +2. Rechnung erneut generieren (auf Draft setzen → Validieren) +3. Oder neuen QR-Code durch neue Rechnung testen -// RICHTIG: -$invoice = new Facture($this->db); -$invoice->fetch($object->id); -$ref = $invoice->ref; -``` +### Extra-Felder fehlen -### Problem: Extra-Felder sind leer - -**Symptom:** Nach Freigabe sind die Felder `qrcode` und `qrcodepfad` leer - -**Lösung:** +Falls die Extrafelder nicht automatisch erstellt wurden: 1. Modul **deaktivieren** 2. Modul wieder **aktivieren** -3. Prüfe: **Setup → Dictionaries → Extra Attributes → Invoices** -4. Sollten 2 Felder da sein: `qrcode` und `qrcodepfad` - -### Problem: QR-Code wird in ODT nicht angezeigt - -**Symptom:** Template zeigt nur `{invoice_options_qrcode}` als Text - -**Lösungen:** -1. **Feldname prüfen:** Muss exakt `{invoice_options_qrcode}` heißen -2. **Extra-Feld aktiviert?** Setup → Extra Attributes → "Printable" = Ja -3. **Template neu generieren:** Rechnung → "Generate document" - -### Problem: Falscher Betrag im QR-Code - -**Symptom:** Banking-App zeigt falschen Betrag - -**Ursache:** Formatierung falsch - -**Lösung:** Modul nutzt korrekt `price2num($invoice->total_ttc, 'MT')` - -### Problem: QR-Code kann nicht gescannt werden - -**Mögliche Ursachen:** -- QR-Code zu klein im PDF → Größe in ODT anpassen -- Externe URL nicht erreichbar → Internetverbindung prüfen -- Falsches Format → Service-URL prüfen - ---- - -## Extra-Felder Details - -### Feld: qrcode - -- **Typ:** HTML -- **Inhalt:** `` -- **Verwendung:** Für ODT-Templates -- **Printable:** Ja -- **Position:** 100 - -### Feld: qrcodepfad - -- **Typ:** varchar(255) -- **Inhalt:** Vollständige QR-Code URL -- **Verwendung:** Backup, Debugging, externe Verarbeitung -- **Printable:** Nein (nur intern) -- **Position:** 101 - ---- - -## Code-Referenz - -### Kompletter Trigger-Code - -```php -public function runTrigger($action, $object, User $user, Translate $langs, Conf $conf) -{ - if ($action == 'BILL_VALIDATE') { - - dol_syslog("EPCQR: Generiere QR-Code für Rechnung ID " . $object->id); - - // ⚠️ WICHTIG: Objekt neu laden für finale Rechnungsnummer! - require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; - $invoice = new Facture($this->db); - $result = $invoice->fetch($object->id); - - if ($result > 0) { - // Bankdaten - HIER ANPASSEN! - $accountHolder = 'Eduard Wisch'; - $iban = 'DE70217625500013438147'; - $bic = 'GENODEF1HUM'; - $amount = price2num($invoice->total_ttc, 'MT'); - $ref = $invoice->ref; // Finale Nummer - - // QR-Code URL generieren - $qrurl = "https://qr.data-it-solution.de/epc?" - . "name=" . urlencode($accountHolder) - . "&iban=" . urlencode($iban) - . "&bic=" . urlencode($bic) - . "&amount=" . urlencode($amount) - . "&remittance=" . urlencode($ref); - - // In Extra-Felder speichern - $invoice->array_options['options_qrcode'] = ""; - $invoice->insertExtraFields(); - $invoice->array_options['options_qrcodepfad'] = $qrurl; - $invoice->insertExtraFields(); - - dol_syslog("EPCQR: QR-Code generiert für " . $invoice->ref); - } else { - dol_syslog("EPCQR: Fehler beim Laden der Rechnung", LOG_ERR); - return -1; - } - - return 1; - } - - return 0; -} -``` - ---- - -## FAQ - -### Kann ich andere QR-Services nutzen? - -Ja! Ändere einfach die URL in `actions_epcqr.class.php`: - -```php -$qrurl = "https://dein-service.de/qr?" - . "deine_parameter=" . urlencode($value); -``` - -### Funktioniert das Modul mit allen Rechnungstypen? - -Ja, der Trigger `BILL_VALIDATE` wird für alle Rechnungstypen aufgerufen: -- Standard-Rechnungen -- Gutschriften -- Ersatzrechnungen -- Anzahlungsrechnungen - -### Kann ich die QR-Code-Größe ändern? - -Ja, in 2 Varianten: - -**1. Im Code:** -```php -$invoice->array_options['options_qrcode'] = ""; -``` - -**2. Im ODT-Template:** -Bild markieren → Rechtsklick → Eigenschaften → Größe ändern - -### Werden alte Rechnungen auch mit QR-Codes versehen? - -Nein, nur neue Rechnungen die nach Modulaktivierung **freigegeben** werden. - -**Für alte Rechnungen:** -1. Rechnung auf "Draft" setzen -2. Erneut freigeben -3. QR-Code wird generiert - ---- - -## Dokumentation - -📖 **Ausführliche Dokumentation:** [doc/BILDER_IN_ODT.md](doc/BILDER_IN_ODT.md) - -Die erweiterte Dokumentation enthält: -- Technische Details zur Bildverarbeitung -- Anleitung für eigene Bilder -- Fehlerbehebung und Debugging -- Code-Referenz und Architektur -- Erweiterte Verwendungsmöglichkeiten - ---- - -## Support & Kontakt - -**Entwickler:** Eduard Wisch -**E-Mail:** data@data-it-solution.de - -### Bei Problemen - -1. **Log-Dateien prüfen:** `documents/dolibarr.log` -2. **Debug aktivieren:** Setup → Other → Enable debug mode -3. **Dokumentation lesen:** [doc/BILDER_IN_ODT.md](doc/BILDER_IN_ODT.md) +3. Prüfen unter: Einstellungen → Wörterbücher → Zusatzattribute → Rechnungen --- ## Changelog -### Version 1.5 (2026-01-27) -- ✅ **Lokale QR-Code-Generierung** mit Caching-System -- ✅ **{qrcode} Keyword** für ODT-Templates -- ✅ **Generisches Bildintegration-System** für beliebige Bilder -- ✅ **Substitutionssystem** aktiviert und implementiert -- ✅ **Hook-basierte ODT-Verarbeitung** (afterODTCreation) -- ✅ **Automatische Bildgrößen-Anpassung** in ODT -- ✅ **Wiederverwendbare Lösung** für andere Module -- ✅ Neue Klassen: QRCodeGenerator, ActionsEpcqr -- ✅ Neue Funktionen: epcqr_processODTImages, epcqr_insertImagesIntoODT -- ✅ Dokumentation: [doc/BILDER_IN_ODT.md](doc/BILDER_IN_ODT.md) +### Version 2.0 (Januar 2025) -### Version 1.4 (2026-01-11) -- ✅ Erste stabile Version -- ✅ Automatische QR-Code Generierung -- ✅ Extra-Felder für ODT-Integration -- ✅ Externe QR-Service Integration -- ✅ Object-Reload Fix für finale Rechnungsnummer +**Neue Funktionen:** +- Vollständiges Admin-Center mit allen Einstellungen +- Modul-Stile: Quadrat, Abgerundet, Punkte, Diamant +- Rahmen-Stile: Kein Rahmen, Einfach, Abgerundet, Doppelt, Gestrichelt +- Transparenter Hintergrund +- Logo-Einbettung mit konfigurierbarer Größe (5-30%) +- Cache-Verwaltung im Admin-Center +- Extrafeld-Sichtbarkeit konfigurierbar +- Verbesserte ODT-Integration mit beforeODTSave Hook +- Kleinere QR-Code-Größen möglich (ab 0.01) +- Systembankkonto-Unterstützung + +**Verbesserungen:** +- Neues Icon (fa-qrcode) +- Deutsche und englische Übersetzungen vollständig +- Bessere Fehlerbehandlung und Logging +- Performance-Optimierungen durch intelligentes Caching +- SQL-Scripts für automatische Extrafeld-Erstellung + +**Technische Änderungen:** +- Komplette Überarbeitung der QRCodeGenerator-Klasse +- Neue Methoden für Modul-Stile und Rahmen +- Hash-basiertes Caching mit Styling-Berücksichtigung +- Verbesserte Transparenz-Unterstützung + +### Version 1.5 (Januar 2025) + +- Lokales QR-Code-Caching +- Extrafeld `qrcodepath` für ODT-Integration +- Grundlegende Farbkonfiguration +- {qrcode} Keyword für ODT-Templates +- Hook-basierte ODT-Verarbeitung + +### Version 1.0 (2025) + +- Initiale Version +- Automatische EPC QR-Code-Generierung +- Extra-Felder für Rechnungen +- Externe QR-Service Integration + +--- + +## API / Entwickler + +### QR-Code manuell generieren + +```php +require_once DOL_DOCUMENT_ROOT.'/custom/epcqr/lib/qrcode.class.php'; + +$qrGen = new QRCodeGenerator($db); +$filepath = $qrGen->generateEPCQRCode( + 'Max Mustermann', // Kontoinhaber + 'DE89370400440532013000', // IBAN + 'COBADEFFXXX', // BIC + 1234.56, // Betrag + 'RE2501-0001' // Verwendungszweck +); +``` + +### Cache leeren + +```php +require_once DOL_DOCUMENT_ROOT.'/custom/epcqr/lib/qrcode.class.php'; + +$qrGen = new QRCodeGenerator($db); +$deleted = $qrGen->clearCache(); +echo "Gelöscht: $deleted QR-Codes"; +``` + +### Styling programmatisch setzen + +Die Styling-Optionen werden aus der Dolibarr-Konfiguration geladen: + +```php +// In llx_const Tabelle: +// EPCQR_FG_COLOR = '#000000' +// EPCQR_BG_COLOR = '#FFFFFF' oder 'transparent' +// EPCQR_LOGO_PATH = '/path/to/logo.png' +// EPCQR_LOGO_SIZE = 20 +// EPCQR_MODULE_STYLE = 'square' | 'rounded' | 'dots' | 'diamond' +// EPCQR_BORDER_STYLE = 'none' | 'simple' | 'rounded' | 'double' | 'dashed' +``` + +--- + +## Support + +Bei Fragen oder Problemen: + +- **E-Mail:** data@data-it-solution.de +- **Website:** https://data-it-solution.de + +### Bei Problemen + +1. **Log-Dateien prüfen:** `documents/dolibarr.log` +2. **Debug-Seite nutzen:** Admin-Center → Debug Tab +3. **EPCQR in Logs suchen:** Alle Meldungen beginnen mit "EPCQR:" --- ## Lizenz -GPL-3.0+ +**GNU General Public License v3.0 oder höher** -Dieses Modul ist freie Software; Sie können es unter den Bedingungen der GNU General Public License Version 3 oder später weitergeben und/oder modifizieren. +Copyright (C) 2025 Eduard Wisch / DATA IT-Solutions ---- +Dieses Programm ist freie Software; Sie können es unter den Bedingungen der GNU General Public License, wie von der Free Software Foundation veröffentlicht, weitergeben und/oder modifizieren; entweder Version 3 der Lizenz oder (nach Ihrer Wahl) jede spätere Version. -**Viel Erfolg mit EPCQR!** 🎉 +Dieses Programm wird in der Hoffnung verteilt, dass es nützlich sein wird, aber OHNE JEDE GEWÄHRLEISTUNG; sogar ohne die implizite Gewährleistung der MARKTGÄNGIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. + +Die vollständige Lizenz finden Sie unter: https://www.gnu.org/licenses/gpl-3.0.html diff --git a/admin/setup.php b/admin/setup.php index 17188b7..5b3c349 100755 --- a/admin/setup.php +++ b/admin/setup.php @@ -20,16 +20,14 @@ /** * \file epcqr/admin/setup.php * \ingroup epcqr - * \brief Epcqr setup page. + * \brief EPCQR Modul Einstellungen */ // Load Dolibarr environment $res = 0; -// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; } -// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME $tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; @@ -44,7 +42,6 @@ if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) { if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; } -// Try main.inc.php using relative path if (!$res && file_exists("../../main.inc.php")) { $res = @include "../../main.inc.php"; } @@ -57,8 +54,8 @@ if (!$res) { // Libraries require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php"; +require_once DOL_DOCUMENT_ROOT."/core/class/html.form.class.php"; require_once '../lib/epcqr.lib.php'; -//require_once "../class/myclass.class.php"; /** * @var Conf $conf @@ -71,250 +68,135 @@ require_once '../lib/epcqr.lib.php'; // Translations $langs->loadLangs(array("admin", "epcqr@epcqr")); -// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context -/** @var HookManager $hookmanager */ -$hookmanager->initHooks(array('epcqrsetup', 'globalsetup')); +// Access control +if (!$user->admin) { + accessforbidden(); +} // Parameters $action = GETPOST('action', 'aZ09'); -$backtopage = GETPOST('backtopage', 'alpha'); -$modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions.inc.php - -$value = GETPOST('value', 'alpha'); -$label = GETPOST('label', 'alpha'); -$scandir = GETPOST('scan_dir', 'alpha'); -$type = 'myobject'; $error = 0; -$setupnotempty = 0; - -// Access control -if (!$user->admin) { - accessforbidden(); -} - - -// Set this to 1 to use the factory to manage constants. Warning, the generated module will be compatible with version v15+ only -$useFormSetup = 1; - -if (!class_exists('FormSetup')) { - require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsetup.class.php'; -} -$formSetup = new FormSetup($db); - -// Access control -if (!$user->admin) { - accessforbidden(); -} - - -// Enter here all parameters in your setup page - -// Setup conf for selection of an URL -$item = $formSetup->newItem('EPCQR_MYPARAM1'); -$item->fieldParams['isMandatory'] = 1; -$item->fieldAttr['placeholder'] = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://') . $_SERVER['HTTP_HOST']; -$item->cssClass = 'minwidth500'; - -// Setup conf for selection of a simple string input -$item = $formSetup->newItem('EPCQR_MYPARAM2'); -$item->defaultFieldValue = 'default value'; -$item->fieldAttr['placeholder'] = 'A placeholder here'; -$item->helpText = 'Tooltip text'; - -// Setup conf for selection of a simple textarea input but we replace the text of field title -$item = $formSetup->newItem('EPCQR_MYPARAM3'); -$item->nameText = $item->getNameText().' more html text '; - -// Setup conf for a selection of a Thirdparty -$item = $formSetup->newItem('EPCQR_MYPARAM4'); -$item->setAsThirdpartyType(); - -// Setup conf for a selection of a boolean -$formSetup->newItem('EPCQR_MYPARAM5')->setAsYesNo(); - -// Setup conf for a selection of an Email template of type thirdparty -$formSetup->newItem('EPCQR_MYPARAM6')->setAsEmailTemplate('thirdparty'); - -// Setup conf for a selection of a secured key -//$formSetup->newItem('EPCQR_MYPARAM7')->setAsSecureKey(); - -// Setup conf for a selection of a Product -$formSetup->newItem('EPCQR_MYPARAM8')->setAsProduct(); - -// Add a title for a new section -$formSetup->newItem('NewSection')->setAsTitle(); - -$TField = array( - 'test01' => $langs->trans('test01'), - 'test02' => $langs->trans('test02'), - 'test03' => $langs->trans('test03'), - 'test04' => $langs->trans('test04'), - 'test05' => $langs->trans('test05'), - 'test06' => $langs->trans('test06'), -); - -// Setup conf for a simple combo list -$formSetup->newItem('EPCQR_MYPARAM9')->setAsSelect($TField); - -// Setup conf for a multiselect combo list -$item = $formSetup->newItem('EPCQR_MYPARAM10'); -$item->setAsMultiSelect($TField); -$item->helpText = $langs->transnoentities('EPCQR_MYPARAM10'); - -// Setup conf for a category selection -$formSetup->newItem('EPCQR_CATEGORY_ID_XXX')->setAsCategory('product'); - -// Setup conf EPCQR_MYPARAM10 -$item = $formSetup->newItem('EPCQR_MYPARAM10'); -$item->setAsColor(); -$item->defaultFieldValue = '#FF0000'; -//$item->fieldValue = ''; -//$item->fieldAttr = array() ; // fields attribute only for compatible fields like input text -//$item->fieldOverride = false; // set this var to override field output will override $fieldInputOverride and $fieldOutputOverride too -//$item->fieldInputOverride = false; // set this var to override field input -//$item->fieldOutputOverride = false; // set this var to override field output - -$item = $formSetup->newItem('EPCQR_MYPARAM11')->setAsHtml(); -$item->nameText = $item->getNameText().' more html text '; -$item->fieldInputOverride = ''; -$item->helpText = $langs->transnoentities('HelpMessage'); -$item->cssClass = 'minwidth500'; - -$item = $formSetup->newItem('EPCQR_MYPARAM12'); -$item->fieldOverride = "Value forced, can't be modified"; -$item->cssClass = 'minwidth500'; - -//$item = $formSetup->newItem('EPCQR_MYPARAM13')->setAsDate(); // Not yet implemented - -// End of definition of parameters - - -$setupnotempty += count($formSetup->items); - - -$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); - -$moduledir = 'epcqr'; -$myTmpObjects = array(); -// TODO Scan list of objects to fill this array -$myTmpObjects['myobject'] = array('label' => 'MyObject', 'includerefgeneration' => 0, 'includedocgeneration' => 0, 'class' => 'MyObject'); - -$tmpobjectkey = GETPOST('object', 'aZ09'); -if ($tmpobjectkey && !array_key_exists($tmpobjectkey, $myTmpObjects)) { - accessforbidden('Bad value for object. Hack attempt ?'); -} - /* * Actions */ +if ($action == 'update') { + $bankDataSource = GETPOST('EPCQR_BANK_DATA_SOURCE', 'alpha'); + $accountHolder = GETPOST('EPCQR_ACCOUNT_HOLDER', 'alphanohtml'); + $iban = GETPOST('EPCQR_IBAN', 'alphanohtml'); + $bic = GETPOST('EPCQR_BIC', 'alphanohtml'); + $imageRatio = GETPOST('EPCQR_IMAGE_RATIO', 'alphanohtml'); -// For retrocompatibility Dolibarr < 15.0 -if (versioncompare(explode('.', DOL_VERSION), array(15)) < 0 && $action == 'update' && !empty($user->admin)) { - $formSetup->saveConfFromPost(); -} - -include DOL_DOCUMENT_ROOT.'/core/actions_setmoduleoptions.inc.php'; - -if ($action == 'updateMask') { - $maskconst = GETPOST('maskconst', 'aZ09'); - $maskvalue = GETPOST('maskvalue', 'alpha'); - - if ($maskconst && preg_match('/_MASK$/', $maskconst)) { - $res = dolibarr_set_const($db, $maskconst, $maskvalue, 'chaine', 0, '', $conf->entity); - if (!($res > 0)) { + // Validate IBAN format (basic check) + if ($bankDataSource == 'manual' && !empty($iban)) { + $iban = str_replace(' ', '', strtoupper($iban)); + if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$/', $iban)) { + setEventMessages($langs->trans("InvalidIBAN"), null, 'errors'); $error++; } } + // Validate BIC format (basic check) + if ($bankDataSource == 'manual' && !empty($bic)) { + $bic = str_replace(' ', '', strtoupper($bic)); + if (!preg_match('/^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/', $bic)) { + setEventMessages($langs->trans("InvalidBIC"), null, 'errors'); + $error++; + } + } + + // Validate image ratio + if (!empty($imageRatio)) { + $imageRatio = str_replace(',', '.', $imageRatio); + if (!is_numeric($imageRatio) || (float)$imageRatio <= 0 || (float)$imageRatio > 2) { + setEventMessages($langs->trans("InvalidImageRatio"), null, 'errors'); + $error++; + } + } + + // Extra field visibility settings + $hideQrcode = GETPOST('EPCQR_HIDE_QRCODE', 'int') ? 1 : 0; + $hideQrcodepfad = GETPOST('EPCQR_HIDE_QRCODEPFAD', 'int') ? 1 : 0; + $hideQrcodepath = GETPOST('EPCQR_HIDE_QRCODEPATH', 'int') ? 1 : 0; + + // QR Code styling settings + $fgColorPost = GETPOST('EPCQR_FG_COLOR', 'alphanohtml'); + $bgTransparent = GETPOST('EPCQR_BG_TRANSPARENT', 'int') ? 1 : 0; + $bgColorPost = $bgTransparent ? 'transparent' : GETPOST('EPCQR_BG_COLOR', 'alphanohtml'); + $logoPathPost = GETPOST('EPCQR_LOGO_PATH', 'alphanohtml'); + $logoSizePost = GETPOST('EPCQR_LOGO_SIZE', 'int'); + + // Validate colors (hex format) + if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $fgColorPost)) { + $fgColorPost = '#000000'; + } + if (!$bgTransparent && !preg_match('/^#[0-9A-Fa-f]{6}$/', $bgColorPost)) { + $bgColorPost = '#FFFFFF'; + } + + // Validate logo size + if ($logoSizePost < 5) $logoSizePost = 5; + if ($logoSizePost > 30) $logoSizePost = 30; + + // Module style + $moduleStylePost = GETPOST('EPCQR_MODULE_STYLE', 'alpha'); + if (!in_array($moduleStylePost, array('square', 'rounded', 'dots', 'diamond'))) { + $moduleStylePost = 'square'; + } + + // Border style + $borderStylePost = GETPOST('EPCQR_BORDER_STYLE', 'alpha'); + if (!in_array($borderStylePost, array('none', 'simple', 'rounded', 'double', 'dashed'))) { + $borderStylePost = 'none'; + } + if (!$error) { + dolibarr_set_const($db, 'EPCQR_BANK_DATA_SOURCE', $bankDataSource, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_ACCOUNT_HOLDER', $accountHolder, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_IBAN', $iban, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_BIC', $bic, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_IMAGE_RATIO', $imageRatio, 'chaine', 0, '', $conf->entity); + + // Save visibility settings + dolibarr_set_const($db, 'EPCQR_HIDE_QRCODE', $hideQrcode, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_HIDE_QRCODEPFAD', $hideQrcodepfad, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_HIDE_QRCODEPATH', $hideQrcodepath, 'chaine', 0, '', $conf->entity); + + // Save styling settings + dolibarr_set_const($db, 'EPCQR_FG_COLOR', $fgColorPost, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_BG_COLOR', $bgColorPost, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_LOGO_PATH', $logoPathPost, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_LOGO_SIZE', $logoSizePost, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_MODULE_STYLE', $moduleStylePost, 'chaine', 0, '', $conf->entity); + dolibarr_set_const($db, 'EPCQR_BORDER_STYLE', $borderStylePost, 'chaine', 0, '', $conf->entity); + + // Update extrafield visibility in database + $extrafieldsToUpdate = array( + 'qrcode' => $hideQrcode, + 'qrcodepfad' => $hideQrcodepfad, + 'qrcodepath' => $hideQrcodepath + ); + + foreach ($extrafieldsToUpdate as $fieldname => $hidden) { + // enabled = '0' means hidden, '1' means visible + $enabled = $hidden ? '0' : '1'; + $sql = "UPDATE ".MAIN_DB_PREFIX."extrafields SET enabled = '".$db->escape($enabled)."'"; + $sql .= " WHERE name = '".$db->escape($fieldname)."' AND elementtype = 'facture'"; + $db->query($sql); + } + setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); - } else { - setEventMessages($langs->trans("Error"), null, 'errors'); - } -} elseif ($action == 'specimen' && $tmpobjectkey) { - $modele = GETPOST('module', 'alpha'); - - $className = $myTmpObjects[$tmpobjectkey]['class']; - $tmpobject = new $className($db); - '@phan-var-force MyObject $tmpobject'; - $tmpobject->initAsSpecimen(); - - // Search template files - $file = ''; - $className = ''; - $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); - foreach ($dirmodels as $reldir) { - $file = dol_buildpath($reldir."core/modules/epcqr/doc/pdf_".$modele."_".strtolower($tmpobjectkey).".modules.php", 0); - if (file_exists($file)) { - $className = "pdf_".$modele."_".strtolower($tmpobjectkey); - break; - } - } - - if ($className !== '') { - require_once $file; - - $module = new $className($db); - '@phan-var-force ModelePDFMyObject $module'; - - '@phan-var-force ModelePDFMyObject $module'; - - if ($module->write_file($tmpobject, $langs) > 0) { - header("Location: ".DOL_URL_ROOT."/document.php?modulepart=epcqr-".strtolower($tmpobjectkey)."&file=SPECIMEN.pdf"); - return; - } else { - setEventMessages($module->error, null, 'errors'); - dol_syslog($module->error, LOG_ERR); - } - } else { - setEventMessages($langs->trans("ErrorModuleNotFound"), null, 'errors'); - dol_syslog($langs->trans("ErrorModuleNotFound"), LOG_ERR); - } -} elseif ($action == 'setmod') { - // TODO Check if numbering module chosen can be activated by calling method canBeActivated - if (!empty($tmpobjectkey)) { - $constforval = 'EPCQR_'.strtoupper($tmpobjectkey)."_ADDON"; - dolibarr_set_const($db, $constforval, $value, 'chaine', 0, '', $conf->entity); - } -} elseif ($action == 'set') { - // Activate a model - $ret = addDocumentModel($value, $type, $label, $scandir); -} elseif ($action == 'del') { - $ret = delDocumentModel($value, $type); - if ($ret > 0) { - if (!empty($tmpobjectkey)) { - $constforval = 'EPCQR_'.strtoupper($tmpobjectkey).'_ADDON_PDF'; - if (getDolGlobalString($constforval) == "$value") { - dolibarr_del_const($db, $constforval, $conf->entity); - } - } - } -} elseif ($action == 'setdoc') { - // Set or unset default model - if (!empty($tmpobjectkey)) { - $constforval = 'EPCQR_'.strtoupper($tmpobjectkey).'_ADDON_PDF'; - if (dolibarr_set_const($db, $constforval, $value, 'chaine', 0, '', $conf->entity)) { - // The constant that was read before the new set - // We therefore requires a variable to have a coherent view - $conf->global->{$constforval} = $value; - } - - // We disable/enable the document template (into llx_document_model table) - $ret = delDocumentModel($value, $type); - if ($ret > 0) { - $ret = addDocumentModel($value, $type, $label, $scandir); - } - } -} elseif ($action == 'unsetdoc') { - if (!empty($tmpobjectkey)) { - $constforval = 'EPCQR_'.strtoupper($tmpobjectkey).'_ADDON_PDF'; - dolibarr_del_const($db, $constforval, $conf->entity); } } -$action = 'edit'; +// Clear cache action +if ($action == 'clearcache') { + require_once __DIR__.'/../lib/qrcode.class.php'; + $qrGen = new QRCodeGenerator($db); + $deleted = $qrGen->clearCache(); + setEventMessages($langs->trans("CacheCleared", $deleted), null, 'mesgs'); +} /* @@ -323,13 +205,12 @@ $action = 'edit'; $form = new Form($db); -$help_url = ''; $title = "EpcqrSetup"; -llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-epcqr page-admin'); +llxHeader('', $langs->trans($title), '', '', 0, 0, '', '', '', 'mod-epcqr page-admin'); // Subheader -$linkback = ''.$langs->trans("BackToModuleList").''; +$linkback = ''.$langs->trans("BackToModuleList").''; print load_fiche_titre($langs->trans($title), $linkback, 'title_setup'); @@ -337,289 +218,282 @@ print load_fiche_titre($langs->trans($title), $linkback, 'title_setup'); $head = epcqrAdminPrepareHead(); print dol_get_fiche_head($head, 'settings', $langs->trans($title), -1, "epcqr@epcqr"); -// Setup page goes here -echo ''.$langs->trans("EpcqrSetupPage").'

'; +// Current values +$bankDataSource = getDolGlobalString('EPCQR_BANK_DATA_SOURCE', 'manual'); +$accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', ''); +$iban = getDolGlobalString('EPCQR_IBAN', ''); +$bic = getDolGlobalString('EPCQR_BIC', ''); +$imageRatio = getDolGlobalString('EPCQR_IMAGE_RATIO', '0.3'); +// Extra field visibility settings +$hideQrcode = getDolGlobalInt('EPCQR_HIDE_QRCODE', 0); +$hideQrcodepfad = getDolGlobalInt('EPCQR_HIDE_QRCODEPFAD', 0); +$hideQrcodepath = getDolGlobalInt('EPCQR_HIDE_QRCODEPATH', 0); -/*if ($action == 'edit') { - print $formSetup->generateOutput(true); - print '
'; - } elseif (!empty($formSetup->items)) { - print $formSetup->generateOutput(); - print '
'; - print ''.$langs->trans("Modify").''; - print '
'; - } - */ -if (!empty($formSetup->items)) { - print $formSetup->generateOutput(true); - print '
'; -} +// QR Code styling settings +$fgColor = getDolGlobalString('EPCQR_FG_COLOR', '#000000'); +$bgColor = getDolGlobalString('EPCQR_BG_COLOR', '#FFFFFF'); +$logoPath = getDolGlobalString('EPCQR_LOGO_PATH', ''); +$logoSize = getDolGlobalInt('EPCQR_LOGO_SIZE', 20); +$moduleStyle = getDolGlobalString('EPCQR_MODULE_STYLE', 'square'); +$borderStyle = getDolGlobalString('EPCQR_BORDER_STYLE', 'none'); +print '
'; +print ''; +print ''; -foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) { - if (!empty($myTmpObjectArray['includerefgeneration'])) { - // Numbering models +// Bank Data Section +print ''; +print ''; +print ''; +print ''; - $setupnotempty++; +// Bank Data Source Selection +print ''; +print ''; +print ''; +print ''; - print load_fiche_titre($langs->trans("NumberingModules", $myTmpObjectArray['label']), '', ''); +// Manual Bank Data Fields +print ''; +print ''; +print ''; +print ''; - print '
'.$langs->trans("BankDataSettings").'
'.$langs->trans("BankDataSource").''; +print ''; +print '
'.$langs->trans("AccountHolder").''; +print ''; +print '
'; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''."\n"; +print ''; +print ''; +print ''; +print ''; - clearstatcache(); +print ''; +print ''; +print ''; +print ''; - foreach ($dirmodels as $reldir) { - $dir = dol_buildpath($reldir."core/modules/".$moduledir); +print '
'.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Example").''.$langs->trans("Status").''.$langs->trans("ShortInfo").'
'.$langs->trans("IBAN").''; +print ''; +print '
'.$langs->trans("BIC").''; +print ''; +print '
'; - if (is_dir($dir)) { - $handle = opendir($dir); - if (is_resource($handle)) { - while (($file = readdir($handle)) !== false) { - if (strpos($file, 'mod_'.strtolower($myTmpObjectKey).'_') === 0 && substr($file, dol_strlen($file) - 3, 3) == 'php') { - $file = substr($file, 0, dol_strlen($file) - 4); +// QR Code Settings Section +print '
'; +print ''; +print ''; +print ''; +print ''; - require_once $dir.'/'.$file.'.php'; +// Image Ratio +print ''; +print ''; +print ''; +print ''; - $module = new $file($db); - '@phan-var-force ModeleNumRefMyObject $module'; +// Foreground Color +print ''; +print ''; +print ''; +print ''; - // Show modules according to features level - if ($module->version == 'development' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2) { - continue; - } - if ($module->version == 'experimental' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1) { - continue; - } +// Background Color +print ''; +print ''; +print ''; +print ''; - if ($module->isEnabled()) { - dol_include_once('/'.$moduledir.'/class/'.strtolower($myTmpObjectKey).'.class.php'); - - print ''; - - // Show example of numbering model - print ''."\n"; - - print ''; - - $className = $myTmpObjectArray['class']; - $mytmpinstance = new $className($db); - '@phan-var-force MyObject $mytmpinstance'; - $mytmpinstance->initAsSpecimen(); - - // Info - $htmltooltip = ''; - $htmltooltip .= ''.$langs->trans("Version").': '.$module->getVersion().'
'; - - $nextval = $module->getNextValue($mytmpinstance); - if ("$nextval" != $langs->trans("NotAvailable")) { // Keep " on nextval - $htmltooltip .= ''.$langs->trans("NextValue").': '; - if ($nextval) { - if (preg_match('/^Error/', $nextval) || $nextval == 'NotConfigured') { - $nextval = $langs->trans($nextval); - } - $htmltooltip .= $nextval.'
'; - } else { - $htmltooltip .= $langs->trans($module->error).'
'; - } - } - - print ''; - - print "\n"; - } - } - } - closedir($handle); - } - } - } - print "
'.$langs->trans("QRCodeSettings").'
'.$langs->trans("QRCodeSize").''; +print ''; +print ' '.$langs->trans("QRCodeSizeHelp").''; +print '
'.$langs->trans("QRCodeFgColor").''; +print ''; +print ' '; +print ' '.$langs->trans("QRCodeFgColorHelp").''; +print '
'.$langs->trans("QRCodeBgColor").''; +$isTransparent = (strtolower($bgColor) === 'transparent' || $bgColor === ''); +print ''; +print ' '; +print '   '; +print ''; +print ' '; +print ' '.$langs->trans("QRCodeBgColorHelp").''; +print '
'.$module->getName($langs)."\n"; - print $module->info($langs); - print ''; - $tmp = $module->getExample(); - if (preg_match('/^Error/', $tmp)) { - $langs->load("errors"); - print '
'.$langs->trans($tmp).'
'; - } elseif ($tmp == 'NotConfigured') { - print $langs->trans($tmp); - } else { - print $tmp; - } - print '
'; - $constforvar = 'EPCQR_'.strtoupper($myTmpObjectKey).'_ADDON'; - $defaultifnotset = 'thevaluetousebydefault'; - $activenumberingmodel = getDolGlobalString($constforvar, $defaultifnotset); - if ($activenumberingmodel == $file) { - print img_picto($langs->trans("Activated"), 'switch_on'); - } else { - print ''; - print img_picto($langs->trans("Disabled"), 'switch_off'); - print ''; - } - print ''; - print $form->textwithpicto('', $htmltooltip, 1, 'info'); - print '

\n"; +print ''; - if (!empty($myTmpObjectArray['includedocgeneration'])) { - /* - * Document templates generators - */ - $setupnotempty++; - $type = strtolower($myTmpObjectKey); +// Logo Path +print ''; +print ''.$langs->trans("QRCodeLogo").''; +print ''; +print ''; +print '
'.$langs->trans("QRCodeLogoHelp").''; +if (!empty($logoPath)) { + if (file_exists($logoPath)) { + print '
✓ '.$langs->trans("FileExists").''; + } else { + print '
✗ '.$langs->trans("FileNotFound").''; + } +} +print ''; +print ''; - print load_fiche_titre($langs->trans("DocumentModules", $myTmpObjectKey), '', ''); +// Logo Size +print ''; +print ''.$langs->trans("QRCodeLogoSize").''; +print ''; +print ''; +print ' % '.$langs->trans("QRCodeLogoSizeHelp").''; +print ''; +print ''; - // Load array def with activated templates - $def = array(); - $sql = "SELECT nom"; - $sql .= " FROM ".$db->prefix()."document_model"; - $sql .= " WHERE type = '".$db->escape($type)."'"; - $sql .= " AND entity = ".$conf->entity; - $resql = $db->query($sql); - if ($resql) { - $i = 0; - $num_rows = $db->num_rows($resql); - while ($i < $num_rows) { - $array = $db->fetch_array($resql); - array_push($def, $array[0]); - $i++; - } +// Module Style +print ''; +print ''.$langs->trans("QRCodeModuleStyle").''; +print ''; +print ''; +print ' '.$langs->trans("QRCodeModuleStyleHelp").''; +print ''; +print ''; + +// Border Style +print ''; +print ''.$langs->trans("QRCodeBorderStyle").''; +print ''; +print ''; +print ' '.$langs->trans("QRCodeBorderStyleHelp").''; +print ''; +print ''; + +print ''; + +// Clear Cache Button +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print '
'.$langs->trans("QRCodeCache").'
'.$langs->trans("ClearQRCodeCache").''; +print ''.$langs->trans("ClearCache").''; +print ' '.$langs->trans("ClearCacheHelp").''; +print '
'; + +// Extra Field Visibility Section +print '
'; +print ''; +print ''; +print ''; +print ''; + +// Hide QR Code field (HTML with image) +print ''; +print ''; +print ''; +print ''; + +// Hide QR Code URL field +print ''; +print ''; +print ''; +print ''; + +// Hide QR Code Path field (local file path) +print ''; +print ''; +print ''; +print ''; + +print '
'.$langs->trans("ExtraFieldVisibility").'
'.$langs->trans("HideQRCodeField").''; +print ''; +print ' '.$langs->trans("HideQRCodeFieldHelp").''; +print '
'.$langs->trans("HideQRCodePfadField").''; +print ''; +print ' '.$langs->trans("HideQRCodePfadFieldHelp").''; +print '
'.$langs->trans("HideQRCodePathField").''; +print ''; +print ' '.$langs->trans("HideQRCodePathFieldHelp").''; +print '
'; + +// Submit button +print '
'; +print '
'; +print ''; +print '
'; + +print '
'; + +// JavaScript to show/hide manual fields based on selection +print ''; + +// Info box about system bank account +if ($bankDataSource == 'system') { + print '
'; + print '
'; + print $langs->trans("SystemBankAccountInfo"); + + // Try to show configured bank accounts + require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php'; + $bankAccount = new Account($db); + $sql = "SELECT rowid, label, iban_prefix, bic FROM ".MAIN_DB_PREFIX."bank_account WHERE entity = ".((int) $conf->entity)." AND clos = 0"; + $resql = $db->query($sql); + if ($resql && $db->num_rows($resql) > 0) { + print '

'.$langs->trans("AvailableBankAccounts").':'; + } else { + print '

'.$langs->trans("NoBankAccountConfigured").''; + } + + print '
'; } // Page end diff --git a/class/actions_epcqr.class.php b/class/actions_epcqr.class.php index f96f627..5cd0cf7 100644 --- a/class/actions_epcqr.class.php +++ b/class/actions_epcqr.class.php @@ -101,6 +101,72 @@ class ActionsEpcqr return 0; } + /** + * Hook vor ODT-Speicherung + * Fügt QR-Code-Bild in ODT ein bevor es gespeichert wird + * + * @param array $parameters Hook-Parameter (enthält odfHandler) + * @param object $object Dolibarr-Objekt + * @param string $action Aktuelle Aktion + * @return int 0 = OK, <0 = Fehler + */ + public function beforeODTSave($parameters, &$object, &$action) + { + global $conf; + + dol_syslog("EPCQR Hook: beforeODTSave aufgerufen", LOG_DEBUG); + + // Prüfen ob odfHandler vorhanden ist + if (empty($parameters['odfHandler'])) { + dol_syslog("EPCQR Hook: Kein odfHandler in Parametern", LOG_DEBUG); + return 0; + } + + $odfHandler = $parameters['odfHandler']; + + // Das Rechnungsobjekt ist in $parameters['object'], nicht in $object! + $invoice = isset($parameters['object']) ? $parameters['object'] : null; + + // Prüfen ob Objekt gültig ist + if (!is_object($invoice) || empty($invoice->id)) { + dol_syslog("EPCQR Hook: Ungültiges Rechnungsobjekt in parameters", LOG_DEBUG); + return 0; + } + + dol_syslog("EPCQR Hook: Verarbeite Rechnung ".$invoice->ref, LOG_DEBUG); + + // Extrafelder laden falls nicht vorhanden + if (empty($invoice->array_options)) { + $invoice->fetch_optionals(); + } + + // QR-Code-Pfad aus Extrafeld holen + $qrcodepath = ''; + if (isset($invoice->array_options['options_qrcodepath'])) { + $qrcodepath = $invoice->array_options['options_qrcodepath']; + } + + dol_syslog("EPCQR Hook: QR-Code-Pfad aus Extrafeld: ".$qrcodepath, LOG_DEBUG); + + if (empty($qrcodepath) || !file_exists($qrcodepath)) { + dol_syslog("EPCQR Hook: Kein QR-Code-Pfad oder Datei nicht gefunden: ".$qrcodepath, LOG_WARNING); + return 0; + } + + // QR-Code als Bild in ODT einfügen + try { + // setImage erwartet: Variablenname (ohne Klammern), Bildpfad, Größenverhältnis + // Ratio 0.3 = ca. 2-3cm bei typischen QR-Code-Größen + $ratio = getDolGlobalString('EPCQR_IMAGE_RATIO', '0.3'); + $odfHandler->setImage('qrcode', $qrcodepath, (float) $ratio); + dol_syslog("EPCQR Hook: QR-Code-Bild erfolgreich eingefügt: ".$qrcodepath." (ratio: ".$ratio.")", LOG_INFO); + } catch (Exception $e) { + dol_syslog("EPCQR Hook: Fehler beim Einfügen des QR-Codes: ".$e->getMessage(), LOG_ERR); + } + + return 0; + } + /** * Hook für PDF-Generierung (falls später benötigt) * diff --git a/core/modules/modEpcqr.class.php b/core/modules/modEpcqr.class.php index 643a8c2..7c7541d 100755 --- a/core/modules/modEpcqr.class.php +++ b/core/modules/modEpcqr.class.php @@ -66,17 +66,17 @@ class modEpcqr extends DolibarrModules // DESCRIPTION_FLAG // Module description, used if translation string 'ModuleEpcqrDesc' not found (Epcqr is name of module). - $this->description = 'QRCode wird generiert und als Bild sowie URL in den Extra Feldern qrcode qrcodepfad hinterlegt'; + $this->description = 'Generiert EPC QR-Codes für SEPA-Überweisungen auf Rechnungen'; // Used only if file README.md and README-LL.md not found. - $this->descriptionlong = "EpcqrDescription"; + $this->descriptionlong = "Generiert automatisch EPC QR-Codes (GiroCode) für Rechnungen. Unterstützt individuelle Farben, Logos, verschiedene Modul-Stile und dekorative Rahmen. Die QR-Codes werden als Extrafelder gespeichert und können in ODT-Vorlagen eingefügt werden."; // Author - $this->editor_name = 'Alles Watt läuft'; - $this->editor_url = ''; // Must be an external online web site + $this->editor_name = 'DATA IT-Solutions'; + $this->editor_url = 'https://data-it-solution.de'; // Must be an external online web site $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@epcqr' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '1.5'; + $this->version = '2.0'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; @@ -87,7 +87,7 @@ class modEpcqr extends DolibarrModules // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue' // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module' // To use a supported fa-xxx css style of font awesome, use this->picto='xxx' - $this->picto = 'fa-file'; + $this->picto = 'fa-qrcode'; // Define some features supported by module (triggers, login, substitutions, menus, css, etc...) $this->module_parts = array( diff --git a/core/substitutions/functions_epcqr.lib.php b/core/substitutions/functions_epcqr.lib.php index f9adf73..a5ccbcb 100644 --- a/core/substitutions/functions_epcqr.lib.php +++ b/core/substitutions/functions_epcqr.lib.php @@ -34,14 +34,14 @@ function epcqr_completesubstitutionarray(&$substitutionarray, $langs, $object, $ { 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; } + dol_syslog("EPCQR: START completesubstitutionarray für ".get_class($object), LOG_DEBUG); + // QR-Code Pfad aus Extra-Feldern $qrCodePath = ''; diff --git a/langs/de_DE/epcqr.lang b/langs/de_DE/epcqr.lang new file mode 100644 index 0000000..d7c1ed3 --- /dev/null +++ b/langs/de_DE/epcqr.lang @@ -0,0 +1,96 @@ +# Translation file - German + +# +# Generic +# + +# Module label 'ModuleEpcqrName' +ModuleEpcqrName = EPC QR-Code +# Module description 'ModuleEpcqrDesc' +ModuleEpcqrDesc = EPC QR-Codes für Rechnungen generieren (SEPA-Zahlung) + +# +# Admin page +# +EpcqrSetup = EPC QR-Code Einstellungen +Settings = Einstellungen +Debug = Debug +EpcqrSetupPage = EPC QR-Code Modul Einstellungen + +# Bank Data Settings +BankDataSettings = Bankverbindung +BankDataSource = Datenquelle +UseSystemBankAccount = Systembankkonto verwenden +ManualEntry = Manuelle Eingabe +AccountHolder = Kontoinhaber +AccountHolderPlaceholder = Firmenname +IBAN = IBAN +BIC = BIC/SWIFT +InvalidIBAN = Ungültiges IBAN-Format +InvalidBIC = Ungültiges BIC/SWIFT-Format +SystemBankAccountInfo = Die Bankdaten werden aus der System-Bankkonto-Konfiguration übernommen. +AvailableBankAccounts = Verfügbare Bankkonten +NoBankAccountConfigured = Kein Bankkonto im System konfiguriert. + +# QR Code Settings +QRCodeSettings = QR-Code Einstellungen +QRCodeSize = QR-Code Größe (Verhältnis) +QRCodeSizeHelp = Größenverhältnis für QR-Code in ODT-Dokumenten (0.05 = sehr klein, 0.1 = klein, 0.3 = mittel, 0.5 = groß) +InvalidImageRatio = Ungültiges Größenverhältnis. Muss eine Zahl zwischen 0.01 und 2 sein. + +# QR Code Styling +QRCodeFgColor = Vordergrundfarbe +QRCodeFgColorHelp = Farbe der QR-Code-Module (Standard: schwarz) +QRCodeBgColor = Hintergrundfarbe +QRCodeBgColorHelp = Hintergrundfarbe des QR-Codes (Standard: weiß) +Transparent = Transparent +QRCodeLogo = Logo (optional) +QRCodeLogoHelp = Absoluter Pfad zu einem Logo-Bild (PNG, JPG, GIF). Das Logo wird in der Mitte des QR-Codes platziert. +QRCodeLogoSize = Logo-Größe +QRCodeLogoSizeHelp = Größe des Logos in Prozent der QR-Code-Größe (5-30%, Standard: 20%) +FileExists = Datei gefunden +FileNotFound = Datei nicht gefunden +QRCodeModuleStyle = Modul-Stil +QRCodeModuleStyleHelp = Form der QR-Code-Module +StyleSquare = Quadrat (Standard) +StyleRounded = Abgerundet +StyleDots = Punkte +StyleDiamond = Diamant + +# QR Code Border +QRCodeBorderStyle = Rahmen-Stil +QRCodeBorderStyleHelp = Rahmen um den QR-Code +BorderNone = Kein Rahmen +BorderSimple = Einfach +BorderRounded = Abgerundet +BorderDouble = Doppelt +BorderDashed = Gestrichelt + +# QR Code Cache +QRCodeCache = QR-Code Cache +ClearQRCodeCache = Cache leeren +ClearCache = Cache leeren +ClearCacheHelp = Löscht alle gecachten QR-Codes. Notwendig nach Style-Änderungen. +CacheCleared = %s QR-Codes aus dem Cache gelöscht. + +# Extra Field Visibility +ExtraFieldVisibility = Extrafeld-Sichtbarkeit (Rechnung) +HideQRCodeField = QR-Code (HTML) ausblenden +HideQRCodeFieldHelp = Blendet das QR-Code-Bildfeld im Rechnungsformular aus +HideQRCodePfadField = QR-Code URL ausblenden +HideQRCodePfadFieldHelp = Blendet das QR-Code-URL-Feld im Rechnungsformular aus +HideQRCodePathField = QR-Code Pfad ausblenden +HideQRCodePathFieldHelp = Blendet das lokale Dateipfad-Feld im Rechnungsformular aus + +# +# About page +# +About = Über +EpcqrAbout = Über EPC QR-Code +EpcqrAboutPage = EPC QR-Code Modul Informationen + +# +# Sample page +# +EpcqrArea = EPC QR-Code +MyPageName = Meine Seite diff --git a/langs/en_US/epcqr.lang b/langs/en_US/epcqr.lang index 6aa6283..e6345be 100755 --- a/langs/en_US/epcqr.lang +++ b/langs/en_US/epcqr.lang @@ -5,34 +5,94 @@ # # Module label 'ModuleEpcqrName' -ModuleEpcqrName = Epcqr +ModuleEpcqrName = EPC QR Code # Module description 'ModuleEpcqrDesc' -ModuleEpcqrDesc = Epcqr description +ModuleEpcqrDesc = Generate EPC QR codes for invoices (SEPA payment) # # Admin page # -EpcqrSetup = Epcqr setup +EpcqrSetup = EPC QR Code Setup Settings = Settings -EpcqrSetupPage = Epcqr setup page -NewSection=New section -EPCQR_MYPARAM1 = My param 1 -EPCQR_MYPARAM1Tooltip = My param 1 tooltip -EPCQR_MYPARAM2=My param 2 -EPCQR_MYPARAM2Tooltip=My param 2 tooltip +Debug = Debug +EpcqrSetupPage = EPC QR Code module settings +# Bank Data Settings +BankDataSettings = Bank Account Settings +BankDataSource = Bank Data Source +UseSystemBankAccount = Use System Bank Account +ManualEntry = Manual Entry +AccountHolder = Account Holder +AccountHolderPlaceholder = Company Name +IBAN = IBAN +BIC = BIC/SWIFT +InvalidIBAN = Invalid IBAN format +InvalidBIC = Invalid BIC/SWIFT format +SystemBankAccountInfo = Bank data will be retrieved from the system bank account configuration. +AvailableBankAccounts = Available Bank Accounts +NoBankAccountConfigured = No bank account configured in the system. + +# QR Code Settings +QRCodeSettings = QR Code Settings +QRCodeSize = QR Code Size (Ratio) +QRCodeSizeHelp = Size ratio for QR code in ODT documents (0.05 = very small, 0.1 = small, 0.3 = medium, 0.5 = large) +InvalidImageRatio = Invalid size ratio. Must be a number between 0.01 and 2. + +# QR Code Styling +QRCodeFgColor = Foreground Color +QRCodeFgColorHelp = Color of QR code modules (default: black) +QRCodeBgColor = Background Color +QRCodeBgColorHelp = Background color of QR code (default: white) +Transparent = Transparent +QRCodeLogo = Logo (optional) +QRCodeLogoHelp = Absolute path to a logo image (PNG, JPG, GIF). The logo will be placed in the center of the QR code. +QRCodeLogoSize = Logo Size +QRCodeLogoSizeHelp = Size of the logo as percentage of QR code size (5-30%, default: 20%) +FileExists = File found +FileNotFound = File not found +QRCodeModuleStyle = Module Style +QRCodeModuleStyleHelp = Shape of QR code modules +StyleSquare = Square (Default) +StyleRounded = Rounded +StyleDots = Dots +StyleDiamond = Diamond + +# QR Code Border +QRCodeBorderStyle = Border Style +QRCodeBorderStyleHelp = Border around the QR code +BorderNone = No Border +BorderSimple = Simple +BorderRounded = Rounded +BorderDouble = Double +BorderDashed = Dashed + +# QR Code Cache +QRCodeCache = QR Code Cache +ClearQRCodeCache = Clear Cache +ClearCache = Clear Cache +ClearCacheHelp = Deletes all cached QR codes. Required after style changes. +CacheCleared = %s QR codes deleted from cache. + +# Extra Field Visibility +ExtraFieldVisibility = Extra Field Visibility (Invoice) +HideQRCodeField = Hide QR Code (HTML) +HideQRCodeFieldHelp = Hides the QR code image field on invoice form +HideQRCodePfadField = Hide QR Code URL +HideQRCodePfadFieldHelp = Hides the QR code URL field on invoice form +HideQRCodePathField = Hide QR Code Path +HideQRCodePathFieldHelp = Hides the local file path field on invoice form # # About page # About = About -EpcqrAbout = About Epcqr -EpcqrAboutPage = Epcqr about page +EpcqrAbout = About EPC QR Code +EpcqrAboutPage = EPC QR Code module information # # Sample page # -EpcqrArea = Home Epcqr +EpcqrArea = EPC QR Code MyPageName = My page name # diff --git a/lib/epcqr.lib.php b/lib/epcqr.lib.php index 7249620..2ab06a9 100755 --- a/lib/epcqr.lib.php +++ b/lib/epcqr.lib.php @@ -30,10 +30,6 @@ function epcqrAdminPrepareHead() { global $langs, $conf; - // global $db; - // $extrafields = new ExtraFields($db); - // $extrafields->fetch_name_optionals_label('myobject'); - $langs->load("epcqr@epcqr"); $h = 0; @@ -44,41 +40,17 @@ function epcqrAdminPrepareHead() $head[$h][2] = 'settings'; $h++; - /* - $head[$h][0] = dol_buildpath("/epcqr/admin/myobject_extrafields.php", 1); - $head[$h][1] = $langs->trans("ExtraFields"); - $nbExtrafields = (isset($extrafields->attributes['myobject']['label']) && is_countable($extrafields->attributes['myobject']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0; - if ($nbExtrafields > 0) { - $head[$h][1] .= '' . $nbExtrafields . ''; - } - $head[$h][2] = 'myobject_extrafields'; + $head[$h][0] = dol_buildpath("/epcqr/test_qrcode.php", 1); + $head[$h][1] = $langs->trans("Debug"); + $head[$h][2] = 'debug'; $h++; - $head[$h][0] = dol_buildpath("/epcqr/admin/myobjectline_extrafields.php", 1); - $head[$h][1] = $langs->trans("ExtraFieldsLines"); - $nbExtrafields = (isset($extrafields->attributes['myobjectline']['label']) && is_countable($extrafields->attributes['myobjectline']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0; - if ($nbExtrafields > 0) { - $head[$h][1] .= '' . $nbExtrafields . ''; - } - $head[$h][2] = 'myobject_extrafieldsline'; - $h++; - */ - $head[$h][0] = dol_buildpath("/epcqr/admin/about.php", 1); $head[$h][1] = $langs->trans("About"); $head[$h][2] = 'about'; $h++; - // Show more tabs from modules - // Entries must be declared in modules descriptor with line - //$this->tabs = array( - // 'entity:+tabname:Title:@epcqr:/epcqr/mypage.php?id=__ID__' - //); // to add new tab - //$this->tabs = array( - // 'entity:-tabname:Title:@epcqr:/epcqr/mypage.php?id=__ID__' - //); // to remove a tab complete_head_from_modules($conf, $langs, null, $head, $h, 'epcqr@epcqr'); - complete_head_from_modules($conf, $langs, null, $head, $h, 'epcqr@epcqr', 'remove'); return $head; @@ -102,11 +74,47 @@ function epcqr_generateQRCodeForInvoice($invoice, $db) $qrGen = new QRCodeGenerator($db); - // Bankverbindung aus Konfiguration laden - // TODO: Diese Werte sollten aus der Modul-Konfiguration kommen - $accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', 'Eduard Wisch'); - $iban = getDolGlobalString('EPCQR_IBAN', 'DE70217625500013438147'); - $bic = getDolGlobalString('EPCQR_BIC', 'GENODEF1HUM'); + // Bankverbindung laden - entweder aus System oder manuell + $bankDataSource = getDolGlobalString('EPCQR_BANK_DATA_SOURCE', 'manual'); + + if ($bankDataSource == 'system') { + // Bankdaten aus Systembankkonto laden + require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php'; + + $accountHolder = ''; + $iban = ''; + $bic = ''; + + // Erstes aktives Bankkonto mit IBAN suchen + $sql = "SELECT rowid, label, proprio, iban_prefix, bic FROM ".MAIN_DB_PREFIX."bank_account"; + $sql .= " WHERE entity = ".((int) $conf->entity)." AND clos = 0 AND iban_prefix IS NOT NULL AND iban_prefix != ''"; + $sql .= " ORDER BY rowid ASC LIMIT 1"; + + $resql = $db->query($sql); + if ($resql && $db->num_rows($resql) > 0) { + $obj = $db->fetch_object($resql); + $accountHolder = !empty($obj->proprio) ? $obj->proprio : $obj->label; + $iban = $obj->iban_prefix; + $bic = $obj->bic; + dol_syslog("EPCQR: Bankdaten aus System geladen - Inhaber: ".$accountHolder.", IBAN: ".substr($iban, 0, 4)."****", LOG_DEBUG); + } else { + dol_syslog("EPCQR: Kein Systembankkonto gefunden, verwende manuelle Einstellungen", LOG_WARNING); + $accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', ''); + $iban = getDolGlobalString('EPCQR_IBAN', ''); + $bic = getDolGlobalString('EPCQR_BIC', ''); + } + } else { + // Manuelle Bankverbindung aus Konfiguration laden + $accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', ''); + $iban = getDolGlobalString('EPCQR_IBAN', ''); + $bic = getDolGlobalString('EPCQR_BIC', ''); + } + + // Prüfen ob Bankdaten vollständig sind + if (empty($accountHolder) || empty($iban)) { + dol_syslog("EPCQR: Bankdaten unvollständig - Kontoinhaber oder IBAN fehlt", LOG_ERR); + return false; + } // Betrag und Referenz aus Rechnung $amount = price2num($invoice->total_ttc, 'MT'); @@ -130,8 +138,8 @@ function epcqr_generateQRCodeForInvoice($invoice, $db) $invoice->array_options['options_qrcode'] = ""; $invoice->insertExtraFields(); - // qrcodepfad: URL zum QR-Code (Kompatibilität mit alter Version) - $invoice->array_options['options_qrcodepfad'] = $qrCodeUrl; + // qrcodepfad: URL zum QR-Code als klickbarer Link + $invoice->array_options['options_qrcodepfad'] = ''.$qrCodeUrl.''; $invoice->insertExtraFields(); dol_syslog("EPCQR: QR-Code erfolgreich generiert: ".$qrCodePath, LOG_INFO); diff --git a/lib/qrcode.class.php b/lib/qrcode.class.php index 528d1f4..98088d3 100644 --- a/lib/qrcode.class.php +++ b/lib/qrcode.class.php @@ -18,18 +18,30 @@ /** * \file lib/qrcode.class.php * \ingroup epcqr - * \brief QR-Code Generator mit lokalem Caching + * \brief QR-Code Generator mit lokalem Caching und Styling */ /** * QRCode Generator Klasse - * Generiert QR-Codes und cached sie lokal für Wiederverwendung + * 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 * @@ -48,6 +60,70 @@ class QRCodeGenerator 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)) + ); } /** @@ -62,8 +138,9 @@ class QRCodeGenerator */ public function generateEPCQRCode($accountHolder, $iban, $bic, $amount, $reference) { - // Eindeutigen Dateinamen generieren basierend auf Parametern - $hash = md5($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; @@ -96,7 +173,8 @@ class QRCodeGenerator public function generateQRCode($data, $prefix = 'qr') { // Eindeutigen Dateinamen generieren - $hash = md5($data); + $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; @@ -149,10 +227,7 @@ class QRCodeGenerator } /** - * Generiert QR-Code-Bild aus Daten - * - * Nutzt zunächst den externen Service, später kann dies durch - * eine native PHP-Implementierung ersetzt werden + * Generiert QR-Code-Bild aus Daten mit Styling * * @param string $data Daten für QR-Code * @param string $filepath Zielpfad für PNG-Datei @@ -160,36 +235,604 @@ class QRCodeGenerator */ private function generateQRImage($data, $filepath) { - // Methode 1: Externe API (aktuell) - // TODO: Später durch native PHP-Generierung ersetzen - $url = 'https://qr.data-it-solution.de/generate?data='.urlencode($data).'&size=300'; + // TCPDF 2D Barcode Bibliothek laden + require_once DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf_barcodes_2d.php'; - // Bild von URL holen - $imageData = @file_get_contents($url); + 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); - if ($imageData === false) { - // Fallback: Versuche mit cURL - if (function_exists('curl_init')) { - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - $imageData = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); + // PNG-Daten generieren + $imageData = $barcodeobj->getBarcodePngData($this->pixelSize, $this->pixelSize, $this->fgColor); - if ($httpCode !== 200 || $imageData === false) { - return false; - } - } else { + 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); + } + } } - // Bild speichern - $result = file_put_contents($filepath, $imageData); + imagedestroy($image); + return $newImage; + } - return ($result !== false); + /** + * 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); } /** @@ -225,4 +868,14 @@ class QRCodeGenerator 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); + } } diff --git a/sql/dolibarr_allversions.sql b/sql/dolibarr_allversions.sql index 5026bb4..a2083c7 100755 --- a/sql/dolibarr_allversions.sql +++ b/sql/dolibarr_allversions.sql @@ -1,3 +1,27 @@ +-- Copyright (C) 2025 Eduard Wisch -- --- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version. +-- 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. -- +-- EPCQR-Modul - Script wird bei jedem Dolibarr-Upgrade ausgeführt +-- Stellt sicher, dass alle Extrafelder existieren +-- + +-- Extrafelder für Rechnungen anlegen (ON DUPLICATE KEY UPDATE verhindert Fehler) + +-- Extrafeld: qrcode (HTML-Anzeige) +INSERT INTO llx_extrafields (name, label, type, size, elementtype, fieldunique, fieldrequired, pos, alwayseditable, param, enabled, perms, list, totalizable, printable, langs, help, entity) +VALUES ('qrcode', 'EPC QR-Code', 'html', '500', 'facture', 0, 0, 800, 0, '', '1', '', '1', 0, 0, NULL, 'QR-Code als HTML-Bild', 0) +ON DUPLICATE KEY UPDATE label = 'EPC QR-Code'; + +-- Extrafeld: qrcodepfad (URL) +INSERT INTO llx_extrafields (name, label, type, size, elementtype, fieldunique, fieldrequired, pos, alwayseditable, param, enabled, perms, list, totalizable, printable, langs, help, entity) +VALUES ('qrcodepfad', 'QR-Code URL', 'html', '500', 'facture', 0, 0, 801, 0, '', '1', '', '0', 0, 0, NULL, 'URL zum QR-Code', 0) +ON DUPLICATE KEY UPDATE label = 'QR-Code URL'; + +-- Extrafeld: qrcodepath (Lokaler Pfad für ODT) +INSERT INTO llx_extrafields (name, label, type, size, elementtype, fieldunique, fieldrequired, pos, alwayseditable, param, enabled, perms, list, totalizable, printable, langs, help, entity) +VALUES ('qrcodepath', 'QR-Code Pfad', 'varchar', '255', 'facture', 0, 0, 802, 0, '', '1', '', '0', 0, 0, NULL, 'Lokaler Pfad zur QR-Code-Bilddatei', 0) +ON DUPLICATE KEY UPDATE label = 'QR-Code Pfad'; diff --git a/sql/llx_epcqr_extrafields.sql b/sql/llx_epcqr_extrafields.sql new file mode 100644 index 0000000..bab9d5e --- /dev/null +++ b/sql/llx_epcqr_extrafields.sql @@ -0,0 +1,104 @@ +-- Copyright (C) 2025 Eduard Wisch +-- +-- 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/. + +-- +-- EPCQR-Modul Version 2.0 - Initiale Installation +-- Erstellt alle notwendigen Extrafelder für die QR-Code-Funktionalität +-- + +-- ============================================ +-- Extrafeld 1: qrcode (HTML-Anzeige mit -Tag) +-- ============================================ +INSERT INTO llx_extrafields ( + name, label, type, size, elementtype, + fieldunique, fieldrequired, pos, alwayseditable, + param, enabled, perms, list, totalizable, printable, + langs, help, entity +) VALUES ( + 'qrcode', + 'EPC QR-Code', + 'html', + '500', + 'facture', + 0, 0, 800, 0, + '', '1', '', '1', 0, 0, + NULL, + 'QR-Code als HTML-Bild für Anzeige im Formular', + 0 +) +ON DUPLICATE KEY UPDATE + label = 'EPC QR-Code', + type = 'html', + size = '500'; + +-- ============================================ +-- Extrafeld 2: qrcodepfad (URL zum QR-Code) +-- ============================================ +INSERT INTO llx_extrafields ( + name, label, type, size, elementtype, + fieldunique, fieldrequired, pos, alwayseditable, + param, enabled, perms, list, totalizable, printable, + langs, help, entity +) VALUES ( + 'qrcodepfad', + 'QR-Code URL', + 'html', + '500', + 'facture', + 0, 0, 801, 0, + '', '1', '', '0', 0, 0, + NULL, + 'URL zum QR-Code-Bild (klickbarer Link)', + 0 +) +ON DUPLICATE KEY UPDATE + label = 'QR-Code URL', + type = 'html', + size = '500'; + +-- ============================================ +-- Extrafeld 3: qrcodepath (Lokaler Dateipfad für ODT) +-- ============================================ +INSERT INTO llx_extrafields ( + name, label, type, size, elementtype, + fieldunique, fieldrequired, pos, alwayseditable, + param, enabled, perms, list, totalizable, printable, + langs, help, entity +) VALUES ( + 'qrcodepath', + 'QR-Code Bildpfad (lokal)', + 'varchar', + '255', + 'facture', + 0, 0, 802, 0, + '', '1', '', '0', 0, 0, + NULL, + 'Lokaler Pfad zur QR-Code-Bilddatei für ODT-Integration', + 0 +) +ON DUPLICATE KEY UPDATE + label = 'QR-Code Bildpfad (lokal)', + type = 'varchar', + size = '255'; + +-- ============================================ +-- Tabellen-Spalten in facture_extrafields +-- ============================================ +-- MySQL/MariaDB: ADD COLUMN IF NOT EXISTS erfordert MariaDB 10.0.2+ +-- Für ältere Versionen werden Fehler ignoriert + +ALTER TABLE llx_facture_extrafields ADD COLUMN qrcode TEXT DEFAULT NULL; +ALTER TABLE llx_facture_extrafields ADD COLUMN qrcodepfad TEXT DEFAULT NULL; +ALTER TABLE llx_facture_extrafields ADD COLUMN qrcodepath VARCHAR(255) DEFAULT NULL; diff --git a/sql/update_1.5.0.sql b/sql/update_1.5.0.sql index 23f63a2..e002e79 100644 --- a/sql/update_1.5.0.sql +++ b/sql/update_1.5.0.sql @@ -39,7 +39,6 @@ INSERT INTO llx_extrafields ( printable, langs, help, - computed, entity ) VALUES ( 'qrcodepath', @@ -59,7 +58,6 @@ INSERT INTO llx_extrafields ( 0, NULL, 'Lokaler Pfad zur QR-Code-Bilddatei für ODT-Integration', - '', 0 ) ON DUPLICATE KEY UPDATE @@ -67,6 +65,10 @@ ON DUPLICATE KEY UPDATE type = 'varchar', size = '255'; +-- Spalte in facture_extrafields anlegen (falls nicht vorhanden) +-- Diese Spalte speichert den tatsächlichen Wert für jede Rechnung +ALTER TABLE llx_facture_extrafields ADD COLUMN IF NOT EXISTS qrcodepath VARCHAR(255) DEFAULT NULL; + -- Hinweis: Die bestehenden Extrafelder 'qrcode' und 'qrcodepfad' bleiben -- aus Kompatibilitätsgründen bestehen: -- - qrcode: HTML-Version mit -Tag diff --git a/test_qrcode.php b/test_qrcode.php index 5455324..b65412d 100644 --- a/test_qrcode.php +++ b/test_qrcode.php @@ -30,7 +30,7 @@ print ''; // 2. Modul-Konfiguration prüfen print 'Substitutionen aktiviert'; $module_parts = $conf->modules_parts; -$subst_active = isset($module_parts['substitutions']) && in_array('epcqr', $module_parts['substitutions']); +$subst_active = isset($module_parts['substitutions']['epcqr']) || (isset($module_parts['substitutions']) && is_array($module_parts['substitutions']) && in_array('epcqr', $module_parts['substitutions'])); print $subst_active ? '✓ Ja' : '✗ Nein (in modEpcqr.class.php aktivieren!)'; print ''; @@ -196,10 +196,12 @@ print '

7. Letzte Log-Einträge (EPCQR)

'; $logFile = DOL_DATA_ROOT.'/dolibarr.log'; if (file_exists($logFile)) { - $lines = file($logFile); + // Nur die letzten 500 Zeilen lesen um Memory-Probleme zu vermeiden $epcqrLogs = array(); + $output = array(); + exec('tail -500 '.escapeshellarg($logFile).' 2>/dev/null', $output); - foreach (array_reverse($lines) as $line) { + foreach (array_reverse($output) as $line) { if (stripos($line, 'EPCQR') !== false || stripos($line, 'epcqr') !== false) { $epcqrLogs[] = $line; if (count($epcqrLogs) >= 10) break; @@ -209,11 +211,11 @@ if (file_exists($logFile)) { if (!empty($epcqrLogs)) { print '
';
         foreach (array_reverse($epcqrLogs) as $log) {
-            print htmlspecialchars($log);
+            print htmlspecialchars($log)."\n";
         }
         print '
'; } else { - print '

Keine EPCQR-Log-Einträge gefunden.

'; + print '

Keine EPCQR-Log-Einträge in den letzten 500 Zeilen gefunden.

'; } } else { print '

Log-Datei nicht gefunden: '.$logFile.'

';