diff --git a/wordpress-plugin/README.md b/wordpress-plugin/README.md new file mode 100644 index 00000000..05d7d032 --- /dev/null +++ b/wordpress-plugin/README.md @@ -0,0 +1,80 @@ +# DeskHub Tickets für WordPress + +Mit diesem Plugin legen Besucher Ihrer WordPress-Seite Support-Tickets direkt in **DeskHub** an und können den Stand ihres Tickets abfragen. Das Plugin spricht die öffentliche API von DeskHub an. Die Standardfunktionen (Formular, Statusabfrage) laufen mit jedem DeskHub. Für **eigene Felder und Texte eines Hilfethemas** braucht DeskHub mindestens **Version 1.0.64**. + +Das Formular liegt auf Ihrer WordPress-Seite und passt sich dem Theme an. DeskHub selbst lässt sich nicht in einen Rahmen (iframe) einbetten und das Plugin braucht das auch nicht. + +## Funktionen + +- **`[deskhub_ticket]`** zeigt das Formular. Das Plugin holt es aus DeskHub: Hast du bei einem **Hilfethema** oder in der **Abteilung** ein eigenes Formular eingerichtet (eigene Felder, Pflichtfelder, Auswahllisten, Texte, Reihenfolge, Breiten), erscheint es genauso auf deiner WordPress-Seite. Änderungen in DeskHub gelten nach spätestens 5 Minuten (oder sofort mit „Formulare neu laden“ in den Einstellungen). Nach dem Absenden sieht der Besucher seine Ticketnummer. +- **`[deskhub_status]`** zeigt die Statusabfrage: Ticketnummer und E-Mail-Adresse eingeben, dann erscheinen Status und öffentlicher Verlauf (interne Notizen bleiben unsichtbar). +- **Einstellungen** unter *Einstellungen → DeskHub Tickets*: Server-Adresse, API-Schlüssel, Abteilung, Hilfethema und Priorität, Einwilligungstext, Text nach dem Absenden, **Verbindung testen**. +- **Spamschutz** (funktioniert auch mit Seiten-Cache): unsichtbares Falle-Feld, Mindestzeit bis zum Absenden, Mengenbegrenzung je Besucher und Stunde, optional **Cloudflare Turnstile**. +- **Datenschutz:** optionales Einwilligungs-Kästchen. In der Datenbank stehen keine IP-Adressen (nur nicht umkehrbare Schlüssel für die Mengenbegrenzung). +- **Farben vom Theme:** Schrift, Rahmen und Feldhintergrund passen sich dem Theme an (auch dunkle Seiten). Die Akzentfarbe (Knopf) liest das Plugin aus Knöpfen und Links der Seite; in den Einstellungen lässt sie sich fest vorgeben. +- Deutsch und Englisch (die englische Oberfläche folgt der Sprache der WordPress-Seite). + +## Einrichten + +1. Ordner `deskhub-tickets` (aus dem ZIP) nach `wp-content/plugins/` kopieren oder das ZIP unter *Plugins → Installieren → Plugin hochladen* hochladen, dann aktivieren. +2. In **DeskHub** unter *Verwalten → API-Schlüssel* einen Schlüssel erstellen: Recht **„Tickets anlegen“**, für die Statusabfrage zusätzlich **„Tickets lesen“**. +3. In WordPress unter *Einstellungen → DeskHub Tickets* Server-Adresse (zum Beispiel `https://support.example.de`) und Schlüssel eintragen, speichern und **„Verbindung testen“** klicken. +4. Auf einer Seite den Shortcode `[deskhub_ticket]` einfügen (im Block-Editor mit dem Block „Shortcode“). Für die Statusabfrage eine zweite Seite mit `[deskhub_status]` anlegen und ihre Adresse in den Einstellungen eintragen, dann erscheint nach dem Absenden ein Link dorthin. + +### Mehr Möglichkeiten im Shortcode + +```text +[deskhub_ticket department="2" topic="5" priority="1"] +[deskhub_ticket topics="Rechnung:3,Technik:4" title="Support anfragen"] +``` + +- `department`, `topic`, `priority`: Nummern in DeskHub; überschreiben die Einstellungen für dieses Formular. Besucher können sie nicht ändern (sie reisen unterschrieben im Formular). +- `topics="Bezeichnung:Nummer,…"`: Besucher wählen ein Thema aus (Nummer des Hilfethemas in DeskHub). Je nach Thema erscheinen die passenden Felder und Texte (ohne Seitenneuladen). +- `topic="5"`: festes Hilfethema; sein Formular wird angezeigt. In den Einstellungen lässt sich das Standardthema aus einer Liste wählen. +- `title`: Überschrift über dem Formular. + +### Sicherer: Schlüssel in der wp-config.php + +```php +define('DESKHUB_TICKETS_API_KEY', 'dh_…'); +define('DESKHUB_TICKETS_SERVER', 'https://support.example.de'); // optional +``` + +Dann steht der Schlüssel nicht in der Datenbank und das Feld in den Einstellungen ist gesperrt. + +## Wenn die Verbindung nicht klappt + +Unter *Einstellungen → DeskHub Tickets* gibt es **„Speichern und Verbindung testen“**. Der Test prüft die Werte, die du gerade eingegeben hast, und sagt, woran es liegt: + +- **„Bitte Server-Adresse und API-Schlüssel eintragen“:** Das Schlüsselfeld war leer. Der Schlüssel wird nach dem Speichern nie wieder angezeigt (das Feld bleibt leer, daneben steht „gespeichert“). Zum Testen den Schlüssel neu eintragen oder in der `wp-config.php` festlegen. +- **„DeskHub lässt diese Adresse nicht zu“:** Der Schlüssel ist richtig, aber bei **„Erlaubte IPs“** des Schlüssels in DeskHub steht eine andere Adresse. Die Meldung nennt die Adresse, die DeskHub sieht: genau diese eintragen oder das Feld leer lassen. **Laufen WordPress und DeskHub auf demselben Server, ist `127.0.0.1` meist falsch:** Das Plugin ruft DeskHub über dessen Internet-Adresse auf, DeskHub sieht dann die öffentliche Adresse des Servers (hinter Cloudflare oder einem Proxy nur dann die richtige, wenn der Proxy in DeskHub unter den vertrauten Proxys eingetragen ist). +- **„DeskHub-Server ist nicht erreichbar“ mit Ursache in Klammern:** Zum Beispiel „Could not resolve host“ (Adresse falsch oder der Server findet seine eigene Domain nicht) oder „Connection timed out“ (manche Hoster erlauben dem Server nicht, sich über seine eigene öffentliche Adresse aufzurufen). +- **„Der Server hat den API-Schlüssel abgelehnt“:** Schlüssel falsch eingetragen, in DeskHub abgeschaltet, oder die erlaubten IPs passen nicht. + +## Sicherheit + +- Der API-Schlüssel wird **nur auf dem Server** gesendet, nie an den Browser. Die Einstellungsseite schreibt ihn nicht wieder in die Seite. +- Verbindung nur über **https** (http nur für `localhost`). Weiterleitungen werden nicht verfolgt. +- Alle Ausgaben werden maskiert. Eingaben werden bereinigt (HTML wird aus Nachrichten entfernt). +- Abteilung, Thema und Priorität sind im Formular **unterschrieben**: Ein Besucher kann nicht selbst eine andere Abteilung oder „dringend“ einsetzen. +- Die Statusabfrage braucht Ticketnummer **und** E-Mail-Adresse und ist auf 10 Abfragen je 10 Minuten begrenzt (bremst das Durchprobieren). +- Ein Schlüssel nur mit dem Recht „Tickets anlegen“ genügt für das Formular. Gibt es keine Statusseite, braucht er das Lesen-Recht nicht. + +## Grenzen + +- **Keine Dateianhänge:** Die öffentliche API von DeskHub nimmt nur Text. Der Anhang-Upload des Kundenformulars fehlt deshalb im WordPress-Formular. +- **Ältere DeskHub-Version:** Ohne die Formular-Schnittstelle (vor 1.0.64) zeigt das Plugin das einfache Formular ohne eigene Felder; Administratoren sehen einen Hinweis. +- **IP-Adresse im Ticket:** DeskHub sieht die IP-Adresse Ihres WordPress-Servers, nicht die des Besuchers. +- **Mengenbegrenzung der API:** DeskHub nimmt über die API höchstens 60 Tickets pro Minute an (für alle Aufrufer zusammen). +- Hinter einem Proxy zählt die Mengenbegrenzung die Adresse des Proxys. Der Filter `deskhub_tickets_client_ip` liefert die richtige Besucher-Adresse. + +## Für Entwickler + +- Filter `deskhub_tickets_form_cache_seconds` (Zwischenspeicher der Formulare, Standard 300), Filter `deskhub_tickets_payload` (Daten vor dem Senden anpassen, z. B. Bestelldaten ergänzen), Aktion `deskhub_tickets_created` (nach dem Anlegen: Ticketnummer und Daten), Filter `deskhub_tickets_allow_insecure` (http für interne Hosts erlauben). +- Tests: `php wordpress-plugin/tests/run.php` (mit Nachbau der WordPress-Funktionen). Mit `DESKHUB_URL` und `DESKHUB_KEY` läuft zusätzlich der ganze Weg gegen einen echten DeskHub-Testserver. +- Übersetzungen: `php wordpress-plugin/tools/extract-strings.php` listet die Texte, `php wordpress-plugin/tools/build-mo.php wordpress-plugin/tools/en.json` baut die `.po`/`.mo`-Dateien. +- ZIP packen: `php wordpress-plugin/tools/pack.php`. + +## Was nicht geprüft ist + +Das Plugin wurde mit einem Nachbau der WordPress-Funktionen und gegen einen echten DeskHub-Server getestet, **nicht in einer echten WordPress-Installation** (und nur mit PHP 8.2). Bitte vor dem Einsatz auf einer Testseite ausprobieren: Formular senden, Statusabfrage, Einstellungsseite, Seiten-Cache. diff --git a/wordpress-plugin/deskhub-tickets/assets/form.css b/wordpress-plugin/deskhub-tickets/assets/form.css new file mode 100644 index 00000000..65f98b47 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/assets/form.css @@ -0,0 +1,203 @@ +/* DeskHub Tickets: bewusst schlicht, damit sich das Formular dem Theme anpasst. Farben lassen sich über CSS-Variablen ändern. */ +.dht-wrap { + --dht-accent: var(--dht-theme-accent, var(--dht-server-accent, var(--wp--preset--color--primary, #0f6f6a))); + --dht-on-accent: var(--dht-theme-on-accent, #fff); + --dht-border: #d6dde5; + --dht-muted: #5b6b7b; + --dht-field-bg: #fff; + --dht-radius: 8px; + max-width: 640px; + margin: 1.5em 0; +} +.dht-wrap * { box-sizing: border-box; } +.dht-title { margin: 0 0 .6em; } +.dht-form p { margin: 0 0 1em; } +.dht-form label { display: block; font-weight: 600; margin-bottom: .25em; } +.dht-form input[type="text"], +.dht-form input[type="email"], +.dht-form select, +.dht-form textarea { + width: 100%; + padding: .6em .75em; + border: 1px solid var(--dht-border); + border-radius: var(--dht-radius); + font: inherit; + background: var(--dht-field-bg); + color: inherit; +} +.dht-form textarea { resize: vertical; min-height: 9em; } +.dht-form input:focus, +.dht-form select:focus, +.dht-form textarea:focus { outline: 2px solid var(--dht-accent); outline-offset: 1px; border-color: var(--dht-accent); } +.dht-consent label { font-weight: 400; display: flex; gap: .6em; align-items: flex-start; } +.dht-consent input { margin-top: .3em; flex: none; } +.dht-button { + appearance: none; + background: var(--dht-accent) !important; + color: var(--dht-on-accent) !important; + border: 0; + border-radius: var(--dht-radius); + padding: .7em 1.4em; + font: inherit; + font-weight: 600; + cursor: pointer; +} +.dht-button:hover { filter: brightness(1.08); } +.dht-button:focus-visible { outline: 2px solid var(--dht-accent); outline-offset: 2px; } + +/* Falle für Roboter: für Menschen unsichtbar, aber nicht mit display:none (das erkennen manche Roboter) */ +.dht-trap { position: absolute !important; left: -9999px !important; width: 1px; height: 1px; overflow: hidden; } + +.dht-notice { padding: .8em 1em; border-radius: var(--dht-radius); margin: 0 0 1.2em; border: 1px solid; } +.dht-notice p { margin: 0 0 .4em; } +.dht-notice p:last-child { margin-bottom: 0; } +.dht-notice ul { margin: 0; padding-left: 1.2em; } +.dht-success { background: #ecfdf3; border-color: #86efac; color: #14532d; } +.dht-error { background: #fef2f2; border-color: #fca5a5; color: #7f1d1d; } + +.dht-ticket { margin: 0 0 2em; } +.dht-meta { color: var(--dht-muted); margin: 0 0 1em; } +.dht-badge { display: inline-block; padding: .1em .65em; border-radius: 999px; font-size: .85em; font-weight: 600; background: #e2e8f0; color: #1e293b; } +.dht-status-open { background: #dbeafe; color: #1e3a8a; } +.dht-status-pending { background: #fef3c7; color: #78350f; } +.dht-status-answered, .dht-status-resolved { background: #dcfce7; color: #14532d; } +.dht-msg { border: 1px solid var(--dht-border); border-radius: var(--dht-radius); padding: .7em .9em; margin: 0 0 .7em; } +.dht-msg-staff { background: #f1f8f7; border-color: #bfdcd9; } +.dht-msg-head { color: var(--dht-muted); font-size: .88em; margin-bottom: .3em; } +.dht-msg-body { overflow-wrap: anywhere; } + +/* Eigene Felder aus DeskHub */ +.dht-intro { margin: 0 0 1em; color: var(--dht-muted); } +.dht-fields { display: flex; flex-wrap: wrap; gap: 0 1em; } +.dht-field { flex: 1 1 100%; min-width: 0; margin: 0 0 1em; } +.dht-field label { display: block; font-weight: 600; margin-bottom: .25em; } +.dht-field input[type="text"], +.dht-field input[type="number"], +.dht-field input[type="date"], +.dht-field select, +.dht-field textarea { + width: 100%; + padding: .6em .75em; + border: 1px solid var(--dht-border); + border-radius: var(--dht-radius); + font: inherit; + background: var(--dht-field-bg); + color: inherit; +} +.dht-field input:focus, .dht-field select:focus, .dht-field textarea:focus { outline: 2px solid var(--dht-accent); outline-offset: 1px; border-color: var(--dht-accent); } +.dht-field .dht-check { display: flex; gap: .6em; align-items: flex-start; font-weight: 400; margin: .25em 0; } +.dht-field .dht-check input { margin-top: .3em; flex: none; } +.dht-radios { border: 0; margin: 0; padding: 0; } +/* Themes machen "legend" und Überschriften oft groß und setzen Linien darunter: Hier bewusst auf Normalgröße zurückgesetzt */ +.dht-wrap .dht-radios > legend { + display: block !important; + float: none !important; + width: auto !important; + margin: 0 0 .25em !important; + padding: 0 !important; + border: 0 !important; + font: inherit !important; + font-size: 1em !important; + font-weight: 600 !important; + line-height: inherit !important; + letter-spacing: normal !important; + text-transform: none !important; +} +.dht-form label > span { font-weight: inherit; font-size: inherit; } +.dht-wrap .dht-group { font-size: 1.1em !important; line-height: 1.3 !important; font-weight: 600 !important; letter-spacing: normal !important; text-transform: none !important; } +.dht-radios.dht-inline { display: flex; flex-wrap: wrap; gap: 0 1.4em; } +.dht-radios.dht-inline legend { flex: 1 1 100%; } +.dht-help { margin: .3em 0 0; font-size: .88em; color: var(--dht-muted); } +.dht-req { color: #b91c1c; } +.dht-group { margin: 1.2em 0 .6em; } +@media (min-width: 600px) { + .dht-w-half { flex: 1 1 calc(50% - .5em); } + .dht-w-third { flex: 1 1 calc(33.333% - .67em); } + .dht-w-twothirds { flex: 1 1 calc(66.666% - .33em); } +} +/* Bei einer Themenauswahl zeigt form.js nur die Felder des gewählten Themas (ohne JavaScript bleibt alles sichtbar) */ +.dht-js .dht-topic-fields { display: none; } +.dht-js .dht-topic-fields.dht-topic-active { display: block; } +.dht-form [hidden] { display: none !important; } + +/* Einheitliche Felder: Themes geben Beschriftungen, Textfeldern und Auswahllisten oft verschiedene Schriften und Höhen. + Hier bekommen alle Beschriftungen und alle einzeiligen Felder dieselbe Schrift, Größe und Höhe. */ +.dht-wrap .dht-fields { align-items: flex-start; } +.dht-wrap .dht-form label:not(.dht-check), +.dht-wrap .dht-radios > legend { + display: block !important; + font-family: inherit !important; + font-size: 1em !important; + font-weight: 600 !important; + line-height: 1.4 !important; + letter-spacing: normal !important; + text-transform: none !important; + margin: 0 0 .35em !important; +} +.dht-wrap .dht-form input[type="text"], +.dht-wrap .dht-form input[type="email"], +.dht-wrap .dht-form input[type="number"], +.dht-wrap .dht-form input[type="date"], +.dht-wrap .dht-form select { + box-sizing: border-box !important; + width: 100% !important; + height: 2.9em !important; + min-height: 0 !important; + margin: 0 !important; + padding: 0 .75em !important; + font-family: inherit !important; + font-size: 1em !important; + line-height: 1.4 !important; +} +.dht-wrap .dht-form select { padding-right: 2em !important; } +.dht-wrap .dht-form textarea { + box-sizing: border-box !important; + width: 100% !important; + margin: 0 !important; + padding: .6em .75em !important; + font-family: inherit !important; + font-size: 1em !important; + line-height: 1.5 !important; +} +.dht-wrap .dht-form .dht-field { margin-bottom: 1em; } + +/* Der rote Stern und andere Textteile in Beschriftungen dürfen die Zeile nicht höher machen (Themes setzen für span oft eigene Schrift und Zeilenhöhe): + Sonst stehen Felder mit Stern tiefer als Felder ohne Stern. */ +.dht-wrap .dht-form label > span, +.dht-wrap .dht-radios > legend > span, +.dht-wrap .dht-req { + font: inherit !important; + line-height: 0 !important; + vertical-align: baseline !important; + letter-spacing: normal !important; + text-transform: none !important; +} +.dht-wrap .dht-form .dht-check > span { line-height: 1.4 !important; } +.dht-wrap .dht-form label:not(.dht-check), +.dht-wrap .dht-radios > legend { min-height: 1.4em; box-sizing: content-box !important; } +.dht-wrap .dht-fields > .dht-field { align-self: flex-start; } + +/* Nebeneinander stehende Ja/Nein-Optionen: so hoch wie ein Textfeld und mittig, damit sie neben Textfeldern und Auswahllisten nicht herausfallen */ +.dht-wrap .dht-radios.dht-inline .dht-check { min-height: 2.9em; align-items: center; margin: 0 !important; } + +/* Farben vom Theme: Rahmen, Hinweistext und Feldhintergrund werden aus der Schriftfarbe der Seite gemischt, + so passen sie sich hellen und dunklen Themes an. Ältere Browser behalten die festen Farben oben. */ +@supports (color: color-mix(in srgb, red 50%, blue)) { + .dht-success { background: color-mix(in srgb, #16a34a 16%, transparent); border-color: color-mix(in srgb, #16a34a 55%, transparent); color: inherit; } + .dht-error { background: color-mix(in srgb, #dc2626 14%, transparent); border-color: color-mix(in srgb, #dc2626 55%, transparent); color: inherit; } + .dht-badge { background: color-mix(in srgb, currentColor 14%, transparent); color: inherit; } + .dht-status-open { background: color-mix(in srgb, #2563eb 20%, transparent); } + .dht-status-pending { background: color-mix(in srgb, #d97706 22%, transparent); } + .dht-status-answered, .dht-status-resolved { background: color-mix(in srgb, #16a34a 20%, transparent); } + .dht-msg-staff { background: color-mix(in srgb, var(--dht-accent) 10%, transparent); border-color: color-mix(in srgb, var(--dht-accent) 40%, transparent); } +} +/* Felder: Aussehen kommt nur aus Hintergrund und Schriftfarbe der Seite (theme.js), nicht aus Akzent- oder Theme-Farben der Eingabefelder */ +.dht-wrap .dht-form input[type="text"], .dht-wrap .dht-form input[type="email"], .dht-wrap .dht-form input[type="number"], +.dht-wrap .dht-form input[type="date"], .dht-wrap .dht-form select, .dht-wrap .dht-form textarea { + background: var(--dht-field-bg) !important; + border-color: var(--dht-border) !important; + color: var(--dht-fg, inherit) !important; +} +.dht-wrap .dht-form input:focus, .dht-wrap .dht-form select:focus, .dht-wrap .dht-form textarea:focus { border-color: var(--dht-accent) !important; } +/* Auswahllisten öffnen sich mit Systemfarben: an den Hintergrund der Seite angleichen (Werte setzt theme.js) */ +.dht-wrap option { background: var(--dht-bg, Canvas); color: var(--dht-fg, CanvasText); } diff --git a/wordpress-plugin/deskhub-tickets/assets/form.js b/wordpress-plugin/deskhub-tickets/assets/form.js new file mode 100644 index 00000000..aed5a481 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/assets/form.js @@ -0,0 +1,62 @@ +/* DeskHub Tickets: schaltet bei einer Themenauswahl die passenden Felder und Texte des Kundenformulars ein. + * Ohne JavaScript bleibt alles sichtbar; der Server prüft beim Absenden ohnehin nur die Felder des gewählten Themas. */ +(function () { + 'use strict'; + + function setText(form, selector, text) { + form.querySelectorAll(selector).forEach(function (el) { el.textContent = text; }); + } + + function init(form) { + var forms; + try { forms = JSON.parse(form.getAttribute('data-dht-forms') || '{}'); } catch (e) { return; } + var select = form.querySelector('select[name="dht_topic"]'); + form.classList.add('dht-js'); + + function apply() { + var id = select ? select.value : Object.keys(forms)[0]; + var f = forms[id]; + + // Felder: nur die des gewählten Themas sind sichtbar und werden abgeschickt + form.querySelectorAll('.dht-topic-fields').forEach(function (box) { + var on = box.getAttribute('data-topic') === String(id); + box.classList.toggle('dht-topic-active', on); + box.querySelectorAll('input, select, textarea').forEach(function (el) { el.disabled = !on; }); + }); + if (!f) { return; } + + // Texte des gewählten Formulars + var title = form.parentNode.querySelector('.dht-form-title'); + if (title && !title.hasAttribute('data-fixed')) { title.textContent = f.title; title.hidden = !f.title; } + var intro = form.parentNode.querySelector('.dht-form-intro'); + if (intro) { intro.textContent = f.intro; intro.hidden = !f.intro; } + setText(form, '.dht-subject-label', f.subjectLabel); + setText(form, '.dht-message-label', f.messageLabel); + var subject = form.querySelector('.dht-subject-wrap'); + if (subject) { + subject.hidden = !f.subjectShow; + var si = subject.querySelector('input'); if (si) { si.disabled = !f.subjectShow; si.required = !!f.subjectShow; } + } + var message = form.querySelector('.dht-message-wrap'); + if (message) { + message.hidden = !f.messageShow; + var mi = message.querySelector('textarea'); + if (mi) { mi.disabled = !f.messageShow; mi.required = !!f.messageShow; mi.placeholder = f.messageHint || ''; } + } + var submit = form.querySelector('.dht-submit'); + if (submit && f.submitLabel) { submit.textContent = f.submitLabel; } + } + + if (select) { select.addEventListener('change', apply); } + apply(); + + // Doppeltes Absenden verhindern + form.addEventListener('submit', function () { + var btn = form.querySelector('.dht-submit'); + if (btn) { btn.disabled = true; } + }); + } + + function ready() { document.querySelectorAll('form.dht-form[data-dht-forms]').forEach(init); } + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', ready); } else { ready(); } +})(); diff --git a/wordpress-plugin/deskhub-tickets/assets/theme.js b/wordpress-plugin/deskhub-tickets/assets/theme.js new file mode 100644 index 00000000..b8e55ca5 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/assets/theme.js @@ -0,0 +1,123 @@ +/* DeskHub Tickets: übernimmt Farben vom Theme. + * Schrift, Hintergrund und Rahmen kommen schon per CSS (currentColor). Hier wird nur die Akzentfarbe gelesen, + * und zwar aus Knöpfen oder Links der Seite. Eine feste Farbe aus den Einstellungen (data-dht-accent) hat Vorrang. */ +(function () { + 'use strict'; + + function parse(c) { + var m = /rgba?\(\s*([\d.]+)[ ,]+([\d.]+)[ ,]+([\d.]+)(?:[ ,/]+([\d.%]+))?\s*\)/.exec(c || ''); + if (!m) { return null; } + var a = m[4] === undefined ? 1 : (m[4].indexOf('%') > -1 ? parseFloat(m[4]) / 100 : parseFloat(m[4])); + return { r: +m[1], g: +m[2], b: +m[3], a: a }; + } + + function lum(c) { + var f = function (v) { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }; + return 0.2126 * f(c.r) + 0.7152 * f(c.g) + 0.0722 * f(c.b); + } + + function contrast(a, b) { + var l1 = lum(a), l2 = lum(b); + return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05); + } + + function css(c) { return 'rgb(' + Math.round(c.r) + ',' + Math.round(c.g) + ',' + Math.round(c.b) + ')'; } + + // Hintergrund, der hinter dem Formular wirklich zu sehen ist + function pageBackground(el) { + for (var n = el; n && n.nodeType === 1; n = n.parentNode) { + var c = parse(getComputedStyle(n).backgroundColor); + if (c && c.a > 0.5) { return c; } + } + return { r: 255, g: 255, b: 255, a: 1 }; + } + + function saturation(c) { + var mx = Math.max(c.r, c.g, c.b), mn = Math.min(c.r, c.g, c.b); + return mx === 0 ? 0 : (mx - mn) / mx; + } + + // Farbwert (auch #hex oder Name) in rgb umrechnen + function resolve(wrap, value) { + if (!value) { return null; } + var span = document.createElement('span'); + span.style.color = value; + if (!span.style.color) { return null; } + wrap.appendChild(span); + var c = parse(getComputedStyle(span).color); + wrap.removeChild(span); + return c; + } + + // Akzent der Seite: häufigste kräftige Farbe von Knöpfen (Füllung oder Rand), sonst Linkfarbe. + // Die Farbe dient als Füllung, die Schrift darauf wird extra gewählt: ein geringer Kontrast zum Hintergrund (z. B. Orange auf Weiß) ist also in Ordnung. + function themeAccent(wrap, bg) { + var counts = {}, best = null, bestN = 0; + function add(c, weight) { + if (!c || c.a < 0.9 || saturation(c) < 0.25 || contrast(c, bg) < 1.3) { return; } + var k = css(c); + counts[k] = (counts[k] || 0) + weight; + if (counts[k] > bestN) { bestN = counts[k]; best = c; } + } + var sel = '.wp-element-button, .wp-block-button__link, .button, .btn, [class*="btn"], [class*="button"], [role="button"], button, input[type="submit"], a'; + var nodes = document.querySelectorAll(sel); + for (var i = 0; i < nodes.length && i < 400; i++) { + if (wrap.contains(nodes[i])) { continue; } + var st = getComputedStyle(nodes[i]); + add(parse(st.backgroundColor), /wp-element-button|wp-block-button__link|(^|\s)(button|btn)(\s|$)/.test(nodes[i].className || '') ? 4 : 2); + if (parseFloat(st.borderTopWidth) > 0) { add(parse(st.borderTopColor), 1); } + } + if (best) { return best; } + // Farbvariablen bekannter Themes und Seitenbauer (Block-Themes, Elementor, Astra, Kadence …) + var names = ['--wp--preset--color--primary', '--wp--preset--color--accent', '--e-global-color-accent', '--e-global-color-primary', + '--ast-global-color-0', '--global-palette1', '--global-palette-btn-bg', '--primary-color', '--accent-color', '--theme-color-primary', + '--color-primary', '--color-accent', '--primary', '--accent', '--brand-color']; + var rootStyle = getComputedStyle(document.documentElement); + for (var k = 0; k < names.length; k++) { + var v = rootStyle.getPropertyValue(names[k]).trim(); + if (!v) { continue; } + var vc = resolve(wrap, v); + if (vc && vc.a > 0.9 && saturation(vc) >= 0.25 && contrast(vc, bg) >= 1.3) { return vc; } + } + var server = resolve(wrap, getComputedStyle(wrap).getPropertyValue('--dht-server-accent').trim()); + if (server) { return server; } + var probe = document.createElement('a'); + probe.href = '#'; + probe.style.cssText = 'position:absolute;visibility:hidden'; + (wrap.parentNode || document.body).appendChild(probe); + var lc = parse(getComputedStyle(probe).color); + probe.parentNode.removeChild(probe); + return lc && saturation(lc) >= 0.25 ? lc : null; + } + + function apply(wrap) { + var bg = pageBackground(wrap); + var fg = parse(getComputedStyle(wrap).color) || { r: 0, g: 0, b: 0, a: 1 }; + var dark = lum(bg) < 0.4; + // Felder und Rahmen: feste Mischfarben aus Seitenhintergrund und Schriftfarbe (unabhängig von der Akzentfarbe) + var mix = function (t) { return css({ r: bg.r + (fg.r - bg.r) * t, g: bg.g + (fg.g - bg.g) * t, b: bg.b + (fg.b - bg.b) * t }); }; + wrap.style.setProperty('--dht-field-bg', mix(0.07)); + wrap.style.setProperty('--dht-border', mix(0.3)); + wrap.style.setProperty('--dht-muted', mix(0.7)); + wrap.style.setProperty('--dht-bg', css(bg)); + wrap.style.setProperty('--dht-fg', css(fg)); + wrap.style.setProperty('color-scheme', dark ? 'dark' : 'light'); + + var accent; + if (wrap.getAttribute('data-dht-accent') === 'fixed') { + accent = resolve(wrap, getComputedStyle(wrap).getPropertyValue('--dht-theme-accent').trim()); + } else { + accent = themeAccent(wrap, bg); + if (accent) { wrap.style.setProperty('--dht-theme-accent', css(accent)); } + } + if (!accent) { return; } + // Schrift auf dem Knopf: schwarz oder weiß, je nachdem, was besser lesbar ist + var white = { r: 255, g: 255, b: 255 }, black = { r: 17, g: 17, b: 17 }; + wrap.style.setProperty('--dht-theme-on-accent', contrast(accent, white) >= contrast(accent, black) ? '#fff' : '#111'); + } + + function ready() { document.querySelectorAll('.dht-wrap').forEach(apply); } + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', ready); } else { ready(); } + // Nochmal nach dem Laden: Theme-Stile und Schriften können später kommen + window.addEventListener('load', ready); +})(); diff --git a/wordpress-plugin/deskhub-tickets/deskhub-tickets.php b/wordpress-plugin/deskhub-tickets/deskhub-tickets.php new file mode 100644 index 00000000..f409cecc --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/deskhub-tickets.php @@ -0,0 +1,34 @@ +base = rtrim($baseUrl, '/'); + $this->key = $apiKey; + } + + /** Client aus den Einstellungen; null, solange Adresse oder Schlüssel fehlen. */ + public static function from_settings(): ?self + { + $opts = deskhub_tickets_options(); + $url = self::normalize_url((string) $opts['server_url']); + if (is_wp_error($url) || trim((string) $opts['api_key']) === '') { + return null; + } + return new self($url, trim((string) $opts['api_key'])); + } + + /** + * Bereinigt die Server-Adresse: https ist Pflicht (http nur für lokale Tests), kein Benutzername, keine Parameter. + * + * @return string|WP_Error + */ + public static function normalize_url(string $input) + { + $s = trim($input); + if ($s === '') { + return new WP_Error('deskhub_url', __('Bitte die Adresse Ihres DeskHub-Servers eintragen.', 'deskhub-tickets')); + } + if (strpos($s, '://') === false) { + $s = 'https://' . $s; + } + $p = wp_parse_url($s); + if (!$p || empty($p['host']) || !empty($p['user']) || !empty($p['pass']) || !empty($p['query']) || !empty($p['fragment'])) { + return new WP_Error('deskhub_url', __('Die Server-Adresse ist ungültig.', 'deskhub-tickets')); + } + $scheme = strtolower((string) ($p['scheme'] ?? '')); + $host = strtolower((string) $p['host']); + $local = in_array($host, ['localhost', '127.0.0.1', '[::1]', '::1'], true); + $insecureOk = $local || (bool) apply_filters('deskhub_tickets_allow_insecure', false, $host); + if ($scheme !== 'https' && !($scheme === 'http' && $insecureOk)) { + return new WP_Error('deskhub_url', __('Die Server-Adresse muss mit https beginnen.', 'deskhub-tickets')); + } + $port = isset($p['port']) ? ':' . (int) $p['port'] : ''; + $path = isset($p['path']) ? rtrim((string) $p['path'], '/') : ''; + return $scheme . '://' . $host . $port . $path; + } + + /** + * Legt ein Ticket an. + * + * @param array{name:string,email:string,subject:string,message:string,helpTopicId?:int,departmentId?:int,priorityId?:int,fields?:array} $data + * @return array{number:string,status:string}|WP_Error + */ + public function create_ticket(array $data) + { + $fields = $data['fields'] ?? null; + unset($data['fields']); + $payload = array_filter($data, static function ($v) { + return $v !== null && $v !== '' && $v !== 0; + }); + if (is_array($fields)) { + $payload['fields'] = (object) $fields; // auch leer senden: Damit prüft DeskHub die Angaben gegen das Kundenformular + } + $res = $this->request('POST', [], $payload); + if (is_wp_error($res)) { + return $res; + } + if (empty($res['number']) || !is_string($res['number'])) { + return new WP_Error('deskhub_response', __('Der Server hat eine unerwartete Antwort geliefert.', 'deskhub-tickets')); + } + return ['number' => $res['number'], 'status' => (string) ($res['status'] ?? 'open')]; + } + + /** + * Das Kundenformular aus DeskHub für ein Hilfethema oder eine Abteilung: eigene Felder, Texte, Reihenfolge, Pflichtfelder. + * Das Ergebnis wird kurz zwischengespeichert, damit nicht jeder Seitenaufruf den Server fragt. + * Kennt der Server die Schnittstelle noch nicht (ältere DeskHub-Version), kommt ein WP_Error "deskhub_form_unsupported": + * Dann zeigt das Plugin das einfache Formular. + * + * @return array|WP_Error + */ + public function get_form(?int $topicId, ?int $deptId) + { + $key = 'dht_form_' . md5($this->base . '|' . (int) $topicId . '|' . (int) $deptId); + $cached = get_transient($key); + if (is_array($cached)) { + return !empty($cached['unsupported']) + ? new WP_Error('deskhub_form_unsupported', __('Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht.', 'deskhub-tickets')) + : $cached['form']; + } + $query = ['form' => '1']; + if ($topicId) { + $query['helpTopicId'] = (string) $topicId; + } + if ($deptId) { + $query['departmentId'] = (string) $deptId; + } + $ttl = (int) apply_filters('deskhub_tickets_form_cache_seconds', 300); + $res = $this->request('GET', $query); + $data = is_wp_error($res) ? $res->get_error_data() : null; + $apiCode = is_array($data) ? (string) ($data['api_code'] ?? '') : ''; + // Ältere Server lesen "form=1" als Ticketabfrage und antworten "Parameter fehlen"; unbekannte Antworten zählen ebenso als "nicht unterstützt" + if ((is_wp_error($res) && $apiCode === 'MISSING_PARAMS') || (!is_wp_error($res) && empty($res['form']))) { + set_transient($key, ['unsupported' => true], max(30, $ttl)); + return new WP_Error('deskhub_form_unsupported', __('Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht.', 'deskhub-tickets')); + } + if (is_wp_error($res)) { + return $res; + } + if ($ttl > 0) { + set_transient($key, ['form' => $res['form']], $ttl); + } + return $res['form']; + } + + /** + * Die öffentlichen Hilfethemen des Servers (für die Auswahl in den Einstellungen). + * + * @return array|WP_Error + */ + public function get_topics() + { + $res = $this->request('GET', ['topics' => '1']); + if (is_wp_error($res)) { + return $res; + } + return isset($res['topics']) && is_array($res['topics']) ? $res['topics'] : new WP_Error('deskhub_form_unsupported', __('Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht.', 'deskhub-tickets')); + } + + /** Leert den Zwischenspeicher der Formulare (zum Beispiel nach einer Änderung in DeskHub). */ + public static function clear_form_cache(): void + { + global $wpdb; + if (isset($wpdb) && is_object($wpdb)) { + $wpdb->query($wpdb->prepare( + "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", + $wpdb->esc_like('_transient_dht_form_') . '%', + $wpdb->esc_like('_transient_timeout_dht_form_') . '%' + )); + } + if (function_exists('wp_cache_flush_group')) { + @wp_cache_flush_group('transient'); + } + } + + /** + * Status und öffentlicher Verlauf eines Tickets (nur mit passender E-Mail-Adresse). + * + * @return array|WP_Error das Feld "ticket" der Antwort + */ + public function get_ticket(string $number, string $email) + { + $res = $this->request('GET', ['number' => $number, 'email' => $email]); + if (is_wp_error($res)) { + return $res; + } + if (empty($res['ticket']) || !is_array($res['ticket'])) { + return new WP_Error('deskhub_response', __('Der Server hat eine unerwartete Antwort geliefert.', 'deskhub-tickets')); + } + return $res['ticket']; + } + + /** + * Prüft Adresse und Schlüssel, ohne ein Ticket anzulegen. Ein Aufruf mit erfundenen Daten genügt: Antwortet der Server mit + * "nicht gefunden" (404) oder "keine Leseberechtigung" (403), ist der Schlüssel gültig. 401 heißt: Schlüssel falsch. + * + * @return array{ok:bool,message:string,canRead:bool} + */ + public function test_connection(): array + { + $res = $this->request('GET', ['number' => 'VERBINDUNGSTEST', 'email' => 'test@example.invalid']); + if (!is_wp_error($res)) { + return ['ok' => true, 'message' => __('Verbindung steht.', 'deskhub-tickets'), 'canRead' => true]; + } + $data = $res->get_error_data(); + $status = is_array($data) ? (int) ($data['status'] ?? 0) : 0; + if ($status === 404) { + return ['ok' => true, 'message' => __('Verbindung steht, der Schlüssel ist gültig und darf Tickets lesen.', 'deskhub-tickets'), 'canRead' => true]; + } + if ($status === 403) { + return ['ok' => true, 'message' => __('Verbindung steht, der Schlüssel ist gültig. Er darf aber keine Tickets lesen: Die Statusabfrage funktioniert damit nicht (in DeskHub unter Verwalten → API-Schlüssel das Recht „Tickets lesen“ geben).', 'deskhub-tickets'), 'canRead' => false]; + } + $apiCode = is_array($data) ? (string) ($data['api_code'] ?? '') : ''; + if ($status === 401 && $apiCode === 'IP_NOT_ALLOWED') { + // DeskHub nennt die Adresse, die es sieht (bei Cloudflare oder Proxy oft nicht die erwartete) + return ['ok' => false, 'message' => sprintf(__('Der Schlüssel ist gültig, aber DeskHub lässt diese Adresse nicht zu. DeskHub meldet: %s', 'deskhub-tickets'), (string) ($data['api_message'] ?? '')), 'canRead' => false]; + } + if ($status === 401) { + return ['ok' => false, 'message' => __('Der Server hat den API-Schlüssel abgelehnt. Bitte den Schlüssel prüfen: Er muss genau so eingetragen sein, aktiv sein und darf bei „Erlaubte IPs“ keine andere Adresse verlangen als die dieses WordPress-Servers (im Zweifel das Feld leer lassen).', 'deskhub-tickets'), 'canRead' => false]; + } + if ($status === 0) { + $detail = is_array($data) && !empty($data['transport']) ? ' (' . (string) $data['transport'] . ')' : ''; + return ['ok' => false, 'message' => $res->get_error_message() . $detail, 'canRead' => false]; + } + return ['ok' => false, 'message' => sprintf(__('Der Server antwortet mit einem Fehler (%d).', 'deskhub-tickets'), $status), 'canRead' => false]; + } + + /** + * Freundliche Meldung für Besucher. Technische Einzelheiten (auch der Schlüssel) gelangen nie in die Seite. + */ + public static function user_message(WP_Error $e): string + { + $data = $e->get_error_data(); + $api = is_array($data) ? (string) ($data['api_code'] ?? '') : ''; + switch ($api) { + case 'INVALID_NAME': + return __('Bitte geben Sie Ihren Namen an (höchstens 120 Zeichen).', 'deskhub-tickets'); + case 'INVALID_EMAIL': + return __('Bitte geben Sie eine gültige E-Mail-Adresse an.', 'deskhub-tickets'); + case 'INVALID_SUBJECT': + return __('Bitte geben Sie einen Betreff an (höchstens 255 Zeichen).', 'deskhub-tickets'); + case 'INVALID_MESSAGE': + return __('Bitte beschreiben Sie Ihr Anliegen.', 'deskhub-tickets'); + case 'BANNED': + case 'REJECTED': + return __('Ihre Anfrage konnte leider nicht angenommen werden.', 'deskhub-tickets'); + case 'RATE_LIMITED': + return __('Gerade sind sehr viele Anfragen eingegangen. Bitte versuchen Sie es in einer Minute noch einmal.', 'deskhub-tickets'); + case 'INVALID_FIELD': + // Diese Meldungen schreibt DeskHub für Besucher („Bitte füllen Sie das Feld … aus.“) + return is_array($data) && !empty($data['api_message']) ? (string) $data['api_message'] : __('Bitte prüfen Sie Ihre Angaben.', 'deskhub-tickets'); + case 'NOT_FOUND': + return __('Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden.', 'deskhub-tickets'); + } + return __('Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal.', 'deskhub-tickets'); + } + + /** + * Ein Aufruf der API. + * + * @param array $query + * @param array|null $body + * @return array|WP_Error + */ + private function request(string $method, array $query = [], ?array $body = null) + { + $url = $this->base . '/api/public/v1/tickets.php'; + if ($query) { + $url = add_query_arg(array_map('rawurlencode', $query), $url); + } + $args = [ + 'method' => $method, + 'timeout' => 15, + 'redirection' => 0, + 'headers' => [ + 'X-API-Key' => $this->key, + 'Accept' => 'application/json', + 'User-Agent' => 'DeskHub-Tickets-WordPress/' . DESKHUB_TICKETS_VERSION, + ], + ]; + if ($body !== null) { + $args['headers']['Content-Type'] = 'application/json'; + $args['body'] = wp_json_encode($body); + } + $response = wp_remote_request($url, $args); + if (is_wp_error($response)) { + error_log('DeskHub Tickets: Server nicht erreichbar: ' . $response->get_error_message()); + return new WP_Error('deskhub_unreachable', __('Der DeskHub-Server ist nicht erreichbar.', 'deskhub-tickets'), ['status' => 0, 'transport' => mb_substr(preg_replace('/\s+/', ' ', $response->get_error_message()), 0, 200)]); + } + $status = (int) wp_remote_retrieve_response_code($response); + $decoded = json_decode((string) wp_remote_retrieve_body($response), true); + if ($status >= 200 && $status < 300 && is_array($decoded)) { + return $decoded; + } + $apiCode = is_array($decoded) ? (string) ($decoded['error']['code'] ?? '') : ''; + $apiMessage = is_array($decoded) ? (string) ($decoded['error']['message'] ?? '') : ''; + if ($status >= 500 || $status === 0 || !is_array($decoded)) { + error_log('DeskHub Tickets: unerwartete Antwort (HTTP ' . $status . ')'); + } elseif ($status === 401 || $status === 403) { + error_log('DeskHub Tickets: DeskHub antwortet ' . $status . ' ' . $apiCode . ': ' . $apiMessage); // enthält nie den Schlüssel + } + return new WP_Error( + 'deskhub_api_' . ($apiCode !== '' ? strtolower($apiCode) : 'error'), + $apiMessage !== '' ? $apiMessage : __('Der Server hat die Anfrage abgelehnt.', 'deskhub-tickets'), + ['status' => $status, 'api_code' => $apiCode, 'api_message' => $apiMessage] + ); + } +} diff --git a/wordpress-plugin/deskhub-tickets/includes/class-form.php b/wordpress-plugin/deskhub-tickets/includes/class-form.php new file mode 100644 index 00000000..a7fdf902 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/includes/class-form.php @@ -0,0 +1,782 @@ +'; + } + + // ───────────────────────────── Ticket anlegen ───────────────────────────── + + /** Plätze im Formular, die DeskHub für eigene Felder kennt (alles andere kommt als „Zusätzliche Angaben“ ans Ende). */ + const SLOTS = ['contact_top', 'contact', 'topic_top', 'topic', 'issue_top', 'issue_mid', 'issue']; + + /** + * @param array|string $atts + */ + public static function render_ticket_form($atts = []): string + { + $atts = shortcode_atts([ + 'department' => '', + 'topic' => '', + 'priority' => '', + 'topics' => '', + 'title' => '', + ], is_array($atts) ? $atts : [], 'deskhub_ticket'); + self::enqueue(); + + $client = Deskhub_Tickets_Client::from_settings(); + if (!$client) { + return current_user_can('manage_options') + ? '

' . esc_html__('DeskHub Tickets ist noch nicht eingerichtet: Bitte unter Einstellungen → DeskHub Tickets Server-Adresse und API-Schlüssel eintragen. (Diese Meldung sehen nur Administratoren.)', 'deskhub-tickets') . '

' + : ''; + } + $opts = deskhub_tickets_options(); + $topics = self::parse_topics((string) $atts['topics']); + $cfg = self::form_config($atts, $opts, $topics); + $cfgB64 = base64_encode((string) wp_json_encode($cfg)); + + $state = self::take_state(); + $values = ['name' => '', 'email' => '', 'subject' => '', 'message' => '', 'topic' => 0, 'fields' => []]; + if (is_user_logged_in()) { + $u = wp_get_current_user(); + $values['name'] = (string) $u->display_name; + $values['email'] = (string) $u->user_email; + } + if ($state && !empty($state['values']) && is_array($state['values'])) { + $values = array_merge($values, array_intersect_key($state['values'], $values)); + } + + ob_start(); + echo self::wrap_open('deskhub-ticket'); + if ($state && !empty($state['ok'])) { + $text = trim((string) $opts['success_text']) !== '' ? (string) $opts['success_text'] : __('Vielen Dank! Wir melden uns so schnell wie möglich per E-Mail.', 'deskhub-tickets'); + echo '

' . esc_html($text) . '

'; + if (!empty($state['number'])) { + echo '

' . sprintf(esc_html__('Ihre Ticketnummer: %s', 'deskhub-tickets'), '' . esc_html((string) $state['number']) . '') . '

'; + } + if ($opts['status_page_url'] !== '') { + echo '

' . esc_html__('Stand abfragen', 'deskhub-tickets') . '

'; + } + echo '
'; + return (string) ob_get_clean(); + } + + // Kundenformulare aus DeskHub: eines je Thema der Auswahl, sonst eines für das Thema/die Abteilung. Ohne Antwort: einfaches Formular. + $forms = []; + $unsupported = false; + $topicIds = $topics ? array_map('intval', array_column($topics, 'id')) : [(int) $cfg['t']]; + foreach ($topicIds as $tid) { + $f = $client->get_form($tid ?: null, (int) $cfg['d'] ?: null); + if (is_wp_error($f)) { + $forms = []; + $unsupported = $f->get_error_code() === 'deskhub_form_unsupported'; + break; + } + $forms[$tid] = self::normalize_form($f); + } + if (!$forms) { + $forms = [$topicIds[0] ?? 0 => self::normalize_form(null)]; + } + $multi = count($topics) > 0; + $selected = $multi ? (int) $values['topic'] : (int) array_key_first($forms); + $active = $forms[$selected] ?? reset($forms); + $texts = []; + foreach ($forms as $tid => $fm) { + $texts[$tid] = ['title' => $fm['title'], 'intro' => $fm['intro'], 'subjectShow' => $fm['subject']['show'], 'subjectLabel' => $fm['subject']['label'], + 'messageShow' => $fm['message']['show'], 'messageLabel' => $fm['message']['label'], 'messageHint' => $fm['message']['hint'], 'submitLabel' => $fm['submitLabel']]; + } + if ($unsupported && current_user_can('manage_options')) { + echo '

' . esc_html__('Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht (bitte DeskHub aktualisieren). Bis dahin wird das einfache Formular ohne eigene Felder gezeigt. (Diese Meldung sehen nur Administratoren.)', 'deskhub-tickets') . '

'; + } + $title = $atts['title'] !== '' ? (string) $atts['title'] : (string) $active['title']; + echo '

' . esc_html($title) . '

'; + echo '

' . esc_html((string) $active['intro']) . '

'; + if ($state && !empty($state['errors'])) { + echo ''; + } + $showSubject = (bool) $active['subject']['show']; + $showMessage = (bool) $active['message']['show']; + ?> +
+ + + + + + + +

+ + +

+

+ + +

+ + + +

+ + +

+ + + +

> + + > +

+ +

> + + +

+ + + + + + +

+
+ '; + return (string) ob_get_clean(); + } + + /** Abteilung, Thema, Priorität und erlaubte Themen dieses Formulars (reisen unterschrieben im Formular). */ + private static function form_config(array $atts, array $opts, array $topics): array + { + $hasTopic = $topics || $atts['topic'] !== '' || (int) $opts['topic_id'] > 0; + // Mit Thema bestimmt DeskHub die Abteilung (Thema und Abteilungsformular gehören zusammen), außer der Shortcode nennt eine ausdrücklich + $dept = $atts['department'] !== '' ? absint($atts['department']) : ($hasTopic ? 0 : (int) $opts['department_id']); + return [ + 'd' => $dept, + 't' => $atts['topic'] !== '' ? absint($atts['topic']) : (int) $opts['topic_id'], + 'p' => $atts['priority'] !== '' ? absint($atts['priority']) : (int) $opts['priority_id'], + 'o' => array_values(array_map('intval', array_column($topics, 'id'))), + ]; + } + + /** Bringt ein Formular von DeskHub (oder null) in eine feste Form mit Standardwerten, Feldern nach Plätzen sortiert. */ + public static function normalize_form(?array $f): array + { + $own = $f !== null && !empty($f['ownForm']); + $str = static function ($v, string $default = ''): string { return is_string($v) && trim($v) !== '' ? trim($v) : $default; }; + $out = [ + 'own' => $own, + 'title' => $f !== null ? $str($f['title'] ?? '') : '', + 'intro' => $f !== null ? $str($f['intro'] ?? '') : '', + // DeskHub meldet für Standardformulare seine eigenen deutschen Standardtexte; hier gelten die (übersetzbaren) des Plugins + 'subject' => [ + 'show' => $f === null || !isset($f['subject']['show']) || !empty($f['subject']['show']), + 'label' => $own ? $str($f['subject']['label'] ?? '', __('Betreff', 'deskhub-tickets')) : __('Betreff', 'deskhub-tickets'), + ], + 'message' => [ + 'show' => $f === null || !isset($f['message']['show']) || !empty($f['message']['show']), + 'label' => $own ? $str($f['message']['label'] ?? '', __('Ihre Nachricht', 'deskhub-tickets')) : __('Ihre Nachricht', 'deskhub-tickets'), + 'hint' => $own ? $str($f['message']['hint'] ?? '') : '', + ], + 'submitLabel' => $own ? $str($f['submitLabel'] ?? '', __('Ticket absenden', 'deskhub-tickets')) : __('Ticket absenden', 'deskhub-tickets'), + 'fields' => [], + ]; + $allowed = ['text', 'textarea', 'select', 'radio', 'checkbox', 'number', 'date']; + foreach ((array) ($f['fields'] ?? []) as $fd) { + if (!is_array($fd) || empty($fd['id']) || !isset($fd['label'])) { + continue; + } + $out['fields'][] = [ + 'id' => (int) $fd['id'], + 'label' => (string) $fd['label'], + 'type' => in_array($fd['type'] ?? '', $allowed, true) ? (string) $fd['type'] : 'text', + 'required' => !empty($fd['required']), + 'options' => array_values(array_filter(array_map('strval', (array) ($fd['options'] ?? [])), static function ($o) { return $o !== ''; })), + 'helpHtml' => (string) ($fd['helpHtml'] ?? ''), + 'width' => in_array($fd['width'] ?? '', ['half', 'third', 'twothirds'], true) ? (string) $fd['width'] : 'full', + 'section' => (string) ($fd['section'] ?? 'extra'), + 'sectionTitle' => (string) ($fd['sectionTitle'] ?? ''), + 'radioInline' => !empty($fd['radioInline']), + ]; + } + return $out; + } + + /** + * Gibt die Felder eines Platzes im Formular aus: je Thema ein Block (bei mehreren Themen schaltet form.js den passenden ein). + * $rest = true: alle Felder, die zu keinem bekannten Platz gehören („Zusätzliche Angaben“ und eigene Abschnitte). + * + * @param array $forms + */ + private static function render_slot(array $forms, array $slots, bool $multi, int $selected, array $posted, bool $rest = false): string + { + $html = ''; + foreach ($forms as $tid => $form) { + $fields = array_values(array_filter($form['fields'], static function ($f) use ($slots, $rest) { + return $rest ? !in_array($f['section'], self::SLOTS, true) : in_array($f['section'], $slots, true); + })); + if (!$fields) { + continue; + } + $on = !$multi || $tid === $selected; + $html .= '
'; + $group = null; + foreach ($fields as $f) { + $heading = $rest ? $f['sectionTitle'] : ''; + if ($rest && $heading !== $group) { + $html .= '
' . ($heading !== '' ? '

' . esc_html($heading) . '

' : '') . '
'; + $group = $heading; + } + $html .= self::render_field($f, (string) $tid, isset($posted[$f['id']]) ? (string) $posted[$f['id']] : '', !$on); + } + $html .= '
'; + } + return $html; + } + + private static function help_html(string $html): string + { + return wp_kses($html, ['a' => ['href' => [], 'target' => [], 'rel' => []], 'br' => []]); + } + + /** Ein Eingabefeld aus der Beschreibung von DeskHub. */ + private static function render_field(array $f, string $uid, string $posted, bool $disabled): string + { + $id = (int) $f['id']; + $name = 'dht_f[' . $id . ']'; + $dom = 'dht-f-' . $uid . '-' . $id; + $req = !empty($f['required']); + $dis = $disabled ? ' disabled' : ''; + $reqAttr = $req ? ' required' : ''; + $star = $req ? ' ' : ''; + $label = esc_html((string) $f['label']); + $help = self::help_html((string) $f['helpHtml']); + $o = '
'; + switch ($f['type']) { + case 'textarea': + $o .= '' + . ''; + break; + case 'select': + $o .= ''; + break; + case 'radio': + $o .= '
' . $label . $star . ''; + foreach ($f['options'] as $i => $opt) { + $o .= ''; + } + $o .= '
'; + break; + case 'checkbox': + $o .= ''; + $help = ''; + break; + case 'number': + case 'date': + $o .= ''; + break; + default: + $o .= ''; + } + if ($help !== '') { + $o .= '

' . $help . '

'; + } + return $o . '
'; + } + + /** + * Prüft die Angaben zu den Feldern des Formulars wie DeskHub selbst (DeskHub prüft beim Anlegen noch einmal). + * + * @param array $fields + * @param array $input Feld-ID => abgeschickter Wert + * @return array{0:string[],1:array} Fehlermeldungen und bereinigte Werte (Feld-ID => Wert) + */ + public static function validate_fields(array $fields, array $input): array + { + $errors = []; + $values = []; + foreach ($fields as $f) { + $id = (int) $f['id']; + $label = (string) $f['label']; + $type = (string) $f['type']; + $raw = isset($input[$id]) ? (string) $input[$id] : ''; + if ($type === 'checkbox') { + $val = in_array(strtolower(trim($raw)), ['1', 'on', 'yes', 'ja', 'true'], true) ? '1' : '0'; + } else { + $val = $type === 'textarea' ? sanitize_textarea_field($raw) : sanitize_text_field($raw); + $val = mb_substr($val, 0, 10000); + } + if (!empty($f['required']) && $type !== 'checkbox' && $val === '') { + $errors[] = sprintf(__('Bitte füllen Sie das Feld „%s“ aus.', 'deskhub-tickets'), $label); + } elseif (!empty($f['required']) && $type === 'checkbox' && $val !== '1') { + $errors[] = sprintf(__('Bitte bestätigen Sie „%s“.', 'deskhub-tickets'), $label); + } elseif (in_array($type, ['select', 'radio'], true) && $val !== '' && !in_array($val, $f['options'], true)) { + $errors[] = sprintf(__('Bitte wählen Sie bei „%s“ eine der angebotenen Möglichkeiten.', 'deskhub-tickets'), $label); + } elseif ($type === 'number' && $val !== '' && !is_numeric($val)) { + $errors[] = sprintf(__('Bitte geben Sie bei „%s“ eine Zahl an.', 'deskhub-tickets'), $label); + } elseif ($type === 'date' && $val !== '' && !(preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $val, $m) && checkdate((int) $m[2], (int) $m[3], (int) $m[1]))) { + $errors[] = sprintf(__('Bitte geben Sie bei „%s“ ein gültiges Datum an.', 'deskhub-tickets'), $label); + } + $values[$id] = $val; + } + return [$errors, $values]; + } + + /** Ticket-Text aus den Feldern, wenn das Formular kein Beschreibungsfeld hat (wie im Kundenportal von DeskHub). */ + public static function compose_message(array $fields, array $values): string + { + $lines = []; + foreach ($fields as $f) { + $v = $values[(int) $f['id']] ?? ''; + if ($f['type'] === 'checkbox') { + $v = $v === '1' ? __('Ja', 'deskhub-tickets') : __('Nein', 'deskhub-tickets'); + } + if ($v !== '') { + $lines[] = $f['label'] . ': ' . $v; + } + } + return $lines ? implode("\n", $lines) : __('Anfrage ohne weitere Angaben', 'deskhub-tickets'); + } + + /** Verarbeitet das abgeschickte Formular (admin-post.php) und leitet auf die Seite zurück. */ + public static function handle_submit(): void + { + $back = self::safe_back(isset($_POST['dht_back']) ? (string) wp_unslash($_POST['dht_back']) : ''); + $opts = deskhub_tickets_options(); + + // 1) Unterschriebene Angaben prüfen (Abteilung, Thema, Priorität) + $cfgB64 = isset($_POST['dht_cfg']) ? (string) wp_unslash($_POST['dht_cfg']) : ''; + $sig = isset($_POST['dht_sig']) ? (string) wp_unslash($_POST['dht_sig']) : ''; + $cfg = null; + if (deskhub_tickets_verify_signature('cfg|' . $cfgB64, $sig)) { + $decoded = json_decode((string) base64_decode($cfgB64, true), true); + if (is_array($decoded)) { + $cfg = $decoded; + } + } + if ($cfg === null) { + self::fail($back, [__('Das Formular ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es noch einmal.', 'deskhub-tickets')], []); + } + + // 2) Eingaben bereinigen + $postedFields = []; + if (isset($_POST['dht_f']) && is_array($_POST['dht_f'])) { + foreach ((array) wp_unslash($_POST['dht_f']) as $fid => $v) { + if (is_scalar($v)) { + $postedFields[absint($fid)] = (string) $v; + } + } + } + $values = [ + 'name' => mb_substr(sanitize_text_field(isset($_POST['dht_name']) ? (string) wp_unslash($_POST['dht_name']) : ''), 0, 120), + 'email' => sanitize_email(isset($_POST['dht_email']) ? (string) wp_unslash($_POST['dht_email']) : ''), + 'subject' => mb_substr(sanitize_text_field(isset($_POST['dht_subject']) ? (string) wp_unslash($_POST['dht_subject']) : ''), 0, 255), + 'message' => mb_substr(sanitize_textarea_field(isset($_POST['dht_message']) ? (string) wp_unslash($_POST['dht_message']) : ''), 0, self::MAX_MESSAGE), + 'topic' => isset($_POST['dht_topic']) ? absint(wp_unslash($_POST['dht_topic'])) : 0, + 'fields' => $postedFields, + ]; + + // 3) Falle-Feld: Wer es ausfüllt, ist ein Roboter. Er bekommt eine Erfolgsmeldung, es wird aber nichts angelegt. + if (isset($_POST['dht_website']) && trim((string) wp_unslash($_POST['dht_website'])) !== '') { + self::succeed($back, ''); + } + + // 4) Zeit seit dem Anzeigen des Formulars + $age = deskhub_tickets_timestamp_age(isset($_POST['dht_ts']) ? (string) wp_unslash($_POST['dht_ts']) : ''); + $minSeconds = (int) $opts['min_seconds']; + if ($age === null || $age < $minSeconds) { + self::fail($back, [__('Das ging etwas zu schnell. Bitte prüfen Sie Ihre Angaben und senden Sie noch einmal ab.', 'deskhub-tickets')], $values); + } + + // 5) Thema und Formular (aus DeskHub; ohne Antwort das einfache Formular) + $errors = []; + $allowedTopics = isset($cfg['o']) && is_array($cfg['o']) ? array_map('intval', $cfg['o']) : []; + $topicId = (int) ($cfg['t'] ?? 0); + if ($allowedTopics) { + if (!in_array($values['topic'], $allowedTopics, true)) { + $errors[] = __('Bitte wählen Sie ein Thema aus.', 'deskhub-tickets'); + } else { + $topicId = $values['topic']; + } + } + $client = Deskhub_Tickets_Client::from_settings(); + if (!$client) { + self::fail($back, [__('Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal.', 'deskhub-tickets')], $values); + } + $form = null; + if (!$errors) { + $remote = $client->get_form($topicId ?: null, (int) ($cfg['d'] ?? 0) ?: null); + if (!is_wp_error($remote)) { + $form = self::normalize_form($remote); + } + } + $showSubject = $form === null || $form['subject']['show']; + $showMessage = $form === null || $form['message']['show']; + + // 6) Pflichtfelder + if ($values['name'] === '') { + $errors[] = __('Bitte geben Sie Ihren Namen an.', 'deskhub-tickets'); + } + if ($values['email'] === '' || !is_email($values['email'])) { + $errors[] = __('Bitte geben Sie eine gültige E-Mail-Adresse an.', 'deskhub-tickets'); + } + if ($showSubject && $values['subject'] === '') { + $errors[] = __('Bitte geben Sie einen Betreff an.', 'deskhub-tickets'); + } + if ($showMessage && trim($values['message']) === '') { + $errors[] = __('Bitte beschreiben Sie Ihr Anliegen.', 'deskhub-tickets'); + } + $fieldValues = []; + if ($form !== null) { + [$fieldErrors, $fieldValues] = self::validate_fields($form['fields'], $postedFields); + $errors = array_merge($errors, $fieldErrors); + } + if (trim((string) $opts['consent_text']) !== '' && empty($_POST['dht_consent'])) { + $errors[] = __('Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu.', 'deskhub-tickets'); + } + if ($errors) { + self::fail($back, $errors, $values); + } + + // 7) Captcha und Mengenbegrenzung + $captcha = self::verify_turnstile(); + if ($captcha !== true) { + self::fail($back, [$captcha], $values); + } + if (!deskhub_tickets_rate_allow('submit', (int) $opts['rate_limit_per_hour'], HOUR_IN_SECONDS)) { + self::fail($back, [__('Sie haben in der letzten Stunde schon mehrere Tickets gesendet. Bitte versuchen Sie es später noch einmal.', 'deskhub-tickets')], $values); + } + + // 8) Ticket anlegen + $text = $showMessage ? $values['message'] : self::compose_message($form['fields'], $fieldValues); + $message = $text . "\n\n— " . sprintf( + /* translators: %s: Adresse der Seite mit dem Formular */ + __('Gesendet über das Formular auf %s', 'deskhub-tickets'), + $back + ); + $payload = [ + 'name' => $values['name'], + 'email' => $values['email'], + 'subject' => $showSubject ? $values['subject'] : '', // ohne Betrefffeld setzt DeskHub „Anfrage “ + 'message' => $message, + 'helpTopicId' => $topicId, + 'departmentId' => (int) ($cfg['d'] ?? 0), + 'priorityId' => (int) ($cfg['p'] ?? 0), + ]; + if ($form !== null) { + $payload['fields'] = $fieldValues; + } + $payload = apply_filters('deskhub_tickets_payload', $payload, $values); + $result = $client->create_ticket($payload); + if (is_wp_error($result)) { + self::fail($back, [Deskhub_Tickets_Client::user_message($result)], $values); + } + do_action('deskhub_tickets_created', $result['number'], $payload); + self::succeed($back, $result['number']); + } + + // ───────────────────────────── Stand abfragen ───────────────────────────── + + /** + * @param array|string $atts + */ + public static function render_status_form($atts = []): string + { + self::enqueue(); + if (!Deskhub_Tickets_Client::from_settings()) { + return current_user_can('manage_options') + ? '

' . esc_html__('DeskHub Tickets ist noch nicht eingerichtet (Einstellungen → DeskHub Tickets). Diese Meldung sehen nur Administratoren.', 'deskhub-tickets') . '

' + : ''; + } + $number = ''; + $email = ''; + $error = ''; + $ticket = null; + if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['dht_status_submit'])) { + $number = trim(sanitize_text_field((string) wp_unslash($_POST['dht_number'] ?? ''))); + $email = sanitize_email((string) wp_unslash($_POST['dht_status_email'] ?? '')); + if (isset($_POST['dht_status_website']) && trim((string) wp_unslash($_POST['dht_status_website'])) !== '') { + $error = __('Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden.', 'deskhub-tickets'); + } elseif (!preg_match('/^[A-Za-z0-9._-]{1,40}$/', $number) || !is_email($email)) { + $error = __('Bitte geben Sie Ihre Ticketnummer und die E-Mail-Adresse an, mit der Sie das Ticket gesendet haben.', 'deskhub-tickets'); + } elseif (!deskhub_tickets_rate_allow('status', 10, 10 * MINUTE_IN_SECONDS)) { + // Bremst das Durchprobieren von Ticketnummern + $error = __('Zu viele Abfragen. Bitte versuchen Sie es in einigen Minuten noch einmal.', 'deskhub-tickets'); + } else { + $client = Deskhub_Tickets_Client::from_settings(); + $res = $client ? $client->get_ticket($number, $email) : new WP_Error('deskhub_config', 'x'); + if (is_wp_error($res)) { + $error = Deskhub_Tickets_Client::user_message($res); + } else { + $ticket = $res; + } + } + } + + ob_start(); + echo self::wrap_open('deskhub-status'); + if ($error !== '') { + echo ''; + } + if ($ticket) { + echo self::render_ticket_status($ticket); + } + ?> +
+ +

+ + +

+

+ + +

+

+
+ '; + return (string) ob_get_clean(); + } + + /** Zeigt Status und öffentlichen Verlauf eines Tickets. Alles wird maskiert; die Texte kommen vom Server als Klartext. */ + public static function render_ticket_status(array $t): string + { + $labels = [ + 'open' => __('Offen', 'deskhub-tickets'), + 'pending' => __('Wartet auf Ihre Antwort', 'deskhub-tickets'), + 'answered' => __('Beantwortet', 'deskhub-tickets'), + 'on_hold' => __('In Bearbeitung', 'deskhub-tickets'), + 'resolved' => __('Gelöst', 'deskhub-tickets'), + 'closed' => __('Geschlossen', 'deskhub-tickets'), + ]; + $status = (string) ($t['status'] ?? ''); + $fmt = function ($s): string { + $ts = strtotime((string) $s); + return $ts ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $ts) : ''; + }; + $out = '
'; + $out .= '

' . esc_html((string) ($t['subject'] ?? '')) . '

'; + $out .= '

' . esc_html($labels[$status] ?? $status) . ' ' + . esc_html(sprintf(__('Ticket %s', 'deskhub-tickets'), (string) ($t['number'] ?? ''))) . ' · ' + . esc_html(sprintf(__('Zuletzt geändert: %s', 'deskhub-tickets'), $fmt($t['updatedAt'] ?? ''))) . '

'; + $messages = isset($t['messages']) && is_array($t['messages']) ? $t['messages'] : []; + foreach ($messages as $m) { + if (!is_array($m)) { + continue; + } + $mine = ($m['author'] ?? '') === 'customer'; + $out .= '
' + . '
' . esc_html($mine ? __('Sie', 'deskhub-tickets') : __('Support', 'deskhub-tickets')) . ' · ' . esc_html($fmt($m['createdAt'] ?? '')) . '
' + . '
' . nl2br(esc_html((string) ($m['message'] ?? ''))) . '
'; + } + return $out . '
'; + } + + // ───────────────────────────── Hilfsfunktionen ──────────────────────────── + + /** „Rechnung:3,Technik:4“ → [['label'=>'Rechnung','id'=>3], …] */ + public static function parse_topics(string $spec): array + { + $out = []; + foreach (explode(',', $spec) as $part) { + $pos = strrpos($part, ':'); + if ($pos === false) { + continue; + } + $label = trim(sanitize_text_field(substr($part, 0, $pos))); + $id = absint(trim(substr($part, $pos + 1))); + if ($label !== '' && $id > 0) { + $out[] = ['label' => $label, 'id' => $id]; + } + } + return $out; + } + + /** Adresse der Seite, auf der das Formular steht (dorthin geht es nach dem Absenden zurück). */ + private static function current_url(): string + { + $url = get_permalink(); + return $url ? (string) $url : home_url('/'); + } + + /** Ziel für „zurück zur Seite“: nur Adressen dieser Website. */ + private static function safe_back(string $url): string + { + $url = remove_query_arg('dht', $url); + $safe = wp_validate_redirect($url, home_url('/')); + return $safe ?: home_url('/'); + } + + /** Merkt sich Fehler oder Erfolg kurz und leitet auf die Seite zurück (kein erneutes Senden beim Neuladen). */ + private static function redirect_with_state(string $back, array $state): void + { + $id = bin2hex(random_bytes(8)); + set_transient('dht_state_' . $id, $state, 10 * MINUTE_IN_SECONDS); + wp_safe_redirect(add_query_arg('dht', $id, $back) . '#deskhub-ticket'); + exit; + } + + private static function fail(string $back, array $errors, array $values): void + { + // Die Eingaben bleiben erhalten, damit der Text der Nachricht nicht verloren geht + self::redirect_with_state($back, ['errors' => $errors, 'values' => $values]); + } + + private static function succeed(string $back, string $number): void + { + self::redirect_with_state($back, ['ok' => true, 'number' => $number]); + } + + /** Liest den kurz gemerkten Zustand (Fehler/Erfolg) aus der Adresse und löscht ihn. */ + private static function take_state(): ?array + { + $id = isset($_GET['dht']) ? (string) wp_unslash($_GET['dht']) : ''; + if (!preg_match('/^[a-f0-9]{16}$/', $id)) { + return null; + } + $state = get_transient('dht_state_' . $id); + delete_transient('dht_state_' . $id); + return is_array($state) ? $state : null; + } + + // ───────────────────────────── Cloudflare Turnstile ─────────────────────── + + private static function turnstile_enabled(): bool + { + $o = deskhub_tickets_options(); + return trim((string) $o['turnstile_site']) !== '' && trim((string) $o['turnstile_secret']) !== ''; + } + + private static function turnstile_widget(): string + { + if (!self::turnstile_enabled()) { + return ''; + } + $site = (string) deskhub_tickets_option('turnstile_site'); + wp_enqueue_script('deskhub-turnstile', 'https://challenges.cloudflare.com/turnstile/v0/api.js', [], null, true); + return '
'; + } + + /** @return true|string true bei Erfolg, sonst die Meldung für den Besucher */ + private static function verify_turnstile() + { + if (!self::turnstile_enabled()) { + return true; + } + $token = isset($_POST['cf-turnstile-response']) ? (string) wp_unslash($_POST['cf-turnstile-response']) : ''; + $fail = __('Die Sicherheitsprüfung ist fehlgeschlagen. Bitte versuchen Sie es noch einmal.', 'deskhub-tickets'); + if ($token === '' || strlen($token) > 2048) { + return $fail; + } + $res = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [ + 'timeout' => 10, + 'body' => ['secret' => (string) deskhub_tickets_option('turnstile_secret'), 'response' => $token, 'remoteip' => deskhub_tickets_client_ip()], + ]); + if (is_wp_error($res)) { + error_log('DeskHub Tickets: Turnstile nicht erreichbar: ' . $res->get_error_message()); + return $fail; + } + $data = json_decode((string) wp_remote_retrieve_body($res), true); + return is_array($data) && !empty($data['success']) ? true : $fail; + } +} diff --git a/wordpress-plugin/deskhub-tickets/includes/class-settings.php b/wordpress-plugin/deskhub-tickets/includes/class-settings.php new file mode 100644 index 00000000..04342719 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/includes/class-settings.php @@ -0,0 +1,279 @@ +' . esc_html__('Einstellungen', 'deskhub-tickets') . ''); + return $links; + } + + public static function register(): void + { + register_setting(self::GROUP, 'deskhub_tickets_options', [ + 'type' => 'array', + 'sanitize_callback' => [__CLASS__, 'sanitize'], + 'default' => [], + ]); + } + + /** + * Prüft und bereinigt die abgeschickten Einstellungen. Der API-Schlüssel bleibt erhalten, wenn das Feld leer ist + * (er wird nie wieder in die Seite geschrieben). + */ + public static function sanitize($input): array + { + $input = is_array($input) ? $input : []; + $old = get_option('deskhub_tickets_options', []); + $old = array_merge(deskhub_tickets_defaults(), is_array($old) ? $old : []); + $out = deskhub_tickets_defaults(); + + $url = trim((string) ($input['server_url'] ?? '')); + if ($url === '') { + $out['server_url'] = ''; + } else { + $norm = Deskhub_Tickets_Client::normalize_url($url); + if (is_wp_error($norm)) { + add_settings_error('deskhub_tickets_options', 'deskhub_url', $norm->get_error_message()); + $out['server_url'] = (string) $old['server_url']; + } else { + $out['server_url'] = $norm; + } + } + + if (!empty($input['clear_key'])) { + $out['api_key'] = ''; + } else { + $key = trim((string) ($input['api_key'] ?? '')); + $key = preg_replace('/[^\x21-\x7E]/', '', $key); + $out['api_key'] = $key !== '' ? $key : (string) $old['api_key']; + } + + foreach (['department_id', 'topic_id', 'priority_id'] as $f) { + $out[$f] = absint($input[$f] ?? 0); + } + $out['consent_text'] = wp_kses((string) ($input['consent_text'] ?? ''), [ + 'a' => ['href' => [], 'target' => [], 'rel' => []], + 'strong' => [], + 'em' => [], + ]); + $accent = sanitize_hex_color((string) ($input['accent_color'] ?? '')); + $out['accent_color'] = empty($input['accent_auto']) && $accent ? $accent : ''; + $out['success_text'] = mb_substr(sanitize_text_field((string) ($input['success_text'] ?? '')), 0, 500); + $out['status_page_url'] = esc_url_raw((string) ($input['status_page_url'] ?? '')); + $out['min_seconds'] = max(0, min(60, absint($input['min_seconds'] ?? 3))); + $out['rate_limit_per_hour'] = max(0, min(100, absint($input['rate_limit_per_hour'] ?? 5))); + $out['turnstile_site'] = sanitize_text_field((string) ($input['turnstile_site'] ?? '')); + $secret = trim(sanitize_text_field((string) ($input['turnstile_secret'] ?? ''))); + $out['turnstile_secret'] = !empty($input['clear_turnstile']) ? '' : ($secret !== '' ? $secret : (string) $old['turnstile_secret']); + + // „Speichern und Verbindung testen“: prüft gleich die Werte, die gerade gespeichert werden (nicht erst die schon gespeicherten) + if (!empty($_POST['dht_test_after']) && current_user_can('manage_options')) { + $key = deskhub_tickets_key_from_config() ? (string) DESKHUB_TICKETS_API_KEY : (string) $out['api_key']; + $url = defined('DESKHUB_TICKETS_SERVER') && DESKHUB_TICKETS_SERVER !== '' ? (string) DESKHUB_TICKETS_SERVER : (string) $out['server_url']; + $norm = Deskhub_Tickets_Client::normalize_url($url); + $result = (is_wp_error($norm) || trim($key) === '') + ? ['ok' => false, 'message' => __('Bitte Server-Adresse und API-Schlüssel eintragen.', 'deskhub-tickets'), 'canRead' => false] + : (new Deskhub_Tickets_Client($norm, trim($key)))->test_connection(); + set_transient('deskhub_tickets_test_' . get_current_user_id(), $result, 120); + } + return $out; + } + + /** „Formulare neu laden“: leert den Zwischenspeicher der Kundenformulare aus DeskHub. */ + public static function handle_clear_cache(): void + { + if (!current_user_can('manage_options')) { + wp_die(esc_html__('Dafür fehlt die Berechtigung.', 'deskhub-tickets'), '', ['response' => 403]); + } + check_admin_referer('deskhub_tickets_clear_cache'); + Deskhub_Tickets_Client::clear_form_cache(); + set_transient('deskhub_tickets_test_' . get_current_user_id(), ['ok' => true, 'message' => __('Die Formulare werden beim nächsten Aufruf neu aus DeskHub geladen.', 'deskhub-tickets'), 'canRead' => true], 120); + wp_safe_redirect(admin_url('options-general.php?page=' . self::PAGE . '&dht_tested=1')); + exit; + } + + public static function render(): void + { + if (!current_user_can('manage_options')) { + return; + } $o = deskhub_tickets_options(); + $name = 'deskhub_tickets_options'; + $keyFromConfig = deskhub_tickets_key_from_config(); + $hasKey = trim((string) $o['api_key']) !== ''; + $hasSecret = trim((string) $o['turnstile_secret']) !== ''; + $topicList = []; + $cl = Deskhub_Tickets_Client::from_settings(); + if ($cl) { + $tl = $cl->get_topics(); + $topicList = is_array($tl) ? $tl : []; + } + $tk = 'deskhub_tickets_test_' . get_current_user_id(); + $test = get_transient($tk); // Ergebnis von „Speichern und Verbindung testen“ bzw. „Formulare neu laden“ (wird nur einmal gezeigt) + delete_transient($tk); + ?> +
+

+

+ + +

+ + + +
+ +

+ + + + + + + + + + + +

+ + + + + + + +

+ + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + +

+ + + +

+
+ +
+ + + + +
+ +

+

+
    +
  • [deskhub_ticket] –
  • +
  • [deskhub_ticket department="2" topic="5" priority="1"] –
  • +
  • [deskhub_ticket topics="Rechnung:3,Technik:4"] –
  • +
  • [deskhub_status] –
  • +
+
+ '', + 'api_key' => '', + 'department_id' => 0, + 'topic_id' => 0, + 'priority_id' => 0, + 'consent_text' => '', + 'accent_color' => '', + 'success_text' => '', + 'status_page_url' => '', + 'min_seconds' => 3, + 'rate_limit_per_hour' => 5, + 'turnstile_site' => '', + 'turnstile_secret' => '', + ]; +} + +/** Alle Einstellungen (mit Standardwerten). Server-Adresse und Schlüssel lassen sich zusätzlich in der wp-config.php festlegen. */ +function deskhub_tickets_options(): array +{ + $saved = get_option('deskhub_tickets_options', []); + $opts = array_merge(deskhub_tickets_defaults(), is_array($saved) ? $saved : []); + if (defined('DESKHUB_TICKETS_SERVER') && DESKHUB_TICKETS_SERVER !== '') { + $opts['server_url'] = (string) DESKHUB_TICKETS_SERVER; + } + if (defined('DESKHUB_TICKETS_API_KEY') && DESKHUB_TICKETS_API_KEY !== '') { + $opts['api_key'] = (string) DESKHUB_TICKETS_API_KEY; + } + return $opts; +} + +function deskhub_tickets_option(string $key) +{ + $opts = deskhub_tickets_options(); + return $opts[$key] ?? null; +} + +/** Wird der API-Schlüssel aus der wp-config.php genommen (dann ist das Feld in den Einstellungen gesperrt)? */ +function deskhub_tickets_key_from_config(): bool +{ + return defined('DESKHUB_TICKETS_API_KEY') && DESKHUB_TICKETS_API_KEY !== ''; +} + +/** + * Erkennt den Besucher für die Mengenbegrenzung. Es zählt nur REMOTE_ADDR: Header wie X-Forwarded-For kann jeder selbst setzen. + * Steht die Seite hinter einem Proxy, liefert der Filter "deskhub_tickets_client_ip" die richtige Adresse. + */ +function deskhub_tickets_client_ip(): string +{ + $ip = isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : ''; + $ip = apply_filters('deskhub_tickets_client_ip', $ip); + return filter_var($ip, FILTER_VALIDATE_IP) ? (string) $ip : 'unknown'; +} + +/** Kurzer Schlüssel für einen Besucher (nicht umkehrbar, damit keine IP-Adressen in der Datenbank stehen). */ +function deskhub_tickets_visitor_key(string $scope): string +{ + return 'dht_' . substr(hash_hmac('sha256', $scope . '|' . deskhub_tickets_client_ip(), wp_salt('auth')), 0, 32); +} + +/** + * Zählt einen Vorgang für den Besucher und sagt, ob er noch erlaubt ist. + * $max Vorgänge je $window Sekunden; false = Grenze erreicht (der Vorgang wird dann nicht mehr gezählt). + */ +function deskhub_tickets_rate_allow(string $scope, int $max, int $window): bool +{ + if ($max <= 0) { + return true; + } + $key = deskhub_tickets_visitor_key($scope); + $count = (int) get_transient($key); + if ($count >= $max) { + return false; + } + set_transient($key, $count + 1, $window); + return true; +} + +/** Unterschreibt Angaben, die durchs Formular laufen (Abteilung, Thema, Priorität), damit Besucher sie nicht ändern können. */ +function deskhub_tickets_sign(string $payload): string +{ + return hash_hmac('sha256', $payload, wp_salt('auth')); +} + +function deskhub_tickets_verify_signature(string $payload, string $signature): bool +{ + return $signature !== '' && hash_equals(deskhub_tickets_sign($payload), $signature); +} + +/** Zeitstempel mit Unterschrift: beweist, wann das Formular angezeigt wurde (Schutz vor Robotern, die sofort absenden). */ +function deskhub_tickets_timestamp_token(): string +{ + $t = (string) time(); + return $t . '.' . deskhub_tickets_sign('ts|' . $t); +} + +/** Alter des Formulars in Sekunden; null, wenn der Wert fehlt oder gefälscht ist. */ +function deskhub_tickets_timestamp_age(string $token): ?int +{ + if (!preg_match('/^(\d{9,11})\.([a-f0-9]{64})$/', $token, $m)) { + return null; + } + if (!deskhub_tickets_verify_signature('ts|' . $m[1], $m[2])) { + return null; + } + return time() - (int) $m[1]; +} diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_AU.mo b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_AU.mo new file mode 100644 index 00000000..0aa12645 Binary files /dev/null and b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_AU.mo differ diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_AU.po b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_AU.po new file mode 100644 index 00000000..fa89a0c3 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_AU.po @@ -0,0 +1,376 @@ +# DeskHub Tickets: English translation (source language is German) +msgid "" +msgstr "" +"Project-Id-Version: DeskHub Tickets 1.0.0\n" +"Language: en_AU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "API-Schlüssel" +msgstr "API key" + +msgid "Abteilung" +msgstr "Department" + +msgid "Adresse des DeskHub-Servers" +msgstr "DeskHub server address" + +msgid "Anfrage ohne weitere Angaben" +msgstr "Request without further details" + +msgid "Beantwortet" +msgstr "Answered" + +msgid "Besucher wählen ein Thema aus (Bezeichnung:Nummer des Hilfethemas)" +msgstr "visitors choose a topic (label:number of the help topic)" + +msgid "Betreff" +msgstr "Subject" + +msgid "Bitte Server-Adresse und API-Schlüssel eintragen." +msgstr "Please enter the server address and API key." + +msgid "Bitte beschreiben Sie Ihr Anliegen." +msgstr "Please describe your request." + +msgid "Bitte bestätigen Sie „%s“." +msgstr "Please confirm “%s”." + +msgid "Bitte die Adresse Ihres DeskHub-Servers eintragen." +msgstr "Please enter the address of your DeskHub server." + +msgid "Bitte füllen Sie das Feld „%s“ aus." +msgstr "Please fill in the field “%s”." + +msgid "Bitte geben Sie Ihre Ticketnummer und die E-Mail-Adresse an, mit der Sie das Ticket gesendet haben." +msgstr "Please enter your ticket number and the email address you used to submit the ticket." + +msgid "Bitte geben Sie Ihren Namen an (höchstens 120 Zeichen)." +msgstr "Please enter your name (at most 120 characters)." + +msgid "Bitte geben Sie Ihren Namen an." +msgstr "Please enter your name." + +msgid "Bitte geben Sie bei „%s“ ein gültiges Datum an." +msgstr "Please enter a valid date for “%s”." + +msgid "Bitte geben Sie bei „%s“ eine Zahl an." +msgstr "Please enter a number for “%s”." + +msgid "Bitte geben Sie eine gültige E-Mail-Adresse an." +msgstr "Please enter a valid email address." + +msgid "Bitte geben Sie einen Betreff an (höchstens 255 Zeichen)." +msgstr "Please enter a subject (at most 255 characters)." + +msgid "Bitte geben Sie einen Betreff an." +msgstr "Please enter a subject." + +msgid "Bitte prüfen Sie Ihre Angaben." +msgstr "Please check your details." + +msgid "Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu." +msgstr "Please agree to the processing of your data." + +msgid "Bitte wählen Sie bei „%s“ eine der angebotenen Möglichkeiten." +msgstr "Please choose one of the offered options for “%s”." + +msgid "Bitte wählen Sie ein Thema aus." +msgstr "Please choose a topic." + +msgid "Bitte wählen …" +msgstr "Please choose …" + +msgid "Cloudflare Turnstile (optional)" +msgstr "Cloudflare Turnstile (optional)" + +msgid "Dafür fehlt die Berechtigung." +msgstr "You do not have permission to do this." + +msgid "Das Formular ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es noch einmal." +msgstr "The form has expired. Please reload the page and try again." + +msgid "Das ging etwas zu schnell. Bitte prüfen Sie Ihre Angaben und senden Sie noch einmal ab." +msgstr "That was a bit too fast. Please check your details and submit again." + +msgid "Dazu kommt ein unsichtbares Feld, das nur Roboter ausfüllen. Das Formular funktioniert auch mit Seiten-Cache." +msgstr "In addition there is an invisible field that only robots fill in. The form also works with page caching." + +msgid "Der DeskHub-Server ist nicht erreichbar." +msgstr "The DeskHub server cannot be reached." + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht (bitte DeskHub aktualisieren). Bis dahin wird das einfache Formular ohne eigene Felder gezeigt. (Diese Meldung sehen nur Administratoren.)" +msgstr "The DeskHub server does not know the form interface yet (please update DeskHub). Until then the simple form without custom fields is shown. (Only administrators see this message.)" + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht." +msgstr "The DeskHub server does not know the form interface yet." + +msgid "Der Schlüssel ist gültig, aber DeskHub lässt diese Adresse nicht zu. DeskHub meldet: %s" +msgstr "The key is valid, but DeskHub does not allow this address. DeskHub says: %s" + +msgid "Der Server antwortet mit einem Fehler (%d)." +msgstr "The server responded with an error (%d)." + +msgid "Der Server hat den API-Schlüssel abgelehnt. Bitte den Schlüssel prüfen: Er muss genau so eingetragen sein, aktiv sein und darf bei „Erlaubte IPs“ keine andere Adresse verlangen als die dieses WordPress-Servers (im Zweifel das Feld leer lassen)." +msgstr "The server rejected the API key. Please check the key: it must be entered exactly like this, be active, and its “Allowed IPs” must not demand a different address than this WordPress server’s (if in doubt, leave that field empty)." + +msgid "Der Server hat die Anfrage abgelehnt." +msgstr "The server rejected the request." + +msgid "Der Server hat eine unerwartete Antwort geliefert." +msgstr "The server returned an unexpected response." + +msgid "Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal." +msgstr "Support is currently not available. Please try again later." + +msgid "Der Test legt kein Ticket an." +msgstr "The test does not create a ticket." + +msgid "DeskHub Tickets" +msgstr "DeskHub Tickets" + +msgid "DeskHub Tickets ist noch nicht eingerichtet (Einstellungen → DeskHub Tickets). Diese Meldung sehen nur Administratoren." +msgstr "DeskHub Tickets is not set up yet (Settings → DeskHub Tickets). Only administrators see this message." + +msgid "DeskHub Tickets ist noch nicht eingerichtet: Bitte unter Einstellungen → DeskHub Tickets Server-Adresse und API-Schlüssel eintragen. (Diese Meldung sehen nur Administratoren.)" +msgstr "DeskHub Tickets is not set up yet: please enter the server address and API key under Settings → DeskHub Tickets. (Only administrators see this message.)" + +msgid "DeskHub entscheidet" +msgstr "DeskHub decides" + +msgid "Die Adresse, unter der Ihr DeskHub erreichbar ist. Nur https (http nur für lokale Tests)." +msgstr "The address where your DeskHub can be reached. https only (http only for local tests)." + +msgid "Die Formulare werden beim nächsten Aufruf neu aus DeskHub geladen." +msgstr "The forms will be reloaded from DeskHub on the next request." + +msgid "Die Nummern stehen in DeskHub bei der jeweiligen Abteilung, dem Hilfethema und der Priorität. 0 = DeskHub entscheidet (Standardabteilung, Regeln des Hilfethemas). Im Shortcode lassen sich die Werte je Formular überschreiben." +msgstr "The numbers are shown in DeskHub next to each department, help topic and priority. 0 = DeskHub decides (default department, rules of the help topic). The values can be overridden per form in the shortcode." + +msgid "Die Server-Adresse ist ungültig." +msgstr "The server address is invalid." + +msgid "Die Server-Adresse muss mit https beginnen." +msgstr "The server address must start with https." + +msgid "Die Shortcodes funktionieren in Seiten und Beiträgen (im Block-Editor mit dem Block „Shortcode“)." +msgstr "The shortcodes work in pages and posts (in the block editor use the “Shortcode” block)." + +msgid "Die Sicherheitsprüfung ist fehlgeschlagen. Bitte versuchen Sie es noch einmal." +msgstr "The security check failed. Please try again." + +msgid "Die Ticketnummer wird automatisch darunter angezeigt." +msgstr "The ticket number is shown below it automatically." + +msgid "E-Mail-Adresse" +msgstr "Email address" + +msgid "Eigene Felder und Texte kommen aus DeskHub und werden 5 Minuten gemerkt. Nach einer Änderung dort hier neu laden." +msgstr "Custom fields and texts come from DeskHub and are remembered for 5 minutes. After a change there, reload them here." + +msgid "Ein kostenloses Captcha ohne Rätsel. Beide Schlüssel eintragen, dann erscheint es im Formular. Dabei wird eine Verbindung zu Cloudflare aufgebaut: Das gehört in Ihre Datenschutzerklärung." +msgstr "A free captcha without puzzles. Enter both keys and it appears in the form. This connects to Cloudflare, so it belongs in your privacy policy." + +msgid "Eingebaut" +msgstr "Built in" + +msgid "Einstellungen" +msgstr "Settings" + +msgid "Einwilligung (Datenschutz)" +msgstr "Consent (data protection)" + +msgid "Formular" +msgstr "Form" + +msgid "Formular zum Anlegen eines Tickets" +msgstr "form for creating a ticket" + +msgid "Formulare neu laden" +msgstr "Reload forms" + +msgid "Geheimer Schlüssel (Secret Key)" +msgstr "Secret key" + +msgid "Geheimer Schlüssel gespeichert" +msgstr "Secret key saved" + +msgid "Gelöst" +msgstr "Resolved" + +msgid "Gerade sind sehr viele Anfragen eingegangen. Bitte versuchen Sie es in einer Minute noch einmal." +msgstr "A lot of requests have come in. Please try again in a minute." + +msgid "Geschlossen" +msgstr "Closed" + +msgid "Gesendet über das Formular auf %s" +msgstr "Sent via the form on %s" + +msgid "Hilfethema" +msgstr "Help topic" + +msgid "Höchstens" +msgstr "At most" + +msgid "Ich habe die Datenschutzerklärung gelesen und bin mit der Verarbeitung meiner Angaben einverstanden." +msgstr "I have read the privacy policy and agree to the processing of my data." + +msgid "Ihr Name" +msgstr "Your name" + +msgid "Ihre Anfrage konnte leider nicht angenommen werden." +msgstr "Unfortunately your request could not be accepted." + +msgid "Ihre E-Mail-Adresse" +msgstr "Your email address" + +msgid "Ihre Nachricht" +msgstr "Your message" + +msgid "Ihre Ticketnummer: %s" +msgstr "Your ticket number: %s" + +msgid "In Bearbeitung" +msgstr "In progress" + +msgid "In DeskHub unter Verwalten → API-Schlüssel einen Schlüssel erstellen. Er braucht das Recht „Tickets anlegen“, für die Statusabfrage zusätzlich „Tickets lesen“. Sicherer: den Schlüssel in der wp-config.php eintragen: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" +msgstr "In DeskHub create a key under Manage → API keys. It needs the right “Create tickets”, and for the status page also “Read tickets”. Safer: enter the key in wp-config.php: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" + +msgid "Ja" +msgstr "Yes" + +msgid "Mindestzeit bis zum Absenden" +msgstr "Minimum time before submitting" + +msgid "Nein" +msgstr "No" + +msgid "Nummern in DeskHub" +msgstr "Numbers in DeskHub" + +msgid "Offen" +msgstr "Open" + +msgid "Optional: Nach dem Absenden erscheint ein Link „Stand abfragen“ zu dieser Seite (dort steht der Shortcode [deskhub_status])." +msgstr "Optional: after submitting, a link “Check status” to this page appears (it contains the shortcode [deskhub_status])." + +msgid "Priorität" +msgstr "Priority" + +msgid "Schlüssel entfernen" +msgstr "Remove key" + +msgid "Seite mit der Statusabfrage" +msgstr "Page with the status lookup" + +msgid "Sekunden" +msgstr "seconds" + +msgid "Sie" +msgstr "You" + +msgid "Sie haben in der letzten Stunde schon mehrere Tickets gesendet. Bitte versuchen Sie es später noch einmal." +msgstr "You have already sent several tickets in the last hour. Please try again later." + +msgid "So binden Sie es ein" +msgstr "How to add it" + +msgid "Spamschutz" +msgstr "Spam protection" + +msgid "Speichern" +msgstr "Save" + +msgid "Speichern und Verbindung testen" +msgstr "Save and test connection" + +msgid "Stand abfragen" +msgstr "Check status" + +msgid "Statusabfrage mit Ticketnummer und E-Mail-Adresse" +msgstr "status lookup with ticket number and email address" + +msgid "Support" +msgstr "Support" + +msgid "Text nach dem Absenden" +msgstr "Text after submitting" + +msgid "Ticket %s" +msgstr "Ticket %s" + +msgid "Ticket absenden" +msgstr "Submit ticket" + +msgid "Ticket-Voreinstellungen" +msgstr "Ticket defaults" + +msgid "Ticketnummer" +msgstr "Ticket number" + +msgid "Tickets je Besucher und Stunde (0 = unbegrenzt)" +msgstr "tickets per visitor and hour (0 = unlimited)" + +msgid "Verbindet WordPress mit Ihrem DeskHub-Helpdesk: Besucher legen Tickets über ein Formular an und können den Stand abfragen." +msgstr "Connects WordPress to your DeskHub helpdesk: visitors create tickets through a form and can check the status." + +msgid "Verbindung" +msgstr "Connection" + +msgid "Verbindung steht, der Schlüssel ist gültig und darf Tickets lesen." +msgstr "Connection works, the key is valid and may read tickets." + +msgid "Verbindung steht, der Schlüssel ist gültig. Er darf aber keine Tickets lesen: Die Statusabfrage funktioniert damit nicht (in DeskHub unter Verwalten → API-Schlüssel das Recht „Tickets lesen“ geben)." +msgstr "Connection works, the key is valid. However it may not read tickets, so the status lookup will not work (in DeskHub give it the right “Read tickets” under Manage → API keys)." + +msgid "Verbindung steht." +msgstr "Connection works." + +msgid "Vielen Dank! Wir melden uns so schnell wie möglich per E-Mail." +msgstr "Thank you! We will get back to you by email as soon as possible." + +msgid "Wartet auf Ihre Antwort" +msgstr "Waiting for your reply" + +msgid "Website-Schlüssel (Site Key)" +msgstr "Site key" + +msgid "Wenn ausgefüllt, muss der Besucher dieses Kästchen anhaken. Erlaubt sind Links (a), fett und kursiv." +msgstr "If filled in, the visitor has to tick this box. Links (a), bold and italic are allowed." + +msgid "Worum geht es?" +msgstr "What is it about?" + +msgid "Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden." +msgstr "Nothing was found for this combination of ticket number and email address." + +msgid "Zu viele Abfragen. Bitte versuchen Sie es in einigen Minuten noch einmal." +msgstr "Too many lookups. Please try again in a few minutes." + +msgid "Zuletzt geändert: %s" +msgstr "Last changed: %s" + +msgid "aus der wp-config.php" +msgstr "from wp-config.php" + +msgid "entfernen" +msgstr "remove" + +msgid "gespeichert, zum Ändern neuen Schlüssel eintragen" +msgstr "saved, enter a new key to change it" + +msgid "mit festen Voreinstellungen für dieses Formular" +msgstr "with fixed defaults for this form" + +msgid "Akzentfarbe" +msgstr "Accent color" + +msgid "Automatisch vom Theme übernehmen" +msgstr "Take automatically from the theme" + +msgid "Schrift, Hintergrund und Rahmen übernimmt das Formular immer vom Theme. Die Akzentfarbe (Knopf, Hervorhebung) wird automatisch aus Knöpfen und Links der Seite gelesen; hier lässt sie sich fest vorgeben." +msgstr "The form always takes font, background and border from the theme. The accent color (button, highlights) is read automatically from buttons and links of the page; you can set it here." + diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_CA.mo b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_CA.mo new file mode 100644 index 00000000..0f58da2d Binary files /dev/null and b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_CA.mo differ diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_CA.po b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_CA.po new file mode 100644 index 00000000..18970dc8 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_CA.po @@ -0,0 +1,376 @@ +# DeskHub Tickets: English translation (source language is German) +msgid "" +msgstr "" +"Project-Id-Version: DeskHub Tickets 1.0.0\n" +"Language: en_CA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "API-Schlüssel" +msgstr "API key" + +msgid "Abteilung" +msgstr "Department" + +msgid "Adresse des DeskHub-Servers" +msgstr "DeskHub server address" + +msgid "Anfrage ohne weitere Angaben" +msgstr "Request without further details" + +msgid "Beantwortet" +msgstr "Answered" + +msgid "Besucher wählen ein Thema aus (Bezeichnung:Nummer des Hilfethemas)" +msgstr "visitors choose a topic (label:number of the help topic)" + +msgid "Betreff" +msgstr "Subject" + +msgid "Bitte Server-Adresse und API-Schlüssel eintragen." +msgstr "Please enter the server address and API key." + +msgid "Bitte beschreiben Sie Ihr Anliegen." +msgstr "Please describe your request." + +msgid "Bitte bestätigen Sie „%s“." +msgstr "Please confirm “%s”." + +msgid "Bitte die Adresse Ihres DeskHub-Servers eintragen." +msgstr "Please enter the address of your DeskHub server." + +msgid "Bitte füllen Sie das Feld „%s“ aus." +msgstr "Please fill in the field “%s”." + +msgid "Bitte geben Sie Ihre Ticketnummer und die E-Mail-Adresse an, mit der Sie das Ticket gesendet haben." +msgstr "Please enter your ticket number and the email address you used to submit the ticket." + +msgid "Bitte geben Sie Ihren Namen an (höchstens 120 Zeichen)." +msgstr "Please enter your name (at most 120 characters)." + +msgid "Bitte geben Sie Ihren Namen an." +msgstr "Please enter your name." + +msgid "Bitte geben Sie bei „%s“ ein gültiges Datum an." +msgstr "Please enter a valid date for “%s”." + +msgid "Bitte geben Sie bei „%s“ eine Zahl an." +msgstr "Please enter a number for “%s”." + +msgid "Bitte geben Sie eine gültige E-Mail-Adresse an." +msgstr "Please enter a valid email address." + +msgid "Bitte geben Sie einen Betreff an (höchstens 255 Zeichen)." +msgstr "Please enter a subject (at most 255 characters)." + +msgid "Bitte geben Sie einen Betreff an." +msgstr "Please enter a subject." + +msgid "Bitte prüfen Sie Ihre Angaben." +msgstr "Please check your details." + +msgid "Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu." +msgstr "Please agree to the processing of your data." + +msgid "Bitte wählen Sie bei „%s“ eine der angebotenen Möglichkeiten." +msgstr "Please choose one of the offered options for “%s”." + +msgid "Bitte wählen Sie ein Thema aus." +msgstr "Please choose a topic." + +msgid "Bitte wählen …" +msgstr "Please choose …" + +msgid "Cloudflare Turnstile (optional)" +msgstr "Cloudflare Turnstile (optional)" + +msgid "Dafür fehlt die Berechtigung." +msgstr "You do not have permission to do this." + +msgid "Das Formular ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es noch einmal." +msgstr "The form has expired. Please reload the page and try again." + +msgid "Das ging etwas zu schnell. Bitte prüfen Sie Ihre Angaben und senden Sie noch einmal ab." +msgstr "That was a bit too fast. Please check your details and submit again." + +msgid "Dazu kommt ein unsichtbares Feld, das nur Roboter ausfüllen. Das Formular funktioniert auch mit Seiten-Cache." +msgstr "In addition there is an invisible field that only robots fill in. The form also works with page caching." + +msgid "Der DeskHub-Server ist nicht erreichbar." +msgstr "The DeskHub server cannot be reached." + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht (bitte DeskHub aktualisieren). Bis dahin wird das einfache Formular ohne eigene Felder gezeigt. (Diese Meldung sehen nur Administratoren.)" +msgstr "The DeskHub server does not know the form interface yet (please update DeskHub). Until then the simple form without custom fields is shown. (Only administrators see this message.)" + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht." +msgstr "The DeskHub server does not know the form interface yet." + +msgid "Der Schlüssel ist gültig, aber DeskHub lässt diese Adresse nicht zu. DeskHub meldet: %s" +msgstr "The key is valid, but DeskHub does not allow this address. DeskHub says: %s" + +msgid "Der Server antwortet mit einem Fehler (%d)." +msgstr "The server responded with an error (%d)." + +msgid "Der Server hat den API-Schlüssel abgelehnt. Bitte den Schlüssel prüfen: Er muss genau so eingetragen sein, aktiv sein und darf bei „Erlaubte IPs“ keine andere Adresse verlangen als die dieses WordPress-Servers (im Zweifel das Feld leer lassen)." +msgstr "The server rejected the API key. Please check the key: it must be entered exactly like this, be active, and its “Allowed IPs” must not demand a different address than this WordPress server’s (if in doubt, leave that field empty)." + +msgid "Der Server hat die Anfrage abgelehnt." +msgstr "The server rejected the request." + +msgid "Der Server hat eine unerwartete Antwort geliefert." +msgstr "The server returned an unexpected response." + +msgid "Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal." +msgstr "Support is currently not available. Please try again later." + +msgid "Der Test legt kein Ticket an." +msgstr "The test does not create a ticket." + +msgid "DeskHub Tickets" +msgstr "DeskHub Tickets" + +msgid "DeskHub Tickets ist noch nicht eingerichtet (Einstellungen → DeskHub Tickets). Diese Meldung sehen nur Administratoren." +msgstr "DeskHub Tickets is not set up yet (Settings → DeskHub Tickets). Only administrators see this message." + +msgid "DeskHub Tickets ist noch nicht eingerichtet: Bitte unter Einstellungen → DeskHub Tickets Server-Adresse und API-Schlüssel eintragen. (Diese Meldung sehen nur Administratoren.)" +msgstr "DeskHub Tickets is not set up yet: please enter the server address and API key under Settings → DeskHub Tickets. (Only administrators see this message.)" + +msgid "DeskHub entscheidet" +msgstr "DeskHub decides" + +msgid "Die Adresse, unter der Ihr DeskHub erreichbar ist. Nur https (http nur für lokale Tests)." +msgstr "The address where your DeskHub can be reached. https only (http only for local tests)." + +msgid "Die Formulare werden beim nächsten Aufruf neu aus DeskHub geladen." +msgstr "The forms will be reloaded from DeskHub on the next request." + +msgid "Die Nummern stehen in DeskHub bei der jeweiligen Abteilung, dem Hilfethema und der Priorität. 0 = DeskHub entscheidet (Standardabteilung, Regeln des Hilfethemas). Im Shortcode lassen sich die Werte je Formular überschreiben." +msgstr "The numbers are shown in DeskHub next to each department, help topic and priority. 0 = DeskHub decides (default department, rules of the help topic). The values can be overridden per form in the shortcode." + +msgid "Die Server-Adresse ist ungültig." +msgstr "The server address is invalid." + +msgid "Die Server-Adresse muss mit https beginnen." +msgstr "The server address must start with https." + +msgid "Die Shortcodes funktionieren in Seiten und Beiträgen (im Block-Editor mit dem Block „Shortcode“)." +msgstr "The shortcodes work in pages and posts (in the block editor use the “Shortcode” block)." + +msgid "Die Sicherheitsprüfung ist fehlgeschlagen. Bitte versuchen Sie es noch einmal." +msgstr "The security check failed. Please try again." + +msgid "Die Ticketnummer wird automatisch darunter angezeigt." +msgstr "The ticket number is shown below it automatically." + +msgid "E-Mail-Adresse" +msgstr "Email address" + +msgid "Eigene Felder und Texte kommen aus DeskHub und werden 5 Minuten gemerkt. Nach einer Änderung dort hier neu laden." +msgstr "Custom fields and texts come from DeskHub and are remembered for 5 minutes. After a change there, reload them here." + +msgid "Ein kostenloses Captcha ohne Rätsel. Beide Schlüssel eintragen, dann erscheint es im Formular. Dabei wird eine Verbindung zu Cloudflare aufgebaut: Das gehört in Ihre Datenschutzerklärung." +msgstr "A free captcha without puzzles. Enter both keys and it appears in the form. This connects to Cloudflare, so it belongs in your privacy policy." + +msgid "Eingebaut" +msgstr "Built in" + +msgid "Einstellungen" +msgstr "Settings" + +msgid "Einwilligung (Datenschutz)" +msgstr "Consent (data protection)" + +msgid "Formular" +msgstr "Form" + +msgid "Formular zum Anlegen eines Tickets" +msgstr "form for creating a ticket" + +msgid "Formulare neu laden" +msgstr "Reload forms" + +msgid "Geheimer Schlüssel (Secret Key)" +msgstr "Secret key" + +msgid "Geheimer Schlüssel gespeichert" +msgstr "Secret key saved" + +msgid "Gelöst" +msgstr "Resolved" + +msgid "Gerade sind sehr viele Anfragen eingegangen. Bitte versuchen Sie es in einer Minute noch einmal." +msgstr "A lot of requests have come in. Please try again in a minute." + +msgid "Geschlossen" +msgstr "Closed" + +msgid "Gesendet über das Formular auf %s" +msgstr "Sent via the form on %s" + +msgid "Hilfethema" +msgstr "Help topic" + +msgid "Höchstens" +msgstr "At most" + +msgid "Ich habe die Datenschutzerklärung gelesen und bin mit der Verarbeitung meiner Angaben einverstanden." +msgstr "I have read the privacy policy and agree to the processing of my data." + +msgid "Ihr Name" +msgstr "Your name" + +msgid "Ihre Anfrage konnte leider nicht angenommen werden." +msgstr "Unfortunately your request could not be accepted." + +msgid "Ihre E-Mail-Adresse" +msgstr "Your email address" + +msgid "Ihre Nachricht" +msgstr "Your message" + +msgid "Ihre Ticketnummer: %s" +msgstr "Your ticket number: %s" + +msgid "In Bearbeitung" +msgstr "In progress" + +msgid "In DeskHub unter Verwalten → API-Schlüssel einen Schlüssel erstellen. Er braucht das Recht „Tickets anlegen“, für die Statusabfrage zusätzlich „Tickets lesen“. Sicherer: den Schlüssel in der wp-config.php eintragen: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" +msgstr "In DeskHub create a key under Manage → API keys. It needs the right “Create tickets”, and for the status page also “Read tickets”. Safer: enter the key in wp-config.php: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" + +msgid "Ja" +msgstr "Yes" + +msgid "Mindestzeit bis zum Absenden" +msgstr "Minimum time before submitting" + +msgid "Nein" +msgstr "No" + +msgid "Nummern in DeskHub" +msgstr "Numbers in DeskHub" + +msgid "Offen" +msgstr "Open" + +msgid "Optional: Nach dem Absenden erscheint ein Link „Stand abfragen“ zu dieser Seite (dort steht der Shortcode [deskhub_status])." +msgstr "Optional: after submitting, a link “Check status” to this page appears (it contains the shortcode [deskhub_status])." + +msgid "Priorität" +msgstr "Priority" + +msgid "Schlüssel entfernen" +msgstr "Remove key" + +msgid "Seite mit der Statusabfrage" +msgstr "Page with the status lookup" + +msgid "Sekunden" +msgstr "seconds" + +msgid "Sie" +msgstr "You" + +msgid "Sie haben in der letzten Stunde schon mehrere Tickets gesendet. Bitte versuchen Sie es später noch einmal." +msgstr "You have already sent several tickets in the last hour. Please try again later." + +msgid "So binden Sie es ein" +msgstr "How to add it" + +msgid "Spamschutz" +msgstr "Spam protection" + +msgid "Speichern" +msgstr "Save" + +msgid "Speichern und Verbindung testen" +msgstr "Save and test connection" + +msgid "Stand abfragen" +msgstr "Check status" + +msgid "Statusabfrage mit Ticketnummer und E-Mail-Adresse" +msgstr "status lookup with ticket number and email address" + +msgid "Support" +msgstr "Support" + +msgid "Text nach dem Absenden" +msgstr "Text after submitting" + +msgid "Ticket %s" +msgstr "Ticket %s" + +msgid "Ticket absenden" +msgstr "Submit ticket" + +msgid "Ticket-Voreinstellungen" +msgstr "Ticket defaults" + +msgid "Ticketnummer" +msgstr "Ticket number" + +msgid "Tickets je Besucher und Stunde (0 = unbegrenzt)" +msgstr "tickets per visitor and hour (0 = unlimited)" + +msgid "Verbindet WordPress mit Ihrem DeskHub-Helpdesk: Besucher legen Tickets über ein Formular an und können den Stand abfragen." +msgstr "Connects WordPress to your DeskHub helpdesk: visitors create tickets through a form and can check the status." + +msgid "Verbindung" +msgstr "Connection" + +msgid "Verbindung steht, der Schlüssel ist gültig und darf Tickets lesen." +msgstr "Connection works, the key is valid and may read tickets." + +msgid "Verbindung steht, der Schlüssel ist gültig. Er darf aber keine Tickets lesen: Die Statusabfrage funktioniert damit nicht (in DeskHub unter Verwalten → API-Schlüssel das Recht „Tickets lesen“ geben)." +msgstr "Connection works, the key is valid. However it may not read tickets, so the status lookup will not work (in DeskHub give it the right “Read tickets” under Manage → API keys)." + +msgid "Verbindung steht." +msgstr "Connection works." + +msgid "Vielen Dank! Wir melden uns so schnell wie möglich per E-Mail." +msgstr "Thank you! We will get back to you by email as soon as possible." + +msgid "Wartet auf Ihre Antwort" +msgstr "Waiting for your reply" + +msgid "Website-Schlüssel (Site Key)" +msgstr "Site key" + +msgid "Wenn ausgefüllt, muss der Besucher dieses Kästchen anhaken. Erlaubt sind Links (a), fett und kursiv." +msgstr "If filled in, the visitor has to tick this box. Links (a), bold and italic are allowed." + +msgid "Worum geht es?" +msgstr "What is it about?" + +msgid "Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden." +msgstr "Nothing was found for this combination of ticket number and email address." + +msgid "Zu viele Abfragen. Bitte versuchen Sie es in einigen Minuten noch einmal." +msgstr "Too many lookups. Please try again in a few minutes." + +msgid "Zuletzt geändert: %s" +msgstr "Last changed: %s" + +msgid "aus der wp-config.php" +msgstr "from wp-config.php" + +msgid "entfernen" +msgstr "remove" + +msgid "gespeichert, zum Ändern neuen Schlüssel eintragen" +msgstr "saved, enter a new key to change it" + +msgid "mit festen Voreinstellungen für dieses Formular" +msgstr "with fixed defaults for this form" + +msgid "Akzentfarbe" +msgstr "Accent color" + +msgid "Automatisch vom Theme übernehmen" +msgstr "Take automatically from the theme" + +msgid "Schrift, Hintergrund und Rahmen übernimmt das Formular immer vom Theme. Die Akzentfarbe (Knopf, Hervorhebung) wird automatisch aus Knöpfen und Links der Seite gelesen; hier lässt sie sich fest vorgeben." +msgstr "The form always takes font, background and border from the theme. The accent color (button, highlights) is read automatically from buttons and links of the page; you can set it here." + diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_GB.mo b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_GB.mo new file mode 100644 index 00000000..c6dcb65d Binary files /dev/null and b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_GB.mo differ diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_GB.po b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_GB.po new file mode 100644 index 00000000..d0e138d1 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_GB.po @@ -0,0 +1,376 @@ +# DeskHub Tickets: English translation (source language is German) +msgid "" +msgstr "" +"Project-Id-Version: DeskHub Tickets 1.0.0\n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "API-Schlüssel" +msgstr "API key" + +msgid "Abteilung" +msgstr "Department" + +msgid "Adresse des DeskHub-Servers" +msgstr "DeskHub server address" + +msgid "Anfrage ohne weitere Angaben" +msgstr "Request without further details" + +msgid "Beantwortet" +msgstr "Answered" + +msgid "Besucher wählen ein Thema aus (Bezeichnung:Nummer des Hilfethemas)" +msgstr "visitors choose a topic (label:number of the help topic)" + +msgid "Betreff" +msgstr "Subject" + +msgid "Bitte Server-Adresse und API-Schlüssel eintragen." +msgstr "Please enter the server address and API key." + +msgid "Bitte beschreiben Sie Ihr Anliegen." +msgstr "Please describe your request." + +msgid "Bitte bestätigen Sie „%s“." +msgstr "Please confirm “%s”." + +msgid "Bitte die Adresse Ihres DeskHub-Servers eintragen." +msgstr "Please enter the address of your DeskHub server." + +msgid "Bitte füllen Sie das Feld „%s“ aus." +msgstr "Please fill in the field “%s”." + +msgid "Bitte geben Sie Ihre Ticketnummer und die E-Mail-Adresse an, mit der Sie das Ticket gesendet haben." +msgstr "Please enter your ticket number and the email address you used to submit the ticket." + +msgid "Bitte geben Sie Ihren Namen an (höchstens 120 Zeichen)." +msgstr "Please enter your name (at most 120 characters)." + +msgid "Bitte geben Sie Ihren Namen an." +msgstr "Please enter your name." + +msgid "Bitte geben Sie bei „%s“ ein gültiges Datum an." +msgstr "Please enter a valid date for “%s”." + +msgid "Bitte geben Sie bei „%s“ eine Zahl an." +msgstr "Please enter a number for “%s”." + +msgid "Bitte geben Sie eine gültige E-Mail-Adresse an." +msgstr "Please enter a valid email address." + +msgid "Bitte geben Sie einen Betreff an (höchstens 255 Zeichen)." +msgstr "Please enter a subject (at most 255 characters)." + +msgid "Bitte geben Sie einen Betreff an." +msgstr "Please enter a subject." + +msgid "Bitte prüfen Sie Ihre Angaben." +msgstr "Please check your details." + +msgid "Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu." +msgstr "Please agree to the processing of your data." + +msgid "Bitte wählen Sie bei „%s“ eine der angebotenen Möglichkeiten." +msgstr "Please choose one of the offered options for “%s”." + +msgid "Bitte wählen Sie ein Thema aus." +msgstr "Please choose a topic." + +msgid "Bitte wählen …" +msgstr "Please choose …" + +msgid "Cloudflare Turnstile (optional)" +msgstr "Cloudflare Turnstile (optional)" + +msgid "Dafür fehlt die Berechtigung." +msgstr "You do not have permission to do this." + +msgid "Das Formular ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es noch einmal." +msgstr "The form has expired. Please reload the page and try again." + +msgid "Das ging etwas zu schnell. Bitte prüfen Sie Ihre Angaben und senden Sie noch einmal ab." +msgstr "That was a bit too fast. Please check your details and submit again." + +msgid "Dazu kommt ein unsichtbares Feld, das nur Roboter ausfüllen. Das Formular funktioniert auch mit Seiten-Cache." +msgstr "In addition there is an invisible field that only robots fill in. The form also works with page caching." + +msgid "Der DeskHub-Server ist nicht erreichbar." +msgstr "The DeskHub server cannot be reached." + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht (bitte DeskHub aktualisieren). Bis dahin wird das einfache Formular ohne eigene Felder gezeigt. (Diese Meldung sehen nur Administratoren.)" +msgstr "The DeskHub server does not know the form interface yet (please update DeskHub). Until then the simple form without custom fields is shown. (Only administrators see this message.)" + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht." +msgstr "The DeskHub server does not know the form interface yet." + +msgid "Der Schlüssel ist gültig, aber DeskHub lässt diese Adresse nicht zu. DeskHub meldet: %s" +msgstr "The key is valid, but DeskHub does not allow this address. DeskHub says: %s" + +msgid "Der Server antwortet mit einem Fehler (%d)." +msgstr "The server responded with an error (%d)." + +msgid "Der Server hat den API-Schlüssel abgelehnt. Bitte den Schlüssel prüfen: Er muss genau so eingetragen sein, aktiv sein und darf bei „Erlaubte IPs“ keine andere Adresse verlangen als die dieses WordPress-Servers (im Zweifel das Feld leer lassen)." +msgstr "The server rejected the API key. Please check the key: it must be entered exactly like this, be active, and its “Allowed IPs” must not demand a different address than this WordPress server’s (if in doubt, leave that field empty)." + +msgid "Der Server hat die Anfrage abgelehnt." +msgstr "The server rejected the request." + +msgid "Der Server hat eine unerwartete Antwort geliefert." +msgstr "The server returned an unexpected response." + +msgid "Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal." +msgstr "Support is currently not available. Please try again later." + +msgid "Der Test legt kein Ticket an." +msgstr "The test does not create a ticket." + +msgid "DeskHub Tickets" +msgstr "DeskHub Tickets" + +msgid "DeskHub Tickets ist noch nicht eingerichtet (Einstellungen → DeskHub Tickets). Diese Meldung sehen nur Administratoren." +msgstr "DeskHub Tickets is not set up yet (Settings → DeskHub Tickets). Only administrators see this message." + +msgid "DeskHub Tickets ist noch nicht eingerichtet: Bitte unter Einstellungen → DeskHub Tickets Server-Adresse und API-Schlüssel eintragen. (Diese Meldung sehen nur Administratoren.)" +msgstr "DeskHub Tickets is not set up yet: please enter the server address and API key under Settings → DeskHub Tickets. (Only administrators see this message.)" + +msgid "DeskHub entscheidet" +msgstr "DeskHub decides" + +msgid "Die Adresse, unter der Ihr DeskHub erreichbar ist. Nur https (http nur für lokale Tests)." +msgstr "The address where your DeskHub can be reached. https only (http only for local tests)." + +msgid "Die Formulare werden beim nächsten Aufruf neu aus DeskHub geladen." +msgstr "The forms will be reloaded from DeskHub on the next request." + +msgid "Die Nummern stehen in DeskHub bei der jeweiligen Abteilung, dem Hilfethema und der Priorität. 0 = DeskHub entscheidet (Standardabteilung, Regeln des Hilfethemas). Im Shortcode lassen sich die Werte je Formular überschreiben." +msgstr "The numbers are shown in DeskHub next to each department, help topic and priority. 0 = DeskHub decides (default department, rules of the help topic). The values can be overridden per form in the shortcode." + +msgid "Die Server-Adresse ist ungültig." +msgstr "The server address is invalid." + +msgid "Die Server-Adresse muss mit https beginnen." +msgstr "The server address must start with https." + +msgid "Die Shortcodes funktionieren in Seiten und Beiträgen (im Block-Editor mit dem Block „Shortcode“)." +msgstr "The shortcodes work in pages and posts (in the block editor use the “Shortcode” block)." + +msgid "Die Sicherheitsprüfung ist fehlgeschlagen. Bitte versuchen Sie es noch einmal." +msgstr "The security check failed. Please try again." + +msgid "Die Ticketnummer wird automatisch darunter angezeigt." +msgstr "The ticket number is shown below it automatically." + +msgid "E-Mail-Adresse" +msgstr "Email address" + +msgid "Eigene Felder und Texte kommen aus DeskHub und werden 5 Minuten gemerkt. Nach einer Änderung dort hier neu laden." +msgstr "Custom fields and texts come from DeskHub and are remembered for 5 minutes. After a change there, reload them here." + +msgid "Ein kostenloses Captcha ohne Rätsel. Beide Schlüssel eintragen, dann erscheint es im Formular. Dabei wird eine Verbindung zu Cloudflare aufgebaut: Das gehört in Ihre Datenschutzerklärung." +msgstr "A free captcha without puzzles. Enter both keys and it appears in the form. This connects to Cloudflare, so it belongs in your privacy policy." + +msgid "Eingebaut" +msgstr "Built in" + +msgid "Einstellungen" +msgstr "Settings" + +msgid "Einwilligung (Datenschutz)" +msgstr "Consent (data protection)" + +msgid "Formular" +msgstr "Form" + +msgid "Formular zum Anlegen eines Tickets" +msgstr "form for creating a ticket" + +msgid "Formulare neu laden" +msgstr "Reload forms" + +msgid "Geheimer Schlüssel (Secret Key)" +msgstr "Secret key" + +msgid "Geheimer Schlüssel gespeichert" +msgstr "Secret key saved" + +msgid "Gelöst" +msgstr "Resolved" + +msgid "Gerade sind sehr viele Anfragen eingegangen. Bitte versuchen Sie es in einer Minute noch einmal." +msgstr "A lot of requests have come in. Please try again in a minute." + +msgid "Geschlossen" +msgstr "Closed" + +msgid "Gesendet über das Formular auf %s" +msgstr "Sent via the form on %s" + +msgid "Hilfethema" +msgstr "Help topic" + +msgid "Höchstens" +msgstr "At most" + +msgid "Ich habe die Datenschutzerklärung gelesen und bin mit der Verarbeitung meiner Angaben einverstanden." +msgstr "I have read the privacy policy and agree to the processing of my data." + +msgid "Ihr Name" +msgstr "Your name" + +msgid "Ihre Anfrage konnte leider nicht angenommen werden." +msgstr "Unfortunately your request could not be accepted." + +msgid "Ihre E-Mail-Adresse" +msgstr "Your email address" + +msgid "Ihre Nachricht" +msgstr "Your message" + +msgid "Ihre Ticketnummer: %s" +msgstr "Your ticket number: %s" + +msgid "In Bearbeitung" +msgstr "In progress" + +msgid "In DeskHub unter Verwalten → API-Schlüssel einen Schlüssel erstellen. Er braucht das Recht „Tickets anlegen“, für die Statusabfrage zusätzlich „Tickets lesen“. Sicherer: den Schlüssel in der wp-config.php eintragen: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" +msgstr "In DeskHub create a key under Manage → API keys. It needs the right “Create tickets”, and for the status page also “Read tickets”. Safer: enter the key in wp-config.php: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" + +msgid "Ja" +msgstr "Yes" + +msgid "Mindestzeit bis zum Absenden" +msgstr "Minimum time before submitting" + +msgid "Nein" +msgstr "No" + +msgid "Nummern in DeskHub" +msgstr "Numbers in DeskHub" + +msgid "Offen" +msgstr "Open" + +msgid "Optional: Nach dem Absenden erscheint ein Link „Stand abfragen“ zu dieser Seite (dort steht der Shortcode [deskhub_status])." +msgstr "Optional: after submitting, a link “Check status” to this page appears (it contains the shortcode [deskhub_status])." + +msgid "Priorität" +msgstr "Priority" + +msgid "Schlüssel entfernen" +msgstr "Remove key" + +msgid "Seite mit der Statusabfrage" +msgstr "Page with the status lookup" + +msgid "Sekunden" +msgstr "seconds" + +msgid "Sie" +msgstr "You" + +msgid "Sie haben in der letzten Stunde schon mehrere Tickets gesendet. Bitte versuchen Sie es später noch einmal." +msgstr "You have already sent several tickets in the last hour. Please try again later." + +msgid "So binden Sie es ein" +msgstr "How to add it" + +msgid "Spamschutz" +msgstr "Spam protection" + +msgid "Speichern" +msgstr "Save" + +msgid "Speichern und Verbindung testen" +msgstr "Save and test connection" + +msgid "Stand abfragen" +msgstr "Check status" + +msgid "Statusabfrage mit Ticketnummer und E-Mail-Adresse" +msgstr "status lookup with ticket number and email address" + +msgid "Support" +msgstr "Support" + +msgid "Text nach dem Absenden" +msgstr "Text after submitting" + +msgid "Ticket %s" +msgstr "Ticket %s" + +msgid "Ticket absenden" +msgstr "Submit ticket" + +msgid "Ticket-Voreinstellungen" +msgstr "Ticket defaults" + +msgid "Ticketnummer" +msgstr "Ticket number" + +msgid "Tickets je Besucher und Stunde (0 = unbegrenzt)" +msgstr "tickets per visitor and hour (0 = unlimited)" + +msgid "Verbindet WordPress mit Ihrem DeskHub-Helpdesk: Besucher legen Tickets über ein Formular an und können den Stand abfragen." +msgstr "Connects WordPress to your DeskHub helpdesk: visitors create tickets through a form and can check the status." + +msgid "Verbindung" +msgstr "Connection" + +msgid "Verbindung steht, der Schlüssel ist gültig und darf Tickets lesen." +msgstr "Connection works, the key is valid and may read tickets." + +msgid "Verbindung steht, der Schlüssel ist gültig. Er darf aber keine Tickets lesen: Die Statusabfrage funktioniert damit nicht (in DeskHub unter Verwalten → API-Schlüssel das Recht „Tickets lesen“ geben)." +msgstr "Connection works, the key is valid. However it may not read tickets, so the status lookup will not work (in DeskHub give it the right “Read tickets” under Manage → API keys)." + +msgid "Verbindung steht." +msgstr "Connection works." + +msgid "Vielen Dank! Wir melden uns so schnell wie möglich per E-Mail." +msgstr "Thank you! We will get back to you by email as soon as possible." + +msgid "Wartet auf Ihre Antwort" +msgstr "Waiting for your reply" + +msgid "Website-Schlüssel (Site Key)" +msgstr "Site key" + +msgid "Wenn ausgefüllt, muss der Besucher dieses Kästchen anhaken. Erlaubt sind Links (a), fett und kursiv." +msgstr "If filled in, the visitor has to tick this box. Links (a), bold and italic are allowed." + +msgid "Worum geht es?" +msgstr "What is it about?" + +msgid "Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden." +msgstr "Nothing was found for this combination of ticket number and email address." + +msgid "Zu viele Abfragen. Bitte versuchen Sie es in einigen Minuten noch einmal." +msgstr "Too many lookups. Please try again in a few minutes." + +msgid "Zuletzt geändert: %s" +msgstr "Last changed: %s" + +msgid "aus der wp-config.php" +msgstr "from wp-config.php" + +msgid "entfernen" +msgstr "remove" + +msgid "gespeichert, zum Ändern neuen Schlüssel eintragen" +msgstr "saved, enter a new key to change it" + +msgid "mit festen Voreinstellungen für dieses Formular" +msgstr "with fixed defaults for this form" + +msgid "Akzentfarbe" +msgstr "Accent color" + +msgid "Automatisch vom Theme übernehmen" +msgstr "Take automatically from the theme" + +msgid "Schrift, Hintergrund und Rahmen übernimmt das Formular immer vom Theme. Die Akzentfarbe (Knopf, Hervorhebung) wird automatisch aus Knöpfen und Links der Seite gelesen; hier lässt sie sich fest vorgeben." +msgstr "The form always takes font, background and border from the theme. The accent color (button, highlights) is read automatically from buttons and links of the page; you can set it here." + diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_NZ.mo b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_NZ.mo new file mode 100644 index 00000000..d299c823 Binary files /dev/null and b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_NZ.mo differ diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_NZ.po b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_NZ.po new file mode 100644 index 00000000..304181f0 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_NZ.po @@ -0,0 +1,376 @@ +# DeskHub Tickets: English translation (source language is German) +msgid "" +msgstr "" +"Project-Id-Version: DeskHub Tickets 1.0.0\n" +"Language: en_NZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "API-Schlüssel" +msgstr "API key" + +msgid "Abteilung" +msgstr "Department" + +msgid "Adresse des DeskHub-Servers" +msgstr "DeskHub server address" + +msgid "Anfrage ohne weitere Angaben" +msgstr "Request without further details" + +msgid "Beantwortet" +msgstr "Answered" + +msgid "Besucher wählen ein Thema aus (Bezeichnung:Nummer des Hilfethemas)" +msgstr "visitors choose a topic (label:number of the help topic)" + +msgid "Betreff" +msgstr "Subject" + +msgid "Bitte Server-Adresse und API-Schlüssel eintragen." +msgstr "Please enter the server address and API key." + +msgid "Bitte beschreiben Sie Ihr Anliegen." +msgstr "Please describe your request." + +msgid "Bitte bestätigen Sie „%s“." +msgstr "Please confirm “%s”." + +msgid "Bitte die Adresse Ihres DeskHub-Servers eintragen." +msgstr "Please enter the address of your DeskHub server." + +msgid "Bitte füllen Sie das Feld „%s“ aus." +msgstr "Please fill in the field “%s”." + +msgid "Bitte geben Sie Ihre Ticketnummer und die E-Mail-Adresse an, mit der Sie das Ticket gesendet haben." +msgstr "Please enter your ticket number and the email address you used to submit the ticket." + +msgid "Bitte geben Sie Ihren Namen an (höchstens 120 Zeichen)." +msgstr "Please enter your name (at most 120 characters)." + +msgid "Bitte geben Sie Ihren Namen an." +msgstr "Please enter your name." + +msgid "Bitte geben Sie bei „%s“ ein gültiges Datum an." +msgstr "Please enter a valid date for “%s”." + +msgid "Bitte geben Sie bei „%s“ eine Zahl an." +msgstr "Please enter a number for “%s”." + +msgid "Bitte geben Sie eine gültige E-Mail-Adresse an." +msgstr "Please enter a valid email address." + +msgid "Bitte geben Sie einen Betreff an (höchstens 255 Zeichen)." +msgstr "Please enter a subject (at most 255 characters)." + +msgid "Bitte geben Sie einen Betreff an." +msgstr "Please enter a subject." + +msgid "Bitte prüfen Sie Ihre Angaben." +msgstr "Please check your details." + +msgid "Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu." +msgstr "Please agree to the processing of your data." + +msgid "Bitte wählen Sie bei „%s“ eine der angebotenen Möglichkeiten." +msgstr "Please choose one of the offered options for “%s”." + +msgid "Bitte wählen Sie ein Thema aus." +msgstr "Please choose a topic." + +msgid "Bitte wählen …" +msgstr "Please choose …" + +msgid "Cloudflare Turnstile (optional)" +msgstr "Cloudflare Turnstile (optional)" + +msgid "Dafür fehlt die Berechtigung." +msgstr "You do not have permission to do this." + +msgid "Das Formular ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es noch einmal." +msgstr "The form has expired. Please reload the page and try again." + +msgid "Das ging etwas zu schnell. Bitte prüfen Sie Ihre Angaben und senden Sie noch einmal ab." +msgstr "That was a bit too fast. Please check your details and submit again." + +msgid "Dazu kommt ein unsichtbares Feld, das nur Roboter ausfüllen. Das Formular funktioniert auch mit Seiten-Cache." +msgstr "In addition there is an invisible field that only robots fill in. The form also works with page caching." + +msgid "Der DeskHub-Server ist nicht erreichbar." +msgstr "The DeskHub server cannot be reached." + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht (bitte DeskHub aktualisieren). Bis dahin wird das einfache Formular ohne eigene Felder gezeigt. (Diese Meldung sehen nur Administratoren.)" +msgstr "The DeskHub server does not know the form interface yet (please update DeskHub). Until then the simple form without custom fields is shown. (Only administrators see this message.)" + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht." +msgstr "The DeskHub server does not know the form interface yet." + +msgid "Der Schlüssel ist gültig, aber DeskHub lässt diese Adresse nicht zu. DeskHub meldet: %s" +msgstr "The key is valid, but DeskHub does not allow this address. DeskHub says: %s" + +msgid "Der Server antwortet mit einem Fehler (%d)." +msgstr "The server responded with an error (%d)." + +msgid "Der Server hat den API-Schlüssel abgelehnt. Bitte den Schlüssel prüfen: Er muss genau so eingetragen sein, aktiv sein und darf bei „Erlaubte IPs“ keine andere Adresse verlangen als die dieses WordPress-Servers (im Zweifel das Feld leer lassen)." +msgstr "The server rejected the API key. Please check the key: it must be entered exactly like this, be active, and its “Allowed IPs” must not demand a different address than this WordPress server’s (if in doubt, leave that field empty)." + +msgid "Der Server hat die Anfrage abgelehnt." +msgstr "The server rejected the request." + +msgid "Der Server hat eine unerwartete Antwort geliefert." +msgstr "The server returned an unexpected response." + +msgid "Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal." +msgstr "Support is currently not available. Please try again later." + +msgid "Der Test legt kein Ticket an." +msgstr "The test does not create a ticket." + +msgid "DeskHub Tickets" +msgstr "DeskHub Tickets" + +msgid "DeskHub Tickets ist noch nicht eingerichtet (Einstellungen → DeskHub Tickets). Diese Meldung sehen nur Administratoren." +msgstr "DeskHub Tickets is not set up yet (Settings → DeskHub Tickets). Only administrators see this message." + +msgid "DeskHub Tickets ist noch nicht eingerichtet: Bitte unter Einstellungen → DeskHub Tickets Server-Adresse und API-Schlüssel eintragen. (Diese Meldung sehen nur Administratoren.)" +msgstr "DeskHub Tickets is not set up yet: please enter the server address and API key under Settings → DeskHub Tickets. (Only administrators see this message.)" + +msgid "DeskHub entscheidet" +msgstr "DeskHub decides" + +msgid "Die Adresse, unter der Ihr DeskHub erreichbar ist. Nur https (http nur für lokale Tests)." +msgstr "The address where your DeskHub can be reached. https only (http only for local tests)." + +msgid "Die Formulare werden beim nächsten Aufruf neu aus DeskHub geladen." +msgstr "The forms will be reloaded from DeskHub on the next request." + +msgid "Die Nummern stehen in DeskHub bei der jeweiligen Abteilung, dem Hilfethema und der Priorität. 0 = DeskHub entscheidet (Standardabteilung, Regeln des Hilfethemas). Im Shortcode lassen sich die Werte je Formular überschreiben." +msgstr "The numbers are shown in DeskHub next to each department, help topic and priority. 0 = DeskHub decides (default department, rules of the help topic). The values can be overridden per form in the shortcode." + +msgid "Die Server-Adresse ist ungültig." +msgstr "The server address is invalid." + +msgid "Die Server-Adresse muss mit https beginnen." +msgstr "The server address must start with https." + +msgid "Die Shortcodes funktionieren in Seiten und Beiträgen (im Block-Editor mit dem Block „Shortcode“)." +msgstr "The shortcodes work in pages and posts (in the block editor use the “Shortcode” block)." + +msgid "Die Sicherheitsprüfung ist fehlgeschlagen. Bitte versuchen Sie es noch einmal." +msgstr "The security check failed. Please try again." + +msgid "Die Ticketnummer wird automatisch darunter angezeigt." +msgstr "The ticket number is shown below it automatically." + +msgid "E-Mail-Adresse" +msgstr "Email address" + +msgid "Eigene Felder und Texte kommen aus DeskHub und werden 5 Minuten gemerkt. Nach einer Änderung dort hier neu laden." +msgstr "Custom fields and texts come from DeskHub and are remembered for 5 minutes. After a change there, reload them here." + +msgid "Ein kostenloses Captcha ohne Rätsel. Beide Schlüssel eintragen, dann erscheint es im Formular. Dabei wird eine Verbindung zu Cloudflare aufgebaut: Das gehört in Ihre Datenschutzerklärung." +msgstr "A free captcha without puzzles. Enter both keys and it appears in the form. This connects to Cloudflare, so it belongs in your privacy policy." + +msgid "Eingebaut" +msgstr "Built in" + +msgid "Einstellungen" +msgstr "Settings" + +msgid "Einwilligung (Datenschutz)" +msgstr "Consent (data protection)" + +msgid "Formular" +msgstr "Form" + +msgid "Formular zum Anlegen eines Tickets" +msgstr "form for creating a ticket" + +msgid "Formulare neu laden" +msgstr "Reload forms" + +msgid "Geheimer Schlüssel (Secret Key)" +msgstr "Secret key" + +msgid "Geheimer Schlüssel gespeichert" +msgstr "Secret key saved" + +msgid "Gelöst" +msgstr "Resolved" + +msgid "Gerade sind sehr viele Anfragen eingegangen. Bitte versuchen Sie es in einer Minute noch einmal." +msgstr "A lot of requests have come in. Please try again in a minute." + +msgid "Geschlossen" +msgstr "Closed" + +msgid "Gesendet über das Formular auf %s" +msgstr "Sent via the form on %s" + +msgid "Hilfethema" +msgstr "Help topic" + +msgid "Höchstens" +msgstr "At most" + +msgid "Ich habe die Datenschutzerklärung gelesen und bin mit der Verarbeitung meiner Angaben einverstanden." +msgstr "I have read the privacy policy and agree to the processing of my data." + +msgid "Ihr Name" +msgstr "Your name" + +msgid "Ihre Anfrage konnte leider nicht angenommen werden." +msgstr "Unfortunately your request could not be accepted." + +msgid "Ihre E-Mail-Adresse" +msgstr "Your email address" + +msgid "Ihre Nachricht" +msgstr "Your message" + +msgid "Ihre Ticketnummer: %s" +msgstr "Your ticket number: %s" + +msgid "In Bearbeitung" +msgstr "In progress" + +msgid "In DeskHub unter Verwalten → API-Schlüssel einen Schlüssel erstellen. Er braucht das Recht „Tickets anlegen“, für die Statusabfrage zusätzlich „Tickets lesen“. Sicherer: den Schlüssel in der wp-config.php eintragen: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" +msgstr "In DeskHub create a key under Manage → API keys. It needs the right “Create tickets”, and for the status page also “Read tickets”. Safer: enter the key in wp-config.php: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" + +msgid "Ja" +msgstr "Yes" + +msgid "Mindestzeit bis zum Absenden" +msgstr "Minimum time before submitting" + +msgid "Nein" +msgstr "No" + +msgid "Nummern in DeskHub" +msgstr "Numbers in DeskHub" + +msgid "Offen" +msgstr "Open" + +msgid "Optional: Nach dem Absenden erscheint ein Link „Stand abfragen“ zu dieser Seite (dort steht der Shortcode [deskhub_status])." +msgstr "Optional: after submitting, a link “Check status” to this page appears (it contains the shortcode [deskhub_status])." + +msgid "Priorität" +msgstr "Priority" + +msgid "Schlüssel entfernen" +msgstr "Remove key" + +msgid "Seite mit der Statusabfrage" +msgstr "Page with the status lookup" + +msgid "Sekunden" +msgstr "seconds" + +msgid "Sie" +msgstr "You" + +msgid "Sie haben in der letzten Stunde schon mehrere Tickets gesendet. Bitte versuchen Sie es später noch einmal." +msgstr "You have already sent several tickets in the last hour. Please try again later." + +msgid "So binden Sie es ein" +msgstr "How to add it" + +msgid "Spamschutz" +msgstr "Spam protection" + +msgid "Speichern" +msgstr "Save" + +msgid "Speichern und Verbindung testen" +msgstr "Save and test connection" + +msgid "Stand abfragen" +msgstr "Check status" + +msgid "Statusabfrage mit Ticketnummer und E-Mail-Adresse" +msgstr "status lookup with ticket number and email address" + +msgid "Support" +msgstr "Support" + +msgid "Text nach dem Absenden" +msgstr "Text after submitting" + +msgid "Ticket %s" +msgstr "Ticket %s" + +msgid "Ticket absenden" +msgstr "Submit ticket" + +msgid "Ticket-Voreinstellungen" +msgstr "Ticket defaults" + +msgid "Ticketnummer" +msgstr "Ticket number" + +msgid "Tickets je Besucher und Stunde (0 = unbegrenzt)" +msgstr "tickets per visitor and hour (0 = unlimited)" + +msgid "Verbindet WordPress mit Ihrem DeskHub-Helpdesk: Besucher legen Tickets über ein Formular an und können den Stand abfragen." +msgstr "Connects WordPress to your DeskHub helpdesk: visitors create tickets through a form and can check the status." + +msgid "Verbindung" +msgstr "Connection" + +msgid "Verbindung steht, der Schlüssel ist gültig und darf Tickets lesen." +msgstr "Connection works, the key is valid and may read tickets." + +msgid "Verbindung steht, der Schlüssel ist gültig. Er darf aber keine Tickets lesen: Die Statusabfrage funktioniert damit nicht (in DeskHub unter Verwalten → API-Schlüssel das Recht „Tickets lesen“ geben)." +msgstr "Connection works, the key is valid. However it may not read tickets, so the status lookup will not work (in DeskHub give it the right “Read tickets” under Manage → API keys)." + +msgid "Verbindung steht." +msgstr "Connection works." + +msgid "Vielen Dank! Wir melden uns so schnell wie möglich per E-Mail." +msgstr "Thank you! We will get back to you by email as soon as possible." + +msgid "Wartet auf Ihre Antwort" +msgstr "Waiting for your reply" + +msgid "Website-Schlüssel (Site Key)" +msgstr "Site key" + +msgid "Wenn ausgefüllt, muss der Besucher dieses Kästchen anhaken. Erlaubt sind Links (a), fett und kursiv." +msgstr "If filled in, the visitor has to tick this box. Links (a), bold and italic are allowed." + +msgid "Worum geht es?" +msgstr "What is it about?" + +msgid "Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden." +msgstr "Nothing was found for this combination of ticket number and email address." + +msgid "Zu viele Abfragen. Bitte versuchen Sie es in einigen Minuten noch einmal." +msgstr "Too many lookups. Please try again in a few minutes." + +msgid "Zuletzt geändert: %s" +msgstr "Last changed: %s" + +msgid "aus der wp-config.php" +msgstr "from wp-config.php" + +msgid "entfernen" +msgstr "remove" + +msgid "gespeichert, zum Ändern neuen Schlüssel eintragen" +msgstr "saved, enter a new key to change it" + +msgid "mit festen Voreinstellungen für dieses Formular" +msgstr "with fixed defaults for this form" + +msgid "Akzentfarbe" +msgstr "Accent color" + +msgid "Automatisch vom Theme übernehmen" +msgstr "Take automatically from the theme" + +msgid "Schrift, Hintergrund und Rahmen übernimmt das Formular immer vom Theme. Die Akzentfarbe (Knopf, Hervorhebung) wird automatisch aus Knöpfen und Links der Seite gelesen; hier lässt sie sich fest vorgeben." +msgstr "The form always takes font, background and border from the theme. The accent color (button, highlights) is read automatically from buttons and links of the page; you can set it here." + diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_US.mo b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_US.mo new file mode 100644 index 00000000..1e3a504d Binary files /dev/null and b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_US.mo differ diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_US.po b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_US.po new file mode 100644 index 00000000..e60a4e48 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_US.po @@ -0,0 +1,376 @@ +# DeskHub Tickets: English translation (source language is German) +msgid "" +msgstr "" +"Project-Id-Version: DeskHub Tickets 1.0.0\n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "API-Schlüssel" +msgstr "API key" + +msgid "Abteilung" +msgstr "Department" + +msgid "Adresse des DeskHub-Servers" +msgstr "DeskHub server address" + +msgid "Anfrage ohne weitere Angaben" +msgstr "Request without further details" + +msgid "Beantwortet" +msgstr "Answered" + +msgid "Besucher wählen ein Thema aus (Bezeichnung:Nummer des Hilfethemas)" +msgstr "visitors choose a topic (label:number of the help topic)" + +msgid "Betreff" +msgstr "Subject" + +msgid "Bitte Server-Adresse und API-Schlüssel eintragen." +msgstr "Please enter the server address and API key." + +msgid "Bitte beschreiben Sie Ihr Anliegen." +msgstr "Please describe your request." + +msgid "Bitte bestätigen Sie „%s“." +msgstr "Please confirm “%s”." + +msgid "Bitte die Adresse Ihres DeskHub-Servers eintragen." +msgstr "Please enter the address of your DeskHub server." + +msgid "Bitte füllen Sie das Feld „%s“ aus." +msgstr "Please fill in the field “%s”." + +msgid "Bitte geben Sie Ihre Ticketnummer und die E-Mail-Adresse an, mit der Sie das Ticket gesendet haben." +msgstr "Please enter your ticket number and the email address you used to submit the ticket." + +msgid "Bitte geben Sie Ihren Namen an (höchstens 120 Zeichen)." +msgstr "Please enter your name (at most 120 characters)." + +msgid "Bitte geben Sie Ihren Namen an." +msgstr "Please enter your name." + +msgid "Bitte geben Sie bei „%s“ ein gültiges Datum an." +msgstr "Please enter a valid date for “%s”." + +msgid "Bitte geben Sie bei „%s“ eine Zahl an." +msgstr "Please enter a number for “%s”." + +msgid "Bitte geben Sie eine gültige E-Mail-Adresse an." +msgstr "Please enter a valid email address." + +msgid "Bitte geben Sie einen Betreff an (höchstens 255 Zeichen)." +msgstr "Please enter a subject (at most 255 characters)." + +msgid "Bitte geben Sie einen Betreff an." +msgstr "Please enter a subject." + +msgid "Bitte prüfen Sie Ihre Angaben." +msgstr "Please check your details." + +msgid "Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu." +msgstr "Please agree to the processing of your data." + +msgid "Bitte wählen Sie bei „%s“ eine der angebotenen Möglichkeiten." +msgstr "Please choose one of the offered options for “%s”." + +msgid "Bitte wählen Sie ein Thema aus." +msgstr "Please choose a topic." + +msgid "Bitte wählen …" +msgstr "Please choose …" + +msgid "Cloudflare Turnstile (optional)" +msgstr "Cloudflare Turnstile (optional)" + +msgid "Dafür fehlt die Berechtigung." +msgstr "You do not have permission to do this." + +msgid "Das Formular ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es noch einmal." +msgstr "The form has expired. Please reload the page and try again." + +msgid "Das ging etwas zu schnell. Bitte prüfen Sie Ihre Angaben und senden Sie noch einmal ab." +msgstr "That was a bit too fast. Please check your details and submit again." + +msgid "Dazu kommt ein unsichtbares Feld, das nur Roboter ausfüllen. Das Formular funktioniert auch mit Seiten-Cache." +msgstr "In addition there is an invisible field that only robots fill in. The form also works with page caching." + +msgid "Der DeskHub-Server ist nicht erreichbar." +msgstr "The DeskHub server cannot be reached." + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht (bitte DeskHub aktualisieren). Bis dahin wird das einfache Formular ohne eigene Felder gezeigt. (Diese Meldung sehen nur Administratoren.)" +msgstr "The DeskHub server does not know the form interface yet (please update DeskHub). Until then the simple form without custom fields is shown. (Only administrators see this message.)" + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht." +msgstr "The DeskHub server does not know the form interface yet." + +msgid "Der Schlüssel ist gültig, aber DeskHub lässt diese Adresse nicht zu. DeskHub meldet: %s" +msgstr "The key is valid, but DeskHub does not allow this address. DeskHub says: %s" + +msgid "Der Server antwortet mit einem Fehler (%d)." +msgstr "The server responded with an error (%d)." + +msgid "Der Server hat den API-Schlüssel abgelehnt. Bitte den Schlüssel prüfen: Er muss genau so eingetragen sein, aktiv sein und darf bei „Erlaubte IPs“ keine andere Adresse verlangen als die dieses WordPress-Servers (im Zweifel das Feld leer lassen)." +msgstr "The server rejected the API key. Please check the key: it must be entered exactly like this, be active, and its “Allowed IPs” must not demand a different address than this WordPress server’s (if in doubt, leave that field empty)." + +msgid "Der Server hat die Anfrage abgelehnt." +msgstr "The server rejected the request." + +msgid "Der Server hat eine unerwartete Antwort geliefert." +msgstr "The server returned an unexpected response." + +msgid "Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal." +msgstr "Support is currently not available. Please try again later." + +msgid "Der Test legt kein Ticket an." +msgstr "The test does not create a ticket." + +msgid "DeskHub Tickets" +msgstr "DeskHub Tickets" + +msgid "DeskHub Tickets ist noch nicht eingerichtet (Einstellungen → DeskHub Tickets). Diese Meldung sehen nur Administratoren." +msgstr "DeskHub Tickets is not set up yet (Settings → DeskHub Tickets). Only administrators see this message." + +msgid "DeskHub Tickets ist noch nicht eingerichtet: Bitte unter Einstellungen → DeskHub Tickets Server-Adresse und API-Schlüssel eintragen. (Diese Meldung sehen nur Administratoren.)" +msgstr "DeskHub Tickets is not set up yet: please enter the server address and API key under Settings → DeskHub Tickets. (Only administrators see this message.)" + +msgid "DeskHub entscheidet" +msgstr "DeskHub decides" + +msgid "Die Adresse, unter der Ihr DeskHub erreichbar ist. Nur https (http nur für lokale Tests)." +msgstr "The address where your DeskHub can be reached. https only (http only for local tests)." + +msgid "Die Formulare werden beim nächsten Aufruf neu aus DeskHub geladen." +msgstr "The forms will be reloaded from DeskHub on the next request." + +msgid "Die Nummern stehen in DeskHub bei der jeweiligen Abteilung, dem Hilfethema und der Priorität. 0 = DeskHub entscheidet (Standardabteilung, Regeln des Hilfethemas). Im Shortcode lassen sich die Werte je Formular überschreiben." +msgstr "The numbers are shown in DeskHub next to each department, help topic and priority. 0 = DeskHub decides (default department, rules of the help topic). The values can be overridden per form in the shortcode." + +msgid "Die Server-Adresse ist ungültig." +msgstr "The server address is invalid." + +msgid "Die Server-Adresse muss mit https beginnen." +msgstr "The server address must start with https." + +msgid "Die Shortcodes funktionieren in Seiten und Beiträgen (im Block-Editor mit dem Block „Shortcode“)." +msgstr "The shortcodes work in pages and posts (in the block editor use the “Shortcode” block)." + +msgid "Die Sicherheitsprüfung ist fehlgeschlagen. Bitte versuchen Sie es noch einmal." +msgstr "The security check failed. Please try again." + +msgid "Die Ticketnummer wird automatisch darunter angezeigt." +msgstr "The ticket number is shown below it automatically." + +msgid "E-Mail-Adresse" +msgstr "Email address" + +msgid "Eigene Felder und Texte kommen aus DeskHub und werden 5 Minuten gemerkt. Nach einer Änderung dort hier neu laden." +msgstr "Custom fields and texts come from DeskHub and are remembered for 5 minutes. After a change there, reload them here." + +msgid "Ein kostenloses Captcha ohne Rätsel. Beide Schlüssel eintragen, dann erscheint es im Formular. Dabei wird eine Verbindung zu Cloudflare aufgebaut: Das gehört in Ihre Datenschutzerklärung." +msgstr "A free captcha without puzzles. Enter both keys and it appears in the form. This connects to Cloudflare, so it belongs in your privacy policy." + +msgid "Eingebaut" +msgstr "Built in" + +msgid "Einstellungen" +msgstr "Settings" + +msgid "Einwilligung (Datenschutz)" +msgstr "Consent (data protection)" + +msgid "Formular" +msgstr "Form" + +msgid "Formular zum Anlegen eines Tickets" +msgstr "form for creating a ticket" + +msgid "Formulare neu laden" +msgstr "Reload forms" + +msgid "Geheimer Schlüssel (Secret Key)" +msgstr "Secret key" + +msgid "Geheimer Schlüssel gespeichert" +msgstr "Secret key saved" + +msgid "Gelöst" +msgstr "Resolved" + +msgid "Gerade sind sehr viele Anfragen eingegangen. Bitte versuchen Sie es in einer Minute noch einmal." +msgstr "A lot of requests have come in. Please try again in a minute." + +msgid "Geschlossen" +msgstr "Closed" + +msgid "Gesendet über das Formular auf %s" +msgstr "Sent via the form on %s" + +msgid "Hilfethema" +msgstr "Help topic" + +msgid "Höchstens" +msgstr "At most" + +msgid "Ich habe die Datenschutzerklärung gelesen und bin mit der Verarbeitung meiner Angaben einverstanden." +msgstr "I have read the privacy policy and agree to the processing of my data." + +msgid "Ihr Name" +msgstr "Your name" + +msgid "Ihre Anfrage konnte leider nicht angenommen werden." +msgstr "Unfortunately your request could not be accepted." + +msgid "Ihre E-Mail-Adresse" +msgstr "Your email address" + +msgid "Ihre Nachricht" +msgstr "Your message" + +msgid "Ihre Ticketnummer: %s" +msgstr "Your ticket number: %s" + +msgid "In Bearbeitung" +msgstr "In progress" + +msgid "In DeskHub unter Verwalten → API-Schlüssel einen Schlüssel erstellen. Er braucht das Recht „Tickets anlegen“, für die Statusabfrage zusätzlich „Tickets lesen“. Sicherer: den Schlüssel in der wp-config.php eintragen: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" +msgstr "In DeskHub create a key under Manage → API keys. It needs the right “Create tickets”, and for the status page also “Read tickets”. Safer: enter the key in wp-config.php: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" + +msgid "Ja" +msgstr "Yes" + +msgid "Mindestzeit bis zum Absenden" +msgstr "Minimum time before submitting" + +msgid "Nein" +msgstr "No" + +msgid "Nummern in DeskHub" +msgstr "Numbers in DeskHub" + +msgid "Offen" +msgstr "Open" + +msgid "Optional: Nach dem Absenden erscheint ein Link „Stand abfragen“ zu dieser Seite (dort steht der Shortcode [deskhub_status])." +msgstr "Optional: after submitting, a link “Check status” to this page appears (it contains the shortcode [deskhub_status])." + +msgid "Priorität" +msgstr "Priority" + +msgid "Schlüssel entfernen" +msgstr "Remove key" + +msgid "Seite mit der Statusabfrage" +msgstr "Page with the status lookup" + +msgid "Sekunden" +msgstr "seconds" + +msgid "Sie" +msgstr "You" + +msgid "Sie haben in der letzten Stunde schon mehrere Tickets gesendet. Bitte versuchen Sie es später noch einmal." +msgstr "You have already sent several tickets in the last hour. Please try again later." + +msgid "So binden Sie es ein" +msgstr "How to add it" + +msgid "Spamschutz" +msgstr "Spam protection" + +msgid "Speichern" +msgstr "Save" + +msgid "Speichern und Verbindung testen" +msgstr "Save and test connection" + +msgid "Stand abfragen" +msgstr "Check status" + +msgid "Statusabfrage mit Ticketnummer und E-Mail-Adresse" +msgstr "status lookup with ticket number and email address" + +msgid "Support" +msgstr "Support" + +msgid "Text nach dem Absenden" +msgstr "Text after submitting" + +msgid "Ticket %s" +msgstr "Ticket %s" + +msgid "Ticket absenden" +msgstr "Submit ticket" + +msgid "Ticket-Voreinstellungen" +msgstr "Ticket defaults" + +msgid "Ticketnummer" +msgstr "Ticket number" + +msgid "Tickets je Besucher und Stunde (0 = unbegrenzt)" +msgstr "tickets per visitor and hour (0 = unlimited)" + +msgid "Verbindet WordPress mit Ihrem DeskHub-Helpdesk: Besucher legen Tickets über ein Formular an und können den Stand abfragen." +msgstr "Connects WordPress to your DeskHub helpdesk: visitors create tickets through a form and can check the status." + +msgid "Verbindung" +msgstr "Connection" + +msgid "Verbindung steht, der Schlüssel ist gültig und darf Tickets lesen." +msgstr "Connection works, the key is valid and may read tickets." + +msgid "Verbindung steht, der Schlüssel ist gültig. Er darf aber keine Tickets lesen: Die Statusabfrage funktioniert damit nicht (in DeskHub unter Verwalten → API-Schlüssel das Recht „Tickets lesen“ geben)." +msgstr "Connection works, the key is valid. However it may not read tickets, so the status lookup will not work (in DeskHub give it the right “Read tickets” under Manage → API keys)." + +msgid "Verbindung steht." +msgstr "Connection works." + +msgid "Vielen Dank! Wir melden uns so schnell wie möglich per E-Mail." +msgstr "Thank you! We will get back to you by email as soon as possible." + +msgid "Wartet auf Ihre Antwort" +msgstr "Waiting for your reply" + +msgid "Website-Schlüssel (Site Key)" +msgstr "Site key" + +msgid "Wenn ausgefüllt, muss der Besucher dieses Kästchen anhaken. Erlaubt sind Links (a), fett und kursiv." +msgstr "If filled in, the visitor has to tick this box. Links (a), bold and italic are allowed." + +msgid "Worum geht es?" +msgstr "What is it about?" + +msgid "Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden." +msgstr "Nothing was found for this combination of ticket number and email address." + +msgid "Zu viele Abfragen. Bitte versuchen Sie es in einigen Minuten noch einmal." +msgstr "Too many lookups. Please try again in a few minutes." + +msgid "Zuletzt geändert: %s" +msgstr "Last changed: %s" + +msgid "aus der wp-config.php" +msgstr "from wp-config.php" + +msgid "entfernen" +msgstr "remove" + +msgid "gespeichert, zum Ändern neuen Schlüssel eintragen" +msgstr "saved, enter a new key to change it" + +msgid "mit festen Voreinstellungen für dieses Formular" +msgstr "with fixed defaults for this form" + +msgid "Akzentfarbe" +msgstr "Accent color" + +msgid "Automatisch vom Theme übernehmen" +msgstr "Take automatically from the theme" + +msgid "Schrift, Hintergrund und Rahmen übernimmt das Formular immer vom Theme. Die Akzentfarbe (Knopf, Hervorhebung) wird automatisch aus Knöpfen und Links der Seite gelesen; hier lässt sie sich fest vorgeben." +msgstr "The form always takes font, background and border from the theme. The accent color (button, highlights) is read automatically from buttons and links of the page; you can set it here." + diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_ZA.mo b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_ZA.mo new file mode 100644 index 00000000..96b3bc8e Binary files /dev/null and b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_ZA.mo differ diff --git a/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_ZA.po b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_ZA.po new file mode 100644 index 00000000..756222fc --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/languages/deskhub-tickets-en_ZA.po @@ -0,0 +1,376 @@ +# DeskHub Tickets: English translation (source language is German) +msgid "" +msgstr "" +"Project-Id-Version: DeskHub Tickets 1.0.0\n" +"Language: en_ZA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "API-Schlüssel" +msgstr "API key" + +msgid "Abteilung" +msgstr "Department" + +msgid "Adresse des DeskHub-Servers" +msgstr "DeskHub server address" + +msgid "Anfrage ohne weitere Angaben" +msgstr "Request without further details" + +msgid "Beantwortet" +msgstr "Answered" + +msgid "Besucher wählen ein Thema aus (Bezeichnung:Nummer des Hilfethemas)" +msgstr "visitors choose a topic (label:number of the help topic)" + +msgid "Betreff" +msgstr "Subject" + +msgid "Bitte Server-Adresse und API-Schlüssel eintragen." +msgstr "Please enter the server address and API key." + +msgid "Bitte beschreiben Sie Ihr Anliegen." +msgstr "Please describe your request." + +msgid "Bitte bestätigen Sie „%s“." +msgstr "Please confirm “%s”." + +msgid "Bitte die Adresse Ihres DeskHub-Servers eintragen." +msgstr "Please enter the address of your DeskHub server." + +msgid "Bitte füllen Sie das Feld „%s“ aus." +msgstr "Please fill in the field “%s”." + +msgid "Bitte geben Sie Ihre Ticketnummer und die E-Mail-Adresse an, mit der Sie das Ticket gesendet haben." +msgstr "Please enter your ticket number and the email address you used to submit the ticket." + +msgid "Bitte geben Sie Ihren Namen an (höchstens 120 Zeichen)." +msgstr "Please enter your name (at most 120 characters)." + +msgid "Bitte geben Sie Ihren Namen an." +msgstr "Please enter your name." + +msgid "Bitte geben Sie bei „%s“ ein gültiges Datum an." +msgstr "Please enter a valid date for “%s”." + +msgid "Bitte geben Sie bei „%s“ eine Zahl an." +msgstr "Please enter a number for “%s”." + +msgid "Bitte geben Sie eine gültige E-Mail-Adresse an." +msgstr "Please enter a valid email address." + +msgid "Bitte geben Sie einen Betreff an (höchstens 255 Zeichen)." +msgstr "Please enter a subject (at most 255 characters)." + +msgid "Bitte geben Sie einen Betreff an." +msgstr "Please enter a subject." + +msgid "Bitte prüfen Sie Ihre Angaben." +msgstr "Please check your details." + +msgid "Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu." +msgstr "Please agree to the processing of your data." + +msgid "Bitte wählen Sie bei „%s“ eine der angebotenen Möglichkeiten." +msgstr "Please choose one of the offered options for “%s”." + +msgid "Bitte wählen Sie ein Thema aus." +msgstr "Please choose a topic." + +msgid "Bitte wählen …" +msgstr "Please choose …" + +msgid "Cloudflare Turnstile (optional)" +msgstr "Cloudflare Turnstile (optional)" + +msgid "Dafür fehlt die Berechtigung." +msgstr "You do not have permission to do this." + +msgid "Das Formular ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es noch einmal." +msgstr "The form has expired. Please reload the page and try again." + +msgid "Das ging etwas zu schnell. Bitte prüfen Sie Ihre Angaben und senden Sie noch einmal ab." +msgstr "That was a bit too fast. Please check your details and submit again." + +msgid "Dazu kommt ein unsichtbares Feld, das nur Roboter ausfüllen. Das Formular funktioniert auch mit Seiten-Cache." +msgstr "In addition there is an invisible field that only robots fill in. The form also works with page caching." + +msgid "Der DeskHub-Server ist nicht erreichbar." +msgstr "The DeskHub server cannot be reached." + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht (bitte DeskHub aktualisieren). Bis dahin wird das einfache Formular ohne eigene Felder gezeigt. (Diese Meldung sehen nur Administratoren.)" +msgstr "The DeskHub server does not know the form interface yet (please update DeskHub). Until then the simple form without custom fields is shown. (Only administrators see this message.)" + +msgid "Der DeskHub-Server kennt die Formular-Schnittstelle noch nicht." +msgstr "The DeskHub server does not know the form interface yet." + +msgid "Der Schlüssel ist gültig, aber DeskHub lässt diese Adresse nicht zu. DeskHub meldet: %s" +msgstr "The key is valid, but DeskHub does not allow this address. DeskHub says: %s" + +msgid "Der Server antwortet mit einem Fehler (%d)." +msgstr "The server responded with an error (%d)." + +msgid "Der Server hat den API-Schlüssel abgelehnt. Bitte den Schlüssel prüfen: Er muss genau so eingetragen sein, aktiv sein und darf bei „Erlaubte IPs“ keine andere Adresse verlangen als die dieses WordPress-Servers (im Zweifel das Feld leer lassen)." +msgstr "The server rejected the API key. Please check the key: it must be entered exactly like this, be active, and its “Allowed IPs” must not demand a different address than this WordPress server’s (if in doubt, leave that field empty)." + +msgid "Der Server hat die Anfrage abgelehnt." +msgstr "The server rejected the request." + +msgid "Der Server hat eine unerwartete Antwort geliefert." +msgstr "The server returned an unexpected response." + +msgid "Der Support ist gerade nicht erreichbar. Bitte versuchen Sie es später noch einmal." +msgstr "Support is currently not available. Please try again later." + +msgid "Der Test legt kein Ticket an." +msgstr "The test does not create a ticket." + +msgid "DeskHub Tickets" +msgstr "DeskHub Tickets" + +msgid "DeskHub Tickets ist noch nicht eingerichtet (Einstellungen → DeskHub Tickets). Diese Meldung sehen nur Administratoren." +msgstr "DeskHub Tickets is not set up yet (Settings → DeskHub Tickets). Only administrators see this message." + +msgid "DeskHub Tickets ist noch nicht eingerichtet: Bitte unter Einstellungen → DeskHub Tickets Server-Adresse und API-Schlüssel eintragen. (Diese Meldung sehen nur Administratoren.)" +msgstr "DeskHub Tickets is not set up yet: please enter the server address and API key under Settings → DeskHub Tickets. (Only administrators see this message.)" + +msgid "DeskHub entscheidet" +msgstr "DeskHub decides" + +msgid "Die Adresse, unter der Ihr DeskHub erreichbar ist. Nur https (http nur für lokale Tests)." +msgstr "The address where your DeskHub can be reached. https only (http only for local tests)." + +msgid "Die Formulare werden beim nächsten Aufruf neu aus DeskHub geladen." +msgstr "The forms will be reloaded from DeskHub on the next request." + +msgid "Die Nummern stehen in DeskHub bei der jeweiligen Abteilung, dem Hilfethema und der Priorität. 0 = DeskHub entscheidet (Standardabteilung, Regeln des Hilfethemas). Im Shortcode lassen sich die Werte je Formular überschreiben." +msgstr "The numbers are shown in DeskHub next to each department, help topic and priority. 0 = DeskHub decides (default department, rules of the help topic). The values can be overridden per form in the shortcode." + +msgid "Die Server-Adresse ist ungültig." +msgstr "The server address is invalid." + +msgid "Die Server-Adresse muss mit https beginnen." +msgstr "The server address must start with https." + +msgid "Die Shortcodes funktionieren in Seiten und Beiträgen (im Block-Editor mit dem Block „Shortcode“)." +msgstr "The shortcodes work in pages and posts (in the block editor use the “Shortcode” block)." + +msgid "Die Sicherheitsprüfung ist fehlgeschlagen. Bitte versuchen Sie es noch einmal." +msgstr "The security check failed. Please try again." + +msgid "Die Ticketnummer wird automatisch darunter angezeigt." +msgstr "The ticket number is shown below it automatically." + +msgid "E-Mail-Adresse" +msgstr "Email address" + +msgid "Eigene Felder und Texte kommen aus DeskHub und werden 5 Minuten gemerkt. Nach einer Änderung dort hier neu laden." +msgstr "Custom fields and texts come from DeskHub and are remembered for 5 minutes. After a change there, reload them here." + +msgid "Ein kostenloses Captcha ohne Rätsel. Beide Schlüssel eintragen, dann erscheint es im Formular. Dabei wird eine Verbindung zu Cloudflare aufgebaut: Das gehört in Ihre Datenschutzerklärung." +msgstr "A free captcha without puzzles. Enter both keys and it appears in the form. This connects to Cloudflare, so it belongs in your privacy policy." + +msgid "Eingebaut" +msgstr "Built in" + +msgid "Einstellungen" +msgstr "Settings" + +msgid "Einwilligung (Datenschutz)" +msgstr "Consent (data protection)" + +msgid "Formular" +msgstr "Form" + +msgid "Formular zum Anlegen eines Tickets" +msgstr "form for creating a ticket" + +msgid "Formulare neu laden" +msgstr "Reload forms" + +msgid "Geheimer Schlüssel (Secret Key)" +msgstr "Secret key" + +msgid "Geheimer Schlüssel gespeichert" +msgstr "Secret key saved" + +msgid "Gelöst" +msgstr "Resolved" + +msgid "Gerade sind sehr viele Anfragen eingegangen. Bitte versuchen Sie es in einer Minute noch einmal." +msgstr "A lot of requests have come in. Please try again in a minute." + +msgid "Geschlossen" +msgstr "Closed" + +msgid "Gesendet über das Formular auf %s" +msgstr "Sent via the form on %s" + +msgid "Hilfethema" +msgstr "Help topic" + +msgid "Höchstens" +msgstr "At most" + +msgid "Ich habe die Datenschutzerklärung gelesen und bin mit der Verarbeitung meiner Angaben einverstanden." +msgstr "I have read the privacy policy and agree to the processing of my data." + +msgid "Ihr Name" +msgstr "Your name" + +msgid "Ihre Anfrage konnte leider nicht angenommen werden." +msgstr "Unfortunately your request could not be accepted." + +msgid "Ihre E-Mail-Adresse" +msgstr "Your email address" + +msgid "Ihre Nachricht" +msgstr "Your message" + +msgid "Ihre Ticketnummer: %s" +msgstr "Your ticket number: %s" + +msgid "In Bearbeitung" +msgstr "In progress" + +msgid "In DeskHub unter Verwalten → API-Schlüssel einen Schlüssel erstellen. Er braucht das Recht „Tickets anlegen“, für die Statusabfrage zusätzlich „Tickets lesen“. Sicherer: den Schlüssel in der wp-config.php eintragen: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" +msgstr "In DeskHub create a key under Manage → API keys. It needs the right “Create tickets”, and for the status page also “Read tickets”. Safer: enter the key in wp-config.php: define('DESKHUB_TICKETS_API_KEY', 'dh_…');" + +msgid "Ja" +msgstr "Yes" + +msgid "Mindestzeit bis zum Absenden" +msgstr "Minimum time before submitting" + +msgid "Nein" +msgstr "No" + +msgid "Nummern in DeskHub" +msgstr "Numbers in DeskHub" + +msgid "Offen" +msgstr "Open" + +msgid "Optional: Nach dem Absenden erscheint ein Link „Stand abfragen“ zu dieser Seite (dort steht der Shortcode [deskhub_status])." +msgstr "Optional: after submitting, a link “Check status” to this page appears (it contains the shortcode [deskhub_status])." + +msgid "Priorität" +msgstr "Priority" + +msgid "Schlüssel entfernen" +msgstr "Remove key" + +msgid "Seite mit der Statusabfrage" +msgstr "Page with the status lookup" + +msgid "Sekunden" +msgstr "seconds" + +msgid "Sie" +msgstr "You" + +msgid "Sie haben in der letzten Stunde schon mehrere Tickets gesendet. Bitte versuchen Sie es später noch einmal." +msgstr "You have already sent several tickets in the last hour. Please try again later." + +msgid "So binden Sie es ein" +msgstr "How to add it" + +msgid "Spamschutz" +msgstr "Spam protection" + +msgid "Speichern" +msgstr "Save" + +msgid "Speichern und Verbindung testen" +msgstr "Save and test connection" + +msgid "Stand abfragen" +msgstr "Check status" + +msgid "Statusabfrage mit Ticketnummer und E-Mail-Adresse" +msgstr "status lookup with ticket number and email address" + +msgid "Support" +msgstr "Support" + +msgid "Text nach dem Absenden" +msgstr "Text after submitting" + +msgid "Ticket %s" +msgstr "Ticket %s" + +msgid "Ticket absenden" +msgstr "Submit ticket" + +msgid "Ticket-Voreinstellungen" +msgstr "Ticket defaults" + +msgid "Ticketnummer" +msgstr "Ticket number" + +msgid "Tickets je Besucher und Stunde (0 = unbegrenzt)" +msgstr "tickets per visitor and hour (0 = unlimited)" + +msgid "Verbindet WordPress mit Ihrem DeskHub-Helpdesk: Besucher legen Tickets über ein Formular an und können den Stand abfragen." +msgstr "Connects WordPress to your DeskHub helpdesk: visitors create tickets through a form and can check the status." + +msgid "Verbindung" +msgstr "Connection" + +msgid "Verbindung steht, der Schlüssel ist gültig und darf Tickets lesen." +msgstr "Connection works, the key is valid and may read tickets." + +msgid "Verbindung steht, der Schlüssel ist gültig. Er darf aber keine Tickets lesen: Die Statusabfrage funktioniert damit nicht (in DeskHub unter Verwalten → API-Schlüssel das Recht „Tickets lesen“ geben)." +msgstr "Connection works, the key is valid. However it may not read tickets, so the status lookup will not work (in DeskHub give it the right “Read tickets” under Manage → API keys)." + +msgid "Verbindung steht." +msgstr "Connection works." + +msgid "Vielen Dank! Wir melden uns so schnell wie möglich per E-Mail." +msgstr "Thank you! We will get back to you by email as soon as possible." + +msgid "Wartet auf Ihre Antwort" +msgstr "Waiting for your reply" + +msgid "Website-Schlüssel (Site Key)" +msgstr "Site key" + +msgid "Wenn ausgefüllt, muss der Besucher dieses Kästchen anhaken. Erlaubt sind Links (a), fett und kursiv." +msgstr "If filled in, the visitor has to tick this box. Links (a), bold and italic are allowed." + +msgid "Worum geht es?" +msgstr "What is it about?" + +msgid "Zu dieser Kombination aus Ticketnummer und E-Mail-Adresse wurde nichts gefunden." +msgstr "Nothing was found for this combination of ticket number and email address." + +msgid "Zu viele Abfragen. Bitte versuchen Sie es in einigen Minuten noch einmal." +msgstr "Too many lookups. Please try again in a few minutes." + +msgid "Zuletzt geändert: %s" +msgstr "Last changed: %s" + +msgid "aus der wp-config.php" +msgstr "from wp-config.php" + +msgid "entfernen" +msgstr "remove" + +msgid "gespeichert, zum Ändern neuen Schlüssel eintragen" +msgstr "saved, enter a new key to change it" + +msgid "mit festen Voreinstellungen für dieses Formular" +msgstr "with fixed defaults for this form" + +msgid "Akzentfarbe" +msgstr "Accent color" + +msgid "Automatisch vom Theme übernehmen" +msgstr "Take automatically from the theme" + +msgid "Schrift, Hintergrund und Rahmen übernimmt das Formular immer vom Theme. Die Akzentfarbe (Knopf, Hervorhebung) wird automatisch aus Knöpfen und Links der Seite gelesen; hier lässt sie sich fest vorgeben." +msgstr "The form always takes font, background and border from the theme. The accent color (button, highlights) is read automatically from buttons and links of the page; you can set it here." + diff --git a/wordpress-plugin/deskhub-tickets/readme.txt b/wordpress-plugin/deskhub-tickets/readme.txt new file mode 100644 index 00000000..b2427d14 --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/readme.txt @@ -0,0 +1,68 @@ +=== DeskHub Tickets === +Contributors: deskhub +Tags: helpdesk, tickets, support, contact form +Requires at least: 5.8 +Tested up to: 6.6 +Requires PHP: 7.4 +Stable tag: 1.2.2 +License: GPLv2 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html + +Create support tickets in your DeskHub helpdesk from WordPress and let visitors check the status of their ticket. + +== Description == + +DeskHub Tickets connects WordPress to a self-hosted DeskHub helpdesk through its public API. + +* `[deskhub_ticket]` shows a form that creates a ticket in DeskHub. +* `[deskhub_status]` lets a visitor look up the status and public history of a ticket with the ticket number and email address. +* The API key stays on the server and is never sent to the visitor's browser. +* Spam protection: hidden trap field, minimum time, limit per visitor and hour, optional Cloudflare Turnstile. Works with page caches. +* Optional consent checkbox for data protection. + +German is the source language of the plugin. An English translation is included. + +== Installation == + +1. Upload the plugin and activate it. +2. In DeskHub create an API key (Manage -> API keys) with the right "Create tickets" (and "Read tickets" for the status page). +3. In WordPress open Settings -> DeskHub Tickets, enter the server address and the key, save and click "Test connection". +4. Add `[deskhub_ticket]` to a page. + +== Frequently Asked Questions == + += Can visitors attach files? = + +Not in this version: the DeskHub public API accepts text only. + += Where is the API key stored? = + +In the WordPress options table. For more safety define `DESKHUB_TICKETS_API_KEY` in wp-config.php; the field in the settings is then locked. + +== Changelog == + += 1.2.2 = +* Fix: only the button takes the accent color; fields and borders are mixed from the page background and text color and are no longer tinted by theme or accent colors. Real theme buttons are preferred when detecting the accent. + += 1.2.1 = +* Accent color detection looks at all buttons and links, the theme.json button color and the color variables of common themes and page builders. + += 1.2.0 = +* The form takes its colors from the theme (text, borders, field background, dark themes); the accent color is read from the buttons and links of the page or set in the settings. + += 1.1.3 = +* Fix: fields in one row start at the same height, also when only some labels have a required-star. + += 1.1.2 = +* Fix: labels, text fields and drop-downs have the same font, size and height, even if the theme styles them differently. + += 1.1.1 = +* Fix: headings of radio fields and sections are no longer enlarged by themes. +* "Save and test connection" tests the values just entered; clearer messages (IP address, connection errors). + += 1.1.0 = +* The form of the help topic is taken from DeskHub: custom fields, texts, required fields and the department form. Needs DeskHub 1.0.64 or newer for custom fields. +* Help topic can be chosen in the settings; "Reload forms" button. + += 1.0.0 = +* First version. diff --git a/wordpress-plugin/deskhub-tickets/uninstall.php b/wordpress-plugin/deskhub-tickets/uninstall.php new file mode 100644 index 00000000..ace411cd --- /dev/null +++ b/wordpress-plugin/deskhub-tickets/uninstall.php @@ -0,0 +1,20 @@ +esc_like('_transient_dht_') . '%'; + $like2 = $wpdb->esc_like('_transient_timeout_dht_') . '%'; + $like3 = $wpdb->esc_like('_transient_deskhub_tickets_') . '%'; + $like4 = $wpdb->esc_like('_transient_timeout_deskhub_tickets_') . '%'; + $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s", $like1, $like2, $like3, $like4)); +} diff --git a/wordpress-plugin/tests/run.php b/wordpress-plugin/tests/run.php new file mode 100644 index 00000000..489da26d --- /dev/null +++ b/wordpress-plugin/tests/run.php @@ -0,0 +1,518 @@ + 'd.m.Y', 'time_format' => 'H:i']; + $GLOBALS['wp_transients'] = []; + $GLOBALS['wp_http_log'] = []; + $GLOBALS['wp_http_handler'] = null; + $GLOBALS['wp_user'] = ['id' => 0, 'caps' => [], 'name' => '', 'email' => '']; + $GLOBALS['wp_settings_errors'] = []; + $GLOBALS['wp_enqueued'] = []; + $_POST = []; $_GET = []; $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REMOTE_ADDR'] = '203.0.113.5'; + if ($opts !== null) { + $GLOBALS['wp_options']['deskhub_tickets_options'] = array_merge([ + 'server_url' => 'https://support.example.de', 'api_key' => 'dh_geheimer_schluessel_123', 'min_seconds' => 0, 'rate_limit_per_hour' => 5, + ], $opts); + } +} +function run_form_post(array $post): ?string +{ + $_POST = $post; + $_SERVER['REQUEST_METHOD'] = 'POST'; + try { Deskhub_Tickets_Form::handle_submit(); } catch (WpRedirect $r) { return $r->location; } + return null; +} +/** Holt den Zustand (Fehler/Erfolg), den handle_submit hinterlassen hat, aus der Weiterleitungsadresse. */ +function state_from(?string $location): ?array +{ + if (!$location || !preg_match('/[?&]dht=([a-f0-9]{16})/', $location, $m)) return null; + return $GLOBALS['wp_transients']['dht_state_' . $m[1]][0] ?? null; +} +function render_form(array $atts = [], ?string $location = null): string +{ + $_GET = []; + if ($location && preg_match('/[?&]dht=([a-f0-9]{16})/', $location, $m)) $_GET['dht'] = $m[1]; + return Deskhub_Tickets_Form::render_ticket_form($atts); +} +/** Ein gültig aussehendes Formular wie vom Plugin erzeugt: unterschriebene Angaben und Zeitstempel aus der Ausgabe lesen. */ +function valid_post(string $html, array $over = []): array +{ + preg_match('/name="dht_cfg" value="([^"]*)"/', $html, $c); + preg_match('/name="dht_sig" value="([^"]*)"/', $html, $s); + preg_match('/name="dht_ts" value="([^"]*)"/', $html, $t); + preg_match('/name="dht_back" value="([^"]*)"/', $html, $b); + return array_merge([ + 'action' => 'deskhub_ticket_submit', 'dht_cfg' => html_entity_decode($c[1] ?? ''), 'dht_sig' => $s[1] ?? '', 'dht_ts' => $t[1] ?? '', 'dht_back' => html_entity_decode($b[1] ?? ''), + 'dht_website' => '', 'dht_name' => 'Erika Muster', 'dht_email' => 'erika@kunde.example', 'dht_subject' => 'Rechnung fehlt', 'dht_message' => "Hallo,\nbitte schicken Sie mir die Rechnung.", + ], $over); +} +function ok_handler(array &$seen = null): callable +{ + return function (string $url, array $args) use (&$seen) { + if (str_contains($url, 'form=1') || str_contains($url, 'topics=1')) return wp_stub_response(422, ['error' => ['code' => 'MISSING_PARAMS', 'message' => 'x']]); // wie ein älterer Server + $seen[] = ['url' => $url, 'args' => $args]; + if (($args['method'] ?? 'GET') === 'POST') return wp_stub_response(201, ['number' => '261101-0001', 'status' => 'open']); + return wp_stub_response(200, ['ticket' => ['number' => '261101-0001', 'subject' => 'Rechnung', 'status' => 'answered', 'createdAt' => '2026-11-01 10:00:00', 'updatedAt' => '2026-11-01 11:00:00', + 'messages' => [['author' => 'customer', 'message' => 'Hallo ', 'createdAt' => '2026-11-01 10:00:00'], ['author' => 'staff', 'message' => "Erledigt.\nViele Grüße", 'createdAt' => '2026-11-01 11:00:00']]]]); + }; +} + +// ── Server-Adresse ───────────────────────────────────────────────────────── +reset_wp(); +foreach ([ + ['support.example.de', 'https://support.example.de'], + [' https://support.example.de/ ', 'https://support.example.de'], + ['https://example.de/helpdesk/', 'https://example.de/helpdesk'], + ['http://127.0.0.1:8080', 'http://127.0.0.1:8080'], + ['http://localhost', 'http://localhost'], +] as [$in, $want]) { + $check(Deskhub_Tickets_Client::normalize_url($in) === $want, "Adresse '$in' wird zu '$want'"); +} +foreach (['', 'http://support.example.de', 'https://user:pw@example.de', 'https://example.de/?x=1', 'https://example.de/#a', 'ftp://example.de', 'http://localhost.evil.de'] as $bad) { + $check(is_wp_error(Deskhub_Tickets_Client::normalize_url($bad)), "Adresse '$bad' wird abgelehnt"); +} + +// ── Nicht eingerichtet ────────────────────────────────────────────────────── +reset_wp(['server_url' => '', 'api_key' => '']); +$check(Deskhub_Tickets_Form::render_ticket_form() === '', 'Ohne Einrichtung sieht ein Besucher nichts'); +$GLOBALS['wp_user']['caps'] = ['manage_options']; +$check(str_contains(Deskhub_Tickets_Form::render_ticket_form(), 'noch nicht eingerichtet'), 'Ein Administrator sieht den Einrichtungshinweis'); + +// ── Formular wird angezeigt ───────────────────────────────────────────────── +reset_wp(['consent_text' => 'Ich habe die Datenschutzerklärung gelesen.']); +$html = render_form(); +foreach (['name="dht_name"', 'name="dht_email"', 'name="dht_subject"', 'name="dht_message"', 'name="dht_website"', 'name="dht_consent"', 'name="dht_ts"', 'name="dht_sig"'] as $f) { + $check(str_contains($html, $f), "Formular enthält $f"); +} +$check(!str_contains($html, 'dh_geheimer_schluessel_123') && !str_contains($html, 'support.example.de'), 'Weder API-Schlüssel noch Server-Adresse stehen im Formular'); +$check(str_contains($html, 'href="/datenschutz"') && !str_contains($html, 'onclick') && !str_contains($html, ' 7, 'caps' => [], 'name' => 'Lena', 'email' => 'lena@example.de']; +$check(str_contains(render_form(), 'value="lena@example.de"'), 'Angemeldete Besucher: E-Mail-Adresse ist vorausgefüllt'); +$GLOBALS['wp_user'] = ['id' => 0, 'caps' => [], 'name' => '', 'email' => '']; + +// ── Absenden: Erfolg und Inhalt der Anfrage ───────────────────────────────── +reset_wp(['department_id' => 2, 'priority_id' => 1]); +$seen = []; +$GLOBALS['wp_http_handler'] = ok_handler($seen); +$html = render_form(); +$loc = run_form_post(valid_post($html, ['dht_departmentId' => '99', 'dht_priority' => '9', 'dht_topic' => '77'])); +$state = state_from($loc); +$check($state && !empty($state['ok']) && $state['number'] === '261101-0001', 'Absenden gelingt, Ticketnummer wird gemerkt'); +$check(str_starts_with((string) $loc, 'https://wp.test/kontakt/?dht=') && str_ends_with((string) $loc, '#deskhub-ticket'), 'Danach geht es auf die Seite des Formulars zurück'); +$req = $seen[0] ?? null; +$sent = json_decode((string) ($req['args']['body'] ?? ''), true); +$check($req && ($req['args']['method'] ?? '') === 'POST' && str_ends_with($req['url'], '/api/public/v1/tickets.php'), 'Anfrage geht per POST an die Ticket-API'); +$check(($req['args']['headers']['X-API-Key'] ?? '') === 'dh_geheimer_schluessel_123', 'Der API-Schlüssel wird im Header gesendet'); +$check(($sent['departmentId'] ?? null) === 2 && ($sent['priorityId'] ?? null) === 1, 'Abteilung und Priorität kommen aus den Einstellungen, nicht aus dem Formular'); +$check(!isset($sent['helpTopicId']), 'Ein vom Besucher erfundenes Thema wird nicht übernommen'); +$check(str_contains((string) ($sent['message'] ?? ''), "bitte schicken Sie mir die Rechnung.") && str_contains((string) $sent['message'], 'https://wp.test/kontakt/'), 'Nachricht enthält den Text und die Herkunftsseite'); +$check(render_form([], $loc) !== '' && str_contains(render_form([], $loc), 'Vielen Dank') === false, 'Der Erfolgszustand wird nur einmal angezeigt (danach wieder das leere Formular)'); +$loc2 = run_form_post(valid_post($html)); +$check(str_contains(render_form([], $loc2), '261101-0001'), 'Erfolgsseite zeigt die Ticketnummer'); + +// ── Manipulation und Roboter ──────────────────────────────────────────────── +reset_wp(); +$seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); +$html = render_form(); +$bad = valid_post($html, ['dht_cfg' => base64_encode('{"d":99,"t":0,"p":9,"o":[]}')]); +$st = state_from(run_form_post($bad)); +$check($st && !empty($st['errors']) && !$seen, 'Veränderte Abteilung/Priorität (falsche Unterschrift) wird abgelehnt, nichts gesendet'); +$st = state_from(run_form_post(valid_post($html, ['dht_website' => 'http://spam.example']))); +$check($st && !empty($st['ok']) && $st['number'] === '' && !$seen, 'Falle-Feld ausgefüllt: scheinbar Erfolg, aber nichts angelegt'); +$st = state_from(run_form_post(valid_post($html, ['dht_ts' => 'abc']))); +$check($st && !empty($st['errors']) && !$seen, 'Gefälschter Zeitstempel wird abgelehnt'); +reset_wp(['min_seconds' => 10]); +$seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); +$st = state_from(run_form_post(valid_post(render_form()))); +$check($st && !empty($st['errors']) && !$seen, 'Zu schnell abgesendet (unter der Mindestzeit) wird abgelehnt'); + +// ── Pflichtfelder, Einwilligung, Themenauswahl ────────────────────────────── +reset_wp(['consent_text' => 'Ich stimme zu.']); +$seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); +$html = render_form(); +$st = state_from(run_form_post(valid_post($html, ['dht_name' => '', 'dht_email' => 'kein-email', 'dht_subject' => '', 'dht_message' => ' ']))); +$check($st && count($st['errors']) === 5 && !$seen, 'Fehlende Pflichtfelder und fehlende Einwilligung ergeben fünf Fehler (nichts gesendet)'); +$st = state_from(run_form_post(valid_post($html, ['dht_message' => 'Hallo Welt ', 'dht_consent' => '1']))); +$sent = json_decode((string) ($seen[0]['args']['body'] ?? ''), true); +$check(!str_contains((string) ($sent['message'] ?? ''), '<'), 'HTML in der Nachricht wird entfernt'); +$check($st && !empty($st['ok']), 'Mit Einwilligung klappt es'); + +reset_wp(['consent_text' => '']); +$seen = []; $GLOBALS['wp_http_handler'] = ok_handler($seen); +$html = render_form(['topics' => 'Rechnung:3,Technik:4', 'department' => '5']); +$check(str_contains($html, '