Stabile Version 2.0 QR Code lokal generiert und ins odt eingefügt.

Veränderungen wie Design, Größe, Rahmen usw
This commit is contained in:
Eduard Wisch 2026-01-29 20:37:59 +01:00
parent 97cfcefe22
commit 3078cc6f67
13 changed files with 1828 additions and 958 deletions

715
README.md
View file

@ -1,150 +1,218 @@
# EPCQR - QR-Code Generator für Dolibarr Rechnungen # EPC QR-Code Modul für Dolibarr
**Version:** 1.5 **Version:** 2.0
**Autor:** Eduard Wisch **Autor:** DATA IT-Solutions (Eduard Wisch)
**Dolibarr:** 13.x - 20.x **Kompatibilität:** Dolibarr 19.0+
**Lizenz:** GPL-3.0+ **Lizenz:** GPL-3.0+
--- ---
## Übersicht ## Ü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 - **Admin-Center** mit allen Einstellungen an einem Ort
**Generisches Bildintegration-System** für ODT-Dokumente - **4 Modul-Stile:** Quadrat, Abgerundet, Punkte, Diamant
**{qrcode} Keyword** in ODT-Templates - **5 Rahmen-Stile:** Kein Rahmen, Einfach, Abgerundet, Doppelt, Gestrichelt
**Substitutionssystem** für beliebige Bilder - **Transparenter Hintergrund** möglich
✅ **Hook-basierte ODT-Verarbeitung** - **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? ### 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) - Empfänger (Kontoinhaber)
- IBAN - IBAN
- BIC - BIC (optional)
- Betrag - Betrag
- Verwendungszweck (Rechnungsnummer) - Verwendungszweck (Rechnungsnummer)
--- Diese QR-Codes sind mit allen europäischen Banking-Apps kompatibel.
## 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**
--- ---
## Installation ## Installation
### 1. Modul aktivieren ### Voraussetzungen
1. Gehe zu: **Home → Setup → Modules/Applications** - Dolibarr 19.0 oder höher
2. Suche nach "EPCQR" - PHP 7.1 oder höher
3. Klicke auf **Activate** - 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 | 2. **Modul aktivieren:**
|----------|-----|--------------| - Einstellungen → Module/Anwendungen
| `qrcode` | HTML | QR-Code als `<img>` Tag (Kompatibilität) | - Kategorie "Finanzen" auswählen
| `qrcodepfad` | Varchar(255) | QR-Code URL via viewimage.php | - "EPC QR-Code" aktivieren
| `qrcodepath` | Varchar(255) | **NEU**: Lokaler Dateipfad für ODT-Integration |
3. **Bankdaten konfigurieren:**
- Einstellungen → Module → EPC QR-Code → Einstellungen
- Bankverbindung eingeben oder Systembankkonto auswählen
--- ---
## Konfiguration ## 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 | Einstellung | Beschreibung |
// Bankdaten - HIER ANPASSEN! |-------------|--------------|
$accountHolder = 'Peter Casimir'; | **Datenquelle** | "Systembankkonto verwenden" oder "Manuelle Eingabe" |
$iban = 'DE70217625500013438147'; | **Kontoinhaber** | Name des Kontoinhabers (bei manueller Eingabe) |
$bic = 'GENODEF1HUM'; | **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 #### Modul-Stil
$accountHolder = 'Dein Name / Firmenname';
$iban = 'DE12345678901234567890';
$bic = 'ABCDEFGH123';
```
**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 ## 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) Der Trigger läuft beim Event `BILL_VALIDATE`.
2. **Freigeben** (Validate)
3. QR-Code wird automatisch generiert
4. Extra-Felder werden befüllt
### 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 | `<img>`-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
``` Um den QR-Code in ODT-Dokumentvorlagen einzufügen:
{qrcode}
```
**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:** Der Platzhalter muss exakt `{qrcode}` lauten.
**Hinweis:** Das alte Format `{invoice_options_qrcode}` funktioniert weiterhin für Kompatibilität.
### Beispiel Template-Position ### 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 ## 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. ```
BCD # Service Tag
**Problem:** Zu diesem Zeitpunkt hat das `$object` noch die provisorische Nummer (z.B. `PROV23`)! 002 # Version
1 # Character set (UTF-8)
**Lösung:** Das Modul lädt das Rechnungsobjekt neu aus der Datenbank: SCT # SEPA Credit Transfer
[BIC] # Bank Identifier Code
```php [Kontoinhaber] # Empfänger Name
$invoice = new Facture($this-&gt;db); [IBAN] # Empfänger IBAN
$invoice-&gt;fetch($object-&gt;id); EUR[Betrag] # Währung und Betrag
// Jetzt hat $invoice-&gt;ref die finale Nummer (z.B. IN26-0001) # Purpose (leer)
[Rechnungsnummer] # Verwendungszweck
# Info (leer)
``` ```
#### 2. QR-Code Service ### Dateispeicherung
Das Modul nutzt einen externen Service: QR-Codes werden gespeichert unter:
**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:**
``` ```
https://qr.data-it-solution.de/epc?name=Eduard+Wisch&amp;iban=DE70217625500013438147&amp;bic=GENODEF1HUM&amp;amount=1234.56&amp;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 Das Modul verwendet folgende Dolibarr-Hooks:
// Als HTML-Tag für ODT
$invoice-&gt;array_options['options_qrcode'] = "<img src="$qrurl" width="100px">";
// Als URL für Backup/Referenz | Hook | Funktion |
$invoice-&gt;array_options['options_qrcodepfad'] = $qrurl; |------|----------|
| `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
``` ### QR-Code wird nicht generiert
┌─────────────────────┐
│ 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 │
└─────────────────────────────┘
```
--- 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 Nach Änderung der Styling-Einstellungen:
// FALSCH: 1. **Cache leeren** im Admin-Center klicken
$ref = $object-&gt;ref; 2. Rechnung erneut generieren (auf Draft setzen → Validieren)
3. Oder neuen QR-Code durch neue Rechnung testen
// RICHTIG: ### Extra-Felder fehlen
$invoice = new Facture($this-&gt;db);
$invoice-&gt;fetch($object-&gt;id);
$ref = $invoice-&gt;ref;
```
### Problem: Extra-Felder sind leer Falls die Extrafelder nicht automatisch erstellt wurden:
**Symptom:** Nach Freigabe sind die Felder `qrcode` und `qrcodepfad` leer
**Lösung:**
1. Modul **deaktivieren** 1. Modul **deaktivieren**
2. Modul wieder **aktivieren** 2. Modul wieder **aktivieren**
3. Prüfe: **Setup → Dictionaries → Extra Attributes → Invoices** 3. Prüfen unter: Einstellungen → Wörterbücher → Zusatzattribute → Rechnungen
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-&gt;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:** `<img src="https://..." width="100px">`
- **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-&gt;id);
// ⚠️ WICHTIG: Objekt neu laden für finale Rechnungsnummer!
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
$invoice = new Facture($this-&gt;db);
$result = $invoice-&gt;fetch($object-&gt;id);
if ($result &gt; 0) {
// Bankdaten - HIER ANPASSEN!
$accountHolder = 'Eduard Wisch';
$iban = 'DE70217625500013438147';
$bic = 'GENODEF1HUM';
$amount = price2num($invoice-&gt;total_ttc, 'MT');
$ref = $invoice-&gt;ref; // Finale Nummer
// QR-Code URL generieren
$qrurl = "https://qr.data-it-solution.de/epc?"
. "name=" . urlencode($accountHolder)
. "&amp;iban=" . urlencode($iban)
. "&amp;bic=" . urlencode($bic)
. "&amp;amount=" . urlencode($amount)
. "&amp;remittance=" . urlencode($ref);
// In Extra-Felder speichern
$invoice-&gt;array_options['options_qrcode'] = "<img src="&quot;.$qrurl.&quot;" width="100px">";
$invoice-&gt;insertExtraFields();
$invoice-&gt;array_options['options_qrcodepfad'] = $qrurl;
$invoice-&gt;insertExtraFields();
dol_syslog("EPCQR: QR-Code generiert für " . $invoice-&gt;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-&gt;array_options['options_qrcode'] = "<img src="&quot;.$qrurl.&quot;" width="150px">";
```
**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 &amp; 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)
--- ---
## Changelog ## Changelog
### Version 1.5 (2026-01-27) ### Version 2.0 (Januar 2025)
- ✅ **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 1.4 (2026-01-11) **Neue Funktionen:**
- ✅ Erste stabile Version - Vollständiges Admin-Center mit allen Einstellungen
- ✅ Automatische QR-Code Generierung - Modul-Stile: Quadrat, Abgerundet, Punkte, Diamant
- ✅ Extra-Felder für ODT-Integration - Rahmen-Stile: Kein Rahmen, Einfach, Abgerundet, Doppelt, Gestrichelt
- ✅ Externe QR-Service Integration - Transparenter Hintergrund
- ✅ Object-Reload Fix für finale Rechnungsnummer - 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 ## 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

View file

@ -20,16 +20,14 @@
/** /**
* \file epcqr/admin/setup.php * \file epcqr/admin/setup.php
* \ingroup epcqr * \ingroup epcqr
* \brief Epcqr setup page. * \brief EPCQR Modul Einstellungen
*/ */
// Load Dolibarr environment // Load Dolibarr environment
$res = 0; $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"])) { if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; $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']; $tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
$tmp2 = realpath(__FILE__); $tmp2 = realpath(__FILE__);
$i = strlen($tmp) - 1; $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")) { 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"; $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")) { if (!$res && file_exists("../../main.inc.php")) {
$res = @include "../../main.inc.php"; $res = @include "../../main.inc.php";
} }
@ -57,8 +54,8 @@ if (!$res) {
// Libraries // Libraries
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php"; 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 '../lib/epcqr.lib.php';
//require_once "../class/myclass.class.php";
/** /**
* @var Conf $conf * @var Conf $conf
@ -71,250 +68,135 @@ require_once '../lib/epcqr.lib.php';
// Translations // Translations
$langs->loadLangs(array("admin", "epcqr@epcqr")); $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 // Access control
/** @var HookManager $hookmanager */ if (!$user->admin) {
$hookmanager->initHooks(array('epcqrsetup', 'globalsetup')); accessforbidden();
}
// Parameters // Parameters
$action = GETPOST('action', 'aZ09'); $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; $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 * 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 // Validate IBAN format (basic check)
if (versioncompare(explode('.', DOL_VERSION), array(15)) < 0 && $action == 'update' && !empty($user->admin)) { if ($bankDataSource == 'manual' && !empty($iban)) {
$formSetup->saveConfFromPost(); $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');
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)) {
$error++; $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) { 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'); 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); $form = new Form($db);
$help_url = '';
$title = "EpcqrSetup"; $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 // Subheader
$linkback = '<a href="'.($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1').'">'.$langs->trans("BackToModuleList").'</a>'; $linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
print load_fiche_titre($langs->trans($title), $linkback, 'title_setup'); 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(); $head = epcqrAdminPrepareHead();
print dol_get_fiche_head($head, 'settings', $langs->trans($title), -1, "epcqr@epcqr"); print dol_get_fiche_head($head, 'settings', $langs->trans($title), -1, "epcqr@epcqr");
// Setup page goes here // Current values
echo '<span class="opacitymedium">'.$langs->trans("EpcqrSetupPage").'</span><br><br>'; $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') { // QR Code styling settings
print $formSetup->generateOutput(true); $fgColor = getDolGlobalString('EPCQR_FG_COLOR', '#000000');
print '<br>'; $bgColor = getDolGlobalString('EPCQR_BG_COLOR', '#FFFFFF');
} elseif (!empty($formSetup->items)) { $logoPath = getDolGlobalString('EPCQR_LOGO_PATH', '');
print $formSetup->generateOutput(); $logoSize = getDolGlobalInt('EPCQR_LOGO_SIZE', 20);
print '<div class="tabsAction">'; $moduleStyle = getDolGlobalString('EPCQR_MODULE_STYLE', 'square');
print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=edit&token='.newToken().'">'.$langs->trans("Modify").'</a>'; $borderStyle = getDolGlobalString('EPCQR_BORDER_STYLE', 'none');
print '</div>';
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="update">';
// Bank Data Section
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td colspan="2">'.$langs->trans("BankDataSettings").'</td>';
print '</tr>';
// Bank Data Source Selection
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans("BankDataSource").'</td>';
print '<td>';
print '<select name="EPCQR_BANK_DATA_SOURCE" id="bankDataSource" class="flat minwidth200">';
print '<option value="system"'.($bankDataSource == 'system' ? ' selected' : '').'>'.$langs->trans("UseSystemBankAccount").'</option>';
print '<option value="manual"'.($bankDataSource == 'manual' ? ' selected' : '').'>'.$langs->trans("ManualEntry").'</option>';
print '</select>';
print '</td>';
print '</tr>';
// Manual Bank Data Fields
print '<tr class="oddeven manual-fields">';
print '<td>'.$langs->trans("AccountHolder").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_ACCOUNT_HOLDER" class="flat minwidth300" value="'.dol_escape_htmltag($accountHolder).'" placeholder="'.$langs->trans("AccountHolderPlaceholder").'">';
print '</td>';
print '</tr>';
print '<tr class="oddeven manual-fields">';
print '<td>'.$langs->trans("IBAN").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_IBAN" class="flat minwidth300" value="'.dol_escape_htmltag($iban).'" placeholder="DE89 3704 0044 0532 0130 00">';
print '</td>';
print '</tr>';
print '<tr class="oddeven manual-fields">';
print '<td>'.$langs->trans("BIC").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_BIC" class="flat minwidth200" value="'.dol_escape_htmltag($bic).'" placeholder="COBADEFFXXX">';
print '</td>';
print '</tr>';
print '</table>';
// QR Code Settings Section
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td colspan="2">'.$langs->trans("QRCodeSettings").'</td>';
print '</tr>';
// Image Ratio
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans("QRCodeSize").'</td>';
print '<td>';
print '<input type="text" name="EPCQR_IMAGE_RATIO" class="flat width75" value="'.dol_escape_htmltag($imageRatio).'">';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeSizeHelp").'</span>';
print '</td>';
print '</tr>';
// Foreground Color
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeFgColor").'</td>';
print '<td>';
print '<input type="color" name="EPCQR_FG_COLOR" value="'.dol_escape_htmltag($fgColor).'" style="width: 60px; height: 30px; padding: 0; border: 1px solid #ccc;">';
print ' <input type="text" name="EPCQR_FG_COLOR_TEXT" class="flat width100" value="'.dol_escape_htmltag($fgColor).'" onchange="document.getElementsByName(\'EPCQR_FG_COLOR\')[0].value=this.value">';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeFgColorHelp").'</span>';
print '</td>';
print '</tr>';
// Background Color
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeBgColor").'</td>';
print '<td>';
$isTransparent = (strtolower($bgColor) === 'transparent' || $bgColor === '');
print '<input type="checkbox" name="EPCQR_BG_TRANSPARENT" id="bgTransparent" value="1"'.($isTransparent ? ' checked' : '').' onchange="toggleBgColor(this.checked)">';
print ' <label for="bgTransparent">'.$langs->trans("Transparent").'</label>';
print ' &nbsp; ';
print '<input type="color" name="EPCQR_BG_COLOR" id="bgColorPicker" value="'.dol_escape_htmltag($isTransparent ? '#FFFFFF' : $bgColor).'" style="width: 60px; height: 30px; padding: 0; border: 1px solid #ccc;"'.($isTransparent ? ' disabled' : '').'>';
print ' <input type="text" name="EPCQR_BG_COLOR_TEXT" id="bgColorText" class="flat width100" value="'.dol_escape_htmltag($isTransparent ? '' : $bgColor).'"'.($isTransparent ? ' disabled' : '').' onchange="document.getElementById(\'bgColorPicker\').value=this.value">';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeBgColorHelp").'</span>';
print '</td>';
print '</tr>';
print '<script>
function toggleBgColor(isTransparent) {
document.getElementById("bgColorPicker").disabled = isTransparent;
document.getElementById("bgColorText").disabled = isTransparent;
if (isTransparent) {
document.getElementById("bgColorText").value = "";
} }
*/
if (!empty($formSetup->items)) {
print $formSetup->generateOutput(true);
print '<br>';
} }
</script>';
// Logo Path
foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) { print '<tr class="oddeven">';
if (!empty($myTmpObjectArray['includerefgeneration'])) { print '<td>'.$langs->trans("QRCodeLogo").'</td>';
// Numbering models print '<td>';
print '<input type="text" name="EPCQR_LOGO_PATH" class="flat minwidth400" value="'.dol_escape_htmltag($logoPath).'" placeholder="/var/www/dolibarr/documents/mycompany/logos/logo.png">';
$setupnotempty++; print '<br><span class="opacitymedium">'.$langs->trans("QRCodeLogoHelp").'</span>';
if (!empty($logoPath)) {
print load_fiche_titre($langs->trans("NumberingModules", $myTmpObjectArray['label']), '', ''); if (file_exists($logoPath)) {
print '<br><span style="color: green;">✓ '.$langs->trans("FileExists").'</span>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td>'.$langs->trans("Name").'</td>';
print '<td>'.$langs->trans("Description").'</td>';
print '<td class="nowrap">'.$langs->trans("Example").'</td>';
print '<td class="center" width="60">'.$langs->trans("Status").'</td>';
print '<td class="center" width="16">'.$langs->trans("ShortInfo").'</td>';
print '</tr>'."\n";
clearstatcache();
foreach ($dirmodels as $reldir) {
$dir = dol_buildpath($reldir."core/modules/".$moduledir);
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);
require_once $dir.'/'.$file.'.php';
$module = new $file($db);
'@phan-var-force ModeleNumRefMyObject $module';
// 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;
}
if ($module->isEnabled()) {
dol_include_once('/'.$moduledir.'/class/'.strtolower($myTmpObjectKey).'.class.php');
print '<tr class="oddeven"><td>'.$module->getName($langs)."</td><td>\n";
print $module->info($langs);
print '</td>';
// Show example of numbering model
print '<td class="nowrap">';
$tmp = $module->getExample();
if (preg_match('/^Error/', $tmp)) {
$langs->load("errors");
print '<div class="error">'.$langs->trans($tmp).'</div>';
} elseif ($tmp == 'NotConfigured') {
print $langs->trans($tmp);
} else { } else {
print $tmp; print '<br><span style="color: red;">✗ '.$langs->trans("FileNotFound").'</span>';
} }
print '</td>'."\n"; }
print '</td>';
print '</tr>';
print '<td class="center">'; // Logo Size
$constforvar = 'EPCQR_'.strtoupper($myTmpObjectKey).'_ADDON'; print '<tr class="oddeven">';
$defaultifnotset = 'thevaluetousebydefault'; print '<td>'.$langs->trans("QRCodeLogoSize").'</td>';
$activenumberingmodel = getDolGlobalString($constforvar, $defaultifnotset); print '<td>';
if ($activenumberingmodel == $file) { print '<input type="number" name="EPCQR_LOGO_SIZE" class="flat width75" value="'.dol_escape_htmltag($logoSize).'" min="5" max="30">';
print img_picto($langs->trans("Activated"), 'switch_on'); print ' % <span class="opacitymedium">'.$langs->trans("QRCodeLogoSizeHelp").'</span>';
print '</td>';
print '</tr>';
// Module Style
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeModuleStyle").'</td>';
print '<td>';
print '<select name="EPCQR_MODULE_STYLE" class="flat minwidth150">';
print '<option value="square"'.($moduleStyle == 'square' ? ' selected' : '').'>'.$langs->trans("StyleSquare").'</option>';
print '<option value="rounded"'.($moduleStyle == 'rounded' ? ' selected' : '').'>'.$langs->trans("StyleRounded").'</option>';
print '<option value="dots"'.($moduleStyle == 'dots' ? ' selected' : '').'>'.$langs->trans("StyleDots").'</option>';
print '<option value="diamond"'.($moduleStyle == 'diamond' ? ' selected' : '').'>'.$langs->trans("StyleDiamond").'</option>';
print '</select>';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeModuleStyleHelp").'</span>';
print '</td>';
print '</tr>';
// Border Style
print '<tr class="oddeven">';
print '<td>'.$langs->trans("QRCodeBorderStyle").'</td>';
print '<td>';
print '<select name="EPCQR_BORDER_STYLE" class="flat minwidth150">';
print '<option value="none"'.($borderStyle == 'none' ? ' selected' : '').'>'.$langs->trans("BorderNone").'</option>';
print '<option value="simple"'.($borderStyle == 'simple' ? ' selected' : '').'>'.$langs->trans("BorderSimple").'</option>';
print '<option value="rounded"'.($borderStyle == 'rounded' ? ' selected' : '').'>'.$langs->trans("BorderRounded").'</option>';
print '<option value="double"'.($borderStyle == 'double' ? ' selected' : '').'>'.$langs->trans("BorderDouble").'</option>';
print '<option value="dashed"'.($borderStyle == 'dashed' ? ' selected' : '').'>'.$langs->trans("BorderDashed").'</option>';
print '</select>';
print ' <span class="opacitymedium">'.$langs->trans("QRCodeBorderStyleHelp").'</span>';
print '</td>';
print '</tr>';
print '</table>';
// Clear Cache Button
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td colspan="2">'.$langs->trans("QRCodeCache").'</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans("ClearQRCodeCache").'</td>';
print '<td>';
print '<a class="button" href="'.$_SERVER["PHP_SELF"].'?action=clearcache&token='.newToken().'">'.$langs->trans("ClearCache").'</a>';
print ' <span class="opacitymedium">'.$langs->trans("ClearCacheHelp").'</span>';
print '</td>';
print '</tr>';
print '</table>';
// Extra Field Visibility Section
print '<br>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<td colspan="2">'.$langs->trans("ExtraFieldVisibility").'</td>';
print '</tr>';
// Hide QR Code field (HTML with image)
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans("HideQRCodeField").'</td>';
print '<td>';
print '<input type="checkbox" name="EPCQR_HIDE_QRCODE" value="1"'.($hideQrcode ? ' checked' : '').'>';
print ' <span class="opacitymedium">'.$langs->trans("HideQRCodeFieldHelp").'</span>';
print '</td>';
print '</tr>';
// Hide QR Code URL field
print '<tr class="oddeven">';
print '<td>'.$langs->trans("HideQRCodePfadField").'</td>';
print '<td>';
print '<input type="checkbox" name="EPCQR_HIDE_QRCODEPFAD" value="1"'.($hideQrcodepfad ? ' checked' : '').'>';
print ' <span class="opacitymedium">'.$langs->trans("HideQRCodePfadFieldHelp").'</span>';
print '</td>';
print '</tr>';
// Hide QR Code Path field (local file path)
print '<tr class="oddeven">';
print '<td>'.$langs->trans("HideQRCodePathField").'</td>';
print '<td>';
print '<input type="checkbox" name="EPCQR_HIDE_QRCODEPATH" value="1"'.($hideQrcodepath ? ' checked' : '').'>';
print ' <span class="opacitymedium">'.$langs->trans("HideQRCodePathFieldHelp").'</span>';
print '</td>';
print '</tr>';
print '</table>';
// Submit button
print '<br>';
print '<div class="center">';
print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
print '</div>';
print '</form>';
// JavaScript to show/hide manual fields based on selection
print '<script type="text/javascript">
$(document).ready(function() {
function toggleManualFields() {
var source = $("#bankDataSource").val();
if (source == "manual") {
$(".manual-fields").show();
} else { } else {
print '<a href="'.$_SERVER["PHP_SELF"].'?action=setmod&token='.newToken().'&object='.strtolower($myTmpObjectKey).'&value='.urlencode($file).'">'; $(".manual-fields").hide();
print img_picto($langs->trans("Disabled"), 'switch_off');
print '</a>';
}
print '</td>';
$className = $myTmpObjectArray['class'];
$mytmpinstance = new $className($db);
'@phan-var-force MyObject $mytmpinstance';
$mytmpinstance->initAsSpecimen();
// Info
$htmltooltip = '';
$htmltooltip .= ''.$langs->trans("Version").': <b>'.$module->getVersion().'</b><br>';
$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.'<br>';
} else {
$htmltooltip .= $langs->trans($module->error).'<br>';
} }
} }
print '<td class="center">'; toggleManualFields();
print $form->textwithpicto('', $htmltooltip, 1, 'info');
print '</td>';
print "</tr>\n"; $("#bankDataSource").on("change", function() {
} toggleManualFields();
} });
} });
closedir($handle); </script>';
}
}
}
print "</table><br>\n";
}
if (!empty($myTmpObjectArray['includedocgeneration'])) { // Info box about system bank account
/* if ($bankDataSource == 'system') {
* Document templates generators print '<br>';
*/ print '<div class="info">';
$setupnotempty++; print $langs->trans("SystemBankAccountInfo");
$type = strtolower($myTmpObjectKey);
print load_fiche_titre($langs->trans("DocumentModules", $myTmpObjectKey), '', ''); // Try to show configured bank accounts
require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
// Load array def with activated templates $bankAccount = new Account($db);
$def = array(); $sql = "SELECT rowid, label, iban_prefix, bic FROM ".MAIN_DB_PREFIX."bank_account WHERE entity = ".((int) $conf->entity)." AND clos = 0";
$sql = "SELECT nom";
$sql .= " FROM ".$db->prefix()."document_model";
$sql .= " WHERE type = '".$db->escape($type)."'";
$sql .= " AND entity = ".$conf->entity;
$resql = $db->query($sql); $resql = $db->query($sql);
if ($resql) { if ($resql && $db->num_rows($resql) > 0) {
$i = 0; print '<br><br><strong>'.$langs->trans("AvailableBankAccounts").':</strong><ul>';
$num_rows = $db->num_rows($resql); while ($obj = $db->fetch_object($resql)) {
while ($i < $num_rows) { print '<li>'.$obj->label;
$array = $db->fetch_array($resql); if (!empty($obj->iban_prefix)) {
array_push($def, $array[0]); print ' - IBAN: '.substr($obj->iban_prefix, 0, 4).'****'.substr($obj->iban_prefix, -4);
$i++;
} }
print '</li>';
}
print '</ul>';
} else { } else {
dol_print_error($db); print '<br><br><span class="warning">'.$langs->trans("NoBankAccountConfigured").'</span>';
} }
print '<table class="noborder centpercent">'."\n"; print '</div>';
print '<tr class="liste_titre">'."\n";
print '<td>'.$langs->trans("Name").'</td>';
print '<td>'.$langs->trans("Description").'</td>';
print '<td class="center" width="60">'.$langs->trans("Status")."</td>\n";
print '<td class="center" width="60">'.$langs->trans("Default")."</td>\n";
print '<td class="center" width="38">'.$langs->trans("ShortInfo").'</td>';
print '<td class="center" width="38">'.$langs->trans("Preview").'</td>';
print "</tr>\n";
clearstatcache();
foreach ($dirmodels as $reldir) {
foreach (array('', '/doc') as $valdir) {
$realpath = $reldir."core/modules/".$moduledir.$valdir;
$dir = dol_buildpath($realpath);
if (is_dir($dir)) {
$handle = opendir($dir);
if (is_resource($handle)) {
$filelist = array();
while (($file = readdir($handle)) !== false) {
$filelist[] = $file;
}
closedir($handle);
arsort($filelist);
foreach ($filelist as $file) {
if (preg_match('/\.modules\.php$/i', $file) && preg_match('/^(pdf_|doc_)/', $file)) {
if (file_exists($dir.'/'.$file)) {
$name = substr($file, 4, dol_strlen($file) - 16);
$className = substr($file, 0, dol_strlen($file) - 12);
require_once $dir.'/'.$file;
$module = new $className($db);
'@phan-var-force ModelePDFMyObject $module';
$modulequalified = 1;
if ($module->version == 'development' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2) {
$modulequalified = 0;
}
if ($module->version == 'experimental' && getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1) {
$modulequalified = 0;
}
if ($modulequalified) {
print '<tr class="oddeven"><td width="100">';
print(empty($module->name) ? $name : $module->name);
print "</td><td>\n";
if (method_exists($module, 'info')) {
print $module->info($langs); // @phan-suppress-current-line PhanUndeclaredMethod
} else {
print $module->description;
}
print '</td>';
// Active
if (in_array($name, $def)) {
print '<td class="center">'."\n";
print '<a href="'.$_SERVER["PHP_SELF"].'?action=del&token='.newToken().'&value='.urlencode($name).'">';
print img_picto($langs->trans("Enabled"), 'switch_on');
print '</a>';
print '</td>';
} else {
print '<td class="center">'."\n";
print '<a href="'.$_SERVER["PHP_SELF"].'?action=set&token='.newToken().'&value='.urlencode($name).'&scan_dir='.urlencode($module->scandir).'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"), 'switch_off').'</a>';
print "</td>";
}
// Default
print '<td class="center">';
$constforvar = 'EPCQR_'.strtoupper($myTmpObjectKey).'_ADDON_PDF';
if (getDolGlobalString($constforvar) == $name) {
//print img_picto($langs->trans("Default"), 'on');
// Even if choice is the default value, we allow to disable it. Replace this with previous line if you need to disable unset
print '<a href="'.$_SERVER["PHP_SELF"].'?action=unsetdoc&token='.newToken().'&object='.urlencode(strtolower($myTmpObjectKey)).'&value='.urlencode($name).'&scan_dir='.urlencode($module->scandir).'&label='.urlencode($module->name).'&amp;type='.urlencode($type).'" alt="'.$langs->trans("Disable").'">'.img_picto($langs->trans("Enabled"), 'on').'</a>';
} else {
print '<a href="'.$_SERVER["PHP_SELF"].'?action=setdoc&token='.newToken().'&object='.urlencode(strtolower($myTmpObjectKey)).'&value='.urlencode($name).'&scan_dir='.urlencode($module->scandir).'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'</a>';
}
print '</td>';
// Info
$htmltooltip = ''.$langs->trans("Name").': '.$module->name;
$htmltooltip .= '<br>'.$langs->trans("Type").': '.($module->type ? $module->type : $langs->trans("Unknown"));
if ($module->type == 'pdf') {
$htmltooltip .= '<br>'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur;
}
$htmltooltip .= '<br>'.$langs->trans("Path").': '.preg_replace('/^\//', '', $realpath).'/'.$file;
$htmltooltip .= '<br><br><u>'.$langs->trans("FeaturesSupported").':</u>';
$htmltooltip .= '<br>'.$langs->trans("Logo").': '.yn($module->option_logo, 1, 1);
$htmltooltip .= '<br>'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang, 1, 1);
print '<td class="center">';
print $form->textwithpicto('', $htmltooltip, 1, 'info');
print '</td>';
// Preview
print '<td class="center">';
if ($module->type == 'pdf') {
$newname = preg_replace('/_'.preg_quote(strtolower($myTmpObjectKey), '/').'/', '', $name);
print '<a href="'.$_SERVER["PHP_SELF"].'?action=specimen&module='.urlencode($newname).'&object='.urlencode($myTmpObjectKey).'">'.img_object($langs->trans("Preview"), 'pdf').'</a>';
} else {
print img_object($langs->transnoentitiesnoconv("PreviewNotAvailable"), 'generic');
}
print '</td>';
print "</tr>\n";
}
}
}
}
}
}
}
}
print '</table>';
}
}
if (empty($setupnotempty)) {
print '<br>'.$langs->trans("NothingToSetup");
} }
// Page end // Page end

View file

@ -101,6 +101,72 @@ class ActionsEpcqr
return 0; 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) * Hook für PDF-Generierung (falls später benötigt)
* *

View file

@ -66,17 +66,17 @@ class modEpcqr extends DolibarrModules
// DESCRIPTION_FLAG // DESCRIPTION_FLAG
// Module description, used if translation string 'ModuleEpcqrDesc' not found (Epcqr is name of module). // 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. // 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 // Author
$this->editor_name = 'Alles Watt läuft'; $this->editor_name = 'DATA IT-Solutions';
$this->editor_url = ''; // Must be an external online web site $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' $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' // 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 // Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt'; //$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 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' // 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' // 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...) // Define some features supported by module (triggers, login, substitutions, menus, css, etc...)
$this->module_parts = array( $this->module_parts = array(

View file

@ -34,14 +34,14 @@ function epcqr_completesubstitutionarray(&$substitutionarray, $langs, $object, $
{ {
global $conf, $db; global $conf, $db;
dol_syslog("EPCQR: START completesubstitutionarray für ".get_class($object), LOG_DEBUG);
// Prüfen ob Objekt gültig ist // Prüfen ob Objekt gültig ist
if (!is_object($object) || empty($object->id)) { if (!is_object($object) || empty($object->id)) {
dol_syslog("EPCQR: Object ist null oder hat keine ID - überspringe", LOG_DEBUG); dol_syslog("EPCQR: Object ist null oder hat keine ID - überspringe", LOG_DEBUG);
return; return;
} }
dol_syslog("EPCQR: START completesubstitutionarray für ".get_class($object), LOG_DEBUG);
// QR-Code Pfad aus Extra-Feldern // QR-Code Pfad aus Extra-Feldern
$qrCodePath = ''; $qrCodePath = '';

96
langs/de_DE/epcqr.lang Normal file
View file

@ -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

View file

@ -5,34 +5,94 @@
# #
# Module label 'ModuleEpcqrName' # Module label 'ModuleEpcqrName'
ModuleEpcqrName = Epcqr ModuleEpcqrName = EPC QR Code
# Module description 'ModuleEpcqrDesc' # Module description 'ModuleEpcqrDesc'
ModuleEpcqrDesc = Epcqr description ModuleEpcqrDesc = Generate EPC QR codes for invoices (SEPA payment)
# #
# Admin page # Admin page
# #
EpcqrSetup = Epcqr setup EpcqrSetup = EPC QR Code Setup
Settings = Settings Settings = Settings
EpcqrSetupPage = Epcqr setup page Debug = Debug
NewSection=New section EpcqrSetupPage = EPC QR Code module settings
EPCQR_MYPARAM1 = My param 1
EPCQR_MYPARAM1Tooltip = My param 1 tooltip
EPCQR_MYPARAM2=My param 2
EPCQR_MYPARAM2Tooltip=My param 2 tooltip
# 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 page
# #
About = About About = About
EpcqrAbout = About Epcqr EpcqrAbout = About EPC QR Code
EpcqrAboutPage = Epcqr about page EpcqrAboutPage = EPC QR Code module information
# #
# Sample page # Sample page
# #
EpcqrArea = Home Epcqr EpcqrArea = EPC QR Code
MyPageName = My page name MyPageName = My page name
# #

View file

@ -30,10 +30,6 @@ function epcqrAdminPrepareHead()
{ {
global $langs, $conf; global $langs, $conf;
// global $db;
// $extrafields = new ExtraFields($db);
// $extrafields->fetch_name_optionals_label('myobject');
$langs->load("epcqr@epcqr"); $langs->load("epcqr@epcqr");
$h = 0; $h = 0;
@ -44,41 +40,17 @@ function epcqrAdminPrepareHead()
$head[$h][2] = 'settings'; $head[$h][2] = 'settings';
$h++; $h++;
/* $head[$h][0] = dol_buildpath("/epcqr/test_qrcode.php", 1);
$head[$h][0] = dol_buildpath("/epcqr/admin/myobject_extrafields.php", 1); $head[$h][1] = $langs->trans("Debug");
$head[$h][1] = $langs->trans("ExtraFields"); $head[$h][2] = 'debug';
$nbExtrafields = (isset($extrafields->attributes['myobject']['label']) && is_countable($extrafields->attributes['myobject']['label'])) ? count($extrafields->attributes['myobject']['label']) : 0;
if ($nbExtrafields > 0) {
$head[$h][1] .= '<span class="badge marginleftonlyshort">' . $nbExtrafields . '</span>';
}
$head[$h][2] = 'myobject_extrafields';
$h++; $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] .= '<span class="badge marginleftonlyshort">' . $nbExtrafields . '</span>';
}
$head[$h][2] = 'myobject_extrafieldsline';
$h++;
*/
$head[$h][0] = dol_buildpath("/epcqr/admin/about.php", 1); $head[$h][0] = dol_buildpath("/epcqr/admin/about.php", 1);
$head[$h][1] = $langs->trans("About"); $head[$h][1] = $langs->trans("About");
$head[$h][2] = 'about'; $head[$h][2] = 'about';
$h++; $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');
complete_head_from_modules($conf, $langs, null, $head, $h, 'epcqr@epcqr', 'remove'); complete_head_from_modules($conf, $langs, null, $head, $h, 'epcqr@epcqr', 'remove');
return $head; return $head;
@ -102,11 +74,47 @@ function epcqr_generateQRCodeForInvoice($invoice, $db)
$qrGen = new QRCodeGenerator($db); $qrGen = new QRCodeGenerator($db);
// Bankverbindung aus Konfiguration laden // Bankverbindung laden - entweder aus System oder manuell
// TODO: Diese Werte sollten aus der Modul-Konfiguration kommen $bankDataSource = getDolGlobalString('EPCQR_BANK_DATA_SOURCE', 'manual');
$accountHolder = getDolGlobalString('EPCQR_ACCOUNT_HOLDER', 'Eduard Wisch');
$iban = getDolGlobalString('EPCQR_IBAN', 'DE70217625500013438147'); if ($bankDataSource == 'system') {
$bic = getDolGlobalString('EPCQR_BIC', 'GENODEF1HUM'); // 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 // Betrag und Referenz aus Rechnung
$amount = price2num($invoice->total_ttc, 'MT'); $amount = price2num($invoice->total_ttc, 'MT');
@ -130,8 +138,8 @@ function epcqr_generateQRCodeForInvoice($invoice, $db)
$invoice->array_options['options_qrcode'] = "<img src='".$qrCodeUrl."' width='100px'>"; $invoice->array_options['options_qrcode'] = "<img src='".$qrCodeUrl."' width='100px'>";
$invoice->insertExtraFields(); $invoice->insertExtraFields();
// qrcodepfad: URL zum QR-Code (Kompatibilität mit alter Version) // qrcodepfad: URL zum QR-Code als klickbarer Link
$invoice->array_options['options_qrcodepfad'] = $qrCodeUrl; $invoice->array_options['options_qrcodepfad'] = '<a href="'.$qrCodeUrl.'" target="_blank">'.$qrCodeUrl.'</a>';
$invoice->insertExtraFields(); $invoice->insertExtraFields();
dol_syslog("EPCQR: QR-Code erfolgreich generiert: ".$qrCodePath, LOG_INFO); dol_syslog("EPCQR: QR-Code erfolgreich generiert: ".$qrCodePath, LOG_INFO);

View file

@ -18,18 +18,30 @@
/** /**
* \file lib/qrcode.class.php * \file lib/qrcode.class.php
* \ingroup epcqr * \ingroup epcqr
* \brief QR-Code Generator mit lokalem Caching * \brief QR-Code Generator mit lokalem Caching und Styling
*/ */
/** /**
* QRCode Generator Klasse * QRCode Generator Klasse
* Generiert QR-Codes und cached sie lokal für Wiederverwendung * Generiert QR-Codes mit optionalen Styling-Optionen (Farben, Logo)
*/ */
class QRCodeGenerator class QRCodeGenerator
{ {
private $db; private $db;
private $cacheDir; 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 * Constructor
* *
@ -48,6 +60,70 @@ class QRCodeGenerator
if (!is_dir($this->cacheDir)) { if (!is_dir($this->cacheDir)) {
dol_mkdir($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) public function generateEPCQRCode($accountHolder, $iban, $bic, $amount, $reference)
{ {
// Eindeutigen Dateinamen generieren basierend auf Parametern // Eindeutigen Dateinamen generieren basierend auf Parametern UND Styling
$hash = md5($accountHolder.$iban.$bic.$amount.$reference); $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'; $filename = 'epc_'.$hash.'.png';
$filepath = $this->cacheDir.'/'.$filename; $filepath = $this->cacheDir.'/'.$filename;
@ -96,7 +173,8 @@ class QRCodeGenerator
public function generateQRCode($data, $prefix = 'qr') public function generateQRCode($data, $prefix = 'qr')
{ {
// Eindeutigen Dateinamen generieren // 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'; $filename = $prefix.'_'.$hash.'.png';
$filepath = $this->cacheDir.'/'.$filename; $filepath = $this->cacheDir.'/'.$filename;
@ -149,10 +227,7 @@ class QRCodeGenerator
} }
/** /**
* Generiert QR-Code-Bild aus Daten * Generiert QR-Code-Bild aus Daten mit Styling
*
* Nutzt zunächst den externen Service, später kann dies durch
* eine native PHP-Implementierung ersetzt werden
* *
* @param string $data Daten für QR-Code * @param string $data Daten für QR-Code
* @param string $filepath Zielpfad für PNG-Datei * @param string $filepath Zielpfad für PNG-Datei
@ -160,36 +235,604 @@ class QRCodeGenerator
*/ */
private function generateQRImage($data, $filepath) private function generateQRImage($data, $filepath)
{ {
// Methode 1: Externe API (aktuell) // TCPDF 2D Barcode Bibliothek laden
// TODO: Später durch native PHP-Generierung ersetzen require_once DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf_barcodes_2d.php';
$url = 'https://qr.data-it-solution.de/generate?data='.urlencode($data).'&size=300';
// Bild von URL holen try {
$imageData = @file_get_contents($url); // QR-Code mit hoher Fehlerkorrektur erstellen (H = 30% - notwendig für Logo)
$errorLevel = !empty($this->logoPath) && file_exists($this->logoPath) ? 'H' : 'M';
$barcodeobj = new TCPDF2DBarcode($data, 'QRCODE,'.$errorLevel);
// PNG-Daten generieren
$imageData = $barcodeobj->getBarcodePngData($this->pixelSize, $this->pixelSize, $this->fgColor);
if ($imageData === false) { if ($imageData === false) {
// Fallback: Versuche mit cURL dol_syslog("QRCodeGenerator: TCPDF konnte kein PNG generieren", LOG_ERR);
if (function_exists('curl_init')) { return false;
$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);
if ($httpCode !== 200 || $imageData === false) { // Bild aus String erstellen für weitere Verarbeitung
return false; $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 { } else {
return false; // 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 // Bild speichern
$result = file_put_contents($filepath, $imageData); $result = imagepng($image, $filepath);
imagedestroy($image);
return ($result !== false); return $result;
} catch (Exception $e) {
dol_syslog("QRCodeGenerator: Exception bei QR-Generierung: ".$e->getMessage(), LOG_ERR);
return false;
}
}
/**
* Wendet Hintergrundfarbe auf das Bild an
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild
*/
private function applyBackgroundColor($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Neues Bild mit Hintergrundfarbe erstellen
$newImage = imagecreatetruecolor($width, $height);
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
imagefill($newImage, 0, 0, $bgColorAlloc);
// Vordergrundfarbe einmal allokieren
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// QR-Code-Module (dunkle Pixel) kopieren
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$rgb = imagecolorat($image, $x, $y);
// Farbwerte extrahieren (funktioniert für indexed und truecolor)
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
// Helligkeit berechnen (0 = schwarz, 255 = weiß)
$brightness = ($r + $g + $b) / 3;
// Dunkle Pixel (QR-Code-Module) = Vordergrundfarbe setzen
if ($brightness < 128) {
imagesetpixel($newImage, $x, $y, $fgColorAlloc);
}
}
}
imagedestroy($image);
return $newImage;
}
/**
* Wendet transparenten Hintergrund auf das Bild an
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit Transparenz
*/
private function applyTransparentBackground($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Neues Bild mit Alpha-Kanal erstellen
$newImage = imagecreatetruecolor($width, $height);
// Alpha-Blending deaktivieren und Alpha speichern aktivieren
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
// Transparente Farbe erstellen und füllen
$transparent = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
imagefill($newImage, 0, 0, $transparent);
// Für das Zeichnen Alpha-Blending aktivieren
imagealphablending($newImage, true);
// Vordergrundfarbe einmal allokieren
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// QR-Code-Module (dunkle Pixel) erkennen und kopieren
// Verwendet Helligkeitswert statt exakte Farbprüfung für bessere Kompatibilität
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$rgb = imagecolorat($image, $x, $y);
// Farbwerte extrahieren (funktioniert für indexed und truecolor)
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
// Helligkeit berechnen (0 = schwarz, 255 = weiß)
$brightness = ($r + $g + $b) / 3;
// Dunkle Pixel (QR-Code-Module) = Helligkeit < 128
// Helle Pixel (Hintergrund) = transparent lassen
if ($brightness < 128) {
imagesetpixel($newImage, $x, $y, $fgColorAlloc);
}
}
}
// Alpha speichern nochmal sicherstellen
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
imagedestroy($image);
return $newImage;
}
/**
* Wendet Modul-Stil auf das QR-Code-Bild an (rounded, dots, diamond)
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit gestylten Modulen
*/
private function applyModuleStyle($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Modul-Matrix extrahieren (welche Module sind dunkel)
$moduleMatrix = $this->extractModuleMatrix($image);
$moduleCount = count($moduleMatrix);
if ($moduleCount == 0) {
return $image;
}
// Neue Bildgröße berechnen (etwas Padding hinzufügen)
$moduleSize = $this->pixelSize;
$newWidth = $moduleCount * $moduleSize;
$newHeight = $moduleCount * $moduleSize;
// Neues Bild erstellen
$newImage = imagecreatetruecolor($newWidth, $newHeight);
// Alpha für transparenten Hintergrund
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
$bgColorAlloc = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
} else {
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
imagefill($newImage, 0, 0, $bgColorAlloc);
if ($this->bgTransparent) {
imagealphablending($newImage, true);
}
// Vordergrundfarbe
$fgColorAlloc = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// Module zeichnen
for ($row = 0; $row < $moduleCount; $row++) {
for ($col = 0; $col < $moduleCount; $col++) {
if (!empty($moduleMatrix[$row][$col])) {
$x = $col * $moduleSize;
$y = $row * $moduleSize;
switch ($this->moduleStyle) {
case 'rounded':
$this->drawRoundedModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
case 'dots':
$this->drawDotModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
case 'diamond':
$this->drawDiamondModule($newImage, $x, $y, $moduleSize, $fgColorAlloc);
break;
default:
imagefilledrectangle($newImage, $x, $y, $x + $moduleSize - 1, $y + $moduleSize - 1, $fgColorAlloc);
}
}
}
}
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
}
imagedestroy($image);
return $newImage;
}
/**
* Extrahiert die Modul-Matrix aus dem QR-Code-Bild
*
* @param resource $image GD-Bild-Ressource
* @return array 2D-Array mit true/false für jedes Modul
*/
private function extractModuleMatrix($image)
{
$width = imagesx($image);
$height = imagesy($image);
// Anzahl der Module berechnen
$moduleCount = (int)($width / $this->pixelSize);
$matrix = array();
for ($row = 0; $row < $moduleCount; $row++) {
$matrix[$row] = array();
for ($col = 0; $col < $moduleCount; $col++) {
// Mitte des Moduls samplen
$x = ($col * $this->pixelSize) + (int)($this->pixelSize / 2);
$y = ($row * $this->pixelSize) + (int)($this->pixelSize / 2);
if ($x < $width && $y < $height) {
$rgb = imagecolorat($image, $x, $y);
if (imageistruecolor($image)) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
} else {
$colors = imagecolorsforindex($image, $rgb);
$r = $colors['red'];
$g = $colors['green'];
$b = $colors['blue'];
}
$brightness = ($r + $g + $b) / 3;
$matrix[$row][$col] = ($brightness < 128);
} else {
$matrix[$row][$col] = false;
}
}
}
return $matrix;
}
/**
* Zeichnet ein abgerundetes Modul
*/
private function drawRoundedModule($image, $x, $y, $size, $color)
{
$radius = (int)($size * 0.3); // 30% Radius für Rundung
// Gefülltes Rechteck mit abgerundeten Ecken simulieren
// Hauptrechteck (ohne Ecken)
imagefilledrectangle($image, $x + $radius, $y, $x + $size - $radius - 1, $y + $size - 1, $color);
imagefilledrectangle($image, $x, $y + $radius, $x + $size - 1, $y + $size - $radius - 1, $color);
// Ecken als gefüllte Kreise
imagefilledellipse($image, $x + $radius, $y + $radius, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $size - $radius - 1, $y + $radius, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $radius, $y + $size - $radius - 1, $radius * 2, $radius * 2, $color);
imagefilledellipse($image, $x + $size - $radius - 1, $y + $size - $radius - 1, $radius * 2, $radius * 2, $color);
}
/**
* Zeichnet ein kreisförmiges Modul (Punkt)
*/
private function drawDotModule($image, $x, $y, $size, $color)
{
$centerX = $x + (int)($size / 2);
$centerY = $y + (int)($size / 2);
$diameter = $size - 2; // Etwas kleiner für Abstand
imagefilledellipse($image, $centerX, $centerY, $diameter, $diameter, $color);
}
/**
* Zeichnet ein rautenförmiges Modul (Diamant)
*/
private function drawDiamondModule($image, $x, $y, $size, $color)
{
$centerX = $x + (int)($size / 2);
$centerY = $y + (int)($size / 2);
$half = (int)($size / 2) - 1;
$points = array(
$centerX, $y + 1, // Oben
$x + $size - 2, $centerY, // Rechts
$centerX, $y + $size - 2, // Unten
$x + 1, $centerY // Links
);
imagefilledpolygon($image, $points, $color);
}
/**
* Fügt Logo in die Mitte des QR-Codes ein
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild
*/
private function addLogo($image)
{
// Logo laden
$logoInfo = getimagesize($this->logoPath);
if ($logoInfo === false) {
dol_syslog("QRCodeGenerator: Logo konnte nicht gelesen werden: ".$this->logoPath, LOG_WARNING);
return $image;
}
$logoType = $logoInfo[2];
$logo = null;
switch ($logoType) {
case IMAGETYPE_PNG:
$logo = imagecreatefrompng($this->logoPath);
break;
case IMAGETYPE_JPEG:
$logo = imagecreatefromjpeg($this->logoPath);
break;
case IMAGETYPE_GIF:
$logo = imagecreatefromgif($this->logoPath);
break;
default:
dol_syslog("QRCodeGenerator: Nicht unterstütztes Logo-Format", LOG_WARNING);
return $image;
}
if ($logo === false) {
return $image;
}
$qrWidth = imagesx($image);
$qrHeight = imagesy($image);
$logoOrigWidth = imagesx($logo);
$logoOrigHeight = imagesy($logo);
// Logo-Größe berechnen (Prozent der QR-Code-Größe)
$logoNewWidth = (int)($qrWidth * $this->logoSize / 100);
$logoNewHeight = (int)($logoOrigHeight * ($logoNewWidth / $logoOrigWidth));
// Logo-Position (Mitte)
$logoX = (int)(($qrWidth - $logoNewWidth) / 2);
$logoY = (int)(($qrHeight - $logoNewHeight) / 2);
// Hintergrund für Logo-Bereich (mit etwas Padding)
// Nur wenn NICHT transparent - bei transparentem Hintergrund kein Rechteck zeichnen
$padding = 4;
if (!$this->bgTransparent) {
$bgColorAlloc = imagecolorallocate($image, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
imagefilledrectangle(
$image,
$logoX - $padding,
$logoY - $padding,
$logoX + $logoNewWidth + $padding,
$logoY + $logoNewHeight + $padding,
$bgColorAlloc
);
} else {
// Bei transparentem Hintergrund: Bereich mit Transparenz füllen
imagealphablending($image, false);
$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
imagefilledrectangle(
$image,
$logoX - $padding,
$logoY - $padding,
$logoX + $logoNewWidth + $padding,
$logoY + $logoNewHeight + $padding,
$transparent
);
imagealphablending($image, true);
}
// Logo skaliert einfügen
imagecopyresampled(
$image,
$logo,
$logoX,
$logoY,
0,
0,
$logoNewWidth,
$logoNewHeight,
$logoOrigWidth,
$logoOrigHeight
);
imagedestroy($logo);
return $image;
}
/**
* Fügt einen Rahmen um den QR-Code hinzu
*
* @param resource $image GD-Bild-Ressource
* @return resource Bearbeitetes Bild mit Rahmen
*/
private function addBorder($image)
{
$origWidth = imagesx($image);
$origHeight = imagesy($image);
// Neue Bildgröße mit Padding und Rahmen
$totalPadding = $this->borderPadding + $this->borderWidth;
$newWidth = $origWidth + ($totalPadding * 2);
$newHeight = $origHeight + ($totalPadding * 2);
// Neues Bild erstellen
$newImage = imagecreatetruecolor($newWidth, $newHeight);
// Alpha für transparenten Hintergrund
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
$bgColorAlloc = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
} else {
$bgColorAlloc = imagecolorallocate($newImage, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
imagefill($newImage, 0, 0, $bgColorAlloc);
if ($this->bgTransparent) {
imagealphablending($newImage, true);
}
// Rahmenfarbe (Vordergrundfarbe)
$borderColor = imagecolorallocate($newImage, $this->fgColor[0], $this->fgColor[1], $this->fgColor[2]);
// Rahmen zeichnen je nach Stil
switch ($this->borderStyle) {
case 'simple':
$this->drawSimpleBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'rounded':
$this->drawRoundedBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'double':
$this->drawDoubleBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
case 'dashed':
$this->drawDashedBorder($newImage, $newWidth, $newHeight, $borderColor);
break;
}
// Original-Bild in die Mitte kopieren
imagecopy($newImage, $image, $totalPadding, $totalPadding, 0, 0, $origWidth, $origHeight);
if ($this->bgTransparent) {
imagealphablending($newImage, false);
imagesavealpha($newImage, true);
}
imagedestroy($image);
return $newImage;
}
/**
* Zeichnet einen einfachen Rahmen
*/
private function drawSimpleBorder($image, $width, $height, $color)
{
imagesetthickness($image, $this->borderWidth);
$offset = (int)($this->borderWidth / 2);
imagerectangle($image, $offset, $offset, $width - $offset - 1, $height - $offset - 1, $color);
}
/**
* Zeichnet einen abgerundeten Rahmen
*/
private function drawRoundedBorder($image, $width, $height, $color)
{
$radius = 15;
$thickness = $this->borderWidth;
// Dicke Linien für die geraden Seiten
imagesetthickness($image, $thickness);
$offset = (int)($thickness / 2);
// Obere Linie (ohne Ecken)
imageline($image, $radius, $offset, $width - $radius, $offset, $color);
// Untere Linie
imageline($image, $radius, $height - $offset - 1, $width - $radius, $height - $offset - 1, $color);
// Linke Linie
imageline($image, $offset, $radius, $offset, $height - $radius, $color);
// Rechte Linie
imageline($image, $width - $offset - 1, $radius, $width - $offset - 1, $height - $radius, $color);
// Abgerundete Ecken als Bögen
imagesetthickness($image, $thickness);
// Oben-links
imagearc($image, $radius, $radius, $radius * 2, $radius * 2, 180, 270, $color);
// Oben-rechts
imagearc($image, $width - $radius - 1, $radius, $radius * 2, $radius * 2, 270, 360, $color);
// Unten-links
imagearc($image, $radius, $height - $radius - 1, $radius * 2, $radius * 2, 90, 180, $color);
// Unten-rechts
imagearc($image, $width - $radius - 1, $height - $radius - 1, $radius * 2, $radius * 2, 0, 90, $color);
}
/**
* Zeichnet einen doppelten Rahmen
*/
private function drawDoubleBorder($image, $width, $height, $color)
{
$gap = 4; // Abstand zwischen den Linien
imagesetthickness($image, 2);
// Äußerer Rahmen
imagerectangle($image, 1, 1, $width - 2, $height - 2, $color);
// Innerer Rahmen
imagerectangle($image, 1 + $gap, 1 + $gap, $width - 2 - $gap, $height - 2 - $gap, $color);
}
/**
* Zeichnet einen gestrichelten Rahmen
*/
private function drawDashedBorder($image, $width, $height, $color)
{
$dashLength = 8;
$gapLength = 4;
// Transparente Farbe für Lücken
if ($this->bgTransparent) {
$gapColor = imagecolorallocatealpha($image, 0, 0, 0, 127);
} else {
$gapColor = imagecolorallocate($image, $this->bgColor[0], $this->bgColor[1], $this->bgColor[2]);
}
// Strichmuster erstellen
$style = array();
for ($i = 0; $i < $dashLength; $i++) {
$style[] = $color;
}
for ($i = 0; $i < $gapLength; $i++) {
$style[] = $gapColor;
}
imagesetstyle($image, $style);
imagesetthickness($image, $this->borderWidth);
$offset = (int)($this->borderWidth / 2);
// Rahmen mit Stil zeichnen
imageline($image, $offset, $offset, $width - $offset, $offset, IMG_COLOR_STYLED);
imageline($image, $width - $offset - 1, $offset, $width - $offset - 1, $height - $offset, IMG_COLOR_STYLED);
imageline($image, $width - $offset, $height - $offset - 1, $offset, $height - $offset - 1, IMG_COLOR_STYLED);
imageline($image, $offset, $height - $offset, $offset, $offset, IMG_COLOR_STYLED);
} }
/** /**
@ -225,4 +868,14 @@ class QRCodeGenerator
dol_syslog("QRCodeGenerator: ".$deleted." alte QR-Codes gelöscht", LOG_INFO); dol_syslog("QRCodeGenerator: ".$deleted." alte QR-Codes gelöscht", LOG_INFO);
return $deleted; 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);
}
} }

View file

@ -1,3 +1,27 @@
-- Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
-- --
-- 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';

View file

@ -0,0 +1,104 @@
-- Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see https://www.gnu.org/licenses/.
--
-- EPCQR-Modul Version 2.0 - Initiale Installation
-- Erstellt alle notwendigen Extrafelder für die QR-Code-Funktionalität
--
-- ============================================
-- Extrafeld 1: qrcode (HTML-Anzeige mit <img>-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;

View file

@ -39,7 +39,6 @@ INSERT INTO llx_extrafields (
printable, printable,
langs, langs,
help, help,
computed,
entity entity
) VALUES ( ) VALUES (
'qrcodepath', 'qrcodepath',
@ -59,7 +58,6 @@ INSERT INTO llx_extrafields (
0, 0,
NULL, NULL,
'Lokaler Pfad zur QR-Code-Bilddatei für ODT-Integration', 'Lokaler Pfad zur QR-Code-Bilddatei für ODT-Integration',
'',
0 0
) )
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
@ -67,6 +65,10 @@ ON DUPLICATE KEY UPDATE
type = 'varchar', type = 'varchar',
size = '255'; 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 -- Hinweis: Die bestehenden Extrafelder 'qrcode' und 'qrcodepfad' bleiben
-- aus Kompatibilitätsgründen bestehen: -- aus Kompatibilitätsgründen bestehen:
-- - qrcode: HTML-Version mit <img>-Tag -- - qrcode: HTML-Version mit <img>-Tag

View file

@ -30,7 +30,7 @@ print '</td></tr>';
// 2. Modul-Konfiguration prüfen // 2. Modul-Konfiguration prüfen
print '<tr><td>Substitutionen aktiviert</td><td>'; print '<tr><td>Substitutionen aktiviert</td><td>';
$module_parts = $conf->modules_parts; $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 ? '<span style="color: green;">✓ Ja</span>' : '<span style="color: red;">✗ Nein (in modEpcqr.class.php aktivieren!)</span>'; print $subst_active ? '<span style="color: green;">✓ Ja</span>' : '<span style="color: red;">✗ Nein (in modEpcqr.class.php aktivieren!)</span>';
print '</td></tr>'; print '</td></tr>';
@ -196,10 +196,12 @@ print '<h2>7. Letzte Log-Einträge (EPCQR)</h2>';
$logFile = DOL_DATA_ROOT.'/dolibarr.log'; $logFile = DOL_DATA_ROOT.'/dolibarr.log';
if (file_exists($logFile)) { if (file_exists($logFile)) {
$lines = file($logFile); // Nur die letzten 500 Zeilen lesen um Memory-Probleme zu vermeiden
$epcqrLogs = array(); $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) { if (stripos($line, 'EPCQR') !== false || stripos($line, 'epcqr') !== false) {
$epcqrLogs[] = $line; $epcqrLogs[] = $line;
if (count($epcqrLogs) >= 10) break; if (count($epcqrLogs) >= 10) break;
@ -209,11 +211,11 @@ if (file_exists($logFile)) {
if (!empty($epcqrLogs)) { if (!empty($epcqrLogs)) {
print '<pre style="background: #f5f5f5; padding: 10px; border: 1px solid #ddd; overflow-x: auto;">'; print '<pre style="background: #f5f5f5; padding: 10px; border: 1px solid #ddd; overflow-x: auto;">';
foreach (array_reverse($epcqrLogs) as $log) { foreach (array_reverse($epcqrLogs) as $log) {
print htmlspecialchars($log); print htmlspecialchars($log)."\n";
} }
print '</pre>'; print '</pre>';
} else { } else {
print '<p>Keine EPCQR-Log-Einträge gefunden.</p>'; print '<p>Keine EPCQR-Log-Einträge in den letzten 500 Zeilen gefunden.</p>';
} }
} else { } else {
print '<p style="color: orange;">Log-Datei nicht gefunden: '.$logFile.'</p>'; print '<p style="color: orange;">Log-Datei nicht gefunden: '.$logFile.'</p>';