Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

---

## [2.38.0] — 2026-08-07

Gutenberg-Block „Datensatz-Karte" — keine IDs mehr abtippen.

### ✨ Added
- **Block „Datensatz-Karte"** als Alternative zum Shortcode `[odw_dataset id="123"]`. Im Editor
über das Plus-Symbol einfügen und den Datensatz aus einer Liste wählen; zur Auswahl stehen nur
veröffentlichte Datensätze. Wer den Shortcode bevorzugt oder klassische Editoren nutzt, kann
ihn unverändert weiterverwenden.

### ℹ️ Umsetzung
- **Dynamischer Block:** Gespeichert wird ausschließlich die Datensatz-ID, das Markup entsteht
beim Ausliefern über `ODW_Shortcode::render()`. Damit gibt es genau eine Quelle für die Karte —
Änderungen daran wirken auf Block und Shortcode gleichermaßen —, und ein später umbenannter
Datensatz erscheint mit seinem aktuellen Titel statt als eingefrorene Kopie im Beitrag.
- **Kein Build-Schritt.** `blocks/dataset-card/` enthält `block.json` und ein Editor-Skript in
schlichtem JavaScript statt JSX. Das Projekt hat keine JS-Build-Kette für den Admin-Bereich,
und eine allein für diesen Block einzuführen wäre viel Apparat für wenig Ertrag.
- **Auswahlliste über `wp_localize_script`,** nicht über den Core-Datenspeicher: Der Custom Post
Type ist bewusst nicht über die WP-REST-API exponiert (`show_in_rest => false`), dafür gibt es
die eigenen Endpunkte. Die Liste wird nur im Editor aufgebaut (`enqueue_block_editor_assets`)
und ist auf 200 Einträge begrenzt.
- **Platzhalter statt Live-Vorschau im Editor.** Eine serverseitig gerenderte Vorschau bekäme das
Frontend-Stylesheet im Editor-Rahmen nicht mit und sähe dort kaputt aus; die Platzhalterkarte
nutzt WordPress' eigene `Placeholder`-Komponente und braucht deshalb kein eigenes CSS.
- `bin/build-release.sh` kopiert `blocks/` mit ins Paket — ohne diese Ergänzung wäre der Block in
der Installation schlicht nicht vorhanden gewesen.

---

## [2.37.0] — 2026-08-07

Alle 24 EU-Amtssprachen statt zwei.
Expand Down
10 changes: 7 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ Externe Harvester rufen /catalog, /datasets/<id>, oder /delta ab
| **ODW_Rdf** | JSON-LD → Turtle (dependency-frei) für RDF-Harvester | `to_turtle()` |
| **ODW_Field_Reference** | Erzeugt `docs/FELD-REFERENZ.md` aus `config/field-catalog.php` | `build()`, `write()`, `js_map()` |
| **ODW_Settings** | Plugin-Einstellungsseite | `get()`, `filter_catalog_title()` |
| **ODW_Block** | Gutenberg-Block „Datensatz-Karte“ (dynamisch, rendert über ODW_Shortcode) | `register()`, `localize()`, `render()` |
| **ODW_Shortcode** | Frontend Download-Card: `[odw_dataset id="123"]` | `render()` |
| **ODW_Batch_Import** | CSV/JSON Batch-Import: Parsing, Validierung, Bulk-Insert | `parse_file()`, `validate_row()`, `import_records()` |
| **ODW_CLI** | WP-CLI Befehle (Qualitäts-Recalc, Cache-Clear) | `quality_recalculate()`, `cache_clear()` |
Expand Down Expand Up @@ -730,7 +731,7 @@ Update **both** locations:
- MINOR: New features (backward-compatible)
- PATCH: Bug fixes only

Current: **v2.37.0**
Current: **v2.38.0**

---

Expand Down Expand Up @@ -769,6 +770,10 @@ Neue technische Festlegungen gehören dorthin, nicht in README oder CLAUDE.md.
- **Gebündelte Vokabulare vervollständigt (v2.37.0):** `access-right` (3 Stufen) und `language`
(alle 24 EU-Amtssprachen) liegen unter `config/vocabularies/`. Die Sprachauswahl bot vorher nur
Deutsch und Englisch, obwohl `odw_resolve_language_tag()` alle 24 Codes bereits umsetzen konnte.
- **Gutenberg-Block (v2.38.0):** „Datensatz-Karte“ als Alternative zum Shortcode. Dynamischer
Block ohne Build-Schritt — `blocks/dataset-card/` mit `block.json` und schlichtem JS; das
Rendern delegiert an `ODW_Shortcode::render()`. Die Auswahlliste kommt über
`wp_localize_script`, weil der CPT bewusst nicht über die WP-REST-API exponiert ist.
- **Content Negotiation (v2.35.0):** Alle drei Endpunkte liefern `jsonld`/`json`/`turtle`; ohne
`?format=` entscheidet der `Accept`-Header (q-Werte, `Vary: Accept`). Explizites `?format=`
hat Vorrang.
Expand All @@ -777,7 +782,6 @@ Neue technische Festlegungen gehören dorthin, nicht in README oder CLAUDE.md.

- **Optional: RDF/XML** als weitere Serialisierung (Turtle und JSON-LD decken die gängigen
Harvester bereits ab).
- **Gutenberg-Block** für die Download-Card (Alternative zum Shortcode).
- **Mehrsprachigkeit der Oberfläche** (WPML/Polylang) — das Datenmodell unterstützt Mehrsprachigkeit
bereits, die Integration fehlt.
- **Optional/künftig:** Registry-getriebenes Formular-/JSON-LD-Rendering (Aufräumarbeit ohne
Expand Down Expand Up @@ -872,6 +876,6 @@ Neue technische Festlegungen gehören dorthin, nicht in README oder CLAUDE.md.

---

**Zuletzt aktualisiert**: Version 2.37.0 (August 2026)
**Zuletzt aktualisiert**: Version 2.38.0 (August 2026)
**Autor**: Open Data Wizard Team (nozilla)
**License**: GPL-2.0-or-later
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
![Version](https://img.shields.io/badge/Version-2.37.0-brightgreen?style=flat-square)
![Version](https://img.shields.io/badge/Version-2.38.0-brightgreen?style=flat-square)
![PRs Welcome](https://img.shields.io/badge/PRs-willkommen-brightgreen?style=flat-square)

📖 [Dokumentation](DOCUMENTATION.md) · 📋 [Feld-Referenz](docs/FELD-REFERENZ.md) · 📐 [Technische Spezifikation](TECHNICAL-SPEC.md) · 📝 [Changelog](CHANGELOG.md) · 🛡️ [Security](SECURITY.md) · ⚖️ [Lizenz](LICENSE)
Expand Down Expand Up @@ -171,11 +171,27 @@ Automatische Metadaten-Qualitätsprüfung nach der [EU-MQA-Methodik](https://dat

Berechnung nach jedem Speichern. Die **Listenspalte** zeigt Prozent + Stufe; die **Qualitäts-Meta-Box** ergänzt den MQA-Rohwert (z. B. „54 / 259 Punkte, von max. 405") als Detailzeile.

### 📥 Download-Card Shortcode
### 📥 Download-Card — Block oder Shortcode

Zwei Wege zur selben Karte. **Empfohlen ist der Block:**

Im Beitrags- oder Seiteneditor über das Plus-Symbol **„Datensatz-Karte"** einfügen und rechts in
der Seitenleiste den Datensatz aus einer Liste wählen — keine ID zum Abtippen. Zur Auswahl stehen
nur veröffentlichte Datensätze.

Alternativ der Shortcode, etwa in klassischen Editoren oder Widgets:

```
[odw_dataset id="123"]
```
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.

Beide rendern dieselbe 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 die Karte auch enthalten.

Der Block speichert ausschließlich die Datensatz-ID und rendert beim Ausliefern — ein später
umbenannter Datensatz erscheint also mit seinem aktuellen Titel, statt eine eingefrorene Kopie im
Beitrag zu hinterlassen.

### 🔗 REST API Endpoints

Expand Down
4 changes: 2 additions & 2 deletions bin/build-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# build-release.sh — erzeugt ein installationsfertiges WordPress-Plugin-ZIP.
#
# Enthält ausschließlich die zur Laufzeit benötigten Dateien:
# - Plugin-Bootstrap, Klassen, Assets, Sprachen, Beispiel-Dateien
# - Plugin-Bootstrap, Klassen, Assets, Blöcke, Sprachen, Beispiel-Dateien
# - config/ (nur Datendateien; Dev-Configs und SHACL-Referenz entfallen)
# - vendor/ mit ausschließlich der Produktionsabhängigkeit (Carbon Fields),
# via `composer install --no-dev`, inkl. Bereinigung der Carbon-Fields-Dev-Dateien
Expand Down Expand Up @@ -43,7 +43,7 @@ mkdir -p "$PLUG_DIR"

# --- Runtime-Quelldateien kopieren (Allowlist) -----------------------------
cp "$ROOT_DIR/$SLUG.php" "$ROOT_DIR/uninstall.php" "$ROOT_DIR/README.md" "$ROOT_DIR/LICENSE" "$PLUG_DIR/"
cp -R "$ROOT_DIR/includes" "$ROOT_DIR/assets" "$ROOT_DIR/languages" "$ROOT_DIR/samples" "$PLUG_DIR/"
cp -R "$ROOT_DIR/includes" "$ROOT_DIR/assets" "$ROOT_DIR/blocks" "$ROOT_DIR/languages" "$ROOT_DIR/samples" "$PLUG_DIR/"

# config/ vollständig kopieren, dann Dev-/Referenz-Anteile entfernen
cp -R "$ROOT_DIR/config" "$PLUG_DIR/"
Expand Down
25 changes: 25 additions & 0 deletions blocks/dataset-card/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "odw/dataset-card",
"title": "Datensatz-Karte",
"category": "widgets",
"icon": "database",
"description": "Zeigt einen veröffentlichten Datensatz als Download-Karte — die Alternative zum Shortcode [odw_dataset].",
"keywords": [ "Datensatz", "Open Data", "Download", "DCAT" ],
"textdomain": "open-data-wizard",
"supports": {
"html": false,
"align": [ "wide" ],
"spacing": {
"margin": true
}
},
"attributes": {
"datasetId": {
"type": "number",
"default": 0
}
},
"editorScript": "odw-dataset-card-editor"
}
124 changes: 124 additions & 0 deletions blocks/dataset-card/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/* global wp */
/**
* Editor-Skript für den Block „Datensatz-Karte".
*
* Bewusst in schlichtem JavaScript statt JSX: Das Projekt hat keine
* JS-Build-Kette für den Admin-Bereich, und eine allein für diesen Block
* einzuführen wäre viel Apparat für wenig Ertrag. Der Preis ist das
* ausgeschriebene createElement() statt der gewohnten JSX-Schreibweise.
*
* Die Auswahlliste kommt aus window.odwDatasetBlock (siehe ODW_Block): Der
* Custom Post Type ist absichtlich nicht über die WP-REST-API exponiert —
* dafür gibt es die eigenen Endpunkte —, also lässt er sich im Editor nicht
* über den Core-Datenspeicher abfragen.
*
* Im Editor steht eine Platzhalterkarte statt einer Live-Vorschau. Eine
* serverseitig gerenderte Vorschau bekäme das Frontend-Stylesheet im
* Editor-Rahmen nicht mit und sähe dort kaputt aus — eine ehrliche
* Platzhalterkarte ist verlässlicher als eine halbe Vorschau.
*/
( function ( blocks, element, blockEditor, components ) {
'use strict';

var el = element.createElement;
var cfg = window.odwDatasetBlock || { datasets: [], labels: {} };
var datasets = cfg.datasets || [];
var labels = cfg.labels || {};

function findDataset( id ) {
for ( var i = 0; i < datasets.length; i++ ) {
if ( datasets[ i ].id === id ) {
return datasets[ i ];
}
}
return null;
}

function selectOptions() {
var options = [ { label: labels.choose || '', value: 0 } ];
for ( var i = 0; i < datasets.length; i++ ) {
options.push( { label: datasets[ i ].title, value: datasets[ i ].id } );
}
return options;
}

/**
* Die Karte im Editor — kein Abbild des Frontends, sondern eine ruhige
* Zusammenfassung dessen, was veröffentlicht wird.
*
* @param {Object} dataset Ausgewählter Datensatz oder null.
* @return {Object} Element.
*/
function preview( dataset, onChange ) {
// Durchgehend die Placeholder-Komponente von WordPress: Sie bringt ihr
// Aussehen selbst mit. Eigenes CSS wäre im Beitragseditor wirkungslos —
// assets/css/admin.css lädt nur auf den Datensatz-Bildschirmen.
if ( ! datasets.length ) {
return el(
components.Placeholder,
{ icon: 'database', label: labels.title },
el( 'p', null, labels.none )
);
}

if ( ! dataset ) {
return el(
components.Placeholder,
{ icon: 'database', label: labels.title, instructions: labels.pick },
el( components.SelectControl, {
value: 0,
options: selectOptions(),
onChange: onChange,
__nextHasNoMarginBottom: true,
} )
);
}

return el(
components.Placeholder,
{ icon: 'database', label: labels.title },
el( 'p', null, el( 'strong', null, dataset.title ) ),
el( 'p', null, labels.rendered )
);
}

blocks.registerBlockType( 'odw/dataset-card', {
edit: function ( props ) {
var blockProps = blockEditor.useBlockProps();
var id = props.attributes.datasetId || 0;
var dataset = findDataset( id );

function choose( value ) {
props.setAttributes( { datasetId: parseInt( value, 10 ) || 0 } );
}

return el(
'div',
blockProps,
el(
blockEditor.InspectorControls,
null,
el(
components.PanelBody,
{ title: labels.panel, initialOpen: true },
el( components.SelectControl, {
label: labels.field,
value: id,
options: selectOptions(),
help: datasets.length ? labels.help : labels.none,
onChange: choose,
__nextHasNoMarginBottom: true,
} )
)
),
preview( dataset, choose )
);
},

// Dynamischer Block: Das Markup entsteht beim Ausliefern in PHP über
// dieselbe Funktion wie beim Shortcode, damit es nur eine Quelle gibt.
save: function () {
return null;
},
} );
}( window.wp.blocks, window.wp.element, window.wp.blockEditor, window.wp.components ) );
Loading
Loading