IDS Connect v2.1 - WKE + WKS mit Sonepar live getestet
- WKE-Flow (Warenkorb empfangen): Sonepar-Integration komplett funktionsfähig inkl. PriceBasis-Handling, Namespace-Stripping, OCI-Unterstützung - WKS-Flow (Warenkorb senden): Lieferantenbestellung → Shop mit vorausgefüllten Artikeln, IDS Connect 2.0 XML-Format - Callback v2.0: NOLOGIN-Seite statt Redirect, 7 Datenquellen, Debug-Daten - URL-Handling: user_base_url-Tracking für Cross-Domain-Szenarien - Sicherheit: CSRF, HMAC-SHA256 Tokens, XXE-Schutz, PIN für WKS - Mock-Server für lokale Tests - Dokumentation mit Roadmap (fehlende Features, Möglichkeiten) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
d91f9dbc9a
32 changed files with 6287 additions and 0 deletions
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Zugangsdaten / Sensible Daten
|
||||
*.eml
|
||||
*.env
|
||||
credentials*
|
||||
|
||||
# Dolibarr Build
|
||||
build/
|
||||
|
||||
# IDE / OS
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Module Builder
|
||||
modulebuilder.txt
|
||||
253
CHANGELOG.md
Normal file
253
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
# IDS Connect Modul - Changelog
|
||||
|
||||
## Projektinfo
|
||||
- **Modul**: IDS Connect für Dolibarr ERP
|
||||
- **Modul-ID**: 500025
|
||||
- **Zweck**: Punchout-Schnittstelle zu Elektrogroßhändlern (Sonepar, Kluxen)
|
||||
- **Protokoll**: IDS Connect 2.0/2.5 (Browser-basiertes Punchout)
|
||||
- **Entwickler**: Eduard Wisch / Claude AI
|
||||
|
||||
---
|
||||
|
||||
## v2.1 - WKS-Flow & URL-Handling (18.02.2026)
|
||||
|
||||
### WKS (Warenkorb senden) - Live getestet mit Sonepar
|
||||
- **WKS End-to-End**: Lieferantenbestellung in Dolibarr anlegen → IDS Connect Tab → "Warenkorb senden" → Artikel im Sonepar-Shop vorausgefüllt → "Nur bestellen"
|
||||
- **buildCartXml()**: Von altem `<Artikel>`-Format auf IDS Connect 2.0 umgestellt
|
||||
- Namespace `xmlns="http://www.itek.de/Shop-Anbindung/Warenkorb/"`
|
||||
- `<OrderItem>` statt `<Artikel>`, `<ArtNo>`, `<Kurztext>`, `<NetPrice>`
|
||||
- `PriceBasis=1` (Dolibarr-Preise sind bereits Stückpreise)
|
||||
- `VAT` aus MwSt-Satz der Bestellposition
|
||||
- **launch.php**: MwSt-Satz (`tva_tx`) und Einheit `PCE` in Warenkorb-Positionen
|
||||
- **Bestellstatus**: Setzt Lieferantenbestellung auf "Bestellt" nach WKS-Versand
|
||||
|
||||
### URL-Handling (Cross-Domain-Fix)
|
||||
- **Problem**: `$dolibarr_main_url_root` war `192.168.155.1:8090` (intern, ohne Protokoll), User greift über `awl.data-it-solution.de` zu → Callback-Links waren kaputt
|
||||
- **user_base_url-Tracking**: Speichert beim Launch die Domain des Users (`HTTP_HOST`) in den Log-Request-Daten, callback.php liest diese für Links zurück
|
||||
- **Protokoll-Prefix**: Automatische `http://`-Ergänzung wenn `://` fehlt
|
||||
- **URL-Priorität** für Callback-Links:
|
||||
1. `user_base_url` aus Log (Domain des Users beim Launch)
|
||||
2. `IDSCONNECT_PUBLIC_URL` aus Einstellungen
|
||||
3. `$dolibarr_main_url_root` als Fallback
|
||||
|
||||
### Sonepar-Shop Buttons (Erkenntnisse)
|
||||
- **"Warenkorb übergeben"** = WKE-Callback zurück an Dolibarr (für Shop-Einkauf)
|
||||
- **"Nur bestellen"** = Bestellung direkt bei Sonepar auslösen (für WKS-Flow)
|
||||
- **"Bestellen"** = beides (übergeben + bestellen)
|
||||
- Bei WKS "Nur bestellen" verwenden, da Bestellung in Dolibarr bereits existiert
|
||||
|
||||
---
|
||||
|
||||
## v2.0 - Sonepar Live-Integration (18.02.2026)
|
||||
|
||||
### Kernproblem
|
||||
Die erste Version konnte den Warenkorb von Sonepar nicht empfangen. Mehrere Probleme:
|
||||
1. `enctype="multipart/form-data"` fehlte im Launch-Formular
|
||||
2. XML-Parser erkannte das IDS Connect Format nicht
|
||||
3. Redirect nach Callback führte zum Login (Session-Problem)
|
||||
4. Preiseinheit (PriceBasis) wurde nicht berücksichtigt
|
||||
|
||||
### Sonepar XML-Format (Erkenntnisse)
|
||||
Sonepar sendet IDS XML im POST-Feld `warenkorb` mit folgender Struktur:
|
||||
```xml
|
||||
<Warenkorb xmlns="http://www.itek.de/Shop-Anbindung/Warenkorb/">
|
||||
<WarenkorbInfo>
|
||||
<Date>2026-02-18+01:00</Date>
|
||||
<Time>05:47:07+01:00</Time>
|
||||
<RueckgabeKZ>Warenkorbrückgabe</RueckgabeKZ>
|
||||
<Version>2.0</Version>
|
||||
</WarenkorbInfo>
|
||||
<Order>
|
||||
<OrderInfo>
|
||||
<ModeOfShipment>Lieferung</ModeOfShipment>
|
||||
<Cur>EUR</Cur>
|
||||
</OrderInfo>
|
||||
<DeliveryPlaceInfo>
|
||||
<Address>
|
||||
<Name1>...</Name1><Name2>...</Name2>
|
||||
<Street>...</Street><PCode>...</PCode>
|
||||
<City>...</City><Country>DE</Country>
|
||||
</Address>
|
||||
</DeliveryPlaceInfo>
|
||||
<OrderItem>
|
||||
<ArtNo>0351817</ArtNo>
|
||||
<Qty>100</Qty>
|
||||
<QU>PCE</QU>
|
||||
<Kurztext>Klauke Stossverbinder f.Massivleiter SV1525</Kurztext>
|
||||
<OfferPrice>26.8</OfferPrice>
|
||||
<NetPrice>16.88</NetPrice>
|
||||
<PriceBasis>100</PriceBasis> <!-- Preis gilt für 100 Stück! -->
|
||||
<VAT>19</VAT>
|
||||
<TechnClarification>No</TechnClarification>
|
||||
</OrderItem>
|
||||
</Order>
|
||||
</Warenkorb>
|
||||
```
|
||||
|
||||
Wichtig: Namespace `xmlns="http://www.itek.de/Shop-Anbindung/Warenkorb/"` verhindert
|
||||
direkten SimpleXML-Zugriff → Namespace-Stripping nötig.
|
||||
|
||||
### Änderungen
|
||||
|
||||
#### callback.php - Komplett-Rewrite auf v2.0
|
||||
- **Versionskennung**: `IDSCONNECT_CALLBACK_VERSION = '2.0'` zur Identifikation
|
||||
- **Kein Redirect mehr**: Zeigt Ergebnis direkt als HTML-Seite (NOLOGIN)
|
||||
- Vorher: `header('Location: idsconnectindex.php?error=...')` → Login-Problem
|
||||
- Nachher: `idsconnectCallbackPage()` rendert eigenständige HTML-Seite
|
||||
- **7 Datenquellen**: Durchsucht POST[warenkorb], POST[cart], FILES[warenkorb],
|
||||
FILES[cart], erste hochgeladene Datei, alle POST-Felder nach XML, php://input
|
||||
- **OCI-Format**: Erkennt `NEW_ITEM-*` POST-Felder (SAP OCI Punchout)
|
||||
- **Debug-Daten**: Speichert immer alle empfangenen Daten in DB (response_data JSON)
|
||||
- **Roh-XML**: Speichert XML sofort in DB, auch bei Parse-Fehler
|
||||
- **Interne Links**: Verwendet `$dolibarr_main_url_root` statt `DOL_URL_ROOT`
|
||||
für korrekte Navigation nach Callback auf öffentlicher Domain
|
||||
|
||||
#### class/idsconnect.class.php - Parser erweitert
|
||||
- **Format 5**: IDS Connect 2.5 `Warenkorb/Order/OrderItem` hinzugefügt
|
||||
- **Format 6**: Namespace-Stripping - entfernt `xmlns` und Prefixe, versucht
|
||||
alle Format-Checks erneut auf bereinigtem XML
|
||||
- **PriceBasis**: Neues Feld `PriceBasis`/`PE`/`Preiseinheit` wird geparst
|
||||
- Einzelpreis = NetPrice / PriceBasis (z.B. 16,88 / 100 = 0,1688€/Stk)
|
||||
- Angebotspreis wird analog umgerechnet
|
||||
- `preiseinheit` und `raw_netprice` werden im Item-Array mitgegeben
|
||||
- **VAT/MwSt**: MwSt-Satz wird aus `VAT`/`MwSt` geparst und als `mwst_satz` gespeichert
|
||||
- **Erweiterte Feldnamen**: ArtNo, Kurztext, Langtext, NetPrice, OfferPrice,
|
||||
ManufacturerID, Hinweis in den Mapping-Arrays ergänzt
|
||||
- **Debug-Logging**: Root-Element und Kinder werden via dol_syslog geloggt
|
||||
- **Bessere Fehlermeldung**: Zeigt Root-Element und Kinder bei "Keine Artikel gefunden"
|
||||
|
||||
#### launch.php - Formular-Fix
|
||||
- **enctype**: `enctype="multipart/form-data"` zum Launch-Formular hinzugefügt
|
||||
(fehlte, dadurch kamen keine Callback-Daten an)
|
||||
|
||||
#### cart_review.php - Preisanzeige verbessert
|
||||
- **PE-Info**: Zeigt unter dem Stückpreis die Preiseinheit-Info:
|
||||
"16,88 / 100 Stk" wenn PriceBasis > 1
|
||||
- **MwSt**: Verwendet `mwst_satz` aus XML bei Bestellerstellung (statt 0%)
|
||||
|
||||
#### mockserver.php - Realitätsnäher
|
||||
- **IDS Connect 2.5 Format**: Generiert `Warenkorb/Order/OrderItem` statt altes
|
||||
`<Artikel>` Format
|
||||
- **Namespace**: Verwendet `xmlns="http://www.itek.de/Shop-Anbindung/Warenkorb/"`
|
||||
wie Sonepar
|
||||
- **Neue Felder**: PriceBasis, VAT im generierten XML
|
||||
|
||||
#### admin/setup.php - Erweiterte Konfiguration
|
||||
- **Öffentliche URL**: Feld für IDSCONNECT_PUBLIC_URL (Reverse-Proxy)
|
||||
- **Callback-URL**: Berechnete Anzeige (read-only, klickbar zum Kopieren)
|
||||
- **Mock-Server URL**: Berechnete Anzeige (immer intern)
|
||||
- **WKS-Warnschwellen**: Mengen- und Wertgrenzen konfigurierbar
|
||||
- **WKS-PIN**: Verschlüsselte PIN für Warenkorb-Senden
|
||||
|
||||
---
|
||||
|
||||
## v1.0 - Initiale Entwicklung (17.02.2026)
|
||||
|
||||
### Grundgerüst
|
||||
- Modul-Struktur nach Dolibarr-Standard (modIdsconnect.class.php)
|
||||
- Berechtigungssystem: read, use, config, delete
|
||||
- Mehrsprachig: de_DE, en_US
|
||||
- SQL-Tabellen: `idsconnect_supplier`, `idsconnect_log`
|
||||
|
||||
### Dateien erstellt
|
||||
|
||||
| Datei | Zweck |
|
||||
|-------|-------|
|
||||
| `class/idsconnect.class.php` | Kern-Klasse: Formular-Builder, XML-Parser, XML-Generator |
|
||||
| `class/idssupplier.class.php` | Großhändler-Konfiguration (URL, Login, Passwort) |
|
||||
| `class/idslog.class.php` | Transaktionslog (CRUD, Status-Updates) |
|
||||
| `callback.php` | HOOKURL-Empfänger, NOLOGIN-Seite |
|
||||
| `launch.php` | Formular-Generator für Shop-Weiterleitung |
|
||||
| `mockserver.php` | Test-Shop für lokale Entwicklung (NOLOGIN, nur Testmodus) |
|
||||
| `idsconnectindex.php` | Hauptseite mit Großhändler-Liste und Log |
|
||||
| `cart_review.php` | Empfangenen Warenkorb prüfen, Bestellung erstellen |
|
||||
| `supplier_card.php` | Großhändler-Konfigurationsseite |
|
||||
| `supplier_list.php` | Großhändler-Liste |
|
||||
| `log_list.php` | Log-Übersicht mit Filter |
|
||||
| `log_detail.php` | Log-Detailansicht (XML, Response, Fehler) |
|
||||
| `tab_supplierorder.php` | Tab auf Lieferantenbestellungen |
|
||||
| `test_connection.php` | AJAX-Verbindungstest |
|
||||
| `admin/setup.php` | Modul-Einstellungen |
|
||||
| `admin/about.php` | Über-Seite |
|
||||
| `lib/idsconnect.lib.php` | Hilfsfunktionen, Tab-Builder |
|
||||
| `build/buildzip.php` | Modul-ZIP erstellen |
|
||||
|
||||
### IDS Connect Actions
|
||||
| Kürzel | Bedeutung | Richtung | Status |
|
||||
|--------|-----------|----------|--------|
|
||||
| WKE | Warenkorb empfangen | IN | Funktioniert (Sonepar getestet) |
|
||||
| WKS | Warenkorb senden | OUT | Funktioniert (Sonepar getestet) |
|
||||
| ADL | Artikel Deep-Link | OUT | Implementiert |
|
||||
| LI | Login-Info | OUT | Implementiert |
|
||||
| SV | Schnittstellenversion | OUT | Implementiert |
|
||||
|
||||
### Sicherheit
|
||||
- CSRF-Tokens auf allen Formularen
|
||||
- Callback-Tokens: HMAC-SHA256, 2h gültig, einmalig verwendbar
|
||||
- XXE-Schutz: `LIBXML_NONET | LIBXML_NOENT`
|
||||
- Passwörter verschlüsselt via `dolEncrypt`/`dolDecrypt`
|
||||
- Testmodus als Standard (IDSCONNECT_TESTMODE=1)
|
||||
- Live-Modus erfordert Bestätigung
|
||||
|
||||
### Sonepar-Schnittstellen
|
||||
Sonepar unterstützt 3 Schnittstellen:
|
||||
1. **IDS** - XML-basiertes Punchout (das was wir implementiert haben)
|
||||
2. **OCI** - SAP Open Catalog Interface, POST-Felder `NEW_ITEM-*` (ebenfalls implementiert)
|
||||
3. **UGL** - ÜberGabeSchnittstelleLang, FTP-basierter Dokumentenaustausch
|
||||
(Angebote, Bestellungen, Lieferscheine, Rechnungen) - nicht implementiert
|
||||
|
||||
---
|
||||
|
||||
## Architektur
|
||||
|
||||
### WKE-Flow (Warenkorb empfangen)
|
||||
```
|
||||
1. User klickt "Zum Shop" in Dolibarr
|
||||
2. launch.php generiert HTML-Formular mit:
|
||||
- Shop-URL als action
|
||||
- USERID, PASSWORT, AESSION (WKE)
|
||||
- HOOKURL (callback.php?token=...)
|
||||
3. Browser submitted Formular an Großhändler-Shop
|
||||
4. User wählt Artikel im Shop
|
||||
5. Shop sendet Warenkorb per POST an HOOKURL
|
||||
6. callback.php empfängt, parst, speichert
|
||||
7. Callback-Seite zeigt Ergebnis + Link zu cart_review.php
|
||||
8. cart_review.php: Artikel prüfen → Lieferantenbestellung erstellen
|
||||
```
|
||||
|
||||
### Preisberechnung mit PriceBasis
|
||||
Im Elektrogroßhandel werden Preise oft pro Preiseinheit (PE) angegeben:
|
||||
```
|
||||
PriceBasis=100 → NetPrice gilt für 100 Stück
|
||||
Einzelpreis pro Stück = NetPrice / PriceBasis
|
||||
Gesamtpreis = Qty * (NetPrice / PriceBasis)
|
||||
|
||||
Beispiel: 100 Stk Stoßverbinder
|
||||
NetPrice=16.88, PriceBasis=100
|
||||
→ Stückpreis = 16.88 / 100 = 0.1688€
|
||||
→ Gesamt = 100 * 0.1688 = 16.88€
|
||||
```
|
||||
|
||||
### Datenbank-Tabellen
|
||||
- `llx_idsconnect_supplier` - Großhändler mit URL, Login, Passwort (verschlüsselt)
|
||||
- `llx_idsconnect_log` - Transaktionslog mit Status, XML, Response-JSON, Token
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### Dolibarr-Einstellungen (Schlüssel)
|
||||
| Schlüssel | Beschreibung | Standard |
|
||||
|-----------|-------------|----------|
|
||||
| IDSCONNECT_TESTMODE | Testmodus aktiv | 1 |
|
||||
| IDSCONNECT_PUBLIC_URL | Öffentliche URL für Callback | (leer) |
|
||||
| IDSCONNECT_LOG_ENABLED | Logging aktiviert | 1 |
|
||||
| IDSCONNECT_WKS_WARN_QTY | WKS Warn-Menge | 0 |
|
||||
| IDSCONNECT_WKS_WARN_VALUE | WKS Warn-Wert | 0 |
|
||||
| IDSCONNECT_WKS_PIN | PIN für WKS (verschlüsselt) | (leer) |
|
||||
|
||||
### Sonepar-Konfiguration
|
||||
- URL: `https://www.sonepar.de/punchout/v1/ids/setup?OrganizationId=12`
|
||||
- Benutzer: ids
|
||||
- Kundennummer: 100184
|
||||
- IDS-Version: 2.0
|
||||
621
COPYING
Executable file
621
COPYING
Executable file
|
|
@ -0,0 +1,621 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
16
ChangeLog.md
Executable file
16
ChangeLog.md
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
# CHANGELOG MODULE IDSCONNECT FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||
|
||||
## 2.1 (18.02.2026)
|
||||
- WKS-Flow live getestet mit Sonepar (Warenkorb senden)
|
||||
- buildCartXml auf IDS Connect 2.0 Format umgestellt
|
||||
- Cross-Domain URL-Handling (user_base_url Tracking)
|
||||
|
||||
## 2.0 (18.02.2026)
|
||||
- Sonepar Live-Integration (WKE-Flow)
|
||||
- PriceBasis-Handling für Preiseinheiten
|
||||
- Callback v2.0 (NOLOGIN-Seite statt Redirect)
|
||||
- OCI-Format Unterstützung
|
||||
- Namespace-Stripping für XML-Parser
|
||||
|
||||
## 1.0 (17.02.2026)
|
||||
- Initiale Version
|
||||
136
README.md
Executable file
136
README.md
Executable file
|
|
@ -0,0 +1,136 @@
|
|||
# IDS Connect für Dolibarr ERP
|
||||
|
||||
Dolibarr-Modul für die **IDS Connect Punchout-Schnittstelle** zu Elektrogroßhändlern.
|
||||
Ermöglicht den direkten Wareneinkauf aus Dolibarr heraus über den Shop des Großhändlers.
|
||||
|
||||
## Status
|
||||
|
||||
| Großhändler | WKE (empfangen) | WKS (senden) | Status |
|
||||
|-------------|-----------------|---------------|--------|
|
||||
| **Sonepar** | Funktioniert | Funktioniert | Live getestet |
|
||||
| **Kluxen** | - | - | Shop-URL fehlt noch |
|
||||
|
||||
## Funktionsübersicht
|
||||
|
||||
### Implementiert und getestet
|
||||
|
||||
- **WKE (Warenkorb empfangen)**: Shop öffnen → Artikel auswählen → Warenkorb kommt automatisch in Dolibarr an → Lieferantenbestellung erstellen
|
||||
- **WKS (Warenkorb senden)**: Lieferantenbestellung in Dolibarr anlegen → per Klick an Shop senden → Artikel sind vorausgefüllt → "Nur bestellen" im Shop
|
||||
- **Callback-System**: NOLOGIN-Seite empfängt XML/OCI-Daten, zeigt Ergebnis direkt an
|
||||
- **PriceBasis-Handling**: Automatische Umrechnung von Preiseinheiten (z.B. Preis/100 Stk → Stückpreis)
|
||||
- **Bestell-Review**: Empfangene Artikel prüfen, Preise kontrollieren, Lieferantenbestellung mit einem Klick erstellen
|
||||
- **Tab auf Lieferantenbestellungen**: Direkter Zugriff auf "Zum Shop" und "Warenkorb senden" pro Bestellung
|
||||
- **Mock-Server**: Lokaler Test-Shop für Entwicklung ohne echte Großhändler-Anbindung
|
||||
- **OCI-Format**: SAP Open Catalog Interface (`NEW_ITEM-*` POST-Felder) wird ebenfalls erkannt
|
||||
- **Transaktionslog**: Alle IDS-Aktionen werden protokolliert mit XML, Status, Fehlermeldungen
|
||||
|
||||
### Implementiert, nicht getestet
|
||||
|
||||
- **ADL (Artikel Deep-Link)**: Direkt zu einem Artikel im Shop springen
|
||||
- **LI (Login-Info)**: Login-Informationen abfragen
|
||||
- **SV (Schnittstellenversion)**: IDS Connect Version des Shops abfragen
|
||||
|
||||
## Was noch fehlt
|
||||
|
||||
### Kurzfristig (nächste Schritte)
|
||||
|
||||
- [ ] **Kluxen-Anbindung**: Shop-URL und Zugangsdaten von Kluxen erfragen und einrichten
|
||||
- [ ] **Bestellabgleich**: Nach WKS prüfen ob Sonepar eine Bestellbestätigung zurücksendet (Auftragsbestätigungsnummer)
|
||||
- [ ] **Sonepar WKS-Verarbeitung klären**: Wie schnell erscheint eine WKS-Bestellung in der Sonepar-Bestellverwaltung? Muss WKS separat freigeschaltet werden?
|
||||
- [ ] **Lieferantenbestellung ↔ Log-Verknüpfung**: Bei WKS den Log-Eintrag automatisch mit der Dolibarr-Bestellung verknüpfen (`fk_commande`)
|
||||
- [ ] **Fehlende Produkt-Zuordnung**: Empfangene Artikel (Lieferanten-Artikelnr) automatisch mit Dolibarr-Produkten matchen
|
||||
|
||||
### Mittelfristig (sinnvolle Erweiterungen)
|
||||
|
||||
- [ ] **Automatischer Wareneingang**: Nach Lieferung den Bestellstatus in Dolibarr aktualisieren
|
||||
- [ ] **Preisvergleich**: Bei WKE die empfangenen Preise mit den hinterlegten Lieferantenpreisen in Dolibarr vergleichen und Abweichungen anzeigen
|
||||
- [ ] **Mehrere Lieferanten gleichzeitig**: Warenkorb aufteilen auf verschiedene Großhändler (z.B. Kluxen + Sonepar) basierend auf Verfügbarkeit/Preis
|
||||
- [ ] **Wiederkehrende Bestellungen**: Standardwarenkörbe speichern und per Klick wiederholt bestellen
|
||||
- [ ] **Lieferstatus-Tracking**: Wenn Großhändler Tracking-Infos per IDS/OCI zurücksenden
|
||||
|
||||
### Langfristig (über IDS Connect hinaus)
|
||||
|
||||
- [ ] **UGL-Schnittstelle (Sonepar)**: FTP-basierter Dokumentenaustausch für Angebote, Bestellungen, Lieferscheine, Rechnungen. Vollautomatischer Belegabgleich ohne Shop-Interaktion
|
||||
- [ ] **Open Masterdata (OMD)**: Stammdaten-Integration - Artikelkataloge, Preislisten, Verfügbarkeiten direkt in Dolibarr importieren
|
||||
- [ ] **ETIM-Klassifizierung**: Artikel nach ETIM-Standard klassifizieren für bessere Produktsuche und -vergleich
|
||||
- [ ] **Automatische Rechnungserfassung**: Eingangsrechnungen von Großhändlern per UGL/EDI empfangen und automatisch als Lieferantenrechnung in Dolibarr anlegen
|
||||
- [ ] **Datanorm-Import**: Artikelstammdaten und Preise aus Datanorm-Dateien importieren (Datanorm 4/5)
|
||||
|
||||
## Installation
|
||||
|
||||
### Voraussetzungen
|
||||
- Dolibarr ERP >= 16.0
|
||||
- PHP >= 7.4 mit SimpleXML, DOM
|
||||
- IDS Connect Zugangsdaten vom Großhändler
|
||||
|
||||
### Einrichtung
|
||||
|
||||
1. Modul-Verzeichnis nach `custom/idsconnect` kopieren
|
||||
2. In Dolibarr: Setup → Module → "IDS Connect" aktivieren
|
||||
3. IDS Connect → Einstellungen:
|
||||
- **Öffentliche URL** setzen (die URL unter der Dolibarr von außen erreichbar ist, z.B. `https://erp.meinedomain.de`)
|
||||
- **Testmodus** ist standardmäßig aktiv
|
||||
4. IDS Connect → Großhändler → Neuen Großhändler anlegen:
|
||||
- Shop-URL, Benutzername, Passwort, IDS-Version
|
||||
- Mit einem Dolibarr-Lieferanten verknüpfen
|
||||
|
||||
### Sonepar-Konfiguration
|
||||
|
||||
- **URL**: `https://www.sonepar.de/punchout/v1/ids/setup?OrganizationId=12`
|
||||
- **IDS-Version**: 2.0
|
||||
- **Kontakt**: digitalsupport@sonepar.de, +49 211 44744 920
|
||||
- Sonepar unterstützt: IDS, OCI, UGL
|
||||
|
||||
## Architektur
|
||||
|
||||
### WKE-Flow (Warenkorb empfangen)
|
||||
```
|
||||
Dolibarr → launch.php → HTML-Formular → Großhändler-Shop
|
||||
↓
|
||||
User wählt Artikel
|
||||
↓
|
||||
Shop sendet POST an callback.php
|
||||
↓
|
||||
callback.php zeigt Ergebnis
|
||||
↓
|
||||
cart_review.php → Lieferantenbestellung
|
||||
```
|
||||
|
||||
### WKS-Flow (Warenkorb senden)
|
||||
```
|
||||
Dolibarr → Lieferantenbestellung anlegen
|
||||
↓
|
||||
IDS Connect Tab → "Warenkorb senden"
|
||||
↓
|
||||
launch.php → XML mit Artikeln generieren → Shop
|
||||
↓
|
||||
Artikel im Shop vorausgefüllt → "Nur bestellen"
|
||||
```
|
||||
|
||||
### Sicherheit
|
||||
- CSRF-Tokens auf allen Formularen
|
||||
- Callback-Tokens: HMAC-SHA256, 2h gültig, einmalig verwendbar
|
||||
- XXE-Schutz beim XML-Parsing
|
||||
- Passwörter verschlüsselt via `dolEncrypt`/`dolDecrypt`
|
||||
- Testmodus als Standard
|
||||
- Live-Modus erfordert extra Bestätigung
|
||||
- WKS optional mit PIN-Schutz
|
||||
|
||||
## Dateien
|
||||
|
||||
| Datei | Zweck |
|
||||
|-------|-------|
|
||||
| `class/idsconnect.class.php` | Kern-Klasse: Formular-Builder, XML-Parser, XML-Generator |
|
||||
| `class/idssupplier.class.php` | Großhändler-Konfiguration (URL, Login, Passwort) |
|
||||
| `class/idslog.class.php` | Transaktionslog (CRUD, Status-Updates) |
|
||||
| `callback.php` | HOOKURL-Empfänger (NOLOGIN) |
|
||||
| `launch.php` | Formular-Generator für Shop-Weiterleitung |
|
||||
| `mockserver.php` | Test-Shop (NOLOGIN, nur Testmodus) |
|
||||
| `cart_review.php` | Empfangenen Warenkorb prüfen, Bestellung erstellen |
|
||||
| `tab_supplierorder.php` | IDS Connect Tab auf Lieferantenbestellungen |
|
||||
| `log_list.php` / `log_detail.php` | Transaktionslog |
|
||||
| `admin/setup.php` | Modul-Einstellungen |
|
||||
|
||||
## Lizenz
|
||||
|
||||
GPLv3 oder höher. Siehe COPYING.
|
||||
118
admin/about.php
Executable file
118
admin/about.php
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
/* Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||
* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/admin/about.php
|
||||
* \ingroup idsconnect
|
||||
* \brief About page of module Idsconnect.
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
|
||||
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
|
||||
}
|
||||
// Try main.inc.php using relative path
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../../main.inc.php")) {
|
||||
$res = @include "../../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
// Libraries
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
|
||||
require_once '../lib/idsconnect.lib.php';
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var HookManager $hookmanager
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
// Translations
|
||||
$langs->loadLangs(array("errors", "admin", "idsconnect@idsconnect"));
|
||||
|
||||
// Access control
|
||||
if (!$user->admin) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Parameters
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$backtopage = GETPOST('backtopage', 'alpha');
|
||||
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
// None
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$form = new Form($db);
|
||||
|
||||
$help_url = '';
|
||||
$title = "IdsconnectSetup";
|
||||
|
||||
llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-idsconnect page-admin_about');
|
||||
|
||||
// Subheader
|
||||
$linkback = '<a href="'.($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1').'">'.$langs->trans("BackToModuleList").'</a>';
|
||||
|
||||
print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
|
||||
|
||||
// Configuration header
|
||||
$head = idsconnectAdminPrepareHead();
|
||||
print dol_get_fiche_head($head, 'about', $langs->trans($title), 0, 'idsconnect@idsconnect');
|
||||
|
||||
dol_include_once('/idsconnect/core/modules/modIdsconnect.class.php');
|
||||
$tmpmodule = new modIdsconnect($db);
|
||||
print $tmpmodule->getDescLong();
|
||||
|
||||
// Page end
|
||||
print dol_get_fiche_end();
|
||||
llxFooter();
|
||||
$db->close();
|
||||
235
admin/setup.php
Executable file
235
admin/setup.php
Executable file
|
|
@ -0,0 +1,235 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/admin/setup.php
|
||||
* \ingroup idsconnect
|
||||
* \brief IDS Connect Konfigurationsseite
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../../main.inc.php")) {
|
||||
$res = @include "../../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
|
||||
require_once '../lib/idsconnect.lib.php';
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var HookManager $hookmanager
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("admin", "idsconnect@idsconnect"));
|
||||
|
||||
// Zugriffskontrolle
|
||||
if (!$user->admin) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$backtopage = GETPOST('backtopage', 'alpha');
|
||||
|
||||
$error = 0;
|
||||
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
if ($action == 'update' && $user->admin) {
|
||||
// CSRF-Schutz
|
||||
if (!verifCond(GETPOST('token', 'alpha') == newToken())) {
|
||||
accessforbidden('Bad CSRF token');
|
||||
}
|
||||
|
||||
// Öffentliche URL
|
||||
dolibarr_set_const($db, 'IDSCONNECT_PUBLIC_URL', trim(GETPOST('IDSCONNECT_PUBLIC_URL', 'alphanohtml')), 'chaine', 0, '', $conf->entity);
|
||||
// Testmodus
|
||||
dolibarr_set_const($db, 'IDSCONNECT_TESTMODE', GETPOSTINT('IDSCONNECT_TESTMODE'), 'chaine', 0, '', $conf->entity);
|
||||
// Logging
|
||||
dolibarr_set_const($db, 'IDSCONNECT_LOG_ENABLED', GETPOSTINT('IDSCONNECT_LOG_ENABLED'), 'chaine', 0, '', $conf->entity);
|
||||
// WKS-Warnschwellen
|
||||
dolibarr_set_const($db, 'IDSCONNECT_WKS_WARN_QTY', GETPOSTINT('IDSCONNECT_WKS_WARN_QTY'), 'chaine', 0, '', $conf->entity);
|
||||
dolibarr_set_const($db, 'IDSCONNECT_WKS_WARN_VALUE', GETPOST('IDSCONNECT_WKS_WARN_VALUE', 'alphanohtml'), 'chaine', 0, '', $conf->entity);
|
||||
// WKS-PIN (nur speichern wenn neue eingegeben)
|
||||
$new_pin = GETPOST('IDSCONNECT_WKS_PIN', 'none');
|
||||
if (!empty($new_pin)) {
|
||||
$pin_hash = password_hash($new_pin, PASSWORD_DEFAULT);
|
||||
dolibarr_set_const($db, 'IDSCONNECT_WKS_PIN', $pin_hash, 'chaine', 0, '', $conf->entity);
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
|
||||
}
|
||||
|
||||
$action = 'edit';
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$form = new Form($db);
|
||||
$title = "IdsconnectSetup";
|
||||
|
||||
llxHeader('', $langs->trans($title), '', '', 0, 0, '', '', '', 'mod-idsconnect page-admin');
|
||||
|
||||
$linkback = '<a href="'.($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1').'">'.$langs->trans("BackToModuleList").'</a>';
|
||||
print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
|
||||
|
||||
$head = idsconnectAdminPrepareHead();
|
||||
print dol_get_fiche_head($head, 'settings', $langs->trans($title), -1, "fa-plug");
|
||||
|
||||
print '<span class="opacitymedium">'.$langs->trans("IdsconnectSetupPage").'</span><br><br>';
|
||||
|
||||
|
||||
// Formular
|
||||
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="update">';
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
|
||||
// Überschrift: Allgemein
|
||||
print '<tr class="liste_titre">';
|
||||
print '<td colspan="2">'.$langs->trans("IdsconnectGeneralSettings").'</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Öffentliche URL (für HOOKURL bei Reverse-Proxy)
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("IdsconnectPublicUrl").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="text" name="IDSCONNECT_PUBLIC_URL" class="minwidth500" value="'.htmlspecialchars(getDolGlobalString('IDSCONNECT_PUBLIC_URL')).'" placeholder="https://awl.data-it-solution.de">';
|
||||
print '<br><span class="opacitymedium">'.$langs->trans("IdsconnectPublicUrlHelp").'</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
// Testmodus
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("IdsconnectTestMode").'</td>';
|
||||
print '<td>';
|
||||
print $form->selectyesno('IDSCONNECT_TESTMODE', getDolGlobalInt('IDSCONNECT_TESTMODE', 1), 1);
|
||||
print ' <span class="opacitymedium">'.$langs->trans("IdsconnectTestModeHelp").'</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
// Logging
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("IdsconnectLogEnabled").'</td>';
|
||||
print '<td>';
|
||||
print $form->selectyesno('IDSCONNECT_LOG_ENABLED', getDolGlobalInt('IDSCONNECT_LOG_ENABLED', 1), 1);
|
||||
print '</td></tr>';
|
||||
|
||||
// Überschrift: WKS-Sicherheit
|
||||
print '<tr class="liste_titre">';
|
||||
print '<td colspan="2">'.$langs->trans("IdsconnectWksSecuritySettings").'</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Mengen-Warnschwelle
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("IdsconnectWksWarnQty").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="number" name="IDSCONNECT_WKS_WARN_QTY" value="'.getDolGlobalInt('IDSCONNECT_WKS_WARN_QTY', 100).'" min="0" class="width100">';
|
||||
print ' <span class="opacitymedium">'.$langs->trans("IdsconnectWksWarnQtyHelp").'</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
// Wert-Warnschwelle
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("IdsconnectWksWarnValueLabel").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="number" name="IDSCONNECT_WKS_WARN_VALUE" value="'.getDolGlobalString('IDSCONNECT_WKS_WARN_VALUE', '10000').'" min="0" step="100" class="width100">';
|
||||
print ' € <span class="opacitymedium">'.$langs->trans("IdsconnectWksWarnValueHelp").'</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
// WKS-PIN
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("IdsconnectWksPinLabel").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="password" name="IDSCONNECT_WKS_PIN" value="" class="width100" autocomplete="new-password" placeholder="'.$langs->trans("IdsconnectWksPinPlaceholder").'">';
|
||||
$has_pin = !empty(getDolGlobalString('IDSCONNECT_WKS_PIN'));
|
||||
if ($has_pin) {
|
||||
print ' <span class="badge badge-status4">'.$langs->trans("IdsconnectWksPinSet").'</span>';
|
||||
} else {
|
||||
print ' <span class="badge badge-status8">'.$langs->trans("IdsconnectWksPinNotSet").'</span>';
|
||||
}
|
||||
print ' <span class="opacitymedium">'.$langs->trans("IdsconnectWksPinHelp").'</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
// Callback-URL anzeigen (nur lesen, berechnet aus öffentlicher URL)
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("IdsconnectCallbackUrl").'</td>';
|
||||
print '<td>';
|
||||
global $dolibarr_main_url_root;
|
||||
$public_url = getDolGlobalString('IDSCONNECT_PUBLIC_URL');
|
||||
$url_base = !empty($public_url) ? rtrim($public_url, '/') : $dolibarr_main_url_root;
|
||||
$callback_url = $url_base.'/custom/idsconnect/callback.php';
|
||||
print '<input type="text" class="minwidth500" value="'.htmlspecialchars($callback_url).'" readonly onclick="this.select()">';
|
||||
if (empty($public_url)) {
|
||||
print ' <span class="badge badge-warning">Intern - '.$langs->trans("IdsconnectPublicUrlMissing").'</span>';
|
||||
}
|
||||
print '<br><span class="opacitymedium">'.$langs->trans("IdsconnectCallbackUrlHelp").'</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
// Mock-Server URL anzeigen (nur lesen, immer intern)
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("IdsconnectMockServerUrl").'</td>';
|
||||
print '<td>';
|
||||
$mock_url = $dolibarr_main_url_root.'/custom/idsconnect/mockserver.php';
|
||||
print '<input type="text" class="minwidth500" value="'.htmlspecialchars($mock_url).'" readonly onclick="this.select()">';
|
||||
print ' <span class="opacitymedium">'.$langs->trans("IdsconnectMockServerHelp").'</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
print '<div class="center">';
|
||||
print '<input type="submit" class="button" value="'.$langs->trans("Save").'">';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
|
||||
|
||||
// Sicherheitsinfo
|
||||
print '<br>';
|
||||
print '<div class="info">';
|
||||
print '<strong>'.$langs->trans("IdsconnectSecurityInfo").'</strong><br>';
|
||||
print $langs->trans("IdsconnectSecurityInfoText");
|
||||
print '</div>';
|
||||
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
474
callback.php
Normal file
474
callback.php
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/callback.php
|
||||
* \ingroup idsconnect
|
||||
* \brief HOOKURL-Callback-Handler - empfängt Warenkörbe vom Großhandels-Shop
|
||||
*/
|
||||
|
||||
// Version zur Überprüfung ob der richtige Code deployed ist
|
||||
define('IDSCONNECT_CALLBACK_VERSION', '2.0');
|
||||
|
||||
// Dolibarr laden (ohne Login-Erfordernis für Callback)
|
||||
define('NOLOGIN', 1);
|
||||
define('NOCSRFCHECK', 1);
|
||||
define('NOREQUIREMENU', 1);
|
||||
define('NOREQUIREHTML', 1);
|
||||
define('NOREQUIREAJAX', 1);
|
||||
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
http_response_code(500);
|
||||
die("Server configuration error");
|
||||
}
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
|
||||
dol_include_once('/idsconnect/class/idsconnect.class.php');
|
||||
dol_include_once('/idsconnect/class/idslog.class.php');
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
|
||||
/**
|
||||
* @var DoliDB $db
|
||||
*/
|
||||
|
||||
// Callback-Eingang loggen (alles erfassen für Debugging)
|
||||
$callback_meta = array(
|
||||
'callback_version' => IDSCONNECT_CALLBACK_VERSION,
|
||||
'method' => $_SERVER['REQUEST_METHOD'],
|
||||
'remote_ip' => getUserRemoteIP(),
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unbekannt',
|
||||
'content_type' => $_SERVER['CONTENT_TYPE'] ?? 'unbekannt',
|
||||
'content_length' => $_SERVER['CONTENT_LENGTH'] ?? 0,
|
||||
'query_string' => $_SERVER['QUERY_STRING'] ?? '',
|
||||
'post_keys' => array_keys($_POST),
|
||||
'files_keys' => array_keys($_FILES),
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
);
|
||||
dol_syslog("IDS Connect Callback v".IDSCONNECT_CALLBACK_VERSION.": Eingang von ".$callback_meta['remote_ip']." Method=".$callback_meta['method']." POST-Keys=".implode(',', $callback_meta['post_keys'])." FILES-Keys=".implode(',', $callback_meta['files_keys']), LOG_INFO);
|
||||
|
||||
// Nur POST und GET erlauben
|
||||
if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'GET'))) {
|
||||
dol_syslog("IDS Connect Callback: Method Not Allowed: ".$_SERVER['REQUEST_METHOD'], LOG_WARNING);
|
||||
http_response_code(405);
|
||||
die("Method not allowed");
|
||||
}
|
||||
|
||||
// Token aus GET oder POST
|
||||
$token = GETPOST('token', 'alphanohtml');
|
||||
|
||||
if (empty($token)) {
|
||||
dol_syslog("IDS Connect Callback: Kein Token - POST-Daten: ".json_encode(array_keys($_POST)), LOG_WARNING);
|
||||
http_response_code(400);
|
||||
die("Missing token - Callback v".IDSCONNECT_CALLBACK_VERSION);
|
||||
}
|
||||
|
||||
// Token verifizieren
|
||||
$idsconnect = new IdsConnect($db);
|
||||
$log = $idsconnect->verifyCallbackToken($token);
|
||||
|
||||
if ($log === false) {
|
||||
dol_syslog("IDS Connect Callback: Ungültiger oder abgelaufener Token: ".$token." von IP: ".getUserRemoteIP(), LOG_WARNING);
|
||||
http_response_code(403);
|
||||
die("Invalid or expired token - Callback v".IDSCONNECT_CALLBACK_VERSION);
|
||||
}
|
||||
|
||||
dol_syslog("IDS Connect Callback: Token gültig - LogID=".$log->id." Supplier=".$log->fk_supplier." Action=".$log->action_type, LOG_INFO);
|
||||
|
||||
// ============================================================
|
||||
// Warenkorb-XML aus verschiedenen Quellen lesen
|
||||
// ============================================================
|
||||
$cart_xml = '';
|
||||
$cart_source = '';
|
||||
|
||||
// 1. Reguläres POST-Feld 'warenkorb' (IDS Connect Standard)
|
||||
if (!empty($_POST['warenkorb'])) {
|
||||
$cart_xml = $_POST['warenkorb'];
|
||||
$cart_source = 'POST[warenkorb]';
|
||||
}
|
||||
// 2. POST-Feld 'cart' (alternativer Name)
|
||||
elseif (!empty($_POST['cart'])) {
|
||||
$cart_xml = $_POST['cart'];
|
||||
$cart_source = 'POST[cart]';
|
||||
}
|
||||
// 3. Datei-Upload 'warenkorb' (bei multipart/form-data möglich)
|
||||
elseif (!empty($_FILES['warenkorb']['tmp_name']) && is_uploaded_file($_FILES['warenkorb']['tmp_name'])) {
|
||||
$cart_xml = file_get_contents($_FILES['warenkorb']['tmp_name']);
|
||||
$cart_source = 'FILES[warenkorb] ('.$_FILES['warenkorb']['name'].' '.$_FILES['warenkorb']['size'].' bytes)';
|
||||
}
|
||||
// 4. Datei-Upload 'cart'
|
||||
elseif (!empty($_FILES['cart']['tmp_name']) && is_uploaded_file($_FILES['cart']['tmp_name'])) {
|
||||
$cart_xml = file_get_contents($_FILES['cart']['tmp_name']);
|
||||
$cart_source = 'FILES[cart] ('.$_FILES['cart']['name'].' '.$_FILES['cart']['size'].' bytes)';
|
||||
}
|
||||
// 5. Erster verfügbarer Datei-Upload
|
||||
elseif (!empty($_FILES)) {
|
||||
$first_file = reset($_FILES);
|
||||
$first_key = key($_FILES);
|
||||
if (!empty($first_file['tmp_name']) && is_uploaded_file($first_file['tmp_name'])) {
|
||||
$cart_xml = file_get_contents($first_file['tmp_name']);
|
||||
$cart_source = 'FILES['.$first_key.'] ('.$first_file['name'].' '.$first_file['size'].' bytes)';
|
||||
}
|
||||
}
|
||||
// 6. Erster POST-Wert der XML enthält
|
||||
if (empty($cart_xml)) {
|
||||
foreach ($_POST as $key => $value) {
|
||||
if (is_string($value) && (strpos($value, '<?xml') !== false || strpos($value, '<Warenkorb') !== false || strpos($value, '<Order') !== false)) {
|
||||
$cart_xml = $value;
|
||||
$cart_source = 'POST['.$key.'] (XML-Suche)';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 7. Raw POST body als Fallback
|
||||
if (empty($cart_xml)) {
|
||||
$raw = file_get_contents('php://input');
|
||||
if (!empty($raw)) {
|
||||
if (strpos($raw, '<?xml') !== false || strpos($raw, '<Warenkorb') !== false) {
|
||||
$cart_xml = $raw;
|
||||
$cart_source = 'php://input ('.strlen($raw).' bytes)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$callback_meta['cart_source'] = $cart_source;
|
||||
|
||||
dol_syslog("IDS Connect Callback: Cart-Source=".$cart_source." Cart-Länge=".strlen($cart_xml)." POST-Felder=".implode(',', array_keys($_POST))." FILES-Felder=".implode(',', array_keys($_FILES)), LOG_INFO);
|
||||
|
||||
// ============================================================
|
||||
// Debug-Daten IMMER in DB speichern (egal was passiert)
|
||||
// ============================================================
|
||||
$debug_data = array(
|
||||
'callback_version' => IDSCONNECT_CALLBACK_VERSION,
|
||||
'callback_meta' => $callback_meta,
|
||||
'cart_source' => $cart_source,
|
||||
'cart_xml_length' => strlen($cart_xml),
|
||||
'post_keys' => array_keys($_POST),
|
||||
'post_values_preview' => array(),
|
||||
'files_info' => array(),
|
||||
);
|
||||
// POST-Werte gekürzt speichern
|
||||
foreach ($_POST as $k => $v) {
|
||||
if ($k === 'pw_kunde') {
|
||||
$debug_data['post_values_preview'][$k] = '***';
|
||||
} elseif (is_string($v)) {
|
||||
$debug_data['post_values_preview'][$k] = substr($v, 0, 500).(strlen($v) > 500 ? '...['.strlen($v).' bytes]' : '');
|
||||
} else {
|
||||
$debug_data['post_values_preview'][$k] = gettype($v);
|
||||
}
|
||||
}
|
||||
// FILES-Info speichern
|
||||
foreach ($_FILES as $k => $f) {
|
||||
$debug_data['files_info'][$k] = array(
|
||||
'name' => $f['name'] ?? '',
|
||||
'type' => $f['type'] ?? '',
|
||||
'size' => $f['size'] ?? 0,
|
||||
'error' => $f['error'] ?? -1,
|
||||
);
|
||||
}
|
||||
|
||||
// Roh-XML immer speichern wenn vorhanden
|
||||
if (!empty($cart_xml)) {
|
||||
$log->updateCart($cart_xml);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// OCI-Format prüfen (NEW_ITEM-* POST-Felder)
|
||||
// ============================================================
|
||||
$oci_items = array();
|
||||
$has_oci = false;
|
||||
foreach ($_POST as $key => $value) {
|
||||
if (strpos($key, 'NEW_ITEM-') === 0) {
|
||||
$has_oci = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($has_oci) {
|
||||
dol_syslog("IDS Connect Callback: OCI-Format erkannt (NEW_ITEM-* Felder)", LOG_INFO);
|
||||
$cart_source = 'OCI (POST NEW_ITEM-* Felder)';
|
||||
$callback_meta['cart_source'] = $cart_source;
|
||||
$debug_data['cart_source'] = $cart_source;
|
||||
$debug_data['format'] = 'OCI';
|
||||
|
||||
// OCI-Felder parsen: NEW_ITEM-FELDNAME[index] = wert
|
||||
$oci_raw = array();
|
||||
foreach ($_POST as $key => $value) {
|
||||
if (preg_match('/^NEW_ITEM-([A-Z_]+)\[?(\d*)\]?$/', $key, $m)) {
|
||||
$field = $m[1];
|
||||
$idx = $m[2] !== '' ? (int) $m[2] : 1;
|
||||
$oci_raw[$idx][$field] = $value;
|
||||
}
|
||||
// Alternatives Format: NEW_ITEM-FELDNAME ohne Index
|
||||
elseif (preg_match('/^NEW_ITEM-([A-Z_]+)$/', $key, $m) && is_array($value)) {
|
||||
foreach ($value as $idx => $val) {
|
||||
$oci_raw[$idx][$m[1]] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($oci_raw as $idx => $fields) {
|
||||
$item = array(
|
||||
'artikelnr' => $fields['VENDORMAT'] ?? $fields['MATNR'] ?? $fields['EXT_PRODUCT_ID'] ?? '',
|
||||
'bezeichnung' => $fields['DESCRIPTION'] ?? $fields['LONGTEXT'] ?? '',
|
||||
'langtext' => $fields['LONGTEXT'] ?? '',
|
||||
'menge' => (float) ($fields['QUANTITY'] ?? 0),
|
||||
'einheit' => $fields['UNIT'] ?? 'STK',
|
||||
'einzelpreis' => (float) ($fields['PRICE'] ?? 0),
|
||||
'angebotspreis' => 0,
|
||||
'gesamtpreis' => 0,
|
||||
'ean' => $fields['EAN'] ?? '',
|
||||
'hersteller' => $fields['MANUFACTMAT'] ?? $fields['VENDOR'] ?? '',
|
||||
'herstellernr' => $fields['MANUFACTMAT'] ?? '',
|
||||
'hinweis' => '',
|
||||
);
|
||||
if ($item['menge'] > 0 && $item['einzelpreis'] > 0) {
|
||||
$item['gesamtpreis'] = $item['menge'] * $item['einzelpreis'];
|
||||
}
|
||||
if (!empty($item['artikelnr']) || !empty($item['bezeichnung'])) {
|
||||
$oci_items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Verarbeitung
|
||||
// ============================================================
|
||||
|
||||
// OCI-Artikel haben Vorrang wenn gefunden
|
||||
if (!empty($oci_items)) {
|
||||
$items = $oci_items;
|
||||
// Als "XML" für die DB speichern (OCI-Rohdaten als JSON)
|
||||
$oci_log = json_encode($_POST, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
$log->updateCart($oci_log);
|
||||
$debug_data['result'] = 'success';
|
||||
$debug_data['format'] = 'OCI';
|
||||
$debug_data['item_count'] = count($items);
|
||||
$debug_data['items'] = $items;
|
||||
$log->updateStatus('success', json_encode($debug_data));
|
||||
dol_syslog("IDS Connect Callback: OCI ".count($items)." Artikel empfangen für Supplier ".$log->fk_supplier, LOG_INFO);
|
||||
|
||||
$_SESSION['idsconnect_callback'] = array(
|
||||
'log_id' => $log->id,
|
||||
'supplier_id' => $log->fk_supplier,
|
||||
'items' => $items,
|
||||
'timestamp' => dol_now(),
|
||||
);
|
||||
|
||||
idsconnectCallbackPage('Warenkorb empfangen (OCI): '.count($items).' Artikel', 'success', $log, $debug_data);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($cart_xml)) {
|
||||
$debug_data['result'] = 'no_cart';
|
||||
$log->updateStatus('cancelled', json_encode($debug_data), 'Kein Warenkorb empfangen');
|
||||
dol_syslog("IDS Connect Callback: Kein Warenkorb empfangen für Token ".$token, LOG_INFO);
|
||||
|
||||
// Ergebnis direkt anzeigen (NOLOGIN-Seite, kein Redirect nötig)
|
||||
idsconnectCallbackPage('Kein Warenkorb', 'warning', $log, $debug_data);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Warenkorb parsen (IDS XML-Format)
|
||||
$items = $idsconnect->parseCartXml($cart_xml);
|
||||
|
||||
if ($items === false) {
|
||||
$debug_data['result'] = 'parse_error';
|
||||
$debug_data['parse_error'] = $idsconnect->error;
|
||||
$debug_data['cart_xml_preview'] = substr($cart_xml, 0, 3000);
|
||||
$log->updateStatus('error', json_encode($debug_data), 'XML-Parse-Fehler: '.$idsconnect->error);
|
||||
dol_syslog("IDS Connect Callback: XML-Parse-Fehler: ".$idsconnect->error." Quelle=".$cart_source." XML-Anfang: ".substr($cart_xml, 0, 300), LOG_ERR);
|
||||
|
||||
idsconnectCallbackPage('XML-Fehler: '.$idsconnect->error, 'error', $log, $debug_data, $cart_xml);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Erfolg!
|
||||
$debug_data['result'] = 'success';
|
||||
$debug_data['item_count'] = count($items);
|
||||
$debug_data['items'] = $items;
|
||||
$log->updateStatus('success', json_encode($debug_data));
|
||||
|
||||
dol_syslog("IDS Connect Callback: ".count($items)." Artikel empfangen für Supplier ".$log->fk_supplier." XML-Länge=".strlen($cart_xml), LOG_INFO);
|
||||
|
||||
// Session-Daten für die Weiterverarbeitung
|
||||
$_SESSION['idsconnect_callback'] = array(
|
||||
'log_id' => $log->id,
|
||||
'supplier_id' => $log->fk_supplier,
|
||||
'items' => $items,
|
||||
'timestamp' => dol_now(),
|
||||
);
|
||||
|
||||
idsconnectCallbackPage('Warenkorb empfangen: '.count($items).' Artikel', 'success', $log, $debug_data);
|
||||
exit;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// HTML-Ausgabe-Funktion (kein Redirect, NOLOGIN-Seite)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Zeigt das Callback-Ergebnis direkt an
|
||||
*
|
||||
* @param string $message Nachricht
|
||||
* @param string $type success, error, warning
|
||||
* @param IdsLog $log Log-Eintrag
|
||||
* @param array $debug Debug-Daten
|
||||
* @param string $raw_xml Roh-XML für Anzeige
|
||||
*/
|
||||
function idsconnectCallbackPage($message, $type, $log, $debug = array(), $raw_xml = '')
|
||||
{
|
||||
$colors = array(
|
||||
'success' => array('bg' => '#d4edda', 'border' => '#28a745', 'text' => '#155724'),
|
||||
'error' => array('bg' => '#f8d7da', 'border' => '#dc3545', 'text' => '#721c24'),
|
||||
'warning' => array('bg' => '#fff3cd', 'border' => '#ffc107', 'text' => '#856404'),
|
||||
);
|
||||
$c = $colors[$type] ?? $colors['warning'];
|
||||
|
||||
// Dolibarr-Link: Die URL verwenden über die der User beim Launch eingeloggt war
|
||||
// Priorität: 1. user_base_url aus Log, 2. IDSCONNECT_PUBLIC_URL, 3. $dolibarr_main_url_root
|
||||
global $dolibarr_main_url_root;
|
||||
$internal_base = '';
|
||||
// Aus dem Log-Eintrag die Herkunfts-URL des Users lesen
|
||||
if ($log && !empty($log->request_data)) {
|
||||
$req = json_decode($log->request_data, true);
|
||||
if (!empty($req['user_base_url'])) {
|
||||
$internal_base = rtrim($req['user_base_url'], '/');
|
||||
}
|
||||
}
|
||||
// Fallback auf IDSCONNECT_PUBLIC_URL oder $dolibarr_main_url_root
|
||||
if (empty($internal_base)) {
|
||||
$public_url = getDolGlobalString('IDSCONNECT_PUBLIC_URL');
|
||||
if (!empty($public_url)) {
|
||||
$internal_base = rtrim($public_url, '/');
|
||||
} else {
|
||||
$internal_base = $dolibarr_main_url_root;
|
||||
if (!empty($internal_base) && strpos($internal_base, '://') === false) {
|
||||
$internal_base = 'http://'.$internal_base;
|
||||
}
|
||||
}
|
||||
}
|
||||
$dolibarr_link = $internal_base.'/custom/idsconnect/idsconnectindex.php';
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
echo '<!DOCTYPE html><html><head><meta charset="UTF-8">';
|
||||
echo '<title>IDS Connect Callback v'.IDSCONNECT_CALLBACK_VERSION.'</title>';
|
||||
echo '<style>body{font-family:Arial,sans-serif;max-width:800px;margin:20px auto;padding:0 20px;background:#f5f5f5}';
|
||||
echo '.box{padding:15px;border-radius:5px;margin:10px 0;border-left:4px solid}';
|
||||
echo 'pre{background:#f8f9fa;padding:10px;overflow:auto;max-height:400px;font-size:12px;border:1px solid #ddd;border-radius:3px}';
|
||||
echo 'details{margin:10px 0}summary{cursor:pointer;font-weight:bold;padding:5px}</style>';
|
||||
echo '</head><body>';
|
||||
|
||||
echo '<h2>IDS Connect Callback <small style="color:#999">v'.IDSCONNECT_CALLBACK_VERSION.'</small></h2>';
|
||||
|
||||
// Hauptnachricht
|
||||
echo '<div class="box" style="background:'.$c['bg'].';border-color:'.$c['border'].';color:'.$c['text'].'">';
|
||||
echo '<strong>'.htmlspecialchars($message).'</strong>';
|
||||
echo '<br>Log-Eintrag: #'.$log->id;
|
||||
echo '</div>';
|
||||
|
||||
// Bei Erfolg: Artikelübersicht
|
||||
if ($type === 'success' && !empty($debug['items'])) {
|
||||
echo '<h3>Empfangene Artikel</h3>';
|
||||
echo '<table style="width:100%;border-collapse:collapse;background:white">';
|
||||
echo '<tr style="background:#2c3e50;color:white"><th style="padding:8px">Art.-Nr.</th><th style="padding:8px">Bezeichnung</th><th style="padding:8px;text-align:right">Menge</th><th style="padding:8px">Einheit</th><th style="padding:8px;text-align:right">Preis</th></tr>';
|
||||
foreach ($debug['items'] as $item) {
|
||||
echo '<tr style="border-bottom:1px solid #eee">';
|
||||
echo '<td style="padding:6px"><code>'.htmlspecialchars($item['artikelnr'] ?? '').'</code></td>';
|
||||
echo '<td style="padding:6px">'.htmlspecialchars($item['bezeichnung'] ?? '').'</td>';
|
||||
echo '<td style="padding:6px;text-align:right">'.($item['menge'] ?? 0).'</td>';
|
||||
echo '<td style="padding:6px">'.htmlspecialchars($item['einheit'] ?? 'STK').'</td>';
|
||||
echo '<td style="padding:6px;text-align:right">'.number_format($item['einzelpreis'] ?? 0, 2, ',', '.').'</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
|
||||
// Debug-Infos
|
||||
echo '<details><summary>Debug-Informationen</summary>';
|
||||
echo '<table style="width:100%;background:white;border-collapse:collapse">';
|
||||
|
||||
$show_fields = array(
|
||||
'Callback-Version' => $debug['callback_version'] ?? '-',
|
||||
'Ergebnis' => $debug['result'] ?? '-',
|
||||
'Cart-Quelle' => $debug['cart_source'] ?? 'keine',
|
||||
'Cart-XML-Länge' => ($debug['cart_xml_length'] ?? 0).' Bytes',
|
||||
'POST-Felder' => implode(', ', $debug['post_keys'] ?? array()) ?: 'keine',
|
||||
'FILES-Felder' => implode(', ', array_keys($debug['files_info'] ?? array())) ?: 'keine',
|
||||
'Content-Type' => $debug['callback_meta']['content_type'] ?? '-',
|
||||
'IP-Adresse' => $debug['callback_meta']['remote_ip'] ?? '-',
|
||||
'Zeitstempel' => $debug['callback_meta']['timestamp'] ?? '-',
|
||||
);
|
||||
|
||||
foreach ($show_fields as $label => $value) {
|
||||
echo '<tr style="border-bottom:1px solid #eee"><td style="padding:5px;font-weight:bold">'.htmlspecialchars($label).'</td>';
|
||||
echo '<td style="padding:5px"><code>'.htmlspecialchars($value).'</code></td></tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
|
||||
// POST-Werte
|
||||
if (!empty($debug['post_values_preview'])) {
|
||||
echo '<details><summary>POST-Werte (gekürzt)</summary><pre>';
|
||||
echo htmlspecialchars(json_encode($debug['post_values_preview'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
echo '</pre></details>';
|
||||
}
|
||||
|
||||
// FILES-Info
|
||||
if (!empty($debug['files_info'])) {
|
||||
echo '<details><summary>FILES-Info</summary><pre>';
|
||||
echo htmlspecialchars(json_encode($debug['files_info'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
echo '</pre></details>';
|
||||
}
|
||||
|
||||
echo '</details>';
|
||||
|
||||
// Roh-XML bei Fehler anzeigen
|
||||
if (!empty($raw_xml)) {
|
||||
echo '<details><summary>Roh-XML ('.strlen($raw_xml).' Bytes)</summary>';
|
||||
echo '<pre>'.htmlspecialchars(substr($raw_xml, 0, 5000)).'</pre>';
|
||||
if (strlen($raw_xml) > 5000) {
|
||||
echo '<p style="color:#999">... gekürzt, vollständiges XML im Log #'.$log->id.'</p>';
|
||||
}
|
||||
echo '</details>';
|
||||
}
|
||||
|
||||
// Links
|
||||
echo '<div style="margin-top:20px;padding:15px;background:white;border-radius:5px">';
|
||||
echo '<p><strong>Weiter:</strong></p>';
|
||||
echo '<a href="'.htmlspecialchars($dolibarr_link).'" style="display:inline-block;padding:8px 20px;background:#27ae60;color:white;text-decoration:none;border-radius:5px;margin-right:10px">Zur IDS Connect Übersicht</a>';
|
||||
if ($type === 'success') {
|
||||
$review_link = $internal_base.'/custom/idsconnect/cart_review.php?log_id='.$log->id;
|
||||
echo '<a href="'.htmlspecialchars($review_link).'" style="display:inline-block;padding:8px 20px;background:#3498db;color:white;text-decoration:none;border-radius:5px">Warenkorb prüfen</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '</body></html>';
|
||||
}
|
||||
258
cart_review.php
Normal file
258
cart_review.php
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/cart_review.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Empfangenen Warenkorb prüfen und als Lieferantenbestellung übernehmen
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
dol_include_once('/idsconnect/class/idslog.class.php');
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/class/idsconnect.class.php');
|
||||
dol_include_once('/idsconnect/lib/idsconnect.lib.php');
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("idsconnect@idsconnect", "orders", "bills"));
|
||||
|
||||
if (!$user->hasRight('idsconnect', 'use')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
$log_id = GETPOSTINT('log_id');
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
|
||||
// Log laden
|
||||
$log = new IdsLog($db);
|
||||
if ($log_id > 0) {
|
||||
$log->fetch($log_id);
|
||||
}
|
||||
|
||||
// Großhändler laden
|
||||
$supplier = new IdsSupplier($db);
|
||||
if ($log->fk_supplier > 0) {
|
||||
$supplier->fetch($log->fk_supplier);
|
||||
}
|
||||
|
||||
// Artikel aus dem Log parsen
|
||||
$items = array();
|
||||
if (!empty($log->response_data)) {
|
||||
$response = json_decode($log->response_data, true);
|
||||
if (!empty($response['items'])) {
|
||||
$items = $response['items'];
|
||||
}
|
||||
}
|
||||
|
||||
// Falls keine Artikel im Response, aus dem XML parsen
|
||||
if (empty($items) && !empty($log->cart_xml)) {
|
||||
$idsconnect = new IdsConnect($db);
|
||||
$items = $idsconnect->parseCartXml($log->cart_xml);
|
||||
if ($items === false) {
|
||||
$items = array();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
// Lieferantenbestellung erstellen
|
||||
if ($action == 'create_order' && $user->hasRight('fournisseur', 'commande', 'creer')) {
|
||||
if (!verifCond(GETPOST('token', 'alpha') == newToken())) {
|
||||
accessforbidden('Bad CSRF token');
|
||||
}
|
||||
|
||||
if ($supplier->fk_soc > 0 && !empty($items)) {
|
||||
$order = new CommandeFournisseur($db);
|
||||
$order->socid = $supplier->fk_soc;
|
||||
$order->note_private = 'Erstellt via IDS Connect aus Warenkorb vom '.dol_print_date($log->date_creation, 'dayhour').' ('.$supplier->label.')';
|
||||
|
||||
$db->begin();
|
||||
$order_id = $order->create($user);
|
||||
|
||||
if ($order_id > 0) {
|
||||
$line_errors = 0;
|
||||
foreach ($items as $item) {
|
||||
$vat_rate = !empty($item['mwst_satz']) ? $item['mwst_satz'] : 19;
|
||||
$result = $order->addline(
|
||||
$item['bezeichnung'], // desc
|
||||
$item['einzelpreis'], // pu_ht (Stückpreis, schon durch PE geteilt)
|
||||
$item['menge'], // qty
|
||||
$vat_rate, // txtva
|
||||
0, // txlocaltax1
|
||||
0, // txlocaltax2
|
||||
0, // fk_product
|
||||
0, // fk_prod_fourn_price
|
||||
$item['artikelnr'], // ref_supplier
|
||||
0, // remise_percent
|
||||
'HT' // price_base_type
|
||||
);
|
||||
if ($result < 0) {
|
||||
$line_errors++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($line_errors == 0) {
|
||||
$db->commit();
|
||||
// Log aktualisieren
|
||||
$log->updateStatus('success', $log->response_data);
|
||||
$log->updateCart($log->cart_xml, $order_id);
|
||||
|
||||
setEventMessages($langs->trans("IdsconnectCartImported"), null, 'mesgs');
|
||||
header('Location: '.DOL_URL_ROOT.'/fourn/commande/card.php?id='.$order_id);
|
||||
exit;
|
||||
} else {
|
||||
$db->rollback();
|
||||
setEventMessages('Fehler beim Erstellen der Bestellpositionen', null, 'errors');
|
||||
}
|
||||
} else {
|
||||
$db->rollback();
|
||||
setEventMessages('Fehler beim Erstellen der Bestellung: '.$order->error, null, 'errors');
|
||||
}
|
||||
} else {
|
||||
if ($supplier->fk_soc <= 0) {
|
||||
setEventMessages('Großhändler hat keinen verknüpften Dolibarr-Lieferanten. Bitte zuerst in der Großhändler-Konfiguration zuweisen.', null, 'errors');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$form = new Form($db);
|
||||
|
||||
llxHeader('', $langs->trans("IdsconnectCartReview"), '', '', 0, 0, '', '', '', 'mod-idsconnect page-cart_review');
|
||||
|
||||
print load_fiche_titre($langs->trans("IdsconnectCartReviewTitle"), '', 'fa-shopping-cart');
|
||||
|
||||
idsconnectShowTestModeBanner();
|
||||
|
||||
if (empty($items)) {
|
||||
print '<div class="warning">'.$langs->trans("IdsconnectCartEmpty").'</div>';
|
||||
print '<br><a href="'.DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php" class="butAction">'.$langs->trans("Back").'</a>';
|
||||
llxFooter();
|
||||
$db->close();
|
||||
exit;
|
||||
}
|
||||
|
||||
// Info-Banner
|
||||
print '<div class="info">';
|
||||
print '<strong>'.$langs->trans("IdsconnectCartReviewInfo").'</strong><br>';
|
||||
print 'Großhändler: <strong>'.htmlspecialchars($supplier->label ?: '-').'</strong>';
|
||||
print ' | Empfangen: '.dol_print_date($log->date_creation, 'dayhour');
|
||||
print ' | Artikel: <strong>'.count($items).'</strong>';
|
||||
print '</div>';
|
||||
|
||||
// Artikel-Tabelle
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans("IdsconnectCartArticleNr").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectCartDescription").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectCartManufacturer").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartQty").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectCartUnit").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartUnitPrice").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartTotalPrice").'</th>';
|
||||
print '</tr>';
|
||||
|
||||
$total = 0;
|
||||
foreach ($items as $item) {
|
||||
$line_total = $item['gesamtpreis'] ?: ($item['menge'] * $item['einzelpreis']);
|
||||
$total += $line_total;
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
print '<td><code>'.htmlspecialchars($item['artikelnr']).'</code></td>';
|
||||
print '<td>'.htmlspecialchars($item['bezeichnung']).'</td>';
|
||||
print '<td>'.htmlspecialchars($item['hersteller'] ?: '-').'</td>';
|
||||
print '<td class="right">'.($item['menge']).'</td>';
|
||||
print '<td>'.htmlspecialchars($item['einheit']).'</td>';
|
||||
print '<td class="right">'.price($item['einzelpreis']);
|
||||
// Preiseinheit-Info anzeigen wenn vorhanden (z.B. "Preis/100")
|
||||
if (!empty($item['preiseinheit']) && $item['preiseinheit'] > 1) {
|
||||
print '<br><span class="opacitymedium" style="font-size:0.85em">'.price($item['raw_netprice']).' / '.$item['preiseinheit'].' Stk</span>';
|
||||
}
|
||||
print '</td>';
|
||||
print '<td class="right">'.price($line_total).'</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
|
||||
// Summe
|
||||
print '<tr class="liste_total">';
|
||||
print '<td colspan="6" class="right"><strong>'.$langs->trans("Total").'</strong></td>';
|
||||
print '<td class="right"><strong>'.price($total).'</strong></td>';
|
||||
print '</tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Aktionsbuttons
|
||||
print '<div class="tabsAction">';
|
||||
|
||||
if ($user->hasRight('fournisseur', 'commande', 'creer') && $supplier->fk_soc > 0) {
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?log_id='.$log->id.'&action=create_order&token='.newToken().'">'.$langs->trans("IdsconnectCartCreateOrder").'</a>';
|
||||
} elseif ($supplier->fk_soc <= 0) {
|
||||
print '<span class="butActionRefused" title="Kein Dolibarr-Lieferant verknüpft">'.$langs->trans("IdsconnectCartCreateOrder").'</span>';
|
||||
}
|
||||
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php">'.$langs->trans("Back").'</a>';
|
||||
|
||||
print '</div>';
|
||||
|
||||
// XML-Details (aufklappbar)
|
||||
if (!empty($log->cart_xml)) {
|
||||
print '<br>';
|
||||
print '<details>';
|
||||
print '<summary class="opacitymedium" style="cursor:pointer;">XML-Rohdaten anzeigen</summary>';
|
||||
print '<div class="idsconnect-log-detail">';
|
||||
print htmlspecialchars($log->cart_xml);
|
||||
print '</div>';
|
||||
print '</details>';
|
||||
}
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
553
class/idsconnect.class.php
Normal file
553
class/idsconnect.class.php
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/class/idsconnect.class.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Kern-Klasse für IDS Connect Schnittstelle
|
||||
*/
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/class/idslog.class.php');
|
||||
|
||||
/**
|
||||
* IDS Connect Schnittstelle - Formular-Builder, XML-Parser, Callback-Verwaltung
|
||||
*/
|
||||
class IdsConnect
|
||||
{
|
||||
/** @var DoliDB */
|
||||
private $db;
|
||||
|
||||
/** @var string Fehler */
|
||||
public $error = '';
|
||||
/** @var array Fehler */
|
||||
public $errors = array();
|
||||
|
||||
// Erlaubte IDS-Aktionen
|
||||
const ACTION_WKE = 'WKE'; // Warenkorb empfangen
|
||||
const ACTION_WKS = 'WKS'; // Warenkorb senden
|
||||
const ACTION_ADL = 'ADL'; // Artikel Deep-Link
|
||||
const ACTION_LI = 'LI'; // Login-Info
|
||||
const ACTION_SV = 'SV'; // Schnittstellenversion
|
||||
|
||||
// Erlaubte IDS-Versionen
|
||||
const ALLOWED_VERSIONS = array('1.3', '2.0', '2.1', '2.2', '2.3', '2.5');
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DoliDB $db Datenbank-Handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob der Testmodus aktiv ist (global ODER pro Großhändler)
|
||||
*
|
||||
* @param IdsSupplier $supplier Großhändler
|
||||
* @return bool true wenn Testmodus aktiv
|
||||
*/
|
||||
public function isTestMode($supplier)
|
||||
{
|
||||
// Globaler Testmodus hat Vorrang
|
||||
if (getDolGlobalInt('IDSCONNECT_TESTMODE')) {
|
||||
return true;
|
||||
}
|
||||
// Dann Supplier-spezifischer Testmodus
|
||||
return !empty($supplier->testmode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert einen sicheren Callback-Token für eine Transaktion
|
||||
*
|
||||
* @param int $supplierId Großhändler-ID
|
||||
* @param int $userId Benutzer-ID
|
||||
* @return string Token (64 Zeichen hex)
|
||||
*/
|
||||
public function generateCallbackToken($supplierId, $userId)
|
||||
{
|
||||
$secret = getDolGlobalString('IDSCONNECT_CALLBACK_SECRET', 'idsconnect_default_secret');
|
||||
$timestamp = dol_now();
|
||||
$random = bin2hex(random_bytes(16));
|
||||
$data = $supplierId.'|'.$userId.'|'.$timestamp.'|'.$random;
|
||||
return hash_hmac('sha256', $data, $secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifiziert einen Callback-Token
|
||||
*
|
||||
* @param string $token Der zu prüfende Token
|
||||
* @return array|false Log-Eintrag wenn gültig, false wenn ungültig
|
||||
*/
|
||||
public function verifyCallbackToken($token)
|
||||
{
|
||||
if (empty($token) || strlen($token) !== 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Token nur Hex-Zeichen erlauben
|
||||
if (!preg_match('/^[a-f0-9]{64}$/', $token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Token in der Datenbank suchen
|
||||
$sql = "SELECT rowid FROM ".$this->db->prefix()."idsconnect_log";
|
||||
$sql .= " WHERE callback_token = '".$this->db->escape($token)."'";
|
||||
$sql .= " AND status = 'pending'";
|
||||
// Token max. 2 Stunden gültig
|
||||
$sql .= " AND date_creation > '".$this->db->idate(dol_now() - 7200)."'";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql && $this->db->num_rows($resql) > 0) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
$log = new IdsLog($this->db);
|
||||
$log->fetch($obj->rowid);
|
||||
return $log;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut die Callback-URL (HOOKURL) für den Großhandels-Shop
|
||||
*
|
||||
* @param string $token Callback-Token
|
||||
* @return string Vollständige HOOKURL
|
||||
*/
|
||||
public function buildHookUrl($token)
|
||||
{
|
||||
global $dolibarr_main_url_root;
|
||||
|
||||
// Öffentliche URL verwenden falls konfiguriert (für Reverse-Proxy-Setups)
|
||||
$public_url = getDolGlobalString('IDSCONNECT_PUBLIC_URL');
|
||||
if (!empty($public_url)) {
|
||||
$urlroot = rtrim($public_url, '/');
|
||||
} else {
|
||||
$urlroot = $dolibarr_main_url_root;
|
||||
// Protokoll sicherstellen
|
||||
if (!empty($urlroot) && strpos($urlroot, '://') === false) {
|
||||
$urlroot = 'http://'.$urlroot;
|
||||
}
|
||||
// Sicherstellen dass HTTPS verwendet wird
|
||||
if (strpos($urlroot, 'http://') === 0 && !empty($_SERVER['HTTPS'])) {
|
||||
$urlroot = 'https://'.substr($urlroot, 7);
|
||||
}
|
||||
}
|
||||
|
||||
return $urlroot.'/custom/idsconnect/callback.php?token='.urlencode($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert das HTML-Formular für den IDS Connect Aufruf
|
||||
*
|
||||
* @param IdsSupplier $supplier Großhändler
|
||||
* @param string $action IDS-Action (WKE, WKS, ADL etc.)
|
||||
* @param User $user Aktueller Benutzer
|
||||
* @param array $extra Zusätzliche Parameter (z.B. 'warenkorb' XML, 'artikelnr' für ADL)
|
||||
* @return array Array mit 'html' und 'log_id', oder false bei Fehler
|
||||
*/
|
||||
public function buildLaunchForm($supplier, $action, $user, $extra = array())
|
||||
{
|
||||
// Validierung: Action prüfen
|
||||
$allowed_actions = array(self::ACTION_WKE, self::ACTION_WKS, self::ACTION_ADL, self::ACTION_LI, self::ACTION_SV);
|
||||
if (!in_array($action, $allowed_actions)) {
|
||||
$this->error = 'Ungültige IDS-Action: '.$action;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validierung: Version prüfen
|
||||
if (!in_array($supplier->ids_version, self::ALLOWED_VERSIONS)) {
|
||||
$this->error = 'Ungültige IDS-Version: '.$supplier->ids_version;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validierung: URL prüfen
|
||||
if (!filter_var($supplier->ids_url, FILTER_VALIDATE_URL)) {
|
||||
$this->error = 'Ungültige Shop-URL: '.$supplier->ids_url;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Testmodus prüfen
|
||||
$testmode = $this->isTestMode($supplier);
|
||||
|
||||
// Callback-Token generieren
|
||||
$token = $this->generateCallbackToken($supplier->id, $user->id);
|
||||
|
||||
// HOOKURL bauen
|
||||
$hookurl = $this->buildHookUrl($token);
|
||||
|
||||
// Log-Eintrag erstellen
|
||||
$log = new IdsLog($this->db);
|
||||
$log->fk_supplier = $supplier->id;
|
||||
$log->fk_user = $user->id;
|
||||
$log->action_type = $action;
|
||||
$log->direction = 'OUT';
|
||||
$log->callback_token = $token;
|
||||
$log->status = 'pending';
|
||||
$log->ip_address = getUserRemoteIP();
|
||||
|
||||
// Im Testmodus: Mock-Server URL verwenden
|
||||
$target_url = $supplier->ids_url;
|
||||
if ($testmode) {
|
||||
global $dolibarr_main_url_root;
|
||||
$mock_base = $dolibarr_main_url_root;
|
||||
if (!empty($mock_base) && strpos($mock_base, '://') === false) {
|
||||
$mock_base = 'http://'.$mock_base;
|
||||
}
|
||||
$target_url = $mock_base.'/custom/idsconnect/mockserver.php';
|
||||
}
|
||||
|
||||
// Herkunfts-URL des Users speichern (damit Callback-Links zur richtigen Dolibarr-Instanz zeigen)
|
||||
$user_base_url = '';
|
||||
if (!empty($_SERVER['HTTP_HOST'])) {
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$user_base_url = $scheme.'://'.$_SERVER['HTTP_HOST'];
|
||||
}
|
||||
|
||||
$request_data = array(
|
||||
'action' => $action,
|
||||
'version' => $supplier->ids_version,
|
||||
'customer_no' => $supplier->ids_customer_no,
|
||||
'username' => $supplier->ids_username,
|
||||
'supplier_ref' => $supplier->ref,
|
||||
'supplier_label' => $supplier->label,
|
||||
'testmode' => $testmode ? 1 : 0,
|
||||
'target_url' => $target_url,
|
||||
'hookurl' => $hookurl,
|
||||
'shop_url' => $supplier->ids_url,
|
||||
'user_base_url' => $user_base_url,
|
||||
);
|
||||
|
||||
// Warenkorb-XML bei WKS
|
||||
if ($action === self::ACTION_WKS && !empty($extra['warenkorb'])) {
|
||||
$log->cart_xml = $extra['warenkorb'];
|
||||
$request_data['has_cart'] = true;
|
||||
$request_data['cart_lines'] = substr_count($extra['warenkorb'], '<OrderItem>');
|
||||
}
|
||||
|
||||
// ADL-Parameter
|
||||
if ($action === self::ACTION_ADL && !empty($extra['artikelnr'])) {
|
||||
$request_data['artikelnr'] = $extra['artikelnr'];
|
||||
}
|
||||
|
||||
$log->request_data = json_encode($request_data);
|
||||
$log_id = $log->create($user);
|
||||
|
||||
if ($log_id < 0) {
|
||||
$this->error = 'Fehler beim Erstellen des Log-Eintrags: '.$log->error;
|
||||
dol_syslog("IDS Connect buildLaunchForm: Log-Eintrag fehlgeschlagen: ".$log->error, LOG_ERR);
|
||||
return false;
|
||||
}
|
||||
|
||||
dol_syslog("IDS Connect Launch: Action=".$action." Supplier=".$supplier->ref." Target=".$target_url." Testmode=".($testmode ? 'JA' : 'NEIN')." LogID=".$log_id, LOG_INFO);
|
||||
|
||||
// HTML-Formular generieren
|
||||
$html = '<!DOCTYPE html>'."\n";
|
||||
$html .= '<html><head><meta charset="UTF-8"><title>IDS Connect - '.$this->escapeHtml($supplier->label).'</title></head>'."\n";
|
||||
$html .= '<body onload="document.forms[\'idsform\'].submit();">'."\n";
|
||||
$html .= '<p>Verbindung zu '.$this->escapeHtml($supplier->label).' wird hergestellt...</p>'."\n";
|
||||
$html .= '<form id="idsform" name="idsform" action="'.$this->escapeHtml($target_url).'" method="post" enctype="multipart/form-data">'."\n";
|
||||
$html .= '<input type="hidden" name="kndnr" value="'.$this->escapeHtml($supplier->ids_customer_no).'">'."\n";
|
||||
$html .= '<input type="hidden" name="name_kunde" value="'.$this->escapeHtml($supplier->ids_username).'">'."\n";
|
||||
$html .= '<input type="hidden" name="pw_kunde" value="'.$this->escapeHtml($supplier->ids_password).'">'."\n";
|
||||
$html .= '<input type="hidden" name="version" value="'.$this->escapeHtml($supplier->ids_version).'">'."\n";
|
||||
$html .= '<input type="hidden" name="action" value="'.$this->escapeHtml($action).'">'."\n";
|
||||
$html .= '<input type="hidden" name="hookurl" value="'.$this->escapeHtml($hookurl).'">'."\n";
|
||||
|
||||
// Warenkorb-XML bei WKS
|
||||
if ($action === self::ACTION_WKS && !empty($extra['warenkorb'])) {
|
||||
$html .= '<input type="hidden" name="warenkorb" value="'.$this->escapeHtml($extra['warenkorb']).'">'."\n";
|
||||
}
|
||||
|
||||
// Artikelnummer bei Deep-Link
|
||||
if ($action === self::ACTION_ADL && !empty($extra['artikelnr'])) {
|
||||
$html .= '<input type="hidden" name="artikelnr" value="'.$this->escapeHtml($extra['artikelnr']).'">'."\n";
|
||||
}
|
||||
|
||||
// Target-Parameter (ab v2.3)
|
||||
if (!empty($extra['target'])) {
|
||||
$html .= '<input type="hidden" name="target" value="'.$this->escapeHtml($extra['target']).'">'."\n";
|
||||
}
|
||||
|
||||
$html .= '<noscript><input type="submit" value="Weiter zum Shop"></noscript>'."\n";
|
||||
$html .= '</form></body></html>';
|
||||
|
||||
return array(
|
||||
'html' => $html,
|
||||
'log_id' => $log_id,
|
||||
'testmode' => $testmode,
|
||||
'token' => $token,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet den empfangenen Warenkorb-XML vom Großhandel
|
||||
*
|
||||
* @param string $xml_string Empfangenes XML
|
||||
* @return array|false Geparste Artikel-Liste oder false bei Fehler
|
||||
*/
|
||||
public function parseCartXml($xml_string)
|
||||
{
|
||||
if (empty($xml_string)) {
|
||||
$this->error = 'Leerer Warenkorb empfangen';
|
||||
return false;
|
||||
}
|
||||
|
||||
// XXE-Schutz: Externe Entities deaktivieren
|
||||
$previousValue = libxml_disable_entity_loader(true);
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
$xml = simplexml_load_string($xml_string, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOENT);
|
||||
|
||||
libxml_disable_entity_loader($previousValue);
|
||||
|
||||
if ($xml === false) {
|
||||
$errors = libxml_get_errors();
|
||||
$this->error = 'XML-Parse-Fehler: ';
|
||||
foreach ($errors as $xmlerror) {
|
||||
$this->error .= trim($xmlerror->message).' ';
|
||||
}
|
||||
libxml_clear_errors();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Debug: Root-Element und Kinder loggen
|
||||
$root_name = $xml->getName();
|
||||
$children = array();
|
||||
foreach ($xml->children() as $child) {
|
||||
$children[] = $child->getName();
|
||||
}
|
||||
dol_syslog("IDS Connect parseCartXml: Root='".$root_name."' Kinder=[".implode(', ', $children)."] XML-Länge=".strlen($xml_string), LOG_DEBUG);
|
||||
|
||||
$items = array();
|
||||
|
||||
// Verschiedene mögliche XML-Strukturen unterstützen
|
||||
$itemNodes = array();
|
||||
|
||||
// Format 1: GAEB-ähnlich mit BoQBody
|
||||
if (isset($xml->Award->BoQBody->BoQCtgy)) {
|
||||
foreach ($xml->Award->BoQBody->BoQCtgy as $category) {
|
||||
if (isset($category->BoQBody->Itemlist->Item)) {
|
||||
foreach ($category->BoQBody->Itemlist->Item as $item) {
|
||||
$itemNodes[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format 2: Einfache Item-Liste
|
||||
if (isset($xml->Item)) {
|
||||
foreach ($xml->Item as $item) {
|
||||
$itemNodes[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Format 3: Artikel-Liste
|
||||
if (isset($xml->Artikel)) {
|
||||
foreach ($xml->Artikel as $item) {
|
||||
$itemNodes[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Format 4: NEW_ITEM (OCI-ähnlich)
|
||||
if (isset($xml->NEW_ITEM)) {
|
||||
foreach ($xml->NEW_ITEM as $item) {
|
||||
$itemNodes[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Format 5: IDS Connect 2.5 - Warenkorb/Order/OrderItem
|
||||
if (isset($xml->Order)) {
|
||||
foreach ($xml->Order as $order) {
|
||||
if (isset($order->OrderItem)) {
|
||||
foreach ($order->OrderItem as $item) {
|
||||
$itemNodes[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format 6: Namespace-Variante - Namespaces strippen und nochmal versuchen
|
||||
if (empty($itemNodes) && (strpos($xml_string, 'xmlns') !== false || strpos($xml_string, ':') !== false)) {
|
||||
dol_syslog("IDS Connect parseCartXml: Keine Items gefunden, versuche Namespace-Stripping", LOG_DEBUG);
|
||||
// Alle Namespace-Deklarationen und Prefixe entfernen
|
||||
$clean_xml = preg_replace('/\s+xmlns(:[a-zA-Z0-9]+)?="[^"]*"/', '', $xml_string);
|
||||
$clean_xml = preg_replace('/<([\/]?)([a-zA-Z0-9]+):/', '<$1', $clean_xml);
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
$xml_clean = simplexml_load_string($clean_xml, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOENT);
|
||||
libxml_clear_errors();
|
||||
|
||||
if ($xml_clean !== false) {
|
||||
$root_clean = $xml_clean->getName();
|
||||
$children_clean = array();
|
||||
foreach ($xml_clean->children() as $child) {
|
||||
$children_clean[] = $child->getName();
|
||||
}
|
||||
dol_syslog("IDS Connect parseCartXml: Nach NS-Strip: Root='".$root_clean."' Kinder=[".implode(', ', $children_clean)."]", LOG_DEBUG);
|
||||
|
||||
// Alle Format-Checks nochmal auf das bereinigte XML
|
||||
if (isset($xml_clean->Order)) {
|
||||
foreach ($xml_clean->Order as $order) {
|
||||
if (isset($order->OrderItem)) {
|
||||
foreach ($order->OrderItem as $item) {
|
||||
$itemNodes[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($itemNodes) && isset($xml_clean->Artikel)) {
|
||||
foreach ($xml_clean->Artikel as $item) {
|
||||
$itemNodes[] = $item;
|
||||
}
|
||||
}
|
||||
if (empty($itemNodes) && isset($xml_clean->Item)) {
|
||||
foreach ($xml_clean->Item as $item) {
|
||||
$itemNodes[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($itemNodes as $item) {
|
||||
// Roh-Preise aus XML (können sich auf Preiseinheit beziehen)
|
||||
$raw_netprice = (float) $this->getXmlValue($item, array('NetPrice', 'UP', 'Einzelpreis', 'einzelpreis', 'EP', 'NEW_ITEM-PRICE'), '0');
|
||||
$raw_offerprice = (float) $this->getXmlValue($item, array('OfferPrice'), '0');
|
||||
$price_basis = (float) $this->getXmlValue($item, array('PriceBasis', 'PE', 'Preiseinheit', 'PriceUnit', 'NEW_ITEM-PRICEUNIT'), '1');
|
||||
$mwst = (float) $this->getXmlValue($item, array('VAT', 'MwSt', 'Mehrwertsteuer'), '0');
|
||||
|
||||
// Preiseinheit normalisieren (0 oder negativ = 1)
|
||||
if ($price_basis <= 0) {
|
||||
$price_basis = 1;
|
||||
}
|
||||
|
||||
// Einzelpreis pro Stück berechnen (NetPrice / PriceBasis)
|
||||
$einzelpreis = ($price_basis != 1) ? $raw_netprice / $price_basis : $raw_netprice;
|
||||
$angebotspreis = ($price_basis != 1 && $raw_offerprice > 0) ? $raw_offerprice / $price_basis : $raw_offerprice;
|
||||
|
||||
$parsed = array(
|
||||
'artikelnr' => $this->getXmlValue($item, array('ArtNo', 'RNoPart', 'Artikelnummer', 'artikelnr', 'ArtNr', 'NEW_ITEM-VENDORMAT')),
|
||||
'bezeichnung' => $this->getXmlValue($item, array('Kurztext', 'Description', 'Bezeichnung', 'bezeichnung', 'Bez', 'NEW_ITEM-DESCRIPTION')),
|
||||
'langtext' => $this->getXmlValue($item, array('Langtext')),
|
||||
'menge' => (float) $this->getXmlValue($item, array('Qty', 'Menge', 'menge', 'NEW_ITEM-QUANTITY'), '0'),
|
||||
'einheit' => $this->getXmlValue($item, array('QU', 'Einheit', 'einheit', 'ME', 'NEW_ITEM-UNIT'), 'STK'),
|
||||
'einzelpreis' => $einzelpreis,
|
||||
'angebotspreis' => $angebotspreis,
|
||||
'preiseinheit' => ($price_basis != 1) ? (int) $price_basis : 0,
|
||||
'raw_netprice' => $raw_netprice,
|
||||
'gesamtpreis' => (float) $this->getXmlValue($item, array('GR', 'Gesamtpreis', 'gesamtpreis', 'GP'), '0'),
|
||||
'mwst_satz' => $mwst,
|
||||
'ean' => $this->getXmlValue($item, array('EAN', 'ean', 'GTIN')),
|
||||
'hersteller' => $this->getXmlValue($item, array('ManufacturerID', 'Manufacturer', 'Hersteller', 'hersteller')),
|
||||
'herstellernr' => $this->getXmlValue($item, array('ManufacturerAID', 'Herstellernummer', 'herstellernr')),
|
||||
'hinweis' => $this->getXmlValue($item, array('Hinweis')),
|
||||
);
|
||||
|
||||
// Gesamtpreis berechnen wenn nicht vorhanden
|
||||
if ($parsed['gesamtpreis'] == 0 && $parsed['menge'] > 0 && $parsed['einzelpreis'] > 0) {
|
||||
$parsed['gesamtpreis'] = $parsed['menge'] * $parsed['einzelpreis'];
|
||||
}
|
||||
|
||||
// Bezeichnung aus Langtext übernehmen wenn Kurztext leer
|
||||
if (empty($parsed['bezeichnung']) && !empty($parsed['langtext'])) {
|
||||
$parsed['bezeichnung'] = $parsed['langtext'];
|
||||
}
|
||||
|
||||
if (!empty($parsed['artikelnr']) || !empty($parsed['bezeichnung'])) {
|
||||
$items[] = $parsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($items)) {
|
||||
$this->error = 'Keine Artikel im Warenkorb gefunden (Root: '.$root_name.', Kinder: '.implode(',', $children).')';
|
||||
return false;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Warenkorb-XML aus Dolibarr-Bestellpositionen (IDS Connect 2.0 Format)
|
||||
*
|
||||
* @param array $lines Array mit Bestellpositionen
|
||||
* @return string XML-String
|
||||
*/
|
||||
public function buildCartXml($lines)
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
|
||||
$xml .= '<Warenkorb xmlns="http://www.itek.de/Shop-Anbindung/Warenkorb/">'."\n";
|
||||
$xml .= ' <WarenkorbInfo>'."\n";
|
||||
$xml .= ' <Date>'.date('Y-m-d').'</Date>'."\n";
|
||||
$xml .= ' <Time>'.date('H:i:s').'</Time>'."\n";
|
||||
$xml .= ' <RueckgabeKZ>Warenkorbsendung</RueckgabeKZ>'."\n";
|
||||
$xml .= ' <Version>2.0</Version>'."\n";
|
||||
$xml .= ' </WarenkorbInfo>'."\n";
|
||||
$xml .= ' <Order>'."\n";
|
||||
$xml .= ' <OrderInfo>'."\n";
|
||||
$xml .= ' <ModeOfShipment>Lieferung</ModeOfShipment>'."\n";
|
||||
$xml .= ' <Cur>EUR</Cur>'."\n";
|
||||
$xml .= ' </OrderInfo>'."\n";
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$netprice = (float) ($line['einzelpreis'] ?? 0);
|
||||
$qty = (float) ($line['menge'] ?? 0);
|
||||
$vat = (float) ($line['mwst_satz'] ?? 19);
|
||||
|
||||
$xml .= ' <OrderItem>'."\n";
|
||||
$xml .= ' <ArtNo>'.htmlspecialchars($line['artikelnr'] ?? '', ENT_XML1, 'UTF-8').'</ArtNo>'."\n";
|
||||
$xml .= ' <Qty>'.$qty.'</Qty>'."\n";
|
||||
$xml .= ' <QU>'.htmlspecialchars($line['einheit'] ?? 'PCE', ENT_XML1, 'UTF-8').'</QU>'."\n";
|
||||
$xml .= ' <Kurztext>'.htmlspecialchars($line['bezeichnung'] ?? '', ENT_XML1, 'UTF-8').'</Kurztext>'."\n";
|
||||
$xml .= ' <NetPrice>'.$netprice.'</NetPrice>'."\n";
|
||||
$xml .= ' <PriceBasis>1</PriceBasis>'."\n";
|
||||
$xml .= ' <VAT>'.$vat.'</VAT>'."\n";
|
||||
$xml .= ' </OrderItem>'."\n";
|
||||
}
|
||||
|
||||
$xml .= ' </Order>'."\n";
|
||||
$xml .= '</Warenkorb>';
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* XML-Wert aus verschiedenen möglichen Feldnamen extrahieren
|
||||
*
|
||||
* @param SimpleXMLElement $node XML-Knoten
|
||||
* @param array $names Mögliche Feldnamen
|
||||
* @param string $default Standardwert
|
||||
* @return string Gefundener Wert
|
||||
*/
|
||||
private function getXmlValue($node, $names, $default = '')
|
||||
{
|
||||
foreach ($names as $name) {
|
||||
if (isset($node->{$name})) {
|
||||
return (string) $node->{$name};
|
||||
}
|
||||
// Auch als Attribut prüfen
|
||||
if (isset($node[$name])) {
|
||||
return (string) $node[$name];
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML-Escaping für sichere Ausgabe
|
||||
*
|
||||
* @param string $str Eingabe-String
|
||||
* @return string Escaped String
|
||||
*/
|
||||
private function escapeHtml($str)
|
||||
{
|
||||
return htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
}
|
||||
}
|
||||
282
class/idslog.class.php
Normal file
282
class/idslog.class.php
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/class/idslog.class.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Transaktionslog für IDS Connect
|
||||
*/
|
||||
|
||||
/**
|
||||
* Klasse für IDS Connect Transaktionslog
|
||||
*/
|
||||
class IdsLog
|
||||
{
|
||||
/** @var DoliDB */
|
||||
public $db;
|
||||
|
||||
/** @var int */
|
||||
public $id;
|
||||
/** @var int */
|
||||
public $fk_supplier;
|
||||
/** @var int */
|
||||
public $fk_user;
|
||||
/** @var string WKE, WKS, ADL, LI, SV */
|
||||
public $action_type;
|
||||
/** @var string IN oder OUT */
|
||||
public $direction = 'OUT';
|
||||
/** @var string JSON mit Request-Daten */
|
||||
public $request_data;
|
||||
/** @var string JSON mit Response-Daten */
|
||||
public $response_data;
|
||||
/** @var string XML des Warenkorbs */
|
||||
public $cart_xml;
|
||||
/** @var string pending, success, error, cancelled */
|
||||
public $status = 'pending';
|
||||
/** @var string */
|
||||
public $error_message;
|
||||
/** @var string Sicherer Token für Callback */
|
||||
public $callback_token;
|
||||
/** @var int Verknüpfte Lieferantenbestellung */
|
||||
public $fk_commande;
|
||||
/** @var string */
|
||||
public $ip_address;
|
||||
/** @var string */
|
||||
public $date_creation;
|
||||
/** @var int */
|
||||
public $entity;
|
||||
|
||||
/** @var string */
|
||||
public $error = '';
|
||||
|
||||
const TABLE = 'idsconnect_log';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DoliDB $db Datenbank-Handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log-Eintrag erstellen
|
||||
*
|
||||
* @param User $user Benutzer
|
||||
* @return int >0 bei Erfolg (ID), <0 bei Fehler
|
||||
*/
|
||||
public function create($user)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (!getDolGlobalInt('IDSCONNECT_LOG_ENABLED')) {
|
||||
// Auch ohne Logging den Token speichern für Callback-Verifikation
|
||||
}
|
||||
|
||||
$this->entity = $conf->entity;
|
||||
$this->date_creation = dol_now();
|
||||
|
||||
$sql = "INSERT INTO ".$this->db->prefix().self::TABLE." (";
|
||||
$sql .= "fk_supplier, fk_user, action_type, direction, request_data,";
|
||||
$sql .= " response_data, cart_xml, status, error_message, callback_token,";
|
||||
$sql .= " fk_commande, ip_address, date_creation, entity";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= ((int) $this->fk_supplier).",";
|
||||
$sql .= " ".((int) $this->fk_user).",";
|
||||
$sql .= " '".$this->db->escape($this->action_type)."',";
|
||||
$sql .= " '".$this->db->escape($this->direction)."',";
|
||||
$sql .= " ".($this->request_data ? "'".$this->db->escape($this->request_data)."'" : "NULL").",";
|
||||
$sql .= " ".($this->response_data ? "'".$this->db->escape($this->response_data)."'" : "NULL").",";
|
||||
$sql .= " ".($this->cart_xml ? "'".$this->db->escape($this->cart_xml)."'" : "NULL").",";
|
||||
$sql .= " '".$this->db->escape($this->status)."',";
|
||||
$sql .= " ".($this->error_message ? "'".$this->db->escape($this->error_message)."'" : "NULL").",";
|
||||
$sql .= " ".($this->callback_token ? "'".$this->db->escape($this->callback_token)."'" : "NULL").",";
|
||||
$sql .= " ".($this->fk_commande > 0 ? ((int) $this->fk_commande) : "NULL").",";
|
||||
$sql .= " ".($this->ip_address ? "'".$this->db->escape($this->ip_address)."'" : "NULL").",";
|
||||
$sql .= " '".$this->db->idate($this->date_creation)."',";
|
||||
$sql .= " ".((int) $this->entity);
|
||||
$sql .= ")";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$this->id = $this->db->last_insert_id($this->db->prefix().self::TABLE);
|
||||
return $this->id;
|
||||
}
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log-Eintrag laden
|
||||
*
|
||||
* @param int $id ID
|
||||
* @return int 1 bei Erfolg, 0 nicht gefunden, -1 Fehler
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
$sql = "SELECT rowid, fk_supplier, fk_user, action_type, direction,";
|
||||
$sql .= " request_data, response_data, cart_xml, status, error_message,";
|
||||
$sql .= " callback_token, fk_commande, ip_address, date_creation, entity";
|
||||
$sql .= " FROM ".$this->db->prefix().self::TABLE;
|
||||
$sql .= " WHERE rowid = ".((int) $id);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
if ($this->db->num_rows($resql) > 0) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
$this->id = $obj->rowid;
|
||||
$this->fk_supplier = $obj->fk_supplier;
|
||||
$this->fk_user = $obj->fk_user;
|
||||
$this->action_type = $obj->action_type;
|
||||
$this->direction = $obj->direction;
|
||||
$this->request_data = $obj->request_data;
|
||||
$this->response_data = $obj->response_data;
|
||||
$this->cart_xml = $obj->cart_xml;
|
||||
$this->status = $obj->status;
|
||||
$this->error_message = $obj->error_message;
|
||||
$this->callback_token = $obj->callback_token;
|
||||
$this->fk_commande = $obj->fk_commande;
|
||||
$this->ip_address = $obj->ip_address;
|
||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||
$this->entity = $obj->entity;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log-Status aktualisieren
|
||||
*
|
||||
* @param string $status Neuer Status
|
||||
* @param string $response_data Response-Daten (JSON)
|
||||
* @param string $error_message Fehlermeldung
|
||||
* @return int 1 bei Erfolg, -1 bei Fehler
|
||||
*/
|
||||
public function updateStatus($status, $response_data = '', $error_message = '')
|
||||
{
|
||||
$sql = "UPDATE ".$this->db->prefix().self::TABLE." SET";
|
||||
$sql .= " status = '".$this->db->escape($status)."'";
|
||||
if ($response_data) {
|
||||
$sql .= ", response_data = '".$this->db->escape($response_data)."'";
|
||||
}
|
||||
if ($error_message) {
|
||||
$sql .= ", error_message = '".$this->db->escape($error_message)."'";
|
||||
}
|
||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$this->status = $status;
|
||||
return 1;
|
||||
}
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warenkorb-XML und verknüpfte Bestellung speichern
|
||||
*
|
||||
* @param string $cart_xml Warenkorb-XML
|
||||
* @param int $fk_commande Lieferantenbestellungs-ID
|
||||
* @return int 1 bei Erfolg, -1 bei Fehler
|
||||
*/
|
||||
public function updateCart($cart_xml, $fk_commande = 0)
|
||||
{
|
||||
$sql = "UPDATE ".$this->db->prefix().self::TABLE." SET";
|
||||
$sql .= " cart_xml = '".$this->db->escape($cart_xml)."'";
|
||||
if ($fk_commande > 0) {
|
||||
$sql .= ", fk_commande = ".((int) $fk_commande);
|
||||
}
|
||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$this->cart_xml = $cart_xml;
|
||||
$this->fk_commande = $fk_commande;
|
||||
return 1;
|
||||
}
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Letzte Log-Einträge laden
|
||||
*
|
||||
* @param int $limit Anzahl
|
||||
* @param int $supplierId Nur für bestimmten Großhändler
|
||||
* @return array|int Array mit Log-Objekten oder -1
|
||||
*/
|
||||
public function fetchLast($limit = 50, $supplierId = 0)
|
||||
{
|
||||
$list = array();
|
||||
|
||||
$sql = "SELECT rowid FROM ".$this->db->prefix().self::TABLE;
|
||||
$sql .= " WHERE entity IN (".getEntity('idsconnect').")";
|
||||
if ($supplierId > 0) {
|
||||
$sql .= " AND fk_supplier = ".((int) $supplierId);
|
||||
}
|
||||
$sql .= " ORDER BY date_creation DESC";
|
||||
$sql .= $this->db->plimit($limit);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$log = new self($this->db);
|
||||
$log->fetch($obj->rowid);
|
||||
$list[] = $log;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status als lokalisiertes Label
|
||||
*
|
||||
* @return string HTML-Badge mit Status
|
||||
*/
|
||||
public function getStatusLabel()
|
||||
{
|
||||
switch ($this->status) {
|
||||
case 'pending':
|
||||
return '<span class="badge badge-warning">Ausstehend</span>';
|
||||
case 'success':
|
||||
return '<span class="badge badge-success">Erfolgreich</span>';
|
||||
case 'error':
|
||||
return '<span class="badge badge-danger">Fehler</span>';
|
||||
case 'cancelled':
|
||||
return '<span class="badge badge-secondary">Abgebrochen</span>';
|
||||
default:
|
||||
return '<span class="badge badge-secondary">'.htmlspecialchars($this->status).'</span>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action als lesbaren Text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getActionLabel()
|
||||
{
|
||||
$labels = array(
|
||||
'WKE' => 'Warenkorb empfangen',
|
||||
'WKS' => 'Warenkorb senden',
|
||||
'ADL' => 'Artikel Deep-Link',
|
||||
'LI' => 'Login-Info',
|
||||
'SV' => 'Schnittstellenversion',
|
||||
);
|
||||
return $labels[$this->action_type] ?? $this->action_type;
|
||||
}
|
||||
}
|
||||
297
class/idssupplier.class.php
Normal file
297
class/idssupplier.class.php
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/class/idssupplier.class.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Großhändler-Konfiguration für IDS Connect
|
||||
*/
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
|
||||
|
||||
/**
|
||||
* Klasse für IDS Connect Großhändler-Konfiguration
|
||||
*/
|
||||
class IdsSupplier
|
||||
{
|
||||
/** @var DoliDB */
|
||||
public $db;
|
||||
|
||||
/** @var int */
|
||||
public $id;
|
||||
/** @var string */
|
||||
public $ref;
|
||||
/** @var string */
|
||||
public $label;
|
||||
/** @var int Verknüpfter Dolibarr-Lieferant */
|
||||
public $fk_soc;
|
||||
/** @var string Shop-URL für IDS Connect */
|
||||
public $ids_url;
|
||||
/** @var string IDS-Version (2.0, 2.3, 2.5) */
|
||||
public $ids_version = '2.5';
|
||||
/** @var string Kundennummer beim Großhändler */
|
||||
public $ids_customer_no;
|
||||
/** @var string Benutzerkennung */
|
||||
public $ids_username;
|
||||
/** @var string Passwort (verschlüsselt in DB) */
|
||||
public $ids_password;
|
||||
/** @var int Testmodus (1=aktiv, 0=live) */
|
||||
public $testmode = 1;
|
||||
/** @var int Aktiv (1=ja, 0=nein) */
|
||||
public $active = 1;
|
||||
/** @var string */
|
||||
public $note_public;
|
||||
/** @var string */
|
||||
public $note_private;
|
||||
/** @var string */
|
||||
public $date_creation;
|
||||
/** @var int */
|
||||
public $fk_user_creat;
|
||||
/** @var int */
|
||||
public $fk_user_modif;
|
||||
/** @var int */
|
||||
public $entity;
|
||||
|
||||
/** @var string */
|
||||
public $error = '';
|
||||
/** @var array */
|
||||
public $errors = array();
|
||||
|
||||
const TABLE = 'idsconnect_supplier';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DoliDB $db Datenbank-Handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Großhändler anlegen
|
||||
*
|
||||
* @param User $user Benutzer der die Aktion ausführt
|
||||
* @return int >0 bei Erfolg, <0 bei Fehler
|
||||
*/
|
||||
public function create($user)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$this->entity = $conf->entity;
|
||||
$this->fk_user_creat = $user->id;
|
||||
$this->date_creation = dol_now();
|
||||
|
||||
// Passwort verschlüsseln
|
||||
$encrypted_password = dolEncrypt($this->ids_password);
|
||||
|
||||
$sql = "INSERT INTO ".$this->db->prefix().self::TABLE." (";
|
||||
$sql .= "ref, label, fk_soc, ids_url, ids_version, ids_customer_no,";
|
||||
$sql .= " ids_username, ids_password, testmode, active,";
|
||||
$sql .= " note_public, note_private, date_creation, fk_user_creat, entity";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= "'".$this->db->escape($this->ref)."',";
|
||||
$sql .= " '".$this->db->escape($this->label)."',";
|
||||
$sql .= " ".($this->fk_soc > 0 ? ((int) $this->fk_soc) : "NULL").",";
|
||||
$sql .= " '".$this->db->escape($this->ids_url)."',";
|
||||
$sql .= " '".$this->db->escape($this->ids_version)."',";
|
||||
$sql .= " '".$this->db->escape($this->ids_customer_no)."',";
|
||||
$sql .= " '".$this->db->escape($this->ids_username)."',";
|
||||
$sql .= " '".$this->db->escape($encrypted_password)."',";
|
||||
$sql .= " ".((int) $this->testmode).",";
|
||||
$sql .= " ".((int) $this->active).",";
|
||||
$sql .= " ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL").",";
|
||||
$sql .= " ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").",";
|
||||
$sql .= " '".$this->db->idate($this->date_creation)."',";
|
||||
$sql .= " ".((int) $this->fk_user_creat).",";
|
||||
$sql .= " ".((int) $this->entity);
|
||||
$sql .= ")";
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$this->id = $this->db->last_insert_id($this->db->prefix().self::TABLE);
|
||||
$this->db->commit();
|
||||
return $this->id;
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Großhändler laden
|
||||
*
|
||||
* @param int $id ID
|
||||
* @param string $ref Referenz
|
||||
* @return int >0 bei Erfolg, 0 nicht gefunden, <0 bei Fehler
|
||||
*/
|
||||
public function fetch($id = 0, $ref = '')
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$sql = "SELECT rowid, ref, label, fk_soc, ids_url, ids_version,";
|
||||
$sql .= " ids_customer_no, ids_username, ids_password, testmode, active,";
|
||||
$sql .= " note_public, note_private, date_creation, fk_user_creat, fk_user_modif, entity";
|
||||
$sql .= " FROM ".$this->db->prefix().self::TABLE;
|
||||
if ($id > 0) {
|
||||
$sql .= " WHERE rowid = ".((int) $id);
|
||||
} elseif ($ref) {
|
||||
$sql .= " WHERE ref = '".$this->db->escape($ref)."'";
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
$sql .= " AND entity IN (".getEntity('idsconnect').")";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
if ($this->db->num_rows($resql) > 0) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
$this->id = $obj->rowid;
|
||||
$this->ref = $obj->ref;
|
||||
$this->label = $obj->label;
|
||||
$this->fk_soc = $obj->fk_soc;
|
||||
$this->ids_url = $obj->ids_url;
|
||||
$this->ids_version = $obj->ids_version;
|
||||
$this->ids_customer_no = $obj->ids_customer_no;
|
||||
$this->ids_username = $obj->ids_username;
|
||||
// Passwort entschlüsseln
|
||||
$this->ids_password = dolDecrypt($obj->ids_password);
|
||||
$this->testmode = $obj->testmode;
|
||||
$this->active = $obj->active;
|
||||
$this->note_public = $obj->note_public;
|
||||
$this->note_private = $obj->note_private;
|
||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||
$this->fk_user_creat = $obj->fk_user_creat;
|
||||
$this->fk_user_modif = $obj->fk_user_modif;
|
||||
$this->entity = $obj->entity;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Großhändler aktualisieren
|
||||
*
|
||||
* @param User $user Benutzer der die Aktion ausführt
|
||||
* @return int >0 bei Erfolg, <0 bei Fehler
|
||||
*/
|
||||
public function update($user)
|
||||
{
|
||||
$encrypted_password = dolEncrypt($this->ids_password);
|
||||
|
||||
$sql = "UPDATE ".$this->db->prefix().self::TABLE." SET";
|
||||
$sql .= " ref = '".$this->db->escape($this->ref)."',";
|
||||
$sql .= " label = '".$this->db->escape($this->label)."',";
|
||||
$sql .= " fk_soc = ".($this->fk_soc > 0 ? ((int) $this->fk_soc) : "NULL").",";
|
||||
$sql .= " ids_url = '".$this->db->escape($this->ids_url)."',";
|
||||
$sql .= " ids_version = '".$this->db->escape($this->ids_version)."',";
|
||||
$sql .= " ids_customer_no = '".$this->db->escape($this->ids_customer_no)."',";
|
||||
$sql .= " ids_username = '".$this->db->escape($this->ids_username)."',";
|
||||
$sql .= " ids_password = '".$this->db->escape($encrypted_password)."',";
|
||||
$sql .= " testmode = ".((int) $this->testmode).",";
|
||||
$sql .= " active = ".((int) $this->active).",";
|
||||
$sql .= " note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL").",";
|
||||
$sql .= " note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").",";
|
||||
$sql .= " fk_user_modif = ".((int) $user->id);
|
||||
$sql .= " WHERE rowid = ".((int) $this->id);
|
||||
|
||||
$this->db->begin();
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Großhändler löschen
|
||||
*
|
||||
* @param User $user Benutzer
|
||||
* @return int >0 bei Erfolg, <0 bei Fehler
|
||||
*/
|
||||
public function delete($user)
|
||||
{
|
||||
$this->db->begin();
|
||||
|
||||
// Zuerst Logs löschen
|
||||
$sql = "DELETE FROM ".$this->db->prefix()."idsconnect_log WHERE fk_supplier = ".((int) $this->id);
|
||||
$this->db->query($sql);
|
||||
|
||||
$sql = "DELETE FROM ".$this->db->prefix().self::TABLE." WHERE rowid = ".((int) $this->id);
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$this->db->commit();
|
||||
return 1;
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
$this->db->rollback();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle aktiven Großhändler laden
|
||||
*
|
||||
* @param int $activeonly Nur aktive (1) oder alle (0)
|
||||
* @return array|int Array mit Objekten oder -1 bei Fehler
|
||||
*/
|
||||
public function fetchAll($activeonly = 1)
|
||||
{
|
||||
$list = array();
|
||||
|
||||
$sql = "SELECT rowid FROM ".$this->db->prefix().self::TABLE;
|
||||
$sql .= " WHERE entity IN (".getEntity('idsconnect').")";
|
||||
if ($activeonly) {
|
||||
$sql .= " AND active = 1";
|
||||
}
|
||||
$sql .= " ORDER BY label ASC";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$supplier = new self($this->db);
|
||||
$supplier->fetch($obj->rowid);
|
||||
$list[] = $supplier;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passwort maskiert zurückgeben (für Anzeige)
|
||||
*
|
||||
* @return string Maskiertes Passwort
|
||||
*/
|
||||
public function getMaskedPassword()
|
||||
{
|
||||
if (empty($this->ids_password)) {
|
||||
return '';
|
||||
}
|
||||
$len = strlen($this->ids_password);
|
||||
if ($len <= 4) {
|
||||
return str_repeat('*', $len);
|
||||
}
|
||||
return substr($this->ids_password, 0, 2).str_repeat('*', $len - 4).substr($this->ids_password, -2);
|
||||
}
|
||||
}
|
||||
290
core/modules/modIdsconnect.class.php
Executable file
290
core/modules/modIdsconnect.class.php
Executable file
|
|
@ -0,0 +1,290 @@
|
|||
<?php
|
||||
/* Copyright (C) 2004-2018 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||
* Copyright (C) 2018-2019 Nicolas ZABOURI <info@inovea-conseil.com>
|
||||
* Copyright (C) 2019-2024 Frédéric France <frederic.france@free.fr>
|
||||
* Copyright (C) 2026 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \defgroup idsconnect Module IDS Connect
|
||||
* \brief IDS Connect Schnittstelle zum Elektrogroßhandel (Kluxen, Sonepar etc.)
|
||||
*
|
||||
* \file htdocs/idsconnect/core/modules/modIdsconnect.class.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Modul-Deskriptor für IDS Connect
|
||||
*/
|
||||
include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
|
||||
|
||||
|
||||
/**
|
||||
* Modul-Deskriptor für IDS Connect
|
||||
*/
|
||||
class modIdsconnect extends DolibarrModules
|
||||
{
|
||||
/**
|
||||
* Constructor. Define names, constants, directories, boxes, permissions
|
||||
*
|
||||
* @param DoliDB $db Database handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
global $conf, $langs;
|
||||
|
||||
$this->db = $db;
|
||||
|
||||
$this->numero = 500025;
|
||||
|
||||
$this->rights_class = 'idsconnect';
|
||||
|
||||
$this->family = "interface";
|
||||
$this->module_position = '90';
|
||||
|
||||
$this->name = preg_replace('/^mod/i', '', get_class($this));
|
||||
$this->description = "IdsconnectDescription";
|
||||
$this->descriptionlong = "IdsconnectDescriptionLong";
|
||||
|
||||
$this->editor_name = 'Alles Watt laeuft';
|
||||
$this->editor_url = '';
|
||||
|
||||
$this->version = '1.8';
|
||||
|
||||
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
|
||||
|
||||
$this->picto = 'fa-plug';
|
||||
|
||||
$this->module_parts = array(
|
||||
'triggers' => 0,
|
||||
'login' => 0,
|
||||
'substitutions' => 0,
|
||||
'menus' => 0,
|
||||
'tpl' => 0,
|
||||
'barcode' => 0,
|
||||
'models' => 0,
|
||||
'printing' => 0,
|
||||
'theme' => 0,
|
||||
'css' => array(
|
||||
'/idsconnect/css/idsconnect.css',
|
||||
),
|
||||
'js' => array(),
|
||||
'hooks' => array(
|
||||
'data' => array(
|
||||
'ordersuppliercard',
|
||||
),
|
||||
'entity' => '0',
|
||||
),
|
||||
'moduleforexternal' => 0,
|
||||
'websitetemplates' => 0,
|
||||
'captcha' => 0
|
||||
);
|
||||
|
||||
$this->dirs = array("/idsconnect/temp");
|
||||
|
||||
$this->config_page_url = array("setup.php@idsconnect");
|
||||
|
||||
$this->hidden = getDolGlobalInt('MODULE_IDSCONNECT_DISABLED');
|
||||
$this->depends = array('modFournisseur');
|
||||
$this->requiredby = array();
|
||||
$this->conflictwith = array();
|
||||
|
||||
$this->langfiles = array("idsconnect@idsconnect");
|
||||
|
||||
$this->phpmin = array(7, 4);
|
||||
$this->need_dolibarr_version = array(19, -3);
|
||||
$this->need_javascript_ajax = 0;
|
||||
|
||||
$this->warnings_activation = array();
|
||||
$this->warnings_activation_ext = array();
|
||||
|
||||
// Konstanten - Testmodus ist Standard AN
|
||||
$this->const = array(
|
||||
1 => array('IDSCONNECT_TESTMODE', 'chaine', '1', 'Testmodus aktiv - keine echten Verbindungen zum Großhandel', 0),
|
||||
2 => array('IDSCONNECT_LOG_ENABLED', 'chaine', '1', 'Logging aller IDS-Transaktionen', 0),
|
||||
3 => array('IDSCONNECT_CALLBACK_SECRET', 'chaine', bin2hex(random_bytes(16)), 'Secret für Callback-Token-Generierung', 0),
|
||||
);
|
||||
|
||||
if (!isModEnabled("idsconnect")) {
|
||||
$conf->idsconnect = new stdClass();
|
||||
$conf->idsconnect->enabled = 0;
|
||||
}
|
||||
|
||||
// Tab in Lieferantenbestellungen
|
||||
$this->tabs = array(
|
||||
'supplier_order:+idsconnect:IdsConnectTab:idsconnect@idsconnect:$user->hasRight(\'idsconnect\', \'use\'):/idsconnect/tab_supplierorder.php?id=__ID__',
|
||||
);
|
||||
|
||||
$this->dictionaries = array();
|
||||
$this->boxes = array();
|
||||
$this->cronjobs = array();
|
||||
|
||||
// ============================================================
|
||||
// Berechtigungen
|
||||
// ============================================================
|
||||
$this->rights = array();
|
||||
$r = 0;
|
||||
|
||||
// Lesen - Modul sehen, Logs anschauen
|
||||
$this->rights[$r][0] = $this->numero . '01';
|
||||
$this->rights[$r][1] = 'IDS Connect Modul sehen und Logs lesen';
|
||||
$this->rights[$r][4] = 'read';
|
||||
$this->rights[$r][5] = '';
|
||||
$r++;
|
||||
|
||||
// Nutzen - Warenkörbe senden/empfangen, Shop öffnen
|
||||
$this->rights[$r][0] = $this->numero . '02';
|
||||
$this->rights[$r][1] = 'IDS Connect Schnittstelle nutzen (Warenkörbe, Deep-Links)';
|
||||
$this->rights[$r][4] = 'use';
|
||||
$this->rights[$r][5] = '';
|
||||
$r++;
|
||||
|
||||
// Konfigurieren - Großhändler verwalten, Zugangsdaten ändern
|
||||
$this->rights[$r][0] = $this->numero . '03';
|
||||
$this->rights[$r][1] = 'IDS Connect Großhändler und Zugangsdaten verwalten';
|
||||
$this->rights[$r][4] = 'config';
|
||||
$this->rights[$r][5] = '';
|
||||
$r++;
|
||||
|
||||
// Löschen - Logs und Großhändler löschen
|
||||
$this->rights[$r][0] = $this->numero . '04';
|
||||
$this->rights[$r][1] = 'IDS Connect Daten löschen';
|
||||
$this->rights[$r][4] = 'delete';
|
||||
$this->rights[$r][5] = '';
|
||||
$r++;
|
||||
|
||||
// ============================================================
|
||||
// Menüs
|
||||
// ============================================================
|
||||
$this->menu = array();
|
||||
$r = 0;
|
||||
|
||||
// Top-Menü
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => '',
|
||||
'type' => 'top',
|
||||
'titre' => 'ModuleIdsconnectName',
|
||||
'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle"'),
|
||||
'mainmenu' => 'idsconnect',
|
||||
'leftmenu' => '',
|
||||
'url' => '/idsconnect/idsconnectindex.php',
|
||||
'langs' => 'idsconnect@idsconnect',
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("idsconnect")',
|
||||
'perms' => '$user->hasRight("idsconnect", "read")',
|
||||
'target' => '',
|
||||
'user' => 0,
|
||||
);
|
||||
|
||||
// Links-Menü: Übersicht
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=idsconnect',
|
||||
'type' => 'left',
|
||||
'titre' => 'IdsconnectOverview',
|
||||
'prefix' => img_picto('', 'fa-home', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'idsconnect',
|
||||
'leftmenu' => 'idsconnect_overview',
|
||||
'url' => '/idsconnect/idsconnectindex.php',
|
||||
'langs' => 'idsconnect@idsconnect',
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("idsconnect")',
|
||||
'perms' => '$user->hasRight("idsconnect", "read")',
|
||||
'target' => '',
|
||||
'user' => 0,
|
||||
);
|
||||
|
||||
// Links-Menü: Großhändler
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=idsconnect',
|
||||
'type' => 'left',
|
||||
'titre' => 'IdsconnectSuppliers',
|
||||
'prefix' => img_picto('', 'fa-building', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'idsconnect',
|
||||
'leftmenu' => 'idsconnect_suppliers',
|
||||
'url' => '/idsconnect/supplier_list.php',
|
||||
'langs' => 'idsconnect@idsconnect',
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("idsconnect")',
|
||||
'perms' => '$user->hasRight("idsconnect", "read")',
|
||||
'target' => '',
|
||||
'user' => 0,
|
||||
);
|
||||
|
||||
// Links-Menü: Großhändler anlegen
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=idsconnect,fk_leftmenu=idsconnect_suppliers',
|
||||
'type' => 'left',
|
||||
'titre' => 'IdsconnectNewSupplier',
|
||||
'mainmenu' => 'idsconnect',
|
||||
'leftmenu' => 'idsconnect_supplier_new',
|
||||
'url' => '/idsconnect/supplier_card.php?action=create',
|
||||
'langs' => 'idsconnect@idsconnect',
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("idsconnect")',
|
||||
'perms' => '$user->hasRight("idsconnect", "config")',
|
||||
'target' => '',
|
||||
'user' => 0,
|
||||
);
|
||||
|
||||
// Links-Menü: Transaktionslog
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=idsconnect',
|
||||
'type' => 'left',
|
||||
'titre' => 'IdsconnectLog',
|
||||
'prefix' => img_picto('', 'fa-list-alt', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'idsconnect',
|
||||
'leftmenu' => 'idsconnect_log',
|
||||
'url' => '/idsconnect/log_list.php',
|
||||
'langs' => 'idsconnect@idsconnect',
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("idsconnect")',
|
||||
'perms' => '$user->hasRight("idsconnect", "read")',
|
||||
'target' => '',
|
||||
'user' => 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modul aktivieren
|
||||
*
|
||||
* @param string $options Options when enabling module ('', 'noboxes')
|
||||
* @return int 1 if OK, <=0 if KO
|
||||
*/
|
||||
public function init($options = '')
|
||||
{
|
||||
global $conf, $langs;
|
||||
|
||||
$result = $this->_load_tables('/idsconnect/sql/');
|
||||
if ($result < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$this->remove($options);
|
||||
|
||||
$sql = array();
|
||||
|
||||
return $this->_init($sql, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modul deaktivieren
|
||||
*
|
||||
* @param string $options Options when enabling module ('', 'noboxes')
|
||||
* @return int 1 if OK, <=0 if KO
|
||||
*/
|
||||
public function remove($options = '')
|
||||
{
|
||||
$sql = array();
|
||||
return $this->_remove($sql, $options);
|
||||
}
|
||||
}
|
||||
95
css/idsconnect.css
Normal file
95
css/idsconnect.css
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/* IDS Connect - Modul-Styles */
|
||||
|
||||
.idsconnect-testmode-banner {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.idsconnect-livemode-banner {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-left: 4px solid #dc3545;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.idsconnect-action-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.idsconnect-action-buttons .butAction {
|
||||
min-width: 180px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.idsconnect-supplier-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.idsconnect-supplier-card:hover {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.idsconnect-supplier-card .supplier-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.idsconnect-supplier-card .supplier-url {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.idsconnect-warn-row {
|
||||
background-color: #fff3cd !important;
|
||||
}
|
||||
|
||||
.idsconnect-warn-text {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.idsconnect-wks-warning {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-left: 4px solid #dc3545;
|
||||
padding: 12px 15px;
|
||||
margin: 15px 0;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.idsconnect-pin-box {
|
||||
background: #e8f4fd;
|
||||
border: 1px solid #bee5eb;
|
||||
border-left: 4px solid #17a2b8;
|
||||
padding: 12px 15px;
|
||||
margin: 15px 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.idsconnect-log-detail {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
184
idsconnectindex.php
Executable file
184
idsconnectindex.php
Executable file
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/idsconnectindex.php
|
||||
* \ingroup idsconnect
|
||||
* \brief IDS Connect Übersichtsseite
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/class/idslog.class.php');
|
||||
dol_include_once('/idsconnect/lib/idsconnect.lib.php');
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("idsconnect@idsconnect"));
|
||||
|
||||
// Berechtigungsprüfung
|
||||
if (!$user->hasRight('idsconnect', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Meldungen aus GET
|
||||
$msg = GETPOST('msg', 'alphanohtml');
|
||||
$errmsg = GETPOST('error', 'alphanohtml');
|
||||
if ($msg) {
|
||||
setEventMessages($msg, null, 'mesgs');
|
||||
}
|
||||
if ($errmsg) {
|
||||
setEventMessages($errmsg, null, 'errors');
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
llxHeader("", $langs->trans("IdsconnectArea"), '', '', 0, 0, '', '', '', 'mod-idsconnect page-index');
|
||||
|
||||
print load_fiche_titre($langs->trans("IdsconnectArea"), '', 'fa-plug');
|
||||
|
||||
// Testmodus-Banner
|
||||
idsconnectShowTestModeBanner();
|
||||
|
||||
print '<div class="fichecenter"><div class="fichethirdleft">';
|
||||
|
||||
// ============================================================
|
||||
// Großhändler-Übersicht mit Aktionsbuttons
|
||||
// ============================================================
|
||||
$supplierObj = new IdsSupplier($db);
|
||||
$suppliers = $supplierObj->fetchAll(0);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th colspan="3">'.$langs->trans("IdsconnectSuppliers");
|
||||
if (is_array($suppliers)) {
|
||||
print '<span class="badge marginleftonlyshort">'.count($suppliers).'</span>';
|
||||
}
|
||||
print '</th></tr>';
|
||||
|
||||
if (is_array($suppliers) && count($suppliers) > 0) {
|
||||
foreach ($suppliers as $sup) {
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>';
|
||||
print '<a href="'.DOL_URL_ROOT.'/custom/idsconnect/supplier_card.php?id='.$sup->id.'">';
|
||||
print '<strong>'.htmlspecialchars($sup->label).'</strong>';
|
||||
print '</a>';
|
||||
print ' <span class="opacitymedium">('.htmlspecialchars($sup->ref).')</span>';
|
||||
if (!$sup->active) {
|
||||
print ' <span class="badge badge-secondary">inaktiv</span>';
|
||||
}
|
||||
if ($sup->testmode) {
|
||||
print ' <span class="badge badge-warning">Test</span>';
|
||||
}
|
||||
print '</td>';
|
||||
print '<td class="right nowrap">';
|
||||
if ($sup->active && $user->hasRight('idsconnect', 'use')) {
|
||||
print '<a class="butAction butActionSmall" href="'.DOL_URL_ROOT.'/custom/idsconnect/launch.php?supplier_id='.$sup->id.'&ids_action=WKE&token='.newToken().'" target="_blank">';
|
||||
print $langs->trans("IdsconnectReceiveCart");
|
||||
print '</a> ';
|
||||
}
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="3" class="opacitymedium">';
|
||||
print $langs->trans("IdsconnectNoSuppliers");
|
||||
print '</td></tr>';
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
if ($user->hasRight('idsconnect', 'config')) {
|
||||
print '<div class="tabsAction">';
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/supplier_card.php?action=create">'.$langs->trans("IdsconnectNewSupplier").'</a>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print '</div><div class="fichetwothirdright">';
|
||||
|
||||
// ============================================================
|
||||
// Letzte Transaktionen
|
||||
// ============================================================
|
||||
$logObj = new IdsLog($db);
|
||||
$logs = $logObj->fetchLast(15);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans("IdsconnectLogDate").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogSupplier").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogAction").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogStatus").'</th>';
|
||||
print '</tr>';
|
||||
|
||||
if (is_array($logs) && count($logs) > 0) {
|
||||
foreach ($logs as $log) {
|
||||
$sup = new IdsSupplier($db);
|
||||
$sup->fetch($log->fk_supplier);
|
||||
|
||||
$detail_url = DOL_URL_ROOT.'/custom/idsconnect/log_detail.php?id='.$log->id;
|
||||
print '<tr class="oddeven">';
|
||||
print '<td class="nowrap"><a href="'.$detail_url.'">'.dol_print_date($log->date_creation, 'dayhour').'</a></td>';
|
||||
print '<td>'.htmlspecialchars($sup->label ?: '-').'</td>';
|
||||
print '<td>'.$log->getActionLabel().' <span class="opacitymedium">('.$log->direction.')</span></td>';
|
||||
print '<td>'.$log->getStatusLabel().'</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="4" class="opacitymedium">'.$langs->trans("IdsconnectLogNoEntries").'</td></tr>';
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
if ($user->hasRight('idsconnect', 'read')) {
|
||||
print '<div class="tabsAction">';
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/log_list.php">'.$langs->trans("IdsconnectLog").'</a>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print '</div></div>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
14
img/README.md
Executable file
14
img/README.md
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
Directory for module image files
|
||||
--------------------------------
|
||||
|
||||
You can put here the .png files of your module:
|
||||
|
||||
|
||||
If the picto of your module is an image (property $picto has been set to 'idsconnect.png@idsconnect', you can put into this
|
||||
directory a .png file called *object_idsconnect.png* (16x16 or 32x32 pixels)
|
||||
|
||||
|
||||
If the picto of an object is an image (property $picto of the object.class.php has been set to 'myobject.png@idsconnect', then you can put into this
|
||||
directory a .png file called *object_myobject.png* (16x16 or 32x32 pixels)
|
||||
|
||||
156
langs/de_DE/idsconnect.lang
Normal file
156
langs/de_DE/idsconnect.lang
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# IDS Connect - Deutsche Sprachdatei
|
||||
|
||||
#
|
||||
# Modul
|
||||
#
|
||||
ModuleIdsconnectName = IDS Connect
|
||||
ModuleIdsconnectDesc = IDS Connect Schnittstelle zum Elektrogroßhandel
|
||||
IdsconnectDescription = IDS Connect Schnittstelle zum Elektrogroßhandel (Kluxen, Sonepar etc.)
|
||||
IdsconnectDescriptionLong = Verbindet Dolibarr mit den Online-Shops der Elektrogroßhändler über die IDS Connect Schnittstelle. Ermöglicht Warenkörbe zu senden und empfangen, Artikel-Deep-Links und Preisabfragen.
|
||||
|
||||
#
|
||||
# Admin / Setup
|
||||
#
|
||||
IdsconnectSetup = IDS Connect Konfiguration
|
||||
IdsconnectSetupPage = Allgemeine Einstellungen für die IDS Connect Schnittstelle
|
||||
IdsconnectGeneralSettings = Allgemeine Einstellungen
|
||||
IdsconnectPublicUrl = Öffentliche URL (für Callback)
|
||||
IdsconnectPublicUrlHelp = Öffentlich erreichbare Dolibarr-URL für den Callback vom Großhandel (z.B. https://awl.data-it-solution.de). Wenn leer wird dolibarr_main_url_root verwendet.
|
||||
IdsconnectPublicUrlMissing = Öffentliche URL nicht konfiguriert!
|
||||
IdsconnectTestMode = Testmodus
|
||||
IdsconnectTestModeHelp = Im Testmodus werden alle Anfragen an den lokalen Mock-Server gesendet, nicht an den echten Großhandel
|
||||
IdsconnectLogEnabled = Transaktions-Logging
|
||||
IdsconnectCallbackUrl = Callback-URL (HOOKURL)
|
||||
IdsconnectCallbackUrlHelp = Diese URL empfängt Warenkörbe vom Großhandel (wird automatisch als HOOKURL gesendet)
|
||||
IdsconnectMockServerUrl = Mock-Server URL
|
||||
IdsconnectMockServerHelp = Lokaler Test-Shop für Entwicklung (nur im Testmodus erreichbar)
|
||||
IdsconnectWksSecuritySettings = WKS-Sicherheit (Warenkorb senden)
|
||||
IdsconnectSecurityInfo = Sicherheitshinweise
|
||||
IdsconnectSecurityInfoText = Passwörter werden verschlüsselt in der Datenbank gespeichert. Der Testmodus ist standardmäßig aktiv - deaktivieren Sie ihn erst, wenn alle Tests erfolgreich waren. Alle Transaktionen werden protokolliert.
|
||||
|
||||
#
|
||||
# Menü und Navigation
|
||||
#
|
||||
IdsconnectArea = IDS Connect
|
||||
IdsconnectOverview = Übersicht
|
||||
IdsconnectSuppliers = Großhändler
|
||||
IdsconnectNewSupplier = Neuer Großhändler
|
||||
IdsconnectLog = Transaktionslog
|
||||
IdsConnectTab = IDS Connect
|
||||
|
||||
#
|
||||
# Großhändler
|
||||
#
|
||||
IdsconnectSupplierCard = Großhändler-Konfiguration
|
||||
IdsconnectSupplierList = Großhändler-Übersicht
|
||||
IdsconnectSupplierNew = Neuen Großhändler anlegen
|
||||
IdsconnectSupplierRef = Referenz/Kürzel
|
||||
IdsconnectSupplierLabel = Bezeichnung
|
||||
IdsconnectSupplierSoc = Dolibarr-Lieferant
|
||||
IdsconnectSupplierUrl = Shop-URL (IDS-Endpoint)
|
||||
IdsconnectSupplierVersion = IDS-Version
|
||||
IdsconnectSupplierCustomerNo = Kundennummer beim Großhändler
|
||||
IdsconnectSupplierUsername = Benutzerkennung
|
||||
IdsconnectSupplierPassword = Passwort
|
||||
IdsconnectSupplierTestmode = Testmodus für diesen Händler
|
||||
IdsconnectSupplierActive = Aktiv
|
||||
IdsconnectSupplierCreated = Großhändler erfolgreich angelegt
|
||||
IdsconnectSupplierUpdated = Großhändler erfolgreich aktualisiert
|
||||
IdsconnectSupplierDeleted = Großhändler gelöscht
|
||||
IdsconnectSupplierNotFound = Großhändler nicht gefunden
|
||||
IdsconnectSupplierInactive = Großhändler ist deaktiviert
|
||||
IdsconnectSupplierConfirmDelete = Möchten Sie diesen Großhändler und alle zugehörigen Logs wirklich löschen?
|
||||
IdsconnectNoSuppliers = Keine Großhändler konfiguriert. Legen Sie zuerst einen Großhändler an.
|
||||
|
||||
#
|
||||
# Aktionen
|
||||
#
|
||||
IdsconnectActionWKE = Warenkorb empfangen
|
||||
IdsconnectActionWKS = Warenkorb senden
|
||||
IdsconnectActionADL = Artikel Deep-Link
|
||||
IdsconnectActionLI = Login-Info
|
||||
IdsconnectActionSV = Schnittstellenversion
|
||||
IdsconnectOpenShop = Shop öffnen
|
||||
IdsconnectSendCart = Warenkorb senden
|
||||
IdsconnectReceiveCart = Warenkorb empfangen
|
||||
IdsconnectDeepLink = Artikel im Shop anzeigen
|
||||
|
||||
#
|
||||
# Launcher / Bestätigung
|
||||
#
|
||||
IdsconnectLaunchConfirm = IDS Connect - Bestätigung
|
||||
IdsconnectLaunchConfirmTitle = Verbindung zum Großhandel bestätigen
|
||||
IdsconnectLaunchConfirmWarning = ACHTUNG: Sie sind NICHT im Testmodus!
|
||||
IdsconnectLaunchConfirmText = Möchten Sie wirklich eine Verbindung zu %s herstellen (Action: %s)?
|
||||
IdsconnectMissingParams = Fehlende Parameter für IDS Connect
|
||||
|
||||
#
|
||||
# WKS-Bestätigung
|
||||
#
|
||||
IdsconnectWksConfirmTitle = Warenkorb an Großhändler senden - Prüfung
|
||||
IdsconnectWksConfirmInfo = Die folgenden Artikel werden an %s gesendet. Bitte prüfen Sie Mengen und Werte sorgfältig!
|
||||
IdsconnectWksConfirmSend = Warenkorb jetzt senden
|
||||
IdsconnectWksWarningTitle = ACHTUNG - Plausibilitätsprüfung:
|
||||
IdsconnectWksWarnQtyLine = Artikel %s hat eine ungewöhnlich hohe Menge: %s (Schwelle: %s)
|
||||
IdsconnectWksWarnValue = Gesamtwert %s übersteigt die Warnschwelle von %s
|
||||
IdsconnectWksWarnQty = Mengen-Warnschwelle pro Position
|
||||
IdsconnectWksWarnQtyHelp = Warnung wenn eine Position mehr als diese Stückzahl hat (0 = deaktiviert)
|
||||
IdsconnectWksWarnValueLabel = Wert-Warnschwelle Gesamtbestellung
|
||||
IdsconnectWksWarnValueHelp = Warnung wenn der Gesamtwert diesen Betrag übersteigt (0 = deaktiviert)
|
||||
IdsconnectWksPin = Sicherheits-PIN
|
||||
IdsconnectWksPinLabel = WKS-Sicherheits-PIN
|
||||
IdsconnectWksPinHelp = Wird als Hash gespeichert. Muss vor jedem Warenkorb-Versand eingegeben werden.
|
||||
IdsconnectWksPinInfo = Bitte PIN eingeben um den Versand zu bestätigen
|
||||
IdsconnectWksPinPlaceholder = Leer lassen = unverändert
|
||||
IdsconnectWksPinWrong = Falsche PIN! Warenkorb wurde NICHT gesendet.
|
||||
IdsconnectWksPinSet = PIN gesetzt
|
||||
IdsconnectWksPinNotSet = Keine PIN
|
||||
|
||||
#
|
||||
# Warenkorb
|
||||
#
|
||||
IdsconnectCartReview = Warenkorb-Prüfung
|
||||
IdsconnectCartReviewTitle = Empfangene Artikel prüfen
|
||||
IdsconnectCartReviewInfo = Die folgenden Artikel wurden vom Großhandels-Shop empfangen. Prüfen Sie die Daten und erstellen Sie bei Bedarf eine Lieferantenbestellung.
|
||||
IdsconnectCartEmpty = Kein Warenkorb vorhanden
|
||||
IdsconnectCartArticleNr = Artikelnummer
|
||||
IdsconnectCartDescription = Bezeichnung
|
||||
IdsconnectCartQty = Menge
|
||||
IdsconnectCartUnit = Einheit
|
||||
IdsconnectCartUnitPrice = Einzelpreis
|
||||
IdsconnectCartTotalPrice = Gesamtpreis
|
||||
IdsconnectCartManufacturer = Hersteller
|
||||
IdsconnectCartCreateOrder = Lieferantenbestellung erstellen
|
||||
IdsconnectCartImported = Warenkorb erfolgreich in Lieferantenbestellung importiert
|
||||
|
||||
#
|
||||
# Log
|
||||
#
|
||||
IdsconnectLogList = Transaktionslog
|
||||
IdsconnectLogDate = Datum
|
||||
IdsconnectLogSupplier = Großhändler
|
||||
IdsconnectLogUser = Benutzer
|
||||
IdsconnectLogAction = Aktion
|
||||
IdsconnectLogDirection = Richtung
|
||||
IdsconnectLogStatus = Status
|
||||
IdsconnectLogStatusPending = Ausstehend
|
||||
IdsconnectLogStatusSuccess = Erfolgreich
|
||||
IdsconnectLogStatusError = Fehler
|
||||
IdsconnectLogStatusCancelled = Abgebrochen
|
||||
IdsconnectLogNoEntries = Keine Log-Einträge vorhanden
|
||||
IdsconnectLogDetail = Log-Details
|
||||
|
||||
#
|
||||
# Testmodus
|
||||
#
|
||||
IdsconnectTestModeActive = TESTMODUS AKTIV
|
||||
IdsconnectTestModeInfo = Alle Verbindungen gehen zum lokalen Mock-Server. Keine echten Bestellungen möglich.
|
||||
IdsconnectLiveModeWarning = LIVE-MODUS - Verbindungen gehen zum echten Großhandel!
|
||||
|
||||
#
|
||||
# Berechtigungen
|
||||
#
|
||||
Permission50002401 = IDS Connect Modul sehen und Logs lesen
|
||||
Permission50002402 = IDS Connect Schnittstelle nutzen (Warenkörbe, Deep-Links)
|
||||
Permission50002403 = IDS Connect Großhändler und Zugangsdaten verwalten
|
||||
Permission50002404 = IDS Connect Daten löschen
|
||||
156
langs/en_US/idsconnect.lang
Executable file
156
langs/en_US/idsconnect.lang
Executable file
|
|
@ -0,0 +1,156 @@
|
|||
# IDS Connect - English language file
|
||||
|
||||
#
|
||||
# Module
|
||||
#
|
||||
ModuleIdsconnectName = IDS Connect
|
||||
ModuleIdsconnectDesc = IDS Connect interface to electrical wholesale
|
||||
IdsconnectDescription = IDS Connect interface to electrical wholesale (Kluxen, Sonepar etc.)
|
||||
IdsconnectDescriptionLong = Connects Dolibarr with electrical wholesale online shops via the IDS Connect interface. Enables sending and receiving shopping carts, article deep-links and price queries.
|
||||
|
||||
#
|
||||
# Admin / Setup
|
||||
#
|
||||
IdsconnectSetup = IDS Connect Configuration
|
||||
IdsconnectSetupPage = General settings for the IDS Connect interface
|
||||
IdsconnectGeneralSettings = General Settings
|
||||
IdsconnectPublicUrl = Public URL (for Callback)
|
||||
IdsconnectPublicUrlHelp = Publicly reachable Dolibarr URL for the callback from the wholesaler (e.g. https://your-domain.com). If empty, dolibarr_main_url_root is used.
|
||||
IdsconnectPublicUrlMissing = Public URL not configured!
|
||||
IdsconnectTestMode = Test Mode
|
||||
IdsconnectTestModeHelp = In test mode all requests are sent to the local mock server, not to the real wholesale shop
|
||||
IdsconnectLogEnabled = Transaction Logging
|
||||
IdsconnectCallbackUrl = Callback URL (HOOKURL)
|
||||
IdsconnectCallbackUrlHelp = This URL receives shopping carts from the wholesale shop (automatically sent as HOOKURL)
|
||||
IdsconnectMockServerUrl = Mock Server URL
|
||||
IdsconnectMockServerHelp = Local test shop for development (only accessible in test mode)
|
||||
IdsconnectWksSecuritySettings = WKS Security (Send Cart)
|
||||
IdsconnectSecurityInfo = Security Notes
|
||||
IdsconnectSecurityInfoText = Passwords are stored encrypted in the database. Test mode is active by default - only deactivate it after all tests were successful. All transactions are logged.
|
||||
|
||||
#
|
||||
# Menu and Navigation
|
||||
#
|
||||
IdsconnectArea = IDS Connect
|
||||
IdsconnectOverview = Overview
|
||||
IdsconnectSuppliers = Wholesalers
|
||||
IdsconnectNewSupplier = New Wholesaler
|
||||
IdsconnectLog = Transaction Log
|
||||
IdsConnectTab = IDS Connect
|
||||
|
||||
#
|
||||
# Suppliers
|
||||
#
|
||||
IdsconnectSupplierCard = Wholesaler Configuration
|
||||
IdsconnectSupplierList = Wholesaler Overview
|
||||
IdsconnectSupplierNew = Create New Wholesaler
|
||||
IdsconnectSupplierRef = Reference/Code
|
||||
IdsconnectSupplierLabel = Name
|
||||
IdsconnectSupplierSoc = Dolibarr Supplier
|
||||
IdsconnectSupplierUrl = Shop URL (IDS Endpoint)
|
||||
IdsconnectSupplierVersion = IDS Version
|
||||
IdsconnectSupplierCustomerNo = Customer Number at Wholesaler
|
||||
IdsconnectSupplierUsername = Username
|
||||
IdsconnectSupplierPassword = Password
|
||||
IdsconnectSupplierTestmode = Test Mode for this Wholesaler
|
||||
IdsconnectSupplierActive = Active
|
||||
IdsconnectSupplierCreated = Wholesaler created successfully
|
||||
IdsconnectSupplierUpdated = Wholesaler updated successfully
|
||||
IdsconnectSupplierDeleted = Wholesaler deleted
|
||||
IdsconnectSupplierNotFound = Wholesaler not found
|
||||
IdsconnectSupplierInactive = Wholesaler is deactivated
|
||||
IdsconnectSupplierConfirmDelete = Do you really want to delete this wholesaler and all associated logs?
|
||||
IdsconnectNoSuppliers = No wholesalers configured. Please create a wholesaler first.
|
||||
|
||||
#
|
||||
# Actions
|
||||
#
|
||||
IdsconnectActionWKE = Receive Cart
|
||||
IdsconnectActionWKS = Send Cart
|
||||
IdsconnectActionADL = Article Deep-Link
|
||||
IdsconnectActionLI = Login Info
|
||||
IdsconnectActionSV = Interface Version
|
||||
IdsconnectOpenShop = Open Shop
|
||||
IdsconnectSendCart = Send Cart
|
||||
IdsconnectReceiveCart = Receive Cart
|
||||
IdsconnectDeepLink = Show Article in Shop
|
||||
|
||||
#
|
||||
# Launcher / Confirmation
|
||||
#
|
||||
IdsconnectLaunchConfirm = IDS Connect - Confirmation
|
||||
IdsconnectLaunchConfirmTitle = Confirm connection to wholesaler
|
||||
IdsconnectLaunchConfirmWarning = WARNING: You are NOT in test mode!
|
||||
IdsconnectLaunchConfirmText = Do you really want to connect to %s (Action: %s)?
|
||||
IdsconnectMissingParams = Missing parameters for IDS Connect
|
||||
|
||||
#
|
||||
# WKS Confirmation
|
||||
#
|
||||
IdsconnectWksConfirmTitle = Send Cart to Wholesaler - Review
|
||||
IdsconnectWksConfirmInfo = The following articles will be sent to %s. Please check quantities and values carefully!
|
||||
IdsconnectWksConfirmSend = Send cart now
|
||||
IdsconnectWksWarningTitle = WARNING - Plausibility check:
|
||||
IdsconnectWksWarnQtyLine = Article %s has an unusually high quantity: %s (threshold: %s)
|
||||
IdsconnectWksWarnValue = Total value %s exceeds the warning threshold of %s
|
||||
IdsconnectWksWarnQty = Quantity warning threshold per line
|
||||
IdsconnectWksWarnQtyHelp = Warning when a line has more than this quantity (0 = disabled)
|
||||
IdsconnectWksWarnValueLabel = Value warning threshold for total order
|
||||
IdsconnectWksWarnValueHelp = Warning when total value exceeds this amount (0 = disabled)
|
||||
IdsconnectWksPin = Security PIN
|
||||
IdsconnectWksPinLabel = WKS Security PIN
|
||||
IdsconnectWksPinHelp = Stored as hash. Must be entered before every cart submission.
|
||||
IdsconnectWksPinInfo = Please enter PIN to confirm submission
|
||||
IdsconnectWksPinPlaceholder = Leave empty = unchanged
|
||||
IdsconnectWksPinWrong = Wrong PIN! Cart was NOT sent.
|
||||
IdsconnectWksPinSet = PIN set
|
||||
IdsconnectWksPinNotSet = No PIN
|
||||
|
||||
#
|
||||
# Cart
|
||||
#
|
||||
IdsconnectCartReview = Cart Review
|
||||
IdsconnectCartReviewTitle = Review Received Articles
|
||||
IdsconnectCartReviewInfo = The following articles were received from the wholesale shop. Review the data and create a supplier order if needed.
|
||||
IdsconnectCartEmpty = No cart available
|
||||
IdsconnectCartArticleNr = Article Number
|
||||
IdsconnectCartDescription = Description
|
||||
IdsconnectCartQty = Quantity
|
||||
IdsconnectCartUnit = Unit
|
||||
IdsconnectCartUnitPrice = Unit Price
|
||||
IdsconnectCartTotalPrice = Total Price
|
||||
IdsconnectCartManufacturer = Manufacturer
|
||||
IdsconnectCartCreateOrder = Create Supplier Order
|
||||
IdsconnectCartImported = Cart successfully imported into supplier order
|
||||
|
||||
#
|
||||
# Log
|
||||
#
|
||||
IdsconnectLogList = Transaction Log
|
||||
IdsconnectLogDate = Date
|
||||
IdsconnectLogSupplier = Wholesaler
|
||||
IdsconnectLogUser = User
|
||||
IdsconnectLogAction = Action
|
||||
IdsconnectLogDirection = Direction
|
||||
IdsconnectLogStatus = Status
|
||||
IdsconnectLogStatusPending = Pending
|
||||
IdsconnectLogStatusSuccess = Success
|
||||
IdsconnectLogStatusError = Error
|
||||
IdsconnectLogStatusCancelled = Cancelled
|
||||
IdsconnectLogNoEntries = No log entries
|
||||
IdsconnectLogDetail = Log Details
|
||||
|
||||
#
|
||||
# Test mode
|
||||
#
|
||||
IdsconnectTestModeActive = TEST MODE ACTIVE
|
||||
IdsconnectTestModeInfo = All connections go to the local mock server. No real orders possible.
|
||||
IdsconnectLiveModeWarning = LIVE MODE - Connections go to the real wholesale shop!
|
||||
|
||||
#
|
||||
# Permissions
|
||||
#
|
||||
Permission50002401 = View IDS Connect module and read logs
|
||||
Permission50002402 = Use IDS Connect interface (carts, deep-links)
|
||||
Permission50002403 = Manage IDS Connect wholesalers and credentials
|
||||
Permission50002404 = Delete IDS Connect data
|
||||
303
launch.php
Normal file
303
launch.php
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/launch.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Launcher - generiert und sendet IDS Connect Formular zum Großhandels-Shop
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
dol_include_once('/idsconnect/class/idsconnect.class.php');
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/lib/idsconnect.lib.php');
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("idsconnect@idsconnect"));
|
||||
|
||||
// Berechtigungsprüfung
|
||||
if (!$user->hasRight('idsconnect', 'use')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Parameter
|
||||
$supplier_id = GETPOSTINT('supplier_id');
|
||||
$action = GETPOST('ids_action', 'alpha');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
$confirm_wks = GETPOST('confirm_wks', 'alpha');
|
||||
$fk_commande = GETPOSTINT('fk_commande');
|
||||
|
||||
// Validierung
|
||||
if (empty($supplier_id) || empty($action)) {
|
||||
setEventMessages($langs->trans("IdsconnectMissingParams"), null, 'errors');
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// CSRF-Schutz für den Launch
|
||||
if (!verifCond(GETPOST('token', 'alpha') == newToken())) {
|
||||
accessforbidden('Bad CSRF token');
|
||||
}
|
||||
|
||||
// Großhändler laden
|
||||
$supplier = new IdsSupplier($db);
|
||||
$result = $supplier->fetch($supplier_id);
|
||||
if ($result <= 0) {
|
||||
setEventMessages($langs->trans("IdsconnectSupplierNotFound"), null, 'errors');
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Prüfen ob der Großhändler aktiv ist
|
||||
if (!$supplier->active) {
|
||||
setEventMessages($langs->trans("IdsconnectSupplierInactive"), null, 'errors');
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// IDS Connect initialisieren
|
||||
$idsconnect = new IdsConnect($db);
|
||||
$testmode = $idsconnect->isTestMode($supplier);
|
||||
|
||||
// Bestätigungsseite bei Live-Modus (extra Sicherheit)
|
||||
if (!$testmode && $confirm !== 'yes') {
|
||||
llxHeader('', $langs->trans("IdsconnectLaunchConfirm"));
|
||||
|
||||
print '<div class="confirmmessage">';
|
||||
print '<h2>'.$langs->trans("IdsconnectLaunchConfirmTitle").'</h2>';
|
||||
print '<p><strong>'.$langs->trans("IdsconnectLaunchConfirmWarning").'</strong></p>';
|
||||
print '<p>'.$langs->trans("IdsconnectLaunchConfirmText", $supplier->label, $action).'</p>';
|
||||
print '<br>';
|
||||
$confirm_url = $_SERVER['PHP_SELF'].'?supplier_id='.$supplier_id.'&ids_action='.urlencode($action).'&confirm=yes&token='.newToken();
|
||||
if ($fk_commande > 0) {
|
||||
$confirm_url .= '&fk_commande='.$fk_commande;
|
||||
}
|
||||
print '<a class="butAction" href="'.$confirm_url.'">'.$langs->trans("Confirm").'</a>';
|
||||
print ' ';
|
||||
print '<a class="butActionDelete" href="'.DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php">'.$langs->trans("Cancel").'</a>';
|
||||
print '</div>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
exit;
|
||||
}
|
||||
|
||||
// Zusätzliche Parameter sammeln
|
||||
$extra = array();
|
||||
if (!empty(GETPOST('artikelnr', 'alphanohtml'))) {
|
||||
$extra['artikelnr'] = GETPOST('artikelnr', 'alphanohtml');
|
||||
}
|
||||
if (!empty(GETPOST('target', 'alpha'))) {
|
||||
$extra['target'] = GETPOST('target', 'alpha');
|
||||
}
|
||||
|
||||
// Bei WKS: Bestellpositionen aus Dolibarr-Bestellung als Warenkorb-XML generieren
|
||||
$cart_lines = array();
|
||||
if ($action === 'WKS' && $fk_commande > 0) {
|
||||
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
|
||||
$order = new CommandeFournisseur($db);
|
||||
$order->fetch($fk_commande);
|
||||
$order->fetch_lines();
|
||||
|
||||
if (count($order->lines) > 0) {
|
||||
$cart_lines = array();
|
||||
foreach ($order->lines as $line) {
|
||||
$cart_lines[] = array(
|
||||
'artikelnr' => $line->ref_supplier ?: ($line->product_ref ?: ''),
|
||||
'bezeichnung' => $line->desc ?: ($line->product_label ?: ''),
|
||||
'menge' => $line->qty,
|
||||
'einheit' => 'PCE',
|
||||
'einzelpreis' => $line->subprice,
|
||||
'mwst_satz' => $line->tva_tx,
|
||||
);
|
||||
}
|
||||
$extra['warenkorb'] = $idsconnect->buildCartXml($cart_lines);
|
||||
} else {
|
||||
setEventMessages('Bestellung hat keine Positionen', null, 'errors');
|
||||
header('Location: '.DOL_URL_ROOT.'/fourn/commande/card.php?id='.$fk_commande);
|
||||
exit;
|
||||
}
|
||||
} elseif (!empty(GETPOST('warenkorb', 'none'))) {
|
||||
$extra['warenkorb'] = GETPOST('warenkorb', 'none');
|
||||
}
|
||||
|
||||
// WKS-PIN prüfen wenn Bestätigung kommt
|
||||
if ($action === 'WKS' && $confirm_wks === 'yes') {
|
||||
$stored_hash = getDolGlobalString('IDSCONNECT_WKS_PIN');
|
||||
if (!empty($stored_hash)) {
|
||||
$pin = GETPOST('wks_pin', 'none');
|
||||
if (empty($pin) || !password_verify($pin, $stored_hash)) {
|
||||
setEventMessages($langs->trans("IdsconnectWksPinWrong"), null, 'errors');
|
||||
$confirm_wks = ''; // Bestätigungsseite erneut zeigen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WKS-Bestätigung: Bestellinhalt prüfen bevor gesendet wird
|
||||
if ($action === 'WKS' && !empty($cart_lines) && $confirm_wks !== 'yes') {
|
||||
$wks_warn_qty = getDolGlobalInt('IDSCONNECT_WKS_WARN_QTY', 100);
|
||||
$wks_warn_value = (float) getDolGlobalString('IDSCONNECT_WKS_WARN_VALUE', '10000');
|
||||
|
||||
llxHeader('', $langs->trans("IdsconnectWksConfirmTitle"), '', '', 0, 0, '', '', '', 'mod-idsconnect page-wks_confirm');
|
||||
|
||||
print load_fiche_titre($langs->trans("IdsconnectWksConfirmTitle"), '', 'fa-paper-plane');
|
||||
|
||||
idsconnectShowTestModeBanner();
|
||||
|
||||
// Info-Box
|
||||
print '<div class="info">';
|
||||
print '<strong>'.$langs->trans("IdsconnectWksConfirmInfo", $supplier->label).'</strong>';
|
||||
print '</div>';
|
||||
|
||||
// Artikeltabelle
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans("IdsconnectCartArticleNr").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectCartDescription").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartQty").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectCartUnit").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartUnitPrice").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartTotalPrice").'</th>';
|
||||
print '</tr>';
|
||||
|
||||
$total = 0;
|
||||
$warnings = array();
|
||||
foreach ($cart_lines as $item) {
|
||||
$line_total = (float) $item['menge'] * (float) $item['einzelpreis'];
|
||||
$total += $line_total;
|
||||
$qty_warn = ((float) $item['menge'] > $wks_warn_qty);
|
||||
|
||||
if ($qty_warn) {
|
||||
$warnings[] = $langs->trans("IdsconnectWksWarnQtyLine", $item['artikelnr'], (float) $item['menge'], $wks_warn_qty);
|
||||
}
|
||||
|
||||
print '<tr class="oddeven'.($qty_warn ? ' idsconnect-warn-row' : '').'">';
|
||||
print '<td><code>'.htmlspecialchars($item['artikelnr']).'</code></td>';
|
||||
print '<td>'.htmlspecialchars($item['bezeichnung']).'</td>';
|
||||
print '<td class="right">'.($qty_warn ? '<strong class="idsconnect-warn-text">' : '').((float) $item['menge']).($qty_warn ? '</strong>' : '').'</td>';
|
||||
print '<td>'.htmlspecialchars($item['einheit']).'</td>';
|
||||
print '<td class="right">'.price($item['einzelpreis']).'</td>';
|
||||
print '<td class="right">'.price($line_total).'</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
|
||||
print '<tr class="liste_total">';
|
||||
print '<td colspan="5" class="right"><strong>'.$langs->trans("Total").'</strong></td>';
|
||||
print '<td class="right"><strong>'.price($total).'</strong></td>';
|
||||
print '</tr>';
|
||||
print '</table>';
|
||||
|
||||
// Wertwarnung
|
||||
if ($total > $wks_warn_value) {
|
||||
$warnings[] = $langs->trans("IdsconnectWksWarnValue", price($total), price($wks_warn_value));
|
||||
}
|
||||
|
||||
// Warnungen anzeigen
|
||||
if (!empty($warnings)) {
|
||||
print '<div class="idsconnect-wks-warning">';
|
||||
print '<strong>'.$langs->trans("IdsconnectWksWarningTitle").'</strong><br>';
|
||||
foreach ($warnings as $w) {
|
||||
print '- '.$w.'<br>';
|
||||
}
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Formular mit PIN-Eingabe und Absenden
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="supplier_id" value="'.$supplier_id.'">';
|
||||
print '<input type="hidden" name="ids_action" value="WKS">';
|
||||
print '<input type="hidden" name="fk_commande" value="'.$fk_commande.'">';
|
||||
print '<input type="hidden" name="confirm_wks" value="yes">';
|
||||
if (!$testmode) {
|
||||
print '<input type="hidden" name="confirm" value="yes">';
|
||||
}
|
||||
|
||||
// PIN-Eingabe (wenn konfiguriert)
|
||||
$has_pin = !empty(getDolGlobalString('IDSCONNECT_WKS_PIN'));
|
||||
if ($has_pin) {
|
||||
print '<div class="idsconnect-pin-box">';
|
||||
print '<label><strong>'.$langs->trans("IdsconnectWksPin").':</strong></label> ';
|
||||
print '<input type="password" name="wks_pin" required autocomplete="off" class="width100" placeholder="****">';
|
||||
print ' <span class="opacitymedium">'.$langs->trans("IdsconnectWksPinInfo").'</span>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print '<div class="tabsAction">';
|
||||
print '<input type="submit" class="butAction" value="'.$langs->trans("IdsconnectWksConfirmSend").'">';
|
||||
print ' ';
|
||||
print '<a class="butActionDelete" href="'.DOL_URL_ROOT.'/fourn/commande/card.php?id='.$fk_commande.'">'.$langs->trans("Cancel").'</a>';
|
||||
print '</div>';
|
||||
print '</form>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
exit;
|
||||
}
|
||||
|
||||
// Bei WKS: Bestellstatus auf "Bestellt" setzen
|
||||
if ($action === 'WKS' && $fk_commande > 0 && isset($order) && $order->id > 0) {
|
||||
if ($order->statut == CommandeFournisseur::STATUS_ACCEPTED) {
|
||||
$order->commande($user);
|
||||
dol_syslog("IDS Connect: Bestellstatus auf 'Bestellt' gesetzt für Bestellung #".$order->id, LOG_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
// Formular generieren
|
||||
$result = $idsconnect->buildLaunchForm($supplier, $action, $user, $extra);
|
||||
|
||||
if ($result === false) {
|
||||
setEventMessages($idsconnect->error, null, 'errors');
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Testmodus-Hinweis loggen
|
||||
if ($result['testmode']) {
|
||||
dol_syslog("IDS Connect Launch: TESTMODUS aktiv - Verbindung geht zum Mock-Server", LOG_INFO);
|
||||
}
|
||||
|
||||
// HTML-Formular direkt ausgeben (leitet zum Shop weiter)
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
echo $result['html'];
|
||||
exit;
|
||||
94
lib/idsconnect.lib.php
Executable file
94
lib/idsconnect.lib.php
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/lib/idsconnect.lib.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Bibliotheksfunktionen für IDS Connect
|
||||
*/
|
||||
|
||||
/**
|
||||
* Admin-Tabs vorbereiten
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function idsconnectAdminPrepareHead()
|
||||
{
|
||||
global $langs, $conf;
|
||||
|
||||
$langs->load("idsconnect@idsconnect");
|
||||
|
||||
$h = 0;
|
||||
$head = array();
|
||||
|
||||
$head[$h][0] = dol_buildpath("/idsconnect/admin/setup.php", 1);
|
||||
$head[$h][1] = $langs->trans("Settings");
|
||||
$head[$h][2] = 'settings';
|
||||
$h++;
|
||||
|
||||
$head[$h][0] = dol_buildpath("/idsconnect/admin/about.php", 1);
|
||||
$head[$h][1] = $langs->trans("About");
|
||||
$head[$h][2] = 'about';
|
||||
$h++;
|
||||
|
||||
complete_head_from_modules($conf, $langs, null, $head, $h, 'idsconnect@idsconnect');
|
||||
complete_head_from_modules($conf, $langs, null, $head, $h, 'idsconnect@idsconnect', 'remove');
|
||||
|
||||
return $head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Großhändler-Karte Tabs vorbereiten
|
||||
*
|
||||
* @param object $object Großhändler-Objekt
|
||||
* @return array
|
||||
*/
|
||||
function idsconnectSupplierPrepareHead($object)
|
||||
{
|
||||
global $langs, $conf;
|
||||
|
||||
$langs->load("idsconnect@idsconnect");
|
||||
|
||||
$h = 0;
|
||||
$head = array();
|
||||
|
||||
$head[$h][0] = dol_buildpath("/idsconnect/supplier_card.php", 1).'?id='.$object->id;
|
||||
$head[$h][1] = $langs->trans("IdsconnectSupplierCard");
|
||||
$head[$h][2] = 'supplier';
|
||||
$h++;
|
||||
|
||||
$head[$h][0] = dol_buildpath("/idsconnect/log_list.php", 1).'?supplier_id='.$object->id;
|
||||
$head[$h][1] = $langs->trans("IdsconnectLog");
|
||||
$head[$h][2] = 'log';
|
||||
$h++;
|
||||
|
||||
return $head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Testmodus-Banner ausgeben
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function idsconnectShowTestModeBanner()
|
||||
{
|
||||
global $langs;
|
||||
$langs->load("idsconnect@idsconnect");
|
||||
|
||||
if (getDolGlobalInt('IDSCONNECT_TESTMODE')) {
|
||||
print '<div class="idsconnect-testmode-banner">';
|
||||
print '<strong>'.$langs->trans("IdsconnectTestModeActive").'</strong> - ';
|
||||
print $langs->trans("IdsconnectTestModeInfo");
|
||||
print '</div>';
|
||||
} else {
|
||||
print '<div class="idsconnect-livemode-banner">';
|
||||
print '<strong>'.$langs->trans("IdsconnectLiveModeWarning").'</strong>';
|
||||
print '</div>';
|
||||
}
|
||||
}
|
||||
334
log_detail.php
Normal file
334
log_detail.php
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/log_detail.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Detailansicht eines Log-Eintrags
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
dol_include_once('/idsconnect/class/idslog.class.php');
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/lib/idsconnect.lib.php');
|
||||
|
||||
/**
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("idsconnect@idsconnect"));
|
||||
|
||||
if (!$user->hasRight('idsconnect', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
$id = GETPOSTINT('id');
|
||||
|
||||
if (empty($id)) {
|
||||
setEventMessages('Keine Log-ID angegeben', null, 'errors');
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/idsconnect/log_list.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Log laden
|
||||
$log = new IdsLog($db);
|
||||
$result = $log->fetch($id);
|
||||
|
||||
if ($result <= 0) {
|
||||
setEventMessages('Log-Eintrag #'.$id.' nicht gefunden', null, 'errors');
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/idsconnect/log_list.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Großhändler laden
|
||||
$supplier = new IdsSupplier($db);
|
||||
$supplier->fetch($log->fk_supplier);
|
||||
|
||||
// Benutzer laden
|
||||
$userObj = new User($db);
|
||||
$userObj->fetch($log->fk_user);
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
llxHeader('', $langs->trans("IdsconnectLogDetail").' #'.$id, '', '', 0, 0, '', '', '', 'mod-idsconnect page-log_detail');
|
||||
|
||||
print load_fiche_titre($langs->trans("IdsconnectLogDetail").' #'.$id, '<a href="'.DOL_URL_ROOT.'/custom/idsconnect/log_list.php">'.$langs->trans("IdsconnectLogList").'</a>', 'fa-search');
|
||||
|
||||
// ============================================================
|
||||
// Basis-Informationen
|
||||
// ============================================================
|
||||
print '<div class="underbanner clearboth"></div>';
|
||||
print '<table class="border centpercent tableforfield">';
|
||||
|
||||
print '<tr><td class="titlefield">'.$langs->trans("ID").'</td>';
|
||||
print '<td>'.$log->id.'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectLogDate").'</td>';
|
||||
print '<td>'.dol_print_date($log->date_creation, 'dayhour').'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectLogSupplier").'</td>';
|
||||
print '<td>';
|
||||
if ($supplier->id > 0) {
|
||||
print '<a href="'.DOL_URL_ROOT.'/custom/idsconnect/supplier_card.php?id='.$supplier->id.'">'.htmlspecialchars($supplier->label).'</a>';
|
||||
print ' <span class="opacitymedium">('.htmlspecialchars($supplier->ref).')</span>';
|
||||
} else {
|
||||
print '<span class="opacitymedium">Großhändler #'.$log->fk_supplier.' (gelöscht?)</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectLogUser").'</td>';
|
||||
print '<td>'.($userObj->id > 0 ? $userObj->getNomUrl(1) : 'User #'.$log->fk_user).'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectLogAction").'</td>';
|
||||
print '<td><strong>'.$log->getActionLabel().'</strong> ('.$log->action_type.')</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectLogDirection").'</td>';
|
||||
print '<td>'.$log->direction.'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectLogStatus").'</td>';
|
||||
print '<td>'.$log->getStatusLabel().'</td></tr>';
|
||||
|
||||
print '<tr><td>IP-Adresse</td>';
|
||||
print '<td>'.htmlspecialchars($log->ip_address ?: '-').'</td></tr>';
|
||||
|
||||
if ($log->fk_commande > 0) {
|
||||
print '<tr><td>Lieferantenbestellung</td>';
|
||||
print '<td><a href="'.DOL_URL_ROOT.'/fourn/commande/card.php?id='.$log->fk_commande.'">#'.$log->fk_commande.'</a></td></tr>';
|
||||
}
|
||||
|
||||
if (!empty($log->error_message)) {
|
||||
print '<tr><td>Fehlermeldung</td>';
|
||||
print '<td><span class="error">'.htmlspecialchars($log->error_message).'</span></td></tr>';
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
// ============================================================
|
||||
// Request-Daten (was wurde gesendet)
|
||||
// ============================================================
|
||||
if (!empty($log->request_data)) {
|
||||
print '<br>';
|
||||
print load_fiche_titre('Request-Daten (gesendet)', '', 'fa-arrow-up');
|
||||
|
||||
$request = json_decode($log->request_data, true);
|
||||
if (is_array($request)) {
|
||||
print '<table class="border centpercent tableforfield">';
|
||||
foreach ($request as $key => $value) {
|
||||
print '<tr>';
|
||||
print '<td class="titlefield"><code>'.$key.'</code></td>';
|
||||
print '<td>';
|
||||
if (is_array($value)) {
|
||||
print '<pre class="idsconnect-log-detail" style="max-height:200px">'.htmlspecialchars(json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)).'</pre>';
|
||||
} elseif ($key === 'hookurl' || $key === 'target_url' || $key === 'shop_url') {
|
||||
print '<code>'.htmlspecialchars($value).'</code>';
|
||||
} elseif ($key === 'testmode') {
|
||||
print $value ? '<span class="badge badge-warning">Testmodus</span>' : '<span class="badge badge-danger">LIVE</span>';
|
||||
} else {
|
||||
print htmlspecialchars($value);
|
||||
}
|
||||
print '</td></tr>';
|
||||
}
|
||||
print '</table>';
|
||||
} else {
|
||||
print '<div class="idsconnect-log-detail">'.htmlspecialchars($log->request_data).'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Response-Daten (was kam zurück)
|
||||
// ============================================================
|
||||
if (!empty($log->response_data)) {
|
||||
print '<br>';
|
||||
print load_fiche_titre('Response-Daten (empfangen)', '', 'fa-arrow-down');
|
||||
|
||||
$response = json_decode($log->response_data, true);
|
||||
if (is_array($response)) {
|
||||
// Callback-Meta-Informationen separat anzeigen
|
||||
if (!empty($response['callback_meta'])) {
|
||||
print '<table class="border centpercent tableforfield">';
|
||||
print '<tr class="liste_titre"><td colspan="2">Callback-Informationen</td></tr>';
|
||||
foreach ($response['callback_meta'] as $key => $value) {
|
||||
print '<tr>';
|
||||
print '<td class="titlefield"><code>'.$key.'</code></td>';
|
||||
print '<td>';
|
||||
if (is_array($value)) {
|
||||
print htmlspecialchars(implode(', ', $value));
|
||||
} else {
|
||||
print htmlspecialchars($value);
|
||||
}
|
||||
print '</td></tr>';
|
||||
}
|
||||
print '</table>';
|
||||
print '<br>';
|
||||
}
|
||||
|
||||
// Empfangene Artikel anzeigen
|
||||
if (!empty($response['items']) && is_array($response['items'])) {
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans("IdsconnectCartArticleNr").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectCartDescription").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartQty").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectCartUnit").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartUnitPrice").'</th>';
|
||||
print '<th class="right">'.$langs->trans("IdsconnectCartTotalPrice").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectCartManufacturer").'</th>';
|
||||
print '</tr>';
|
||||
|
||||
$total = 0;
|
||||
foreach ($response['items'] as $item) {
|
||||
$line_total = (float) ($item['gesamtpreis'] ?? ((float) ($item['menge'] ?? 0) * (float) ($item['einzelpreis'] ?? 0)));
|
||||
$total += $line_total;
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
print '<td><code>'.htmlspecialchars($item['artikelnr'] ?? '-').'</code></td>';
|
||||
print '<td>'.htmlspecialchars($item['bezeichnung'] ?? '-').'</td>';
|
||||
print '<td class="right">'.((float) ($item['menge'] ?? 0)).'</td>';
|
||||
print '<td>'.htmlspecialchars($item['einheit'] ?? 'STK').'</td>';
|
||||
print '<td class="right">'.price($item['einzelpreis'] ?? 0).'</td>';
|
||||
print '<td class="right">'.price($line_total).'</td>';
|
||||
print '<td>'.htmlspecialchars($item['hersteller'] ?? '-').'</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
|
||||
print '<tr class="liste_total">';
|
||||
print '<td colspan="5" class="right"><strong>'.$langs->trans("Total").'</strong></td>';
|
||||
print '<td class="right"><strong>'.price($total).'</strong></td>';
|
||||
print '<td></td></tr>';
|
||||
print '</table>';
|
||||
}
|
||||
|
||||
// Restliche Response-Felder
|
||||
$skip_keys = array('callback_meta', 'items');
|
||||
$remaining = array_diff_key($response, array_flip($skip_keys));
|
||||
if (!empty($remaining)) {
|
||||
print '<br><table class="border centpercent tableforfield">';
|
||||
print '<tr class="liste_titre"><td colspan="2">Weitere Response-Daten</td></tr>';
|
||||
foreach ($remaining as $key => $value) {
|
||||
print '<tr>';
|
||||
print '<td class="titlefield"><code>'.$key.'</code></td>';
|
||||
print '<td>';
|
||||
if (is_array($value)) {
|
||||
print '<pre class="idsconnect-log-detail" style="max-height:200px">'.htmlspecialchars(json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)).'</pre>';
|
||||
} else {
|
||||
print htmlspecialchars($value);
|
||||
}
|
||||
print '</td></tr>';
|
||||
}
|
||||
print '</table>';
|
||||
}
|
||||
} else {
|
||||
print '<div class="idsconnect-log-detail">'.htmlspecialchars($log->response_data).'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Warenkorb-XML (Rohdaten)
|
||||
// ============================================================
|
||||
if (!empty($log->cart_xml)) {
|
||||
print '<br>';
|
||||
print load_fiche_titre('Warenkorb-XML ('.strlen($log->cart_xml).' Bytes)', '', 'fa-code');
|
||||
|
||||
// XML formatiert ausgeben
|
||||
$dom = new DOMDocument('1.0');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$formatted_xml = $log->cart_xml;
|
||||
if (@$dom->loadXML($log->cart_xml)) {
|
||||
$formatted_xml = $dom->saveXML();
|
||||
}
|
||||
|
||||
print '<div class="idsconnect-log-detail">';
|
||||
print htmlspecialchars($formatted_xml);
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Callback-Token (für Debugging)
|
||||
// ============================================================
|
||||
if (!empty($log->callback_token)) {
|
||||
print '<br>';
|
||||
print load_fiche_titre('Callback-Token', '', 'fa-key');
|
||||
print '<table class="border centpercent tableforfield">';
|
||||
print '<tr><td class="titlefield">Token</td>';
|
||||
print '<td><code class="small">'.htmlspecialchars($log->callback_token).'</code></td></tr>';
|
||||
print '<tr><td>Token-Status</td>';
|
||||
print '<td>';
|
||||
if ($log->status === 'pending') {
|
||||
$token_age = dol_now() - $log->date_creation;
|
||||
if ($token_age < 7200) {
|
||||
$remaining = 7200 - $token_age;
|
||||
print '<span class="badge badge-status4">Gültig (noch '.round($remaining / 60).' Min.)</span>';
|
||||
} else {
|
||||
print '<span class="badge badge-status8">Abgelaufen</span>';
|
||||
}
|
||||
} else {
|
||||
print '<span class="badge badge-status6">Eingelöst ('.$log->status.')</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
print '</table>';
|
||||
}
|
||||
|
||||
// Aktionsbuttons
|
||||
print '<div class="tabsAction">';
|
||||
|
||||
// Warenkorb prüfen / Bestellung erstellen - wenn WKE mit Artikeln und noch keine Bestellung verknüpft
|
||||
if ($log->action_type === 'WKE' && $log->status === 'success' && $log->fk_commande <= 0) {
|
||||
$response_check = json_decode($log->response_data, true);
|
||||
if (!empty($response_check['items']) || !empty($log->cart_xml)) {
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/cart_review.php?log_id='.$log->id.'">'.$langs->trans("IdsconnectCartCreateOrder").'</a>';
|
||||
}
|
||||
}
|
||||
|
||||
// Link zur verknüpften Bestellung wenn vorhanden
|
||||
if ($log->fk_commande > 0) {
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/fourn/commande/card.php?id='.$log->fk_commande.'">Bestellung #'.$log->fk_commande.' anzeigen</a>';
|
||||
}
|
||||
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/log_list.php';
|
||||
if ($log->fk_supplier > 0) {
|
||||
print '?supplier_id='.$log->fk_supplier;
|
||||
}
|
||||
print '">'.$langs->trans("Back").'</a>';
|
||||
print '</div>';
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
137
log_list.php
Normal file
137
log_list.php
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/log_list.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Transaktionslog anzeigen
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
dol_include_once('/idsconnect/class/idslog.class.php');
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/lib/idsconnect.lib.php');
|
||||
|
||||
/**
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("idsconnect@idsconnect"));
|
||||
|
||||
if (!$user->hasRight('idsconnect', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
$supplier_id = GETPOSTINT('supplier_id');
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
llxHeader('', $langs->trans("IdsconnectLogList"), '', '', 0, 0, '', '', '', 'mod-idsconnect page-log_list');
|
||||
|
||||
// Wenn für einen bestimmten Supplier: Tabs anzeigen
|
||||
if ($supplier_id > 0) {
|
||||
$supplier = new IdsSupplier($db);
|
||||
$supplier->fetch($supplier_id);
|
||||
if ($supplier->id > 0) {
|
||||
$head = idsconnectSupplierPrepareHead($supplier);
|
||||
print dol_get_fiche_head($head, 'log', $langs->trans("IdsconnectSupplierCard").' - '.htmlspecialchars($supplier->label), -1, 'fa-plug');
|
||||
}
|
||||
} else {
|
||||
print load_fiche_titre($langs->trans("IdsconnectLogList"), '', 'fa-list-alt');
|
||||
}
|
||||
|
||||
$logObj = new IdsLog($db);
|
||||
$logs = $logObj->fetchLast(100, $supplier_id);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans("ID").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogDate").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogSupplier").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogUser").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogAction").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogDirection").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectLogStatus").'</th>';
|
||||
print '<th>'.$langs->trans("IP").'</th>';
|
||||
print '</tr>';
|
||||
|
||||
if (is_array($logs) && count($logs) > 0) {
|
||||
foreach ($logs as $log) {
|
||||
$sup = new IdsSupplier($db);
|
||||
$sup->fetch($log->fk_supplier);
|
||||
|
||||
$userObj = new User($db);
|
||||
$userObj->fetch($log->fk_user);
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
print '<td><a href="'.DOL_URL_ROOT.'/custom/idsconnect/log_detail.php?id='.$log->id.'">'.$log->id.'</a></td>';
|
||||
print '<td class="nowrap"><a href="'.DOL_URL_ROOT.'/custom/idsconnect/log_detail.php?id='.$log->id.'">'.dol_print_date($log->date_creation, 'dayhour').'</a></td>';
|
||||
print '<td>';
|
||||
if ($sup->id > 0) {
|
||||
print '<a href="'.DOL_URL_ROOT.'/custom/idsconnect/supplier_card.php?id='.$sup->id.'">'.htmlspecialchars($sup->label).'</a>';
|
||||
}
|
||||
print '</td>';
|
||||
print '<td>'.($userObj->id > 0 ? $userObj->getNomUrl(1) : '-').'</td>';
|
||||
print '<td>'.$log->getActionLabel().'</td>';
|
||||
print '<td>'.$log->direction.'</td>';
|
||||
print '<td>'.$log->getStatusLabel().'</td>';
|
||||
print '<td class="small">'.htmlspecialchars($log->ip_address ?: '-').'</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Detail-Zeile bei Fehler
|
||||
if ($log->status == 'error' && !empty($log->error_message)) {
|
||||
print '<tr class="oddeven"><td></td><td colspan="7">';
|
||||
print '<span class="opacitymedium small">Fehler: '.htmlspecialchars($log->error_message).'</span>';
|
||||
print '</td></tr>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="8" class="opacitymedium">'.$langs->trans("IdsconnectLogNoEntries").'</td></tr>';
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
if ($supplier_id > 0) {
|
||||
print dol_get_fiche_end();
|
||||
}
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
338
mockserver.php
Normal file
338
mockserver.php
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/mockserver.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Mock-Server der einen Großhandels-Shop simuliert
|
||||
*
|
||||
* Dieser Server empfängt IDS Connect Anfragen und simuliert einen Shop.
|
||||
* Damit können alle Funktionen getestet werden, ohne echte Bestellungen auszulösen.
|
||||
*/
|
||||
|
||||
// Dolibarr laden (ohne Login für den Mock-Server)
|
||||
define('NOLOGIN', 1);
|
||||
define('NOCSRFCHECK', 1);
|
||||
define('NOREQUIREMENU', 1);
|
||||
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
// Nur im Testmodus erreichbar
|
||||
if (!getDolGlobalInt('IDSCONNECT_TESTMODE')) {
|
||||
http_response_code(403);
|
||||
die("Mock-Server ist nur im Testmodus verfügbar");
|
||||
}
|
||||
|
||||
// IDS-Parameter aus POST lesen
|
||||
$kndnr = GETPOST('kndnr', 'alphanohtml') ?: '';
|
||||
$name_kunde = GETPOST('name_kunde', 'alphanohtml') ?: '';
|
||||
$pw_kunde = GETPOST('pw_kunde', 'none') ?: '';
|
||||
$version = GETPOST('version', 'alphanohtml') ?: '2.5';
|
||||
$ids_action = GETPOST('action', 'alpha') ?: '';
|
||||
$hookurl = GETPOST('hookurl', 'alphanohtml') ?: '';
|
||||
$warenkorb_xml = GETPOST('warenkorb', 'none') ?: '';
|
||||
$artikelnr = GETPOST('artikelnr', 'alphanohtml') ?: '';
|
||||
|
||||
// Mock-Server Action bestimmen
|
||||
$mock_action = GETPOST('mock_action', 'alpha') ?: '';
|
||||
|
||||
// Beispiel-Artikel für den Mock-Shop
|
||||
$mock_articles = array(
|
||||
array('nr' => '1234567', 'bezeichnung' => 'Leitungsschutzschalter B16 1-polig', 'preis' => 5.42, 'einheit' => 'STK', 'hersteller' => 'Hager', 'ean' => '3250613160012'),
|
||||
array('nr' => '2345678', 'bezeichnung' => 'FI-Schutzschalter 40A 30mA 4-polig', 'preis' => 28.90, 'einheit' => 'STK', 'hersteller' => 'ABB', 'ean' => '7612271443526'),
|
||||
array('nr' => '3456789', 'bezeichnung' => 'NYM-J 3x1,5 mm² (100m Ring)', 'preis' => 62.50, 'einheit' => 'STK', 'hersteller' => 'Lapp', 'ean' => '4044774881328'),
|
||||
array('nr' => '4567890', 'bezeichnung' => 'Schalterdose UP D=60mm', 'preis' => 0.35, 'einheit' => 'STK', 'hersteller' => 'Kaiser', 'ean' => '4013456000216'),
|
||||
array('nr' => '5678901', 'bezeichnung' => 'Zählerschrank 1-Feld komplett', 'preis' => 189.00, 'einheit' => 'STK', 'hersteller' => 'Hager', 'ean' => '3250616167483'),
|
||||
array('nr' => '6789012', 'bezeichnung' => 'Wallbox 11kW mit Ladesteckdose Typ 2', 'preis' => 495.00, 'einheit' => 'STK', 'hersteller' => 'ABL', 'ean' => '4015141000001'),
|
||||
array('nr' => '7890123', 'bezeichnung' => 'SLS-Schalter E-Charakteristik 35A 1-polig', 'preis' => 22.80, 'einheit' => 'STK', 'hersteller' => 'Hager', 'ean' => '3250613211363'),
|
||||
array('nr' => '8901234', 'bezeichnung' => 'Überspannungsschutz Typ 2 TN-S', 'preis' => 78.50, 'einheit' => 'STK', 'hersteller' => 'Dehn', 'ean' => '4013364108530'),
|
||||
);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Warenkorb an HOOKURL zurücksenden
|
||||
// ============================================================
|
||||
if ($mock_action === 'send_cart' && !empty($hookurl)) {
|
||||
$cart_items = array();
|
||||
if (!empty($_POST['cart_items']) && is_array($_POST['cart_items'])) {
|
||||
foreach ($_POST['cart_items'] as $idx => $item) {
|
||||
$qty = (int) ($item['qty'] ?? 0);
|
||||
if ($qty > 0 && isset($mock_articles[$idx])) {
|
||||
$art = $mock_articles[$idx];
|
||||
$cart_items[] = array(
|
||||
'nr' => $art['nr'],
|
||||
'bezeichnung' => $art['bezeichnung'],
|
||||
'menge' => $qty,
|
||||
'einheit' => $art['einheit'],
|
||||
'einzelpreis' => $art['preis'],
|
||||
'ean' => $art['ean'],
|
||||
'hersteller' => $art['hersteller'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($cart_items)) {
|
||||
$error_msg = 'Keine Artikel ausgewählt';
|
||||
} else {
|
||||
// Warenkorb-XML im IDS Connect 2.5 Format generieren
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
|
||||
$xml .= '<Warenkorb xmlns="http://www.itek.de/Shop-Anbindung/Warenkorb/">'."\n";
|
||||
$xml .= ' <WarenkorbInfo>'."\n";
|
||||
$xml .= ' <Date>'.date('Y-m-d').'</Date>'."\n";
|
||||
$xml .= ' <Time>'.date('H:i:s').'</Time>'."\n";
|
||||
$xml .= ' <RueckgabeKZ>Warenkorbruückgabe</RueckgabeKZ>'."\n";
|
||||
$xml .= ' <Version>2.5</Version>'."\n";
|
||||
$xml .= ' </WarenkorbInfo>'."\n";
|
||||
$xml .= ' <Order>'."\n";
|
||||
$xml .= ' <OrderInfo>'."\n";
|
||||
$xml .= ' <ModeOfShipment>Lieferung</ModeOfShipment>'."\n";
|
||||
$xml .= ' <Cur>EUR</Cur>'."\n";
|
||||
$xml .= ' </OrderInfo>'."\n";
|
||||
foreach ($cart_items as $item) {
|
||||
$xml .= ' <OrderItem>'."\n";
|
||||
$xml .= ' <ArtNo>'.htmlspecialchars($item['nr'], ENT_XML1).'</ArtNo>'."\n";
|
||||
$xml .= ' <Qty>'.$item['menge'].'</Qty>'."\n";
|
||||
$xml .= ' <QU>'.htmlspecialchars($item['einheit'], ENT_XML1).'</QU>'."\n";
|
||||
$xml .= ' <Kurztext>'.htmlspecialchars($item['bezeichnung'], ENT_XML1).'</Kurztext>'."\n";
|
||||
$xml .= ' <NetPrice>'.$item['einzelpreis'].'</NetPrice>'."\n";
|
||||
$xml .= ' <OfferPrice>'.$item['einzelpreis'].'</OfferPrice>'."\n";
|
||||
$xml .= ' <PriceBasis>1</PriceBasis>'."\n";
|
||||
$xml .= ' <VAT>19</VAT>'."\n";
|
||||
$xml .= ' <EAN>'.htmlspecialchars($item['ean'], ENT_XML1).'</EAN>'."\n";
|
||||
$xml .= ' <ManufacturerID>'.htmlspecialchars($item['hersteller'], ENT_XML1).'</ManufacturerID>'."\n";
|
||||
$xml .= ' </OrderItem>'."\n";
|
||||
}
|
||||
$xml .= ' </Order>'."\n";
|
||||
$xml .= '</Warenkorb>';
|
||||
|
||||
// Per Redirect mit POST-Daten an HOOKURL senden
|
||||
// Wir nutzen ein Auto-Submit-Formular (wie IDS Connect es spezifiziert)
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
echo '<!DOCTYPE html><html><head><meta charset="UTF-8">';
|
||||
echo '<title>Mock-Server - Warenkorb wird gesendet...</title></head>';
|
||||
echo '<body onload="document.forms[0].submit();">';
|
||||
echo '<p>Warenkorb wird an Dolibarr gesendet ('.count($cart_items).' Artikel)...</p>';
|
||||
echo '<form method="post" action="'.htmlspecialchars($hookurl).'" enctype="multipart/form-data">';
|
||||
echo '<input type="hidden" name="warenkorb" value="'.htmlspecialchars($xml).'">';
|
||||
echo '<noscript><input type="submit" value="Warenkorb senden"></noscript>';
|
||||
echo '</form></body></html>';
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Mock-Shop HTML ausgeben
|
||||
// ============================================================
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>IDS Connect Mock-Server - Testshop</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 900px; margin: 20px auto; padding: 0 20px; background: #f5f5f5; }
|
||||
.header { background: #e74c3c; color: white; padding: 15px 20px; border-radius: 5px 5px 0 0; }
|
||||
.header h1 { margin: 0; font-size: 20px; }
|
||||
.header .badge { background: #c0392b; padding: 3px 10px; border-radius: 3px; font-size: 12px; margin-left: 10px; }
|
||||
.content { background: white; padding: 20px; border-radius: 0 0 5px 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
|
||||
.info { background: #ffeaa7; padding: 10px; border-radius: 3px; margin-bottom: 15px; border-left: 4px solid #fdcb6e; }
|
||||
.credentials { background: #dfe6e9; padding: 10px; border-radius: 3px; margin-bottom: 15px; font-size: 13px; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
|
||||
th { background: #2c3e50; color: white; padding: 10px; text-align: left; }
|
||||
td { padding: 8px 10px; border-bottom: 1px solid #eee; }
|
||||
tr:hover { background: #f8f9fa; }
|
||||
input[type="number"] { width: 60px; padding: 5px; border: 1px solid #ccc; border-radius: 3px; }
|
||||
.btn { background: #27ae60; color: white; padding: 10px 25px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; }
|
||||
.btn:hover { background: #219a52; }
|
||||
.btn-cancel { background: #95a5a6; }
|
||||
.btn-cancel:hover { background: #7f8c8d; }
|
||||
.price { text-align: right; font-family: monospace; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🔌 Mock-Elektrogroßhandel <span class="badge">TESTMODUS</span></h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="info">
|
||||
<strong>⚠️ Dies ist ein Testshop!</strong> Keine echten Bestellungen werden ausgelöst.
|
||||
Alle Daten sind Beispieldaten für Entwicklungszwecke.
|
||||
</div>
|
||||
|
||||
<?php if (!empty($error_msg)): ?>
|
||||
<div style="background:#fab1a0; padding:10px; border-radius:3px; margin-bottom:15px;">
|
||||
<strong>Fehler:</strong> <?php echo htmlspecialchars($error_msg); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="credentials">
|
||||
<strong>Empfangene IDS-Daten:</strong>
|
||||
Kundennr: <code><?php echo htmlspecialchars($kndnr); ?></code> |
|
||||
Benutzer: <code><?php echo htmlspecialchars($name_kunde); ?></code> |
|
||||
Version: <code><?php echo htmlspecialchars($version); ?></code> |
|
||||
Action: <code><?php echo htmlspecialchars($ids_action); ?></code>
|
||||
</div>
|
||||
|
||||
<?php if ($ids_action === 'WKS' && !empty($warenkorb_xml)): ?>
|
||||
<h2>Bestellung empfangen (WKS)</h2>
|
||||
<div style="background:#d4edda; padding:15px; border-radius:5px; border-left:4px solid #28a745; margin-bottom:15px;">
|
||||
<strong>Bestellung erfolgreich empfangen!</strong><br>
|
||||
Der Großhandel hat folgende Artikel erhalten:
|
||||
</div>
|
||||
<?php
|
||||
// Empfangenes Warenkorb-XML parsen und anzeigen
|
||||
dol_include_once('/idsconnect/class/idsconnect.class.php');
|
||||
$parser = new IdsConnect($db);
|
||||
$received_items = $parser->parseCartXml($warenkorb_xml);
|
||||
if (is_array($received_items) && count($received_items) > 0):
|
||||
?>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Art.-Nr.</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th class="price">Einzelpreis (€)</th>
|
||||
<th>Menge</th>
|
||||
<th>Einheit</th>
|
||||
</tr>
|
||||
<?php $wks_total = 0; foreach ($received_items as $ri):
|
||||
$line_total = $ri['menge'] * $ri['einzelpreis'];
|
||||
$wks_total += $line_total;
|
||||
?>
|
||||
<tr>
|
||||
<td><code><?php echo htmlspecialchars($ri['artikelnr']); ?></code></td>
|
||||
<td><?php echo htmlspecialchars($ri['bezeichnung']); ?></td>
|
||||
<td class="price"><?php echo number_format($ri['einzelpreis'], 2, ',', '.'); ?></td>
|
||||
<td><?php echo (int)$ri['menge']; ?></td>
|
||||
<td><?php echo htmlspecialchars($ri['einheit']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr style="font-weight:bold; border-top:2px solid #333;">
|
||||
<td colspan="4" style="text-align:right;">Gesamt netto:</td>
|
||||
<td class="price"><?php echo number_format($wks_total, 2, ',', '.'); ?> €</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p style="color:#e74c3c;">Warenkorb-XML konnte nicht geparst werden.</p>
|
||||
<details>
|
||||
<summary>XML-Rohdaten</summary>
|
||||
<pre style="background:#f8f9fa; padding:10px; overflow:auto;"><?php echo htmlspecialchars($warenkorb_xml); ?></pre>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin-top:20px; padding:15px; background:#fff3cd; border-radius:5px; border-left:4px solid #ffc107;">
|
||||
<strong>Mock-Server Simulation:</strong> Im Live-Betrieb würde der Sonepar-Shop diese Bestellung entgegennehmen und verarbeiten.
|
||||
</div>
|
||||
|
||||
<?php elseif ($ids_action === 'WKE' || empty($ids_action)): ?>
|
||||
<h2>Artikel auswählen</h2>
|
||||
<form method="post" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>">
|
||||
<input type="hidden" name="mock_action" value="send_cart">
|
||||
<input type="hidden" name="hookurl" value="<?php echo htmlspecialchars($hookurl); ?>">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Art.-Nr.</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th>Hersteller</th>
|
||||
<th class="price">Preis (€)</th>
|
||||
<th>Menge</th>
|
||||
</tr>
|
||||
<?php foreach ($mock_articles as $idx => $art): ?>
|
||||
<tr>
|
||||
<td><code><?php echo htmlspecialchars($art['nr']); ?></code></td>
|
||||
<td><?php echo htmlspecialchars($art['bezeichnung']); ?></td>
|
||||
<td><?php echo htmlspecialchars($art['hersteller']); ?></td>
|
||||
<td class="price"><?php echo number_format($art['preis'], 2, ',', '.'); ?></td>
|
||||
<td><input type="number" name="cart_items[<?php echo $idx; ?>][qty]" value="0" min="0" max="999"></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
|
||||
<div style="text-align:right; margin-top:15px;">
|
||||
<?php if (!empty($hookurl)): ?>
|
||||
<button type="submit" class="btn">🛒 Warenkorb an Dolibarr senden</button>
|
||||
<?php else: ?>
|
||||
<p style="color:#e74c3c;">Keine HOOKURL empfangen - Rücksendung nicht möglich.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php elseif ($ids_action === 'ADL'): ?>
|
||||
<h2>Artikel Deep-Link</h2>
|
||||
<div style="padding:20px; background:#f8f9fa; border-radius:5px;">
|
||||
<p>Angefragter Artikel: <strong><?php echo htmlspecialchars($artikelnr); ?></strong></p>
|
||||
<?php
|
||||
$found = false;
|
||||
foreach ($mock_articles as $art) {
|
||||
if ($art['nr'] === $artikelnr) {
|
||||
$found = true;
|
||||
echo '<table>';
|
||||
echo '<tr><td><strong>Artikelnummer:</strong></td><td>'.htmlspecialchars($art['nr']).'</td></tr>';
|
||||
echo '<tr><td><strong>Bezeichnung:</strong></td><td>'.htmlspecialchars($art['bezeichnung']).'</td></tr>';
|
||||
echo '<tr><td><strong>Hersteller:</strong></td><td>'.htmlspecialchars($art['hersteller']).'</td></tr>';
|
||||
echo '<tr><td><strong>Preis:</strong></td><td>'.number_format($art['preis'], 2, ',', '.').' €</td></tr>';
|
||||
echo '<tr><td><strong>Verfügbar:</strong></td><td>✅ Auf Lager (Mock)</td></tr>';
|
||||
echo '</table>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
echo '<p style="color:#e74c3c;">Artikel nicht gefunden (Mock-Datenbank enthält nur Beispieldaten)</p>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<?php elseif ($ids_action === 'SV'): ?>
|
||||
<h2>Schnittstellenversion</h2>
|
||||
<p>Unterstützte IDS Connect Version: <strong>2.5</strong></p>
|
||||
|
||||
<?php elseif ($ids_action === 'LI'): ?>
|
||||
<h2>Login-Information</h2>
|
||||
<p>Login erfolgreich (Mock-Server)</p>
|
||||
<p>Kundennummer: <strong><?php echo htmlspecialchars($kndnr); ?></strong></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<hr style="margin-top:20px;">
|
||||
<p style="font-size:12px; color:#999;">
|
||||
IDS Connect Mock-Server v1.0 | Nur für Entwicklung und Tests |
|
||||
<a href="<?php echo DOL_URL_ROOT; ?>/custom/idsconnect/idsconnectindex.php">Zurück zu Dolibarr</a>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
3
sql/dolibarr_allversions.sql
Executable file
3
sql/dolibarr_allversions.sql
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
--
|
||||
-- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version.
|
||||
--
|
||||
14
sql/llx_idsconnect_log.key.sql
Normal file
14
sql/llx_idsconnect_log.key.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
-- Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
--
|
||||
-- Indizes für llx_idsconnect_log
|
||||
--
|
||||
|
||||
ALTER TABLE llx_idsconnect_log ADD INDEX idx_idsconnect_log_fk_supplier (fk_supplier);
|
||||
ALTER TABLE llx_idsconnect_log ADD INDEX idx_idsconnect_log_fk_user (fk_user);
|
||||
ALTER TABLE llx_idsconnect_log ADD INDEX idx_idsconnect_log_action (action_type);
|
||||
ALTER TABLE llx_idsconnect_log ADD INDEX idx_idsconnect_log_status (status);
|
||||
ALTER TABLE llx_idsconnect_log ADD INDEX idx_idsconnect_log_token (callback_token);
|
||||
ALTER TABLE llx_idsconnect_log ADD INDEX idx_idsconnect_log_date (date_creation);
|
||||
|
||||
ALTER TABLE llx_idsconnect_log ADD CONSTRAINT fk_idsconnect_log_supplier FOREIGN KEY (fk_supplier) REFERENCES llx_idsconnect_supplier(rowid);
|
||||
ALTER TABLE llx_idsconnect_log ADD CONSTRAINT fk_idsconnect_log_user FOREIGN KEY (fk_user) REFERENCES llx_user(rowid);
|
||||
23
sql/llx_idsconnect_log.sql
Normal file
23
sql/llx_idsconnect_log.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
-- Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
--
|
||||
-- Transaktionslog für IDS Connect
|
||||
--
|
||||
|
||||
CREATE TABLE llx_idsconnect_log (
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
fk_supplier integer NOT NULL,
|
||||
fk_user integer NOT NULL,
|
||||
action_type varchar(10) NOT NULL,
|
||||
direction varchar(10) NOT NULL DEFAULT 'OUT',
|
||||
request_data text,
|
||||
response_data text,
|
||||
cart_xml mediumtext,
|
||||
status varchar(20) NOT NULL DEFAULT 'pending',
|
||||
error_message text,
|
||||
callback_token varchar(128),
|
||||
fk_commande integer DEFAULT NULL,
|
||||
ip_address varchar(45),
|
||||
date_creation datetime NOT NULL,
|
||||
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
entity integer DEFAULT 1 NOT NULL
|
||||
) ENGINE=InnoDB;
|
||||
11
sql/llx_idsconnect_supplier.key.sql
Normal file
11
sql/llx_idsconnect_supplier.key.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
-- Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
--
|
||||
-- Indizes für llx_idsconnect_supplier
|
||||
--
|
||||
|
||||
ALTER TABLE llx_idsconnect_supplier ADD UNIQUE INDEX uk_idsconnect_supplier_ref (ref, entity);
|
||||
ALTER TABLE llx_idsconnect_supplier ADD INDEX idx_idsconnect_supplier_fk_soc (fk_soc);
|
||||
ALTER TABLE llx_idsconnect_supplier ADD INDEX idx_idsconnect_supplier_active (active);
|
||||
|
||||
ALTER TABLE llx_idsconnect_supplier ADD CONSTRAINT fk_idsconnect_supplier_fk_soc FOREIGN KEY (fk_soc) REFERENCES llx_societe(rowid);
|
||||
ALTER TABLE llx_idsconnect_supplier ADD CONSTRAINT fk_idsconnect_supplier_fk_user_creat FOREIGN KEY (fk_user_creat) REFERENCES llx_user(rowid);
|
||||
25
sql/llx_idsconnect_supplier.sql
Normal file
25
sql/llx_idsconnect_supplier.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
-- Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
--
|
||||
-- Großhändler-Konfiguration für IDS Connect
|
||||
--
|
||||
|
||||
CREATE TABLE llx_idsconnect_supplier (
|
||||
rowid integer AUTO_INCREMENT PRIMARY KEY,
|
||||
ref varchar(128) NOT NULL,
|
||||
label varchar(255) NOT NULL,
|
||||
fk_soc integer DEFAULT NULL,
|
||||
ids_url varchar(512) NOT NULL,
|
||||
ids_version varchar(10) DEFAULT '2.5',
|
||||
ids_customer_no varchar(128) NOT NULL,
|
||||
ids_username varchar(255) NOT NULL,
|
||||
ids_password varchar(512) NOT NULL,
|
||||
testmode smallint DEFAULT 1,
|
||||
active smallint DEFAULT 1,
|
||||
note_public text,
|
||||
note_private text,
|
||||
date_creation datetime NOT NULL,
|
||||
tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
fk_user_creat integer NOT NULL,
|
||||
fk_user_modif integer,
|
||||
entity integer DEFAULT 1 NOT NULL
|
||||
) ENGINE=InnoDB;
|
||||
380
supplier_card.php
Normal file
380
supplier_card.php
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/supplier_card.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Großhändler anlegen/bearbeiten
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/class/idsconnect.class.php');
|
||||
dol_include_once('/idsconnect/lib/idsconnect.lib.php');
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("idsconnect@idsconnect", "admin"));
|
||||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
$id = GETPOSTINT('id');
|
||||
|
||||
$object = new IdsSupplier($db);
|
||||
if ($id > 0) {
|
||||
$object->fetch($id);
|
||||
}
|
||||
|
||||
// Berechtigungsprüfung
|
||||
if ($action == 'create' || $action == 'add') {
|
||||
if (!$user->hasRight('idsconnect', 'config')) {
|
||||
accessforbidden();
|
||||
}
|
||||
} elseif ($action == 'edit' || $action == 'update') {
|
||||
if (!$user->hasRight('idsconnect', 'config')) {
|
||||
accessforbidden();
|
||||
}
|
||||
} elseif ($action == 'confirm_delete') {
|
||||
if (!$user->hasRight('idsconnect', 'delete')) {
|
||||
accessforbidden();
|
||||
}
|
||||
} else {
|
||||
if (!$user->hasRight('idsconnect', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
// Anlegen
|
||||
if ($action == 'add' && $user->hasRight('idsconnect', 'config')) {
|
||||
if (!verifCond(GETPOST('token', 'alpha') == newToken())) {
|
||||
accessforbidden('Bad CSRF token');
|
||||
}
|
||||
|
||||
$object->ref = GETPOST('ref', 'alphanohtml');
|
||||
$object->label = GETPOST('label', 'alphanohtml');
|
||||
$object->fk_soc = GETPOSTINT('fk_soc');
|
||||
$object->ids_url = GETPOST('ids_url', 'alphanohtml');
|
||||
$object->ids_version = GETPOST('ids_version', 'alphanohtml');
|
||||
$object->ids_customer_no = GETPOST('ids_customer_no', 'alphanohtml');
|
||||
$object->ids_username = GETPOST('ids_username', 'alphanohtml');
|
||||
$object->ids_password = GETPOST('ids_password', 'none');
|
||||
$object->testmode = GETPOSTINT('testmode');
|
||||
$object->active = GETPOSTINT('active');
|
||||
$object->note_public = GETPOST('note_public', 'restricthtml');
|
||||
$object->note_private = GETPOST('note_private', 'restricthtml');
|
||||
|
||||
// Validierung
|
||||
$error = 0;
|
||||
if (empty($object->ref)) {
|
||||
setEventMessages('Referenz ist Pflichtfeld', null, 'errors');
|
||||
$error++;
|
||||
}
|
||||
if (empty($object->label)) {
|
||||
setEventMessages('Bezeichnung ist Pflichtfeld', null, 'errors');
|
||||
$error++;
|
||||
}
|
||||
if (empty($object->ids_url)) {
|
||||
setEventMessages('Shop-URL ist Pflichtfeld', null, 'errors');
|
||||
$error++;
|
||||
}
|
||||
if (empty($object->ids_customer_no)) {
|
||||
setEventMessages('Kundennummer ist Pflichtfeld', null, 'errors');
|
||||
$error++;
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
$result = $object->create($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans("IdsconnectSupplierCreated"), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$result);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($object->error, null, 'errors');
|
||||
$action = 'create';
|
||||
}
|
||||
} else {
|
||||
$action = 'create';
|
||||
}
|
||||
}
|
||||
|
||||
// Aktualisieren
|
||||
if ($action == 'update' && $user->hasRight('idsconnect', 'config')) {
|
||||
if (!verifCond(GETPOST('token', 'alpha') == newToken())) {
|
||||
accessforbidden('Bad CSRF token');
|
||||
}
|
||||
|
||||
$object->ref = GETPOST('ref', 'alphanohtml');
|
||||
$object->label = GETPOST('label', 'alphanohtml');
|
||||
$object->fk_soc = GETPOSTINT('fk_soc');
|
||||
$object->ids_url = GETPOST('ids_url', 'alphanohtml');
|
||||
$object->ids_version = GETPOST('ids_version', 'alphanohtml');
|
||||
$object->ids_customer_no = GETPOST('ids_customer_no', 'alphanohtml');
|
||||
$object->ids_username = GETPOST('ids_username', 'alphanohtml');
|
||||
|
||||
// Passwort nur aktualisieren wenn ein neues eingegeben wurde
|
||||
$new_password = GETPOST('ids_password', 'none');
|
||||
if (!empty($new_password)) {
|
||||
$object->ids_password = $new_password;
|
||||
}
|
||||
|
||||
$object->testmode = GETPOSTINT('testmode');
|
||||
$object->active = GETPOSTINT('active');
|
||||
$object->note_public = GETPOST('note_public', 'restricthtml');
|
||||
$object->note_private = GETPOST('note_private', 'restricthtml');
|
||||
|
||||
$result = $object->update($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans("IdsconnectSupplierUpdated"), null, 'mesgs');
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id);
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($object->error, null, 'errors');
|
||||
$action = 'edit';
|
||||
}
|
||||
}
|
||||
|
||||
// Löschen
|
||||
if ($action == 'confirm_delete' && $confirm == 'yes' && $user->hasRight('idsconnect', 'delete')) {
|
||||
if (!verifCond(GETPOST('token', 'alpha') == newToken())) {
|
||||
accessforbidden('Bad CSRF token');
|
||||
}
|
||||
|
||||
$result = $object->delete($user);
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans("IdsconnectSupplierDeleted"), null, 'mesgs');
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php');
|
||||
exit;
|
||||
} else {
|
||||
setEventMessages($object->error, null, 'errors');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$form = new Form($db);
|
||||
|
||||
$title = ($action == 'create') ? $langs->trans("IdsconnectSupplierNew") : $langs->trans("IdsconnectSupplierCard");
|
||||
llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-idsconnect page-supplier_card');
|
||||
|
||||
// ============================================================
|
||||
// Formular: Anlegen oder Bearbeiten
|
||||
// ============================================================
|
||||
if ($action == 'create' || $action == 'edit') {
|
||||
$isEdit = ($action == 'edit');
|
||||
|
||||
print load_fiche_titre($title, '', 'fa-plug');
|
||||
|
||||
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="'.($isEdit ? 'update' : 'add').'">';
|
||||
if ($isEdit) {
|
||||
print '<input type="hidden" name="id" value="'.$object->id.'">';
|
||||
}
|
||||
|
||||
print dol_get_fiche_head(array(), '', '', 0);
|
||||
|
||||
print '<table class="border centpercent tableforfieldcreate">';
|
||||
|
||||
// Referenz
|
||||
print '<tr><td class="titlefieldcreate fieldrequired">'.$langs->trans("IdsconnectSupplierRef").'</td>';
|
||||
print '<td><input type="text" name="ref" value="'.htmlspecialchars($object->ref ?: GETPOST('ref')).'" class="minwidth200" maxlength="128" required></td></tr>';
|
||||
|
||||
// Bezeichnung
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans("IdsconnectSupplierLabel").'</td>';
|
||||
print '<td><input type="text" name="label" value="'.htmlspecialchars($object->label ?: GETPOST('label')).'" class="minwidth300" maxlength="255" required></td></tr>';
|
||||
|
||||
// Dolibarr-Lieferant
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierSoc").'</td>';
|
||||
print '<td>'.$form->select_company($object->fk_soc ?: GETPOSTINT('fk_soc'), 'fk_soc', 's.fournisseur=1', 'SelectThirdParty', 0, 0, null, 0, 'minwidth300').'</td></tr>';
|
||||
|
||||
// Shop-URL
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans("IdsconnectSupplierUrl").'</td>';
|
||||
print '<td><input type="url" name="ids_url" value="'.htmlspecialchars($object->ids_url ?: GETPOST('ids_url')).'" class="minwidth400" placeholder="https://webshop.example.de/ids" required></td></tr>';
|
||||
|
||||
// IDS-Version
|
||||
$versions = array('2.5' => 'IDS 2.5 (empfohlen)', '2.3' => 'IDS 2.3', '2.0' => 'IDS 2.0', '1.3' => 'IDS 1.3 (veraltet)');
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierVersion").'</td>';
|
||||
print '<td>'.$form->selectarray('ids_version', $versions, $object->ids_version ?: '2.5').'</td></tr>';
|
||||
|
||||
// Kundennummer
|
||||
print '<tr><td class="fieldrequired">'.$langs->trans("IdsconnectSupplierCustomerNo").'</td>';
|
||||
print '<td><input type="text" name="ids_customer_no" value="'.htmlspecialchars($object->ids_customer_no ?: GETPOST('ids_customer_no')).'" class="minwidth200" required></td></tr>';
|
||||
|
||||
// Benutzerkennung
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierUsername").'</td>';
|
||||
print '<td><input type="text" name="ids_username" value="'.htmlspecialchars($object->ids_username ?: GETPOST('ids_username')).'" class="minwidth200" autocomplete="off"></td></tr>';
|
||||
|
||||
// Passwort
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierPassword").'</td>';
|
||||
print '<td><input type="password" name="ids_password" value="" class="minwidth200" autocomplete="new-password"';
|
||||
if ($isEdit) {
|
||||
print ' placeholder="(unverändert lassen wenn leer)"';
|
||||
}
|
||||
print '></td></tr>';
|
||||
|
||||
// Testmodus
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierTestmode").'</td>';
|
||||
print '<td>'.$form->selectyesno('testmode', isset($object->testmode) ? $object->testmode : 1, 1).'</td></tr>';
|
||||
|
||||
// Aktiv
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierActive").'</td>';
|
||||
print '<td>'.$form->selectyesno('active', isset($object->active) ? $object->active : 1, 1).'</td></tr>';
|
||||
|
||||
// Notizen
|
||||
print '<tr><td>'.$langs->trans("NotePublic").'</td>';
|
||||
print '<td><textarea name="note_public" class="minwidth400" rows="3">'.htmlspecialchars($object->note_public ?: '').'</textarea></td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("NotePrivate").'</td>';
|
||||
print '<td><textarea name="note_private" class="minwidth400" rows="3">'.htmlspecialchars($object->note_private ?: '').'</textarea></td></tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
print '<div class="center">';
|
||||
print '<input type="submit" class="button" value="'.$langs->trans($isEdit ? "Save" : "Create").'">';
|
||||
print ' <a class="button button-cancel" href="'.($isEdit ? $_SERVER['PHP_SELF'].'?id='.$object->id : DOL_URL_ROOT.'/custom/idsconnect/idsconnectindex.php').'">'.$langs->trans("Cancel").'</a>';
|
||||
print '</div>';
|
||||
|
||||
print '</form>';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Anzeige: Großhändler-Details
|
||||
// ============================================================
|
||||
elseif ($id > 0 && $object->id > 0) {
|
||||
|
||||
// Lösch-Bestätigung
|
||||
if ($action == 'delete') {
|
||||
$formconfirm = $form->formconfirm(
|
||||
$_SERVER["PHP_SELF"].'?id='.$object->id,
|
||||
$langs->trans('Delete'),
|
||||
$langs->trans('IdsconnectSupplierConfirmDelete'),
|
||||
'confirm_delete',
|
||||
'',
|
||||
0,
|
||||
1
|
||||
);
|
||||
print $formconfirm;
|
||||
}
|
||||
|
||||
$head = idsconnectSupplierPrepareHead($object);
|
||||
print dol_get_fiche_head($head, 'supplier', $langs->trans("IdsconnectSupplierCard"), -1, 'fa-plug');
|
||||
|
||||
// Testmodus-Banner
|
||||
idsconnectShowTestModeBanner();
|
||||
|
||||
print '<table class="border centpercent tableforfield">';
|
||||
|
||||
print '<tr><td class="titlefield">'.$langs->trans("IdsconnectSupplierRef").'</td>';
|
||||
print '<td>'.htmlspecialchars($object->ref).'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierLabel").'</td>';
|
||||
print '<td>'.htmlspecialchars($object->label).'</td></tr>';
|
||||
|
||||
if ($object->fk_soc > 0) {
|
||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||
$soc = new Societe($db);
|
||||
$soc->fetch($object->fk_soc);
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierSoc").'</td>';
|
||||
print '<td>'.$soc->getNomUrl(1).'</td></tr>';
|
||||
}
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierUrl").'</td>';
|
||||
print '<td><a href="'.htmlspecialchars($object->ids_url).'" target="_blank">'.htmlspecialchars($object->ids_url).'</a></td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierVersion").'</td>';
|
||||
print '<td>IDS '.htmlspecialchars($object->ids_version).'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierCustomerNo").'</td>';
|
||||
print '<td>'.htmlspecialchars($object->ids_customer_no).'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierUsername").'</td>';
|
||||
print '<td>'.htmlspecialchars($object->ids_username).'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierPassword").'</td>';
|
||||
print '<td>'.$object->getMaskedPassword().'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierTestmode").'</td>';
|
||||
print '<td>'.yn($object->testmode).'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("IdsconnectSupplierActive").'</td>';
|
||||
print '<td>'.yn($object->active).'</td></tr>';
|
||||
|
||||
print '<tr><td>'.$langs->trans("DateCreation").'</td>';
|
||||
print '<td>'.dol_print_date($object->date_creation, 'dayhour').'</td></tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
// Aktionsbuttons
|
||||
print '<div class="tabsAction">';
|
||||
|
||||
if ($object->active && $user->hasRight('idsconnect', 'use')) {
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/launch.php?supplier_id='.$object->id.'&ids_action=WKE&token='.newToken().'" target="_blank">'.$langs->trans("IdsconnectReceiveCart").'</a>';
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/launch.php?supplier_id='.$object->id.'&ids_action=SV&token='.newToken().'" target="_blank">'.$langs->trans("IdsconnectActionSV").'</a>';
|
||||
}
|
||||
|
||||
if ($user->hasRight('idsconnect', 'config')) {
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=edit">'.$langs->trans("Modify").'</a>';
|
||||
}
|
||||
|
||||
if ($user->hasRight('idsconnect', 'delete')) {
|
||||
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=delete&token='.newToken().'">'.$langs->trans("Delete").'</a>';
|
||||
}
|
||||
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
114
supplier_list.php
Normal file
114
supplier_list.php
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/supplier_list.php
|
||||
* \ingroup idsconnect
|
||||
* \brief Liste aller konfigurierten Großhändler
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/lib/idsconnect.lib.php');
|
||||
|
||||
/**
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("idsconnect@idsconnect"));
|
||||
|
||||
if (!$user->hasRight('idsconnect', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
llxHeader('', $langs->trans("IdsconnectSupplierList"), '', '', 0, 0, '', '', '', 'mod-idsconnect page-supplier_list');
|
||||
|
||||
print load_fiche_titre($langs->trans("IdsconnectSupplierList"), '', 'fa-plug');
|
||||
|
||||
idsconnectShowTestModeBanner();
|
||||
|
||||
$supplierObj = new IdsSupplier($db);
|
||||
$suppliers = $supplierObj->fetchAll(0);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans("IdsconnectSupplierRef").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectSupplierLabel").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectSupplierUrl").'</th>';
|
||||
print '<th>'.$langs->trans("IdsconnectSupplierVersion").'</th>';
|
||||
print '<th class="center">'.$langs->trans("IdsconnectSupplierTestmode").'</th>';
|
||||
print '<th class="center">'.$langs->trans("Status").'</th>';
|
||||
print '<th class="right">'.$langs->trans("Action").'</th>';
|
||||
print '</tr>';
|
||||
|
||||
if (is_array($suppliers) && count($suppliers) > 0) {
|
||||
foreach ($suppliers as $sup) {
|
||||
print '<tr class="oddeven">';
|
||||
print '<td><a href="'.DOL_URL_ROOT.'/custom/idsconnect/supplier_card.php?id='.$sup->id.'">'.htmlspecialchars($sup->ref).'</a></td>';
|
||||
print '<td>'.htmlspecialchars($sup->label).'</td>';
|
||||
print '<td class="small">'.htmlspecialchars($sup->ids_url).'</td>';
|
||||
print '<td>'.htmlspecialchars($sup->ids_version).'</td>';
|
||||
print '<td class="center">'.yn($sup->testmode).'</td>';
|
||||
print '<td class="center">'.yn($sup->active).'</td>';
|
||||
print '<td class="right nowrap">';
|
||||
if ($sup->active && $user->hasRight('idsconnect', 'use')) {
|
||||
print '<a class="butAction butActionSmall" href="'.DOL_URL_ROOT.'/custom/idsconnect/launch.php?supplier_id='.$sup->id.'&ids_action=WKE&token='.newToken().'" target="_blank">'.$langs->trans("IdsconnectOpenShop").'</a>';
|
||||
}
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="7" class="opacitymedium">'.$langs->trans("IdsconnectNoSuppliers").'</td></tr>';
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
if ($user->hasRight('idsconnect', 'config')) {
|
||||
print '<div class="tabsAction">';
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/supplier_card.php?action=create">'.$langs->trans("IdsconnectNewSupplier").'</a>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
147
tab_supplierorder.php
Normal file
147
tab_supplierorder.php
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file idsconnect/tab_supplierorder.php
|
||||
* \ingroup idsconnect
|
||||
* \brief IDS Connect Tab in Lieferantenbestellungen
|
||||
*/
|
||||
|
||||
// Dolibarr laden
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include 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";
|
||||
}
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/fourn.lib.php';
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
dol_include_once('/idsconnect/class/idsconnect.class.php');
|
||||
dol_include_once('/idsconnect/lib/idsconnect.lib.php');
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
$langs->loadLangs(array("idsconnect@idsconnect", "orders"));
|
||||
|
||||
if (!$user->hasRight('idsconnect', 'use')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
$id = GETPOSTINT('id');
|
||||
|
||||
// Bestellung laden
|
||||
$object = new CommandeFournisseur($db);
|
||||
$object->fetch($id);
|
||||
$object->fetch_thirdparty();
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
llxHeader('', $langs->trans("IdsConnectTab"), '', '', 0, 0, '', '', '', 'mod-idsconnect page-tab_supplierorder');
|
||||
|
||||
// Tabs der Lieferantenbestellung
|
||||
$head = ordersupplier_prepare_head($object);
|
||||
print dol_get_fiche_head($head, 'idsconnect', $langs->trans("SupplierOrder"), -1, 'order');
|
||||
|
||||
// Bestellungs-Info
|
||||
print '<table class="border centpercent tableforfield">';
|
||||
print '<tr><td class="titlefield">'.$langs->trans("Ref").'</td><td>'.$object->getNomUrl(1).'</td></tr>';
|
||||
print '<tr><td>'.$langs->trans("Supplier").'</td><td>'.$object->thirdparty->getNomUrl(1).'</td></tr>';
|
||||
print '</table>';
|
||||
|
||||
print '<br>';
|
||||
|
||||
idsconnectShowTestModeBanner();
|
||||
|
||||
// Passende Großhändler für diesen Lieferanten finden
|
||||
$supplierObj = new IdsSupplier($db);
|
||||
$allSuppliers = $supplierObj->fetchAll(1);
|
||||
$matchingSuppliers = array();
|
||||
|
||||
if (is_array($allSuppliers)) {
|
||||
foreach ($allSuppliers as $sup) {
|
||||
// Großhändler die mit diesem Dolibarr-Lieferanten verknüpft sind
|
||||
if ($sup->fk_soc == $object->socid) {
|
||||
$matchingSuppliers[] = $sup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($matchingSuppliers)) {
|
||||
print '<h3>'.$langs->trans("IdsconnectSuppliers").'</h3>';
|
||||
|
||||
foreach ($matchingSuppliers as $sup) {
|
||||
print '<div class="idsconnect-supplier-card">';
|
||||
print '<div class="supplier-name">'.htmlspecialchars($sup->label).' <span class="opacitymedium">('.htmlspecialchars($sup->ref).')</span></div>';
|
||||
print '<div class="supplier-url">'.htmlspecialchars($sup->ids_url).' | IDS '.htmlspecialchars($sup->ids_version).'</div>';
|
||||
|
||||
if ($sup->testmode) {
|
||||
print ' <span class="badge badge-warning">Testmodus</span>';
|
||||
}
|
||||
|
||||
print '<div class="idsconnect-action-buttons" style="margin-top:10px;">';
|
||||
|
||||
// Shop öffnen (WKE)
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/launch.php?supplier_id='.$sup->id.'&ids_action=WKE&token='.newToken().'" target="_blank">';
|
||||
print $langs->trans("IdsconnectOpenShop");
|
||||
print '</a>';
|
||||
|
||||
// Warenkorb aus Bestellung senden (WKS)
|
||||
if (count($object->lines) > 0) {
|
||||
print '<a class="butAction" href="'.DOL_URL_ROOT.'/custom/idsconnect/launch.php?supplier_id='.$sup->id.'&ids_action=WKS&fk_commande='.$object->id.'&token='.newToken().'" target="_blank">';
|
||||
print $langs->trans("IdsconnectSendCart");
|
||||
print '</a>';
|
||||
}
|
||||
|
||||
print '</div></div>';
|
||||
}
|
||||
} else {
|
||||
print '<div class="opacitymedium">';
|
||||
print 'Kein IDS Connect Großhändler für diesen Lieferanten konfiguriert.<br>';
|
||||
if ($user->hasRight('idsconnect', 'config')) {
|
||||
print '<a href="'.DOL_URL_ROOT.'/custom/idsconnect/supplier_card.php?action=create&fk_soc='.$object->socid.'">';
|
||||
print $langs->trans("IdsconnectNewSupplier");
|
||||
print '</a>';
|
||||
}
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
207
test_connection.php
Normal file
207
test_connection.php
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
/**
|
||||
* IDS Connect Verbindungstest
|
||||
* Testet die Verbindung zum Großhändler per curl
|
||||
*
|
||||
* Aufruf: php test_connection.php [supplier_id]
|
||||
*/
|
||||
|
||||
if (php_sapi_name() !== 'cli') {
|
||||
die("Dieses Skript muss per CLI ausgeführt werden.\n");
|
||||
}
|
||||
|
||||
// Dolibarr laden
|
||||
define('NOLOGIN', 1);
|
||||
define('NOCSRFCHECK', 1);
|
||||
define('NOREQUIREMENU', 1);
|
||||
define('NOREQUIREHTML', 1);
|
||||
define('NOREQUIREAJAX', 1);
|
||||
|
||||
$res = @include '/srv/http/dolibarr/main.inc.php';
|
||||
if (!$res) {
|
||||
die("Fehler: Dolibarr main.inc.php nicht gefunden\n");
|
||||
}
|
||||
|
||||
dol_include_once('/idsconnect/class/idsconnect.class.php');
|
||||
dol_include_once('/idsconnect/class/idssupplier.class.php');
|
||||
|
||||
/**
|
||||
* @var DoliDB $db
|
||||
*/
|
||||
|
||||
// Farben
|
||||
$G = "\033[32m";
|
||||
$R = "\033[31m";
|
||||
$Y = "\033[33m";
|
||||
$C = "\033[36m";
|
||||
$B = "\033[1m";
|
||||
$X = "\033[0m";
|
||||
|
||||
// Supplier-ID als Argument oder alle anzeigen
|
||||
$supplier_id = isset($argv[1]) ? (int) $argv[1] : 0;
|
||||
|
||||
$supplierObj = new IdsSupplier($db);
|
||||
|
||||
if ($supplier_id <= 0) {
|
||||
// Alle Großhändler anzeigen
|
||||
$all = $supplierObj->fetchAll();
|
||||
if (empty($all)) {
|
||||
echo "{$R}Keine Großhändler konfiguriert.{$X}\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "\n{$B}{$C}Verfügbare Großhändler:{$X}\n\n";
|
||||
foreach ($all as $sup) {
|
||||
echo " ID {$B}{$sup->id}{$X}: {$sup->label} ({$sup->ref})";
|
||||
echo " | URL: {$sup->ids_url}";
|
||||
echo " | ".($sup->active ? "{$G}Aktiv{$X}" : "{$R}Inaktiv{$X}");
|
||||
echo " | ".($sup->testmode ? "{$Y}Testmodus{$X}" : "{$R}Live{$X}");
|
||||
echo "\n";
|
||||
}
|
||||
echo "\nAufruf: php test_connection.php <supplier_id>\n\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Supplier laden
|
||||
$result = $supplierObj->fetch($supplier_id);
|
||||
if ($result <= 0) {
|
||||
echo "{$R}Großhändler mit ID {$supplier_id} nicht gefunden.{$X}\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\n{$B}{$C}================================================================{$X}\n";
|
||||
echo "{$B}{$C} IDS Connect Verbindungstest: {$supplierObj->label}{$X}\n";
|
||||
echo "{$B}{$C}================================================================{$X}\n\n";
|
||||
|
||||
echo "{$B}Konfiguration:{$X}\n";
|
||||
echo " Referenz: {$supplierObj->ref}\n";
|
||||
echo " Shop-URL: {$supplierObj->ids_url}\n";
|
||||
echo " IDS-Version: {$supplierObj->ids_version}\n";
|
||||
echo " Kundennr: {$supplierObj->ids_customer_no}\n";
|
||||
echo " Benutzer: {$supplierObj->ids_username}\n";
|
||||
echo " Passwort: ".(empty($supplierObj->ids_password) ? "{$R}NICHT GESETZT{$X}" : str_repeat('*', min(strlen($supplierObj->ids_password), 8)))."\n";
|
||||
echo " Testmodus: ".($supplierObj->testmode ? "{$Y}Ja{$X}" : "{$R}Nein{$X}")."\n";
|
||||
echo "\n";
|
||||
|
||||
if (empty($supplierObj->ids_password)) {
|
||||
echo "{$R}ABBRUCH: Kein Passwort konfiguriert. Bitte erst in der Großhändler-Konfiguration eingeben.{$X}\n\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Test-Actions
|
||||
$actions = array('SV', 'LI');
|
||||
|
||||
foreach ($actions as $action) {
|
||||
echo "{$B}--- Test: Action {$action} ---{$X}\n\n";
|
||||
|
||||
// POST-Daten zusammenbauen (wie buildLaunchForm)
|
||||
$post_data = array(
|
||||
'kndnr' => $supplierObj->ids_customer_no,
|
||||
'name_kunde' => $supplierObj->ids_username,
|
||||
'pw_kunde' => $supplierObj->ids_password,
|
||||
'version' => $supplierObj->ids_version,
|
||||
'action' => $action,
|
||||
'hookurl' => 'http://localhost/dolibarr/custom/idsconnect/callback.php?token=test',
|
||||
);
|
||||
|
||||
echo " {$C}POST-Parameter:{$X}\n";
|
||||
foreach ($post_data as $k => $v) {
|
||||
if ($k === 'pw_kunde') {
|
||||
echo " {$k} = ********\n";
|
||||
} else {
|
||||
echo " {$k} = {$v}\n";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
echo " {$C}Sende POST an: {$supplierObj->ids_url}{$X}\n\n";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $supplierObj->ids_url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'IDS Connect Test / Dolibarr');
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
|
||||
$effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||
$redirectUrl = curl_getinfo($ch, CURLINFO_REDIRECT_URL);
|
||||
$curlError = curl_error($ch);
|
||||
$curlErrno = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlErrno) {
|
||||
echo " {$R}CURL-FEHLER #{$curlErrno}: {$curlError}{$X}\n";
|
||||
echo "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$headers = substr($response, 0, $headerSize);
|
||||
$body = substr($response, $headerSize);
|
||||
|
||||
// HTTP-Status
|
||||
$statusColor = ($httpCode >= 200 && $httpCode < 400) ? $G : $R;
|
||||
echo " HTTP-Status: {$statusColor}{$httpCode}{$X}\n";
|
||||
echo " Antwortzeit: {$totalTime}s\n";
|
||||
|
||||
if (!empty($redirectUrl)) {
|
||||
echo " Redirect zu: {$Y}{$redirectUrl}{$X}\n";
|
||||
}
|
||||
|
||||
// Response-Header anzeigen
|
||||
echo "\n {$C}Response-Header:{$X}\n";
|
||||
$headerLines = explode("\r\n", trim($headers));
|
||||
foreach ($headerLines as $hl) {
|
||||
if (!empty(trim($hl))) {
|
||||
echo " {$hl}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Response-Body
|
||||
echo "\n {$C}Response-Body";
|
||||
$bodyLen = strlen($body);
|
||||
echo " ({$bodyLen} Bytes):{$X}\n";
|
||||
|
||||
if ($bodyLen > 0) {
|
||||
// Prüfen ob es HTML oder XML ist
|
||||
$trimmed = trim($body);
|
||||
if (strpos($trimmed, '<?xml') === 0) {
|
||||
echo " {$G}[XML-Antwort]{$X}\n";
|
||||
// XML formatiert ausgeben
|
||||
$dom = new DOMDocument('1.0');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
if (@$dom->loadXML($trimmed)) {
|
||||
$formatted = $dom->saveXML();
|
||||
foreach (explode("\n", $formatted) as $line) {
|
||||
echo " {$line}\n";
|
||||
}
|
||||
} else {
|
||||
echo " ".$trimmed."\n";
|
||||
}
|
||||
} elseif (strpos($trimmed, '<') === 0) {
|
||||
echo " {$Y}[HTML-Antwort - gekürzt auf 2000 Zeichen]{$X}\n";
|
||||
echo " ".substr($trimmed, 0, 2000)."\n";
|
||||
if ($bodyLen > 2000) {
|
||||
echo " {$Y}... ({$bodyLen} Bytes gesamt){$X}\n";
|
||||
}
|
||||
} else {
|
||||
echo " ".$trimmed."\n";
|
||||
}
|
||||
} else {
|
||||
echo " {$Y}(Leere Antwort){$X}\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
echo "{$B}{$C}================================================================{$X}\n";
|
||||
echo "{$B}Test abgeschlossen.{$X}\n\n";
|
||||
|
||||
$db->close();
|
||||
Loading…
Reference in a new issue