Die API-Seite zur Baustelle-PWA holt nach, was der Editor mit 1.5.0 bekommen hat.
photo.php kann jetzt size=thumb&w=<px> und rendert ueber bericht_attachment_thumb()
(GD, Cache, EXIF-Rotation) statt die Originaldatei auszuliefern. size=small konnte nur
ein von Dolibarr vorgefertigtes thumbs/<name>_small.<ext> liefern - fuer Uploads ueber
orders.php?action=upload_photo gibt es das nicht, dort laeuft kein vignette(). Die PWA
lud dadurch fuer jede Kachel das komplette Foto: gemessen 9,8 KB statt 235 KB je Kachel.
Antwort mit ETag aus der mtime, Folgeaufrufe enden mit 304.
orders.php?action=photos liefert taken_at (EXIF-Aufnahmezeit ueber bericht_file_taken_at,
sonst Dateidatum) und sortiert danach statt nach filemtime.
reports.php gibt je Seite title und composite_path aus. title konnte ueber pages.php
gesetzt werden, kam aber nie zurueck. composite_path ist das im Editor gebaute
Seitenbild - ohne das zeigen Clients nur das Rohbild ohne Anmerkungen, bei
Raster-Layouts eines von bis zu sechs Bildern und bei title_only gar keins.
Neu: api/note.php - Textnotizen zum Auftrag, aufgebaut wie die Sprachnotiz. Die Notiz
liegt als notiz_<betreff>_<datum>.txt im Auftragsverzeichnis und ist damit auch im
Dolibarr-Auftrag und in der Anhaenge-Spalte des Editors sichtbar. Liste, Lesen, Anlegen,
Aendern, Loeschen. Der file-Parameter wird gegen ^notiz_[A-Za-z0-9_.-]*\.txt$ geprueft;
gegen Pfad-Ausbrueche getestet.
ROADMAP.md wiederhergestellt - sie war mit eb37a4b geloescht worden, weil alle Punkte
abgehakt waren. Sie bleibt ab jetzt liegen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
179 lines
6.9 KiB
PHP
179 lines
6.9 KiB
PHP
<?php
|
|
/* Textnotizen zu einem Auftrag — getippt statt diktiert.
|
|
*
|
|
* GET /api/note.php?order_id=<id> — Liste aller Notizen
|
|
* GET /api/note.php?order_id=<id>&file=<name> — eine Notiz mit Text
|
|
* POST /api/note.php?order_id=<id> — anlegen { subject, text }
|
|
* POST /api/note.php?order_id=<id>&file=<name> — ändern { subject, text }
|
|
* POST /api/note.php?order_id=<id>&file=<name>&delete=1 — löschen
|
|
*
|
|
* Gegenstück zu voice.php: die Notiz liegt als Datei im Auftrags-Verzeichnis und taucht
|
|
* damit in der Dokumentliste der PWA und in der Anhänge-Spalte des Bericht-Editors auf.
|
|
*
|
|
* Dateiformat (bewusst als Klartext lesbar, auch wenn jemand die .txt direkt öffnet):
|
|
*
|
|
* Betreff: Zählernummern Keller
|
|
* Erfasst: 22.08.2026 17:51 · data
|
|
* Geändert: 22.08.2026 18:20 · data
|
|
*
|
|
* <freier Text>
|
|
*
|
|
* Der Kopf geht bis zur ersten Leerzeile. Der Betreff steht zusätzlich im Dateinamen,
|
|
* damit man die Notiz in der Dokumentliste wiederfindet.
|
|
*/
|
|
require_once __DIR__.'/_inc.php';
|
|
|
|
api_authenticate();
|
|
global $db, $user, $conf;
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
|
|
|
$order_id = (int) ($_GET['order_id'] ?? 0);
|
|
if (!$order_id) api_fail('order_id fehlt');
|
|
|
|
$cmd = new Commande($db);
|
|
if ($cmd->fetch($order_id) <= 0) api_fail('Auftrag nicht gefunden', 404);
|
|
|
|
$dir = $conf->commande->multidir_output[$cmd->entity].'/'.dol_sanitizeFileName($cmd->ref);
|
|
|
|
/**
|
|
* Prüft den übergebenen Dateinamen. Nur Notiz-Dateien im Auftragsordner sind erlaubt —
|
|
* ohne das könnte man über ../ jede Datei unter DOL_DATA_ROOT überschreiben.
|
|
*/
|
|
function note_safe_path($dir, $name)
|
|
{
|
|
$base = basename((string) $name);
|
|
if ($base === '' || $base !== $name) return null;
|
|
if (!preg_match('/^notiz_[A-Za-z0-9_.\-]*\.txt$/', $base)) return null;
|
|
return $dir.'/'.$base;
|
|
}
|
|
|
|
/** Kopf + Text einer Notizdatei auseinandernehmen. */
|
|
function note_parse($raw)
|
|
{
|
|
$meta = array('subject' => '', 'created' => '', 'modified' => '');
|
|
$lines = preg_split('/\r\n|\r|\n/', (string) $raw);
|
|
$i = 0;
|
|
for (; $i < count($lines); $i++) {
|
|
$line = $lines[$i];
|
|
if (trim($line) === '') { $i++; break; }
|
|
if (preg_match('/^Betreff:\s*(.*)$/u', $line, $m)) { $meta['subject'] = trim($m[1]); continue; }
|
|
if (preg_match('/^Erfasst:\s*(.*)$/u', $line, $m)) { $meta['created'] = trim($m[1]); continue; }
|
|
if (preg_match('/^Geändert:\s*(.*)$/u', $line, $m)) { $meta['modified'] = trim($m[1]); continue; }
|
|
// Keine Kopfzeile — die Datei hat keinen Kopf, alles ist Text
|
|
$i = 0;
|
|
break;
|
|
}
|
|
$meta['text'] = implode("\n", array_slice($lines, $i));
|
|
return $meta;
|
|
}
|
|
|
|
/** Kopf + Text wieder zu einer Datei zusammensetzen. */
|
|
function note_build($subject, $text, $created, $modified)
|
|
{
|
|
$head = 'Betreff: '.$subject."\n".'Erfasst: '.$created."\n";
|
|
if ($modified !== '') $head .= 'Geändert: '.$modified."\n";
|
|
return $head."\n".rtrim((string) $text)."\n";
|
|
}
|
|
|
|
/* ---------- Liste ---------- */
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET' && empty($_GET['file'])) {
|
|
$out = array();
|
|
foreach (glob($dir.'/notiz_*.txt', GLOB_NOSORT) ?: array() as $f) {
|
|
if (!is_file($f)) continue;
|
|
$meta = note_parse(@file_get_contents($f));
|
|
$text = trim($meta['text']);
|
|
$out[] = array(
|
|
'file' => basename($f),
|
|
'subject' => $meta['subject'],
|
|
'created' => $meta['created'],
|
|
'modified' => $meta['modified'],
|
|
// Kurzfassung für die Liste, damit die App nicht jede Datei einzeln holen muss
|
|
'preview' => dol_trunc(preg_replace('/\s+/u', ' ', $text), 140, 'right', 'UTF-8', 1),
|
|
'date' => (int) @filemtime($f),
|
|
'size' => (int) @filesize($f),
|
|
'relpath' => str_replace(DOL_DATA_ROOT.'/', '', $f),
|
|
);
|
|
}
|
|
usort($out, function ($a, $b) { return $b['date'] <=> $a['date']; });
|
|
api_ok(array('notes' => $out, 'count' => count($out)));
|
|
}
|
|
|
|
/* ---------- Einzelne Notiz lesen ---------- */
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
$path = note_safe_path($dir, $_GET['file'] ?? '');
|
|
if (!$path || !is_file($path)) api_fail('Notiz nicht gefunden', 404);
|
|
$meta = note_parse(@file_get_contents($path));
|
|
api_ok(array('note' => array(
|
|
'file' => basename($path),
|
|
'subject' => $meta['subject'],
|
|
'text' => $meta['text'],
|
|
'created' => $meta['created'],
|
|
'modified' => $meta['modified'],
|
|
'relpath' => str_replace(DOL_DATA_ROOT.'/', '', $path),
|
|
)));
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') api_fail('Methode nicht unterstützt', 405);
|
|
if (!$user->hasRight('bericht', 'write')) api_fail('Permission denied', 403);
|
|
|
|
/* ---------- Löschen ---------- */
|
|
if (!empty($_GET['delete'])) {
|
|
if (!$user->hasRight('bericht', 'delete')) api_fail('Permission denied', 403);
|
|
$path = note_safe_path($dir, $_GET['file'] ?? '');
|
|
if (!$path || !is_file($path)) api_fail('Notiz nicht gefunden', 404);
|
|
if (!@unlink($path)) api_fail('Löschen fehlgeschlagen', 500);
|
|
api_ok();
|
|
}
|
|
|
|
$in = api_input();
|
|
$subject = trim((string) ($in['subject'] ?? ''));
|
|
$text = (string) ($in['text'] ?? '');
|
|
if ($subject === '' && trim($text) === '') api_fail('Notiz ist leer');
|
|
if (strlen($text) > 200000) api_fail('Notiz ist zu lang');
|
|
|
|
$stamp = dol_print_date(dol_now(), '%d.%m.%Y %H:%M').' · '.$user->login;
|
|
|
|
/* ---------- Ändern ---------- */
|
|
if (!empty($_GET['file'])) {
|
|
$path = note_safe_path($dir, $_GET['file']);
|
|
if (!$path || !is_file($path)) api_fail('Notiz nicht gefunden', 404);
|
|
$old = note_parse(@file_get_contents($path));
|
|
$created = $old['created'] !== '' ? $old['created'] : $stamp;
|
|
|
|
if (@file_put_contents($path, note_build($subject, $text, $created, $stamp)) === false) {
|
|
api_fail('Speichern fehlgeschlagen', 500);
|
|
}
|
|
api_ok(array(
|
|
'file' => basename($path),
|
|
'subject' => $subject,
|
|
'relpath' => str_replace(DOL_DATA_ROOT.'/', '', $path),
|
|
));
|
|
}
|
|
|
|
/* ---------- Neu anlegen ---------- */
|
|
if (!is_dir($dir)) dol_mkdir($dir);
|
|
|
|
// Betreff in einen dateinamenstauglichen Teil überführen — der Name ist das Einzige,
|
|
// was in der Dokumentliste zu sehen ist.
|
|
$subject_part = '';
|
|
if ($subject !== '') {
|
|
$s = dol_string_nospecial(dol_sanitizeFileName($subject), '_');
|
|
$s = trim(preg_replace('/_+/', '_', $s), '_');
|
|
if ($s !== '') $subject_part = '_'.dol_trunc($s, 60, 'right', 'UTF-8', 1);
|
|
}
|
|
|
|
$filename = 'notiz'.$subject_part.'_'.dol_print_date(dol_now(), '%Y%m%d_%H%M%S').'.txt';
|
|
$target = $dir.'/'.$filename;
|
|
if (@file_put_contents($target, note_build($subject, $text, $stamp, '')) === false) {
|
|
api_fail('Speichern fehlgeschlagen', 500);
|
|
}
|
|
@chmod($target, 0664);
|
|
|
|
api_ok(array(
|
|
'file' => $filename,
|
|
'subject' => $subject,
|
|
'relpath' => str_replace(DOL_DATA_ROOT.'/', '', $target),
|
|
'size' => (int) filesize($target),
|
|
));
|