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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

---

## [2.41.1] — 2026-08-27

Ein Nachtrag zum Usability-Test: der Startwert der Qualitätsprüfung.

### 🐛 Fixed
- **„Neuen Datensatz anlegen" startete bei 7 %, nicht bei 0 %.** Zwei Ursachen, beide
dieselbe Sorte Fehler — der Wizard rechnete sich an, was er selbst vorgibt. WordPress legt
beim Öffnen des Formulars einen Auto-Entwurf an und feuert dabei `save_post`: Das
Änderungsdatum wurde gestempelt (5 Punkte), obwohl niemand etwas gespeichert hatte. Und
Carbon Fields liefert für ungespeicherte Felder ihren Vorgabewert zurück — die Zugriffsrechte
stehen auf „öffentlich", das zählte als Angabe plus Vokabulartreffer (10 + 5 Punkte).
Zusammen 20 von 295 bewertbaren Punkten. Der Bericht wertet auf einem Auto-Entwurf jetzt
nichts als erfüllt, und `set_modified_date()` überspringt ihn. Der Nenner bleibt dabei
stehen: Die Metriken sind prüfbar, sie sind nur noch nicht erfüllt — 0 von 0 Punkten ergäbe
wieder eine überraschende Zahl. Zwei Unit-Tests und ein E2E-Test halten die 0 fest.

---

## [2.41.0] — 2026-08-27

Fünf Punkte aus dem Usability-Test. Hilfetexte haben jetzt je einen Ort, die Definition zeigt
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ Update **both** locations:
- MINOR: New features (backward-compatible)
- PATCH: Bug fixes only

Current: **v2.39.1**
Current: **v2.41.1**

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<p align="center">
<img src="https://img.shields.io/github/license/daimpad/OpenDataWizard?style=flat-square&color=03FF9A&labelColor=101010&label=Lizenz" alt="Lizenz">
<img src="https://img.shields.io/badge/Version-2.41.0-03FF9A?style=flat-square&labelColor=101010" alt="Version">
<img src="https://img.shields.io/badge/Version-2.41.1-03FF9A?style=flat-square&labelColor=101010" alt="Version">
<img src="https://img.shields.io/badge/DCAT--AP-3.0-03FF9A?style=flat-square&labelColor=101010" alt="DCAT-AP 3.0">
<img src="https://img.shields.io/badge/PHP-%3E%3D%208.1-03FF9A?style=flat-square&labelColor=101010&logo=php&logoColor=white" alt="PHP >= 8.1">
<img src="https://img.shields.io/badge/WordPress-6.4%2B-03FF9A?style=flat-square&labelColor=101010&logo=wordpress&logoColor=white" alt="WordPress 6.4+">
Expand Down
9 changes: 9 additions & 0 deletions includes/class-fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,15 @@ public static function set_modified_date( int $post_id, \WP_Post $post ): void {
return;
}

// Auto-Entwürfe überspringen: WordPress legt sie beim Öffnen von „Neuer
// Datensatz" an und feuert dabei save_post. Ein dct:modified zu schreiben,
// bevor überhaupt jemand etwas eingetragen hat, datiert den Datensatz auf
// das Aufrufen des Formulars — und ließ die Qualitätsprüfung mit Punkten
// starten, die niemand verdient hatte.
if ( 'auto-draft' === $post->post_status ) {
return;
}

remove_action( 'save_post', array( self::class, 'set_modified_date' ), 20 );
update_post_meta( $post_id, '_odw_modified', current_time( 'Y-m-d' ) );
add_action( 'save_post', array( self::class, 'set_modified_date' ), 20, 2 );
Expand Down
24 changes: 18 additions & 6 deletions includes/class-quality.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,16 +202,28 @@ public static function calculate( int $post_id ): array {
private static function evaluate_metric( array $metric, \WP_Post $post ): ?bool {
$type = (string) ( $metric['type'] ?? '' );

// Ein Auto-Entwurf ist nichts, was jemand gespeichert hat: WordPress legt
// ihn beim Öffnen von „Neuer Datensatz" selbst an. Carbon Fields liefert
// für ungespeicherte Felder ihren Vorgabewert zurück — bei den
// Zugriffsrechten etwa „öffentlich". Ohne diese Abfrage zählte der Bericht
// also, was das Formular vorschlägt, statt was jemand eingetragen hat, und
// ein frisch geöffnetes Formular stünde nicht bei 0 %.
//
// Nicht erfüllt, nicht „nicht bewertbar": Die Metrik ist prüfbar, sie ist
// nur (noch) nicht erfüllt. Sonst schrumpfte der Nenner und 0 von 0
// Punkten ergäbe wieder eine Zahl, die niemand erwartet.
$ungespeichert = 'auto-draft' === $post->post_status;

if ( 'present' === $type ) {
return self::check_metric( (string) $metric['check'], $post );
return $ungespeichert ? false : self::check_metric( (string) $metric['check'], $post );
}

if ( 'vocab' === $type ) {
return self::check_vocab_metric( (string) $metric['check'], $post );
return $ungespeichert ? false : self::check_vocab_metric( (string) $metric['check'], $post );
}

if ( 'reachable' === $type && self::url_checks_enabled() ) {
return self::check_reachable_metric( (string) $metric['check'], $post );
return $ungespeichert ? false : self::check_reachable_metric( (string) $metric['check'], $post );
}

// SHACL (und Erreichbarkeit bei deaktivierter Einstellung) folgt in Phase 3+.
Expand Down Expand Up @@ -779,8 +791,8 @@ public static function render_meta_box( \WP_Post $post ): void {
</div>

<?php
// Erklärt den Startwert: Ein frisch gespeicherter, leerer Entwurf steht
// nicht bei 0 %, weil das Änderungsdatum automatisch gesetzt wird.
// Erklärt den Sprung nach dem ersten Speichern: Das Änderungsdatum wird
// automatisch gesetzt, ein leerer Entwurf steht danach also über 0 %.
$auto_points = 0;
foreach ( self::AUTO_FULFILLED as $auto_key ) {
if ( 'passed' === ( $metrics[ $auto_key ]['status'] ?? '' ) ) {
Expand All @@ -794,7 +806,7 @@ public static function render_meta_box( \WP_Post $post ): void {
echo esc_html(
sprintf(
/* translators: %d: MQA points the plugin fulfils by itself */
__( 'Davon steuert der Wizard %d Punkte selbst bei: Das Änderungsdatum wird bei jedem Speichern automatisch gesetzt. Deshalb steht auch ein noch leerer Datensatz nicht bei 0 %%.', 'open-data-wizard' ),
__( 'Davon steuert der Wizard %d Punkte selbst bei: Das Änderungsdatum wird bei jedem Speichern automatisch gesetzt. Ein neu angelegter Datensatz steht deshalb bei 0 %% und steigt mit dem ersten Speichern — zusammen mit den Feldern, die das Formular vorbelegt (etwa die Zugriffsrechte).', 'open-data-wizard' ),
$auto_points
)
);
Expand Down
Binary file modified languages/open-data-wizard-en_US.mo
Binary file not shown.
4 changes: 2 additions & 2 deletions languages/open-data-wizard-en_US.po
Original file line number Diff line number Diff line change
Expand Up @@ -1910,8 +1910,8 @@ msgstr "+%d available"
msgid "(automatisch)"
msgstr "(automatic)"

msgid "Davon steuert der Wizard %d Punkte selbst bei: Das Änderungsdatum wird bei jedem Speichern automatisch gesetzt. Deshalb steht auch ein noch leerer Datensatz nicht bei 0 %%."
msgstr "Of these, the wizard contributes %d points itself: the modification date is set automatically every time you save. That is why even an empty dataset does not start at 0 %%."
msgid "Davon steuert der Wizard %d Punkte selbst bei: Das Änderungsdatum wird bei jedem Speichern automatisch gesetzt. Ein neu angelegter Datensatz steht deshalb bei 0 %% und steigt mit dem ersten Speichern — zusammen mit den Feldern, die das Formular vorbelegt (etwa die Zugriffsrechte)."
msgstr "Of these, the wizard contributes %d points by itself: the modification date is set automatically on every save. A newly created dataset therefore starts at 0 %% and rises with the first save — together with the fields the form pre-fills (the access rights, for instance)."

msgid "(ohne Titel, ID %d)"
msgstr "(untitled, ID %d)"
Expand Down
2 changes: 1 addition & 1 deletion languages/open-data-wizard.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1531,7 +1531,7 @@ msgstr ""
msgid "(automatisch)"
msgstr ""

msgid "Davon steuert der Wizard %d Punkte selbst bei: Das Änderungsdatum wird bei jedem Speichern automatisch gesetzt. Deshalb steht auch ein noch leerer Datensatz nicht bei 0 %%."
msgid "Davon steuert der Wizard %d Punkte selbst bei: Das Änderungsdatum wird bei jedem Speichern automatisch gesetzt. Ein neu angelegter Datensatz steht deshalb bei 0 %% und steigt mit dem ersten Speichern — zusammen mit den Feldern, die das Formular vorbelegt (etwa die Zugriffsrechte)."
msgstr ""

msgid "Welchen Engagementfeldern ist dieser Datensatz zuzuordnen?"
Expand Down
4 changes: 2 additions & 2 deletions open-data-wizard.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.41.0
* Version: 2.41.1
* Requires at least: 6.4
* Requires PHP: 8.1
* Author: nozilla
Expand All @@ -26,7 +26,7 @@
exit;
}

define( 'ODW_VERSION', '2.41.0' );
define( 'ODW_VERSION', '2.41.1' );
define( 'ODW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'ODW_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'ODW_PLUGIN_FILE', __FILE__ );
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-data-wizard",
"version": "2.41.0",
"version": "2.41.1",
"description": "WordPress plugin for publishing open data as DCAT-AP 3.0 compliant metadata",
"author": "Open Data Wizard Team",
"license": "GPL-2.0-or-later",
Expand Down
11 changes: 11 additions & 0 deletions tests/e2e/admin-workflow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ test.describe('Formular', () => {
await expect(page.locator(cfField('odw_cessda_topic'))).toHaveValue(/^https?:\/\//);
});

test('ein frisch geöffnetes Formular startet bei 0 %', async ({ page }) => {
await page.goto('/wp-admin/post-new.php?post_type=odw_dataset');

// WordPress legt beim Öffnen einen Auto-Entwurf an und feuert dabei
// save_post. Vorher zählte der Bericht dessen Vorgabewerte mit — die
// Zugriffsrechte stehen auf „öffentlich", das Änderungsdatum wurde
// gestempelt — und das leere Formular startete bei 7 %.
const box = page.locator('#odw-quality-report');
await expect(box.locator('.odw-quality-percent')).toHaveText('0 %');
});

test('wechselt auf den zweiten Reiter', async ({ page }) => {
await page.goto('/wp-admin/post-new.php?post_type=odw_dataset');

Expand Down
108 changes: 108 additions & 0 deletions tests/test-quality.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,41 @@

use PHPUnit\Framework\TestCase;

if ( ! class_exists( 'WP_Post' ) ) {
/**
* Minimaler WP_Post-Ersatz.
*
* ODW_Quality::calculate() und evaluate_metric() sind auf \WP_Post typisiert,
* WP_Mock bringt die Klasse aber nicht mit. Mehr als diese drei Felder liest
* der Qualitätsbericht nicht.
*
* @package OpenDataWizard
*/
class WP_Post {

/**
* Post ID.
*
* @var int
*/
public $ID = 0;

/**
* Post type.
*
* @var string
*/
public $post_type = 'odw_dataset';

/**
* Post status.
*
* @var string
*/
public $post_status = 'draft';
}
}

/**
* Unit tests for ODW_Quality.
*
Expand Down Expand Up @@ -550,4 +585,77 @@ function ( $id, $key ) {

$this->assertSame( 'http://dcat-ap.de/def/licenses/cc-by/4.0', $rows[1]['license'] );
}

// -------------------------------------------------------------------------
// Startwert eines frisch geöffneten Formulars
// -------------------------------------------------------------------------

/**
* Baut ein Post-Objekt für die Bewertung.
*
* @param string $status Post-Status.
* @return \WP_Post
*/
private function post_with_status( string $status ): \WP_Post {
$post = new \WP_Post();
$post->ID = 42;
$post->post_type = 'odw_dataset';
$post->post_status = $status;

return $post;
}

/**
* Auf einem Auto-Entwurf zählt kein Wert — auch keiner, der dasteht.
*
* WordPress legt den Auto-Entwurf beim Öffnen von „Neuer Datensatz" selbst
* an. Carbon Fields liefert für ungespeicherte Felder ihren Vorgabewert
* zurück; ohne die Abfrage zählte der Bericht also, was das Formular
* vorschlägt. Derselbe Meta-Wert, einmal je Status: Nur der Entwurf zählt.
*/
public function test_evaluate_metric_counts_nothing_on_auto_draft(): void {
$this->load_class();

\WP_Mock::userFunction( 'get_post_meta' )->andReturn( '2026-08-27' );

$metric = array(
'key' => 'modified',
'type' => 'present',
'check' => 'modified',
);

$this->assertTrue(
$this->call_private( 'evaluate_metric', $metric, $this->post_with_status( 'draft' ) ),
'Ein gespeicherter Entwurf mit Änderungsdatum muss die Metrik erfüllen.'
);

$this->assertFalse(
$this->call_private( 'evaluate_metric', $metric, $this->post_with_status( 'auto-draft' ) ),
'Auf dem Auto-Entwurf darf derselbe Wert nicht zählen.'
);
}

/**
* „Neuen Datensatz anlegen" startet bei 0 %, nicht bei 7 %.
*
* Die Mocks liefern absichtlich für jedes Feld einen gefüllten Wert: Der Test
* hält damit nicht bloß fest, dass ein leeres Formular 0 ergibt, sondern dass
* der Auto-Entwurf gar nichts anrechnet. Der Nenner muss stehen bleiben —
* 0 von 0 Punkten ergäbe sonst wieder eine überraschende Zahl.
*/
public function test_calculate_scores_a_fresh_form_at_zero(): void {
$this->load_class();

\WP_Mock::userFunction( 'get_post' )->andReturn( $this->post_with_status( 'auto-draft' ) );
\WP_Mock::userFunction( 'carbon_get_post_meta' )->andReturn( 'irgendein Wert' );
\WP_Mock::userFunction( 'get_post_meta' )->andReturn( '2026-08-27' );
\WP_Mock::userFunction( 'current_time' )->andReturn( '2026-08-27 10:00:00' );
\WP_Mock::userFunction( '__' )->andReturnArg( 0 );

$result = ODW_Quality::calculate( 42 );

$this->assertSame( 0, $result['achieved'] );
$this->assertSame( 0, $result['score'] );
$this->assertGreaterThan( 0, $result['assessable'], 'Die Metriken bleiben bewertbar, sie sind nur nicht erfüllt.' );
}
}