diff --git a/local/config/finna/SearchApiRecordFields.yaml.sample b/local/config/finna/SearchApiRecordFields.yaml.sample index d750920f317..76b848a4e76 100644 --- a/local/config/finna/SearchApiRecordFields.yaml.sample +++ b/local/config/finna/SearchApiRecordFields.yaml.sample @@ -130,6 +130,12 @@ corporateAuthors: type: array items: type: string +controlNumbers: + vufind.method: getControlNumbers + description: Record control numbers + type: array + items: + type: string dedupIds: vufind.method: "Formatter::getDedupIds" description: IDs of all records deduplicated with the current record @@ -337,6 +343,12 @@ lccn: type: array items: type: string +majorGenres: + vufind.method: getMajorGenres + description: Major genres + type: array + items: + type: string manufacturer: vufind.method: getManufacturer description: manufacturer @@ -347,6 +359,12 @@ measurements: type: array items: type: string +mediaTypes: + vufind.method: getMediaTypesAsStrings + description: Media types collected from links + type: array + items: + type: string newerTitles: vufind.method: getNewerTitles description: Successor titles @@ -476,6 +494,10 @@ rawData: vufind.method: "Formatter::getRawData" description: All data in the index fields type: string +recordFormat: + vufind.method: getRecordFormat + description: Record format + type: string recordLinks: vufind.method: "Formatter::getRecordLinks" description: Links to other related records @@ -618,6 +640,20 @@ urls: type: array items: $ref: '#/components/schemas/Url' +usageRights: + vufind.method: getUsageRights + description: Usage rights + (see [usage_rights_str_mv at https://www.kiwi.fi/display/Finna/Kenttien+mappaukset+eri+formaateista+Finnan+indeksiin] + (https://github.com/NatLibFi/RecordManager-Finna/blob/dev/mappings/usage_rights.map.sample)) + type: array + items: + type: string +usageRightsExtended: + vufind.method: getUsageRightsExt + description: Extended usage rights (see https://github.com/NatLibFi/RecordManager-Finna/blob/dev/mappings/usage_rights_ext.map.sample) + type: array + items: + $ref: '#/components/schemas/TranslatedField' year: vufind.method: getYear description: > diff --git a/local/config/finna/config.ini.sample b/local/config/finna/config.ini.sample index dc9d42edeb1..02d8d97a368 100644 --- a/local/config/finna/config.ini.sample +++ b/local/config/finna/config.ini.sample @@ -975,6 +975,7 @@ url = https://www.myendnoteweb.com/EndNoteWeb.html ;set_query['eod_books'] = "institution:kfu AND publishDate:[1911 TO 1911]" ;set_query['eod_ebooks'] = "format:eBook" ;vufind_api_format_fields = "id,authors,cleanIsbn,cleanIssn,formats,title" +;finna_api_format_fields = "id,authors,cleanIsbn,cleanIssn,formats,title,images,mediaTypes,controlNumbers,institutions,year,recordFormat,sectors,majorGenres,usageRights,usageRightsExtended,measurements,collections" ; Proxy Server is Optional. [Proxy] diff --git a/module/Finna/src/Finna/OAI/Server.php b/module/Finna/src/Finna/OAI/Server.php index 9a118329eb8..4d93bd4f1d1 100644 --- a/module/Finna/src/Finna/OAI/Server.php +++ b/module/Finna/src/Finna/OAI/Server.php @@ -29,6 +29,8 @@ namespace Finna\OAI; +use VuFind\RecordDriver\AbstractBase as AbstractRecordDriver; + /** * OAI Server class * @@ -42,6 +44,31 @@ */ class Server extends \VuFind\OAI\Server { + /** + * Finna specific api fields from [OAI] section + * + * @var array + */ + protected array $finnaApiFields = []; + + /** + * Finna metadata prefix + * + * @var string + */ + protected const OAI_FINNA_JSON = 'oai_finna_json'; + + /** + * Does the current configuration support the Finna metadata format (using + * the API's record formatter. + * + * @return bool + */ + protected function supportsFinnaMetadata() + { + return !empty($this->finnaApiFields) && null !== $this->recordFormatter; + } + /** * Initialize data about metadata formats. (This is called on demand and is * defined as a separate method to allow easy override by child classes). @@ -69,5 +96,126 @@ protected function initializeMetadataFormats() $this->metadataFormats['oai_qdc'] = [ 'schema' => $qdc, 'namespace' => 'urn:dc:qdc:container']; + if ($this->supportsFinnaMetadata()) { + $this->metadataFormats[self::OAI_FINNA_JSON] = [ + 'schema' => 'https://vufind.org/xsd/oai_vufind_json-1.0.xsd', + 'namespace' => 'http://vufind.org/oai_vufind_json-1.0', + ]; + } + } + + /** + * Get record as a metadata presentation + * + * @param AbstractRecordDriver $record A record driver object + * @param string $format Metadata format to obtain + * + * @return string|bool String on success or false if error occurs + */ + protected function getRecordAsXML(AbstractRecordDriver $record, string $format): string|false + { + if (self::OAI_FINNA_JSON === $format && $this->supportsFinnaMetadata()) { + return $this->getFinnaMetadata($record); + } + return parent::getRecordAsXml($record, $format); + } + + /** + * Respond to a ListMetadataFormats request. + * + * @return string|false + */ + protected function listMetadataFormats() + { + // If a specific ID was provided, try to load the related record; otherwise, + // set $record to false so we know it is a generic request. + if (isset($this->params['identifier'])) { + if (!($record = $this->loadRecord($this->params['identifier']))) { + return $this->showError('idDoesNotExist', 'Unknown Record'); + } + } else { + $record = false; + } + + // Loop through all available metadata formats and see if they apply in + // the current context (all apply if $record is false, since that + // means that no specific record ID was requested; otherwise, they only + // apply if the current record driver supports them): + $response = $this->createResponse(); + $xml = $response->addChild('ListMetadataFormats'); + foreach ($this->getMetadataFormats() as $prefix => $details) { + if ( + $record === false + || $record->getXML($prefix) !== false + || ('oai_vufind_json' === $prefix && $this->supportsVuFindMetadata()) + || (self::OAI_FINNA_JSON === $prefix && $this->supportsFinnaMetadata()) + ) { + $node = $xml->addChild('metadataFormat'); + $node->metadataPrefix = $prefix; + if (isset($details['schema'])) { + $node->schema = $details['schema']; + } + if (isset($details['namespace'])) { + $node->metadataNamespace = $details['namespace']; + } + } + } + + // Display the response: + return $response->asXML(); + } + + /** + * Load data from the OAI section of config.ini. (This is called by the + * constructor and is only a separate method to allow easy override by child + * classes). + * + * @param \Laminas\Config\Config $config VuFind configuration + * + * @return void + */ + protected function initializeSettings(\Laminas\Config\Config $config) + { + parent::initializeSettings($config); + // Initialize Finna API format fields: + $this->finnaApiFields = array_filter( + explode( + ',', + $config->OAI->finna_api_format_fields ?? '' + ) + ); + } + + /** + * Support method for attachNonDeleted() to build the Finna metadata for + * a record driver. + * + * @param object $record A record driver object + * + * @return string + */ + protected function getFinnaMetadata($record) + { + // Root node + $recordDoc = new \DOMDocument(); + $finnaFormat = $this->getMetadataFormats()[self::OAI_FINNA_JSON]; + $rootNode = $recordDoc->createElementNS($finnaFormat['namespace'], self::OAI_FINNA_JSON . ':record'); + $rootNode->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $rootNode->setAttribute('xsi:schemaLocation', $finnaFormat['namespace'] . ' ' . $finnaFormat['schema']); + $recordDoc->appendChild($rootNode); + + // Add oai_dc part + $oaiDc = new \DOMDocument(); + $oaiDc->loadXML($record->getXML('oai_dc', $this->baseHostURL, $this->recordLinkerHelper)); + $rootNode->appendChild($recordDoc->importNode($oaiDc->documentElement, true)); + + // Add Finna specific metadata + $records = $this->recordFormatter->format([$record], $this->finnaApiFields); + $metadataNode = $recordDoc->createElementNS($finnaFormat['namespace'], self::OAI_FINNA_JSON . ':metadata'); + $metadataNode->setAttribute('type', 'application/json'); + $metadataNode->appendChild($recordDoc->createCDATASection(json_encode($records[0]))); + $rootNode->appendChild($metadataNode); + + return $recordDoc->saveXML(); } } diff --git a/module/Finna/src/Finna/RecordDriver/Feature/SolrFinnaTrait.php b/module/Finna/src/Finna/RecordDriver/Feature/SolrFinnaTrait.php index 3da7ef98372..9246c246b1d 100644 --- a/module/Finna/src/Finna/RecordDriver/Feature/SolrFinnaTrait.php +++ b/module/Finna/src/Finna/RecordDriver/Feature/SolrFinnaTrait.php @@ -763,6 +763,49 @@ public function getFirstIndexed() return $this->fields['first_indexed'] ?? ''; } + /** + * Get an array containing media types as strings. + * + * @return array + */ + public function getMediaTypesAsStrings(): array + { + return array_map( + fn ($entry) => (string)$entry, + $this->fields['media_type_str_mv'] ?? [] + ); + } + + /** + * Get array containing ctrlnum. + * + * @return array + */ + public function getControlNumbers(): array + { + return $this->fields['ctrlnum'] ?? []; + } + + /** + * Get array containing major genres + * + * @return array + */ + public function getMajorGenres(): array + { + return $this->fields['major_genre_str_mv'] ?? []; + } + + /** + * Get array containing Usage rights extended + * + * @return array + */ + public function getUsageRightsExt(): array + { + return $this->fields['usage_rights_ext_str_mv'] ?? []; + } + /** * Is rating allowed. *