From 439ffd202ddf68240528718e3934a29e937cf640 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 10:29:56 +0000 Subject: [PATCH] feat: File link OR media-library upload + Datenatlas publish link (v2.17.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Data provision (#5): the access-URL field is relabelled "Ergänzen Sie den Link zu Ihrem Datensatz oder laden Sie die Datei in die Mediathek hoch" and is no longer hard-required client-side. A media-library upload now doubles as the distribution — on save, the access URL (and format) are derived from the uploaded file when the user didn't enter them, so the URL never has to be typed twice. Publish validation accepts either an access URL or an uploaded file. Datenatlas (#1, option A): the final wizard step (preview) shows an optional button linking to https://datenatlas-zivilgesellschaft.de. - class-fields.php: field relabel + optional; Datenatlas HTML section in tab 5 - class-admin.php: save_file_attachment derives _odw_access_url/_odw_format - class-validation.php: has_valid_distribution accepts an uploaded file - admin.css: Datenatlas section styling Version 2.17.0. Tests: 151 pass, PHPStan clean, PHPCS exit 0. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JB1xUQM892bVZ4Yv3MZjvq --- CHANGELOG.md | 13 +++++++++++++ README.md | 2 +- assets/css/admin.css | 14 ++++++++++++++ includes/class-admin.php | 21 ++++++++++++++++++++- includes/class-fields.php | 15 ++++++++++++--- includes/class-validation.php | 16 +++++++++++++++- open-data-wizard.php | 4 ++-- 7 files changed, 77 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d6465f..7992ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.ht --- +## [2.17.0] — 2026-07-09 + +### ✨ Added +- **Datei per Link ODER Upload** (Datenbereitstellung): Das Zugriffs-URL-Feld heißt jetzt + „Ergänzen Sie den Link zu Ihrem Datensatz oder laden Sie die Datei in die Mediathek hoch". Wer eine + Datei über die Mediathek-Box hochlädt, muss die URL **nicht** mehr eintippen — Zugriffs-URL und + Format werden beim Speichern automatisch aus der Datei übernommen. Pflichtprüfung ist erfüllt, + sobald **entweder** ein Link eingetragen **oder** eine Datei hochgeladen wurde. +- **„Auf dem Datenatlas Zivilgesellschaft veröffentlichen"** (optional): Im letzten Wizard-Schritt + (Vorschau) verlinkt ein Button auf [datenatlas-zivilgesellschaft.de](https://datenatlas-zivilgesellschaft.de). + +--- + ## [2.16.0] — 2026-07-08 ### ✨ Changed diff --git a/README.md b/README.md index 8ced30d..16cfd90 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.16.0-brightgreen?style=flat-square) +![Version](https://img.shields.io/badge/Version-2.17.0-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/css/admin.css b/assets/css/admin.css index 54f2d00..8df5bba 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -857,3 +857,17 @@ color: #a0a5aa; font-style: italic; } + +/* ========================================================================= + Datenatlas Zivilgesellschaft — Veröffentlichen-Abschnitt (Tab 5) + ========================================================================= */ + +.odw-datenatlas-publish { + margin-top: 20px; + padding-top: 16px; + border-top: 1px solid var(--odw-color-border, #c3c4c7); +} + +.odw-datenatlas-publish h4 { + font-size: 14px; +} diff --git a/includes/class-admin.php b/includes/class-admin.php index 42f9cd3..d82387a 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -544,10 +544,29 @@ public static function save_file_attachment( int $post_id, \WP_Post $post ): voi update_post_meta( $post_id, '_odw_file_id', $file_id ); if ( $file_id > 0 ) { + $ext = ''; $file_path = get_attached_file( $file_id ); if ( $file_path && file_exists( $file_path ) ) { update_post_meta( $post_id, '_odw_file_size', (int) filesize( $file_path ) ); - update_post_meta( $post_id, '_odw_file_format', strtoupper( (string) pathinfo( $file_path, PATHINFO_EXTENSION ) ) ); + $ext = strtoupper( (string) pathinfo( $file_path, PATHINFO_EXTENSION ) ); + update_post_meta( $post_id, '_odw_file_format', $ext ); + } + + // A media-library upload doubles as the distribution: derive the + // access URL (and format) from the file when the user did not enter + // them manually, so they never have to type the URL twice. + if ( '' === trim( (string) get_post_meta( $post_id, '_odw_access_url', true ) ) ) { + $attachment_url = wp_get_attachment_url( $file_id ); + if ( $attachment_url ) { + update_post_meta( $post_id, '_odw_access_url', esc_url_raw( $attachment_url ) ); + } + } + + if ( '' !== $ext + && '' === trim( (string) get_post_meta( $post_id, '_odw_format', true ) ) + && array() !== ODW_Fields::get_format_meta( $ext ) + ) { + update_post_meta( $post_id, '_odw_format', $ext ); } } else { delete_post_meta( $post_id, '_odw_file_size' ); diff --git a/includes/class-fields.php b/includes/class-fields.php index d76c038..9d7776d 100644 --- a/includes/class-fields.php +++ b/includes/class-fields.php @@ -126,11 +126,10 @@ private static function register_required_fields(): void { ->add_tab( __( '3 — Datenbereitstellung', 'open-data-wizard' ), array( - Field::make( 'text', 'odw_access_url', __( 'Wo kann ich die Datei herunterladen?', 'open-data-wizard' ) ) - ->set_required( true ) + Field::make( 'text', 'odw_access_url', __( 'Ergänzen Sie den Link zu Ihrem Datensatz oder laden Sie die Datei in die Mediathek hoch', 'open-data-wizard' ) ) ->set_attribute( 'placeholder', 'https://beispiel.de/daten/datei.csv' ) ->set_attribute( 'type', 'url' ) - ->set_help_text( __( 'ZUGRIFFS-URL (dcat:accessURL)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: https://beispiel.de/daten/datei.csv', 'open-data-wizard' ) ), + ->set_help_text( __( 'ZUGRIFFS-URL (dcat:accessURL)', 'open-data-wizard' ) . "\n\n" . __( 'Zwei Wege — einer genügt: Tragen Sie hier den Link zu Ihrer Datei ein ODER laden Sie die Datei in der Box „Download-Datei (Mediathek)" hoch. Bei einem Upload wird die URL beim Speichern automatisch übernommen; dieses Feld können Sie dann leer lassen.', 'open-data-wizard' ) ), Field::make( 'select', 'odw_format', __( 'In welchem Format ist die Datei?', 'open-data-wizard' ) ) ->add_options( self::get_format_options() ) @@ -416,6 +415,16 @@ private static function register_required_fields(): void { array( Field::make( 'html', 'odw_preview_html' ) ->set_html( self::get_preview_html() ), + + Field::make( 'html', 'odw_datenatlas_publish' ) + ->set_html( + '
' + . '

' . esc_html__( 'Auf dem Datenatlas Zivilgesellschaft veröffentlichen', 'open-data-wizard' ) . '

' + . '

' . esc_html__( 'Optional: Machen Sie diesen Datensatz zusätzlich auf dem Datenatlas Zivilgesellschaft auffindbar.', 'open-data-wizard' ) . '

' + . '' + . esc_html__( 'Zum Datenatlas Zivilgesellschaft', 'open-data-wizard' ) . ' ↗' + . '
' + ), ) ); } diff --git a/includes/class-validation.php b/includes/class-validation.php index ed1ab3d..1fd217b 100644 --- a/includes/class-validation.php +++ b/includes/class-validation.php @@ -162,7 +162,21 @@ private static function has_valid_distribution( int $post_id, array $cf_input ): // Fall back to existing meta. $access_url = (string) carbon_get_post_meta( $post_id, 'odw_access_url' ); - return ! empty( $access_url ) && self::is_valid_url( $access_url ); + if ( ! empty( $access_url ) && self::is_valid_url( $access_url ) ) { + return true; + } + + // A media-library upload also counts as a valid distribution — its access + // URL is derived from the file on save (see ODW_Admin::save_file_attachment). + $file_id = (int) get_post_meta( $post_id, '_odw_file_id', true ); + if ( 0 === $file_id + && isset( $_POST['_odw_file_id'], $_POST['odw_file_upload_nonce'] ) + && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['odw_file_upload_nonce'] ) ), 'odw_save_file_attachment' ) + ) { + $file_id = absint( wp_unslash( $_POST['_odw_file_id'] ) ); + } + + return $file_id > 0; } /** diff --git a/open-data-wizard.php b/open-data-wizard.php index abfa7ee..9c39374 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.16.0 + * Version: 2.17.0 * Requires at least: 6.4 * Requires PHP: 8.1 * Author: nozilla @@ -26,7 +26,7 @@ exit; } -define( 'ODW_VERSION', '2.16.0' ); +define( 'ODW_VERSION', '2.17.0' ); define( 'ODW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'ODW_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); define( 'ODW_PLUGIN_FILE', __FILE__ );