All checks were successful
Deploy bericht / deploy (push) Successful in 1s
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]
128 lines
5.1 KiB
PHP
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();
|
|
}
|