From 956786592aa640f94c661a84b10d78c3fd94d9c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 6 May 2026 10:48:35 +0000 Subject: [PATCH 1/2] Fix Carbon Fields attribute error: remove class attribute from byte_size field CF5 only allows data-* and a specific set of HTML attributes on its React inputs. Replace .odw-byte-size-backing CSS class (disallowed) with the existing [data-odw-backing] data attribute as the hide target. https://claude.ai/code/session_FEyiS --- assets/css/admin.css | 4 ++-- includes/class-fields.php | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/assets/css/admin.css b/assets/css/admin.css index b77cf34..306264b 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -529,11 +529,11 @@ } /* Hide the backing CF byte_size input; JS syncs to it */ -.odw-byte-size-backing { +[data-odw-backing] { display: none !important; } /* Also hide the entire CF field wrapper for the backing field */ -.cf-field:has(.odw-byte-size-backing) { +.cf-field:has([data-odw-backing]) { display: none !important; } diff --git a/includes/class-fields.php b/includes/class-fields.php index e9c5d44..bf87314 100644 --- a/includes/class-fields.php +++ b/includes/class-fields.php @@ -149,8 +149,7 @@ private static function register_required_fields(): void { Field::make( 'text', 'byte_size', __( 'Dateigröße (Bytes)', 'open-data-wizard' ) ) ->set_attribute( 'type', 'number' ) ->set_attribute( 'min', '0' ) - ->set_attribute( 'data-odw-backing', 'byte_size' ) - ->set_attribute( 'class', 'odw-byte-size-backing' ), + ->set_attribute( 'data-odw-backing', 'byte_size' ), Field::make( 'select', 'license', __( 'Unter welcher Lizenz sind diese Daten verfügbar?', 'open-data-wizard' ) ) ->set_required( true ) From 514f8986586a99fcfa7363b3121587099aeef350 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 6 May 2026 10:57:15 +0000 Subject: [PATCH 2/2] Docs update, repo cleanup, bump version to 2.1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete stale planning docs (docs/WEBHOOK_IMPLEMENTATION.md, docs/I18N_IMPLEMENTATION.md) — unimplemented, no longer relevant - Bump plugin version to 2.1.0 - README: rewrite to reflect current architecture (per-distribution license, 4 quality levels, new tab structure, CESSDA, composite file size widget, shortcode overhaul, updated file tree) - CHANGELOG: close v2.0.0 entry, add v2.1.0 and v2.1.1 entries - CLAUDE.md: update version constant, test count (90), quality levels (4), version/feature matrix, test file list https://claude.ai/code/session_FEyiS --- CHANGELOG.md | 224 +++--- CLAUDE.md | 15 +- README.md | 252 ++++--- docs/I18N_IMPLEMENTATION.md | 828 ----------------------- docs/WEBHOOK_IMPLEMENTATION.md | 1159 -------------------------------- open-data-wizard.php | 4 +- 6 files changed, 223 insertions(+), 2259 deletions(-) delete mode 100644 docs/I18N_IMPLEMENTATION.md delete mode 100644 docs/WEBHOOK_IMPLEMENTATION.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 1790cf0..3f60d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,24 +7,63 @@ Versionierung folgt [Semantic Versioning](https://semver.org/). --- -## [2.0.0] — TBD (In Entwicklung) +## [2.1.1] — 2026-05-06 + +### Behoben +- **Carbon Fields Attribut-Fehler**: `set_attribute('class', ...)` ist in CF5 nicht erlaubt (nur `data-*` und eine feste Whitelist). Das `byte_size` Backing-Feld verwendete fälschlicherweise ein `class`-Attribut — ersetzt durch den bereits vorhandenen `[data-odw-backing]` Selektor im CSS. + +--- + +## [2.1.0] — 2026-05-06 + +### Hinzugefügt +- **Externe Konfigurationsdateien** für einfachere Wartung ohne PHP-Kenntnisse: + - `config/licenses.txt` — Lizenzdatei im Format `URI | Label`; enthält CC0, CC-BY, CC-BY-SA, ODbL u.a. + - `config/dct-format-list.php` — 18 Dateiformate mit MIME-Type und EU-URI (CSV, JSON, GeoJSON, RDF usw.) + - `config/dcat-ap-fields.php` — Single Source of Truth für alle Felddefinitionen (Schlüssel, DCAT-AP Prädikat, Punkte, Pflicht-Flag) +- **Lizenz pro Distribution**: Lizenz ist jetzt Pflichtfeld für jede Distribution (DCAT-AP-konform). Entfernt vom Dataset-Level. + - Option „Sonstige" öffnet ein Freitextfeld mit Auto-Suggest aus `config/licenses.txt` + - Deutschland-Lizenz entfernt +- **CESSDA-Themenklassifikation**: Neues Auto-Suggest-Feld in Tab 2 mit 95 deutschen Konzepten aus der CESSDA Topic Classification 4.2.3 (SKOS/RDF, 24h Transient-Cache) +- **Vier Qualitätsstufen** (vorher drei): + - Perfekt (100 Punkte) — alle Felder ausgefüllt + - Gut (56–99 Punkte) — über Mindestanforderung + - Ausreichend (55 Punkte) — genau alle Pflichtfelder erfüllt + - Verbesserungsbedarf (< 55 Punkte) — Pflichtfelder fehlen +- **Wizard-Tabs umstrukturiert**: + - Tab 1: Grundlegende Informationen (Herausgeber, Beschreibung, Thema) + - Tab 2: Inhaltliche Angaben (Sprache, Schlagworte, Datum, CESSDA) + - Tab 3: Datenbereitstellung (Distributions mit Lizenz als Pflichtfeld) + - Tab 4: Erweiterte Angaben (unverändert) + - Tab 5: Vorschau (unverändert) +- **Composite Dateigröße-Widget** in jeder Distribution: Zahleneingabe + Einheiten-Dropdown (KB/MB/GB); JavaScript berechnet Bytes für das versteckte `byte_size` Backing-Feld; Anzeige wird beim Laden aus gespeicherten Bytes wiederhergestellt +- **`assets/js/odw-admin-fields.js`** — neues Script für License Auto-Suggest, CESSDA Auto-Suggest und Dateigröße-Widget (kein jQuery, MutationObserver für dynamische CF-Gruppen) +- **Shortcode-Widget überarbeitet**: Qualitätsanzeige entfernt; Schlagwörter als Tag-Pillen; Metadaten-Download-Button (JSON-LD REST-Endpoint) + +### Geändert +- Plugin-Author: „Datenatlas Zivilgesellschaft" → **nozilla** (GitHub: https://github.com/daimpad/OpenDataWizard) +- Alle Civora-, Piveau- und Datenatlas-Markenreferenzen aus Kommentaren und Dokumentation entfernt; funktionaler REST-Namespace `datenatlas/v1` bleibt erhalten +- `ODW_Fields::get_required_fields()` — Lizenz nicht mehr als skalares Pflichtfeld (jetzt per Distribution via `all_distributions_have_license()`) +- `ODW_Quality::check_indicator('license')` — prüft jetzt Distributions statt `_odw_license` Meta +- Demo-Publisher-Name: „Datenatlas Zivilgesellschaft e.V." → „Musterorganisation e.V." +- Stale Planungsdokumente (`docs/WEBHOOK_IMPLEMENTATION.md`, `docs/I18N_IMPLEMENTATION.md`) entfernt + +### Behoben +- Lizenz-Anzeige in der Admin-Listenansicht liest jetzt aus erster Distribution statt `_odw_license` + +--- + +## [2.0.0] — 2026-04-29 ### Hinzugefügt -- **Benutzerfreundlicher Wizard-Form (Phase 1+2 UX-Verbesserung)**: - - Alle 19 Form-Felder mit **benutzergerechten Fragen statt technischen Begriffen**: - - Tab 1: „Wer gibt diese Daten heraus?" statt „Herausgebende Organisation (dct:publisher)" - - Tab 2: „In welche Kategorie gehört dieser Datensatz?" statt „Thema (dcat:theme)" - - Tab 3: „Wo können die Daten heruntergeladen werden?" statt „Distributionen (dcat:distribution)" - - Tab 4: „Wie oft werden diese Daten aktualisiert?" statt „Aktualisierungsfrequenz (dct:accrualPeriodicity)" - - **Hilftexte mit Beispielen** für alle Felder (Format: Original-Label + DCAT-AP Bezeichnung klein + praktisches Beispiel) - - **Validierungsmeldungen** verwenden jetzt neue, verständliche Labels - - Reduziert Anfängerhürde drastisch: Admins verstehen sofort, was in welches Feld gehört -- **WP-CLI Befehle für Massenoperationen** (`includes/class-cli.php`): - - `wp open-data-wizard quality recalculate` — Qualitätsscores für alle (oder gefilterte) Datasets neu berechnen - - `wp open-data-wizard quality recalculate --all` — Einschließlich Draft und Trash-Posts - - `wp open-data-wizard cache clear` — Alle REST API Transient-Caches (Catalog, Delta, Dataset) löschen - - Nützlich für Massenänderungen, Migrationen, Cron-Jobs und CI/CD-Automatisierung - - 4 neue PHPUnit-Tests für CLI-Funktionalität +- **Benutzerfreundlicher Wizard (Phase 1+2 UX-Verbesserung)**: + - Alle 19 Formularfelder mit benutzergerechten Fragen statt technischen DCAT-AP-Begriffen + - Hilfetexte mit Original-Label, DCAT-AP Bezeichnung und praktischen Beispielen für alle Felder + - Validierungsmeldungen verwenden verständliche Labels +- **WP-CLI Befehle** (`includes/class-cli.php`): + - `wp open-data-wizard quality recalculate` — Qualitätsscores neu berechnen + - `wp open-data-wizard quality recalculate --all` — inkl. Draft und Trash + - `wp open-data-wizard cache clear` — REST API Transient-Caches löschen --- @@ -32,182 +71,99 @@ Versionierung folgt [Semantic Versioning](https://semver.org/). ### Hinzugefügt - **Delta-Harvesting Endpoint** (`GET /wp-json/datenatlas/v1/delta?since=`): - - Inkrementelles Harvesting für externe Datenportale (z.B. Civora, Piveau) - - Liefert nur Datasets, die nach einem Zeitstempel geändert wurden + - Inkrementelles Harvesting für externe Datenportale - Tombstone-Einträge für gelöschte/verschobene Datasets (`odw:removed` Array) - - ISO 8601 Zeitformat-Unterstützung (YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, ISO-offsets) - - Response-Header: `X-ODW-Delta-Since`, `X-ODW-Generated-At` (für nächsten `since` Wert) - - 13 neue PHPUnit-Tests für Validierung, Caching, Tombstones -- **CLAUDE.md** — Umfassender Entwicklerleitfaden für zukünftige Claude Code Instanzen: - - Architektur-Übersicht, Klassen-Hierarchie - - Wichtigste Patterns (Capabilities, JSON-LD, Caching, Validierung, Companion Functions) - - Debugging-Tipps, Testing-Anleitung, Security-Noten - - Erweiterbarkeit via Filters und Hooks + - ISO 8601 Zeitformat-Unterstützung + - Response-Header: `X-ODW-Delta-Since`, `X-ODW-Generated-At` + - 13 neue PHPUnit-Tests +- **CLAUDE.md** — Umfassender Entwicklerleitfaden ### Geändert -- **WordPress Coding Standards** vollständig implementiert (PHPCS Level): - - 178 Violations → 0 Violations (alle Docblocks, @param/@return Tags, Kommentar-Zeichensetzung) - - `phpcs.xml` Konfiguration zur Ausgrenzung praktischer Sniffs (Carbon Fields, Tests) - - Yoda Conditions, Short Ternaries, WordPress Globals-Konflikte behoben +- WordPress Coding Standards vollständig implementiert (178 → 0 Violations) ### Sicherheit -- **`access_url` Sanitisierung**: Alle Distribution-URLs werden in der JSON-LD Ausgabe durch `esc_url_raw()` geführt (strippt `javascript:`, `data:` etc. vor Ausgabe) -- **Capability-basierte Zugriffssteuerung**: CPT `capability_type` auf `odw_dataset` mit explizitem Mapping aller Schreib-Operationen (create, edit, delete, publish) auf `manage_open_data` Capability; effektiv: nur Admins/Editoren können Datasets anlegen - -### Behoben -- PHPStan 2.x Konfiguration (extensions zu includes migriert) -- Nonce-Verification Warnings in class-validation.php konsolidiert -- Dokumentation in README.md mit Delta-Endpoint erweitert +- `access_url` Sanitisierung: alle Distribution-URLs durch `esc_url_raw()` (strippt `javascript:`, `data:`) +- Capability-basierte Zugriffssteuerung: nur `manage_open_data` kann Datasets anlegen --- ## [1.8.0] — 2026-04-21 ### Hinzugefügt -- **Native wp.media Upload-Widget** als Sidebar-Meta-Box auf dem Datensatz-Edit-Screen: - - Button „Datei auswählen / hochladen" öffnet den nativen WordPress Media Library Frame - - Dateivorschau zeigt den Dateinamen (oder „Keine Datei ausgewählt") mit Dokumenten-Icon - - „Entfernen"-Button löscht die Verknüpfung und leert alle abhängigen Meta-Felder -- **Automatische Meta-Berechnung beim Speichern**: Nach jeder Dateiauswahl werden `_odw_file_size` (Bytes als Integer) und `_odw_file_format` (z.B. „CSV") direkt aus der Mediathek-Datei ausgelesen und gespeichert — kein Runtime-`filesize()`-Aufruf mehr beim Shortcode-Rendering nötig -- **`assets/js/odw-file-upload.js`** — jQuery + wp.media Integration; UI-Zustand wird serverseitig via `wp_localize_script` initialisiert; Media-Frame-Instanz wird wiederverwendet +- **Native wp.media Upload-Widget** als Sidebar-Meta-Box +- **Automatische Meta-Berechnung**: `_odw_file_size` und `_odw_file_format` werden beim Speichern berechnet ### Geändert -- **Shortcode** `[odw_dataset]` liest `_odw_file_size` und `_odw_file_format` jetzt aus vorberechnetem Post-Meta (mit Fallback auf Runtime-Berechnung für ältere Datensätze) -- Carbon Fields `Field::make('file', 'odw_file_id')` in Tab 3 entfernt — ersetzt durch die native Meta-Box-Implementierung in der Sidebar - -### Sicherheit -- `save_file_attachment()` prüft `wp_verify_nonce('odw_save_file_attachment')` und `current_user_can('edit_post')` vor jeder Speicherung +- Shortcode liest `_odw_file_size` und `_odw_file_format` aus vorberechnetem Post-Meta --- ## [1.7.0] — 2026-04-21 ### Hinzugefügt -- **Tab 4 — Erweiterte Angaben** im Datensatz-Formular mit 8 neuen DCAT-AP 3.0 Feldern: - - `dcat:landingPage` — URL der Projektwebsite - - `dct:accrualPeriodicity` — Aktualisierungsfrequenz (EU Publications Office Vokabular: täglich bis zweijährlich) - - `dct:spatial` — Geographische Abdeckung (Freitext oder URI, z.B. GeoNames) - - `dct:temporal` — Zeitlicher Bezug mit Start- und Enddatum (`dcat:startDate`, `dcat:endDate`) - - `dcat:contactPoint` — Kontaktpunkt mit Name, E-Mail (`mailto:`-Prefix) und Website (`vcard:Organization`) -- **`vcard` und `skos` Namespaces** im JSON-LD `@context` der REST API -- **`ODW_Fields::get_periodicity_options()`** — Kontrolliertes Vokabular für Aktualisierungsfrequenzen - -### Geändert -- Vorschau-Tab umbenannt von „4" auf „5" (Erweiterte Angaben ist jetzt Tab 4) -- Help Tab Beschreibung aktualisiert +- **Tab 4 — Erweiterte Angaben**: `dcat:landingPage`, `dct:accrualPeriodicity`, `dct:spatial`, `dct:temporal`, `dcat:contactPoint` +- `vcard` und `skos` Namespaces im JSON-LD `@context` --- ## [1.6.0] — 2026-04-21 ### Hinzugefügt -- **Settings-Seite** unter *Datensätze → Einstellungen* mit vier Sektionen: - - **Katalog**: Katalog-Titel (überschreibt den Standardwert im REST API), Herausgebende Organisation - - **Standardwerte**: Standard-Lizenz und Standard-Sprache — werden bei neuen Datensätzen automatisch vorausgefüllt (via `set_default_value()` in Carbon Fields) - - **REST API**: Cache-Laufzeit konfigurierbar (60–86400 s, Standard 300 s) - - **Deinstallation**: Checkbox für opt-in Datenlöschung (ersetzt separate Option) -- **„Alle Qualitätsscores neu berechnen"**-Button auf der Settings-Seite (nonce-gesichert, zeigt Anzahl aktualisierter Datensätze) -- **`ODW_Settings::get()`** — zentrale API für Einstellungszugriff in anderen Klassen -- **`ODW_Rest_API::delete_catalog_transients_public()`** — öffentlicher Alias für Cache-Invalidierung nach Einstellungsänderungen - -### Geändert -- `uninstall.php`: liest jetzt `odw_settings[delete_on_uninstall]` statt separater Option; löscht auch `odw_settings`, `odw_demo_post_id`, `odw_show_welcome` +- **Settings-Seite** mit Katalog, Standardwerte, REST API und Deinstallation +- „Alle Qualitätsscores neu berechnen"-Button --- ## [1.5.0] — 2026-04-21 ### Hinzugefügt -- **Demo-Datensatz bei Installation**: Beim ersten Admin-Aufruf nach der Aktivierung wird automatisch ein vollständig befüllter Demo-Datensatz (`odw_dataset`) erstellt — inklusive Beispiel-CSV aus der Mediathek (`assets/sample/beispiel-datensatz.csv`), allen Meta-Feldern, CF-Distribution und berechnetem Qualitätsscore -- **Willkommens-Notice** (einmalig, dismissible): Zeigt nach der Aktivierung den fertigen Shortcode (`[odw_dataset id="…"]`) zum direkten Copy-Paste, Links zum Demo-Datensatz und zur Übersicht sowie einen „Hinweis ausblenden"-Link (Nonce-gesichert) -- **`includes/class-setup.php`**: Kapselt die gesamte Installations-Logik; `on_activation()` setzt nur eine Option (kein CF-Zugriff), `maybe_create_demo()` läuft auf `admin_init` wenn Carbon Fields vollständig initialisiert ist +- **Demo-Datensatz bei Installation** mit Beispiel-CSV und Willkommens-Notice --- ## [1.4.0] — 2026-04-21 ### Hinzugefügt -- **Shortcode `[odw_dataset id="…"]`** — gibt eine Download-Card im Frontend aus mit Titel, Thema-Badge, Lizenz, DCAT-Qualität, Dateigröße/Format und Download-Button -- **Download-Datei über Mediathek** (`_odw_file_id`): Neues File-Feld in Tab 3 verknüpft eine Datei aus der WordPress-Mediathek; Dateigröße und Format werden automatisch ermittelt -- **Shortcode-Spalte in der Admin-Übersicht**: Zeigt `[odw_dataset id="123"]` als klickbares, schreibgeschütztes Textfeld (Klick = Markierung für Copy-Paste) -- **`assets/css/frontend.css`**: Strukturelles Layout der Download-Card (Flexbox/Grid, Abstände, Rahmen) ohne feste Farben — erbt vollständig vom aktiven Theme +- **Shortcode `[odw_dataset id="…"]`** — Download-Card im Frontend +- Shortcode-Spalte in der Admin-Übersicht --- ## [1.3.0] — 2026-04-21 ### Hinzugefügt -- **Qualitätsindikatoren / Ampellogik** (`includes/class-quality.php`): Automatische Bewertung der Metadaten-Vollständigkeit (0–100 Punkte, 3 Levels: Gut/Mittel/Verbesserungsbedarf) - - 10 Indikatoren in 3 Gruppen: Pflichtfelder (55 Pkt.), Empfohlene Felder (40 Pkt.), Optionale Angaben (5 Pkt.) - - Automatische Neuberechnung nach jedem Speichern (`save_post_odw_dataset`, Priorität 30) - - Persistenz in 4 Meta-Keys: `_odw_quality_score`, `_odw_quality_level`, `_odw_quality_indicators`, `_odw_quality_calculated_at` -- **Qualitätsspalte in der Admin-Listenansicht**: Farbiger Badge (● 85) mit Tooltip; sortierbar -- **Qualitätsbericht-Meta-Box** auf dem Edit-Screen: Fortschrittsbalken, Ampel-Badge, gruppierte Indikator-Tabelle (✓/✗) mit Punkten, Zeitstempel der letzten Berechnung -- **`odw:qualityScore` im JSON-LD**: Qualitätsdaten werden via `odw_dataset_jsonld` Filter an den REST-API Output angehängt (`odw:score`, `odw:maxScore`, `odw:level`, `odw:calculatedAt`) -- **`odw:` JSON-LD Namespace** (`https://github.com/daimpad/OpenDataWizard/ns#`) in `JSONLD_CONTEXT` -- **CSS Qualitäts-Styles**: `--odw-color-quality-*` Custom Properties; `.odw-quality-badge`, `.odw-quality-gauge`, `.odw-quality-table` Komponenten +- **Qualitätsindikatoren** (`includes/class-quality.php`): 0–100 Punkte, Ampellogik +- Qualitätsspalte und Qualitätsbericht-Meta-Box im Admin +- `odw:qualityScore` im JSON-LD --- ## [1.2.0] — 2026-04-21 ### Hinzugefügt -- **?format= Parameter** an beiden REST-Endpoints (`/catalog`, `/datasets/`): `jsonld` (Standard, `application/ld+json`) oder `json` (`application/json`) — Grundlage für spätere Content-Negotiation -- **PHPStan Level 6** Konfiguration (`phpstan.neon`) -- **WordPress Coding Standards** via WPCS (`phpcs`/`phpcbf` Scripts in composer.json) -- **PHPUnit** Test-Setup (`phpunit.xml`, `tests/bootstrap.php`, erste Test-Suite für `ODW_Fields`) -- **GitHub Actions CI** Workflow (`.github/workflows/ci.yml`): PHPCS, PHPStan, PHPUnit auf PHP 8.1/8.2/8.3 -- **`ODW_Fields::get_required_fields()`** — zentrale Pflichtfeld-Registry als Single Source of Truth - -### Geändert -- **Validierungslogik zentralisiert**: `class-validation.php` iteriert über `ODW_Fields::get_required_fields()` statt Felder doppelt zu pflegen -- **`get_field_value()`** vereinfacht: CF-Key-Parameter entfernt, meta_key reicht als Identifier -- **composer.json**: `require-dev` Sektion mit PHPStan, WPCS, PHPUnit hinzugefügt; `allow-plugins` Konfiguration ergänzt +- `?format=` Parameter (jsonld/json) an REST-Endpoints +- PHPStan Level 6, WPCS, PHPUnit Test-Setup +- GitHub Actions CI (PHP 8.1/8.2/8.3) --- ## [1.1.0] — 2026-04-21 ### Hinzugefügt -- **Activation Hook**: CPT registrieren, Rewrite Rules flushen, Capability `manage_open_data` vergeben -- **Deactivation Hook**: Rewrite Rules flushen -- **`uninstall.php`**: Opt-in Datenlöschung bei Deinstallation (hinter `odw_delete_data_on_uninstall` Option) -- **REST API Transient-Cache**: 5 Minuten TTL für `/catalog` und `/datasets/`; Cache-Invalidierung bei `save_post_odw_dataset` und `trashed_post`; `X-ODW-Cache: HIT/MISS` Header -- **Capability `manage_open_data`**: Administrator und Editor erhalten die Capability bei Plugin-Aktivierung -- **Filter-Hooks**: `odw_license_options`, `odw_theme_options`, `odw_dataset_jsonld`, `odw_catalog_title` -- **Admin Help Tabs**: DCAT-AP Feldbeschreibungen und Harvest-Endpoint Doku auf dem Edit-Screen -- **`ODW_Fields::get_license_label()`**: Single Source of Truth für Lizenz-URI → Label Übersetzung -- **CSS Custom Properties**: `--odw-color-*` Variablen statt hard-codierter Hex-Werte - -### Behoben -- **Zeitzonen-Bug**: `gmdate()` → `current_time()` für `_odw_modified` (verhinderte Datums-Abweichung um 1 Tag bei Nicht-UTC-Servern) -- **Sortierbare Spalte „Thema"**: `pre_get_posts` Hook mit `meta_key`/`meta_value` — Sortierung war vorher defekt -- **`$_GET` Sanitization**: `wp_unslash()` + `sanitize_text_field()` konsequent; `absint()` für post_id (class-admin.php, class-validation.php) -- **Byte-Size Validierung**: `is_numeric()` + `>= 0` Prüfung vor JSON-LD Ausgabe -- **Transient-TTL**: 60s → 300s für Validierungsnotices (verhindert Ablauf bei langsamen Servern) -- **sessionStorage Safety**: `try/catch` Wrapper für Private-Browsing-Modus und Quota-Überschreitung; post_id-spezifischer Key (`odw_active_tab_`) -- **MutationObserver Speicherleck**: `disconnect()` via `beforeunload` Event -- **Carbon Fields Boot-Fehler**: `try/catch` um `boot()` mit hilfreicher Admin-Notice statt fatalen PHP-Fehler +- Activation/Deactivation Hooks, `uninstall.php` +- REST API Transient-Cache (5 min TTL) +- Capability `manage_open_data` +- Filter-Hooks: `odw_license_options`, `odw_theme_options`, `odw_dataset_jsonld`, `odw_catalog_title` +- Admin Help Tabs --- ## [1.0.0] — 2026-03-02 ### Hinzugefügt -- **Custom Post Type `odw_dataset`** mit deutschen Labels und Dashicons-database Icon -- **Carbon Fields Formular** mit 4 Tabs: - - Tab 1: Pflichtfelder (Titel, Beschreibung, Publisher, Lizenz) - - Tab 2: Optionale Felder (Sprache, Schlagworte, Thema, Datum) - - Tab 3: Distributionen (accessURL, Format, byteSize) — wiederholbares Complex Field - - Tab 4: JSON-LD Vorschau (read-only) -- **REST API**: - - `GET /wp-json/datenatlas/v1/catalog` mit Paginierung und Filtern (`?theme=`, `?license=`) - - `GET /wp-json/datenatlas/v1/datasets/` - - Content-Type `application/ld+json`, DCAT-AP 3.0 `@context` -- **Admin-Listenansicht**: Spalten Titel, Lizenz, Thema, Status, Änderungsdatum; Status-Dropdown-Filter -- **Pflichtfeldvalidierung**: Blockiert Veröffentlichung bei fehlenden Pflichtfeldern; Admin-Notice mit Feldnamen -- **Tab-Navigation** (Vanilla JS, kein jQuery): sessionStorage-Persistenz, Keyboard-Navigation -- **Carbon Fields** v3.6 via Composer im Plugin gebündelt (kein Composer-Wissen nötig) -- **DCAT-AP 3.0 JSON-LD** Ausgabe mit allen Pflicht- und empfohlenen Feldern -- **Lizenz-Kurzaliase** im API-Filter (`?license=cc-by`, `?license=cc0` etc.) -- Automatische Aktualisierung von `dct:modified` bei jedem Speichern +- Custom Post Type `odw_dataset` +- Carbon Fields Formular mit 4 Tabs +- REST API: `/catalog` und `/datasets/` +- Admin-Listenansicht mit Spalten und Filtern +- Pflichtfeldvalidierung +- DCAT-AP 3.0 JSON-LD Ausgabe diff --git a/CLAUDE.md b/CLAUDE.md index c16c05e..b397f9f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ composer install # Install all dev dependencies (PHPUnit, PHPStan composer phpcs # Run WordPress Coding Standards checks composer phpcbf # Auto-fix PHPCS violations composer phpstan # Static analysis (Level 6) — false positives expected from Carbon Fields stubs -composer test # Run PHPUnit (all 69 tests) +composer test # Run PHPUnit (all 90 tests) composer test -- tests/test-fields.php # Run a single test file composer test -- --filter testMethodName # Run specific test by name ``` @@ -33,7 +33,7 @@ Open Data Wizard is a **WordPress plugin** that bridges the gap between WordPres 3. On publish, **validation hooks** block publishing if required fields are missing 4. Metadata is **persisted as post meta** + **JSON-LD cached in transients** 5. **REST API endpoints** expose published datasets as machine-readable JSON-LD -6. External **Harvesting systems** (Civora, Piveau, CKANN) can fetch the `/catalog` endpoint and automatically ingest datasets +6. External **harvesting systems** can fetch the `/catalog` endpoint and automatically ingest datasets ### Class Hierarchy @@ -60,7 +60,7 @@ ODW_Something::init(); // Called in odw_bootstrap() | `ODW_Post_Types` | Registers the `odw_dataset` CPT with capability mapping | `register()` — maps all write ops to `manage_open_data` cap | | `ODW_Fields` | Defines the 5-tab Carbon Fields form + JSON-LD builder | `register()`, `register_required_fields()`, `odw_build_dataset_jsonld()` (companion function) | | `ODW_Validation` | Blocks publishing if required fields missing; stores errors as transients | `intercept_publish()`, `validate()` | -| `ODW_Quality` | Auto-calculates DCAT-AP completeness (0–100 score, 3 levels) after save | `calculate()`, `check_indicator()`, `get_level()` | +| `ODW_Quality` | Auto-calculates DCAT-AP completeness (0–100 score, 4 levels) after save | `calculate()`, `check_indicator()`, `get_level()` | | `ODW_Admin` | Admin UI: list columns, sortable columns, help tabs, wp.media upload meta-box | `render_column()`, `handle_meta_orderby()` (for sortable theme column via `pre_get_posts`) | | `ODW_Rest_API` | `/catalog` and `/datasets/` + `/delta?since=` endpoints | `get_catalog()`, `get_dataset()`, `get_delta()` — all with transient caching (5 min TTL) | | `ODW_Shortcode` | `[odw_dataset id="123"]` renders download card in frontend | `render()` | @@ -181,11 +181,13 @@ The plugin tracks features by version in `CHANGELOG.md`. Key versions: - **v1.8** — Native wp.media upload widget in sidebar - **v1.9** — Delta-Harvesting endpoint (`/delta?since=` for incremental harvesting), comprehensive CLAUDE.md - **v2.0.0** — **Phase 1+2 UX improvements**: All 19 form field labels rewritten with user-friendly questions + practical examples; WP-CLI commands for batch operations +- **v2.1.0** — Per-distribution license, CESSDA topic classification, 4-level quality scoring, external config files (`config/licenses.txt`, `config/dct-format-list.php`, `config/dcat-ap-fields.php`), composite file size widget, shortcode overhaul (keywords + metadata download), plugin rebrand to nozilla +- **v2.1.1** — Bugfix: remove invalid `class` attribute from CF5 input (use `[data-odw-backing]` CSS selector) ### Constants (Defined in `open-data-wizard.php`) ```php -define( 'ODW_VERSION', '1.8.0' ); // Current version +define( 'ODW_VERSION', '2.1.0' ); // Current version define( 'ODW_PLUGIN_DIR', dirname( __FILE__ ) . '/' ); // /path/to/plugin/ define( 'ODW_PLUGIN_URL', plugins_url( '', __FILE__ ) ); // https://site.com/wp-content/plugins/open-data-wizard/ define( 'ODW_PLUGIN_FILE', __FILE__ ); // /path/to/plugin/open-data-wizard.php @@ -230,7 +232,7 @@ private function load_class(): void { ### Running Tests ```bash -composer test # All 69 tests +composer test # All 90 tests composer test -- tests/test-fields.php # Single file composer test -- --filter testMethodName # Single test composer test -- --verbose # Show test names @@ -239,10 +241,11 @@ composer test -- --verbose # Show test names **Test files:** - `test-fields.php` — `ODW_Fields` static methods (license labels, format MIME types, required fields) - `test-fields-extended.php` — JSON-LD builder (`odw_build_dataset_jsonld()`) -- `test-quality.php` — `ODW_Quality` scoring and caching +- `test-quality.php` — `ODW_Quality` scoring and caching (4 levels) - `test-settings.php` — `ODW_Settings` get/filter methods - `test-shortcode.php` — `ODW_Shortcode` rendering and utilities - `test-rest-delta.php` — Delta endpoint validation, caching, tombstones +- `test-cli.php` — WP-CLI commands --- diff --git a/README.md b/README.md index d73ca94..5168ae3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.1-8892BF?style=flat-square&logo=php&logoColor=white) ![WordPress](https://img.shields.io/badge/WordPress-compatible-21759B?style=flat-square&logo=wordpress&logoColor=white) ![DCAT-AP](https://img.shields.io/badge/DCAT--AP-3.0-brightgreen?style=flat-square) -![Status](https://img.shields.io/badge/Status-v2.0.0%20in%20Entwicklung-brightgreen?style=flat-square) +![Version](https://img.shields.io/badge/Version-2.1.0-brightgreen?style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-willkommen-brightgreen?style=flat-square) **Ein WordPress-Plugin zur einfachen Veröffentlichung offener Daten nach DCAT-AP 3.0** @@ -38,7 +38,7 @@ Open-Data-Plattformen können diese URL als Harvest-Quelle einbinden und die Met DCAT-AP (Data Catalog Vocabulary — Application Profile) ist ein europäischer Standard zur Beschreibung von Datensätzen und Datenkatalogen. Er definiert, welche Angaben ein Datensatz braucht, damit er von Plattformen, Suchmaschinen und Anwendungen einheitlich gelesen und verarbeitet werden kann — Titel, Beschreibung, Lizenz, Format, Herausgeber und mehr. -Open Data Wizard implementiert **DCAT-AP 3.0** und erzeugt valide **JSON-LD**-Ausgaben, die direkt von kompatiblen Harvesting-Systemen (z.B. Piveau/CKANN) verarbeitet werden können. +Open Data Wizard implementiert **DCAT-AP 3.0** und erzeugt valide **JSON-LD**-Ausgaben. --- @@ -57,54 +57,62 @@ Open Data Wizard implementiert **DCAT-AP 3.0** und erzeugt valide **JSON-LD**-Au Eigener Bereich im WordPress-Backend mit Übersicht, Filterung und Statusverwaltung (Entwurf / Veröffentlicht). ### 😊 Benutzerfreundliche Formularsprache -**Neu in v2.0.0:** Das Wizard-Formular wurde komplett überarbeitet, um es auch ohne DCAT-AP-Kenntnisse intuitiv zu machen: -- **Klare Fragen statt technischer Begriffe:** Statt „Herausgebende Organisation (dct:publisher)" fragt das Plugin jetzt: „Wer gibt diese Daten heraus?" -- **Hilfreiche Beispiele:** Jedes Feld hat konkrete, praxisnahe Beispiele (z.B. „Beispiel: Musterstadt Statistikamt, Umweltbundesamt") -- **Ursprüngliche Labels in Hilfetexten:** DCAT-AP Bezeichnungen und technische Details bleiben in den Hilfetexten sichtbar — nichts geht verloren -- **Validierungsmeldungen in Klartext:** Fehler zeigen neue, verständliche Feldnamen statt technischer Ausdrücke +Das Wizard-Formular wurde vollständig überarbeitet, um es auch ohne DCAT-AP-Kenntnisse intuitiv zu machen: +- **Klare Fragen statt technischer Begriffe:** Statt „Herausgebende Organisation (dct:publisher)" fragt das Plugin: „Wer gibt diese Daten heraus?" +- **Hilfreiche Beispiele:** Jedes Feld hat konkrete, praxisnahe Beispiele +- **Ursprüngliche Labels in Hilfetexten:** DCAT-AP Bezeichnungen und technische Details bleiben in den Hilfetexten sichtbar +- **Validierungsmeldungen in Klartext:** Fehler zeigen verständliche Feldnamen statt technischer Ausdrücke ### 🧭 Geführter Wizard -Fünf-Tab-Assistent mit Pflichtfeldprüfung, benutzerfreundlichen Frageformulierungen und praktischen Beispielen: +Fünf-Tab-Assistent mit Pflichtfeldprüfung und praktischen Beispielen: -1. **Pflichtangaben** — „Wer gibt diese Daten heraus?", „Worum geht es in diesem Datensatz?", „Unter welcher Lizenz sind diese Daten verfügbar?" -2. **Optionale Angaben** — „In welcher Sprache sind die Daten?", „Mit welchen Stichworten finde ich diese Daten?", „In welche Kategorie gehört dieser Datensatz?", Zeitangaben, Änderungsdatum (auto) -3. **Distribution** — „Wo können die Daten heruntergeladen werden?" (wiederholbar): Zugriffs-URL, Format, Dateigröße mit Inline-Hilfe und Beispielen -4. **Erweiterte Angaben** — „Wo finde ich mehr Informationen?", „Wie oft werden diese Daten aktualisiert?", geografische und zeitliche Abdeckung, Kontaktinformationen +1. **Grundlegende Informationen** — „Wer gibt diese Daten heraus?", „Worum geht es in diesem Datensatz?", „In welche Kategorie gehört dieser Datensatz?" +2. **Inhaltliche Angaben** — „In welcher Sprache sind die Daten?", „Mit welchen Stichworten finde ich diese Daten?", Zeitangaben, CESSDA-Themenklassifikation +3. **Datenbereitstellung** — Distributionen (wiederholbar): Zugriffs-URL, Format, Dateigröße, **Lizenz (Pflichtfeld pro Distribution)**, Zuschreibungstext +4. **Erweiterte Angaben** — Projektseite, Aktualisierungsfrequenz, geografische und zeitliche Abdeckung, Kontaktinformationen 5. **Vorschau** — generiertes JSON-LD live einsehen -**Phase 1+2 UX-Verbesserungen (v2.0.0):** Alle 19 Formularfelder wurden mit benutzergerechten Frageformulierungen statt technischen DCAT-AP-Begriffen neugestaltet. Jedes Feld hat Hilfetexte mit dem Original-Label, der DCAT-AP Bezeichnung und praktischen Beispielen. Dies macht das Plugin auch für Anfänger ohne DCAT-AP-Kenntnisse intuitiv bedienbar. +### 🏷 Lizenz-Auswahl +- Vordefinierte Auswahlliste mit gängigen offenen Lizenzen (CC0, CC-BY, ODbL u.v.m.) aus `config/licenses.txt` +- Option „Sonstige" öffnet ein Freitextfeld mit Auto-Suggest aus der Lizenzdatei +- Lizenz ist **Pflichtfeld pro Distribution** (nicht am Datensatz selbst) + +### 🎓 CESSDA-Themenklassifikation +Auto-Suggest-Feld aus der CESSDA Topic Classification 4.2.3 (95 deutsche Konzepte, SKOS/RDF, 24h Cache). ### 📎 Download-Datei (nativer wp.media Upload) -Sidebar-Meta-Box auf dem Edit-Screen — vollständig unabhängig von Carbon Fields: -- **„Datei auswählen / hochladen"**-Button öffnet den nativen WordPress Media Library Frame (`wp.media`) -- Dateivorschau zeigt den Attachment-Titel mit Dokumenten-Icon; „Entfernen"-Button mit Capability-Check -- Beim Speichern werden `_odw_file_size` (Bytes) und `_odw_file_format` (z.B. „CSV") automatisch aus der Datei berechnet und gespeichert — kein Runtime-I/O beim Shortcode-Rendering nötig -- JavaScript (`assets/js/odw-file-upload.js`, jQuery): Media-Frame-Instanz wird wiederverwendet; aktueller Dateiname via `wp_localize_script` aus PHP initialisiert -- Sicherheit: `wp_verify_nonce` + `current_user_can('edit_post')` im PHP-Save-Handler +Sidebar-Meta-Box — vollständig unabhängig von Carbon Fields: +- „Datei auswählen / hochladen"-Button öffnet den nativen WordPress Media Library Frame +- Beim Speichern werden `_odw_file_size` (Bytes) und `_odw_file_format` (z.B. „CSV") automatisch berechnet +- Sicherheit: `wp_verify_nonce` + `current_user_can('edit_post')` ### ⚙️ Einstellungsseite Untermenü unter *Datensätze → Einstellungen* mit vier Bereichen: -- **Katalog** — Titel (überschreibt DCAT-AP `dct:title` im Catalog-Endpoint) und Herausgebende Organisation -- **Standardwerte** — Standard-Lizenz und -Sprache (werden bei neuen Datensätzen vorausgefüllt) +- **Katalog** — Titel und Herausgebende Organisation +- **Standardwerte** — Standard-Sprache (wird bei neuen Datensätzen vorausgefüllt) - **REST API** — Cache-Laufzeit (60–86400 Sekunden) - **Deinstallation** — Opt-in Checkbox für vollständige Datenlöschung ### 📊 Qualitätsindikatoren -Automatische Metadaten-Vollständigkeitsprüfung nach DCAT-AP 3.0 (0–100 Punkte, Ampellogik): -- **Grün** (≥ 80 Pkt.) · **Gelb** (50–79 Pkt.) · **Rot** (< 50 Pkt.) -- Berechnung nach jedem Speichern; Ergebnis in der Admin-Listenansicht und als Meta-Box sichtbar -- Score wird im JSON-LD als `odw:qualityScore` mitgeliefert +Automatische Metadaten-Vollständigkeitsprüfung nach DCAT-AP 3.0 (0–100 Punkte, 4 Stufen): + +| Stufe | Punkte | Bedeutung | +|---|---|---| +| Perfekt | 100 | Alle Felder ausgefüllt | +| Gut | 56–99 | Über Mindestanforderung | +| Ausreichend | 55 | Genau alle Pflichtfelder | +| Verbesserungsbedarf | < 55 | Pflichtfelder unvollständig | + +Berechnung nach jedem Speichern; Ergebnis in der Admin-Listenansicht und als Meta-Box sichtbar. ### 📥 Download-Card Shortcode ``` [odw_dataset id="123"] ``` -Rendert eine strukturierte Download-Card im Frontend: Titel, Thema-Badge, Lizenz, DCAT-Qualitätsscore, Dateigröße/-format und Download-Button. CSS (`assets/css/frontend.css`) wird nur auf Seiten geladen, die den Shortcode enthalten. +Rendert eine strukturierte Download-Card im Frontend: Titel, Thema-Badge, Lizenz, Schlagwörter als Tag-Pillen, Download-Button sowie einen **Metadaten-Download-Button (JSON-LD)**. CSS (`assets/css/frontend.css`) wird nur auf Seiten geladen, die den Shortcode enthalten. ### 🔗 REST API Endpoints -Das Plugin stellt öffentliche REST API Endpoints bereit: - ``` GET https://deine-website.de/wp-json/datenatlas/v1/catalog GET https://deine-website.de/wp-json/datenatlas/v1/datasets/ @@ -115,7 +123,7 @@ Diese URLs können bei einer Open-Data-Plattform als Harvest-Quelle eingetragen **Catalog-Parameter:** `page`, `per_page`, `theme`, `license`, `format` (`jsonld` oder `json`) -**Delta-Parameter:** `since` (erforderlich, ISO 8601), `page`, `per_page`, `format` — liefert nur Datensätze, die nach dem angegebenen Zeitstempel geändert wurden, plus Tombstones für gelöschte Datensätze (ideal für inkrementelles Harvesting) +**Delta-Parameter:** `since` (erforderlich, ISO 8601), `page`, `per_page`, `format` — liefert nur Datensätze, die nach dem angegebenen Zeitstempel geändert wurden, plus Tombstones für gelöschte Datensätze ### ✅ DCAT-AP 3.0 Konformität Alle Ausgaben sind DCAT-AP 3.0 konform und in JSON-LD serialisiert. @@ -176,71 +184,81 @@ Infrastruktur → REST API, JSON-LD Serialisierung, Custom Post Type ``` open-data-wizard/ -├── open-data-wizard.php # Plugin-Header & Bootstrap (v1.9.0) +├── open-data-wizard.php # Plugin-Header & Bootstrap ├── uninstall.php # Opt-in Datenlöschung ├── composer.json -├── phpstan.neon # Statische Analyse Level 6 -├── phpunit.xml # PHPUnit 9 Konfiguration -├── vendor/ # Carbon Fields + Dev-Dependencies (gebündelt) +├── config/ +│ ├── licenses.txt # Lizenzdatei (URI | Label) +│ ├── dct-format-list.php # Dateiformate (MIME + EU-URI) +│ ├── dcat-ap-fields.php # Felddefinitionen (Qualität + Validierung) +│ ├── TopicClassification-4.2.3_de-4.2.3.rdf # CESSDA SKOS/RDF (95 Konzepte) +│ ├── phpcs.xml # PHPCS Konfiguration +│ ├── phpstan.neon # PHPStan Level 6 +│ └── phpunit.xml # PHPUnit 9 Konfiguration +├── vendor/ # Carbon Fields + Dev-Dependencies ├── includes/ │ ├── class-post-types.php # CPT-Registrierung: odw_dataset -│ ├── class-fields.php # Carbon Fields (5 Tabs), DCAT-AP Mapping, JSON-LD Builder -│ ├── class-rest-api.php # REST Endpoints /catalog + /datasets/ + /delta, Transient-Cache [v1.9.0] +│ ├── class-fields.php # Carbon Fields (5 Tabs), JSON-LD Builder +│ ├── class-rest-api.php # REST Endpoints + Transient-Cache │ ├── class-validation.php # Pflichtfeldprüfung vor Veröffentlichung -│ ├── class-quality.php # Qualitätsindikatoren (0–100 Punkte, Ampellogik) [v1.3.0] -│ ├── class-admin.php # Listenansicht, wp.media Meta-Box, Help Tabs, Assets -│ ├── class-shortcode.php # [odw_dataset]-Shortcode, Download-Card [v1.4.0] -│ ├── class-setup.php # Demo-Datensatz bei Aktivierung, Willkommens-Notice [v1.5.0] -│ ├── class-settings.php # Einstellungsseite (Catalog, Defaults, API, Cleanup) [v1.6.0] -│ └── class-cli.php # WP-CLI Befehle (Massenoperationen) [v2.0.0] +│ ├── class-quality.php # Qualitätsindikatoren (0–100, 4 Stufen) +│ ├── class-admin.php # Listenansicht, wp.media Meta-Box, Help Tabs +│ ├── class-shortcode.php # [odw_dataset]-Shortcode, Download-Card +│ ├── class-setup.php # Demo-Datensatz bei Aktivierung +│ ├── class-settings.php # Einstellungsseite +│ └── class-cli.php # WP-CLI Befehle ├── assets/ │ ├── js/ -│ │ ├── wizard-tabs.js # Tab-Navigation (Vanilla JS, kein jQuery) -│ │ └── odw-file-upload.js # wp.media Upload-Widget (jQuery) [v1.8.0] +│ │ ├── wizard-tabs.js # Tab-Navigation (Vanilla JS) +│ │ ├── odw-file-upload.js # wp.media Upload-Widget (jQuery) +│ │ └── odw-admin-fields.js # License/CESSDA Auto-Suggest, Dateigröße-Widget │ ├── css/ -│ │ ├── admin.css # Admin-Styles (CSS Custom Properties) -│ │ └── frontend.css # Shortcode Download-Card (strukturell, kein Theme-Overhead) +│ │ ├── admin.css # Admin-Styles +│ │ └── frontend.css # Shortcode Download-Card │ └── sample/ -│ └── beispiel-datensatz.csv # Demo-Datensatz für die Aktivierungs-Installation +│ └── beispiel-datensatz.csv # Demo-Datensatz CSV ├── tests/ -│ ├── bootstrap.php # PHPUnit + WP_Mock Bootstrap -│ ├── test-fields.php # Tests: ODW_Fields (Lizenz, Format, Pflichtfelder) -│ ├── test-settings.php # Tests: ODW_Settings (get(), filter_catalog_title()) -│ ├── test-quality.php # Tests: ODW_Quality (Scoring, Level, Meta) -│ ├── test-shortcode.php # Tests: ODW_Shortcode (format_bytes, render edge cases) -│ └── test-fields-extended.php # Tests: JSON-LD Builder v1.7.0 Felder -├── .github/workflows/ci.yml # CI: PHPCS + PHPStan + PHPUnit (PHP 8.1–8.3) -└── languages/ +│ ├── bootstrap.php +│ ├── test-fields.php +│ ├── test-fields-extended.php +│ ├── test-quality.php +│ ├── test-settings.php +│ ├── test-shortcode.php +│ ├── test-rest-delta.php +│ └── test-cli.php +└── .github/workflows/ci.yml # CI: PHPCS + PHPStan + PHPUnit (PHP 8.1–8.3) ``` ### Feldmapping DCAT-AP 3.0 -#### Tab 1 — Pflichtangaben +#### Tab 1 — Grundlegende Informationen | Feld | DCAT-AP Prädikat | Pflicht | |---|---|---| | Titel | `dct:title` | ✓ | | Beschreibung | `dct:description` | ✓ | | Herausgeber | `dct:publisher` → `foaf:Organization` | ✓ | -| Lizenz | `dct:license` (URI) | ✓ | +| Thema | `dcat:theme` | — | -#### Tab 2 — Optionale Angaben +#### Tab 2 — Inhaltliche Angaben | Feld | DCAT-AP Prädikat | Pflicht | |---|---|---| | Sprache | `dct:language` | — | | Schlagworte (eine pro Zeile) | `dcat:keyword` | — | -| Thema | `dcat:theme` | — | | Veröffentlichungsdatum | `dct:issued` | — | | Änderungsdatum | `dct:modified` (auto) | — | +| CESSDA Themenklassifikation | `cessda:topic` | — | -#### Tab 3 — Distribution +#### Tab 3 — Datenbereitstellung (Distribution) | Feld | DCAT-AP Prädikat | Pflicht | |---|---|---| | Zugriffs-URL | `dcat:accessURL` | ✓ (min. 1) | | Format | `dct:format` (MIME) | — | -| Dateigröße in Bytes | `dcat:byteSize` | — | +| Dateigröße | `dcat:byteSize` | — | +| Lizenz | `dct:license` (URI) | ✓ pro Distribution | +| Zuschreibungstext | `odrl:attribution` | — | #### Tab 4 — Erweiterte Angaben @@ -259,9 +277,9 @@ open-data-wizard/ | Feld | Interner Meta-Key | Beschreibung | |---|---|---| -| Attachment-ID | `_odw_file_id` | Mediathek-Datei (wird als Download-Button im Shortcode verwendet) | +| Attachment-ID | `_odw_file_id` | Mediathek-Datei | | Dateigröße (auto) | `_odw_file_size` | Bytes, auto-berechnet beim Speichern | -| Dateiformat (auto) | `_odw_file_format` | Großbuchstaben-Extension (z.B. „CSV"), auto-berechnet | +| Dateiformat (auto) | `_odw_file_format` | z.B. „CSV", auto-berechnet | ### REST API @@ -269,14 +287,13 @@ open-data-wizard/ ``` GET /wp-json/datenatlas/v1/catalog ``` -Liefert alle veröffentlichten Datasets als `dcat:Catalog` in JSON-LD. - -| Parameter | Standard | Beschreibung | -|------------|----------|----------------------------------------------------| -| `page` | 1 | Seitennummer | -| `per_page` | 20 | Einträge pro Seite (max. 100) | -| `theme` | – | Filter nach Thema (z.B. `Bildung`) | -| `license` | – | Filter: Kurzform (`cc-by`) oder volle URI | + +| Parameter | Standard | Beschreibung | +|------------|----------|--------------| +| `page` | 1 | Seitennummer | +| `per_page` | 20 | Einträge pro Seite (max. 100) | +| `theme` | – | Filter nach Thema | +| `license` | – | Filter: Kurzform (`cc-by`) oder volle URI | | `format` | `jsonld` | `jsonld` → `application/ld+json`, `json` → `application/json` | Response-Header: `X-WP-Total`, `X-WP-TotalPages`, `X-ODW-Cache` (`HIT`/`MISS`) @@ -290,20 +307,15 @@ GET /wp-json/datenatlas/v1/datasets/ ``` GET /wp-json/datenatlas/v1/delta?since=2024-01-01T00:00:00Z ``` -Liefert nur Datensätze, die nach dem `since`-Zeitstempel geändert wurden, plus Tombstone-Einträge für gelöschte Datensätze. -| Parameter | Standard | Beschreibung | -|------------|----------|----------------------------------------------------| -| `since` | erforderlich | ISO 8601 Datetime (z.B. `2024-01-01`, `2024-06-15T12:30:00Z`) — nur Änderungen nach dieser Zeit | -| `page` | 1 | Seitennummer (für geänderte Datasets) | -| `per_page` | 20 | Einträge pro Seite (max. 100); gilt nur für geänderte Datasets, nicht für Tombstones | -| `format` | `jsonld` | `jsonld` → `application/ld+json`, `json` → `application/json` | - -Response-Header: `X-ODW-Delta-Since` (echo des Parameters), `X-ODW-Generated-At` (aktueller Zeitstempel — nutze ihn als nächster `since` Wert), `X-WP-Total`, `X-WP-TotalPages`, `X-ODW-Cache` +| Parameter | Standard | Beschreibung | +|------------|----------|--------------| +| `since` | erforderlich | ISO 8601 Datetime | +| `page` | 1 | Seitennummer | +| `per_page` | 20 | Einträge pro Seite (max. 100) | +| `format` | `jsonld` | Content-Type | -Response-Body enthält: -- `dcat:dataset` — Array mit vollständigen JSON-LD Objekten aller geänderten Datasets -- `odw:removed` — Array mit Tombstone-Objekten (`@id`, `@type`, `odw:removedAt`) für gelöschte Datasets +Response enthält `dcat:dataset` (geänderte Datasets) und `odw:removed` (Tombstones). #### Beispiel-Response @@ -326,21 +338,15 @@ Response-Body enthält: "dct:title": "Mitgliederdaten 2023", "dct:description": "Anonymisierte Mitgliederstatistik.", "dct:publisher": { "@type": "foaf:Organization", "foaf:name": "Musterorganisation e.V." }, - "dct:license": "https://creativecommons.org/licenses/by/4.0/", "dcat:distribution": [ { "@type": "dcat:Distribution", "dcat:accessURL": "https://organisation.de/daten/mitglieder.csv", "dct:format": "text/csv", + "dct:license": "https://creativecommons.org/licenses/by/4.0/", "dcat:byteSize": 20480 } - ], - "dcat:contactPoint": { - "@type": "vcard:Organization", - "vcard:fn": "Open Data Team", - "vcard:hasEmail": "mailto:opendata@organisation.de" - }, - "odw:qualityScore": { "odw:score": 85, "odw:maxScore": 100, "odw:level": "high" } + ] } ] } @@ -348,14 +354,12 @@ Response-Body enthält: ### Erweiterbarkeit -Das Plugin stellt folgende WordPress-Filter zur Erweiterung bereit: - -| Hook | Beschreibung | -|-----------------------|---------------------------------------------------| -| `odw_license_options` | Weitere Lizenz-Optionen hinzufügen | -| `odw_theme_options` | Weitere Thema-Optionen hinzufügen | -| `odw_dataset_jsonld` | JSON-LD Array vor Ausgabe anpassen | -| `odw_catalog_title` | Catalog-Titel anpassen | +| Hook | Beschreibung | +|-----------------------|--------------| +| `odw_license_options` | Weitere Lizenz-Optionen hinzufügen | +| `odw_theme_options` | Weitere Thema-Optionen hinzufügen | +| `odw_dataset_jsonld` | JSON-LD Array vor Ausgabe anpassen | +| `odw_catalog_title` | Catalog-Titel anpassen | ```php // Eigene Lizenz hinzufügen @@ -366,15 +370,13 @@ add_filter( 'odw_license_options', function( array $options ): array { // JSON-LD Dataset anpassen add_filter( 'odw_dataset_jsonld', function( array $jsonld, int $post_id ): array { - $jsonld['dct:spatial'] = 'https://sws.geonames.org/2921044/'; // Deutschland + $jsonld['dct:spatial'] = 'https://sws.geonames.org/2921044/'; return $jsonld; }, 10, 2 ); ``` ### WP-CLI Befehle -Das Plugin stellt WP-CLI Befehle für Massenoperationen bereit: - ```bash # Qualitätsscores für alle veröffentlichten Datasets neu berechnen wp open-data-wizard quality recalculate @@ -386,11 +388,6 @@ wp open-data-wizard quality recalculate --all wp open-data-wizard cache clear ``` -Diese Befehle sind nützlich für: -- Regelmäßige Qualitäts-Neubewertung nach Massenänderungen -- Cache-Invalidierung nach manuellen DB-Eingriffen oder Migrationen -- Automatisierung via Cron-Jobs oder CI/CD-Pipelines - --- ### Abhängigkeiten @@ -407,26 +404,27 @@ Diese Befehle sind nützlich für: ## Roadmap -**Abgeschlossen (v1.0 — v2.0.0):** -- [x] Custom Post Type `odw_dataset` mit vollständiger DCAT-AP 3.0 Unterstützung — v1.0.0 -- [x] Five-Tab Wizard-Formular mit Validierung und Hilfetexten — v1.0.0 -- [x] REST API Endpoints: `/catalog`, `/datasets/`, `/delta?since=` — v1.9.0 -- [x] Qualitätsindikatoren / Ampellogik (0–100 Punkte) — v1.3.0 -- [x] Download-Card Shortcode `[odw_dataset]` — v1.4.0 -- [x] Demo-Datensatz bei Aktivierung — v1.5.0 -- [x] Einstellungsseite (Catalog-Titel, Defaults, API, Cleanup) — v1.6.0 -- [x] Erweiterte DCAT-AP Felder (Tab 4) — v1.7.0 -- [x] Nativer wp.media Upload-Widget — v1.8.0 -- [x] **Benutzerfreundliche UX-Überarbeitung (Phase 1+2)** — v2.0.0 -- [x] WP-CLI Befehle für Massenoperationen — v2.0.0 - -**In Planung (v2.1+):** -- [ ] Push/Webhook bei Statusänderung an Piveau +**Abgeschlossen (v1.0 — v2.1):** +- [x] Custom Post Type `odw_dataset` mit DCAT-AP 3.0 Unterstützung +- [x] Five-Tab Wizard-Formular mit Validierung und Hilfetexten +- [x] REST API Endpoints: `/catalog`, `/datasets/`, `/delta?since=` +- [x] Qualitätsindikatoren / 4-Stufen-Ampellogik (Perfekt / Gut / Ausreichend / Verbesserungsbedarf) +- [x] Download-Card Shortcode `[odw_dataset]` mit Keywords und Metadaten-Download +- [x] Demo-Datensatz bei Aktivierung +- [x] Einstellungsseite (Catalog-Titel, Defaults, API, Cleanup) +- [x] Erweiterte DCAT-AP Felder (Tab 4) +- [x] Nativer wp.media Upload-Widget +- [x] Benutzerfreundliche UX-Überarbeitung +- [x] WP-CLI Befehle für Massenoperationen +- [x] Lizenz pro Distribution (DCAT-AP-konform) +- [x] CESSDA-Themenklassifikation (Auto-Suggest aus SKOS/RDF) +- [x] Externe Konfigurationsdateien (licenses.txt, dct-format-list.php, dcat-ap-fields.php) + +**In Planung (v2.2+):** - [ ] Content Negotiation: Turtle / RDF-XML Ausgabe - [ ] Gutenberg Block für die Download-Card - [ ] Mehrsprachigkeit (WPML/Polylang) -- [ ] CESSDA-Felder als optionales Profil -- [ ] Phase 3 UX: Tooltip-Popups, Video-Tutorials, Wizard-Vorschau +- [ ] Phase 3 UX: Tooltip-Popups, Wizard-Vorschau --- @@ -436,19 +434,13 @@ Beiträge sind willkommen — ob Fehlermeldungen, Verbesserungsvorschläge oder Bitte öffne zunächst ein [Issue](https://github.com/daimpad/OpenDataWizard/issues), bevor du größere Änderungen einreichst. -```bash -git checkout -b feature/mein-feature -git commit -m "Add: Kurzbeschreibung" -git push origin feature/mein-feature -``` - --- ## Deinstallation Das Plugin löscht bei Deinstallation standardmäßig **keine** Daten (Opt-in). -Um alle Plugin-Daten zu löschen, die Checkbox unter **Datensätze → Einstellungen → Deinstallation** aktivieren und dann das Plugin im WordPress-Backend deinstallieren. `uninstall.php` entfernt in diesem Fall alle `odw_dataset`-Posts, alle `_odw_*`-Metafelder sowie die Plugin-Optionen (`odw_settings`, `odw_demo_post_id`, `odw_show_welcome`). +Um alle Plugin-Daten zu löschen, die Checkbox unter **Datensätze → Einstellungen → Deinstallation** aktivieren und dann das Plugin im WordPress-Backend deinstallieren. `uninstall.php` entfernt alle `odw_dataset`-Posts, alle `_odw_*`-Metafelder sowie die Plugin-Optionen. --- diff --git a/docs/I18N_IMPLEMENTATION.md b/docs/I18N_IMPLEMENTATION.md deleted file mode 100644 index 1f1851a..0000000 --- a/docs/I18N_IMPLEMENTATION.md +++ /dev/null @@ -1,828 +0,0 @@ -# Internationalization (i18n) Implementation Plan für Open Data Wizard - -**Feature:** Plugin-Übersetzung in Englisch (English localization) -**Version:** v1.10.0+ -**Geschätzter Aufwand:** 5-7 Stunden -**Status:** Geplant (nicht implementiert) -**Erstellt:** 2026-04-22 - ---- - -## Inhaltsverzeichnis - -1. [Überblick](#überblick) -2. [Analyse Ergebnisse](#analyse-ergebnisse) -3. [Phase 1: Setup & POT-Generierung](#phase-1-setup--pot-generierung) -4. [Phase 2: Englische Übersetzung](#phase-2-englische-übersetzung) -5. [Phase 3: Kompilierung & Testing](#phase-3-kompilierung--testing) -6. [Phase 4: Dokumentation & Automation](#phase-4-dokumentation--automation) -7. [Checkliste](#checkliste) - ---- - -## Überblick - -Das Open Data Wizard Plugin soll in mehreren Sprachen verfügbar sein. Zuerst wird die englische Übersetzung implementiert, dann können weitere Sprachen folgen. - -### Aktueller Status - -- ✅ **Text-Domain richtig**: `open-data-wizard` (überall konsistent verwendet) -- ✅ **i18n Funktionen**: Alle Strings sind mit `__()`, `_e()`, `_x()`, `esc_html__()` etc. wrappingiert -- ✅ **load_plugin_textdomain()**: Bereits in `open-data-wizard.php` (Zeile 155-162) konfiguriert -- ✅ **Languages Verzeichnis**: `/languages/` existiert (aktuell leer) -- ⚠️ **Translation Files**: .pot, .po, .mo Dateien fehlen - -### Translatable Strings - -**Analyseergebnis: 219+ unique translatable Strings** - -**Verteilung nach Datei:** -| Datei | Strings | Priorität | -|-------|---------|-----------| -| class-fields.php | ~45 | Hoch (User-facing Form Labels) | -| class-post-types.php | ~40 | Hoch (CPT Labels) | -| class-settings.php | ~35 | Hoch (Settings UI) | -| class-admin.php | ~25 | Hoch (Admin UI) | -| class-quality.php | ~20 | Mittel (Quality Indicators) | -| class-validation.php | ~15 | Mittel (Error Messages) | -| class-rest-api.php | ~12 | Mittel (API Responses) | -| class-shortcode.php | ~10 | Mittel (Frontend) | -| class-cli.php | ~8 | Niedrig (CLI) | -| open-data-wizard.php | ~2 | Niedrig (Plugin Header) | -| class-webhooks.php | ~4 | Niedrig (Webhook Messages) | - ---- - -## Analyse Ergebnisse - -### String Scanning Details - -Alle gescannten Strings sind korrekt mit WordPress i18n Funktionen wrappingiert: - -✅ **Korrekte Pattern gefunden:** -```php -// Standard Translation -__( 'Datensätze', 'open-data-wizard' ) -_e( 'Datensatz bearbeiten', 'open-data-wizard' ) - -// Mit Kontext (Disambiguation) -_x( 'Datensätze', 'Post Type General Name', 'open-data-wizard' ) - -// Mit Escaping -esc_html__( 'Beschreibung', 'open-data-wizard' ) -esc_attr__( 'Titel', 'open-data-wizard' ) - -// Mit sprintf/Pluralisierung -sprintf( __( 'Recalculated quality scores for %d dataset(s).', 'open-data-wizard' ), $count ) -_n( 'Dataset', 'Datasets', $count, 'open-data-wizard' ) -``` - -❌ **Nicht zu übersetzen (korrekt ausgeschlossen):** -- Namespace URIs: `dcat:`, `dct:`, `foaf:`, etc. -- Meta Keys: `_odw_*`, `odw_dataset` -- Code Kommentare -- Regex Patterns -- Debug-Ausgaben - ---- - -## Phase 1: Setup & POT-Generierung - -### 1.1 Systemvoraussetzungen - -**Option A: Gettext Tools (Empfohlen)** -```bash -# Installation -apt-get install gettext # Debian/Ubuntu -brew install gettext # macOS - -# Verfügbare Tools nach Installation -xgettext # String-Extraktion aus Quellcode -msgmerge # POT mit existierenden PO mergen -msgfmt # PO → MO kompilieren -``` - -**Option B: WP-CLI (Alternative)** -```bash -# Funktioniert mit WordPress Installation -wp i18n make-pot . languages/open-data-wizard.pot --domain=open-data-wizard -``` - -**Option C: PHP-Script (Fallback, kein System-Tool nötig)** -- Custom Regex-basierter Parser -- Länger, aber portable - -**Empfehlung für Projekt:** Option A (Gettext Tools) verwenden - -### 1.2 Verzeichnisstruktur vorbereiten - -**Zielstruktur nach Implementierung:** - -``` -/languages/ -├── open-data-wizard.pot (Template mit allen Strings) -├── open-data-wizard-en_US.po (English Quelle) -├── open-data-wizard-en_US.mo (English kompiliert, Binary) -│ -├── [ZUKÜNFTIG] -├── open-data-wizard-de_DE.po (German, falls nötig) -├── open-data-wizard-de_DE.mo -├── open-data-wizard-fr_FR.po (French, falls nötig) -└── open-data-wizard-fr_FR.mo -``` - -### 1.3 POT-Generierung - -#### Schritt A: Mit Gettext Tools - -```bash -# Kommando ausführen im Plugin-Root-Verzeichnis -xgettext \ - --language=PHP \ - --keyword=__ \ - --keyword=_e \ - --keyword=_x:1,2c \ - --keyword=_n:1,2 \ - --keyword=_nx:1,2,4c \ - --keyword=esc_html__ \ - --keyword=esc_attr__ \ - --keyword=esc_attr_e \ - --keyword=esc_html_e \ - --add-comments=translators \ - --sort-output \ - --package-name="Open Data Wizard" \ - --msgid-bugs-address="https://github.com/daimpad/OpenDataWizard/issues" \ - -o languages/open-data-wizard.pot \ - $(find . -name "*.php" ! -path "./vendor/*" ! -path "./tests/*") -``` - -**Was dieses Kommando macht:** -- `--language=PHP` — Parser für PHP-Syntax -- `--keyword=__` — Findet alle `__('string')` Aufrufe -- `--keyword=_x:1,2c` — Findet `_x()` mit Kontext (2. Parameter) -- `--keyword=_n:1,2` — Findet `_n()` für Pluralisierung (singular + plural) -- `--add-comments=translators` — Inkludiert `// translators:` Kommentare -- `--sort-output` — Sortiert Einträge alphabetisch -- `-o` — Ausgabedatei - -#### Ergebnis: `open-data-wizard.pot` - -**Dateiformat:** -``` -# Open Data Wizard Translations -# Copyright (C) 2026 nozilla -# This file is distributed under the same license as the Open Data Wizard package. -# -msgid "" -msgstr "" -"Project-Id-Version: Open Data Wizard 1.9.0\n" -"Report-Msgid-Bugs-To: https://github.com/daimpad/OpenDataWizard/issues\n" -"POT-Creation-Date: 2026-04-22 10:30+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \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" - -#: includes/class-post-types.php:41 -msgid "Datensätze" -msgstr "" - -#: includes/class-post-types.php:42 -msgid "Datensatz" -msgstr "" - -#: includes/class-post-types.php:44 -msgid "Open Data Wizard" -msgstr "" - -... (216 weitere Einträge) -``` - -**Validierung der POT-Datei:** - -```bash -# Syntax-Check -msgfmt -c languages/open-data-wizard.pot - -# Zeilenanzahl prüfen (sollte ~400-500 Zeilen sein) -wc -l languages/open-data-wizard.pot - -# Einträge zählen (sollte ~219 msgid-Blöcke sein) -grep -c '^msgid ' languages/open-data-wizard.pot -``` - -### 1.4 Qualitätsprüfung POT - -**Checkliste für generierte POT:** - -- [ ] Keine Duplicate msgid-Einträge (`grep '^msgid ' | sort | uniq -d` sollte leer sein) -- [ ] Alle Strings haben 'open-data-wizard' Text Domain (nicht 'default' o.ä.) -- [ ] Header mit korrekter Charset (UTF-8) -- [ ] Quellcode-Referenzen (`#: includes/...`) sind aktuell -- [ ] Pluralisierungen korrekt geflaggt (msgid_plural vorhanden) -- [ ] Keine Debug-Strings oder Code-Kommentare -- [ ] Filesize ~15-25 KB (normalerweise) - ---- - -## Phase 2: Englische Übersetzung - -### 2.1 English Translation File (.po) erstellen - -**Vorgang:** -1. .pot Datei kopieren → en_US.po -2. Header aktualisieren (Sprache, Translator-Info) -3. Alle 219 Strings von Deutsch → Englisch übersetzen - -**Datei:** `/languages/open-data-wizard-en_US.po` - -#### Header anpassen - -```po -# English translation for Open Data Wizard -# Copyright (C) 2026 nozilla -# This file is distributed under the same license as the Open Data Wizard package. -# -msgid "" -msgstr "" -"Project-Id-Version: Open Data Wizard 1.9.0\n" -"Report-Msgid-Bugs-To: https://github.com/daimpad/OpenDataWizard/issues\n" -"POT-Creation-Date: 2026-04-22 10:30+0000\n" -"PO-Revision-Date: 2026-04-22 10:30+0000\n" -"Last-Translator: Open Data Wizard Contributors\n" -"Language-Team: English\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" -"X-Generator: Poedit 3.0\n" - -#: includes/class-post-types.php:41 -msgid "Datensätze" -msgstr "Datasets" - -#: includes/class-post-types.php:42 -msgid "Datensatz" -msgstr "Dataset" - -... (216 weitere Übersetzungen) -``` - -### 2.2 Übersetzungsliste (219 Strings) - -**Zu übersetzende Strings (Priorität nach Nutzer-Sichtbarkeit):** - -#### CPT & Admin Labels (Hoch-Priorität) - -| Deutsch | English | Datei | Notizen | -|---------|---------|-------|---------| -| Datensätze | Datasets | class-post-types.php | Post Type General Name | -| Datensatz | Dataset | class-post-types.php | Post Type Singular Name | -| Open Data Wizard | Open Data Wizard | class-post-types.php | Menu Name (bleibt gleich) | -| Neuen Datensatz anlegen | Create New Dataset | class-post-types.php | Add New Item | -| Alle Datensätze | All Datasets | class-post-types.php | All Items | -| Datensätze suchen | Search Datasets | class-post-types.php | Search Items | -| Datensatz bearbeiten | Edit Dataset | class-post-types.php | Edit Item | -| Datensatz ansehen | View Dataset | class-post-types.php | View Item | -| Keine Datensätze gefunden | No datasets found | class-post-types.php | Not Found | -| DCAT-AP 3.0 konforme Datensatz-Metadaten | DCAT-AP 3.0 compliant dataset metadata | class-post-types.php | Description | - -#### Settings & Form Labels (Hoch-Priorität) - -| Deutsch | English | Datei | Kontext | -|---------|---------|-------|---------| -| Pflichtangaben | Mandatory Fields | class-fields.php | Tab 1 Title | -| Optionale Angaben | Optional Fields | class-fields.php | Tab 2 Title | -| Distribution | Distribution | class-fields.php | Tab 3 Title | -| Erweiterte Angaben | Advanced Fields | class-fields.php | Tab 4 Title | -| Vorschau | Preview | class-fields.php | Tab 5 Title | -| Titel | Title | class-fields.php | dct:title | -| Beschreibung | Description | class-fields.php | dct:description | -| Herausgeber | Publisher | class-fields.php | dct:publisher | -| Lizenz | License | class-fields.php | dct:license | -| Sprache | Language | class-fields.php | dct:language | -| Schlagworte | Keywords | class-fields.php | dcat:keyword | -| Thema | Theme | class-fields.php | dcat:theme | -| Datum | Date | class-fields.php | temporal | -| Zugriffs-URL | Access URL | class-fields.php | dcat:accessURL | -| Format | Format | class-fields.php | dct:format | -| Dateigröße | File Size | class-fields.php | dcat:byteSize | -| ... (weitere ~30) | ... | ... | ... | - -#### Validierungs- & Error Messages (Mittel-Priorität) - -| Deutsch | English | Datei | Kontext | -|---------|---------|-------|---------| -| Pflichtfelder fehlen | Required fields missing | class-validation.php | Error Notice | -| Bitte füllen Sie alle Pflichtfelder aus | Please fill in all required fields | class-validation.php | Help Text | -| Datensatz konnte nicht gespeichert werden | Dataset could not be saved | class-validation.php | Error | - -#### Quality & Admin (Mittel-Priorität) - -| Deutsch | English | Datei | Kontext | -|---------|---------|-------|---------| -| Qualitätsscore | Quality Score | class-quality.php | Badge Label | -| Sehr gut | Excellent | class-quality.php | Quality Level | -| Befriedigend | Satisfactory | class-quality.php | Quality Level | -| Verbesserungsbedürftig | Needs Improvement | class-quality.php | Quality Level | - -#### REST API & Hooks (Niedrig-Priorität) - -| Deutsch | English | Datei | Kontext | -|---------|---------|-------|---------| -| Katalog | Catalog | class-rest-api.php | API Response | -| Veröffentlichte Datasets | Published Datasets | class-rest-api.php | Filter | - -#### Webhooks (Niedrig-Priorität) - -| Deutsch | English | Datei | Kontext | -|---------|---------|-------|---------| -| Webhooks | Webhooks | class-settings.php | Settings Section | -| Webhook URL | Webhook URL | class-settings.php | Settings Field | -| API Token | API Token | class-settings.php | Settings Field | -| Webhook gesendet | Webhook sent | class-webhooks.php | Log Entry | - -**Gesamtzahl: ~219 einzigartige Strings** - -### 2.3 Übersetzungs-Guidelines - -#### Allgemeine Regeln - -1. **Terminologie konsistent halten** - - "Datensatz" → immer "Dataset" (nicht "Data Set" oder "Data Record") - - "Herausgeber" → "Publisher" (nicht "Editor" = verwirrend) - - "Lizenz" → "License" (US Standard Schreibweise) - -2. **Proper Nouns nicht übersetzen** - - "DCAT-AP 3.0" bleibt gleich - - "Civora", "Piveau" → bleibt gleich - - "JSON-LD" → bleibt gleich - -3. **Markup & HTML bewahren** - - `
`, ``, `` bleiben in msgstr - - Beispiel: `"Text mit Betonung"` → `"Text with emphasis"` - -4. **Pluralisierung korrekt handhaben** - - Deutsch: `_n( 'Datensatz', 'Datensätze', $count )` - - Englisch: `msgid` = "Dataset", `msgid_plural` = "Datasets" - - Im .po File: msgstr[0] und msgstr[1] - -5. **Variablen und Platzhalter bewahren** - - `%d` (Zahlen) und `%s` (Strings) müssen erhalten bleiben - - Beispiel: `"Recalculated quality scores for %d dataset(s)."` bleibt mit `%d` - -6. **Kontex-Strings prüfen** - - `_x('Datensätze', 'Post Type General Name', 'open-data-wizard')` - - msgctxt muss vorhanden sein in .po Datei - -#### WordPress Terminologie (Glossary) - -Benutze diese englischen Begriffe für Konsistenz mit WordPress Standard: - -| Deutsch | English (WordPress Standard) | -|---------|---| -| Datensatz/Post | Dataset/Post | -| Entwurf | Draft | -| Veröffentlicht | Published | -| Privatpost | Private | -| Gelöscht | Trash | -| Veröffentlichen | Publish | -| Speichern | Save | -| Bearbeiten | Edit | -| Löschen | Delete | -| Vorschau | Preview | -| Ansicht | View | - ---- - -## Phase 3: Kompilierung & Testing - -### 3.1 PO → MO Kompilierung - -**Kommando:** -```bash -msgfmt languages/open-data-wizard-en_US.po -o languages/open-data-wizard-en_US.mo -``` - -**Validierung:** -```bash -# Syntax-Check vor Kompilierung -msgfmt -c languages/open-data-wizard-en_US.po - -# MO File wurde erstellt (sollte ~50-100 KB sein) -ls -lh languages/open-data-wizard-en_US.mo - -# Binary Format prüfen -file languages/open-data-wizard-en_US.mo -# Erwartete Ausgabe: "GNU gettext message catalogue" -``` - -### 3.2 Testing in WordPress - -#### Test-Setup - -**1. WordPress Config für English setzen:** -```php -// In wp-config.php oder mu-plugin: -define( 'WPLANG', 'en_US' ); -``` - -**2. Plugin aktivieren und Settings prüfen:** -- Admin Menu sollte englisch sein: "Open Data Wizard" → "All Datasets" -- CPT Labels: "Create New Dataset", "Edit Dataset", etc. - -#### Spot-Check Locations - -- [ ] **Admin Menu** → "Open Data Wizard" (sollte Englisch sein) -- [ ] **CPT Labels** → "All Datasets", "Edit Dataset", etc. -- [ ] **Settings Page** → "Datasets" → "Settings" - - [ ] Section Headers - - [ ] Field Labels - - [ ] Help Text -- [ ] **Edit Screen** - - [ ] Tab Titles (Mandatory Fields, Optional Fields, etc.) - - [ ] Field Labels - - [ ] Help Text - - [ ] Meta-Box Titles -- [ ] **Admin List View** - - [ ] Column Headers - - [ ] Filter Dropdowns -- [ ] **Validation Messages** - - [ ] Try to publish with missing required field - - [ ] Error notice should be in English -- [ ] **Shortcode Output** (Frontend) - - [ ] Button Labels - - [ ] Status Text - -#### Fehler-Handling - -**Wenn Englisch nicht angezeigt wird:** - -1. **MO-Datei korrekt kompiliert?** - ```bash - strings languages/open-data-wizard-en_US.mo | head -20 - # Sollte englische Strings enthalten - ``` - -2. **Dateiname korrekt?** - - Muss `open-data-wizard-en_US.po` und `open-data-wizard-en_US.mo` sein - - Text Domain in Code muss `open-data-wizard` sein - -3. **WordPress Locale erkannt?** - ```php - // In Admin, Test mit: - echo get_locale(); // Sollte 'en_US' sein - ``` - -4. **Plugin Load-Order?** - - `load_plugin_textdomain()` wird auf `init` Hook (Prio default) aufgerufen - - Sollte vor anderen i18n Operationen laufen - ---- - -## Phase 4: Dokumentation & Automation - -### 4.1 Extraction Script erstellen - -**Datei:** `/scripts/generate-pot.sh` - -```bash -#!/bin/bash -# Generate POT translation template file -# Usage: ./scripts/generate-pot.sh - -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PLUGIN_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -POT_FILE="$PLUGIN_DIR/languages/open-data-wizard.pot" - -# Check if xgettext is installed -if ! command -v xgettext &> /dev/null; then - echo "Error: xgettext not found" - echo "Install with: apt-get install gettext (Debian/Ubuntu) or brew install gettext (macOS)" - exit 1 -fi - -echo "Generating POT file..." - -xgetect \ - --language=PHP \ - --keyword=__ \ - --keyword=_e \ - --keyword=_x:1,2c \ - --keyword=_n:1,2 \ - --keyword=_nx:1,2,4c \ - --keyword=esc_html__ \ - --keyword=esc_attr__ \ - --keyword=esc_attr_e \ - --keyword=esc_html_e \ - --add-comments=translators \ - --sort-output \ - --package-name="Open Data Wizard" \ - --msgid-bugs-address="https://github.com/daimpad/OpenDataWizard/issues" \ - -o "$POT_FILE" \ - $(find "$PLUGIN_DIR" -name "*.php" ! -path "*/vendor/*" ! -path "*/tests/*") - -echo "✓ POT file generated: $POT_FILE" - -# Validation -echo "Validating..." -msgfmt -c "$POT_FILE" -echo "✓ POT file is valid" - -# Count strings -COUNT=$(grep -c '^msgid ' "$POT_FILE" || echo 0) -echo "✓ Found $COUNT translatable strings" -``` - -**Nutzung:** -```bash -chmod +x scripts/generate-pot.sh -./scripts/generate-pot.sh -``` - -### 4.2 Übersetzungs-Contributions Guide - -**Datei:** `/docs/TRANSLATIONS.md` - -```markdown -# Translation Guide for Open Data Wizard - -## Adding Translations - -### For English (en_US) - -1. **Generate POT file** (Template) - ```bash - ./scripts/generate-pot.sh - ``` - -2. **Copy template to English translation** - ```bash - cp languages/open-data-wizard.pot languages/open-data-wizard-en_US.po - ``` - -3. **Edit with Poedit or text editor** - - Update header with language "en_US" - - Translate all German msgid → English msgstr - - Save file - -4. **Compile to binary format** - ```bash - msgfmt languages/open-data-wizard-en_US.po -o languages/open-data-wizard-en_US.mo - ``` - -5. **Test in WordPress** - - Set WPLANG = 'en_US' in wp-config.php - - Verify Admin UI, Settings, Messages are in English - -### For Other Languages (e.g., French) - -```bash -# Copy template -cp languages/open-data-wizard.pot languages/open-data-wizard-fr_FR.po - -# Edit fr_FR.po with your translations -# nano languages/open-data-wizard-fr_FR.po - -# Compile -msgfmt languages/open-data-wizard-fr_FR.po -o languages/open-data-wizard-fr_FR.mo - -# Test by setting WPLANG = 'fr_FR' -``` - -## Tools - -- **Poedit** (desktop, GUI): https://poedit.net/ -- **Weblate** (online, collaborative): https://weblate.org/ -- **Crowdin** (professional, GitHub sync): https://crowdin.com/ - -## Adding New Translatable Strings - -When adding new strings to the code: - -1. Always wrap in translation function: - ```php - __( 'String to translate', 'open-data-wizard' ) - _e( 'String to output', 'open-data-wizard' ) - esc_html__( 'Safe string', 'open-data-wizard' ) - ``` - -2. Use context for disambiguation: - ```php - _x( 'Datasets', 'Post Type General Name', 'open-data-wizard' ) - ``` - -3. Regenerate POT file: - ```bash - ./scripts/generate-pot.sh - ``` - -4. Update all .po files: - ```bash - msgmerge -U languages/open-data-wizard-en_US.po languages/open-data-wizard.pot - ``` - -5. Retranslate new strings in .po file - -6. Recompile .mo file - -## Pluralization - -For strings with plural forms: - -```php -ngettext( - 'One dataset', - '%d datasets', - $count, - 'open-data-wizard' -) -``` - -In .po file, this becomes: -```po -#: file.php:line -msgid "One dataset" -msgid_plural "%d datasets" -msgstr[0] "Ein Datensatz" -msgstr[1] "%d Datensätze" -``` -``` - -### 4.3 GitHub Actions Automation (Optional) - -**Datei:** `/.github/workflows/generate-pot.yml` - -```yaml -name: Generate POT Translation Template - -on: - push: - branches: [ main ] - paths: - - '**.php' - - '.github/workflows/generate-pot.yml' - -jobs: - pot: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Install gettext - run: sudo apt-get install -y gettext - - - name: Generate POT - run: ./scripts/generate-pot.sh - - - name: Commit POT if changed - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: "chore: regenerate POT translation template" - file_pattern: languages/open-data-wizard.pot - commit_options: '--no-verify' -``` - ---- - -## Implementation Checkliste - -### Vorbereitung - -- [ ] Gettext Tools installiert (`apt-get install gettext`) -- [ ] Git-Branch verifiziert: `claude/wordpress-open-data-wizard-MmOEL` -- [ ] Keine ungespeicherten Änderungen im Repository - -### Phase 1: POT-Generierung - -- [ ] `./scripts/generate-pot.sh` ausgeführt -- [ ] `languages/open-data-wizard.pot` erstellt (~15-25 KB) -- [ ] POT mit `msgfmt -c` validiert ✓ -- [ ] Keine Duplicate msgid Einträge -- [ ] ~219 msgid Blöcke vorhanden -- [ ] Header mit korrekter Charset (UTF-8) - -### Phase 2: English Translation - -- [ ] `languages/open-data-wizard-en_US.po` erstellt -- [ ] Header angepasst: Language: en_US -- [ ] Alle 219 Strings ins Englische übersetzt - - [ ] CPT Labels (Datasets, Dataset, Create New Dataset, etc.) - - [ ] Form Field Labels (Title, Description, License, etc.) - - [ ] Settings Page Labels - - [ ] Validation Messages - - [ ] Quality Indicators - - [ ] API Responses - - [ ] Webhook Messages - - [ ] CLI Messages -- [ ] Pluralisierungen korrekt (msgid_plural) -- [ ] HTML/Markup bewahrt -- [ ] Variablen %d, %s erhalten geblieben -- [ ] WordPress Terminologie konsistent - -### Phase 3: Kompilierung & Testing - -- [ ] `msgfmt open-data-wizard-en_US.po -o open-data-wizard-en_US.mo` ausgeführt -- [ ] MO-Datei erstellt und korrekt formatiert (`file` Kommando) -- [ ] WordPress WPLANG auf 'en_US' gesetzt -- [ ] Admin Menu englisch: "Open Data Wizard" angezeigt -- [ ] CPT Labels englisch: "All Datasets", "Edit Dataset" -- [ ] Settings Page englisch -- [ ] Validation Messages englisch -- [ ] Shortcode Output englisch -- [ ] Keine Fallback-Strings in Deutsch sichtbar - -### Phase 4: Dokumentation - -- [ ] `/docs/TRANSLATIONS.md` erstellt -- [ ] `/scripts/generate-pot.sh` erstellt und ausführbar -- [ ] `/.github/workflows/generate-pot.yml` (optional) erstellt -- [ ] README.md mit i18n-Hinweis aktualisiert -- [ ] CLAUDE.md mit Translation-Info aktualisiert -- [ ] CHANGELOG.md mit v1.10.0 Entry - -### Finalisierung - -- [ ] Alle .pot, .po, .mo Dateien in Git committed -- [ ] Scripts sind ausführbar (chmod +x) -- [ ] Git push zu `claude/wordpress-open-data-wizard-MmOEL` -- [ ] Keine ungespeicherten Dateien -- [ ] Tests passing: `composer test` -- [ ] PHPCS clean: `composer phpcs` - ---- - -## Geschätzter Zeitaufwand - -| Phase | Aufgabe | Dauer | -|-------|---------|-------| -| 1 | Gettext Setup + POT-Generierung | 1-2h | -| 2 | English Übersetzung (219 Strings) | 2-3h | -| 3 | Kompilierung + Testing | 30-45 min | -| 4 | Dokumentation + Scripts | 1h | -| — | Debugging & Feinschliff | 30 min | -| **Total** | | **5-7h** | - ---- - -## Kritische Dateien - -**Neu zu erstellen:** -- `/languages/open-data-wizard.pot` -- `/languages/open-data-wizard-en_US.po` -- `/languages/open-data-wizard-en_US.mo` -- `/scripts/generate-pot.sh` -- `/docs/TRANSLATIONS.md` - -**Zu modifizieren (optional):** -- `README.md` — i18n Section hinzufügen -- `CLAUDE.md` — Translation Workflow dokumentieren -- `CHANGELOG.md` — v1.10.0 Entry mit i18n Feature -- `.github/workflows/generate-pot.yml` — GitHub Actions (optional) - -**Zu prüfen (keine Änderungen nötig):** -- `open-data-wizard.php` — load_plugin_textdomain() bereits korrekt -- `includes/*.php` — Alle Strings bereits mit i18n Functions wrappingiert - ---- - -## Notizen für zukünftige Implementierung - -1. **Locale Loading** - - WordPress lädt automatisch `-.mo` Dateien - - Fallback auf Original-Strings wenn .mo nicht vorhanden - - Locale wird via WPLANG oder Site Settings bestimmt - -2. **Pluralization** - - English hat nplurals=2 (singular/plural) - - Andere Sprachen können mehr Forms haben (z.B. Russisch = 3) - - Plural-Forms im .po Header MUSS korrekt sein - -3. **Performance** - - .mo Dateien sind binär optimiert (schneller als .po) - - Caching auf WordPress-Ebene vorhanden - - Keine Performance-Probleme zu erwarten - -4. **Zukunfts-Sprachen** - - Nach English einfach weitere .po/.mo Datei-Paare hinzufügen - - Naming-Konvention: `open-data-wizard-.po/mo` - - Locale-Codes: en_US, de_DE, fr_FR, es_ES, etc. - -5. **Crowdin/Weblate Integration** - - Falls später automatisierte Übersetzungen gewünscht - - Können direkt mit GitHub Repo sync - - Auto-Generate .po von .pot und sync zurück - ---- - -**Status: Bereit zur Implementierung** -**Letzte Überprüfung:** 2026-04-22 -**Nächster Schritt:** Implementierung starten (Phase 1 - POT Generierung) diff --git a/docs/WEBHOOK_IMPLEMENTATION.md b/docs/WEBHOOK_IMPLEMENTATION.md deleted file mode 100644 index 137b84b..0000000 --- a/docs/WEBHOOK_IMPLEMENTATION.md +++ /dev/null @@ -1,1159 +0,0 @@ -# Webhook Implementation Plan für Open Data Wizard - -**Feature:** Push/Webhook bei Statusänderung an Civora/Piveau -**Version:** v2.1.0 -**Geschätzter Aufwand:** 3 Stunden -**Status:** Geplant (nicht implementiert) -**Erstellt:** 2026-04-22 - ---- - -## Inhaltsverzeichnis - -1. [Überblick](#überblick) -2. [Phase 1: Settings-Erweiterung](#phase-1-settings-erweiterung) -3. [Phase 2: Core Webhook-Klasse](#phase-2-core-webhook-klasse) -4. [Phase 3: Datenbank-Logging](#phase-3-datenbank-logging) -5. [Phase 4: Admin-UI Integration](#phase-4-admin-ui-integration) -6. [Phase 5: Tests](#phase-5-tests) -7. [Phase 6: Dokumentation](#phase-6-dokumentation) -8. [Checkliste](#checkliste) - ---- - -## Überblick - -### Webhook-Funktionalität - -Das Plugin soll beim Veröffentlichen, Aktualisieren oder Löschen von Datasets einen POST-Request an einen konfigurierbaren Endpoint (z.B. Civora/Piveau) senden. - -**Payload-Struktur:** -```json -{ - "event": "dataset.published|dataset.updated|dataset.deleted", - "timestamp": "2026-04-22T14:30:00Z", - "dataset_id": 123, - "dataset": { ... JSON-LD ... }, - "changes": { - "status": "publish", - "modified": "2026-04-22" - } -} -``` - -### Architektur-Entscheidungen - -| Aspekt | Entscheidung | Begründung | -|--------|-------------|-----------| -| Neue Klasse | `class-webhooks.php` | Single Responsibility; unabhängig von Admin-UI | -| Ausführung | Synchron + async Retry | Sofortige Zustellung mit wp-cron Fallback | -| Logging | Custom DB-Tabelle `wp_odw_webhook_logs` | Persistent, querybar, keine Transient-Ablaufs | -| Auth | Bearer Token | Standard, einfach, erweiterbar via Filter | -| Hook-Point | `transition_post_status` + `delete_post` | Zuverlässig für alle Status-Änderungen | -| Event-Filter | Konfigurierbar in Settings | Verhindert Webhook-Spam bei Edits | - ---- - -## Phase 1: Settings-Erweiterung - -### 1.1 Webhook-Felder in `class-settings.php` hinzufügen - -**Datei:** `/home/user/OpenDataWizard/includes/class-settings.php` - -**Schritt 1a:** In der Methode `register_settings()` eine neue Settings-Section hinzufügen: - -```php -// Ungefähr nach der letzten add_settings_section() (vor dem Filter am Ende): -add_settings_section( - 'odw_section_webhooks', - __( 'Webhooks', 'open-data-wizard' ), - function () { - echo '

' . esc_html__( 'Configure webhook endpoints to push dataset changes to external platforms (e.g., Civora, Piveau).', 'open-data-wizard' ) . '

'; - }, - 'odw-settings' -); -``` - -**Schritt 1b:** Settings-Felder registrieren: - -```php -// Nach der add_settings_section(): - -// Webhook Enable/Disable -add_settings_field( - 'odw_webhook_enabled', - __( 'Enable Webhooks', 'open-data-wizard' ), - function () { - $enabled = get_option( 'odw_settings' )['webhook_enabled'] ?? false; - ?> - /> - - - - - - - - __( 'When dataset is published', 'open-data-wizard' ), - 'update' => __( 'When dataset is updated', 'open-data-wizard' ), - 'delete' => __( 'When dataset is deleted', 'open-data-wizard' ), - ); - - foreach ( $event_options as $value => $label ) { - $checked = in_array( $value, (array) $events, true ) ? 'checked' : ''; - echo '
'; - } - }, - 'odw-settings', - 'odw_section_webhooks' -); - -// Webhook Max Retries -add_settings_field( - 'odw_webhook_max_retries', - __( 'Max Retries', 'open-data-wizard' ), - function () { - $settings = get_option( 'odw_settings' ) ?? array(); - $retries = $settings['webhook_max_retries'] ?? 3; - - $retry_options = array( 1, 3, 5 ); - ?> - - - - - - - post_type ) { - return; - } - - // Avoid double-triggering (e.g., on revisions). - if ( wp_is_post_revision( $post->ID ) ) { - return; - } - - $event_type = self::get_event_type( $old_status, $new_status ); - - if ( ! $event_type ) { - return; // No relevant event. - } - - self::send_webhook( $post->ID, $event_type ); - } - - /** - * Called when a post is deleted. - * - * @param int $post_id Post ID. - */ - public static function on_post_delete( int $post_id ): void { - $post = get_post( $post_id ); - - if ( ! $post || 'odw_dataset' !== $post->post_type ) { - return; - } - - self::send_webhook( $post_id, 'deleted' ); - } - - /** - * Determine event type from status transition. - * - * @param string $old_status Old status. - * @param string $new_status New status. - * @return string|null Event type (published, updated, unpublished) or null. - */ - private static function get_event_type( string $old_status, string $new_status ): ?string { - // Not published → Published = "published" event. - if ( 'publish' === $new_status && 'publish' !== $old_status ) { - return 'published'; - } - - // Published → Unpublished = no event (filter-dependent). - if ( 'publish' === $old_status && 'publish' !== $new_status ) { - return null; // Could be 'unpublished' if filtered. - } - - // Published → Published = "updated" event. - if ( 'publish' === $old_status && 'publish' === $new_status ) { - return 'updated'; - } - - return null; - } - - /** - * Send webhook POST request for a dataset. - * - * @param int $post_id Dataset post ID. - * @param string $event_type Event type (published, updated, deleted). - */ - public static function send_webhook( int $post_id, string $event_type ): void { - // Check if webhooks are enabled. - if ( ! self::is_enabled() ) { - return; - } - - // Check if this event type is configured. - if ( ! self::should_send_event( $event_type ) ) { - return; - } - - $url = self::get_webhook_url(); - $token = self::get_webhook_token(); - $post = get_post( $post_id ); - - if ( ! $url || ! $token || ! $post ) { - return; - } - - // Build payload. - $payload = self::build_payload( $post, $event_type ); - - // Send request. - $response = self::make_request( $url, $payload, $token ); - - // Log attempt. - self::log_webhook_attempt( $post_id, $event_type, $response ); - - // Schedule retry if failed. - if ( ! $response['success'] && $response['attempt'] < self::get_max_retries() ) { - self::schedule_retry( $post_id, $event_type, $response['attempt'] ); - } - } - - /** - * Check if webhooks are enabled in settings. - * - * @return bool - */ - private static function is_enabled(): bool { - if ( ! class_exists( 'ODW_Settings' ) ) { - return false; - } - - $enabled = ODW_Settings::get( 'webhook_enabled' ); - return ! empty( $enabled ); - } - - /** - * Check if a specific event type should trigger webhooks. - * - * @param string $event_type Event type. - * @return bool - */ - private static function should_send_event( string $event_type ): bool { - if ( ! class_exists( 'ODW_Settings' ) ) { - return false; - } - - $events = ODW_Settings::get( 'webhook_events' ); - - if ( ! is_array( $events ) ) { - return false; - } - - // Map internal events to configured event types. - $event_map = array( - 'published' => 'publish', - 'updated' => 'update', - 'deleted' => 'delete', - ); - - $event_key = $event_map[ $event_type ] ?? null; - - return $event_key && in_array( $event_key, $events, true ); - } - - /** - * Get webhook URL from settings. - * - * @return string|null - */ - private static function get_webhook_url(): ?string { - if ( ! class_exists( 'ODW_Settings' ) ) { - return null; - } - - $url = ODW_Settings::get( 'webhook_url' ); - return ! empty( $url ) ? (string) $url : null; - } - - /** - * Get webhook token from settings. - * - * @return string|null - */ - private static function get_webhook_token(): ?string { - if ( ! class_exists( 'ODW_Settings' ) ) { - return null; - } - - $token = ODW_Settings::get( 'webhook_token' ); - return ! empty( $token ) ? (string) $token : null; - } - - /** - * Get max retries from settings. - * - * @return int - */ - private static function get_max_retries(): int { - if ( ! class_exists( 'ODW_Settings' ) ) { - return 3; - } - - $max = ODW_Settings::get( 'webhook_max_retries' ); - return absint( $max ) > 0 ? absint( $max ) : 3; - } - - /** - * Build webhook payload from dataset. - * - * @param WP_Post $post Dataset post. - * @param string $event_type Event type. - * @return array Payload array. - */ - private static function build_payload( WP_Post $post, string $event_type ): array { - $jsonld = array(); - if ( function_exists( 'odw_build_dataset_jsonld' ) ) { - $jsonld = odw_build_dataset_jsonld( $post->ID ) ?? array(); - } - - return array( - 'event' => 'dataset.' . $event_type, - 'timestamp' => gmdate( 'c' ), - 'dataset_id' => $post->ID, - 'dataset' => $jsonld, - 'changes' => array( - 'status' => $post->post_status, - 'modified' => $post->post_modified_gmt, - ), - ); - } - - /** - * Make HTTP POST request to webhook endpoint. - * - * @param string $url Webhook URL. - * @param array $payload Payload data. - * @param string $token Bearer token. - * @return array Response: ['success' => bool, 'status_code' => int, 'body' => string, 'error' => string, 'attempt' => int] - */ - private static function make_request( string $url, array $payload, string $token ): array { - $response = array( - 'success' => false, - 'status_code' => 0, - 'body' => '', - 'error' => '', - 'attempt' => 1, - ); - - $args = array( - 'method' => 'POST', - 'timeout' => 10, - 'sslverify' => true, - 'headers' => array( - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - 'User-Agent' => 'Open Data Wizard/' . ODW_VERSION, - ), - 'body' => wp_json_encode( $payload ), - ); - - $http_response = wp_remote_post( $url, $args ); - - if ( is_wp_error( $http_response ) ) { - $response['error'] = $http_response->get_error_message(); - return $response; - } - - $status_code = (int) wp_remote_retrieve_response_code( $http_response ); - $response['status_code'] = $status_code; - $response['body'] = wp_remote_retrieve_body( $http_response ); - - // Success: 2xx status code. - if ( $status_code >= 200 && $status_code < 300 ) { - $response['success'] = true; - } else { - $response['error'] = "HTTP {$status_code}"; - } - - return $response; - } - - /** - * Log webhook attempt to database. - * - * @param int $post_id Dataset post ID. - * @param string $event_type Event type. - * @param array $response Response from make_request(). - */ - private static function log_webhook_attempt( int $post_id, string $event_type, array $response ): void { - global $wpdb; - - $table = "{$wpdb->prefix}odw_webhook_logs"; - - // phpcs:ignore WordPress.DB.DirectDatabaseQuery - $wpdb->insert( - $table, - array( - 'post_id' => $post_id, - 'event_type' => $event_type, - 'timestamp' => current_time( 'mysql', true ), - 'attempt' => $response['attempt'] ?? 1, - 'status_code' => $response['status_code'] ?? 0, - 'response_body' => substr( $response['body'] ?? '', 0, 1000 ), - 'error_message' => substr( $response['error'] ?? '', 0, 500 ), - ), - array( '%d', '%s', '%s', '%d', '%d', '%s', '%s' ) - ); - } - - /** - * Schedule a retry attempt via wp-cron. - * - * @param int $post_id Dataset post ID. - * @param string $event_type Event type. - * @param int $attempt Current attempt number. - */ - private static function schedule_retry( int $post_id, string $event_type, int $attempt ): void { - $next_attempt = $attempt + 1; - - // Exponential backoff: 30s, 2m, 5m. - $delays = array( - 1 => 30, // Attempt 1 failed → retry in 30s. - 2 => 120, // Attempt 2 failed → retry in 2m. - 3 => 300, // Attempt 3 failed → retry in 5m. - ); - - $delay = $delays[ $attempt ] ?? 300; - - wp_schedule_single_event( - time() + $delay, - 'wp_scheduled_odw_webhook_retry', - array( $post_id, $event_type ) - ); - } - - /** - * Handle scheduled retry execution (called by wp-cron). - * - * @param int $post_id Dataset post ID. - * @param string $event_type Event type. - */ - public static function handle_scheduled_retry( int $post_id, string $event_type ): void { - // Simply retry by calling send_webhook again. - self::send_webhook( $post_id, $event_type ); - } - - /** - * Handle AJAX test webhook request. - * - * @wp-hook wp_ajax_odw_test_webhook - */ - public static function handle_test_webhook(): void { - // Verify nonce. - if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'odw_test_webhook' ) ) { - wp_send_json_error( array( 'message' => __( 'Security check failed', 'open-data-wizard' ) ) ); - } - - // Check capability. - if ( ! current_user_can( 'manage_options' ) ) { - wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'open-data-wizard' ) ) ); - } - - $url = self::get_webhook_url(); - $token = self::get_webhook_token(); - - if ( ! $url ) { - wp_send_json_error( array( 'message' => __( 'Webhook URL not configured', 'open-data-wizard' ) ) ); - } - - if ( ! $token ) { - wp_send_json_error( array( 'message' => __( 'Webhook token not configured', 'open-data-wizard' ) ) ); - } - - // Find a published dataset for testing (or use a sample). - $sample_post = get_posts( - array( - 'post_type' => 'odw_dataset', - 'post_status' => 'publish', - 'posts_per_page' => 1, - ) - ); - - if ( ! empty( $sample_post ) ) { - $payload = self::build_payload( $sample_post[0], 'test' ); - } else { - // Sample payload if no published datasets exist. - $payload = array( - 'event' => 'dataset.test', - 'timestamp' => gmdate( 'c' ), - 'dataset_id' => 0, - 'dataset' => array( - '@context' => 'https://www.w3.org/ns/dcat', - '@type' => 'dcat:Dataset', - 'dct:title' => 'Test Dataset', - ), - 'changes' => array( 'status' => 'test' ), - ); - } - - $response = self::make_request( $url, $payload, $token ); - - if ( $response['success'] ) { - wp_send_json_success( - array( - /* translators: %d = HTTP status code */ - 'message' => sprintf( __( 'Webhook sent successfully (HTTP %d)', 'open-data-wizard' ), $response['status_code'] ), - ) - ); - } else { - wp_send_json_error( - array( - /* translators: %s = error message */ - 'message' => sprintf( __( 'Webhook failed: %s', 'open-data-wizard' ), $response['error'] ), - ) - ); - } - } - - /** - * Get webhook logs for a dataset (for admin UI). - * - * @param int $post_id Dataset post ID. - * @param int $limit Number of recent logs to fetch. - * @return array Array of log entries. - */ - public static function get_logs( int $post_id, int $limit = 10 ): array { - global $wpdb; - - $table = "{$wpdb->prefix}odw_webhook_logs"; - - // phpcs:ignore WordPress.DB.DirectDatabaseQuery - $logs = $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$table} WHERE post_id = %d ORDER BY timestamp DESC LIMIT %d", - $post_id, - $limit - ) - ); - - return $logs ?? array(); - } -} -``` - -### 2.2 Bootstrap-Integration - -**Datei:** `/home/user/OpenDataWizard/open-data-wizard.php` - -**Schritt:** In der `odw_bootstrap()` Funktion (ungefähr nach Zeile 136) die neue Klasse laden und initialisieren: - -```php -// Nach: require_once ODW_PLUGIN_DIR . 'includes/class-shortcode.php'; - -require_once ODW_PLUGIN_DIR . 'includes/class-webhooks.php'; - -// Nach: ODW_Shortcode::init(); - -ODW_Webhooks::init(); -``` - ---- - -## Phase 3: Datenbank-Logging - -### 3.1 Logging-Tabelle bei Aktivierung erstellen - -**Datei:** `/home/user/OpenDataWizard/includes/class-setup.php` - -**Schritt:** In der Methode `on_activation()` oder `maybe_create_demo()` die Tabelle erstellen: - -```php -// In class-setup.php, eine neue static Methode hinzufügen: - -/** - * Create webhook logging table. - */ -public static function create_webhook_logs_table(): void { - global $wpdb; - - $table = "{$wpdb->prefix}odw_webhook_logs"; - $charset = $wpdb->get_charset_collate(); - - $sql = "CREATE TABLE IF NOT EXISTS {$table} ( - id INT AUTO_INCREMENT PRIMARY KEY, - post_id INT NOT NULL, - event_type VARCHAR(50) NOT NULL, - timestamp DATETIME NOT NULL, - attempt INT DEFAULT 1, - status_code INT DEFAULT 0, - response_body LONGTEXT, - error_message TEXT, - KEY post_id (post_id), - KEY timestamp (timestamp) - ) {$charset};"; - - require_once ABSPATH . 'wp-admin/includes/upgrade.php'; - dbDelta( $sql ); -} - -// In maybe_create_demo() oder on_activation() aufrufen: -self::create_webhook_logs_table(); -``` - -### 3.2 Tabelle bei Deinstallation löschen - -**Datei:** `/home/user/OpenDataWizard/uninstall.php` - -**Schritt:** Am Ende der Datei, vor dem `exit`: - -```php -// Wenn delete_on_uninstall aktiviert ist, auch Webhook-Logs-Tabelle löschen. -if ( $delete_on_uninstall ) { - global $wpdb; - $table = "{$wpdb->prefix}odw_webhook_logs"; - // phpcs:ignore WordPress.DB.DirectDatabaseQuery - $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); -} -``` - ---- - -## Phase 4: Admin-UI Integration - -### 4.1 Webhook-Logs Meta-Box auf Edit-Screen - -**Datei:** `/home/user/OpenDataWizard/includes/class-admin.php` - -**Schritt:** In der Methode `init()` einen neuen Hook hinzufügen: - -```php -add_action( 'add_meta_boxes', array( self::class, 'register_webhook_meta_box' ) ); -``` - -**Schritt:** Neue Methode hinzufügen: - -```php -/** - * Register webhook logs meta box on dataset edit screen. - */ -public static function register_webhook_meta_box(): void { - add_meta_box( - 'odw_webhook_logs', - __( 'Webhook Activity', 'open-data-wizard' ), - array( self::class, 'render_webhook_meta_box' ), - 'odw_dataset', - 'side' - ); -} - -/** - * Render webhook logs meta box. - */ -public static function render_webhook_meta_box( $post ): void { - if ( ! class_exists( 'ODW_Webhooks' ) ) { - echo '

' . esc_html__( 'Webhooks not available', 'open-data-wizard' ) . '

'; - return; - } - - $logs = ODW_Webhooks::get_logs( $post->ID, 5 ); - - if ( empty( $logs ) ) { - echo '

' . esc_html__( 'No webhook activity yet', 'open-data-wizard' ) . '

'; - return; - } - - echo ''; - echo ''; - - foreach ( $logs as $log ) { - $status_html = ''; - if ( $log->status_code >= 200 && $log->status_code < 300 ) { - $status_html = '✓ ' . esc_html( $log->status_code ) . ''; - } else { - $status_html = '✗ ' . esc_html( $log->error_message ) . ''; - } - - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - } - - echo '
' . esc_html__( 'Date', 'open-data-wizard' ) . '' . esc_html__( 'Event', 'open-data-wizard' ) . '' . esc_html__( 'Status', 'open-data-wizard' ) . '
' . esc_html( $log->timestamp ) . '' . esc_html( $log->event_type ) . '' . wp_kses_post( $status_html ) . '
'; -} -``` - -### 4.2 Webhook-Status in der Listen-Spalte - -**Datei:** `/home/user/OpenDataWizard/includes/class-admin.php` - -**Schritt:** In der Methode `add_columns()` eine neue Spalte hinzufügen: - -```php -$columns['webhook_status'] = __( 'Webhook', 'open-data-wizard' ); -``` - -**Schritt:** In der Methode `render_column()` den Content für die neue Spalte hinzufügen: - -```php -case 'webhook_status': - if ( ! class_exists( 'ODW_Webhooks' ) ) { - echo '—'; - break; - } - - $logs = ODW_Webhooks::get_logs( $post_id, 1 ); - if ( ! empty( $logs ) ) { - $log = $logs[0]; - if ( $log->status_code >= 200 && $log->status_code < 300 ) { - echo '✓ Sent'; - } else { - echo '⚠ Retry'; - } - } else { - echo '—'; - } - break; -``` - ---- - -## Phase 5: Tests - -### 5.1 Neue Test-Datei erstellen - -**Datei:** `/home/user/OpenDataWizard/tests/test-webhooks.php` - -**Inhalt:** (siehe separate Test-Implementierungsdatei) - -Die Tests sollten folgende Szenarien abdecken: -- `test_webhook_disabled_does_not_send()` — Wenn Webhooks deaktiviert, nichts senden -- `test_publish_event_triggers_webhook()` — Bei Veröffentlichung triggert Webhook -- `test_payload_includes_jsonld()` — Payload enthält JSON-LD des Datasets -- `test_failed_request_is_logged()` — Failed Response wird in DB geloggt -- `test_retry_scheduled_on_failure()` — Nach Fehler wird Retry geplant -- `test_test_webhook_handler()` — Test-Webhook AJAX-Handler funktioniert - ---- - -## Phase 6: Dokumentation - -### 6.1 README.md aktualisieren - -**Datei:** `/home/user/OpenDataWizard/README.md` - -**Neue Sektion hinzufügen** (vor "Roadmap"): - -```markdown -### Webhook-Integration für Civora/Piveau - -Das Plugin kann Datasets automatisch an externe Harvesting-Plattformen pushen, wenn sie veröffentlicht, aktualisiert oder gelöscht werden. - -#### Konfiguration - -Unter **Datensätze → Einstellungen → Webhooks**: - -1. **Enable Webhooks** — Checkbox zum Aktivieren/Deaktivieren -2. **Webhook URL** — Endpoint der Harvesting-Plattform (z.B. `https://harvest.civora.de/push`) -3. **API Token** — Bearer Token für Authentication (wird als `Authorization: Bearer {token}` gesendet) -4. **Events to Send** — Auswählen welche Events triggern (Publish, Update, Delete) -5. **Max Retries** — Anzahl der Wiederholungsversuche bei Fehlern (1, 3 oder 5) - -#### Webhook-Payload - -Beim Trigger wird ein POST-Request mit folgender Struktur gesendet: - -```json -{ - "event": "dataset.published|dataset.updated|dataset.deleted", - "timestamp": "2026-04-22T14:30:00Z", - "dataset_id": 123, - "dataset": { ... JSON-LD ... }, - "changes": { - "status": "publish", - "modified": "2026-04-22" - } -} -``` - -#### Fehlerbehandlung & Retries - -Wenn ein Webhook fehlschlägt (Timeout, 4xx/5xx Response): -- Fehler wird in die Webhook-Logs eingetragen -- Automatischer Retry nach 30 Sekunden (Versuch 1) -- Dann nach 2 Minuten (Versuch 2) -- Dann nach 5 Minuten (Versuch 3) -- Konfigurierbare maximale Retry-Versuche - -#### Webhook-Aktivität im Admin - -- **Meta-Box auf Edit-Screen** — Zeigt die letzten 5 Webhook-Versuche -- **Spalte in der Listen-Ansicht** — Status-Icon (✓ oder ⚠) -- **Test-Webhook Button** — In Einstellungen zum Testen der Konfiguration -``` - -### 6.2 CLAUDE.md aktualisieren - -**Neue Sektion hinzufügen** unter "Common Development Tasks": - -```markdown -### Adding a Webhook Event - -To trigger webhooks on new dataset events: - -1. **Modify trigger logic** in `class-webhooks.php::get_event_type()`: - ```php - if ( 'my_new_status' === $old_status && 'publish' === $new_status ) { - return 'my_event_type'; - } - ``` - -2. **Update event mapping** in `should_send_event()`: - ```php - $event_map = array( - ... - 'my_event_type' => 'my_filter_key', - ); - ``` - -3. **Add setting in Settings UI** for the new event filter option - -4. **Test** with `wp open-data-wizard webhooks send-test` -``` - -### 6.3 CHANGELOG.md aktualisieren - -**Neue Entry hinzufügen**: - -```markdown -## [2.1.0] — TBD - -### Hinzugefügt -- **Webhook-Integration für Civora/Piveau** (`GET /wp-json/datenatlas/v1/webhooks/...`): - - Automatisches Pushen von Datasets bei Veröffentlichung, Aktualisierung, Löschung - - Konfigurierbare Webhook-URL und Bearer Token in Einstellungen - - Event-Filterung (Publish, Update, Delete) - - Automatische Retry-Logik mit exponentiellem Backoff (30s, 2m, 5m) - - Webhook-Logs in Custom DB-Tabelle mit Admin-UI Anzeige - - Test-Webhook Button zur Verifikation der Konfiguration - - 6 neue PHPUnit-Tests für Webhook-Funktionalität -``` - ---- - -## Checkliste - -### Vor Implementierung - -- [ ] Diesen Plan durchgelesen und verstanden -- [ ] Git-Branch verifiziert: `claude/wordpress-open-data-wizard-MmOEL` -- [ ] `composer install` ausgeführt (dev dependencies vorhanden) - -### Implementierung - -#### Phase 1: Settings -- [ ] `class-settings.php` erweitert mit 5 Webhook-Feldern -- [ ] Sanitize-Callback implementiert -- [ ] Test-Webhook AJAX-Handler gebaut - -#### Phase 2: Webhooks -- [ ] `class-webhooks.php` erstellt (150 Zeilen) -- [ ] In `open-data-wizard.php` bootstrapped -- [ ] Alle 6 Hooks registriert: - - [ ] `transition_post_status` - - [ ] `delete_post` - - [ ] `wp_scheduled_odw_webhook_retry` - - [ ] `wp_ajax_odw_test_webhook` - -#### Phase 3: Datenbank -- [ ] Logging-Tabelle-Erstellung in `class-setup.php` -- [ ] Tabellen-Löschen in `uninstall.php` -- [ ] Migrations-Check (nicht nötig für MVP, aber dokumentieren) - -#### Phase 4: Admin-UI -- [ ] Meta-Box für Webhook-Logs auf Edit-Screen -- [ ] Spalte in Listen-Ansicht -- [ ] Status-Icons (✓, ⚠, ✗) - -#### Phase 5: Tests -- [ ] `test-webhooks.php` mit 6+ Tests -- [ ] Alle Tests passing: `composer test -- --filter Test_ODW_Webhooks` -- [ ] PHPCS check: `composer phpcs -- includes/class-webhooks.php` - -#### Phase 6: Dokumentation -- [ ] README.md mit Webhook-Sektion -- [ ] CLAUDE.md mit neuer Event-Klasse dokumentiert -- [ ] CHANGELOG.md mit v2.1.0 Entry -- [ ] Inline-Docstrings in Klasse vollständig - -### Nach Implementierung - -- [ ] Alle Tests passing (69+ Tests) -- [ ] PHPCS 0 Violations -- [ ] PHPStan Level 6 clean -- [ ] Commit mit beschreibender Message -- [ ] Push zu `claude/wordpress-open-data-wizard-MmOEL` -- [ ] Git-Log zeigt 1 neuen Commit - -### Testing (manuell) - -- [ ] Settings speichern/laden funktioniert -- [ ] Webhook beim Veröffentlichen getriggert -- [ ] Logs in DB erscheinen -- [ ] Meta-Box zeigt Logs korrekt -- [ ] Test-Webhook Button funktioniert -- [ ] Retry wird nach Fehler geplant -- [ ] Bearer Token wird korrekt gesendet - ---- - -## Geschätzter Zeitaufwand - -| Phase | Aufgabe | Dauer | -|-------|---------|-------| -| 1 | Settings-Erweiterung + Sanitize | 30 min | -| 2 | Webhook-Klasse vollständig | 60 min | -| 3 | DB-Logging-Tabelle | 15 min | -| 4 | Admin-UI (Meta-Box + Spalte) | 30 min | -| 5 | Tests (6+) | 30 min | -| 6 | Dokumentation | 15 min | -| — | Debugging & Verfeinerung | 30 min | -| **Total** | | **3 Stunden** | - ---- - -## Notizen für zukünftige Implementierung - -1. **Keine Breaking Changes** — Bestehendes REST API bleibt unverändert -2. **Backward Compatible** — Settings optional, Webhooks können jederzeit deaktiviert werden -3. **Graceful Degradation** — Fehlerhafte Webhooks blockieren nicht das Dataset-Speichern -4. **Logging wichtig** — Alle Webhook-Attempts müssen geloggt werden für Debugging -5. **Security** — Bearer Token mit `esc_attr()` behandeln, nie in Logs speichern (nur Fehler) -6. **Performance** — Synchrone HTTP-Requests; falls zu langsam, zu async Job Queue migrieren -7. **Testing** — Mock `wp_remote_post()` für Unit-Tests, use `wp_http_validate_url()` für URL-Validierung -8. **Erweiterbarkeit** — Filter einbauen z.B. `apply_filters( 'odw_webhook_payload', $payload, $post_id )` - ---- - -**Status: Bereit zur Implementierung** -**Letzte Überprüfung:** 2026-04-22 -**Nächster Schritt:** Implementierung starten (Phase 1) diff --git a/open-data-wizard.php b/open-data-wizard.php index d672c8a..51767c5 100644 --- a/open-data-wizard.php +++ b/open-data-wizard.php @@ -3,7 +3,7 @@ * Plugin Name: Open Data Wizard * Plugin URI: https://github.com/daimpad/OpenDataWizard * Description: DCAT-AP 3.0 konforme Open Data Metadatenverwaltung für WordPress. Bereitstellung als maschinenlesbarer JSON-LD-Endpoint für offene Daten. - * Version: 1.9.0 + * Version: 2.1.0 * Requires at least: 6.4 * Requires PHP: 8.1 * Author: nozilla @@ -22,7 +22,7 @@ exit; } -define( 'ODW_VERSION', '1.9.0' ); +define( 'ODW_VERSION', '2.1.0' ); define( 'ODW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'ODW_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); define( 'ODW_PLUGIN_FILE', __FILE__ );