diff --git a/includes/class-fields.php b/includes/class-fields.php
index 3648e52..adb54ff 100644
--- a/includes/class-fields.php
+++ b/includes/class-fields.php
@@ -75,13 +75,7 @@ private static function register_required_fields(): void {
array(
Field::make( 'select', 'odw_language', __( 'In welcher Sprache sind die Daten?', 'open-data-wizard' ) )
->set_default_value( class_exists( 'ODW_Settings' ) ? (string) ODW_Settings::get( 'default_language' ) : '' )
- ->add_options(
- array(
- '' => __( '— Bitte wählen —', 'open-data-wizard' ),
- 'de' => __( 'Deutsch (DE)', 'open-data-wizard' ),
- 'en' => __( 'Englisch (EN)', 'open-data-wizard' ),
- )
- )
+ ->add_options( self::get_language_options() )
->set_help_text( __( 'SPRACHE (dct:language)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: Deutsch, Englisch', 'open-data-wizard' ) ),
Field::make( 'textarea', 'odw_keywords', __( 'Mit welchen Stichworten finde ich diese Daten?', 'open-data-wizard' ) )
@@ -127,6 +121,10 @@ private static function register_required_fields(): void {
->set_attribute( 'type', 'number' )
->set_attribute( 'min', '0' )
->set_help_text( __( 'DATEIGRÖSSE IN BYTES (dcat:byteSize)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: 204800 (ca. 200 KB). Optional.', 'open-data-wizard' ) ),
+
+ Field::make( 'text', 'attribution_text', __( 'Welcher Namensnennungstext soll bei Weiternutzung angegeben werden?', 'open-data-wizard' ) )
+ ->set_attribute( 'placeholder', __( 'optional – nur bei CC BY oder CC BY-SA', 'open-data-wizard' ) )
+ ->set_help_text( __( 'NAMENSNENNUNGSTEXT (dcatde:licenseAttributionByText)', 'open-data-wizard' ) . "\n\n" . __( 'Empfohlen bei CC BY und CC BY-SA Lizenzen. Beispiel: Datensatz von Musterorganisation e.V., bereitgestellt unter CC BY 4.0', 'open-data-wizard' ) ),
)
)
->set_help_text( __( 'DISTRIBUTIONEN (dcat:distribution)', 'open-data-wizard' ) . "\n\n" . __( 'Sie können mehrere Dateiformate (z.B. CSV und JSON) als separate Distributionen anbieten.', 'open-data-wizard' ) ),
@@ -153,6 +151,10 @@ private static function register_required_fields(): void {
Field::make( 'html', 'odw_ext_hint_coverage' )
->set_html( '
' . esc_html__( 'Abdeckung', 'open-data-wizard' ) . '
' ),
+ Field::make( 'select', 'odw_political_geocoding_level', __( 'Auf welcher Verwaltungsebene wurden diese Daten erhoben?', 'open-data-wizard' ) )
+ ->add_options( self::get_political_geocoding_level_options() )
+ ->set_help_text( __( 'VERWALTUNGSEBENE (dcatde:politicalGeocodingLevelURI)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: Gemeinde, Landkreis, Land, Bund', 'open-data-wizard' ) ),
+
Field::make( 'text', 'odw_spatial', __( 'Welche geografische Region betreffen diese Daten?', 'open-data-wizard' ) )
->set_attribute( 'placeholder', __( 'z.B. Deutschland, Berlin oder GeoNames-URI', 'open-data-wizard' ) )
->set_help_text( __( 'GEOGRAPHISCHE ABDECKUNG (dct:spatial)', 'open-data-wizard' ) . "\n\n" . __( 'Freitext oder URI. Beispiel: Deutschland, Berlin, https://sws.geonames.org/2950159/', 'open-data-wizard' ) ),
@@ -293,21 +295,28 @@ public static function get_license_label( string $uri ): string {
}
/**
- * Themen-Vokabular als Label → Label Map für das DCAT-AP `dcat:theme`-Feld.
+ * Themen-Vokabular als EU-Vocabulary-URI → Label Map für das DCAT-AP `dcat:theme`-Feld.
+ * URIs entstammen dem EU Publications Office Data Theme Vocabulary.
*
* @return array Erweiterbar via `add_filter('odw_theme_options', ...)`.
*/
public static function get_theme_options(): array {
+ $base = 'http://publications.europa.eu/resource/authority/data-theme/';
$options = array(
- '' => __( '— Bitte wählen —', 'open-data-wizard' ),
- 'Bildung' => __( 'Bildung', 'open-data-wizard' ),
- 'Gesundheit' => __( 'Gesundheit', 'open-data-wizard' ),
- 'Soziales' => __( 'Soziales', 'open-data-wizard' ),
- 'Umwelt' => __( 'Umwelt', 'open-data-wizard' ),
- 'Wirtschaft' => __( 'Wirtschaft', 'open-data-wizard' ),
- 'Kultur' => __( 'Kultur', 'open-data-wizard' ),
- 'Sport' => __( 'Sport', 'open-data-wizard' ),
- 'Sonstiges' => __( 'Sonstiges', 'open-data-wizard' ),
+ '' => __( '— Bitte wählen —', 'open-data-wizard' ),
+ $base . 'EDUC' => __( 'Bildung, Kultur & Sport', 'open-data-wizard' ),
+ $base . 'HEAL' => __( 'Gesundheit', 'open-data-wizard' ),
+ $base . 'SOCI' => __( 'Bevölkerung & Gesellschaft', 'open-data-wizard' ),
+ $base . 'ENVI' => __( 'Umwelt', 'open-data-wizard' ),
+ $base . 'ECON' => __( 'Wirtschaft & Finanzen', 'open-data-wizard' ),
+ $base . 'GOVE' => __( 'Verwaltung & öffentlicher Sektor', 'open-data-wizard' ),
+ $base . 'TECH' => __( 'Wissenschaft & Technologie', 'open-data-wizard' ),
+ $base . 'TRAN' => __( 'Verkehr', 'open-data-wizard' ),
+ $base . 'AGRI' => __( 'Landwirtschaft & Ernährung', 'open-data-wizard' ),
+ $base . 'ENER' => __( 'Energie', 'open-data-wizard' ),
+ $base . 'JUST' => __( 'Justiz & Sicherheit', 'open-data-wizard' ),
+ $base . 'REGI' => __( 'Regionen & Städte', 'open-data-wizard' ),
+ $base . 'INTR' => __( 'Internationale Themen', 'open-data-wizard' ),
);
return (array) apply_filters( 'odw_theme_options', $options );
@@ -374,6 +383,59 @@ public static function get_format_mime( string $format ): string {
return $map[ $format ] ?? $format;
}
+ /**
+ * Maps a short format label to its EU Publications Office file-type URI.
+ * Used in dct:format for DCAT-AP.de / Civora compliance.
+ *
+ * @param string $format Short format label (e.g. "CSV").
+ * @return string EU file-type URI, or the original string if unknown.
+ */
+ public static function get_format_eu_uri( string $format ): string {
+ $base = 'http://publications.europa.eu/resource/authority/file-type/';
+ $map = array(
+ 'CSV' => $base . 'CSV',
+ 'JSON' => $base . 'JSON',
+ 'XLSX' => $base . 'XLSX',
+ 'PDF' => $base . 'PDF',
+ 'GeoJSON' => $base . 'GEOJSON',
+ 'XML' => $base . 'XML',
+ );
+
+ return $map[ $format ] ?? $format;
+ }
+
+ /**
+ * Language options as EU Publications Office language URI → label map.
+ * Used in dct:language for DCAT-AP.de / Civora compliance.
+ *
+ * @return array
+ */
+ public static function get_language_options(): array {
+ $base = 'http://publications.europa.eu/resource/authority/language/';
+ return array(
+ '' => __( '— Bitte wählen —', 'open-data-wizard' ),
+ $base . 'DEU' => __( 'Deutsch (DE)', 'open-data-wizard' ),
+ $base . 'ENG' => __( 'Englisch (EN)', 'open-data-wizard' ),
+ );
+ }
+
+ /**
+ * Administrative geocoding level options from the DCAT-AP.de political geocoding vocabulary.
+ * Used in dcatde:politicalGeocodingLevelURI for Civora compliance.
+ *
+ * @return array
+ */
+ public static function get_political_geocoding_level_options(): array {
+ $base = 'http://dcat-ap.de/def/politicalGeocoding/Level/';
+ return array(
+ '' => __( '— Bitte wählen —', 'open-data-wizard' ),
+ $base . 'federal' => __( 'Bund (Federal)', 'open-data-wizard' ),
+ $base . 'state' => __( 'Land (Bundesland)', 'open-data-wizard' ),
+ $base . 'administrativeDistrict' => __( 'Landkreis', 'open-data-wizard' ),
+ $base . 'municipality' => __( 'Gemeinde', 'open-data-wizard' ),
+ );
+ }
+
/**
* Generates the HTML for the JSON-LD preview tab.
*
@@ -442,14 +504,15 @@ function odw_build_dataset_jsonld( int $post_id ): ?array {
$distributions = carbon_get_post_meta( $post_id, 'odw_distributions' );
// Extended DCAT-AP fields (Tab 4).
- $landing_page = (string) carbon_get_post_meta( $post_id, 'odw_landing_page' );
- $accrual_periodicity = (string) carbon_get_post_meta( $post_id, 'odw_accrual_periodicity' );
- $spatial = (string) carbon_get_post_meta( $post_id, 'odw_spatial' );
- $temporal_start = (string) carbon_get_post_meta( $post_id, 'odw_temporal_start' );
- $temporal_end = (string) carbon_get_post_meta( $post_id, 'odw_temporal_end' );
- $contact_name = (string) carbon_get_post_meta( $post_id, 'odw_contact_name' );
- $contact_email = (string) carbon_get_post_meta( $post_id, 'odw_contact_email' );
- $contact_url = (string) carbon_get_post_meta( $post_id, 'odw_contact_url' );
+ $landing_page = (string) carbon_get_post_meta( $post_id, 'odw_landing_page' );
+ $accrual_periodicity = (string) carbon_get_post_meta( $post_id, 'odw_accrual_periodicity' );
+ $political_geocoding_level = (string) carbon_get_post_meta( $post_id, 'odw_political_geocoding_level' );
+ $spatial = (string) carbon_get_post_meta( $post_id, 'odw_spatial' );
+ $temporal_start = (string) carbon_get_post_meta( $post_id, 'odw_temporal_start' );
+ $temporal_end = (string) carbon_get_post_meta( $post_id, 'odw_temporal_end' );
+ $contact_name = (string) carbon_get_post_meta( $post_id, 'odw_contact_name' );
+ $contact_email = (string) carbon_get_post_meta( $post_id, 'odw_contact_email' );
+ $contact_url = (string) carbon_get_post_meta( $post_id, 'odw_contact_url' );
$dataset = array(
'@type' => 'dcat:Dataset',
@@ -460,11 +523,21 @@ function odw_build_dataset_jsonld( int $post_id ): ?array {
'@type' => 'foaf:Organization',
'foaf:name' => $publisher,
),
- 'dct:license' => $license,
);
+ if ( ! empty( $license ) ) {
+ $dataset['dct:license'] = array( '@id' => (string) $license );
+ }
+
if ( ! empty( $language ) ) {
- $dataset['dct:language'] = $language;
+ // Normalize legacy ISO codes ('de', 'en') to EU language URIs.
+ $lang_base = 'http://publications.europa.eu/resource/authority/language/';
+ $lang_legacy = array(
+ 'de' => $lang_base . 'DEU',
+ 'en' => $lang_base . 'ENG',
+ );
+ $lang_uri = $lang_legacy[ (string) $language ] ?? (string) $language;
+ $dataset['dct:language'] = array( '@id' => $lang_uri );
}
if ( ! empty( $keywords ) && is_string( $keywords ) ) {
@@ -475,7 +548,19 @@ function odw_build_dataset_jsonld( int $post_id ): ?array {
}
if ( ! empty( $theme ) ) {
- $dataset['dcat:theme'] = $theme;
+ // Normalize legacy text labels to EU data-theme URIs.
+ $theme_base = 'http://publications.europa.eu/resource/authority/data-theme/';
+ $theme_legacy = array(
+ 'Bildung' => $theme_base . 'EDUC',
+ 'Gesundheit' => $theme_base . 'HEAL',
+ 'Soziales' => $theme_base . 'SOCI',
+ 'Umwelt' => $theme_base . 'ENVI',
+ 'Wirtschaft' => $theme_base . 'ECON',
+ 'Kultur' => $theme_base . 'EDUC',
+ 'Sport' => $theme_base . 'EDUC',
+ 'Sonstiges' => $theme_base . 'GOVE',
+ );
+ $dataset['dcat:theme'] = array( '@id' => $theme_legacy[ (string) $theme ] ?? (string) $theme );
}
if ( ! empty( $issued ) ) {
@@ -507,13 +592,21 @@ function odw_build_dataset_jsonld( int $post_id ): ?array {
);
if ( ! empty( $dist['format'] ) ) {
- $dist_item['dct:format'] = ODW_Fields::get_format_mime( $dist['format'] );
+ $dist_item['dct:format'] = array( '@id' => ODW_Fields::get_format_eu_uri( $dist['format'] ) );
}
if ( isset( $dist['byte_size'] ) && '' !== $dist['byte_size'] && is_numeric( $dist['byte_size'] ) && (int) $dist['byte_size'] >= 0 ) {
$dist_item['dcat:byteSize'] = (int) $dist['byte_size'];
}
+ if ( ! empty( $license ) ) {
+ $dist_item['dct:license'] = array( '@id' => (string) $license );
+ }
+
+ if ( ! empty( $dist['attribution_text'] ) ) {
+ $dist_item['dcatde:licenseAttributionByText'] = (string) $dist['attribution_text'];
+ }
+
$dist_list[] = $dist_item;
}
@@ -532,6 +625,10 @@ function odw_build_dataset_jsonld( int $post_id ): ?array {
$dataset['dct:accrualPeriodicity'] = array( '@id' => $accrual_periodicity );
}
+ if ( ! empty( $political_geocoding_level ) ) {
+ $dataset['dcatde:politicalGeocodingLevelURI'] = array( '@id' => $political_geocoding_level );
+ }
+
if ( ! empty( $spatial ) ) {
$dataset['dct:spatial'] = array(
'@type' => 'dct:Location',
diff --git a/includes/class-rest-api.php b/includes/class-rest-api.php
index a36f17d..878979c 100644
--- a/includes/class-rest-api.php
+++ b/includes/class-rest-api.php
@@ -27,16 +27,17 @@ class ODW_Rest_API {
private const CACHE_TTL = 300;
/**
- * DCAT-AP 3.0 JSON-LD @context inkl. Plugin-eigenem odw:-Namespace für Qualitätsdaten.
+ * DCAT-AP 3.0 / DCAT-AP.de JSON-LD @context inkl. Plugin-eigenem odw:-Namespace.
*/
private const JSONLD_CONTEXT = array(
- 'dcat' => 'https://www.w3.org/ns/dcat#',
- 'dct' => 'http://purl.org/dc/terms/',
- 'foaf' => 'http://xmlns.com/foaf/0.1/',
- 'xsd' => 'http://www.w3.org/2001/XMLSchema#',
- 'vcard' => 'http://www.w3.org/2006/vcard/ns#',
- 'skos' => 'http://www.w3.org/2004/02/skos/core#',
- 'odw' => 'https://github.com/daimpad/OpenDataWizard/ns#',
+ 'dcat' => 'https://www.w3.org/ns/dcat#',
+ 'dct' => 'http://purl.org/dc/terms/',
+ 'foaf' => 'http://xmlns.com/foaf/0.1/',
+ 'xsd' => 'http://www.w3.org/2001/XMLSchema#',
+ 'vcard' => 'http://www.w3.org/2006/vcard/ns#',
+ 'skos' => 'http://www.w3.org/2004/02/skos/core#',
+ 'dcatde' => 'http://dcat-ap.de/def/dcatde/',
+ 'odw' => 'https://github.com/daimpad/OpenDataWizard/ns#',
);
/**
@@ -219,6 +220,13 @@ public static function get_catalog( WP_REST_Request $request ): WP_REST_Response
get_bloginfo( 'name' ) . ' — Datenkatalog'
);
+ /**
+ * Filters the catalog description in the JSON-LD output.
+ *
+ * @param string $description The catalog description (empty by default).
+ */
+ $catalog_description = (string) apply_filters( 'odw_catalog_description', '' );
+
$catalog = array(
'@context' => self::JSONLD_CONTEXT,
'@type' => 'dcat:Catalog',
@@ -230,6 +238,10 @@ public static function get_catalog( WP_REST_Request $request ): WP_REST_Response
'dcat:dataset' => $datasets,
);
+ if ( '' !== $catalog_description ) {
+ $catalog['dct:description'] = $catalog_description;
+ }
+
set_transient(
$cache_key,
array(
diff --git a/includes/class-settings.php b/includes/class-settings.php
index eefd439..abee54f 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -7,6 +7,7 @@
*
* Verfügbare Einstellungen:
* catalog_title — Titel des Datenkatalogs (REST API dct:title)
+ * catalog_description — Beschreibung des Datenkatalogs (REST API dct:description)
* default_publisher — Vorausgefüllter Herausgeber für neue Datensätze
* default_license — Vorausgewählte Lizenz für neue Datensätze
* default_language — Vorausgewählte Sprache für neue Datensätze
@@ -39,6 +40,7 @@ public static function init(): void {
add_action( 'admin_init', array( self::class, 'register_settings' ) );
add_action( 'admin_post_odw_recalculate_quality', array( self::class, 'handle_recalculate_quality' ) );
add_filter( 'odw_catalog_title', array( self::class, 'filter_catalog_title' ) );
+ add_filter( 'odw_catalog_description', array( self::class, 'filter_catalog_description' ) );
}
// -------------------------------------------------------------------------
@@ -138,6 +140,7 @@ static function (): void {
);
add_settings_field( 'catalog_title', __( 'Katalog-Titel', 'open-data-wizard' ), array( self::class, 'field_catalog_title' ), 'odw-settings', 'odw_section_catalog' );
+ add_settings_field( 'catalog_description', __( 'Katalog-Beschreibung', 'open-data-wizard' ), array( self::class, 'field_catalog_description' ), 'odw-settings', 'odw_section_catalog' );
add_settings_field( 'default_publisher', __( 'Herausgebende Organisation', 'open-data-wizard' ), array( self::class, 'field_default_publisher' ), 'odw-settings', 'odw_section_catalog' );
// --- Standardwerte ---
@@ -196,6 +199,21 @@ class="regular-text"
+
+
+ __( '— Kein Standard —', 'open-data-wizard' ),
- 'de' => __( 'Deutsch (DE)', 'open-data-wizard' ),
- 'en' => __( 'Englisch (EN)', 'open-data-wizard' ),
+ $options = array_merge(
+ array( '' => __( '— Kein Standard —', 'open-data-wizard' ) ),
+ array_slice( ODW_Fields::get_language_options(), 1 )
);
?>