bericht/ajax/save_annotations.php
Eduard Wisch 195942a2f9
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
feat: Phase 6 — Client-WYSIWYG via Composite-PNG + Text-BG + Dark-Input-Fix
Paradigmen-Wechsel: Editor rendert bei jedem Save sein Fabric-Canvas
als PNG und lädt es hoch. PDF nutzt dieses PNG 1:1 statt die Shapes
serverseitig nachzuzeichnen.

Damit ist garantiert: was du im Editor siehst, ist EXAKT das was im PDF
landet. Alle Pfeil/Text/Shape-Rendering-Bugs zwischen Fabric-JSON und
PHP-Nachzeichnung sind Geschichte.

Kernänderungen:

1. DB: Neue Spalte bericht_page.composite_path (Migration im init())
2. ajax/save_annotations.php: nimmt multipart file 'composite' entgegen,
   speichert es unter bericht/work/<fkb>/composite_<pid>.png
3. lib/bericht.lib.php: bericht_render_page_to_pdf prüft composite_path
   zuerst — wenn vorhanden, wird eine Seite mit genau diesem PNG als
   volles Bild gerendert, fertig. Fallback auf alte Logik bei alten
   Berichten ohne Composite.
4. editor.js renderImage: Quellbild wird NICHT mehr auf pdfCanvas
   gezeichnet, sondern als fabric.Image ins Fabric-Canvas geladen —
   ZIEHBAR, SKALIERBAR, ROTIERBAR wie jedes andere Objekt.
   Mehrere Bilder auf einer Seite kein Problem mehr.
5. editor.js savePageAnnotations: nach Shape-State wird toDataURL
   mit multiplier:2 aufgerufen, PNG-Blob hochgeladen zusammen mit
   fabric_json (für spätere Edits) und note.
6. editor.js loadPage: wenn fabric_json existiert, wird dieses
   clientseitig wieder eingeladen (inkl. eingebettete Bilder) — das
   Quell-Bild wird nicht mehr neu aus der Quelle geholt. Bei leerer
   Seite läuft der alte Render-Flow.

Phase 6 Bonus — Text mit Hintergrund:
- Neuer color-picker 'BG:' in der Toolbar + 'Ø'-Button (kein BG)
- Fabric IText bekommt textBackgroundColor + padding:6
- Bei selektiertem Text-Objekt wird BG live angewendet
- Dataset-Flag 'active' toggelt zwischen ein/aus

Dark-Input-Fix:
- Textarea in .bericht-page-note nutzte --inputbackgroundcolor
  (existiert in awl-dark nicht → Fallback #fff = weiße Fläche mit
  schwarzer Schrift auf Dark-Theme)
- Jetzt: --colorbackbody + --colortext + --colorboxbordertitle1
- Generischer Input-Style für alle Text-Eingaben in .bericht-editor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:26:57 +02:00

60 lines
2.4 KiB
PHP

<?php
/* Speichert Fabric.js-JSON + Notiz + optional Composite-PNG für eine Seite.
*
* POST:
* pageid — Seiten-ID
* fabric_json — Fabric-Serialisierung (für späteren Edit)
* note — Notiz für den PDF-Footer
* rotation — Rotation 0/90/180/270
* composite — (optional) multipart/form-data file: PNG der kompletten Seite
* token
*/
require_once __DIR__.'/_inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
global $db, $user;
if (!$user->hasRight('bericht', 'write')) bericht_ajax_fail('Permission denied', 403);
$pageid = (int) ($_POST['pageid'] ?? 0);
if (!$pageid) bericht_ajax_fail('pageid fehlt');
$fabric = (string) ($_POST['fabric_json'] ?? '');
$note = (string) ($_POST['note'] ?? '');
$rotation = (int) ($_POST['rotation'] ?? 0);
$rotation = (($rotation % 360) + 360) % 360;
// Page laden und fk_bericht ermitteln
$res = $db->query("SELECT rowid, fk_bericht FROM ".$db->prefix()."bericht_page WHERE rowid = ".((int) $pageid));
if (!$res || !($row = $db->fetch_object($res))) bericht_ajax_fail('Page nicht gefunden', 404);
$fk_bericht = (int) $row->fk_bericht;
// Composite-PNG speichern wenn hochgeladen
$composite_relpath = null;
if (!empty($_FILES['composite']['tmp_name']) && is_uploaded_file($_FILES['composite']['tmp_name'])) {
$workdir = DOL_DATA_ROOT.'/bericht/work/'.$fk_bericht;
if (!is_dir($workdir)) dol_mkdir($workdir);
$filename = 'composite_'.$pageid.'.png';
$target = $workdir.'/'.$filename;
if (move_uploaded_file($_FILES['composite']['tmp_name'], $target)) {
$composite_relpath = str_replace(DOL_DATA_ROOT.'/', '', $target);
}
}
// Prüfen ob composite_path-Spalte existiert
$has_composite = false;
$ccheck = $db->query("SHOW COLUMNS FROM ".$db->prefix()."bericht_page LIKE 'composite_path'");
if ($ccheck && $db->num_rows($ccheck) > 0) $has_composite = true;
$sets = array(
"fabric_json = ".($fabric !== '' ? "'".$db->escape($fabric)."'" : "NULL"),
"note = ".($note !== '' ? "'".$db->escape($note)."'" : "NULL"),
"rotation = ".((int) $rotation),
);
if ($composite_relpath !== null && $has_composite) {
$sets[] = "composite_path = '".$db->escape($composite_relpath)."'";
}
$sql = "UPDATE ".$db->prefix()."bericht_page SET ".implode(', ', $sets)." WHERE rowid = ".((int) $pageid);
if (!$db->query($sql)) bericht_ajax_fail('DB-Fehler: '.$db->lasterror());
bericht_ajax_ok(array('composite_saved' => $composite_relpath !== null));