All checks were successful
Deploy Eplan / deploy (push) Successful in 9s
Aus /dolibarr audit Report: - en_US: 5 fehlende Tab-/Placeholder-Keys ergänzt (Parity 100%) - LIKE-Wildcard-Escape: %, _ und \\ im User-Input werden maskiert bevor in LIKE '%..%'-Pattern eingebaut (sonst matched "100%" zu viel) Betrifft auftraege_listen (Z.75-77) und kunden_suchen (Z.124) - Rate-Limit: File-basiert nach KB #354 — max. 10 fehlgeschlagene Token- Checks pro IP in 15 Minuten, dann 429. Bei Erfolg wird Zähler resettet. Zähler liegen unter DOL_DATA_ROOT/eplan/loginattempts/<sha1(ip)>.json Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
183 lines
8.4 KiB
PHP
183 lines
8.4 KiB
PHP
<?php
|
|
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
|
* GPL v3+
|
|
*
|
|
* NOLOGIN-Endpoint für die ElektroPlan-PWA / FastAPI-Backend.
|
|
* Auth: Header "X-Eplan-Token: <secret>" gegen Const EPLAN_PWA_SECRET (hash_equals).
|
|
*
|
|
* Actions (?action=...):
|
|
* - ping → {aktiv: true, version: "1.1.0"}
|
|
* - auftraege_listen → Liste offener Aufträge (Commande)
|
|
* - auftrag_details&id= → Einzelner Auftrag mit Positionen
|
|
* - kunden_suchen&such= → Third-Party-Suche
|
|
* - dokument_upload (POST) → Plan-PDF an Auftrag/Kunde anhängen
|
|
*/
|
|
|
|
if (!defined('NOCSRFCHECK')) define('NOCSRFCHECK', '1');
|
|
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
|
if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1');
|
|
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
|
|
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
|
if (!defined('NOLOGIN')) define('NOLOGIN', '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); echo json_encode(['error' => 'main.inc.php not found']); exit; }
|
|
|
|
dol_include_once('/eplan/lib/eplan_token.lib.php');
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Cache-Control: no-store');
|
|
|
|
// --- Auth ---
|
|
if (!eplanRateLimit()) {
|
|
http_response_code(429);
|
|
echo json_encode(['error' => 'Too Many Requests', 'detail' => 'Zu viele fehlgeschlagene Versuche. Warte 15 Minuten.']);
|
|
exit;
|
|
}
|
|
$token = eplanTokenAusRequest();
|
|
if (!eplanTokenPruefen($token)) {
|
|
eplanRateLimit(false); // Fehlversuch zählen
|
|
http_response_code(401);
|
|
echo json_encode(['error' => 'Unauthorized', 'detail' => 'Ungültiger oder fehlender Token']);
|
|
exit;
|
|
}
|
|
eplanRateLimit(true); // Erfolg → Zähler zurücksetzen
|
|
|
|
if (!isModEnabled('eplan')) {
|
|
http_response_code(503);
|
|
echo json_encode(['error' => 'Eplan-Modul deaktiviert']);
|
|
exit;
|
|
}
|
|
|
|
$action = GETPOST('action', 'aZ09');
|
|
|
|
try {
|
|
switch ($action) {
|
|
case 'ping':
|
|
echo json_encode([
|
|
'aktiv' => true,
|
|
'modul' => 'eplan',
|
|
'version' => '1.2.0',
|
|
'dolibarr' => DOL_VERSION,
|
|
]);
|
|
break;
|
|
|
|
case 'auftraege_listen':
|
|
$such = GETPOST('such', 'alphanohtml');
|
|
$limit = max(1, min(200, (int) GETPOST('limit', 'int') ?: 50));
|
|
$sql = "SELECT c.rowid as id, c.ref, c.ref_client, c.date_commande, c.total_ttc, c.fk_statut,
|
|
t.nom as kunde
|
|
FROM ".MAIN_DB_PREFIX."commande c
|
|
LEFT JOIN ".MAIN_DB_PREFIX."societe t ON t.rowid = c.fk_soc
|
|
WHERE c.entity IN (".getEntity('commande').")";
|
|
if (!empty($such)) {
|
|
// LIKE-Wildcards im User-Input escapen, dann erst SQL-escape
|
|
$such_esc = $db->escape(str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $such));
|
|
$sql .= " AND (c.ref LIKE '%".$such_esc."%'
|
|
OR c.ref_client LIKE '%".$such_esc."%'
|
|
OR t.nom LIKE '%".$such_esc."%')";
|
|
}
|
|
$sql .= " ORDER BY c.date_commande DESC LIMIT ".((int) $limit);
|
|
$resql = $db->query($sql);
|
|
$liste = [];
|
|
if ($resql) {
|
|
while ($obj = $db->fetch_object($resql)) {
|
|
$liste[] = [
|
|
'id' => (int) $obj->id,
|
|
'ref' => $obj->ref,
|
|
'ref_client' => $obj->ref_client,
|
|
'kunde' => $obj->kunde,
|
|
'status' => (int) $obj->fk_statut,
|
|
'datum' => $obj->date_commande,
|
|
'total_ttc' => $obj->total_ttc !== null ? (float) $obj->total_ttc : null,
|
|
];
|
|
}
|
|
$db->free($resql);
|
|
}
|
|
echo json_encode($liste);
|
|
break;
|
|
|
|
case 'auftrag_details':
|
|
$id = (int) GETPOST('id', 'int');
|
|
if ($id <= 0) { http_response_code(400); echo json_encode(['error' => 'id fehlt']); break; }
|
|
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
|
$c = new Commande($db);
|
|
if ($c->fetch($id) <= 0) { http_response_code(404); echo json_encode(['error' => 'nicht gefunden']); break; }
|
|
$c->fetch_thirdparty();
|
|
echo json_encode([
|
|
'id' => (int) $c->id,
|
|
'ref' => $c->ref,
|
|
'ref_client' => $c->ref_client,
|
|
'kunde' => $c->thirdparty ? $c->thirdparty->name : null,
|
|
'kunde_id' => $c->thirdparty ? (int) $c->thirdparty->id : null,
|
|
'datum' => $c->date_commande,
|
|
'status' => (int) $c->statut,
|
|
'total_ttc' => (float) $c->total_ttc,
|
|
]);
|
|
break;
|
|
|
|
case 'kunden_suchen':
|
|
$such = GETPOST('such', 'alphanohtml');
|
|
if (empty($such) || strlen($such) < 2) { echo json_encode([]); break; }
|
|
$such_esc = $db->escape(str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $such));
|
|
$sql = "SELECT rowid as id, nom, code_client FROM ".MAIN_DB_PREFIX."societe
|
|
WHERE entity IN (".getEntity('societe').")
|
|
AND (nom LIKE '%".$such_esc."%' OR code_client LIKE '%".$such_esc."%')
|
|
ORDER BY nom LIMIT 30";
|
|
$resql = $db->query($sql);
|
|
$liste = [];
|
|
if ($resql) {
|
|
while ($obj = $db->fetch_object($resql)) {
|
|
$liste[] = ['id' => (int) $obj->id, 'name' => $obj->nom, 'code' => $obj->code_client];
|
|
}
|
|
$db->free($resql);
|
|
}
|
|
echo json_encode($liste);
|
|
break;
|
|
|
|
case 'dokument_upload':
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
http_response_code(405); echo json_encode(['error' => 'POST benötigt']); break;
|
|
}
|
|
$auftrag_id = (int) GETPOST('auftrag_id', 'int');
|
|
if ($auftrag_id <= 0) { http_response_code(400); echo json_encode(['error' => 'auftrag_id fehlt']); break; }
|
|
if (empty($_FILES['datei'])) { http_response_code(400); echo json_encode(['error' => 'Keine Datei']); break; }
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
|
$c = new Commande($db);
|
|
if ($c->fetch($auftrag_id) <= 0) { http_response_code(404); echo json_encode(['error' => 'Auftrag nicht gefunden']); break; }
|
|
|
|
$ref_sanitiert = dol_sanitizeFileName($c->ref);
|
|
$ziel_dir = $conf->commande->dir_output.'/'.$ref_sanitiert;
|
|
dol_mkdir($ziel_dir);
|
|
|
|
$dateiname = dol_sanitizeFileName($_FILES['datei']['name']);
|
|
$ziel = $ziel_dir.'/'.$dateiname;
|
|
$erfolg = dol_move_uploaded_file($_FILES['datei']['tmp_name'], $ziel, 1, 0, 0);
|
|
if ($erfolg > 0) {
|
|
echo json_encode(['erfolg' => true, 'pfad' => $ref_sanitiert.'/'.$dateiname]);
|
|
} else {
|
|
http_response_code(500);
|
|
echo json_encode(['error' => 'Upload fehlgeschlagen', 'code' => $erfolg]);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Unbekannte Action', 'action' => $action]);
|
|
}
|
|
} catch (Throwable $e) {
|
|
dol_syslog('eplan/pwa_api: '.$e->getMessage(), LOG_ERR);
|
|
http_response_code(500);
|
|
echo json_encode(['error' => 'Server-Fehler', 'detail' => $e->getMessage()]);
|
|
}
|
|
|
|
$db->close();
|