* Liefert eine Datei aus DOL_DATA_ROOT aus, authentifiziert per JWT. * Whitelist: nur facture/, commande/, propal/, bericht/ * * Query: * relpath — relativer Pfad unter DOL_DATA_ROOT * size=thumb&w= — serverseitig erzeugtes Thumbnail (GD, gecacht, mit ETag). * Das ist der Weg für Kachel-Ansichten. w: 40–800, Default 320. * size=small|mini — altes Verhalten: nutzt ein von Dolibarr vorgefertigtes * thumbs/_small., falls vorhanden. Für Dateien aus * der PWA existiert so eines nicht (kein vignette() beim Upload). * download=1 — als Anhang ausliefern statt inline */ // Dieser Endpoint liefert Binärdaten aus — KEIN JSON Content-Type! // Deshalb nicht _inc.php nutzen, sondern awlauth + Dolibarr manuell laden. if (!defined('NOLOGIN')) define('NOLOGIN', '1'); 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'); $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 __DIR__.'/../lib/bericht.lib.php'; // Auth über das zentrale SSO-Cookie awl_sso. Bewusst awlauth_verify (OHNE CSRF), // weil dieser reine GET-Read auch per window.location/ geladen wird // (Download-Link, Audio-Element) und dort kein X-Requested-With setzbar ist. if (!dol_include_once('/awlauth/lib/awlauth.lib.php') || !function_exists('awlauth_verify')) { http_response_code(500); header('Content-Type: text/plain'); echo 'SSO-Modul (awlauth) nicht verfügbar'; exit; } $user = awlauth_verify(); if (!$user || !$user->hasRight('bericht', 'read')) { http_response_code(401); header('Content-Type: text/plain'); echo 'Nicht angemeldet'; exit; } $relpath = (string) ($_GET['relpath'] ?? ''); $size = (string) ($_GET['size'] ?? ''); if (empty($relpath)) { http_response_code(400); header('Content-Type: text/plain'); echo 'relpath fehlt'; exit; } // Whitelist if (!preg_match('#^(facture|commande|propal|bericht)/#', $relpath)) { http_response_code(403); header('Content-Type: text/plain'); echo 'Pfad nicht erlaubt: '.$relpath; exit; } $full = bericht_resolve_data_path($relpath); if (!$full || !file_exists($full)) { http_response_code(404); header('Content-Type: text/plain'); echo 'Datei nicht gefunden: '.$relpath; exit; } // Serverseitig erzeugtes Thumbnail (GD, gecacht) — der Weg für die Kachel-Ansicht der PWA. // // Die alte Variante size=small/mini konnte nur ein Thumbnail ausliefern, das Dolibarr selbst // unter thumbs/_small. abgelegt hatte. Fotos aus der PWA kommen aber über // orders.php?action=upload_photo herein, und dort läuft kein vignette() — es gab also nie // eines, und jede Kachel lud das komplette Handyfoto. bericht_attachment_thumb() (seit 1.5.0, // auch vom Editor genutzt) rendert bei Bedarf und legt das Ergebnis unter bericht/thumbs/ ab. if ($size === 'thumb') { $w = (int) ($_GET['w'] ?? 320); if ($w < 40) $w = 40; if ($w > 800) $w = 800; // ETag über mtime: ein ersetztes Original liefert einen neuen Wert, sonst antwortet // der Server mit 304 und überträgt gar nichts. $etag = '"'.sha1($relpath.'|'.@filemtime($full).'|'.$w).'"'; header('ETag: '.$etag); header('Cache-Control: private, max-age=2592000'); if (trim((string) ($_SERVER['HTTP_IF_NONE_MATCH'] ?? '')) === $etag) { http_response_code(304); exit; } $thumb_data = bericht_attachment_thumb($full, $w); if ($thumb_data !== null) { header('Content-Type: image/jpeg'); header('Content-Length: '.strlen($thumb_data)); echo $thumb_data; exit; } // Kein unterstütztes Bild (PDF/Video) oder zu groß für den Speicher: // unten regulär das Original ausliefern. } // Alt-Variante: von Dolibarr vorgefertigtes Thumbnail, falls vorhanden if ($size === 'small' || $size === 'mini') { $dir = dirname($full); $base = pathinfo($full, PATHINFO_FILENAME); $ext = pathinfo($full, PATHINFO_EXTENSION); $thumb = $dir.'/thumbs/'.$base.'_'.$size.'.'.$ext; if (file_exists($thumb)) $full = $thumb; } $mime = function_exists('dol_mimetype') ? dol_mimetype($full) : 'application/octet-stream'; $download = !empty($_GET['download']); $filename = $download ? basename($full) : ''; header('Content-Type: '.$mime); header('Content-Length: '.filesize($full)); header('Cache-Control: private, max-age=3600'); if ($download) { header('Content-Disposition: attachment; filename="'.addslashes($filename).'"'); } readfile($full); exit;