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
155 changes: 126 additions & 29 deletions includes/class-fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' ) )
Expand Down Expand Up @@ -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' ) ),
Expand All @@ -153,6 +151,10 @@ private static function register_required_fields(): void {
Field::make( 'html', 'odw_ext_hint_coverage' )
->set_html( '<h4 style="margin:16px 0 4px">' . esc_html__( 'Abdeckung', 'open-data-wizard' ) . '</h4>' ),

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' ) ),
Expand Down Expand Up @@ -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<string, string> 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 );
Expand Down Expand Up @@ -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<string, string>
*/
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<string, string>
*/
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.
*
Expand Down Expand Up @@ -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',
Expand All @@ -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 ) ) {
Expand All @@ -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 ) ) {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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',
Expand Down
28 changes: 20 additions & 8 deletions includes/class-rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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#',
);

/**
Expand Down Expand Up @@ -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',
Expand All @@ -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(
Expand Down
47 changes: 42 additions & 5 deletions includes/class-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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' ) );
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -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 ---
Expand Down Expand Up @@ -196,6 +199,21 @@ class="regular-text"
<?php
}

/**
* Renders the catalog description settings field.
*/
public static function field_catalog_description(): void {
$value = self::get( 'catalog_description' );
?>
<textarea
name="<?php echo esc_attr( self::OPTION_KEY . '[catalog_description]' ); ?>"
rows="3"
class="large-text"
><?php echo esc_textarea( $value ); ?></textarea>
<p class="description"><?php esc_html_e( 'Kurze Beschreibung des Katalogs — erscheint als dct:description in der REST-API-Antwort (Pflichtfeld gemäß DCAT-AP.de).', 'open-data-wizard' ); ?></p>
<?php
}

/**
* Renders the default publisher settings field.
*/
Expand Down Expand Up @@ -236,10 +254,9 @@ public static function field_default_license(): void {
*/
public static function field_default_language(): void {
$current = self::get( 'default_language' );
$options = array(
'' => __( '— 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 )
);
?>
<select name="<?php echo esc_attr( self::OPTION_KEY . '[default_language]' ); ?>">
Expand Down Expand Up @@ -306,11 +323,19 @@ public static function sanitize( array $input ): array {

$output = array();
$output['catalog_title'] = sanitize_text_field( $input['catalog_title'] ?? '' );
$output['catalog_description'] = sanitize_textarea_field( $input['catalog_description'] ?? '' );
$output['default_publisher'] = sanitize_text_field( $input['default_publisher'] ?? '' );
$output['default_license'] = sanitize_text_field( $input['default_license'] ?? '' );
$output['default_language'] = sanitize_text_field( $input['default_language'] ?? '' );
$output['delete_on_uninstall'] = ! empty( $input['delete_on_uninstall'] ) ? '1' : '0';

// Migrate legacy ISO language codes to EU language URIs.
$lang_raw = sanitize_text_field( $input['default_language'] ?? '' );
$lang_map = array(
'de' => 'http://publications.europa.eu/resource/authority/language/DEU',
'en' => 'http://publications.europa.eu/resource/authority/language/ENG',
);
$output['default_language'] = $lang_map[ $lang_raw ] ?? $lang_raw;

$ttl = (int) ( $input['cache_ttl'] ?? $defaults['cache_ttl'] );
$output['cache_ttl'] = max( 60, min( 86400, $ttl ) );

Expand Down Expand Up @@ -376,6 +401,17 @@ public static function filter_catalog_title( string $odw_default ): string {
return '' !== $custom ? $custom : $odw_default;
}

/**
* Filter callback for `odw_catalog_description`: returns stored catalog description.
*
* @param string $fallback Fallback empty string provided by the caller.
* @return string Catalog description when set, otherwise fallback.
*/
public static function filter_catalog_description( string $fallback ): string {
$stored = trim( (string) self::get( 'catalog_description' ) );
return '' !== $stored ? $stored : $fallback;
}

// -------------------------------------------------------------------------
// Datenzugriff
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -405,6 +441,7 @@ public static function get( string $key = '' ) {
private static function get_defaults(): array {
return array(
'catalog_title' => '',
'catalog_description' => '',
'default_publisher' => '',
'default_license' => '',
'default_language' => '',
Expand Down
Loading
Loading