bericht/ajax/page_image.php
Eduard Wisch 4102ebfe5b
All checks were successful
Deploy bericht / deploy (push) Successful in 13s
Editor-Ueberarbeitung 1.5.0: Anhaenge-Kacheln, echte Seitengeometrie, Arbeiten ohne Reload [deploy]
Anhaenge-Spalte: Kachelraster mit Server-Thumbnails statt Dateinamen-Liste (Bilder per GD,
PDFs per PDF.js), Grossansicht per Klick, Auswahlreihenfolge sichtbar, Sortierung/Filter
(Aufnahmezeit per eigenem EXIF-Parser, da im Prod-Container ohne exif-Modul), ziehbare
Spaltenbreite, Kachelgroesse, Drag & Drop auf Seitenliste/Arbeitsflaeche/Raster-Plaetze.

Arbeitstisch zeigt jetzt Kopf-, Inhalts- und Fussbereich passend zur echten Seitengeometrie
(bericht_page_geometry()) - Editor und PDF-Erzeugung rechnen mit denselben Werten, das PDF
setzt die Arbeitsflaeche 1:1 statt sie ein zweites Mal einzupassen.

Neu: Bild einer Seite austauschen (auch einzelne Raster-Plaetze), Seiten mehrfach markieren
und sammeln loeschen/duplizieren, Autosave mit Verlassen-Warnung, Tastenkuerzel, kein
location.reload() mehr nach Hinzufuegen/Loeschen/Hochladen (ajax/fragments.php).

Nebenbei behoben: dol_dir_list() ohne mode=1 (Dateigroesse leer), PDF.js-Worker-Pfad ohne
/custom-Praefix, PDFs liefen als Download statt inline, Groessen-Select blieb leer,
Mehrfach-Upload nahm nur die erste Datei, Race-Condition bei schnellem Seitenwechsel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 00:33:20 +02:00

136 lines
5.9 KiB
PHP

<?php
/* Liefert das Bild/PDF einer Bericht-Seite zur Anzeige im Editor.
* GET: pageid
*
* Bei source_type=image: Original-Bild
* Bei source_type=pdf: PDF wird direkt ausgeliefert (PDF.js rendert clientseitig)
* Bei source_type=upload: ebenso, je nach Endung
*/
$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';
if (!$user->hasRight('bericht', 'read')) accessforbidden();
$pageid = GETPOSTINT('pageid');
if (!$pageid) { http_response_code(400); exit; }
$res = $db->query("SELECT source_type, source_path, COALESCE(layout,'single') AS layout FROM ".$db->prefix()."bericht_page WHERE rowid = ".((int) $pageid));
if (!$res) { http_response_code(500); exit; }
$row = $db->fetch_object($res);
if (!$row) { http_response_code(404); exit; }
// Bei Multi-Image-Seiten: serverseitig ein Composite-Bild rendern (PNG)
if ($row->layout !== 'single') {
// Format/Orientierung des Berichts holen, damit die Vorschau dieselbe
// Proportion hat wie der Inhaltsbereich im Editor und im PDF
$bfmt = 'A4'; $bori = 'P';
$bq = $db->query("SELECT b.page_format, b.page_orientation FROM ".$db->prefix()."bericht b"
." INNER JOIN ".$db->prefix()."bericht_page p ON p.fk_bericht = b.rowid"
." WHERE p.rowid = ".((int) $pageid));
if ($bq && ($brow = $db->fetch_object($bq))) {
if (in_array($brow->page_format, array('A4','A3','A5','Letter'), true)) $bfmt = $brow->page_format;
if (in_array($brow->page_orientation, array('P','L'), true)) $bori = $brow->page_orientation;
}
$imgs_q = $db->query("SELECT slot, source_path FROM ".$db->prefix()."bericht_page_image WHERE fk_page = ".((int) $pageid)." ORDER BY slot");
if (!$imgs_q) { http_response_code(500); exit; }
$imgs = array();
while ($r = $db->fetch_object($imgs_q)) {
$imgs[(int) $r->slot] = $r->source_path;
}
if (empty($imgs)) { http_response_code(404); exit; }
$composite = bericht_render_grid_png($row->layout, $imgs, bericht_page_geometry($bfmt, $bori, false));
if (!$composite) { http_response_code(500); exit; }
header('Content-Type: image/png');
header('Cache-Control: no-store');
echo $composite;
exit;
}
$full = bericht_resolve_data_path($row->source_path);
if (!$full || !file_exists($full)) { http_response_code(404); exit; }
$mime = dol_mimetype($full);
header('Content-Type: '.$mime);
header('Content-Length: '.filesize($full));
header('Cache-Control: private, max-age=300');
readfile($full);
/**
* Erstellt ein Composite-PNG für eine Multi-Image-Seite (Editor-Vorschau).
*/
function bericht_render_grid_png($layout, $imgs, array $g)
{
// Grundfläche = Inhaltsbereich der Seite bei ~150 DPI, damit die Vorschau
// exakt der Fläche entspricht, die das PDF später bedruckt.
$px_per_mm = 150 / 25.4;
$W = (int) round($g['contentW'] * $px_per_mm);
$H = (int) round($g['contentH'] * $px_per_mm);
$gap = (int) round(4 * $px_per_mm);
$canvas = imagecreatetruecolor($W, $H);
imagefill($canvas, 0, 0, imagecolorallocate($canvas, 255, 255, 255));
// Slot-Aufteilung wie BerichtPage::slotRectsInBox, hier in Pixel
$rects = array();
if ($layout === 'grid_2') {
$h = ($H - $gap) / 2;
for ($i = 0; $i < 2; $i++) $rects[] = array(0, $i * ($h + $gap), $W, $h);
} elseif ($layout === 'grid_2v' || $layout === 'before_after') {
$w = ($W - $gap) / 2;
for ($i = 0; $i < 2; $i++) $rects[] = array($i * ($w + $gap), 0, $w, $H);
} elseif ($layout === 'grid_4') {
$w = ($W - $gap) / 2;
$h = ($H - $gap) / 2;
for ($r = 0; $r < 2; $r++) for ($c = 0; $c < 2; $c++) {
$rects[] = array($c * ($w + $gap), $r * ($h + $gap), $w, $h);
}
} elseif ($layout === 'grid_6') {
$w = ($W - 2 * $gap) / 3;
$h = ($H - $gap) / 2;
for ($r = 0; $r < 2; $r++) for ($c = 0; $c < 3; $c++) {
$rects[] = array($c * ($w + $gap), $r * ($h + $gap), $w, $h);
}
}
foreach ($rects as $i => $r) {
list($rx, $ry, $rw, $rh) = $r;
// Slot-Hintergrund + Border
$border = imagecolorallocate($canvas, 200, 200, 200);
imagerectangle($canvas, $rx, $ry, $rx + $rw, $ry + $rh, $border);
if (!isset($imgs[$i])) continue;
$full = bericht_resolve_data_path($imgs[$i]);
if (!$full || !file_exists($full)) continue;
$info = @getimagesize($full);
if (!$info) continue;
$src = null;
if ($info[2] === IMAGETYPE_JPEG) $src = @imagecreatefromjpeg($full);
elseif ($info[2] === IMAGETYPE_PNG) $src = @imagecreatefrompng($full);
if (!$src) continue;
$sw = $info[0]; $sh = $info[1];
$ratio = min($rw / $sw, $rh / $sh);
$dw = (int) ($sw * $ratio);
$dh = (int) ($sh * $ratio);
$dx = (int) ($rx + ($rw - $dw) / 2);
$dy = (int) ($ry + ($rh - $dh) / 2);
imagecopyresampled($canvas, $src, $dx, $dy, 0, 0, $dw, $dh, $sw, $sh);
imagedestroy($src);
}
ob_start();
imagepng($canvas, null, 6);
imagedestroy($canvas);
return ob_get_clean();
}