Skip to content

Commit 81d5f5c

Browse files
authored
Merge pull request #68 from daimpad/claude/fix-data-prep-errors-kJYpl
feat: Add dcatde:geocodingDescription + collapsible Tab-4 subsections (v2.26.0)
2 parents 99c4662 + 0906af8 commit 81d5f5c

12 files changed

Lines changed: 195 additions & 29 deletions

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
77

88
---
99

10+
## [2.26.0] — 2026-07-30
11+
12+
### ✨ Added — DCAT-AP.de-Feld & Profi-UX
13+
- **Neues Feld `dcatde:geocodingDescription`** („Wie lässt sich der räumliche Bezug in Worten
14+
beschreiben?", Tab „Erweiterte Angaben" → Abdeckung): textuelle Ergänzung zu `dct:spatial` /
15+
`dcatde:politicalGeocodingURI`. Im JSON-LD als sprachgetaggtes Literal, in Feld-Referenz und
16+
Frontend-Widget-Accordion enthalten.
17+
- **Profi-UX v2:** Der große Bereich „Erweiterte Angaben" (Tab 4) ist jetzt in **einzeln aufklappbare
18+
Untergruppen** gegliedert (Verantwortlichkeiten · Zugriff · HVD · Weitere DCAT-AP-Felder ·
19+
Distribution) statt eines einzigen großen Reveals. Standardmäßig eingeklappt; Zustand je Gruppe in
20+
`sessionStorage`. Progressive Enhancement: ohne JS bleiben alle Felder sichtbar.
21+
22+
### 🌍 i18n / ✅ Tests
23+
- 7 neue UI-Strings in `en_US`; JSON-LD-Test für `dcatde:geocodingDescription` (167 Tests). Feld-Referenz
24+
neu generiert (53 Felder).
25+
26+
---
27+
1028
## [2.25.0] — 2026-07-30
1129

1230
### 📚 Added — Offizielle SHACL-Shapes gebündelt

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.1-8892BF?style=flat-square&logo=php&logoColor=white)
88
![WordPress](https://img.shields.io/badge/WordPress-compatible-21759B?style=flat-square&logo=wordpress&logoColor=white)
99
![DCAT-AP](https://img.shields.io/badge/DCAT--AP-3.0-brightgreen?style=flat-square)
10-
![Version](https://img.shields.io/badge/Version-2.25.0-brightgreen?style=flat-square)
10+
![Version](https://img.shields.io/badge/Version-2.26.0-brightgreen?style=flat-square)
1111
![PRs Welcome](https://img.shields.io/badge/PRs-willkommen-brightgreen?style=flat-square)
1212

1313
📖 [Dokumentation](DOCUMENTATION.md) · 📋 [Feld-Referenz](docs/FELD-REFERENZ.md) · 📐 [Technische Spezifikation](TECHNICAL-SPEC.md) · 📝 [Changelog](CHANGELOG.md) · 🛡️ [Security](SECURITY.md) · ⚖️ [Lizenz](LICENSE)

assets/css/admin.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,38 @@
642642
transform: rotate( 90deg );
643643
}
644644

645+
/* Collapsible subsection headers (Tab 4 „Erweiterte Angaben") */
646+
.odw-section-toggle {
647+
display: flex;
648+
align-items: center;
649+
gap: 6px;
650+
width: 100%;
651+
margin: 12px 0 4px;
652+
padding: 8px 12px;
653+
background: var( --odw-color-bg-light, #f6f7f7 );
654+
border: 1px solid var( --odw-color-border, #c3c4c7 );
655+
border-radius: 4px;
656+
color: var( --odw-color-text, #1d2327 );
657+
font-size: 13px;
658+
font-weight: 600;
659+
text-align: left;
660+
cursor: pointer;
661+
}
662+
663+
.odw-section-toggle:hover {
664+
background: #eef1f3;
665+
color: var( --odw-color-primary, #2271b1 );
666+
}
667+
668+
.odw-section-caret {
669+
color: var( --odw-color-primary, #2271b1 );
670+
transition: transform 0.15s ease;
671+
}
672+
673+
.odw-section-toggle.odw-section-open .odw-section-caret {
674+
transform: rotate( 90deg );
675+
}
676+
645677
/* -------------------------------------------------------------------------
646678
* Help tooltips (ⓘ popup next to field labels)
647679
* ------------------------------------------------------------------------- */

assets/js/odw-admin-fields.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -249,37 +249,49 @@
249249
// toggle into an opt-in section. Progressive enhancement: if JS is absent,
250250
// every field simply stays visible. State persists in sessionStorage.
251251
// -------------------------------------------------------------------------
252-
var PRO_KEY = 'odw_pro_open';
253-
254-
function applyProState( wrapper, btn, open ) {
255-
var node = wrapper.nextElementSibling;
252+
// Fields belonging to a section = every .cf-field after the heading until
253+
// the next section heading (or the end of the tab).
254+
function sectionFields( wrapper ) {
255+
var fields = [];
256+
var node = wrapper.nextElementSibling;
256257
while ( node ) {
257258
if ( node.classList && node.classList.contains( 'cf-field' ) ) {
258-
node.classList.toggle( 'odw-pro-collapsed', ! open );
259+
if ( node.querySelector && node.querySelector( '[data-odw-section-toggle]' ) ) {
260+
break;
261+
}
262+
fields.push( node );
259263
}
260264
node = node.nextElementSibling;
261265
}
266+
return fields;
267+
}
268+
269+
function applySection( btn, fields, open ) {
270+
fields.forEach( function ( f ) {
271+
f.classList.toggle( 'odw-pro-collapsed', ! open );
272+
} );
262273
btn.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
263-
btn.classList.toggle( 'odw-pro-open', open );
274+
btn.classList.toggle( 'odw-section-open', open );
264275
}
265276

266277
function initProSection() {
267-
document.querySelectorAll( '[data-odw-pro-toggle]' ).forEach( function ( btn ) {
278+
document.querySelectorAll( '[data-odw-section-toggle]' ).forEach( function ( btn ) {
268279
var wrapper = btn.closest( '.cf-field' );
269280
if ( ! wrapper || ! wrapper.parentNode ) {
270281
return;
271282
}
272-
var open = sessionStorage.getItem( PRO_KEY ) === '1';
273-
applyProState( wrapper, btn, open );
283+
var key = 'odw_sec_' + ( btn.getAttribute( 'data-odw-section-toggle' ) || '' );
284+
var open = sessionStorage.getItem( key ) === '1'; // Default: collapsed.
285+
applySection( btn, sectionFields( wrapper ), open );
274286

275-
if ( ! btn.getAttribute( 'data-odw-pro-bound' ) ) {
276-
btn.setAttribute( 'data-odw-pro-bound', '1' );
287+
if ( ! btn.getAttribute( 'data-odw-sec-bound' ) ) {
288+
btn.setAttribute( 'data-odw-sec-bound', '1' );
277289
btn.addEventListener( 'click', function () {
278-
var nowOpen = sessionStorage.getItem( PRO_KEY ) !== '1';
290+
var nowOpen = sessionStorage.getItem( key ) !== '1';
279291
try {
280-
sessionStorage.setItem( PRO_KEY, nowOpen ? '1' : '0' );
292+
sessionStorage.setItem( key, nowOpen ? '1' : '0' );
281293
} catch ( e ) {} // eslint-disable-line no-empty
282-
applyProState( wrapper, btn, nowOpen );
294+
applySection( btn, sectionFields( wrapper ), nowOpen );
283295
} );
284296
}
285297
} );

config/field-catalog.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,18 @@
292292
'desc_dcat' => '`dcatde:politicalGeocodingURI` (DCAT-AP.de) referenziert ein konkretes amtliches Gebiet über eine URI (z. B. amtlicher Regionalschlüssel/AGS als URI). Kardinalität 0..n. Ermöglicht die eindeutige, maschinenlesbare Verortung.',
293293
'desc_human' => 'Verlinken Sie das genaue amtliche Gebiet, auf das sich die Daten beziehen — als offizielle Kennung (z. B. Gemeindeschlüssel). Damit ist eindeutig, welche Region gemeint ist. Beispiel: die URI zum amtlichen Gemeindeschlüssel Ihrer Stadt.',
294294
),
295+
array(
296+
'key' => 'geocoding_description',
297+
'meta_key' => '_odw_geocoding_description',
298+
'dcat_prop' => 'dcatde:geocodingDescription',
299+
'tab' => '4 — Erweiterte Angaben',
300+
'tier' => 'optional',
301+
'vocab' => '',
302+
'q_dcat' => 'Wie lautet die `dcatde:geocodingDescription` (textuelle Beschreibung des räumlichen Bezugs)?',
303+
'q_human' => 'Wie lässt sich der räumliche Bezug in Worten beschreiben?',
304+
'desc_dcat' => '`dcatde:geocodingDescription` (DCAT-AP.de) ist eine frei formulierte textuelle Beschreibung der räumlichen Abdeckung (`literal`/`literal-lang`). Kardinalität 0..n. Ergänzt die maschinenlesbaren Angaben `dct:spatial` und `dcatde:politicalGeocodingURI` um eine menschenlesbare Erläuterung.',
305+
'desc_human' => 'Beschreiben Sie den räumlichen Bezug zusätzlich in Worten — etwa wenn ein Gebietsschlüssel allein nicht genau genug ist. Das hilft Nutzenden, die Abdeckung einzuordnen. Beispiel: „Stadtgebiet Musterstadt ohne den Ortsteil X".',
306+
),
295307
array(
296308
'key' => 'spatial',
297309
'meta_key' => '_odw_spatial',

docs/FELD-REFERENZ.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Legende der Stufen-Spalte: **Pflicht** (Veröffentlichung wird ohne dieses Feld
4040
- [Wie oft werden diese Daten aktualisiert?](#wie-oft-werden-diese-daten-aktualisiert)
4141
- [Auf welcher Verwaltungsebene wurden diese Daten erhoben?](#auf-welcher-verwaltungsebene-wurden-diese-daten-erhoben)
4242
- [Auf welches amtliche Gebiet beziehen sich die Daten?](#auf-welches-amtliche-gebiet-beziehen-sich-die-daten)
43+
- [Wie lässt sich der räumliche Bezug in Worten beschreiben?](#wie-lässt-sich-der-räumliche-bezug-in-worten-beschreiben)
4344
- [Welche geografische Region betreffen diese Daten?](#welche-geografische-region-betreffen-diese-daten)
4445
- [Ab wann sind diese Daten gültig?](#ab-wann-sind-diese-daten-gültig)
4546
- [Bis wann sind diese Daten gültig?](#bis-wann-sind-diese-daten-gültig)
@@ -429,6 +430,23 @@ Legende der Stufen-Spalte: **Pflicht** (Veröffentlichung wird ohne dieses Feld
429430

430431
**4 · Verständliche Langbeschreibung:** Verlinken Sie das genaue amtliche Gebiet, auf das sich die Daten beziehen — als offizielle Kennung (z. B. Gemeindeschlüssel). Damit ist eindeutig, welche Region gemeint ist. Beispiel: die URI zum amtlichen Gemeindeschlüssel Ihrer Stadt.
431432

433+
### Wie lässt sich der räumliche Bezug in Worten beschreiben?
434+
435+
| Eigenschaft | Wert |
436+
|---|---|
437+
| DCAT-Property | `dcatde:geocodingDescription` |
438+
| Meta-Key | `_odw_geocoding_description` |
439+
| Stufe | Optional |
440+
| Vokabular ||
441+
442+
**1 · DCAT-AP-Frage:** Wie lautet die `dcatde:geocodingDescription` (textuelle Beschreibung des räumlichen Bezugs)?
443+
444+
**2 · Verständliche Frage:** Wie lässt sich der räumliche Bezug in Worten beschreiben?
445+
446+
**3 · DCAT-AP-Langbeschreibung:** `dcatde:geocodingDescription` (DCAT-AP.de) ist eine frei formulierte textuelle Beschreibung der räumlichen Abdeckung (`literal`/`literal-lang`). Kardinalität 0..n. Ergänzt die maschinenlesbaren Angaben `dct:spatial` und `dcatde:politicalGeocodingURI` um eine menschenlesbare Erläuterung.
447+
448+
**4 · Verständliche Langbeschreibung:** Beschreiben Sie den räumlichen Bezug zusätzlich in Worten — etwa wenn ein Gebietsschlüssel allein nicht genau genug ist. Das hilft Nutzenden, die Abdeckung einzuordnen. Beispiel: „Stadtgebiet Musterstadt ohne den Ortsteil X".
449+
432450
### Welche geografische Region betreffen diese Daten?
433451

434452
| Eigenschaft | Wert |

includes/class-fields.php

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,10 @@ private static function register_required_fields(): void {
308308
->set_attribute( 'placeholder', 'http://dcat-ap.de/def/politicalGeocoding/regionalKey/09' )
309309
->set_help_text( __( 'AMTLICHER GEBIETSSCHLÜSSEL (dcatde:politicalGeocodingURI)', 'open-data-wizard' ) . "\n\n" . __( 'URI des amtlichen Regional-/Gemeindeschlüssels (AGS/ARS). Beispiel: http://dcat-ap.de/def/politicalGeocoding/regionalKey/09', 'open-data-wizard' ) ),
310310

311+
Field::make( 'text', 'odw_geocoding_description', __( 'Wie lässt sich der räumliche Bezug in Worten beschreiben?', 'open-data-wizard' ) )
312+
->set_attribute( 'placeholder', __( 'z.B. Stadtgebiet Musterstadt ohne Ortsteil X', 'open-data-wizard' ) )
313+
->set_help_text( __( 'RÄUMLICHE BESCHREIBUNG (dcatde:geocodingDescription)', 'open-data-wizard' ) . "\n\n" . __( 'Freitextbeschreibung des räumlichen Bezugs, ergänzend zum amtlichen Gebietsschlüssel. Beispiel: Stadtgebiet Musterstadt', 'open-data-wizard' ) ),
314+
311315
Field::make( 'text', 'odw_spatial', __( 'Welche geografische Region betreffen diese Daten?', 'open-data-wizard' ) )
312316
->set_attribute( 'placeholder', __( 'Region aus der Liste wählen oder eintippen…', 'open-data-wizard' ) )
313317
->set_attribute( 'data-odw-autosuggest', 'spatial' )
@@ -350,18 +354,22 @@ private static function register_required_fields(): void {
350354
->set_attribute( 'placeholder', 'https://beispiel.de/kontakt' )
351355
->set_help_text( __( 'Website mit weiteren Kontaktinformationen.', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: https://beispiel.de/kontakt', 'open-data-wizard' ) ),
352356

353-
// Toggle: collapses all following Tab-4 fields into an opt-in
354-
// "advanced/pro" section (progressive enhancement via odw-admin-fields.js).
355-
Field::make( 'html', 'odw_pro_toggle' )
357+
// "Erweiterte Angaben" — the groups below are individually
358+
// collapsible (progressive enhancement via odw-admin-fields.js:
359+
// each odw-section-toggle collapses its fields until the next).
360+
Field::make( 'html', 'odw_ext_hint_pro' )
356361
->set_html(
357-
'<button type="button" class="odw-pro-toggle" data-odw-pro-toggle aria-expanded="false">'
358-
. '<span class="odw-pro-caret" aria-hidden="true">▸</span> '
359-
. esc_html__( 'Erweiterte Angaben (DCAT-AP.de, HVD, Zugriffsrechte) anzeigen', 'open-data-wizard' )
360-
. '</button>'
362+
'<h4 style="margin:20px 0 2px">' . esc_html__( 'Erweiterte Angaben (optional)', 'open-data-wizard' ) . '</h4>'
363+
. '<p class="description" style="margin:0 0 4px">' . esc_html__( 'Für Profis: DCAT-AP.de-, HVD- und weitere Detailfelder. Öffnen Sie gezielt nur die Gruppen, die Sie brauchen.', 'open-data-wizard' ) . '</p>'
361364
),
362365

363366
Field::make( 'html', 'odw_ext_hint_responsibility' )
364-
->set_html( '<h4 style="margin:16px 0 4px">' . esc_html__( 'Verantwortlichkeiten & Herkunft', 'open-data-wizard' ) . '</h4>' ),
367+
->set_html(
368+
'<button type="button" class="odw-section-toggle" data-odw-section-toggle="responsibility" aria-expanded="false">'
369+
. '<span class="odw-section-caret" aria-hidden="true">▸</span> '
370+
. esc_html__( 'Verantwortlichkeiten & Herkunft', 'open-data-wizard' )
371+
. '</button>'
372+
),
365373

366374
Field::make( 'text', 'odw_contributor_id', __( 'Welche Stelle stellt diese Daten im GovData-Verbund bereit?', 'open-data-wizard' ) )
367375
->set_attribute( 'data-odw-vocab', 'contributors' )
@@ -397,7 +405,12 @@ private static function register_required_fields(): void {
397405
->set_help_text( __( 'QUALITÄTSPROZESS (dcatde:qualityProcessURI)', 'open-data-wizard' ) . "\n\n" . __( 'URL zur Beschreibung des Qualitätssicherungs-Prozesses (optional).', 'open-data-wizard' ) ),
398406

399407
Field::make( 'html', 'odw_ext_hint_access' )
400-
->set_html( '<h4 style="margin:16px 0 4px">' . esc_html__( 'Zugriff & weitere Auffindbarkeit', 'open-data-wizard' ) . '</h4>' ),
408+
->set_html(
409+
'<button type="button" class="odw-section-toggle" data-odw-section-toggle="access" aria-expanded="false">'
410+
. '<span class="odw-section-caret" aria-hidden="true">▸</span> '
411+
. esc_html__( 'Zugriff & weitere Auffindbarkeit', 'open-data-wizard' )
412+
. '</button>'
413+
),
401414

402415
Field::make( 'select', 'odw_access_rights', __( 'Wer darf auf diese Daten zugreifen?', 'open-data-wizard' ) )
403416
->add_options( self::get_access_rights_options() )
@@ -409,7 +422,12 @@ private static function register_required_fields(): void {
409422
->set_help_text( __( 'ZUSÄTZLICHES THEMA (dcat:theme)', 'open-data-wizard' ) . "\n\n" . __( 'Optional ein weiteres Thema aus der offiziellen EU-Themenliste (ergänzt die Kategorie aus Tab 1).', 'open-data-wizard' ) ),
410423

411424
Field::make( 'html', 'odw_ext_hint_hvd' )
412-
->set_html( '<h4 style="margin:16px 0 4px">' . esc_html__( 'High-Value-Datensatz (HVD)', 'open-data-wizard' ) . '</h4>' ),
425+
->set_html(
426+
'<button type="button" class="odw-section-toggle" data-odw-section-toggle="hvd" aria-expanded="false">'
427+
. '<span class="odw-section-caret" aria-hidden="true">▸</span> '
428+
. esc_html__( 'High-Value-Datensatz (HVD)', 'open-data-wizard' )
429+
. '</button>'
430+
),
413431

414432
Field::make( 'select', 'odw_is_hvd', __( 'Ist dies ein hochwertiger Datensatz (HVD)?', 'open-data-wizard' ) )
415433
->add_options( self::get_hvd_flag_options() )
@@ -429,7 +447,12 @@ private static function register_required_fields(): void {
429447
),
430448

431449
Field::make( 'html', 'odw_ext_hint_more' )
432-
->set_html( '<h4 style="margin:16px 0 4px">' . esc_html__( 'Weitere DCAT-AP-Felder (optional)', 'open-data-wizard' ) . '</h4>' ),
450+
->set_html(
451+
'<button type="button" class="odw-section-toggle" data-odw-section-toggle="more" aria-expanded="false">'
452+
. '<span class="odw-section-caret" aria-hidden="true">▸</span> '
453+
. esc_html__( 'Weitere DCAT-AP-Felder (optional)', 'open-data-wizard' )
454+
. '</button>'
455+
),
433456

434457
Field::make( 'text', 'odw_identifier', __( 'Welche eindeutige Kennung hat dieser Datensatz?', 'open-data-wizard' ) )
435458
->set_attribute( 'placeholder', __( 'z. B. DOI oder interne ID', 'open-data-wizard' ) )
@@ -474,7 +497,12 @@ private static function register_required_fields(): void {
474497
->set_help_text( __( 'HERKUNFT (dct:provenance)', 'open-data-wizard' ) . "\n\n" . __( 'Freitext zur Entstehung und Herkunft der Daten.', 'open-data-wizard' ) ),
475498

476499
Field::make( 'html', 'odw_ext_hint_dist' )
477-
->set_html( '<h4 style="margin:16px 0 4px">' . esc_html__( 'Distribution — erweitert', 'open-data-wizard' ) . '</h4>' ),
500+
->set_html(
501+
'<button type="button" class="odw-section-toggle" data-odw-section-toggle="dist" aria-expanded="false">'
502+
. '<span class="odw-section-caret" aria-hidden="true">▸</span> '
503+
. esc_html__( 'Distribution — erweitert', 'open-data-wizard' )
504+
. '</button>'
505+
),
478506

479507
Field::make( 'text', 'odw_dist_title', __( 'Wie heißt die bereitgestellte Datei/Distribution?', 'open-data-wizard' ) )
480508
->set_help_text( __( 'TITEL DER DISTRIBUTION (dct:title)', 'open-data-wizard' ) . "\n\n" . __( 'Kurzer Titel der Distribution. Beispiel: Gesamtdaten als CSV', 'open-data-wizard' ) ),
@@ -1512,6 +1540,7 @@ function odw_build_dataset_jsonld( int $post_id ): ?array {
15121540
$accrual_periodicity = (string) carbon_get_post_meta( $post_id, 'odw_accrual_periodicity' );
15131541
$political_geocoding_level = (string) carbon_get_post_meta( $post_id, 'odw_political_geocoding_level' );
15141542
$political_geocoding_uri = (string) carbon_get_post_meta( $post_id, 'odw_political_geocoding_uri' );
1543+
$geocoding_description = (string) carbon_get_post_meta( $post_id, 'odw_geocoding_description' );
15151544
$spatial = (string) carbon_get_post_meta( $post_id, 'odw_spatial' );
15161545
$temporal_start = (string) carbon_get_post_meta( $post_id, 'odw_temporal_start' );
15171546
$temporal_end = (string) carbon_get_post_meta( $post_id, 'odw_temporal_end' );
@@ -1744,6 +1773,10 @@ function odw_build_dataset_jsonld( int $post_id ): ?array {
17441773
$dataset['dcatde:politicalGeocodingURI'] = array( '@id' => odw_sanitize_jsonld_id( (string) $political_geocoding_uri ) );
17451774
}
17461775

1776+
if ( '' !== trim( (string) $geocoding_description ) ) {
1777+
$dataset['dcatde:geocodingDescription'] = odw_lang_literal( (string) $geocoding_description, $lang_tag );
1778+
}
1779+
17471780
if ( ! empty( $spatial ) ) {
17481781
$location = array(
17491782
'@type' => 'dct:Location',

includes/class-shortcode.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ private static function collect_metadata_rows( int $post_id, string $license_lab
285285
$add( __( 'Zugriffsrechte', 'open-data-wizard' ), self::meta( $post_id, 'odw_access_rights' ), true );
286286

287287
$add( __( 'Räumliche Abdeckung', 'open-data-wizard' ), self::meta( $post_id, 'odw_spatial' ) );
288+
$add( __( 'Räumliche Beschreibung', 'open-data-wizard' ), self::meta( $post_id, 'odw_geocoding_description' ) );
288289

289290
$t_start = self::meta( $post_id, 'odw_temporal_start' );
290291
$t_end = self::meta( $post_id, 'odw_temporal_end' );
961 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)