From eb88f832f9725acd4d9ebf3f49218e0dfa1d3694 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Apr 2026 13:17:33 +0000 Subject: [PATCH 1/2] v1.4.0: Shortcode [odw_dataset] mit Download-Card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neuer Shortcode gibt eine strukturierte Download-Card im Frontend aus; neue Shortcode-Spalte in der Admin-Übersicht für schnellen Copy-Paste. - class-shortcode.php: Shortcode-Handler; liest _odw_theme, _odw_license, _odw_quality_level/score, _odw_file_id; ermittelt Dateigröße und Format automatisch via get_attached_file(); CSS wird nur geladen wenn der Shortcode tatsächlich auf der Seite verwendet wird - assets/css/frontend.css: strukturelles Layout (Flexbox/Grid) ohne feste Farben oder Schriften — erbt vollständig vom aktiven Theme - class-fields.php: File-Feld odw_file_id (→ _odw_file_id) in Tab 3 für Mediathek-Verknüpfung der Download-Datei - class-admin.php: odw_shortcode-Spalte mit readonly-Input (Klick = Select) - admin.css: .column-odw_shortcode Breite + .odw-shortcode-input Monospace-Style https://claude.ai/code/session_013ma6QYffgnE2eKgDfh1Qgn --- CHANGELOG.md | 10 +++ assets/css/admin.css | 16 ++++ assets/css/frontend.css | 133 ++++++++++++++++++++++++++++ includes/class-admin.php | 12 ++- includes/class-fields.php | 3 + includes/class-shortcode.php | 162 +++++++++++++++++++++++++++++++++++ open-data-wizard.php | 6 +- 7 files changed, 339 insertions(+), 3 deletions(-) create mode 100644 assets/css/frontend.css create mode 100644 includes/class-shortcode.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a23002..d8fec00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ Versionierung folgt [Semantic Versioning](https://semver.org/). --- +## [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 + +--- + ## [1.3.0] — 2026-04-21 ### Hinzugefügt diff --git a/assets/css/admin.css b/assets/css/admin.css index db269fd..60907b0 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -150,6 +150,22 @@ width: 130px; } +.column-odw_shortcode { + width: 210px; +} + +.odw-shortcode-input { + width: 100%; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; + font-size: 11px; + cursor: text; + background: var(--odw-color-bg-light); + border: 1px solid var(--odw-color-border); + border-radius: var(--odw-radius); + padding: 3px 6px; + color: var(--odw-color-text); +} + /* Status badges */ .odw-status-badge { display: inline-block; diff --git a/assets/css/frontend.css b/assets/css/frontend.css new file mode 100644 index 0000000..eee119f --- /dev/null +++ b/assets/css/frontend.css @@ -0,0 +1,133 @@ +/** + * Open Data Wizard — Frontend Styles (Download-Card) + * + * Nur strukturelle Styles (Abstände, Layout, Rahmen). + * Farben und Schriften werden vom aktiven Theme geerbt. + */ + +/* ========================================================================= + Download-Card + ========================================================================= */ + +.odw-download-card { + border: 1px solid currentColor; + border-radius: 4px; + padding: 1.25rem; + margin: 1.5rem 0; + opacity: 0.99; /* Stacking context für border-color inherit */ +} + +/* Header: Titel + Thema-Badge nebeneinander */ +.odw-download-card__header { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + justify-content: space-between; + gap: 0.5rem 0.75rem; + margin-bottom: 0.875rem; +} + +.odw-download-card__title { + margin: 0; + font-size: 1.1em; + font-weight: 600; + line-height: 1.3; + flex: 1 1 auto; +} + +.odw-download-card__theme { + flex-shrink: 0; + display: inline-block; + padding: 0.2em 0.65em; + border: 1px solid currentColor; + border-radius: 99px; + font-size: 0.8em; + opacity: 0.65; + white-space: nowrap; +} + +/* Metadaten-Liste */ +.odw-download-card__meta { + display: grid; + grid-template-columns: max-content 1fr; + gap: 0.3rem 1rem; + margin: 0 0 1rem; + padding: 0; + font-size: 0.9em; +} + +.odw-download-card__meta-row { + display: contents; +} + +.odw-download-card__meta dt { + font-weight: 500; + opacity: 0.65; +} + +.odw-download-card__meta dd { + margin: 0; +} + +/* Footer: Download-Button + Datei-Info */ +.odw-download-card__footer { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.625rem; + padding-top: 0.875rem; + border-top: 1px solid currentColor; + opacity: inherit; +} + +.odw-download-card__button { + display: inline-block; + padding: 0.45em 1.1em; + border: 1px solid currentColor; + border-radius: 3px; + font-weight: 600; + font-size: 0.9em; + text-decoration: none; + line-height: 1.4; +} + +.odw-download-card__button:hover { + opacity: 0.8; +} + +.odw-download-card__file-info { + font-size: 0.85em; + opacity: 0.65; +} + +/* ========================================================================= + Responsive + ========================================================================= */ + +@media screen and (max-width: 480px) { + .odw-download-card__header { + flex-direction: column; + } + + .odw-download-card__meta { + grid-template-columns: 1fr; + gap: 0; + } + + .odw-download-card__meta-row { + display: block; + margin-bottom: 0.4rem; + } + + .odw-download-card__meta dt { + display: inline; + } + + .odw-download-card__meta dt::after { + content: ': '; + } + + .odw-download-card__meta dd { + display: inline; + } +} diff --git a/includes/class-admin.php b/includes/class-admin.php index 1771a3d..a495e2a 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -36,9 +36,10 @@ public static function set_columns( array $columns ): array { $new_columns['title'] = __( 'Titel', 'open-data-wizard' ); $new_columns['odw_license'] = __( 'Lizenz', 'open-data-wizard' ); $new_columns['odw_theme'] = __( 'Thema', 'open-data-wizard' ); - $new_columns['odw_quality'] = __( 'Qualität', 'open-data-wizard' ); + $new_columns['odw_quality'] = __( 'Qualität', 'open-data-wizard' ); $new_columns['odw_status'] = __( 'Status', 'open-data-wizard' ); $new_columns['odw_modified'] = __( 'Änderungsdatum', 'open-data-wizard' ); + $new_columns['odw_shortcode'] = __( 'Shortcode', 'open-data-wizard' ); return $new_columns; } @@ -92,6 +93,15 @@ public static function render_column( string $column, int $post_id ): void { $modified = get_post_meta( $post_id, '_odw_modified', true ); echo esc_html( $modified ?: '—' ); break; + + case 'odw_shortcode': + $shortcode = '[odw_dataset id="' . $post_id . '"]'; + printf( + '', + esc_attr( $shortcode ), + esc_attr__( 'Klicken zum Markieren', 'open-data-wizard' ) + ); + break; } } diff --git a/includes/class-fields.php b/includes/class-fields.php index 68de4f8..5d455c7 100644 --- a/includes/class-fields.php +++ b/includes/class-fields.php @@ -99,6 +99,9 @@ private static function register_required_fields(): void { ->set_attribute( 'type', 'number' ) ->set_attribute( 'min', '0' ), ] ), + + Field::make( 'file', 'odw_file_id', __( 'Download-Datei (Mediathek)', 'open-data-wizard' ) ) + ->set_help_text( __( 'Datei aus der WordPress-Mediathek verknüpfen. Dateigröße und Format werden automatisch aus der Datei ermittelt und vom [odw_dataset]-Shortcode als Download-Button verwendet.', 'open-data-wizard' ) ), ] ) ->add_tab( diff --git a/includes/class-shortcode.php b/includes/class-shortcode.php new file mode 100644 index 0000000..f8e90d3 --- /dev/null +++ b/includes/class-shortcode.php @@ -0,0 +1,162 @@ +|string $atts + */ + public static function render( $atts ): string { + $atts = shortcode_atts( [ 'id' => '0' ], $atts, 'odw_dataset' ); + $post_id = absint( $atts['id'] ); + + if ( ! $post_id ) { + return ''; + } + + $post = get_post( $post_id ); + + if ( ! $post || 'odw_dataset' !== $post->post_type || 'publish' !== $post->post_status ) { + return ''; + } + + wp_enqueue_style( 'odw-frontend' ); + + // --- Metadaten --- + $title = get_the_title( $post ); + $theme = (string) get_post_meta( $post_id, '_odw_theme', true ); + $license_uri = (string) get_post_meta( $post_id, '_odw_license', true ); + $license_label = ODW_Fields::get_license_label( $license_uri ); + $quality_level = (string) get_post_meta( $post_id, '_odw_quality_level', true ); + $quality_score = (int) get_post_meta( $post_id, '_odw_quality_score', true ); + $file_id = (int) get_post_meta( $post_id, '_odw_file_id', true ); + + // --- Datei-Informationen aus der Mediathek --- + $file_url = ''; + $file_size = ''; + $file_format = ''; + + if ( $file_id > 0 ) { + $url = wp_get_attachment_url( $file_id ); + if ( $url ) { + $file_url = $url; + $file_format = strtoupper( (string) pathinfo( $url, PATHINFO_EXTENSION ) ); + $file_path = get_attached_file( $file_id ); + if ( $file_path && is_readable( $file_path ) ) { + $file_size = self::format_bytes( (int) filesize( $file_path ) ); + } + } + } + + // --- Qualitätslabel --- + $quality_label = ''; + if ( '' !== $quality_level ) { + $quality_label = ODW_Quality::get_level_label( $quality_level ); + if ( $quality_score > 0 ) { + $quality_label .= ' (' . $quality_score . '/100)'; + } + } + + // --- HTML aufbauen --- + ob_start(); + ?> +
+ +
+

+ + + +
+ + +
+ + +
+
+
+
+ + + +
+
+
+
+ + +
+ + + + + + +
+ = 1_073_741_824 ) { + return round( $bytes / 1_073_741_824, 1 ) . ' GB'; + } + if ( $bytes >= 1_048_576 ) { + return round( $bytes / 1_048_576, 1 ) . ' MB'; + } + if ( $bytes >= 1_024 ) { + return round( $bytes / 1_024, 1 ) . ' KB'; + } + return $bytes . ' B'; + } +} diff --git a/open-data-wizard.php b/open-data-wizard.php index 5f12841..9106e06 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 zivilgesellschaftliche Organisationen. Bereitstellung als maschinenlesbarer Endpoint für Civora/Piveau-Harvesting. - * Version: 1.3.0 + * Version: 1.4.0 * Requires at least: 6.4 * Requires PHP: 8.1 * Author: Datenatlas Zivilgesellschaft @@ -19,7 +19,7 @@ exit; } -define( 'ODW_VERSION', '1.3.0' ); +define( 'ODW_VERSION', '1.4.0' ); define( 'ODW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'ODW_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); define( 'ODW_PLUGIN_FILE', __FILE__ ); @@ -110,6 +110,7 @@ function odw_bootstrap(): void { require_once ODW_PLUGIN_DIR . 'includes/class-validation.php'; require_once ODW_PLUGIN_DIR . 'includes/class-quality.php'; require_once ODW_PLUGIN_DIR . 'includes/class-admin.php'; + require_once ODW_PLUGIN_DIR . 'includes/class-shortcode.php'; ODW_Post_Types::init(); ODW_Fields::init(); @@ -117,6 +118,7 @@ function odw_bootstrap(): void { ODW_Validation::init(); ODW_Quality::init(); ODW_Admin::init(); + ODW_Shortcode::init(); } add_action( 'after_setup_theme', 'odw_bootstrap' ); From d73adebe0fd60d1a5269f393402a78919c910110 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Apr 2026 13:29:31 +0000 Subject: [PATCH 2/2] v1.5.0: Demo-Datensatz und Willkommens-Notice bei Installation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim ersten Admin-Aufruf nach der Aktivierung wird automatisch ein vollständig befüllter Demo-Datensatz erstellt und eine einmalige Willkommens-Notice mit fertigem Shortcode angezeigt. - class-setup.php: on_activation() setzt nur eine Option (kein CF-Zugriff); maybe_create_demo() läuft auf admin_init wenn CF vollständig initialisiert ist; erstellt odw_dataset mit allen Meta-Feldern, CF-Distribution und berechnetem Qualitätsscore; importiert Beispiel-CSV in die Mediathek; handle_dismiss() löscht Option nach Nonce-Check + Redirect; render_welcome_notice() zeigt Shortcode-Input (click-to-select) + Links - assets/sample/beispiel-datensatz.csv: 8 fiktive zivilgesellschaftliche Organisationen als Demo-Download - open-data-wizard.php: class-setup.php früh laden (vor Bootstrap); ODW_Setup::on_activation() in odw_activate(); ODW_Setup::init() im Bootstrap https://claude.ai/code/session_013ma6QYffgnE2eKgDfh1Qgn --- CHANGELOG.md | 9 ++ assets/sample/beispiel-datensatz.csv | 9 ++ includes/class-setup.php | 231 +++++++++++++++++++++++++++ open-data-wizard.php | 10 +- 4 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 assets/sample/beispiel-datensatz.csv create mode 100644 includes/class-setup.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d8fec00..8c0295d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ Versionierung folgt [Semantic Versioning](https://semver.org/). --- +## [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 + +--- + ## [1.4.0] — 2026-04-21 ### Hinzugefügt diff --git a/assets/sample/beispiel-datensatz.csv b/assets/sample/beispiel-datensatz.csv new file mode 100644 index 0000000..26be6e2 --- /dev/null +++ b/assets/sample/beispiel-datensatz.csv @@ -0,0 +1,9 @@ +Name,Kategorie,Stadt,Schwerpunkt,Aktiv_seit,Mitglieder +Bürgerinitiative Grünstadt e.V.,Umweltschutz,Beispielstadt,Lokaler Umwelt- und Klimaschutz,2015,120 +Nachbarschaftshilfe Nord e.V.,Soziale Hilfe,Musterort,Alltagshilfe und Begegnung,2018,45 +Kulturverein Bunt,Kultur & Kunst,Testdorf,Kulturvermittlung und Integration,2010,230 +Sportjugend West,Sport,Demostadt,Jugendsport und Prävention,2005,380 +Digitale Bürger e.V.,Digitale Bildung,Netzstadt,Medienkompetenz und Open Data,2020,67 +Tafelrunde Mitte,Soziale Hilfe,Musterhausen,Lebensmittelversorgung Bedürftiger,2003,155 +Förderverein Stadtbibliothek,Bildung,Beispieldorf,Leseförderung und Bildungszugang,2012,89 +Repair-Café Südstadt,Umweltschutz,Demogemeinde,Reparatur und Ressourcenschonung,2019,34 diff --git a/includes/class-setup.php b/includes/class-setup.php new file mode 100644 index 0000000..c168762 --- /dev/null +++ b/includes/class-setup.php @@ -0,0 +1,231 @@ + __( 'Beispiel: Zivilgesellschaftliche Organisationen', 'open-data-wizard' ), + 'post_status' => 'publish', + 'post_type' => 'odw_dataset', + ] ); + + if ( is_wp_error( $post_id ) || ! $post_id ) { + return 0; + } + + // Einfache Felder direkt per update_post_meta setzen — + // Carbon Fields liest dieselben _odw_* Keys über carbon_get_post_meta(). + update_post_meta( $post_id, '_odw_description', __( + 'Dieser Demo-Datensatz enthält eine Beispielliste zivilgesellschaftlicher Organisationen. Er wurde automatisch bei der Plugin-Installation erstellt und kann als Vorlage oder zum Testen des [odw_dataset]-Shortcodes verwendet werden.', + 'open-data-wizard' + ) ); + update_post_meta( $post_id, '_odw_publisher', __( 'Datenatlas Zivilgesellschaft e.V.', 'open-data-wizard' ) ); + update_post_meta( $post_id, '_odw_license', 'https://creativecommons.org/publicdomain/zero/1.0/' ); + update_post_meta( $post_id, '_odw_language', 'de' ); + update_post_meta( $post_id, '_odw_keywords', "Zivilgesellschaft\nEngagement\nOrganisationen\nDemo" ); + update_post_meta( $post_id, '_odw_theme', 'Soziales' ); + update_post_meta( $post_id, '_odw_issued', current_time( 'Y-m-d' ) ); + update_post_meta( $post_id, '_odw_modified', current_time( 'Y-m-d' ) ); + + // Beispiel-CSV importieren und als Mediathek-Eintrag verknüpfen. + $file_id = self::import_sample_file(); + + if ( $file_id ) { + update_post_meta( $post_id, '_odw_file_id', $file_id ); + + // Distribution via Carbon Fields API setzen, damit JSON-LD und + // Qualitätsprüfung korrekte Daten lesen. + if ( function_exists( 'carbon_set_post_meta' ) ) { + $file_url = wp_get_attachment_url( $file_id ); + if ( $file_url ) { + carbon_set_post_meta( $post_id, 'odw_distributions', [ + [ + 'access_url' => $file_url, + 'format' => 'CSV', + 'byte_size' => '', + ], + ] ); + } + } + } + + // Qualitätsscore sofort berechnen und persistieren. + ODW_Quality::store( $post_id, ODW_Quality::calculate( $post_id ) ); + + return $post_id; + } + + /** + * Kopiert die gebündelte Beispiel-CSV in das Upload-Verzeichnis und + * legt einen Mediathek-Eintrag an. + */ + private static function import_sample_file(): int { + $source = ODW_PLUGIN_DIR . 'assets/sample/beispiel-datensatz.csv'; + + if ( ! file_exists( $source ) ) { + return 0; + } + + $upload = wp_upload_dir(); + $dest = trailingslashit( $upload['path'] ) . 'odw-beispiel-datensatz.csv'; + + if ( ! copy( $source, $dest ) ) { + return 0; + } + + $attachment_id = wp_insert_attachment( + [ + 'post_mime_type' => 'text/csv', + 'post_title' => __( 'Open Data Wizard — Demo-Datensatz (CSV)', 'open-data-wizard' ), + 'post_status' => 'inherit', + ], + $dest + ); + + if ( is_wp_error( $attachment_id ) || ! $attachment_id ) { + if ( file_exists( $dest ) ) { + unlink( $dest ); + } + return 0; + } + + return (int) $attachment_id; + } + + // ------------------------------------------------------------------------- + // Willkommens-Notice + // ------------------------------------------------------------------------- + + /** + * Verarbeitet den Dismiss-Link (GET-Parameter + Nonce). + */ + public static function handle_dismiss(): void { + if ( ! isset( $_GET['odw_dismiss_welcome'] ) ) { + return; + } + + $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; + + if ( ! wp_verify_nonce( $nonce, 'odw_dismiss_welcome' ) ) { + wp_die( esc_html__( 'Sicherheitsüberprüfung fehlgeschlagen.', 'open-data-wizard' ) ); + } + + delete_option( self::WELCOME_OPTION ); + + wp_safe_redirect( remove_query_arg( [ 'odw_dismiss_welcome', '_wpnonce' ] ) ); + exit; + } + + /** + * Gibt die einmalige Willkommens-Notice aus. + */ + public static function render_welcome_notice(): void { + if ( ! get_option( self::WELCOME_OPTION ) ) { + return; + } + + $demo_id = (int) get_option( self::DEMO_OPTION ); + $edit_url = $demo_id ? get_edit_post_link( $demo_id ) : null; + $list_url = admin_url( 'edit.php?post_type=odw_dataset' ); + $dismiss_url = wp_nonce_url( add_query_arg( 'odw_dismiss_welcome', '1' ), 'odw_dismiss_welcome' ); + $shortcode = $demo_id ? '[odw_dataset id="' . $demo_id . '"]' : ''; + ?> +
+

+ +

+ + +

+ +

+

+ +

+ + +

+ + + + +   + + + + +   + + + +

+
+