From 31dea6bf92e8e2d2050414f4b40495ec7382cae6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 28 Jun 2026 09:58:01 +0000 Subject: [PATCH] fix: Tooltip-/CESSDA-/Qualitaets-Bugs aus Live-Test (v2.11.3) - Hilfe-Tooltip (i) jetzt im Label verankert -> gleiche Zeile wie die Formularfrage (statt Umbruch unter dem block-level Label). - CESSDA-Themenklassifikation: das ausgeblendete CF-Feld hatte keinen sichtbaren Tooltip; das JS-Widget erhaelt nun denselben i-Tooltip mit Erklaerung (dct:subject) und Handlungsaufforderung. - Qualitaetspruefung: Labels Beschreibung/Herausgeber/Lizenz tragen nun ihren DCAT-AP-Namen (dct:description/publisher/license), konsistent mit den uebrigen Indikatoren (auch in Validierungsmeldungen). - Qualitaets-Score beim ersten Speichern korrekt: Recalc-Hook von save_post_odw_dataset auf generisches save_post (Prio 30, nach dem Carbon-Fields-Save bei Prio 10) verschoben + Post-Type-Guard. WordPress feuert save_post_{post_type} vor save_post, daher lasen wir zuvor veraltete Meta (Score erst beim 2. Speichern korrekt). Batch-Import berechnet die Qualitaet nun explizit nach dem Schreiben der Meta. - Neuer Registry-Test: jedes Label traegt seinen DCAT-AP-Property-Namen. Version 2.11.3, CHANGELOG, i18n (.pot/.po/.mo). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01QipNktYu915Uv3Nj13mWRY --- CHANGELOG.md | 15 ++++ README.md | 2 +- assets/js/odw-admin-fields.js | 119 ++++++++++++++++++---------- config/dcat-ap-fields.php | 6 +- includes/class-admin.php | 1 + includes/class-batch-import.php | 7 ++ includes/class-quality.php | 14 +++- languages/open-data-wizard-en_US.mo | Bin 39909 -> 40533 bytes languages/open-data-wizard-en_US.po | 6 ++ languages/open-data-wizard.pot | 6 ++ open-data-wizard.php | 4 +- tests/test-registry-schema.php | 19 +++++ 12 files changed, 147 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62a8e5d..6eedee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.ht --- +## [2.11.3] — 2026-06-28 + +UX-/Korrektur-Fixes aus Praxis-Feedback bei der Live-Installation. + +### 🐛 Fixed +- **Hilfe-Tooltip (ⓘ) sprang in die nächste Zeile:** Das ⓘ-Symbol wird jetzt **innerhalb des Feld-Labels** verankert und sitzt damit in derselben Zeile wie die Formularfrage (zuvor als Geschwister-Element unter dem block-level Label umgebrochen). +- **CESSDA-Themenklassifikation ohne Erklärung:** Da das eigentliche CF-Feld (mit Hilfetext) ausgeblendet ist, hatte das JS-gebaute Eingabe-Widget keinen Tooltip. Es erhält nun denselben ⓘ-Tooltip mit Erklärung **(dct:subject)** inkl. Handlungsaufforderung („Thema eintippen oder auswählen"). Die URI wird weiterhin automatisch verknüpft. +- **DCAT-AP-Namen in der Qualitätsprüfung ergänzt:** Die Indikatoren *Beschreibung*, *Herausgeber* und *Lizenz* trugen in der Registry (`config/dcat-ap-fields.php`) noch die reine Formularfrage als Label; sie heißen nun einheitlich `Beschreibung (dct:description)`, `Herausgeber (dct:publisher)` und `Lizenz (dct:license)` — konsistent mit den übrigen Indikatoren (wirkt auch auf Validierungsmeldungen). +- **Qualitäts-Score wird beim ersten Veröffentlichen/Speichern korrekt berechnet:** Der Neuberechnungs-Hook lief auf `save_post_odw_dataset`, das WordPress **vor** dem generischen `save_post` feuert — genau dort speichert Carbon Fields aber erst seine Meta-Werte (Priorität 10). Dadurch wurde der Score aus veralteten Daten berechnet und stimmte erst nach dem **zweiten** Speichern. Der Hook läuft nun auf `save_post` (Priorität 30, nach dem CF-Save) mit Post-Type-Guard. Batch-Importe berechnen die Qualität nun ebenfalls explizit nach dem Schreiben der Metadaten. + +### ✅ Tests +- Neuer Registry-Test stellt sicher, dass jedes Label seinen DCAT-AP-Property-Namen trägt. + +--- + ## [2.11.2] — 2026-06-28 ### 🔧 Changed diff --git a/README.md b/README.md index 42b55d7..e6c56d2 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) -![Version](https://img.shields.io/badge/Version-2.11.2-brightgreen?style=flat-square) +![Version](https://img.shields.io/badge/Version-2.11.3-brightgreen?style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-willkommen-brightgreen?style=flat-square) 📖 [Dokumentation](DOCUMENTATION.md) · 📐 [Technische Spezifikation](TECHNICAL-SPEC.md) · 📝 [Changelog](CHANGELOG.md) · 🛡️ [Security](SECURITY.md) · ⚖️ [Lizenz](LICENSE) diff --git a/assets/js/odw-admin-fields.js b/assets/js/odw-admin-fields.js index 67c2c9c..d0069e7 100644 --- a/assets/js/odw-admin-fields.js +++ b/assets/js/odw-admin-fields.js @@ -115,6 +115,13 @@ lbl.className = 'odw-cessda-label'; lbl.textContent = w.label || ''; + // The real CF field (with its help text) is hidden, so attach the same + // ⓘ tooltip here to explain what the CESSDA classification is. + if ( w.help ) { + var tipLabel = ( data.helpTip && data.helpTip.label ) || ''; + lbl.appendChild( buildHelpTip( helpTextNode( w.help ), tipLabel ) ); + } + var input = document.createElement( 'input' ); input.type = 'text'; input.className = 'odw-cessda-input'; @@ -415,6 +422,66 @@ // label one hover/click away. Progressive enhancement: without JS the help // text simply stays inline. // ------------------------------------------------------------------------- + // One document-level handler closes any open click-tooltip on outside tap. + function ensureTipDocHandler() { + if ( document.body.dataset.odwTipDocBound ) { + return; + } + document.body.dataset.odwTipDocBound = '1'; + document.addEventListener( 'click', function ( e ) { + document.querySelectorAll( '.odw-help-tip-wrap.is-open' ).forEach( function ( w ) { + if ( ! w.contains( e.target ) ) { + w.classList.remove( 'is-open' ); + var b = w.querySelector( '.odw-help-tip' ); + if ( b ) { + b.setAttribute( 'aria-expanded', 'false' ); + } + } + } ); + } ); + } + + // Build an ⓘ tooltip wrapper around a content node. Reused by the field help + // tooltips and the JS-built widgets (e.g. CESSDA) so they look identical. + function buildHelpTip( contentNode, tipLabel ) { + ensureTipDocHandler(); + + var wrap = document.createElement( 'span' ); + wrap.className = 'odw-help-tip-wrap'; + + var btn = document.createElement( 'button' ); + btn.type = 'button'; + btn.className = 'odw-help-tip'; + btn.setAttribute( 'aria-label', tipLabel || 'Hilfe' ); + btn.setAttribute( 'aria-expanded', 'false' ); + btn.innerHTML = ''; + + var pop = document.createElement( 'span' ); + pop.className = 'odw-help-pop'; + pop.setAttribute( 'role', 'tooltip' ); + pop.appendChild( contentNode ); + + wrap.appendChild( btn ); + wrap.appendChild( pop ); + + // Click toggles for touch/keyboard; hover/focus is handled in CSS. + btn.addEventListener( 'click', function ( e ) { + e.preventDefault(); + var open = wrap.classList.toggle( 'is-open' ); + btn.setAttribute( 'aria-expanded', open ? 'true' : 'false' ); + } ); + + return wrap; + } + + // Build a popup content node from a plain (multi-line) help string. + function helpTextNode( text ) { + var span = document.createElement( 'span' ); + span.className = 'odw-help-pop__content'; + span.textContent = text; // CSS white-space:pre-line preserves line breaks. + return span; + } + function initHelpTooltips() { var tip = data.helpTip || {}; document.querySelectorAll( '.cf-field' ).forEach( function ( field ) { @@ -427,62 +494,26 @@ if ( ! help || ! ( help.textContent || '' ).trim() ) { return; } - var head = field.querySelector( ':scope > .cf-field__head' ) || field.querySelector( ':scope > label' ); - if ( ! head ) { + // Anchor the ⓘ inside the label so it sits inline with the question + // text (the head/label is block-level, so a sibling would wrap below). + var anchor = field.querySelector( ':scope > .cf-field__head > .cf-field__label' ) || + field.querySelector( ':scope > .cf-field__head' ) || + field.querySelector( ':scope > label' ); + if ( ! anchor ) { return; } // The attribute both guards re-processing and drives the CSS rule that // hides the inline help (robust even if React re-renders the ). field.dataset.odwTipInit = '1'; - var wrap = document.createElement( 'span' ); - wrap.className = 'odw-help-tip-wrap'; - - var btn = document.createElement( 'button' ); - btn.type = 'button'; - btn.className = 'odw-help-tip'; - btn.setAttribute( 'aria-label', tip.label || 'Hilfe' ); - btn.setAttribute( 'aria-expanded', 'false' ); - btn.innerHTML = ''; - - var pop = document.createElement( 'span' ); - pop.className = 'odw-help-pop'; - pop.setAttribute( 'role', 'tooltip' ); - // Clone (not move) the React-owned help node into the popup so its // exact content/formatting is preserved while React keeps owning the - // original, which the CSS rule above hides from the inline flow. + // original, which the CSS rule hides from the inline flow. var content = help.cloneNode( true ); content.className = 'odw-help-pop__content'; - pop.appendChild( content ); - - wrap.appendChild( btn ); - wrap.appendChild( pop ); - head.appendChild( wrap ); - // Click toggles for touch/keyboard; hover/focus is handled in CSS. - btn.addEventListener( 'click', function ( e ) { - e.preventDefault(); - var open = wrap.classList.toggle( 'is-open' ); - btn.setAttribute( 'aria-expanded', open ? 'true' : 'false' ); - } ); + anchor.appendChild( buildHelpTip( content, tip.label ) ); } ); - - // One document-level handler closes any open click-tooltip on outside tap. - if ( ! document.body.dataset.odwTipDocBound ) { - document.body.dataset.odwTipDocBound = '1'; - document.addEventListener( 'click', function ( e ) { - document.querySelectorAll( '.odw-help-tip-wrap.is-open' ).forEach( function ( w ) { - if ( ! w.contains( e.target ) ) { - w.classList.remove( 'is-open' ); - var b = w.querySelector( '.odw-help-tip' ); - if ( b ) { - b.setAttribute( 'aria-expanded', 'false' ); - } - } - } ); - } ); - } } // ------------------------------------------------------------------------- diff --git a/config/dcat-ap-fields.php b/config/dcat-ap-fields.php index 7c802ea..5d2d3cf 100644 --- a/config/dcat-ap-fields.php +++ b/config/dcat-ap-fields.php @@ -59,7 +59,7 @@ 'key' => 'description', 'meta_key' => '_odw_description', 'dcat_prop' => 'dct:description', - 'label' => __( 'Worum geht es in diesem Datensatz?', 'open-data-wizard' ), + 'label' => __( 'Beschreibung (dct:description)', 'open-data-wizard' ), 'points' => 10, 'required' => true, 'profile' => 'ap', @@ -73,7 +73,7 @@ 'key' => 'publisher', 'meta_key' => '_odw_publisher', 'dcat_prop' => 'dct:publisher', - 'label' => __( 'Wer gibt diese Daten heraus?', 'open-data-wizard' ), + 'label' => __( 'Herausgeber (dct:publisher)', 'open-data-wizard' ), 'points' => 10, 'required' => true, 'profile' => 'ap', @@ -87,7 +87,7 @@ 'key' => 'license', 'meta_key' => '', 'dcat_prop' => 'dct:license', - 'label' => __( 'Unter welcher Lizenz sind diese Daten verfügbar?', 'open-data-wizard' ), + 'label' => __( 'Lizenz (dct:license)', 'open-data-wizard' ), 'points' => 10, 'required' => true, 'profile' => 'ap', diff --git a/includes/class-admin.php b/includes/class-admin.php index be781fe..0f502d0 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -378,6 +378,7 @@ public static function enqueue_assets( string $hook ): void { 'label' => __( 'CESSDA Themenklassifikation', 'open-data-wizard' ), 'placeholder' => __( 'Thema eintippen oder auswählen…', 'open-data-wizard' ), 'linkLabel' => __( 'Verknüpfte URI:', 'open-data-wizard' ), + 'help' => __( 'CESSDA-THEMENKLASSIFIKATION (dct:subject)', 'open-data-wizard' ) . "\n\n" . __( 'Fachliche Einordnung Ihres Datensatzes aus dem CESSDA Controlled Vocabulary (Version 4.2.3, Deutsch). Tippen Sie ein Thema ein oder wählen Sie eines aus der Liste — die passende URI wird automatisch verknüpft. Beispiel: Volkszählungen, Migration, Wirtschaftspolitik.', 'open-data-wizard' ), ), 'helpTip' => array( 'label' => __( 'Hilfe anzeigen', 'open-data-wizard' ), diff --git a/includes/class-batch-import.php b/includes/class-batch-import.php index ec86d6b..385e14d 100644 --- a/includes/class-batch-import.php +++ b/includes/class-batch-import.php @@ -489,6 +489,13 @@ private static function create_dataset_from_record( array $record, int $row_inde // Set import tracking. update_post_meta( $post_id, '_odw_imported', current_time( 'mysql' ) ); + // Recalculate quality now that all meta is written. wp_insert_post() above + // fired save_post before these meta values existed, so the score stored then + // would be stale; recompute explicitly to reflect the imported metadata. + if ( class_exists( 'ODW_Quality' ) ) { + ODW_Quality::store( $post_id, ODW_Quality::calculate( $post_id ) ); + } + return array( 'success' => true, 'post_id' => $post_id, diff --git a/includes/class-quality.php b/includes/class-quality.php index 2fb87d8..2dac924 100644 --- a/includes/class-quality.php +++ b/includes/class-quality.php @@ -39,8 +39,13 @@ class ODW_Quality { * Registers WordPress hooks. */ public static function init(): void { - // Qualität nach jedem echten Speichern neu berechnen (nach CF-Save und set_modified_date). - add_action( 'save_post_odw_dataset', array( self::class, 'recalculate_on_save' ), 30 ); + // Qualität nach jedem echten Speichern neu berechnen. Wichtig: das generische + // save_post (Priorität 30) statt save_post_odw_dataset verwenden — WordPress + // feuert save_post_{post_type} VOR dem generischen save_post, an dem Carbon + // Fields seine Meta-Werte erst speichert (Priorität 10). Auf save_post_odw_dataset + // liefen wir vor dem CF-Save und lasen veraltete Meta (Score erst beim 2. Speichern + // korrekt). Priorität 30 garantiert, dass CF (10) bereits geschrieben hat. + add_action( 'save_post', array( self::class, 'recalculate_on_save' ), 30 ); // Meta-Box auf dem Edit-Screen registrieren. add_action( 'add_meta_boxes', array( self::class, 'register_meta_box' ) ); @@ -332,6 +337,11 @@ public static function recalculate_on_save( int $post_id ): void { return; } + // Now hooked on the generic save_post, so guard the post type explicitly. + if ( 'odw_dataset' !== get_post_type( $post_id ) ) { + return; + } + $result = self::calculate( $post_id ); self::store( $post_id, $result ); } diff --git a/languages/open-data-wizard-en_US.mo b/languages/open-data-wizard-en_US.mo index 53bbdafe859e70b583871c6aa7bcc1c2c87492c7..da6189072b98b2492b1ea70a6fb0a8022d295c53 100644 GIT binary patch delta 7022 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 delta 6653 zcmYM%2~?L=8prXAiXfn|%kCc(*#r$l5flRyw=|bfb0sxJl3WoN@b5AtX)akth9+iZ zQH=|>O0pqVQ`!^^AEb{QqXqe3^bCUO*r8i7>{6A*e@tIgZ3I`lZ+b*I*Cag4y^U z#$pS$GsbOPk+&Kq8H>2l6a8@=PQp6ugg3D>h7qMN_C>}pgE0%IV-9Y|d_0FeF*4ei z3LJ@g??coAPGJo5n=fc+(JdH^zA;vpV57SGpGr_N7Y0y z52*(Fq23#ZTF^r0`WDo4FQK;JH0I)kc=A7!hO4WMbS;uK^9Y{CHChI;NrBnIQGwQ=-g^S|-X5HTNAL)yQ6LKR25!PXa64{rlW%Vt$tia4Gw=lc#W)oU zy0I$Uja)Ln;AQMg1^J4~^3WwrHvb*xzOw z9bd;d?q9@c^c-lby)(|HKL=G@XYc`RQ)tXIT!^ZLGuQ>Mpq^_#$bRB;&{yYwA`LHY z%tBT5TvTzazzBR8HQ*j>i$0t+K7u9ywRO3uQ!p6k;6!BC%!jBg@f>20Z4#E1U~APy=-uY9~%d9iQP&e+GKfcVj!O!9?7Gdhbotp8gFpvHdXnegWQ1e*$*G zy{N4@iT=!QzM!EP^aKqtVC}<__7))57TO-4Xh9q=x|KJaX1-QVF&yI zm4WZ@cI-mbaxAKdYfyn69!36@l8ao>F=;j0t~eCE=x3up=3^|5L8Y!7H9)m4pZ<4%)l5fYN4ZXGcG`7uC;rdJ&&n4mkar*qB?;0V-v>Upz-#% z-Aq(1?86XjL7kFdzAg%^JNCwDs0=-cTFBd|Ok6~be;pNoJB+V_&UHR2#l`5t)yOx* z?8bP!gepegiS~YX>`H$mD&-ZZ6+Y+OKZZf{FXO$~j0#}#BwLiF$X2>d6^-`X*nlpq zLlxH{RHVn8{&kF^|0`nD*Z8&el~W(d8iN8W>hiOBHu7mkIKXq z9OkC+6OCxhBTtHaD(Zn1sEOBO7VdKV3{|W_(`@QKGSXH zIy$DHTdC|%<4zodT5&yw;p;dLKf&&pKg0eOTYx$ZCs2X3U>{7n&lcTu)b$#t--rt2 z9QMMW@gk)*%y{L@1Q7=A$3gn>E{}go^uA>$d zT56AD9I{)c2(_TsPywGnJ$KPfLj!(?s#c%-?YB7sb-f$5!@_DGH!fU3rL;R0cMM0MABN1gsg1)_`a@7# zQ-Qg-6$9{;<2R@+z3CXUzY07F8Oy;ej=%jMSjc`6H;z z?8FYZ7xe*p3t5b5LQODqkv)EsQ45&i^yj0BbBWVmfw4OO)ijj4J*WvAQN?%&wZe~4 z1NoKPtqMm4l#1H>9MpL)KxJ?!D$v=OflHnHFJl7zgP4e)qkB6I&&BrJ|1@f%_c0GY zK_3jN;ExK7K}|FXm7(dViAqodFF}2%>M<72V+uB-7L>5W7F`yqs7LDWFP+l~T+qrF zVHj394?K=abv^FHy{LiiT569`F$U3JjGAa2s_I`wW#S*GiN42RY{t&$zsxSQ=Q8rI zJq}6-QA4eT>?o3#bKLM+M%3D%RU7 z?E+%mG&E3e49B4uiZh%CmSY(GM^GzhK&AX`)PyHdD>{oT&NO2PmRH$v*P}A~l+)jX zL+Sqo)6ng?!u}=G6SenCPy<(^2HJpKaW_Weaa3(w!G4%b2DJcvNhjcP)QZob?q9-% z9t644{%)vUWdk~d%;PpkY1~Q~D&-eYD+*q1_cRMz(Vu~uXcj6H%TT9g6ZXeC%)utq za~;>v#$vi@*Bzn=$z;>8}D#n4R7bjr? zmZ4U>74`l>yc17g6ozwTw8CDf_X<&gjzb@;KrM9jdh)O8euxW&_%sg2tEiQvKg`j> zf%pKn*hv@^*FQ!bvrn-bUUIGnZnS}ip-xNhjpU!BZT52^3%hNy zMRgx46Z0`1m*ObgiOS6Pn1aoiiV2(T3X4$Vl%s0u&yFWh0sR|Y_&aKxX!j#_!UT-q zLI&!_2-E=Mo&I8+Lw_Z9#s8oJ4BldoTNLWJrl2yChjDl}Y5^6Pgga10_yKlC_vbYF z&@hkMqRGbYbZ27^+=fc+amTOm9{Md9i{l@&?=M58`XKTRG8a%2OxbEzz8XvDKZbhm z4=mRC@ASA$-AYvI>QS|D9s|%-Ya>p_O!|W`9jj19xDO-oB&w*cp{o4{OvcD3?EOM? z(VvFO>>BK&^S_IR_Oc08rC!_Y3kj$R3Q*sLX&8oU(S`Nc0gvEt{5z_;WBA$xVG?SK z`k^-tM2$BBeX$G^Cev8zJg^HjK?7=l_fV0ZMSuJW2VvVf8^8!uhUQ`juEId9#cXV- gYdjwQU!TUyUH)j__+!SDUX7KdnSPBsDqipUKf;h;@c;k- diff --git a/languages/open-data-wizard-en_US.po b/languages/open-data-wizard-en_US.po index 8384f60..738f069 100644 --- a/languages/open-data-wizard-en_US.po +++ b/languages/open-data-wizard-en_US.po @@ -1235,3 +1235,9 @@ msgstr "The JSON-LD view below shows the last saved result." msgid "Speichern Sie den Datensatz, um sie zu aktualisieren." msgstr "Save the dataset to update it." + +msgid "CESSDA-THEMENKLASSIFIKATION (dct:subject)" +msgstr "CESSDA TOPIC CLASSIFICATION (dct:subject)" + +msgid "Fachliche Einordnung Ihres Datensatzes aus dem CESSDA Controlled Vocabulary (Version 4.2.3, Deutsch). Tippen Sie ein Thema ein oder wählen Sie eines aus der Liste — die passende URI wird automatisch verknüpft. Beispiel: Volkszählungen, Migration, Wirtschaftspolitik." +msgstr "A subject classification of your dataset from the CESSDA Controlled Vocabulary (version 4.2.3, German). Type a topic or pick one from the list — the matching URI is linked automatically. Example: censuses, migration, economic policy." diff --git a/languages/open-data-wizard.pot b/languages/open-data-wizard.pot index 2c54c2f..db45c79 100644 --- a/languages/open-data-wizard.pot +++ b/languages/open-data-wizard.pot @@ -1232,3 +1232,9 @@ msgstr "" msgid "Speichern Sie den Datensatz, um sie zu aktualisieren." msgstr "" + +msgid "CESSDA-THEMENKLASSIFIKATION (dct:subject)" +msgstr "" + +msgid "Fachliche Einordnung Ihres Datensatzes aus dem CESSDA Controlled Vocabulary (Version 4.2.3, Deutsch). Tippen Sie ein Thema ein oder wählen Sie eines aus der Liste — die passende URI wird automatisch verknüpft. Beispiel: Volkszählungen, Migration, Wirtschaftspolitik." +msgstr "" diff --git a/open-data-wizard.php b/open-data-wizard.php index b485cf5..825e383 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: 2.11.2 + * Version: 2.11.3 * Requires at least: 6.4 * Requires PHP: 8.1 * Author: nozilla @@ -26,7 +26,7 @@ exit; } -define( 'ODW_VERSION', '2.11.2' ); +define( 'ODW_VERSION', '2.11.3' ); define( 'ODW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'ODW_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); define( 'ODW_PLUGIN_FILE', __FILE__ ); diff --git a/tests/test-registry-schema.php b/tests/test-registry-schema.php index 6ea2e21..fc778a1 100644 --- a/tests/test-registry-schema.php +++ b/tests/test-registry-schema.php @@ -109,4 +109,23 @@ public function test_keys_are_unique(): void { $keys = array_column( $this->defs, 'key' ); $this->assertSame( count( $keys ), count( array_unique( $keys ) ) ); } + + /** + * Every label carries its DCAT-AP property name so the quality report and + * validation messages stay consistent (regression: description/publisher/ + * license previously used the bare form question without the dct/dcat name). + */ + public function test_labels_include_dcat_property_name(): void { + foreach ( $this->defs as $entry ) { + $prop = (string) ( $entry['dcat_prop'] ?? '' ); + if ( '' === $prop ) { + continue; + } + $this->assertStringContainsString( + '(' . $prop . ')', + (string) $entry['label'], + "Label for '{$entry['key']}' must include its DCAT-AP property name {$prop}." + ); + } + } }