dolibarr.idsconnect/class/idslog.class.php
data d91f9dbc9a 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>
2026-02-18 06:37:15 +01:00

282 lines
8 KiB
PHP

<?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;
}
}