— Liste aller Notizen * GET /api/note.php?order_id=&file= — eine Notiz mit Text * POST /api/note.php?order_id= — anlegen { subject, text } * POST /api/note.php?order_id=&file= — ändern { subject, text } * POST /api/note.php?order_id=&file=&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 * * * * 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), ));