| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/arni-solutions.de/web/terminal/ |
Upload File : |
# Terminal-System (Grundstruktur)
Neutrales, PIN-gesichertes Terminal-Portal. Ein **Terminal** ist ein dauerhafter,
PIN-gesicherter Zugang mit festem Hash-Link. Einem Terminal werden **Ansichten**
zugeordnet, die über das Terminal genutzt werden können (Inhalt der Ansichten folgt später).
> **Verwaltung:** Terminals & Ansichten werden in der **ArNi CRM** unter
> **System → Terminals** verwaltet (Admin-Login der CRM). Dieses Verzeichnis enthält
> nur noch das öffentliche Portal – es gibt **kein eigenes Admin-Login mehr**.
## Struktur
```
terminal/
config.inc.php Konfiguration (DB, lädt Secret + Libs)
includes/
secret.php Gemeinsames HMAC-Secret (auch von der CRM gelesen)
db.php PDO-Verbindung + Tabellen-Präfix (term_)
helpers.php Escaping, Signatur, t_html()/t_css()
terminals.php Terminal-Bibliothek (PIN, Cookie, Laden, Zuordnung, grants)
terminal.php?h=<hash> Öffentliches Portal: PIN-Gate → Startseite
view.php?t=<token>&term=<hash> Ansicht über ein Terminal (Stub)
sql/schema.sql Tabellen term_terminals, term_views, term_terminal_access
```
## Datenbank
Nutzt die ArNi-CRM-Datenbank (`arni_shopping`) mit Präfix `term_`.
Schema einspielen (idempotent):
```
php -r 'require "config.inc.php"; term_db()->exec(file_get_contents("sql/schema.sql"));'
```
## Verwaltung (in der CRM)
ArNi CRM → **System → Terminals** (nur Admin).
- Terminal anlegen: Bezeichnung + E-Mail (optional) + PIN (4–8 Ziffern) + Aktiv + Ansichts-Zuordnung.
- Es entsteht ein fester Link `…/terminal/terminal.php?h=<hash>` (inkl. QR). Gültig bis Deaktivieren/Löschen.
- Ansichten (neutrale Platzhalter) anlegen/aktivieren/löschen.
Die CRM teilt das HMAC-Secret über `includes/secret.php` (Pfad in der CRM-Config
unter `terminal.secret_file`). Dadurch sind im CRM gesetzte PINs im Portal gültig.
## Portal-Ablauf
1. `terminal.php?h=<hash>` aufrufen → PIN-Abfrage (Terminal-PIN).
2. Nach Eingabe wird ein Freischalt-Cookie `term_<hash>` gesetzt (Standard 1 Jahr).
3. Startseite im **Premium-Dashboard-Stil** (`assets/home.{css,js}`): dunkle
Fläche `#0C0F15`, Karten `#161B24` (24px-Radius, weiche Schatten, Hover
`scale(1.02)`), Primär-Verlauf `#5D5FEF → #7A5AF8`; Grün nur für den
VPN-Verbunden-Status, Rot nur für Warnungen (`warn`-Flag). Begrüßung + Uhr
im fixierten Header, Pfeiltasten-Navigation.
Kachel-Layout: Icon links, Titel/Beschreibung floaten daneben; darunter
**klickbare Vorschau-Zeilen** (`{ic,t,s,url,img,ok,warn}`, PHP
`term_tv_item()` = JS `itemNode()`), die Datensätze **direkt aus der Karte
öffnen** – Medien per `&item=<id>` (öffnet die Detailansicht), Webseiten
per `&site=<id>` (Vorauswahl), Worklog/Monatsliste per `&m=YYYY-MM`,
Pinnwand-Beiträge per `&post=<id>`. Worklog/Monatsliste rendern ihre
Zeilen direkt (lokale Daten); VPN, Medien, Arbeitszeit, Rechnungen,
Webseiten und Slots laden **asynchron** über die Modul-Aktion `api=tile`
nach (bis dahin Skeleton-Zeilen). Antwort-Varianten: `variant=duo`
(VPN: zwei gleich große Infofelder – Land/Status | Exit-IP) und
`variant=rail` (Medien: horizontale **Poster-Rail** mit echten Covern
3:4 aus der CRM, Play-Button, Deep-Link je Titel).
Die Pinnwand ist eine Hero-Kachel im Social-Feed-Stil: 3 neueste Beiträge
mit Avatar, Name, Zeit und **echtem Vorschaubild rechts** (Bild-Thumbnail,
Video-Poster mit Play-Overlay, „+N“-Stapel-Badge bei mehreren Bildern,
gecachte Link-/YouTube-Vorschau – alles über `pinnwand-media.php`),
dünne Trennlinien, Ungelesen-Badge und **Schnell-Beitrag** (postet direkt
über `view.php?…&api=post_create`, ohne die Ansicht zu öffnen; Eingabefeld
mit Fokus-Glow, runder Senden-Button).
4. Eine Ansicht öffnet `view.php?t=<token>&term=<hash>`; `term_terminal_grants()`
prüft das Freischalt-Cookie – eine evtl. eigene PIN der Ansicht wird übersprungen.
5. **Einstellungen** (`?settings=1`, über den Initialen-Button im Header) in Tabs:
„PIN" (PIN ändern), „Startseite" (Widgets per ▲▼ sortieren und per Schalter
ein-/ausblenden; nur Nutzer-Präferenz – die Zuordnung/Berechtigung bleibt in
der CRM, daher jederzeit wieder aktivierbar; Speicherung in
`term_terminal_meta` `tile_order`/`tile_hidden`, Tabelle wird zur Laufzeit
angelegt) sowie „Slots" (Guthaben & Spielauswahl, falls zugeordnet).
## Sicherheit
- PIN wird als HMAC (`term_sign`, Secret in `includes/secret.php`) gespeichert, nie im Klartext.
- Freischalt-Cookies sind `HttpOnly`, `SameSite=Lax`, `Secure` (bei HTTPS), auf `/terminal` begrenzt.
- Öffentliche Seiten setzen `Cache-Control: no-store`.
## Modul „Pinnwand" (öffentlicher Feed)
Ansichtsmodul `pinnwand` (`includes/modules/pinnwand.php` + `assets/modules/pinnwand.{css,js}`):
gemeinsamer Feed je Ansicht – am sinnvollsten als **globale** Ansicht (ein Feed für alle Terminals).
- **Beiträge:** Text (Limit konfigurierbar, Standard 2000 Zeichen), mehrere Bilder,
Video(s), Link mit Vorschau. Autor = Terminal (Initialen-Avatar). Bearbeiten
(nur Text, „bearbeitet"-Badge) & Löschen (Soft-Delete) durch den Autor.
- **Feed:** Cursor-Pagination (id DESC), Infinite Scroll, Filter
(Alle/Bilder/Videos/Links/Eigene/Angepinnt; „Videos" umfasst auch
YouTube-Links), Suche, 15-s-Polling (neue Beiträge
werden direkt eingefügt, bei vielen ein Hinweis-Button; kein Feed-Springen),
Skeleton-/Leer-/Fehler-/Offline-Zustände,
Entwurf in `localStorage`. Direktlink je Beitrag: `view.php?t=…&term=…&post=<id>`.
- **Uploads:** Chunk-Upload (~900 KB je Chunk, wegen nginx-Limit 1M auf arni-solutions.de);
Bilder clientseitig vorverkleinert, serverseitig per GD neu kodiert (EXIF-Rotation,
**Metadaten entfernt**, 4 Größen: Original ≤2560 / `_m` 1280 / `_t` 360 /
`_s` 160 **Mini** für Kachel-/Kommentar-Thumbs; Bestandsdateien werden bei der
ersten Mini-Anfrage lazy aus `_t`/`_p`/Link-Bild erzeugt und gecacht,
`s=s|ls` in `pinnwand-media.php`), Videos nur MP4/WebM per **Magic Bytes**
(MOV nur, wenn `ffmpeg` vorhanden → Remux; ffmpeg liefert optional Poster+Dauer).
**Video-Poster ohne ffmpeg:** der Client zieht einen Frame aufs Canvas
(`capturePoster`), prüft die Helligkeit und nimmt bei (fast) schwarzem Frame
(Fade-In) einen späteren Zeitpunkt (1/3/6/10 s, sonst hellster Versuch) und
reicht ihn per `up_poster` nach (`{base}_p.jpg`, GD-Re-Encode). Bestandsvideos
ohne Poster werden beim Betrachten im Feed **lazy nachgezogen** (erster
Schreiber gewinnt, fremde Poster werden nie überschrieben; 1 Versuch je
Gerät via localStorage). Auch der ffmpeg-Pfad prüft auf Schwarzframes
(`module_pinnwand_frame_dark`) und probiert spätere Zeitpunkte. Mit dem
Poster erscheint das Video-Thumb auch auf der Startseiten-Kachel (Mini `_s`).
Zufällige Dateinamen, Ablage `data/pinnwand/<view_id>/`, Auslieferung ausschließlich
über `pinnwand-media.php` (Cookie-Auth wie `view.php`, HTTP-Range für Video).
- **Link-Vorschau:** SSRF-geschützt (nur http/https, DNS-Auflösung mit Sperrung
privater/reservierter IP-Bereiche je Redirect-Hop, max. 3 Redirects, 512-KB-Cap);
og:image wird lokal gecacht (kein Hotlinking). Cache je URL 7 Tage (`term_post_links`).
YouTube-Links (watch/shorts/live/youtu.be) laufen über **oEmbed** (Titel, Kanal,
Thumbnail; kein API-Key nötig), da YouTube dem Bot nur die Consent-Seite liefert;
im Feed startet der Klick einen **eingebetteten Player** (youtube-nocookie,
lädt erst nach dem Klick) statt zu YouTube abzuspringen. Über den eigenen
⛶-Button („Vollbild mit Kommentaren") faden **nur im Vollbild** die
Kommentare des Beitrags als Ticker über der Steuerleiste ein (Glas-Pille,
nacheinander in Schleife; YouTubes interner Vollbildmodus kann technisch
nicht überlagert werden); Klick auf einen Ticker-Kommentar verlässt das
Vollbild, öffnet das Kommentar-Modal und **springt zum angeklickten
Kommentar** (zentriert + kurz hervorgehoben, lädt bei Bedarf ältere Seiten nach).
Existiert eine Vorschau-Karte, wird die nackte URL im Beitragstext ausgeblendet.
- **Profile:** je Terminal & Ansicht: Anzeigename, Profilfoto, Header-Foto
(Zuschnitt per Drag/Zoom im Client, serverseitig GD-Re-Encode). Eigenes Profil über
den Avatar in der Sticky-Filterleiste; Klick auf fremde Avatare öffnet die
Profilansicht mit allen Beiträgen der Person (Tabelle `term_post_profiles`,
Auslieferung `pinnwand-media.php?s=a|h`).
- **Live-Refresh:** 15-s-Polling liefert neue Beiträge direkt mit und fügt sie oben
ein (Scroll-Position wird kompensiert); bei vielen neuen Beiträgen Hinweis-Button.
- **Reaktionen:** Facebook-Stil – Zusammenfassung (Emoji-Stapel + Summe links,
„N Kommentare" rechts) über der Aktionszeile „Gefällt mir | Kommentieren".
Tippen auf „Gefällt mir" setzt/entfernt 👍; **Gedrückthalten** öffnet die
Auswahl (Standard: 👍 ❤️ 😂 😘 😎 😡 😢, Set konfigurierbar). **Eine** Reaktion je
Terminal & Beitrag – eine andere Auswahl ersetzt die bisherige.
Klick auf die Zusammenfassung öffnet das Modal **„Reaktionen"** (`reaction_list`):
wer hat wie reagiert, mit Filter-Chips je Emoji (bei mehreren Typen).
- **Kommentare:** je Beitrag (💬 in der Reaktionsleiste), Chat-Stil (neueste unten,
ältere oben nachladbar), Bearbeiten/Löschen (Soft-Delete) durch Autor bzw. Moderator,
Zeichenlimit konfigurierbar (Standard 500), Rate-Limit = 3× Beitrags-Limit.
Abschaltbar (`comments_enabled`); bestehende Kommentare bleiben dann lesbar.
**Bild-Anhänge** (📷, falls Bilder erlaubt; max. `min(4, max_images)` je Kommentar,
auch ohne Text): gleiche Upload-Pipeline wie Beiträge – Client-Resize + Chunk-Upload
+ serverseitige GD-Neukodierung, d. h. zu große oder ungeeignete Formate werden
automatisch konvertiert. Ablage in `term_post_media` über die Spalte `comment_id`
(wird bei Bestandsinstallationen zur Laufzeit nachgerüstet); Anzeige als
Mini-Galerie in der Kommentar-Blase mit Lightbox.
**Handy-Sticker** (iOS/Android: über die Tastatur eingefügte Bild-Sticker):
lösen im Textfeld ein `paste`-Event mit Bilddatei aus → werden wie ein
📷-Anhang behandelt (Komposer UND Kommentare, `pastedImages()` in
`pinnwand.js`). **Transparenz bleibt erhalten:** Bilder mit Alphakanal werden
clientseitig als PNG hochgeladen (Stichprobe in `resizeImage`) und in
`module_pinnwand_store_image` in allen 4 Größen als PNG gespeichert
(`ext='png'` in `term_post_media`; Erkennung per 48×48-Alpha-Stichprobe,
deckende Bilder werden weiter platzsparend JPEG). Transparente Ränder werden
per `imagecropauto(IMG_CROP_TRANSPARENT)` abgeschnitten (Sticker-Apps polstern
auf eine quadratische Leinwand); Anzeige in natürlicher Größe
(`width:auto; max-width:100%`), nicht auf volle Breite gezogen. Auslieferung:
`pinnwand-media.php` fällt je Größe von `.jpg` auf `.png` zurück; der
Serializer liefert `alpha:true` → `.is-alpha` (kein schwarzer Kasten/Rahmen,
`object-fit: contain`). Link-Vorschaubilder und Profilfotos bleiben immer
JPEG (`$keepAlpha = false`).
- **Mitteilungen:** 🔔 oben rechts mit Ungelesen-Badge (fremde Kommentare auf eigene
Beiträge, Zähler kommt mit dem 15-s-Polling). Öffnen der Liste markiert alles als
gelesen (`notif_seen_at` in `term_post_profiles`); Klick springt zum Beitrag und
öffnet die Kommentare.
- **Startseiten-Badge:** auf der Terminal-Startseite zeigt die Pinnwand-Karte einen
roten Zähler mit neuen fremden Beiträgen + Kommentaren seit dem letzten Öffnen
(`feed_seen_at`, wird beim Öffnen/Polling der Pinnwand aktualisiert).
- **Meldungen/Moderation:** Gründe Spam/Beleidigend/Unangemessen/Datenschutz/Falschinfo/Sonstiges,
1 Meldung je Terminal & Beitrag, optional Auto-Verbergen ab Schwellwert.
Moderator-Terminals (in den Einstellungen) können im Portal anpinnen/verbergen/löschen
und Meldungen abarbeiten; vollständige Moderation zusätzlich im CRM.
- **Verwaltung:** CRM → System → Terminals → Zahnrad an der Pinnwand-Ansicht:
Einstellungen (Beiträge an/aus, Limits, Formate, Reaktionen, Kommentare an/aus
+ Kommentar-Zeichenlimit, Rate-Limit, Auto-Verbergen-Schwelle, Moderatoren)
+ offene Meldungen. Speicherung in
`term_widget_meta` (`pin:settings`).
- **Schema:** Tabellen `term_posts`, `term_post_media`, `term_post_reactions`,
`term_post_reports`, `term_post_links`, `term_post_profiles`, `term_post_comments`
– werden beim ersten Aufruf **selbst angelegt** (Referenz in `sql/schema.sql`).
## Hinweis
Module: `monthly_list`, `worklog`, `websites`, `time_tracking`, `invoices`,
`media`, `slots`, `slf`, `pinnwand`, `vpn` (Registry: `includes/modules/registry.php`,
Spiegel in der CRM: `TerminalController::modules()`).
Worklog: Erfassungsformular mit optionalem Feld **„Deck"** (max. 40 Zeichen,
Zahl oder Freitext; `deck` im Eintrags-JSON) – schmales Eingabefeld neben der
Auftrags-Auswahl. Anzeige als ⚓-Tag in der Tagesliste; im PDF in der Spalte
„Auftrag" (`Auftragsname · Deck 3`) sowie in den Bildunterschriften der
Foto-Seiten (Zuordnung der Fotos zum Schiffsbereich). Eingaben wie „3" werden
zu „Deck 3" normalisiert. Alt-Einträge ohne `deck`-Schlüssel bleiben gültig.