bericht/ajax/page_image.php
Eduard Wisch 06cd70d4a3
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
feat: Phase 1.4 + 1.5 — Multi-Image Grids und Bildgröße komplett
DB-Klasse:
- BerichtPage::getImages() liest llx_bericht_page_image
- BerichtPage::setSlotImage() / clearImages()
- BerichtPage::slotCountForLayout()
- BerichtPage::slotRects() berechnet Slot-Positionen für 1/2/2v/4/6
- create()/update()/fetchAllForBericht() inkludieren layout/scale/align

Endpoints (alle im Bericht-Modul):
- ajax/save_page_options.php — speichert layout, image_scale, image_align
- ajax/create_grid_page.php — erstellt Multi-Image-Seite mit gewähltem Layout
- ajax/set_slot_image.php — setzt einzelnes Bild eines Slots
- ajax/page_meta.php liefert layout/scale/align mit
- ajax/page_image.php rendert Composite-PNG (GD) für Multi-Image-Seiten

UI:
- Layout-Dropdown in 3. Toolbar-Zeile (Single/Grid 2/2v/4/6)
- Bildgröße-Dropdown (100/70/50/30%) — single-only
- Position-Dropdown (Anpassen/Zentriert/Ecken) — single-only
- 'Als Grid hinzufügen'-Buttons in der Anhänge-Liste (▭▭ ▯▯ ▦ ▦▦)
- Auto-Sync der single-only-Felder beim Layout-Wechsel

Rendering:
- bericht_render_page_to_pdf() in lib/bericht.lib.php — zentrale
  Render-Funktion für Single + Grid + PDF-Quelle + image_scale + align
- bericht_align_position() für die 6 Align-Modi
- generate_pdf + preview_pdf nutzen die gemeinsame Funktion (DRY)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 22:33:44 +02:00

128 lines
5.1 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') {
$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);
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)
{
// A4 Hochformat als Grundfläche, 150 DPI
$W = 1240; // ~A4 @ 150dpi
$H = 1754;
$margin = 40;
$gap = 16;
$canvas = imagecreatetruecolor($W, $H);
imagefill($canvas, 0, 0, imagecolorallocate($canvas, 255, 255, 255));
// Slot-Berechnung (gleiche Logik wie BerichtPage::slotRects, aber in Pixel)
$iw = $W - 2 * $margin;
$ih = $H - 2 * $margin - 60; // 60px für Notiz-Bereich
$rects = array();
if ($layout === 'grid_2') {
$h = ($ih - $gap) / 2;
for ($i = 0; $i < 2; $i++) $rects[] = array($margin, $margin + $i * ($h + $gap), $iw, $h);
} elseif ($layout === 'grid_2v') {
$w = ($iw - $gap) / 2;
for ($i = 0; $i < 2; $i++) $rects[] = array($margin + $i * ($w + $gap), $margin, $w, $ih);
} elseif ($layout === 'grid_4') {
$w = ($iw - $gap) / 2;
$h = ($ih - $gap) / 2;
for ($r = 0; $r < 2; $r++) for ($c = 0; $c < 2; $c++) {
$rects[] = array($margin + $c * ($w + $gap), $margin + $r * ($h + $gap), $w, $h);
}
} elseif ($layout === 'grid_6') {
$w = ($iw - 2 * $gap) / 3;
$h = ($ih - $gap) / 2;
for ($r = 0; $r < 2; $r++) for ($c = 0; $c < 3; $c++) {
$rects[] = array($margin + $c * ($w + $gap), $margin + $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();
}