* * 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, ' 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 '
'; echo '| Art.-Nr. | Bezeichnung | Menge | Einheit | Preis |
|---|---|---|---|---|
'.htmlspecialchars($item['artikelnr'] ?? '').' | ';
echo ''.htmlspecialchars($item['bezeichnung'] ?? '').' | '; echo ''.($item['menge'] ?? 0).' | '; echo ''.htmlspecialchars($item['einheit'] ?? 'STK').' | '; echo ''.number_format($item['einzelpreis'] ?? 0, 2, ',', '.').' | '; echo '
| '.htmlspecialchars($label).' | '; echo ''.htmlspecialchars($value).' |
'; echo htmlspecialchars(json_encode($debug['post_values_preview'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); echo '
'; echo htmlspecialchars(json_encode($debug['files_info'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); echo '
'.htmlspecialchars(substr($raw_xml, 0, 5000)).''; if (strlen($raw_xml) > 5000) { echo '
... gekürzt, vollständiges XML im Log #'.$log->id.'
'; } echo 'Weiter:
'; echo 'Zur IDS Connect Übersicht'; if ($type === 'success') { $review_link = $internal_base.'/custom/idsconnect/cart_review.php?log_id='.$log->id; echo 'Warenkorb prüfen'; } echo '