diff --git a/assets/css/admin.css b/assets/css/admin.css index d00b2df..b77cf34 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -438,3 +438,102 @@ margin-bottom: 4px; } } + +/* ========================================================================= + Qualitätslevel — Perfekt (maps onto high-color) + Ausreichend + ========================================================================= */ + +.odw-quality--perfect .odw-quality-dot, +.odw-quality-bar--perfect { + background-color: var(--odw-color-quality-high-dot); +} + +.odw-quality--perfect { + color: var(--odw-color-quality-high-text); + background-color: var(--odw-color-quality-high-bg); +} + +.odw-quality--sufficient .odw-quality-dot, +.odw-quality-bar--sufficient { + background-color: #b45309; +} + +.odw-quality--sufficient { + color: #6a3800; + background-color: #fef3c7; +} + +/* ========================================================================= + Distribution intro text + ========================================================================= */ + +.odw-distribution-intro { + margin-bottom: 16px; + padding: 12px 16px; + background: var(--odw-color-bg-light); + border-left: 4px solid var(--odw-color-primary); + border-radius: 0 var(--odw-radius) var(--odw-radius) 0; +} + +.odw-distribution-intro h4 { + margin: 0 0 6px; + font-size: 13px; +} + +/* ========================================================================= + Composite file-size widget (Änderung 8) + ========================================================================= */ + +.odw-filesize-widget { + margin: 4px 0 8px; +} + +.odw-filesize-label { + display: block; + font-weight: 600; + margin-bottom: 4px; + font-size: 13px; +} + +.odw-filesize-optional { + font-weight: normal; + color: var(--odw-color-text-muted); + margin-left: 4px; + font-size: 12px; +} + +.odw-filesize-row { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.odw-filesize-number { + width: 120px; + min-width: 80px; +} + +.odw-filesize-unit { + width: 80px; +} + +.odw-filesize-hint { + color: var(--odw-color-text-muted); + font-size: 12px; +} + +.odw-filesize-helptext { + margin-top: 4px; + font-size: 12px; +} + +/* Hide the backing CF byte_size input; JS syncs to it */ +.odw-byte-size-backing { + display: none !important; +} + +/* Also hide the entire CF field wrapper for the backing field */ +.cf-field:has(.odw-byte-size-backing) { + display: none !important; +} diff --git a/assets/js/odw-admin-fields.js b/assets/js/odw-admin-fields.js new file mode 100644 index 0000000..046dc26 --- /dev/null +++ b/assets/js/odw-admin-fields.js @@ -0,0 +1,186 @@ +/* global odwAdminFields */ +/** + * Open Data Wizard — Admin Field Enhancements + * + * Handles: + * 1. Auto-suggest datalist for license_custom (inside distributions) + * 2. Auto-suggest datalist for CESSDA topic classification + * 3. Composite file-size widget (Zahl + Einheit → Bytes in backing field) + */ +( function () { + 'use strict'; + + var data = ( typeof odwAdminFields !== 'undefined' ) ? odwAdminFields : {}; + + // ------------------------------------------------------------------------- + // Utility: set a value on a React-controlled or plain input + // ------------------------------------------------------------------------- + function setInputValue( input, value ) { + if ( ! input ) { + return; + } + var nativeSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, 'value' ); + if ( nativeSetter && nativeSetter.set ) { + nativeSetter.set.call( input, value ); + } else { + input.value = value; + } + input.dispatchEvent( new Event( 'input', { bubbles: true } ) ); + input.dispatchEvent( new Event( 'change', { bubbles: true } ) ); + } + + // ------------------------------------------------------------------------- + // Attach datalist to a single input + // ------------------------------------------------------------------------- + function attachDatalist( input, listId, options ) { + if ( ! input || input.getAttribute( 'list' ) ) { + return; + } + var existing = document.getElementById( listId ); + if ( ! existing ) { + var dl = document.createElement( 'datalist' ); + dl.id = listId; + options.forEach( function ( opt ) { + var el = document.createElement( 'option' ); + el.value = opt.value; + el.label = opt.label; + dl.appendChild( el ); + } ); + document.body.appendChild( dl ); + } + input.setAttribute( 'list', listId ); + } + + // ------------------------------------------------------------------------- + // 1. License auto-suggest (inside distribution complex groups) + // ------------------------------------------------------------------------- + function initLicenseAutosuggest() { + if ( ! data.licenseOptions || ! data.licenseOptions.length ) { + return; + } + document.querySelectorAll( 'input[data-odw-autosuggest="license_custom"]' ).forEach( function ( input ) { + attachDatalist( input, 'odw-license-datalist', data.licenseOptions ); + } ); + } + + // ------------------------------------------------------------------------- + // 2. CESSDA topic auto-suggest (standalone field) + // ------------------------------------------------------------------------- + function initCessdaAutosuggest() { + if ( ! data.cessdaOptions || ! data.cessdaOptions.length ) { + return; + } + document.querySelectorAll( 'input[data-odw-autosuggest="cessda"]' ).forEach( function ( input ) { + attachDatalist( input, 'odw-cessda-datalist', data.cessdaOptions ); + } ); + } + + // ------------------------------------------------------------------------- + // 3. File-size composite widget + // Finds every .odw-filesize-widget and wires up its number+unit inputs + // to write computed bytes into the backing CF field. + // ------------------------------------------------------------------------- + function initFileSizeWidgets() { + document.querySelectorAll( '.odw-filesize-widget' ).forEach( function ( widget ) { + var numberInput = widget.querySelector( '.odw-filesize-number' ); + var unitSelect = widget.querySelector( '.odw-filesize-unit' ); + var hint = widget.querySelector( '.odw-filesize-hint' ); + + // Find the backing CF input (data-odw-backing="byte_size" in same group). + var group = widget.closest( '.carbon-fields-group, .carbon-complex-group, .cf-group, [data-group]' ); + var backing = group + ? group.querySelector( 'input[data-odw-backing="byte_size"]' ) + : null; + + if ( ! numberInput || ! unitSelect ) { + return; + } + + var factors = { KB: 1024, MB: 1048576, GB: 1073741824 }; + + function updateBacking() { + var num = parseFloat( numberInput.value ); + var unit = unitSelect.value; + if ( isNaN( num ) || num < 0 ) { + numberInput.setCustomValidity( 'Bitte einen positiven Wert eingeben.' ); + if ( hint ) { + hint.textContent = ''; + } + return; + } + numberInput.setCustomValidity( '' ); + + var bytes = Math.round( num * ( factors[ unit ] || 1048576 ) ); + + if ( hint ) { + hint.textContent = '= ' + bytes.toLocaleString( 'de-DE' ) + ' Bytes'; + } + + if ( backing ) { + setInputValue( backing, String( bytes ) ); + } + } + + numberInput.addEventListener( 'input', updateBacking ); + numberInput.addEventListener( 'change', updateBacking ); + unitSelect.addEventListener( 'change', updateBacking ); + + // Restore display value from stored bytes on page load. + if ( backing && backing.value && backing.value !== '0' && backing.value !== '' ) { + var stored = parseInt( backing.value, 10 ); + if ( ! isNaN( stored ) && stored > 0 ) { + var displayUnit = 'MB'; + var displayVal; + if ( stored >= 1073741824 ) { + displayUnit = 'GB'; + displayVal = stored / 1073741824; + } else if ( stored >= 1048576 ) { + displayUnit = 'MB'; + displayVal = stored / 1048576; + } else { + displayUnit = 'KB'; + displayVal = stored / 1024; + } + numberInput.value = parseFloat( displayVal.toFixed( 2 ) ); + unitSelect.value = displayUnit; + updateBacking(); + } + } + } ); + } + + // ------------------------------------------------------------------------- + // Observe DOM for dynamically added CF complex groups (e.g. new distribution) + // ------------------------------------------------------------------------- + function observeNewGroups() { + var observer = new MutationObserver( function ( mutations ) { + mutations.forEach( function ( mutation ) { + mutation.addedNodes.forEach( function ( node ) { + if ( node.nodeType !== 1 ) { + return; + } + // Re-run inits for any newly added groups. + node.querySelectorAll( 'input[data-odw-autosuggest="license_custom"]' ).forEach( function ( input ) { + attachDatalist( input, 'odw-license-datalist', data.licenseOptions || [] ); + } ); + node.querySelectorAll( '.odw-filesize-widget' ).forEach( function () { + initFileSizeWidgets(); + } ); + } ); + } ); + } ); + + observer.observe( document.body, { childList: true, subtree: true } ); + } + + // ------------------------------------------------------------------------- + // Boot on DOMContentLoaded + // ------------------------------------------------------------------------- + document.addEventListener( 'DOMContentLoaded', function () { + initLicenseAutosuggest(); + initCessdaAutosuggest(); + initFileSizeWidgets(); + observeNewGroups(); + } ); + +} )(); diff --git a/config/TopicClassification-4.2.3_de-4.2.3.rdf b/config/TopicClassification-4.2.3_de-4.2.3.rdf new file mode 100644 index 0000000..d8596d6 --- /dev/null +++ b/config/TopicClassification-4.2.3_de-4.2.3.rdf @@ -0,0 +1,783 @@ + + + + + + TopicClassification + CESSDA Themenklassifikation + Thematische Klassifikation für Daten + 4.2.3 + + Copyright ©CESSDA 2025 + + + + + + + + + + + + + + + + + + + + + + + + Demography + BEVÖLKERUNGSSTATISTIK + + + + + + + + + Demography.Censuses + Volkszählungen + Befragungen der Gesamtbevölkerung, im Gegensatz zu +Stichprobenbefragungen. In der Regel ist der Zweck eine offizielle +Erhebung der Bevölkerungszahl, üblicherweise bei gleichzeitiger Erhebung + weiterer demographischer Informationen. In manchen Ländern sind +Volkszählungen nicht üblich, da entsprechende demographische +Informationen der offiziellen Statistik bzw. amtlichen Verzeichnissen +entnommen werden können. + + + + + Demography.Migration + Migration + Beinhaltet Immigration, Emigration, Binnenmigration sowie andere Formen geographischer Mobilität. Immigration bedeutet, in einem anderen als dem Heimatland sesshaft zu werden. Emigration bedeutet, das Heimatland zu verlassen, um in einem anderen Land sesshaft zu werden. Der Begriff Migration umfasst beide Sachverhalte. Migration schließt auch Binnenmigration ein, das heißt Mobilität innerhalb eines Landes, zwischen Regionen, Kommunen etc. + + + + + Demography.MorbidityAndMortality + Morbidität und Mortalität + Unter Morbidität wird die Krankheitshäufigkeit innerhalb einer Bevölkerungsgruppe verstanden. Daten können sich auf das Vorkommen oder die Morbiditätsrate einer Krankheit beziehen, zum Beispiel die Anzahl der an Lungenkrebs leidenden Personen in einem Land im Zeitraum eines Jahres. Daten zur Mortalität geben die Anzahl der Todesfälle in einer Bevölkerungsgruppe nach Ort, Zeit, Ursache, Personengruppe o.ä. an. Beispiele sind Müttersterblichkeit und Säuglingssterblichkeit. Verwendungshinweis: Für Mikrodaten zu Krankheiten und Gesundheitsproblemen bitte die entsprechenden Unterklassen der Klasse GESUNDHEIT verwenden. + + + + + Economics + WIRTSCHAFT + + + + + + + + + + + Economics.ConsumptionAndConsumerBehaviour + Konsum und Konsumverhalten + Bezieht sich auf den Vorgang, dass etwas erworben und/oder verwendet wird, um Bedürfnisse und Wünsche einer Person, Gemeinschaft oder Gesellschaft zu erfüllen. Diese Klasse umfasst auch Daten zu (Wandel in) Konsummustern und zu ethischen Aspekten des Konsums. Erfasst ist davon zum Beispiel, wie häufig Personen bestimmte Produkte kaufen, wann und wo sie einkaufen und ob ethische Überlegungen ihre Konsumentscheidungen beeinflussen. + + + + + Economics.EconomicConditionsAndIndicators + Wirtschaftliche Lage und Indikatoren + Wirtschaftliche Indikatoren geben an, in welche Richtung sich eine Volkswirtschaft bewegt. Dazu gehören zum Beispiel Bruttoinlandsprodukt, Arbeitslosenquote, Inflations- und Zinsrate, Konsum- und Investitionsaktivitäten, Verschuldungsrate. Verwendungshinweis: Die Klasse umfasst auch die erhobene oder erfasste wirtschaftliche Lage von Individuen und Haushalten. + + + + + Economics.EconomicPolicyPublicExpenditureAndRevenue + Wirtschaftspolitik, öffentliche Ausgaben und Einnahmen + Daten zu Wirtschaftspolitik, einschließlich Haushalts- und Finanzpolitik, Staatsausgaben und Staatseinnahmen. + + + + + Economics.EconomicSystemsAndDevelopment + Wirtschaftssysteme und wirtschaftliche Entwicklung + Daten zu kapitalistischen, sozialistischen oder anderen volkswirtschaftlichen Systemen sowie Daten, die beispielsweise internationale Entwicklungsprogramme untersuchen. + + + + + Economics.IncomePropertyAndInvestmentSaving + Einkommen, Vermögen und Geldanlagen/Sparen + Daten zu persönlichem Vermögenszuwachs, einschließlich Einkommen, Vermögen, Geldanlagen und Ersparnissen. + + + + + Education + BILDUNG + + + + + + + + + + + Education.CompulsoryAndPreschoolEducation + Vorschulerziehung und Bildung in der Pflichtschulzeit + Bildung in der Pflichtschulzeit bezieht sich auf eine gesetzlich vorgegebene Bildungsphase, die alle in einem Land lebenden Personen absolvieren müssen. Vorschulerziehung bezieht sich auf Betreuungs- und Bildungsangebote vor der Pflichtschulzeit. Verwenden Sie diese Klasse für Daten zu Vorschulzeiten (ISCED-Level 0) und Bildungszeiten in der Primarstufe (ISCED-Level 1) sowie Sekundarstufe I (ISCED-Level 2). Davon erfasst sind Daten zu Schülerinnen und Schülern, Lehrerinnen und Lehrern, Unterrichtsfächern, vermittelte Kompetenzen, Einrichtungen usw. + + + + + Education.EducationalPolicy + Bildungspolitik + In der Bildungspolitik werden in Bezug auf Bildung und Bildungsversorgung Strategien und Regelungen diskutiert, Entscheidungen getroffen sowie Richtlinien auf nationaler, regionaler oder lokaler Ebene umgesetzt. Verwendungshinweis: Für bereits implementierte Gesetze und Regelungen bitte auch die Klasse 'Gesetzgebung und Rechtssysteme' verwenden. + + + + + Education.HigherAndFurtherEducation + Höhere Bildung + Höhere Bildung umfasst Allgemeinbildung im Rahmen der ISCED-Level 3 (Sekundarstufe II) und 4 (postsekundare Bildung) sowie tertiäre Bildung (ISCED-Level 5). Dazu gehören gymnasiale Oberstufen, Abendgymnasien, Kollegs sowie Universitäten, Fachhochschulen und Berufsakademien. Davon erfasst sind Daten zu Schülerinnen und Schülern, Studierenden, Lehrenden, Unterrichts- und Studienfächern, vermittelte Kompetenzen, Einrichtungen etc. Für Berufsausbildung im Rahmen der ISCED-Level 3 und 4 bitte die Klasse 'Berufsausbildung' verwenden. + + + + + Education.LifelongContinuingEducation + Lebenslanges Lernen + Lebenslanges Lernen bezieht sich auf Bildungsangebote unterschliedlicher Art für Erwachsene, die den formalen Bildungsweg bereits absolviert haben. Erwachsene nehmen diese Bildungsangebote zum Beispiel wahr, um ihre Kompentenzen und Qualifikationen zu verbessern, zur beruflichen Neuorientierung (Umschulung) oder einfach um sich mehr Wissen anzueignen. Lebenslanges Lernen bezieht sich nicht zwingend auf Bildungsangebote, die zu einer bestimmten Qualifikation oder einem Abschluss oder zu einer beruflichen Fortbildung führen. + + + + + Education.VocationalEducationAndTraining + Berufsausbildung + Berufsausbildung bezieht sich auf Bildungsprogramme, in denen Lernenden das Wissen, die Fertigkeiten und die Kompetenzen für einen bestimmten Beruf, ein bestimmtes Gewerbe oder eine bestimmte Branche vermittelt werden. Die Klasse umfasst Berufsausbildungen im Rahmen der ISCED-Level 3 und 4, einschließlich Lehrberufe. Dazu gehört sowohl die Ausbildung an Berufsfachschulen, als auch die duale Ausbildung an Berufsschulen und in Betrieben. Im Rahmen des Dualen Systems hat die Berufsausbildung sowohl schulische, als auch betriebspraktische Elemente. Davon erfasst sind Daten zu Schülerinnen und Schülern, Studierenden, Lehrenden, Unterrichts- und Studienfächern, vermittelte Kompetenzen, Einrichtungen etc. Für allgemeinbildende Programme im Rahmen der ISCED-Level 3 und 4 bitte die Klasse 'Höhere Bildung und Weiterbildung' verwenden. + + + + + Health + GESUNDHEIT + + + + + + + + + + + + + + + + + + Health.DietAndNutrition + Ernährung + Ernährungsweisen, Essverhalten und messbare Kenngrößen zur Ernährungsweise wie Body-Maß-Index (BMI), Gewicht, Vitaminspiegel. + + + + + Health.DrugAbuseAlcoholAndSmoking + Drogenmissbrauch, Alkoholkonsum und Rauchen + Daten zu Missbrauch von Drogen und anderen Substanzen. Daten zu Rauch- und Trinkgewohnheiten. Für Daten zum medizinischen Gebrauch von Drogen (z. B. medizinischer Gebrauch von Cannabis) verwenden Sie "Medizinische Behandlung und Medikation" + + + + + Health.GeneralHealthAndWellbeing + Allgemeine Gesundheit und Wohlbefinden + Daten zu allgemeinem Wohlbefinden oder Gesundheitszustand eines Individuums, einer Gruppe oder einer Population, entweder objektiv gemessen/beobachtet oder selbst empfunden. Wohlbefinden umfasst beispielsweise körperliche und geistige Leistungsfähigkeit und Verfassung sowie die soziale Situation. + + + + + Health.HealthCareServicesAndPolicies + Gesundheitswesen + Die Klasse bezieht sich auf die Verfügbarkeit und Qualität von Dienstleistungen, Einrichtungen und Personal im Gesundheitsbereich sowie auf wirtschaftliche und politische Entscheidungen und Regelungen zu Medizin und Gesundheitswesen auf nationaler, regionaler oder lokaler Ebene. Verwendungshinweis: Für bereits implementierte Gesetze und Regelungen bitte die Klasse 'Gesetzgebung und Rechtssysteme' verwenden. + + + + + Health.MedicationAndTreatment + Medizinische Behandlung und Medikation + Bezieht sich auf die Therapie von Krankheiten oder auf Maßnahmen, die Krankheiten vorbeugen sollen. Gemeint ist hier die individuelle Patientenversorgung sowie Forschung zu medikamentösen und sonstigen Therapien. Umfasst beispielsweise Daten zu chirurgischen Verfahren, Rehabilitation, Arztbesuchen, Physiotherapie, Manuelle Therapie, Pflege im Krankenhaus oder zuhause, Psychotherapie, Medikation. + + + + + Health.OccupationalHealth + Arbeitsmedizin + Daten zu Gefährdungen am Arbeitsplatz, arbeitsbezogenen Erkrankungen, Sicherheit am Arbeitsplatz, Krankheitstagen, Arbeitsfähigkeit und Arbeitsunfähigkeit, Berufsunfähigkeitsrenten. + + + + + Health.PhysicalFitnessAndExercise + Körperliche Fitness und Bewegung + Daten zur generellen körperlichen Kondition, der körperlichen Aktivität und zur ausgeübten Bewegung der Befragten. + + + + + Health.PublicHealth + Öffentliche Gesundheit/Public Health + Umfasst die Vorbeugung und Überwachung von Krankheiten und Behinderungen sowie die Förderung körperlicher und geistiger Gesundheit der Bevölkerung auf internationaler, nationaler, regionaler oder lokaler Ebene. Zum Beispiel Maßnahmen zur Gesundheitsförderung und Krankheitsprävention; Wirkung von Public-Health-Programmen; Bewältigung lokaler, nationaler oder globaler Gesundheitsprobleme; Verbreitung und Auswirkung von Krankheiten, Gesundheitsproblemen oder Erregern; Ausbruch von Krankheiten. + + + + + Health.ReproductiveHealth + Reproduktionsmedizin + Die Reproduktionsmedizin befasst sich mit den Vorgängen, den Funktionen und dem System der Fortpflanzung in allen Lebensphasen. Umfasst Daten zu Schwangerschaft, Geburt, Familienplanung, Empfängnis, Abtreibung und Menopause. + + + + + Health.SignsAndSymptomsPathologicalConditions + Krankheitszeichen, Symptome und krankhafte Befunde + Daten zu anatomischen oder physiologischen Befunden und objektiven oder subjektiven Anzeichen von Erkrankungen oder Symptomen, die nicht als Krankheit oder Syndrom eingeordnet werden (in solchen Fällen bitte die Klasse 'Spezifische Krankheiten, Störungen und Gesundheitsprobleme' verwenden). Davon erfasst sind klinische Anzeichen, die entweder objektiv von medizinisch ausgebildeten Personen oder subjektiv durch Betroffene wahrgenommen werden. Beispiele sind Schmerzen, Durchfall, Ausschlag, Beinlängendifferenz, Schlafstörungen, Zähneknirschen, Entzündung, Herzrhythmusstörung. + + + + + Health.SpecificDiseasesDisordersAndMedicalConditions + Spezifische Krankheiten, Störungen und Gesundheitsprobleme + Daten zu einzelnen Krankheiten, Störungen oder Syndromen (etwa entsprechend ICD-10). Zum Beispiel medizinische Forschung zu den Ursachen von Diabetes oder Forschung zur physischen und psychischen Genesung nach Herz-OPs. + + + + + Health.WoundsAndInjuries + Verwundungen und Verletzungen + Verwundungen und Verletzungen, sowohl physisch, als auch psychisch, verursacht entweder durch Unfälle oder Gewalteinwirkung (durch aggressives Verhalten eines Einzelnen oder einer Gruppe). Verwendungshinweis: Für arbeitsbezogene Verwundungen und Verletzungen bitte die Klasse 'Arbeitsmedizin' verwenden. + + + + + History + GESCHICHTE + Verwendungshinweis: Diese Hauptklasse bitte mit einer jeweils inhaltlich passenden Klasse kombinieren. Zum Beispiel 'Arbeitsbedingungen' und 'Gesundheitswesen' zusammen mit 'GESCHICHTE' für Daten zur Arbeitsumgebung von Hausärzten in den 1960er Jahren. Die Hauptklasse 'GESCHICHTE' sollte nur in solchen Fällen alleine verwendet werden, für die keine spezifischere passende Klasse vorhanden ist. + + + + + HousingAndLandUse + WOHNRAUM UND FLÄCHENNUTZUNG + + + + + + + + HousingAndLandUse.Housing + Wohnraum + Daten zur Wohnraumsituation in einem Gebiet, einschließlich Versorgung mit sozialem Wohnungsbau sowie Verfügbarkeit und Kosten von Wohnraum. Wohnraum schließt Haupt-, Zweit- und Ferienwohnungen bzw. -häuser ein. + + + + + HousingAndLandUse.LandUseAndPlanning + Flächennutzung und Raumplanung + Bezieht sich sowohl auf die Planung der Raum- oder Flächennutzung, als auch auf die Nutzung selbst. Raumplanung bezieht sich auf Vorgänge, die eine optimale und nachhaltige Nutzung von Flächen in ländlichen und städtischen Umgebungen sicherstellen sollen. + + + + + LabourAndEmployment + ARBEIT UND BESCHÄFTIGUNG + + + + + + + + + + + + + LabourAndEmployment.EmployeeTraining + Berufliche Weiterbildung + Unternehmensinterne Weiterbildung bzw. vom Unternehmen finanzierte Weiterbildungsmaßnahmen für Beschäftigte. Berufliche Weiterbildung hat die Verbesserung der Fertigkeiten und Kompetenzen der Beschäftigten zum Ziel. Verwendungshinweis: Für Berufsausbildungen, z.B. nach dem Dualen System, bitte die Klasse 'Berufsausbildung' verwenden. + + + + + LabourAndEmployment.Employment + Beschäftigung und Erwerbstätigkeit + Daten zu Beschäftigungsquoten und -statistiken, zum Arbeitsmarkt, zu offenen Stellen, zur Arbeitssuche, zu Tätigkeitsmerkmalen, zur Beschäftigung bestimmter Gruppen (z.B. Beschäftigung von Jugendlichen oder Minderheiten), Arbeitsvermittlung und Karriere. Verwendungshinweis: Für Daten zu Löhnen und Gehältern bitte die Klasse 'Arbeitsbedingungen' verwenden. + + + + + LabourAndEmployment.LabourAndEmploymentPolicy + Beschäftigungspolitik + Daten zu Beschäftigungspolitik in Bezug auf industrielle Beziehungen, den Arbeitsmarkt sowie Gleichbehandlung und Diskriminierung bei der Arbeit. Auf nationaler Ebene schließt Beschäftigungspolitik die Einflussnahme auf Arbeitsplatznachfrage und -angebot und das Funktionieren des Arbeitsmarkts ein. Verwendungshinweis: Für bereits implementierte Gesetze und Regelungen bitte die Klasse 'Gesetzgebung und Rechtssysteme' verwenden. + + + + + LabourAndEmployment.LabourRelationsConflict + Arbeitsbeziehungen und Arbeitskonflikte + Beziehungen zwischen Arbeitgebern und Arbeitnehmern. + + + + + LabourAndEmployment.Retirement + Ruhestand + Umfasst Daten zu allen Formen des Ruhestands, etwa auch zu Altersteilzeit, zum Rentenalter und zum Frühruhestand. + + + + + LabourAndEmployment.Unemployment + Arbeitslosigkeit + Daten zu Arbeitslosenquoten und -statistiken, zu Beschäftigungsprogrammen und Arbeitsbeschaffungsmaßnahmen, zu Dauer und Häufigkeit individueller Arbeitslosigkeit, zu sozialen und psychologischen Auswirkungen von Arbeitslosigkeit usw. + + + + + LabourAndEmployment.WorkingConditions + Arbeitsbedingungen + Arbeitsbedingungen sind die Bedingungen, unter denen Beschäftigte arbeiten. Zum Beispiel Vertragsarten, Arbeitszeiten (Stundenzahl, Pausenzeiten, Arbeitspläne, Überstunden), Löhne und Gehälter, körperliche Aspekte und geistige Herausforderungen der Arbeit, Rechte und Pflichten der Beschäftigten, Arbeitsbelastung, Einflussmöglichkeiten der Beschäftigten, Urlaubsansprüche, physische Arbeitsumgebung. Verwendungshinweis: Für Daten zur Sicherheit am Arbeitsplatz bitte auch die Klasse 'Arbeitsmedizin' verwenden. + + + + + LawCrimeAndLegalSystems + GESETZ, KRIMINALITÄT UND RECHTSSYSTEME + + + + + + + + LawCrimeAndLegalSystems.CrimeAndLawEnforcement + Kriminalität und Gesetzesvollzug + Daten zu Kriminalität, Kriminalitätsbekämpfung, Opfern von Straftaten, Straftätern, Polizei, Geheimdienst, Justizvollzuganstalten (z.B. Gefängnisse, Jugendarrestanstalten), Grenzkontrollen und Zöllen, einschließlich Daten zu den entsprechenden Einrichtungen und den dort Beschäftigten. + + + + + LawCrimeAndLegalSystems.LegislationAndLegalSystems + Gesetzgebung und Rechtssysteme + Daten zu internationaler oder nationaler Gesetzgebung (Legislative), zu Verwaltungsvorschriften, zur Judikative (Einfluss und Aktivitäten der Gerichte), zu alternativer Streitbeilegung und Wiedergutmachung sowie zu Strafen. + + + + + MediaCommunicationAndLanguage + MEDIEN, KOMMUNIKATION UND SPRACHE + + + + + + + + + + MediaCommunicationAndLanguage.InformationSociety + Informationsgesellschaft + Die Klasse bezieht sich auf die wirtschaftlich, politisch oder kulturell relevante Generierung, Verbreitung, Nutzung, Integration und Verarbeitung von Information. Umfasst beispielsweise Daten zu e-Commerce (Internethandel), e-Government, Sozialen Medien, Mobilen Empfangsgeräten, Internetzugang und -nutzung, Datenschutz, Informations- und Bibliotheksressourcen. Verwendungshinweis: Für einzelne Technologien bitte die Klasse 'Informations- und Kommunikationstechnologie' verwenden. + + + + + MediaCommunicationAndLanguage.LanguageAndLinguistics + Sprache und Sprachwissenschaft + Daten zu sprachwissenschaftlichen Studien und Analysen, Soziolinguistik, Sprachregelungen, individuelle Sprachkenntnisse, auf der Arbeit und zuhause gesprochene Sprachen, Einstellungen zu Minderheitensprachen usw. + + + + + MediaCommunicationAndLanguage.Media + Medien + Daten mit Bezug zu jeglicher Form von Massenmedien. Zum Beispiel gedruckte oder Online-Zeitungen, Zeitschriften, Fernsehen und Radio. Verwendungshinweis: Für Soziale Medien bitte die Klasse 'Informationsgesellschaft' verwenden. + + + + + MediaCommunicationAndLanguage.PublicRelations + Öffentlichkeitsarbeit/Public Relations + Öffentlichkeitsarbeit beinhaltet Maßnahmen zur Erhaltung oder Verbesserung des Rufs (Image) von Individuen, Unternehmen, Organisationen und Regierungen, zum Beispiel durch Werbemaßnahmen, Lobby-Arbeit usw. + + + + + NaturalEnvironment + UMWELT UND NATUR + + + + + + + + + + NaturalEnvironment.EnergyAndNaturalResources + Energiequellen und natürliche Rohstoffquellen + Natürliche Rohstoffquellen sind Güter wie Land, Wälder, Energiequellen, Wasser oder Mineralien, die in der Natur vorkommen und verarbeitet oder konsumiert werden können. Energiequellen sind fossile Brennstoffe wie Öl, Erdgas oder Kohle sowie Kernkraft, Sonne, Gezeiten und Wind oder erneuerbare organische Energiequellen. + + + + + NaturalEnvironment.EnvironmentAndConservation + Umweltschutz und Naturschutz + Die Klasse umfasst Fragen der Umweltzerstörung und -verschmutzung, des Umweltschutzes, des Klimawandels, des Hochwasserschutzes und der Bodenerosion. + + + + + NaturalEnvironment.NaturalLandscapes + Naturlandschaften + Naturlandschaften sind Landschaften und Bestandteile von Landschaften, die nicht von Menschen verändert oder beeinflusst wurden. + + + + + NaturalEnvironment.PlantsAndAnimals + Pflanzen und Tiere + Bezieht sich auf Haustiere, Nutztiere und wilde Tiere sowie auf alle Arten von Pflanzen (z.B. Blumen, Bäume, Moos, Gemüse). + + + + + Politics + POLITIK + + + + + + + + + + + + Politics.ConflictSecurityAndPeace + Konflikte, Sicherheit und Frieden + Die Klasse bezieht sich auf innere und internationale Sicherheit, innere und internationale Konflikte, Krieg und Frieden, Verteidigung und Streitkräfte (Militär). Verwendungshinweis: Für Daten zum Thema Polizei verwenden sie bitte die Klasse 'Kriminalität und Gesetzesvollzug'. + + + + + Politics.Elections + Wahlen + Daten zu Wahlen, Volksabstimmungen, Wahlkampagnen, Wahlergebnissen, Kandidatinnen und Kandidaten, Wahlempfehlungsanwendungen, Wahlkampffinanzierung usw. Für Wahlverhalten bitte zusätzlich die Klasse 'Politisches Verhalten und politische Einstellungen' vergeben. + + + + + Politics.GovernmentPoliticalSystemsAndOrganisations + Regierung, politische Systeme, Parteien und Organisationen + Die Klasse bezieht sich auf Angelegenheiten nationaler, regionaler und lokaler politischer Institutionen (z.B. Regierung, Parlament, Lokalregierungen), öffentliche Verwaltung und andere politische Organisationsformen wie politische Parteien. Zum Beispiel Daten zur Regierungspolitik, zu politischen Parteien und deren Politik, zu Führungspersonen politischer Parteien oder zum Vertrauen in Regierungsinstitutionen. + + + + + Politics.InternationalPoliticsAndOrganisations + Internationale Politik und Internationale Organisationen + Die Klasse umfasst Global Governance, Diplomatie, Außenpolitik, internationale Beziehungen, internationale Organisationen (z.B. EU, WHO, UN, WTO) und internationale Abkommen. + + + + + Politics.PoliticalBehaviourAndAttitudes + Politisches Verhalten und politische Einstellungen + Daten zu parteipolitischen Einstellungen, zu politischem Engagement und politischer Teilhabe, zu Wahlverhalten und zur Zufriedenheit mit der Regierung. Verwendungshinweis: Für Daten zu einzelnen Doktrinen bitte die Klasse 'Politische Ideologie' verwenden. + + + + + Politics.PoliticalIdeology + Politische Ideologie + Politische Ideologie umfasst Ideen, Ideale und Theorien zu Regierungs- und Gesellschaftsformen bzw. deren Organisation. Bedeutende politische Ideologien sind Anarchie, Demokratie, Liberalismus, Konservatismus usw. + + + + + Psychology + PSYCHOLOGIE + + + + + + ScienceAndTechnology + WISSENSCHAFT UND TECHNIK + + + + + + + + ScienceAndTechnology.Biotechnology + Biotechnologie + Daten zum Einsatz von Prozessen auf biomolekularer und zellularer Ebene zum Zweck der Entwicklung neuer Produkte und technischer Verfahrensweisen. + + + + + ScienceAndTechnology.InformationTechnology + Informations- und Kommunikationstechnologie + Bezieht sich auf Informations- und Kommunikationstechnologie die zuhause, am Arbeitsplatz oder in anderen sozialen Situationen zum Einsatz kommt. Verwendungshinweis: Für Anwendung und Auswirkungen dieser Technologien bitte die Klasse 'Informationsgesellschaft' verwenden. + + + + + SocialStratificationAndGroupings + SOZIALE SCHICHTUNG UND GRUPPIERUNGEN + + + + + + + + + + + + + + + SocialStratificationAndGroupings.Children + Kinder + Verwendungshinweis: Wenn ein bestimmter Aspekt der Kindheit untersucht wird, muss möglicherweise noch eine weitere entsprechende Klasse vergeben werden. + + + + + SocialStratificationAndGroupings.Elderly + Ältere Menschen + Verwendungshinweis: Wenn ein bestimmter Aspekt des Alters untersucht wird, muss möglicherweise noch eine weitere entsprechende Klasse vergeben werden. + + + + + SocialStratificationAndGroupings.ElitesAndLeadership + Eliten und Führungspersönlichkeiten + Erfasst Studien zu Existenz, Rolle und Einfluss von Eliten in einer Gesellschaft. Eliten sind Personen oder Gruppen, die viel Macht oder Einfluss auf Gesellschaft, Politik, Wohlstandsverhältnisse usw. ausüben. Sie tun dies entweder durch Entscheidungen oder beispielsweise auch dadurch, dass sie ihren Vorstellungen oder Gefühlen Ausdruck verleihen bzw. für diese einstehen. Eingeschlossen sind alle Themen mit Bezug zu einflussreichen Personen in allen Gesellschaftsbereichen, zum Beispiel zu politischen Entscheidungsträgerinnen und Entscheidungsträgern, Führungspersonen politischer Parteien, einflussreichen Kunstschaffenden und Medienprominenz (auch aus den sozialen Medien). + + + + + SocialStratificationAndGroupings.EqualityInequalityAndSocialExclusion + Gleichheit, Ungleichheit und soziale Ausgrenzung + Diese Klasse bezieht sich auf Studien die untersuchen, inwiefern sich Individuen oder Gruppen einer Gesellschaft in Bezug auf Status und Chancengleichheit unterscheiden. Gleichheit wird beispielsweise in Bezug auf Bildung, (finanzielles) Vermögen, Einkommen, berufliche Stellung, Macht, Prestige, Arbeitsbedingungen, Freizeit, Zugang zu Dienstleistungen und soziale Beziehungen untersucht. Marginalisierung und soziale Ausgrenzung (Exklusion) können aus systematischer Benachteiligung in unterschiedlichen Lebensbereichen resultieren. Betroffenen fehlt es möglicherweise an Rechten, Chancen, Mitteln (Resourcen) und sozialer Integration. Beispiele für Daten sind: Statistiken zu Einkommensunterschieden; Studien zu wahrgenommener, gruppenbezogener Diskriminierung (z.B. gegenüber Fremden, Obdachlosen, Asylsuchenden); Studien zu wahrgenommenen Unterschieden zwischen Männern und Frauen in Bezug auf Mögichkeiten, führende Rollen in der Politik einzunehmen; Studien zum Einfluss elterlicher Arbeitslosigkeit auf soziale Beziehungen und das Bildungsniveau ihrer Kinder. + + + + + SocialStratificationAndGroupings.FamilyLifeAndMarriage + Familie und Ehe + Die Klasse bezieht sich auf die Themen Familie, Verwandschaft, Generationen und Haushaltszusammensetzung. Beispiele: Daten zur Kindererziehung; Arbeitsaufteilung in der Familie; Unterstützung für Familienmitglieder. + + + + + SocialStratificationAndGroupings.GenderAndGenderRoles + Geschlecht und Geschlechterrollen + Die Klasse umfasst: Geschlechtsidentität; Geschlechterrepräsentationen (z.B. Repräsentation der Geschlechter in Entscheidungsgremien, in den Medien, in der Literatur usw.); soziale, kulturelle und psychologische Eigenschaften, die männlich oder weiblich assoziiert sind; Frauenforschung; Männerforschung; erwartetes Verhalten und erwartete Entscheidungen in Bezug auf Geschlechterrollen sowie die Auswirkungen solcher Erwartungen. + + + + + SocialStratificationAndGroupings.Minorities + Minderheiten + Gruppen von Individuen, die sich von der Mehrheit der Bevölkerung in irgendeiner Weise unterschieden. Unterscheidungsmerkmale sind zum Beispiel Ethnien, Religion, Gesundheit, sexuelle Orientierung, Muttersprache und Kultur. Verwendungshinweis: Für bestimmte Minderheitenthemen kann es sinnvoll sein, zusätzlich eine weitere Klasse zu vergeben, zum Beispiel 'Gleichheit, Ungleichheit und soziale Ausgrenzung' oder 'Sprache und Sprachwissenschaft'. + + + + + SocialStratificationAndGroupings.SocialAndOccupationalMobility + Soziale und berufliche Mobilität + Die Klasse bezieht sich auf soziale und berufliche Mobilität von Individuen, Familien oder Gruppen im Zeitverlauf. Dazu gehören horizontale Mobilität (Positionswechsel ohne Statuswechsel) und vertikale Mobilität (statusbezogene Auf- oder Abwärtsbewegung). Beispiele: Arbeiter und Arbeiterinnen aus verschwindenden Industriezweigen wechseln in andere Industriezweige (horizontale berufliche Mobilität); Kinder aus armen Familien erreichen eine höhere soziale Schicht als ihre Eltern (vertikale zwischengenerationale soziale Mobilität). + + + + + SocialStratificationAndGroupings.Youth + Jugend + Verwendungshinweis: Wenn ein bestimmter Aspekt der Jugend untersucht wird, muss möglicherweise noch eine weitere entsprechende Klasse vergeben werden. + + + + + SocialWelfarePolicyAndSystems + SOZIALPOLITIK, SOZIALFÜRSORGE UND SOZIALSYSTEME + + + + + + + + + SocialWelfarePolicyAndSystems.SocialWelfarePolicy + Sozialpolitik + Sozialpolitik bezieht sich auf Entscheidungen und die Umsetzung von Richtlinien zur Sozialfürsorge, etwa zu deren Finanzierung und Verfügbarkeit, sowohl auf nationaler und regionaler, als auch auf lokaler Ebene. Mögliche Inhalte: Wem stehen welche Sozialleistungen und Hilfen zu und auf welcher Grundlage; Höhe von Sozialleistungen; wer kann Fürsorge leisten. Beispiele: eine Regelung, die vorgibt, dass ältere Menschen so lange zuhause gepflegt werden sollen wie möglich; eine Regelung, die festlegt, wie lange Anspruch auf Arbeitslosengeld besteht. Verwendungshinweis: Für bereits implementierte Gesetze und Regelungen bitte zusätzlich die Klasse 'Gesetzgebung und Rechtssysteme' verwenden. + + + + + SocialWelfarePolicyAndSystems.SocialWelfareSystemsStructures + Sozialsysteme und Strukturen der Sozialfürsorge + Die Klasse bezieht sich auf die tatsächliche Leistung der Sozialfürsorge; wer setzt die Sozialfürsorge in der Praxis um und in welchem Umfang; wie sind die Leistungen konkret aufgebaut. Beispiel: Fragen dazu, wie eine Kommunalverwaltung die lokale Altenpflege organisiert. + + + + + SocialWelfarePolicyAndSystems.SpecificSocialServicesUseAndAvailability + Inanspruchnahme sozialer Dienstleistungen + Beispiele für Daten in dieser Klasse sind: Angaben zum Umfang der Inanspruchnahme spezifischer Sozialleistungen; Meinungen zur Angemessenheit, Zugänglichkeit und Verfügbarkeit einzelner Leistungen; allgemeine Zufriedenheit mit den Leistungen. Zum Beispiel werden zum Thema der Inanspruchnahme von Leistungen durch eine Personengruppe (z.B. ältere Menschen) sowohl Befragungen dieser Personen, als auch Auswertungen von Statistiken zu diesen Leistungen von dieser Klasse erfasst. + + + + + SocietyAndCulture + GESELLSCHAFT UND KULTUR + + + + + + + + + + + + + + + SocietyAndCulture.CommunityUrbanAndRuralLife + Gemeinde, Wohnumgebung, Stadtleben und Landleben + Umfasst Daten zu verschiedenen Aspekten des Lebens in der Wohnumgebung oder städtischen bzw. ländlichen Gegenden. Erfasst sind auch Meinungen zu und Vorstellungen von der eigenen Wohngegend, städtischen und ländlichen Gegenden. Auch erfasst sind Dienstleistungen, Einrichtungen und Geschäftsgelegenheiten in der Wohnumgebung. + + + + + SocietyAndCulture.CulturalActivitiesAndParticipation + Kulturelle Aktivitäten und kulturelle Teilhabe + Aktive oder passive Teilnahme an jeglicher kultureller Aktivität sowie Einstellungen und Auffassungen zu diesen Aktivitäten. + + + + + SocietyAndCulture.CulturalAndNationalIdentity + Kulturelle und nationale Identität + Daten zur Identitätsbildung, zum persönlichen Selbstbild und zu Zugehörigkeitsgefühlen in Bezug auf Nationalität, Örtlichkeit, soziale Klasse, Generation, Ethnie, Religion oder andere soziale/kulturelle Zugehörigkeit. Mit erfasst ist das Sich-Ausschließen oder das Ausschließen anderer aus diesen Zugehörigkeiten. + + + + + SocietyAndCulture.LeisureTourismAndSport + Freizeit, Tourismus und Sport + Die Klasse bezieht sich auf Entspannungsgewohnheiten, Freizeitaktivitäten und Hobbys. Dazu gehören das Verfolgen von Sportveranstaltungen, Teilnahme an Sportwettbewerben, Urlaubsreisen. Verwendungshinweis: Für Daten zu körperlicher Betätigung aus gesundheitlichen Gründen bitte die Klasse 'Körperliche Fitness und Bewegung' verwenden. + + + + + SocietyAndCulture.ReligionAndValues + Religion und Werte + Umfasst Daten zu Fragen religiösen Verhaltens, religiöser Bewegungen, Spiritualität, Moral und anderen Werten. + + + + + SocietyAndCulture.SocialBehaviourAndAttitudes + Soziales Verhalten und soziale Einstellungen + Umfasst Daten zu Sozialverhalten, sozialer Interaktion und sozialen Netzwerken, sozialer Verantwortung, sozialen Normen und Regeln sowie Einstellungen zu diesen Themen. + + + + + SocietyAndCulture.SocialChange + Sozialer Wandel + Daten zu gesellschaftlichen Veränderungen, einschließlich Veränderungen in den Einstellungen. + + + + + SocietyAndCulture.SocialConditionsAndIndicators + Soziale Lage und soziale Indikatoren + Diese Klasse umfasst alle Aspekte der Lebensqualität und des Wohlbefindens auf individueller sowie gesellschaftlicher Ebene und wie diese Aspekte wahrgenommen werden. Verwendungshinweis: Je nachdem, welcher Aspekt untersucht wird, muss möglicherweise noch eine weitere Klasse vergeben werden, zum Beispiel 'Gesundheit und Wohlbefinden', 'Gleichheit, Ungleichheit und soziale Ausgrenzung', 'Wirtschaftliche Lage und Indikatoren' usw. + + + + + SocietyAndCulture.TimeUse + Zeitbudget + Bezieht sich auf Daten zu Zeitanteilen, die Befragte für verschiedene Aktivitäten aufwenden. + + + + + TradeIndustryAndMarkets + HANDEL, INDUSTRIE UND MÄRKTE + + + + + + + + + TradeIndustryAndMarkets.AgricultureAndRuralIndustry + Landwirtschaft und Agrarindustrie + Daten zur landwirtschaftlichen Produktion und allen ländlichen Gewerben, zum Beispiel auch Fischerei und Forstwirtschaft. + + + + + TradeIndustryAndMarkets.BusinessIndustrialManagementAndOrganisation + Management und Organisation von Betrieben + Umfasst Daten zu Industrie und Betrieb wie zum Beispiel Management, Produktivität, Geschäftswachstum und -rückgang, Organisationsstruktur, Verwaltung, Investition in Forschung und Entwicklung, Märkte und Marktanteile. + + + + + TradeIndustryAndMarkets.ForeignTrade + Außenhandel + Umfasst Daten zum Austausch von Kapital, Waren oder Dienstleistungen über internationale Grenzen hinweg. + + + + + TransportAndTravel + TRANSPORT UND REISEN + Umfasst Daten zu jeglicher Art des Gütertransports sowie zu den Themen Pendeln, Öffentlicher Personennahverkehr (Zug-, Bus-, U-Bahn-Verkehr etc.) und städtische Verkehrsverbünde. Erfasst sind auch Daten zu Mobilitätsgewohnheiten, zum Beispiel grenzübergreifendes Einkaufen; Verkehrswege und -mittel, die genutzt werden, um ein Ziel zu erreichen. Verwendungshinweis: Für Urlaubs- und Freizeitreisen bitte die Klasse 'Freizeit, Tourismus und Sport' verwenden. + + + + + Other + SONSTIGES + Verwenden Sie diese Klasse für Themen, die keine eigene Klasse haben. + + diff --git a/config/dcat-ap-fields.php b/config/dcat-ap-fields.php new file mode 100644 index 0000000..5aaac59 --- /dev/null +++ b/config/dcat-ap-fields.php @@ -0,0 +1,125 @@ + 'title', + 'meta_key' => '', + 'dcat_prop' => 'dct:title', + 'label' => __( 'Titel (dct:title)', 'open-data-wizard' ), + 'points' => 10, + 'required' => true, + ), + array( + 'key' => 'description', + 'meta_key' => '_odw_description', + 'dcat_prop' => 'dct:description', + 'label' => __( 'Worum geht es in diesem Datensatz?', 'open-data-wizard' ), + 'points' => 10, + 'required' => true, + ), + array( + 'key' => 'publisher', + 'meta_key' => '_odw_publisher', + 'dcat_prop' => 'dct:publisher', + 'label' => __( 'Wer gibt diese Daten heraus?', 'open-data-wizard' ), + 'points' => 10, + 'required' => true, + ), + array( + 'key' => 'license', + 'meta_key' => '', + 'dcat_prop' => 'dct:license', + 'label' => __( 'Unter welcher Lizenz sind diese Daten verfügbar?', 'open-data-wizard' ), + 'points' => 10, + 'required' => true, + ), + array( + 'key' => 'distribution', + 'meta_key' => '', + 'dcat_prop' => 'dcat:accessURL', + 'label' => __( 'Distribution mit Zugriffs-URL (dcat:accessURL)', 'open-data-wizard' ), + 'points' => 15, + 'required' => true, + ), + + // ------------------------------------------------------------------------- + // Empfohlene Felder (DCAT-AP 3.0 recommended) — 40 Punkte + // ------------------------------------------------------------------------- + + array( + 'key' => 'language', + 'meta_key' => '_odw_language', + 'dcat_prop' => 'dct:language', + 'label' => __( 'Sprache (dct:language)', 'open-data-wizard' ), + 'points' => 10, + 'required' => false, + ), + array( + 'key' => 'keywords', + 'meta_key' => '_odw_keywords', + 'dcat_prop' => 'dcat:keyword', + 'label' => __( 'Schlagworte (dcat:keyword)', 'open-data-wizard' ), + 'points' => 10, + 'required' => false, + ), + array( + 'key' => 'theme', + 'meta_key' => '_odw_theme', + 'dcat_prop' => 'dcat:theme', + 'label' => __( 'Thema (dcat:theme)', 'open-data-wizard' ), + 'points' => 10, + 'required' => false, + ), + array( + 'key' => 'issued', + 'meta_key' => '_odw_issued', + 'dcat_prop' => 'dct:issued', + 'label' => __( 'Veröffentlichungsdatum (dct:issued)', 'open-data-wizard' ), + 'points' => 10, + 'required' => false, + ), + + // ------------------------------------------------------------------------- + // Optionale Angaben — 5 Punkte + // ------------------------------------------------------------------------- + + array( + 'key' => 'dist_format', + 'meta_key' => '', + 'dcat_prop' => 'dct:format', + 'label' => __( 'Format der Distribution (dct:format)', 'open-data-wizard' ), + 'points' => 5, + 'required' => false, + ), + +); +// Summe: 55 + 40 + 5 = 100. diff --git a/config/dct-format-list.php b/config/dct-format-list.php new file mode 100644 index 0000000..4907c6a --- /dev/null +++ b/config/dct-format-list.php @@ -0,0 +1,89 @@ + ['mime' => MIME-Typ, 'eu_uri' => EU-Publications-Office-Kürzel] + * eu_uri wird zu http://publications.europa.eu/resource/authority/file-type/{eu_uri} aufgelöst. + * Leere eu_uri bedeutet: kein EU-Standard-URI vorhanden → Kurzbezeichnung als Fallback. + * + * @package OpenDataWizard + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + +return array( + 'CSV' => array( + 'mime' => 'text/csv', + 'eu_uri' => 'CSV', + ), + 'JSON' => array( + 'mime' => 'application/json', + 'eu_uri' => 'JSON', + ), + 'XLSX' => array( + 'mime' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'eu_uri' => 'XLSX', + ), + 'ODS' => array( + 'mime' => 'application/vnd.oasis.opendocument.spreadsheet', + 'eu_uri' => 'ODS', + ), + 'PDF' => array( + 'mime' => 'application/pdf', + 'eu_uri' => 'PDF', + ), + 'XML' => array( + 'mime' => 'application/xml', + 'eu_uri' => 'XML', + ), + 'GeoJSON' => array( + 'mime' => 'application/geo+json', + 'eu_uri' => 'GEOJSON', + ), + 'GML' => array( + 'mime' => 'application/gml+xml', + 'eu_uri' => 'GML', + ), + 'SHP' => array( + 'mime' => 'application/x-esri-shape', + 'eu_uri' => 'SHP', + ), + 'GPKG' => array( + 'mime' => 'application/geopackage+sqlite3', + 'eu_uri' => 'GPKG', + ), + 'KML' => array( + 'mime' => 'application/vnd.google-earth.kml+xml', + 'eu_uri' => 'KML', + ), + 'ZIP' => array( + 'mime' => 'application/zip', + 'eu_uri' => 'ZIP', + ), + 'TIFF' => array( + 'mime' => 'image/tiff', + 'eu_uri' => 'TIFF', + ), + 'RDF' => array( + 'mime' => 'application/rdf+xml', + 'eu_uri' => 'RDF_XML', + ), + 'TURTLE' => array( + 'mime' => 'text/turtle', + 'eu_uri' => 'TURTLE', + ), + 'N-TRIPLES' => array( + 'mime' => 'application/n-triples', + 'eu_uri' => 'N_TRIPLES', + ), + 'JSON-LD' => array( + 'mime' => 'application/ld+json', + 'eu_uri' => 'JSON_LD', + ), + 'Sonstiges' => array( + 'mime' => '', + 'eu_uri' => '', + ), +); diff --git a/config/licenses.txt b/config/licenses.txt new file mode 100644 index 0000000..96c9df9 --- /dev/null +++ b/config/licenses.txt @@ -0,0 +1,18 @@ +# Open Data Lizenzen — eine Lizenz pro Zeile +# Format: URI | Anzeigename +# Kommentarzeilen beginnen mit # + +https://creativecommons.org/publicdomain/zero/1.0/ | CC0 1.0 (Public Domain) +https://creativecommons.org/licenses/by/4.0/ | CC-BY 4.0 (Namensnennung) +https://creativecommons.org/licenses/by-sa/4.0/ | CC-BY-SA 4.0 (Namensnennung, Weitergabe unter gleichen Bedingungen) +https://creativecommons.org/licenses/by-nc/4.0/ | CC-BY-NC 4.0 (Namensnennung, nicht kommerziell) +https://creativecommons.org/licenses/by-nd/4.0/ | CC-BY-ND 4.0 (Namensnennung, keine Bearbeitung) +https://creativecommons.org/licenses/by-nc-sa/4.0/ | CC-BY-NC-SA 4.0 (Namensnennung, nicht kommerziell, Weitergabe) +https://creativecommons.org/licenses/by-nc-nd/4.0/ | CC-BY-NC-ND 4.0 (Namensnennung, nicht kommerziell, keine Bearbeitung) +https://www.govdata.de/dl-de/by-2-0 | Datenlizenz Deutschland – Namensnennung 2.0 +https://www.govdata.de/dl-de/zero-2-0 | Datenlizenz Deutschland – Zero 2.0 +https://opendatacommons.org/licenses/odbl/1-0/ | ODbL 1.0 (Open Database License) +https://opendatacommons.org/licenses/pddl/1-0/ | PDDL 1.0 (Public Domain Dedication and License) +https://opendatacommons.org/licenses/by/1-0/ | ODC-BY 1.0 (Open Data Commons Attribution) +https://opensource.org/licenses/MIT | MIT License +https://www.apache.org/licenses/LICENSE-2.0 | Apache License 2.0 diff --git a/includes/class-admin.php b/includes/class-admin.php index 71dfd8c..ed609cd 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -73,8 +73,21 @@ public static function set_columns( array $columns ): array { public static function render_column( string $column, int $post_id ): void { switch ( $column ) { case 'odw_license': - $license = (string) carbon_get_post_meta( $post_id, 'odw_license' ); - echo esc_html( ODW_Fields::get_license_label( $license ) ); + // License is now stored per distribution (Änderung 7); show label from first distribution. + $dists = carbon_get_post_meta( $post_id, 'odw_distributions' ); + $lic = ''; + if ( is_array( $dists ) ) { + foreach ( $dists as $dist ) { + $candidate = (string) ( $dist['license'] ?? '' ); + if ( '' !== $candidate ) { + $lic = ( 'sonstige' === $candidate && ! empty( $dist['license_custom'] ) ) + ? (string) $dist['license_custom'] + : $candidate; + break; + } + } + } + echo esc_html( '' !== $lic ? ODW_Fields::get_license_label( $lic ) : '—' ); break; case 'odw_theme': @@ -261,6 +274,41 @@ public static function enqueue_assets( string $hook ): void { true ); + wp_enqueue_script( + 'odw-admin-fields', + ODW_PLUGIN_URL . 'assets/js/odw-admin-fields.js', + array(), + ODW_VERSION, + true + ); + + // Build license auto-suggest options from config/licenses.txt. + $license_file_options = array(); + foreach ( ODW_Fields::load_license_list() as $uri => $label ) { + $license_file_options[] = array( + 'value' => $uri, + 'label' => $label, + ); + } + + // Build CESSDA auto-suggest options from SKOS file. + $cessda_options = array(); + foreach ( ODW_Fields::load_cessda_options() as $uri => $label ) { + $cessda_options[] = array( + 'value' => $uri, + 'label' => $label, + ); + } + + wp_localize_script( + 'odw-admin-fields', + 'odwAdminFields', + array( + 'licenseOptions' => $license_file_options, + 'cessdaOptions' => $cessda_options, + ) + ); + global $post; $file_id = $post ? (int) get_post_meta( $post->ID, '_odw_file_id', true ) : 0; $file_name = ''; diff --git a/includes/class-fields.php b/includes/class-fields.php index adb54ff..e9c5d44 100644 --- a/includes/class-fields.php +++ b/includes/class-fields.php @@ -40,15 +40,25 @@ public static function register(): void { /** * Registers the tabbed meta container with all dataset fields. + * Tab structure (v2.1+): + * 1 — Grundlegende Informationen + * 2 — Inhaltliche Angaben + * 3 — Datenbereitstellung (Lizenz + Distribution) + * 4 — Erweiterte Angaben + * 5 — Vorschau */ private static function register_required_fields(): void { Container::make( 'post_meta', __( 'Pflichtangaben', 'open-data-wizard' ) ) ->where( 'post_type', '=', 'odw_dataset' ) ->set_priority( 'high' ) + + // ----------------------------------------------------------------- + // Tab 1 — Grundlegende Informationen + // ----------------------------------------------------------------- ->add_tab( - __( '1 — Pflichtangaben', 'open-data-wizard' ), + __( '1 — Grundlegende Informationen', 'open-data-wizard' ), array( - Field::make( 'html', 'odw_description_tab1_hint' ) + Field::make( 'html', 'odw_tab1_hint' ) ->set_html( '

' . esc_html__( 'Pflichtfelder gemäß DCAT-AP 3.0. Ohne diese Angaben kann der Datensatz nicht veröffentlicht werden.', 'open-data-wizard' ) . '

' ), Field::make( 'text', 'odw_publisher', __( 'Wer gibt diese Daten heraus?', 'open-data-wizard' ) ) @@ -57,21 +67,23 @@ private static function register_required_fields(): void { ->set_attribute( 'placeholder', __( 'z.B. Musterorganisation e.V.', 'open-data-wizard' ) ) ->set_help_text( __( 'HERAUSGEBENDE ORGANISATION (dct:publisher)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: Musterstadt Statistikamt, Umweltbundesamt, Verbraucherzentrale e.V.', 'open-data-wizard' ) ), + Field::make( 'select', 'odw_theme', __( 'In welche Kategorie gehört dieser Datensatz?', 'open-data-wizard' ) ) + ->add_options( self::get_theme_options() ) + ->set_help_text( __( 'THEMA (dcat:theme)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: Umwelt, Bildung, Gesundheit, Wirtschaft, Kultur', 'open-data-wizard' ) ), + Field::make( 'textarea', 'odw_description', __( 'Worum geht es in diesem Datensatz?', 'open-data-wizard' ) ) ->set_required( true ) ->set_rows( 5 ) ->set_attribute( 'placeholder', __( 'Kurze Beschreibung des Datensatzes…', 'open-data-wizard' ) ) ->set_help_text( __( 'BESCHREIBUNG (dct:description)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: Ein Überblick über die bevölkerungsreichsten Städte in Deutschland mit statistischen Daten zu Einwohnerzahl und Entwicklung.', 'open-data-wizard' ) ), - - Field::make( 'select', 'odw_license', __( 'Unter welcher Lizenz sind diese Daten verfügbar?', 'open-data-wizard' ) ) - ->set_required( true ) - ->set_default_value( class_exists( 'ODW_Settings' ) ? (string) ODW_Settings::get( 'default_license' ) : '' ) - ->add_options( self::get_license_options() ) - ->set_help_text( __( 'LIZENZ (dct:license)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: CC0 1.0, CC-BY 4.0 – Diese bestimmt, wie andere die Daten nutzen dürfen.', 'open-data-wizard' ) ), ) ) + + // ----------------------------------------------------------------- + // Tab 2 — Inhaltliche Angaben + // ----------------------------------------------------------------- ->add_tab( - __( '2 — Optionale Angaben', 'open-data-wizard' ), + __( '2 — Inhaltliche Angaben', 'open-data-wizard' ), 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' ) : '' ) @@ -83,10 +95,6 @@ private static function register_required_fields(): void { ->set_attribute( 'placeholder', __( 'z.B. Umwelt', 'open-data-wizard' ) ) ->set_help_text( __( 'SCHLAGWORTE (dcat:keyword)', 'open-data-wizard' ) . "\n\n" . __( 'Jedes Schlagwort in einer eigenen Zeile. Beispiel: Umwelt, Wasser, Luftverschmutzung', 'open-data-wizard' ) ), - Field::make( 'select', 'odw_theme', __( 'In welche Kategorie gehört dieser Datensatz?', 'open-data-wizard' ) ) - ->add_options( self::get_theme_options() ) - ->set_help_text( __( 'THEMA (dcat:theme)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: Umwelt, Bildung, Gesundheit, Wirtschaft, Kultur', 'open-data-wizard' ) ), - Field::make( 'date', 'odw_issued', __( 'Wann wurden diese Daten zum ersten Mal veröffentlicht?', 'open-data-wizard' ) ) ->set_storage_format( 'Y-m-d' ) ->set_picker_options( array( 'dateFormat' => 'Y-m-d' ) ) @@ -96,11 +104,30 @@ private static function register_required_fields(): void { ->set_storage_format( 'Y-m-d' ) ->set_picker_options( array( 'dateFormat' => 'Y-m-d' ) ) ->set_help_text( __( 'ÄNDERUNGSDATUM (dct:modified)', 'open-data-wizard' ) . "\n\n" . __( 'Wird automatisch bei jeder Speicherung aktualisiert. Beispiel: 2026-04-22', 'open-data-wizard' ) ), + + Field::make( 'text', 'odw_cessda_topic', __( 'CESSDA Themenklassifikation', 'open-data-wizard' ) ) + ->set_attribute( 'placeholder', __( 'Thema eintippen oder URI eingeben…', 'open-data-wizard' ) ) + ->set_attribute( 'data-odw-autosuggest', 'cessda' ) + ->set_help_text( __( 'CESSDA THEMENKLASSIFIKATION (cessda:topic)', 'open-data-wizard' ) . "\n\n" . __( 'Aus dem CESSDA Controlled Vocabulary (Version 4.2.3, Deutsch). Beispiel: Volkszählungen, Migration, Wirtschaftspolitik', 'open-data-wizard' ) ), ) ) + + // ----------------------------------------------------------------- + // Tab 3 — Datenbereitstellung (Lizenz + Distribution) + // ----------------------------------------------------------------- ->add_tab( - __( '3 — Distribution', 'open-data-wizard' ), + __( '3 — Datenbereitstellung', 'open-data-wizard' ), array( + Field::make( 'html', 'odw_dist_intro' ) + ->set_html( + '
' . + '

' . esc_html__( 'Was ist eine Distribution?', 'open-data-wizard' ) . '

' . + '

' . + esc_html__( 'Eine Distribution beschreibt eine konkrete Bereitstellungsform Ihrer Daten — zum Beispiel eine CSV-Datei, eine JSON-API oder ein PDF-Dokument. Ein Datensatz kann mehrere Distributionen in verschiedenen Formaten haben. Jede Distribution erhält eine eigene Lizenz.', 'open-data-wizard' ) . + '

' . + '
' + ), + Field::make( 'complex', 'odw_distributions', __( 'Wo können die Daten heruntergeladen werden?', 'open-data-wizard' ) ) ->set_min( 1 ) ->set_collapsed( false ) @@ -116,11 +143,34 @@ private static function register_required_fields(): void { ->add_options( self::get_format_options() ) ->set_help_text( __( 'FORMAT (dct:format)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: CSV, JSON, PDF', 'open-data-wizard' ) ), - Field::make( 'text', 'byte_size', __( 'Wie groß ist die Datei (in Bytes)?', 'open-data-wizard' ) ) - ->set_attribute( 'placeholder', __( 'optional, z.B. 204800', 'open-data-wizard' ) ) + Field::make( 'html', 'byte_size_ui' ) + ->set_html( self::get_filesize_widget_html() ), + + Field::make( 'text', 'byte_size', __( 'Dateigröße (Bytes)', 'open-data-wizard' ) ) ->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' ) ), + ->set_attribute( 'data-odw-backing', 'byte_size' ) + ->set_attribute( 'class', 'odw-byte-size-backing' ), + + Field::make( 'select', 'license', __( 'Unter welcher Lizenz sind diese Daten verfügbar?', 'open-data-wizard' ) ) + ->set_required( true ) + ->set_default_value( class_exists( 'ODW_Settings' ) ? (string) ODW_Settings::get( 'default_license' ) : '' ) + ->add_options( self::get_license_options() ) + ->set_help_text( __( 'LIZENZ (dct:license)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: CC0 1.0, CC-BY 4.0 – Diese bestimmt, wie andere die Daten nutzen dürfen.', 'open-data-wizard' ) ), + + Field::make( 'text', 'license_custom', __( 'Lizenz-URI eingeben oder auswählen', 'open-data-wizard' ) ) + ->set_attribute( 'placeholder', __( 'https://example.org/meine-lizenz', 'open-data-wizard' ) ) + ->set_attribute( 'data-odw-autosuggest', 'license_custom' ) + ->set_help_text( __( 'EIGENE LIZENZ-URI', 'open-data-wizard' ) . "\n\n" . __( 'Vollständige URI der Lizenz eingeben oder aus der Liste auswählen. Beispiel: https://creativecommons.org/licenses/by/4.0/', 'open-data-wizard' ) ) + ->set_conditional_logic( + array( + array( + 'field' => 'license', + 'value' => 'sonstige', + 'compare' => '=', + ), + ) + ), 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' ) ) @@ -128,13 +178,15 @@ private static function register_required_fields(): void { ) ) ->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' ) ), - ) ) + + // ----------------------------------------------------------------- + // Tab 4 — Erweiterte Angaben (unverändert) + // ----------------------------------------------------------------- ->add_tab( __( '4 — Erweiterte Angaben', 'open-data-wizard' ), array( - // --- Projektseite & Aktualität --- Field::make( 'html', 'odw_ext_hint_landing' ) ->set_html( '

' . esc_html__( 'Projektseite & Aktualität', 'open-data-wizard' ) . '

' ), @@ -147,7 +199,6 @@ private static function register_required_fields(): void { ->add_options( self::get_periodicity_options() ) ->set_help_text( __( 'AKTUALISIERUNGSFREQUENZ (dct:accrualPeriodicity)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: Täglich, Monatlich, Jährlich, Unregelmäßig', 'open-data-wizard' ) ), - // --- Abdeckung --- Field::make( 'html', 'odw_ext_hint_coverage' ) ->set_html( '

' . esc_html__( 'Abdeckung', 'open-data-wizard' ) . '

' ), @@ -169,7 +220,6 @@ private static function register_required_fields(): void { ->set_picker_options( array( 'dateFormat' => 'Y-m-d' ) ) ->set_help_text( __( 'ZEITLICHER BEZUG — ENDE (dct:temporal)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: 2024-12-31', 'open-data-wizard' ) ), - // --- Kontaktpunkt --- Field::make( 'html', 'odw_ext_hint_contact' ) ->set_html( '

' . esc_html__( 'Kontaktpunkt (dcat:contactPoint)', 'open-data-wizard' ) . '

' ), @@ -188,6 +238,10 @@ private static function register_required_fields(): void { ->set_help_text( __( 'WEBSITE (dcat:contactPoint)', 'open-data-wizard' ) . "\n\n" . __( 'Beispiel: https://beispiel.de/kontakt', 'open-data-wizard' ) ), ) ) + + // ----------------------------------------------------------------- + // Tab 5 — Vorschau + // ----------------------------------------------------------------- ->add_tab( __( '5 — Vorschau', 'open-data-wizard' ), array( @@ -226,11 +280,8 @@ public static function set_modified_date( int $post_id, \WP_Post $post ): void { return; } - // Update without triggering an infinite loop. remove_action( 'save_post_odw_dataset', array( self::class, 'set_modified_date' ), 10 ); - update_post_meta( $post_id, '_odw_modified', current_time( 'Y-m-d' ) ); - add_action( 'save_post_odw_dataset', array( self::class, 'set_modified_date' ), 10, 2 ); } @@ -241,24 +292,171 @@ public static function set_modified_date( int $post_id, \WP_Post $post ): void { /** * Returns the required scalar fields definition used by both form rendering * and the validation class. Each entry: [meta_key, label]. + * Loaded from config/dcat-ap-fields.php. * * @return array */ public static function get_required_fields(): array { - return array( - array( - 'meta_key' => '_odw_description', - 'label' => __( 'Worum geht es in diesem Datensatz?', 'open-data-wizard' ), - ), - array( - 'meta_key' => '_odw_publisher', - 'label' => __( 'Wer gibt diese Daten heraus?', 'open-data-wizard' ), - ), - array( - 'meta_key' => '_odw_license', - 'label' => __( 'Unter welcher Lizenz sind diese Daten verfügbar?', 'open-data-wizard' ), - ), - ); + $all = self::load_field_definitions(); + + $required = array(); + foreach ( $all as $field ) { + // Only scalar fields (with a meta_key) are validated here. + // Distribution and title are handled separately in ODW_Validation. + if ( $field['required'] && ! empty( $field['meta_key'] ) ) { + $required[] = array( + 'meta_key' => $field['meta_key'], + 'label' => $field['label'], + ); + } + } + + return $required; + } + + // ------------------------------------------------------------------------- + // Config file loaders + // ------------------------------------------------------------------------- + + /** + * Load field definitions from config/dcat-ap-fields.php. + * Used by ODW_Fields::get_required_fields() and ODW_Quality::get_indicators(). + * + * @return array + */ + public static function load_field_definitions(): array { + $file = ODW_PLUGIN_DIR . 'config/dcat-ap-fields.php'; + + if ( ! file_exists( $file ) ) { + return array(); + } + + $data = require $file; + return is_array( $data ) ? $data : array(); + } + + /** + * Load format definitions from config/dct-format-list.php. + * + * @return array + */ + private static function load_format_list(): array { + $file = ODW_PLUGIN_DIR . 'config/dct-format-list.php'; + + if ( ! file_exists( $file ) ) { + return array(); + } + + $data = require $file; + return is_array( $data ) ? $data : array(); + } + + /** + * Load license options from config/licenses.txt. + * Format: URI | Label (one per line; lines starting with # are comments). + * + * @return array URI => Label map (excludes the Sonstige entry). + */ + public static function load_license_list(): array { + $file = ODW_PLUGIN_DIR . 'config/licenses.txt'; + + if ( ! file_exists( $file ) ) { + return array(); + } + + $lines = file( $file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ); + $options = array(); + + if ( ! is_array( $lines ) ) { + return array(); + } + + foreach ( $lines as $line ) { + $line = trim( $line ); + + if ( '' === $line || str_starts_with( $line, '#' ) ) { + continue; + } + + $parts = array_map( 'trim', explode( '|', $line, 2 ) ); + + if ( 2 === count( $parts ) && '' !== $parts[0] && '' !== $parts[1] ) { + $options[ $parts[0] ] = $parts[1]; + } + } + + return $options; + } + + /** + * Parse CESSDA SKOS/RDF file and return concept URI => German label map. + * Result is cached in a transient (24h TTL). + * + * @return array URI => German label. + */ + public static function load_cessda_options(): array { + $cache_key = 'odw_cessda_options'; + $cached = get_transient( $cache_key ); + + if ( is_array( $cached ) ) { + return $cached; + } + + $file = ODW_PLUGIN_DIR . 'config/TopicClassification-4.2.3_de-4.2.3.rdf'; + + if ( ! file_exists( $file ) ) { + return array(); + } + + $options = array(); + + // Suppress XML warnings; file is valid RDF/XML from CESSDA. + libxml_use_internal_errors( true ); + $xml = simplexml_load_file( $file ); + libxml_clear_errors(); + + if ( false === $xml ) { + return array(); + } + + $xml->registerXPathNamespace( 'rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' ); + $xml->registerXPathNamespace( 'skos', 'http://www.w3.org/2004/02/skos/core#' ); + + $descriptions = $xml->xpath( '//rdf:Description' ); + + if ( ! is_array( $descriptions ) ) { + return array(); + } + + foreach ( $descriptions as $desc ) { + $attrs = $desc->attributes( 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' ); + $uri = (string) ( $attrs['about'] ?? '' ); + + // Skip ConceptScheme root and entries without a #fragment identifier. + if ( '' === $uri || ! str_contains( $uri, '#' ) ) { + continue; + } + + // Get German prefLabel. + $labels = $desc->children( 'http://www.w3.org/2004/02/skos/core#' ); + foreach ( $labels as $name => $node ) { + if ( 'prefLabel' !== $name ) { + continue; + } + $xml_attrs = $node->attributes( 'http://www.w3.org/XML/1998/namespace' ); + if ( 'de' === (string) ( $xml_attrs['lang'] ?? '' ) ) { + $options[ $uri ] = (string) $node; + break; + } + } + } + + // Sort by label for a better auto-suggest experience. + asort( $options ); + + set_transient( $cache_key, $options, DAY_IN_SECONDS ); + + return $options; } // ------------------------------------------------------------------------- @@ -267,6 +465,7 @@ public static function get_required_fields(): array { /** * Lizenzen als URI → Label Map für Select-Felder und den `odw_license_options`-Filter. + * Lädt Standard-Optionen aus dem Formular; vollständige Liste via licenses.txt. * * @return array Erweiterbar via `add_filter('odw_license_options', ...)`. */ @@ -276,7 +475,7 @@ public static function get_license_options(): array { 'https://creativecommons.org/publicdomain/zero/1.0/' => 'CC0 1.0', 'https://creativecommons.org/licenses/by/4.0/' => 'CC-BY 4.0', 'https://creativecommons.org/licenses/by-sa/4.0/' => 'CC-BY-SA 4.0', - 'https://www.govdata.de/dl-de/by-2-0' => 'Datenlizenz Deutschland Namensnennung 2.0', + 'sonstige' => __( 'Sonstige…', 'open-data-wizard' ), ); return (array) apply_filters( 'odw_license_options', $options ); @@ -284,21 +483,27 @@ public static function get_license_options(): array { /** * Translate a license URI to its human-readable label. - * Single source of truth — used by Fields and Admin classes. + * Checks known options first, then the external licenses.txt list. * - * @param string $uri License URI. + * @param string $uri License URI (or 'sonstige'). * @return string Human-readable label, or the URI itself if not found. */ public static function get_license_label( string $uri ): string { $options = self::get_license_options(); - return $options[ $uri ] ?? $uri; + + if ( isset( $options[ $uri ] ) ) { + return $options[ $uri ]; + } + + $extended = self::load_license_list(); + + return $extended[ $uri ] ?? $uri; } /** - * 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. + * Themen-Vokabular als EU-Vocabulary-URI → Label Map. * - * @return array Erweiterbar via `add_filter('odw_theme_options', ...)`. + * @return array */ public static function get_theme_options(): array { $base = 'http://publications.europa.eu/resource/authority/data-theme/'; @@ -323,10 +528,7 @@ public static function get_theme_options(): array { } /** - * Aktualisierungsfrequenzen aus dem EU Publications Office Frequency Vocabulary. - * - * Basis-URI: http://publications.europa.eu/resource/authority/frequency/ - * Vollständige URI wird als Wert gespeichert und im JSON-LD als `@id` ausgegeben. + * Aktualisierungsfrequenzen. * * @return array */ @@ -346,67 +548,51 @@ public static function get_periodicity_options(): array { } /** - * Dateiformate als Kurzbezeichnung → Kurzbezeichnung Map für das Distribution-Feld. - * Die Kurzbezeichnung wird via `get_format_mime()` in den MIME-Typ für JSON-LD übersetzt. + * Dateiformate aus config/dct-format-list.php. * * @return array */ public static function get_format_options(): array { - return array( - '' => __( '— Bitte wählen —', 'open-data-wizard' ), - 'CSV' => 'CSV', - 'JSON' => 'JSON', - 'XLSX' => 'XLSX', - 'PDF' => 'PDF', - 'GeoJSON' => 'GeoJSON', - 'XML' => 'XML', - 'Sonstiges' => __( 'Sonstiges', 'open-data-wizard' ), - ); + $list = self::load_format_list(); + $options = array( '' => __( '— Bitte wählen —', 'open-data-wizard' ) ); + + foreach ( array_keys( $list ) as $key ) { + $options[ $key ] = $key; + } + + return $options; } /** - * Format MIME-type mapping for JSON-LD output. + * Format MIME-type mapping — loaded from config/dct-format-list.php. * * @param string $format Short format label (e.g. "CSV"). * @return string MIME type, or the original format string if unknown. */ public static function get_format_mime( string $format ): string { - $map = array( - 'CSV' => 'text/csv', - 'JSON' => 'application/json', - 'XLSX' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'PDF' => 'application/pdf', - 'GeoJSON' => 'application/geo+json', - 'XML' => 'application/xml', - ); - - return $map[ $format ] ?? $format; + $list = self::load_format_list(); + return $list[ $format ]['mime'] ?? $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', - ); + $list = self::load_format_list(); + $eu_code = $list[ $format ]['eu_uri'] ?? ''; - return $map[ $format ] ?? $format; + if ( '' === $eu_code ) { + return $format; + } + + return 'http://publications.europa.eu/resource/authority/file-type/' . $eu_code; } /** - * Language options as EU Publications Office language URI → label map. - * Used in dct:language for DCAT-AP.de / Civora compliance. + * Language options. * * @return array */ @@ -420,8 +606,7 @@ public static function get_language_options(): array { } /** - * Administrative geocoding level options from the DCAT-AP.de political geocoding vocabulary. - * Used in dcatde:politicalGeocodingLevelURI for Civora compliance. + * Administrative geocoding level options. * * @return array */ @@ -436,6 +621,48 @@ public static function get_political_geocoding_level_options(): array { ); } + // ------------------------------------------------------------------------- + // HTML helpers + // ------------------------------------------------------------------------- + + /** + * Renders the HTML for the composite file-size widget (Änderung 8). + * The backing byte_size field is hidden via CSS; JS syncs the two. + * + * @return string HTML markup. + */ + private static function get_filesize_widget_html(): string { + ob_start(); + ?> +
+ +
+ + + +
+

+ +

+
+ post_title; $description = carbon_get_post_meta( $post_id, 'odw_description' ); $publisher = carbon_get_post_meta( $post_id, 'odw_publisher' ); - $license = carbon_get_post_meta( $post_id, 'odw_license' ); $language = carbon_get_post_meta( $post_id, 'odw_language' ); $keywords = carbon_get_post_meta( $post_id, 'odw_keywords' ); $theme = carbon_get_post_meta( $post_id, 'odw_theme' ); $issued = carbon_get_post_meta( $post_id, 'odw_issued' ); $modified = get_post_meta( $post_id, '_odw_modified', true ); $distributions = carbon_get_post_meta( $post_id, 'odw_distributions' ); + $cessda_topic = (string) carbon_get_post_meta( $post_id, 'odw_cessda_topic' ); // Extended DCAT-AP fields (Tab 4). $landing_page = (string) carbon_get_post_meta( $post_id, 'odw_landing_page' ); @@ -525,12 +752,7 @@ function odw_build_dataset_jsonld( int $post_id ): ?array { ), ); - if ( ! empty( $license ) ) { - $dataset['dct:license'] = array( '@id' => (string) $license ); - } - if ( ! empty( $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', @@ -548,7 +770,6 @@ function odw_build_dataset_jsonld( int $post_id ): ?array { } if ( ! empty( $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', @@ -563,6 +784,10 @@ function odw_build_dataset_jsonld( int $post_id ): ?array { $dataset['dcat:theme'] = array( '@id' => $theme_legacy[ (string) $theme ] ?? (string) $theme ); } + if ( ! empty( $cessda_topic ) ) { + $dataset['cessda:topic'] = array( '@id' => $cessda_topic ); + } + if ( ! empty( $issued ) ) { $dataset['dct:issued'] = array( '@type' => 'xsd:date', @@ -579,9 +804,10 @@ function odw_build_dataset_jsonld( int $post_id ): ?array { if ( ! empty( $distributions ) && is_array( $distributions ) ) { $dist_list = array(); + foreach ( $distributions as $dist ) { - // esc_url_raw() strips javascript:, data:, and other non-HTTP schemes. $access_url = esc_url_raw( (string) ( $dist['access_url'] ?? '' ) ); + if ( empty( $access_url ) ) { continue; } @@ -595,12 +821,19 @@ function odw_build_dataset_jsonld( int $post_id ): ?array { $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']; + // File size: prefer new composite fields (byte_size_value + unit), fall back to legacy byte_size. + $byte_size = odw_compute_byte_size( $dist ); + if ( $byte_size > 0 ) { + $dist_item['dcat:byteSize'] = $byte_size; } - if ( ! empty( $license ) ) { - $dist_item['dct:license'] = array( '@id' => (string) $license ); + // Lizenz pro Distribution (Änderung 7). + $dist_license = (string) ( $dist['license'] ?? '' ); + if ( 'sonstige' === $dist_license && ! empty( $dist['license_custom'] ) ) { + $dist_license = (string) $dist['license_custom']; + } + if ( ! empty( $dist_license ) && 'sonstige' !== $dist_license ) { + $dist_item['dct:license'] = array( '@id' => $dist_license ); } if ( ! empty( $dist['attribution_text'] ) ) { @@ -675,3 +908,33 @@ function odw_build_dataset_jsonld( int $post_id ): ?array { */ return (array) apply_filters( 'odw_dataset_jsonld', $dataset, $post_id ); } + +/** + * Compute byte size from a distribution array. + * Supports the legacy single-field format (byte_size in bytes) + * and the new composite format (byte_size_value + byte_size_unit). + * + * @param array $dist Distribution sub-array from Carbon Fields. + * @return int Byte count, or 0 if not set. + */ +function odw_compute_byte_size( array $dist ): int { + // New composite format. + $value = $dist['byte_size_value'] ?? ''; + if ( '' !== $value && is_numeric( $value ) && (float) $value > 0 ) { + $unit = (string) ( $dist['byte_size_unit'] ?? 'MB' ); + $factors = array( + 'KB' => 1024, + 'MB' => 1048576, + 'GB' => 1073741824, + ); + return (int) round( (float) $value * ( $factors[ $unit ] ?? 1048576 ) ); + } + + // Legacy: raw byte count. + $legacy = $dist['byte_size'] ?? ''; + if ( '' !== $legacy && is_numeric( $legacy ) && (int) $legacy >= 0 ) { + return (int) $legacy; + } + + return 0; +} diff --git a/includes/class-quality.php b/includes/class-quality.php index c7dc9a2..5af0fa8 100644 --- a/includes/class-quality.php +++ b/includes/class-quality.php @@ -26,9 +26,13 @@ */ class ODW_Quality { - public const LEVEL_HIGH = 'high'; - public const LEVEL_MEDIUM = 'medium'; - public const LEVEL_LOW = 'low'; + public const LEVEL_PERFECT = 'perfect'; + public const LEVEL_HIGH = 'high'; + public const LEVEL_SUFFICIENT = 'sufficient'; + public const LEVEL_LOW = 'low'; + + /** Score at which all required fields are exactly fulfilled (no optional). */ + private const REQUIRED_ONLY_SCORE = 55; /** * Registers WordPress hooks. @@ -50,12 +54,20 @@ public static function init(): void { /** * Gibt alle Qualitätsindikatoren mit Punktewertung zurück. + * Geladen aus config/dcat-ap-fields.php via ODW_Fields::load_field_definitions(). * * @return array */ public static function get_indicators(): array { + if ( class_exists( 'ODW_Fields' ) ) { + $defs = ODW_Fields::load_field_definitions(); + if ( ! empty( $defs ) ) { + return $defs; + } + } + + // Fallback when ODW_Fields is not available (e.g. unit tests). return array( - // Pflichtfelder (DCAT-AP 3.0 mandatory) — 55 Punkte. array( 'key' => 'title', 'label' => __( 'Titel (dct:title)', 'open-data-wizard' ), @@ -86,8 +98,6 @@ public static function get_indicators(): array { 'points' => 15, 'required' => true, ), - - // Empfohlene Felder (DCAT-AP 3.0 recommended) — 40 Punkte. array( 'key' => 'language', 'label' => __( 'Sprache (dct:language)', 'open-data-wizard' ), @@ -112,8 +122,6 @@ public static function get_indicators(): array { 'points' => 10, 'required' => false, ), - - // Optionale Angaben — 5 Punkte. array( 'key' => 'dist_format', 'label' => __( 'Format der Distribution (dct:format)', 'open-data-wizard' ), @@ -121,7 +129,6 @@ public static function get_indicators(): array { 'required' => false, ), ); - // Summe: 55 + 40 + 5 = 100. } // ------------------------------------------------------------------------- @@ -185,7 +192,21 @@ private static function check_indicator( string $key, \WP_Post $post ): bool { return '' !== trim( (string) carbon_get_post_meta( $post->ID, 'odw_publisher' ) ); case 'license': - return '' !== trim( (string) carbon_get_post_meta( $post->ID, 'odw_license' ) ); + // License is now per-distribution (Änderung 7). + $dists = carbon_get_post_meta( $post->ID, 'odw_distributions' ); + if ( ! is_array( $dists ) ) { + return false; + } + foreach ( $dists as $dist ) { + $lic = (string) ( $dist['license'] ?? '' ); + if ( '' !== $lic && 'sonstige' !== $lic ) { + return true; + } + if ( 'sonstige' === $lic && ! empty( $dist['license_custom'] ) ) { + return true; + } + } + return false; case 'distribution': $dists = carbon_get_post_meta( $post->ID, 'odw_distributions' ); @@ -230,17 +251,25 @@ private static function check_indicator( string $key, \WP_Post $post ): bool { } /** - * Ermittelt das Ampel-Level aus dem Score. + * Ermittelt das Qualitätslevel aus dem Score. + * + * 100 → Perfekt (alle Felder ausgefüllt) + * 56–99 → Gut (alle Pflichtfelder + einige optionale) + * REQUIRED_ONLY → Ausreichend (genau alle Pflichtfelder, keine optionalen) + * < REQUIRED → Verbesserungsbedarf (Pflichtfelder fehlen) * * @param int $score Numeric score 0–100. - * @return string One of LEVEL_HIGH, LEVEL_MEDIUM, LEVEL_LOW. + * @return string One of LEVEL_PERFECT, LEVEL_HIGH, LEVEL_SUFFICIENT, LEVEL_LOW. */ public static function get_level( int $score ): string { - if ( $score >= 80 ) { + if ( 100 === $score ) { + return self::LEVEL_PERFECT; + } + if ( $score > self::REQUIRED_ONLY_SCORE ) { return self::LEVEL_HIGH; } - if ( $score >= 50 ) { - return self::LEVEL_MEDIUM; + if ( self::REQUIRED_ONLY_SCORE === $score ) { + return self::LEVEL_SUFFICIENT; } return self::LEVEL_LOW; } @@ -367,7 +396,8 @@ public static function render_meta_box( \WP_Post $post ): void { $score = $quality['score']; $level = $quality['level']; $level_label = self::get_level_label( $level ); - $level_class = 'odw-quality--' . $level; + // Map 'perfect' to 'high' CSS class so existing styles apply. + $level_class = 'odw-quality--' . ( 'perfect' === $level ? 'high' : $level ); $stored = $quality['indicators']; ?>
@@ -467,9 +497,10 @@ public static function render_meta_box( \WP_Post $post ): void { */ public static function get_level_label( string $level ): string { return array( - self::LEVEL_HIGH => __( 'Gut', 'open-data-wizard' ), - self::LEVEL_MEDIUM => __( 'Mittel', 'open-data-wizard' ), - self::LEVEL_LOW => __( 'Verbesserungsbedarf', 'open-data-wizard' ), + self::LEVEL_PERFECT => __( 'Perfekt', 'open-data-wizard' ), + self::LEVEL_HIGH => __( 'Gut', 'open-data-wizard' ), + self::LEVEL_SUFFICIENT => __( 'Ausreichend', 'open-data-wizard' ), + self::LEVEL_LOW => __( 'Verbesserungsbedarf', 'open-data-wizard' ), )[ $level ] ?? __( 'Unbekannt', 'open-data-wizard' ); } diff --git a/includes/class-validation.php b/includes/class-validation.php index c3233b9..9748f08 100644 --- a/includes/class-validation.php +++ b/includes/class-validation.php @@ -114,6 +114,11 @@ private static function validate( int $post_id, array $postarr ): array { $errors[] = __( 'Mindestens eine Distribution mit Zugriffs-URL (dcat:accessURL)', 'open-data-wizard' ); } + // --- Lizenz in jeder Distribution (Änderung 7) --- + if ( $has_distribution && ! self::all_distributions_have_license( $post_id, $cf_input ) ) { + $errors[] = __( 'Jede Distribution benötigt eine Lizenzangabe (dct:license)', 'open-data-wizard' ); + } + return $errors; } @@ -166,6 +171,67 @@ private static function has_valid_distribution( int $post_id, array $cf_input ): return false; } + /** + * Check that every distribution with an access_url also has a license. + * + * @param int $post_id Post ID. + * @param array $cf_input Decoded Carbon Fields compact input. + */ + private static function all_distributions_have_license( int $post_id, array $cf_input ): bool { + // Collect licenses from CF compact input. + $url_keys = array(); + $license_keys = array(); + + foreach ( $cf_input as $key => $value ) { + $key = (string) $key; + if ( str_contains( $key, '_odw_distributions' ) && str_contains( $key, 'access_url' ) && ! empty( $value ) ) { + // Extract group index from key like _odw_distributions[0][access_url]. + preg_match( '/\[(\d+)\]/', $key, $matches ); + if ( isset( $matches[1] ) ) { + $url_keys[] = $matches[1]; + } + } + if ( str_contains( $key, '_odw_distributions' ) && str_contains( $key, '][license]' ) && ! empty( $value ) ) { + preg_match( '/\[(\d+)\]/', $key, $matches ); + if ( isset( $matches[1] ) ) { + $license_keys[] = $matches[1]; + } + } + } + + // If we found distribution data in CF compact input, check it. + if ( ! empty( $url_keys ) ) { + foreach ( $url_keys as $idx ) { + if ( ! in_array( $idx, $license_keys, true ) ) { + return false; + } + } + return true; + } + + // Fall back to existing meta. + $distributions = carbon_get_post_meta( $post_id, 'odw_distributions' ); + + if ( ! is_array( $distributions ) ) { + return true; + } + + foreach ( $distributions as $dist ) { + if ( empty( $dist['access_url'] ) ) { + continue; + } + $lic = (string) ( $dist['license'] ?? '' ); + if ( '' === $lic ) { + return false; + } + if ( 'sonstige' === $lic && empty( $dist['license_custom'] ) ) { + return false; + } + } + + return true; + } + /** * Validate that a string is a safe HTTP(S) URL. * Blocks javascript:, data:, and other non-HTTP schemes. diff --git a/tests/test-fields-extended.php b/tests/test-fields-extended.php index 091131c..f44af97 100644 --- a/tests/test-fields-extended.php +++ b/tests/test-fields-extended.php @@ -511,9 +511,9 @@ public function test_build_distribution_format_uses_eu_uri(): void { } /** - * Each distribution carries the dataset-level license as dct:license @id. + * Distribution carries its own per-distribution license as dct:license @id. */ - public function test_build_distribution_inherits_license(): void { + public function test_build_distribution_has_own_license(): void { $this->load_fields(); $license_uri = 'https://creativecommons.org/publicdomain/zero/1.0/'; @@ -521,12 +521,12 @@ public function test_build_distribution_inherits_license(): void { 25, 'odw_dataset', array( - 'odw_license' => $license_uri, 'odw_distributions' => array( array( 'access_url' => 'https://example.com/data.csv', 'format' => 'CSV', 'byte_size' => '', + 'license' => $license_uri, ), ), ) @@ -584,18 +584,33 @@ public function test_build_includes_political_geocoding_level_uri(): void { } /** - * The dct:license at dataset level is emitted as an @id reference. + * The dct:license is emitted per distribution (not at dataset level). + * A distribution with license 'sonstige' + license_custom uses the custom URI. */ - public function test_build_dataset_license_is_id_reference(): void { + public function test_build_distribution_sonstige_license_uses_custom_uri(): void { $this->load_fields(); - $license_uri = 'https://creativecommons.org/licenses/by/4.0/'; - $this->setup_jsonld_mocks( 28, 'odw_dataset', array( 'odw_license' => $license_uri ) ); + $custom_uri = 'https://example.org/my-custom-license'; + $this->setup_jsonld_mocks( + 28, + 'odw_dataset', + array( + 'odw_distributions' => array( + array( + 'access_url' => 'https://example.com/data.csv', + 'format' => 'CSV', + 'byte_size' => '', + 'license' => 'sonstige', + 'license_custom' => $custom_uri, + ), + ), + ) + ); $result = odw_build_dataset_jsonld( 28 ); $this->assertIsArray( $result ); - $this->assertArrayHasKey( 'dct:license', $result ); - $this->assertSame( array( '@id' => $license_uri ), $result['dct:license'] ); + $dist = $result['dcat:distribution'][0]; + $this->assertSame( array( '@id' => $custom_uri ), $dist['dct:license'] ); } } diff --git a/tests/test-fields.php b/tests/test-fields.php index 67c5bfb..129d5e1 100644 --- a/tests/test-fields.php +++ b/tests/test-fields.php @@ -17,7 +17,8 @@ class Test_ODW_Fields extends TestCase { /** - * Returns a non-empty array with the three required meta keys. + * Returns scalar required meta keys (description + publisher). + * License is now per-distribution and not included as a scalar field. */ public function test_get_required_fields_returns_expected_keys(): void { require_once ODW_PLUGIN_DIR . 'includes/class-fields.php'; @@ -25,12 +26,12 @@ public function test_get_required_fields_returns_expected_keys(): void { $fields = ODW_Fields::get_required_fields(); $this->assertIsArray( $fields ); - $this->assertCount( 3, $fields ); $meta_keys = array_column( $fields, 'meta_key' ); $this->assertContains( '_odw_description', $meta_keys ); $this->assertContains( '_odw_publisher', $meta_keys ); - $this->assertContains( '_odw_license', $meta_keys ); + // License is now per-distribution; no longer a scalar dataset-level field. + $this->assertNotContains( '_odw_license', $meta_keys ); } /** diff --git a/tests/test-quality.php b/tests/test-quality.php index 329cf11..47f2864 100644 --- a/tests/test-quality.php +++ b/tests/test-quality.php @@ -52,27 +52,35 @@ public function test_get_level_returns_high_at_80(): void { } /** - * Score 100 maps to LEVEL_HIGH. + * Score 100 maps to LEVEL_PERFECT. */ - public function test_get_level_returns_high_at_100(): void { + public function test_get_level_returns_perfect_at_100(): void { $this->load_class(); - $this->assertSame( ODW_Quality::LEVEL_HIGH, ODW_Quality::get_level( 100 ) ); + $this->assertSame( ODW_Quality::LEVEL_PERFECT, ODW_Quality::get_level( 100 ) ); } /** - * Score 50 maps to LEVEL_MEDIUM. + * Score 56 maps to LEVEL_HIGH (some optional fields filled). */ - public function test_get_level_returns_medium_at_50(): void { + public function test_get_level_returns_high_at_56(): void { $this->load_class(); - $this->assertSame( ODW_Quality::LEVEL_MEDIUM, ODW_Quality::get_level( 50 ) ); + $this->assertSame( ODW_Quality::LEVEL_HIGH, ODW_Quality::get_level( 56 ) ); } /** - * Score 79 is still LEVEL_MEDIUM (boundary check). + * Score 55 maps to LEVEL_SUFFICIENT (exactly required fields only). */ - public function test_get_level_returns_medium_at_79(): void { + public function test_get_level_returns_sufficient_at_55(): void { $this->load_class(); - $this->assertSame( ODW_Quality::LEVEL_MEDIUM, ODW_Quality::get_level( 79 ) ); + $this->assertSame( ODW_Quality::LEVEL_SUFFICIENT, ODW_Quality::get_level( 55 ) ); + } + + /** + * Score 99 maps to LEVEL_HIGH (boundary check below perfect). + */ + public function test_get_level_returns_high_at_99(): void { + $this->load_class(); + $this->assertSame( ODW_Quality::LEVEL_HIGH, ODW_Quality::get_level( 99 ) ); } /** @@ -107,14 +115,25 @@ public function test_get_level_label_high(): void { } /** - * LEVEL_MEDIUM maps to label "Mittel". + * LEVEL_PERFECT maps to label "Perfekt". + */ + public function test_get_level_label_perfect(): void { + $this->load_class(); + + \WP_Mock::userFunction( '__' )->andReturnArg( 0 ); + + $this->assertSame( 'Perfekt', ODW_Quality::get_level_label( ODW_Quality::LEVEL_PERFECT ) ); + } + + /** + * LEVEL_SUFFICIENT maps to label "Ausreichend". */ - public function test_get_level_label_medium(): void { + public function test_get_level_label_sufficient(): void { $this->load_class(); \WP_Mock::userFunction( '__' )->andReturnArg( 0 ); - $this->assertSame( 'Mittel', ODW_Quality::get_level_label( ODW_Quality::LEVEL_MEDIUM ) ); + $this->assertSame( 'Ausreichend', ODW_Quality::get_level_label( ODW_Quality::LEVEL_SUFFICIENT ) ); } /**