From 847dcd79e66fb17666d4e678efa425ec239884b9 Mon Sep 17 00:00:00 2001 From: Ere Maijala Date: Wed, 4 Feb 2026 14:15:01 +0200 Subject: [PATCH 1/6] Add initial support for qualified Dublin Core records. --- composer.json | 1 + composer.lock | 56 +++++- .../VuFind/RecordDriver/Feature/XmlTrait.php | 93 +++++++++ .../src/VuFind/RecordDriver/PluginManager.php | 2 + .../src/VuFind/RecordDriver/SolrDefault.php | 20 ++ .../RecordDriver/SolrDefaultFactory.php | 2 + .../src/VuFind/RecordDriver/SolrQdc.php | 154 +++++++++++++++ module/VuFind/tests/fixtures/qdc/qdc.xml | 7 + .../VuFindTest/RecordDriver/SolrQdcTest.php | 182 ++++++++++++++++++ 9 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 module/VuFind/src/VuFind/RecordDriver/Feature/XmlTrait.php create mode 100644 module/VuFind/src/VuFind/RecordDriver/SolrQdc.php create mode 100644 module/VuFind/tests/fixtures/qdc/qdc.xml create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/SolrQdcTest.php diff --git a/composer.json b/composer.json index c20e3ee9370..9439f8962c6 100644 --- a/composer.json +++ b/composer.json @@ -96,6 +96,7 @@ "matthiasmullie/minify": "1.3.75", "monolog/monolog": "^3.9", "mpdf/mpdf": "v8.2.6", + "natlibfi/finna-xml": "1.6.0", "paytrail/paytrail-php-sdk": "2.7.5", "pear/archive_tar": "^1.4", "phing/phing": "3.1.0", diff --git a/composer.lock b/composer.lock index f90e527b78d..ca8ae1ea9bb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a87c640baf64e1091768c0d37659e4ea", + "content-hash": "74d81eb28a9e22c439933bf0d049d919", "packages": [ { "name": "ahand/mobileesp", @@ -7328,6 +7328,60 @@ ], "time": "2025-08-01T08:46:24+00:00" }, + { + "name": "natlibfi/finna-xml", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/NatLibFi/finna-xml.git", + "reference": "1213fce9d4690029d17bbfde9f71bdc50c357db4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/NatLibFi/finna-xml/zipball/1213fce9d4690029d17bbfde9f71bdc50c357db4", + "reference": "1213fce9d4690029d17bbfde9f71bdc50c357db4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.89.2", + "phing/phing": "3.1.1", + "phpstan/phpstan": "2.1.32", + "phpunit/php-code-coverage": "^11", + "phpunit/phpunit": "11.5.50", + "rector/rector": "2.2.7", + "squizlabs/php_codesniffer": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "FinnaXml\\": "src/FinnaXml" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ere Maijala", + "email": "ere.maijala@helsinki.fi" + } + ], + "description": "Yet another XML parser, reader and writer for diverse records with and without namespaces.", + "keywords": [ + "dom", + "parser", + "xml" + ], + "support": { + "issues": "https://github.com/NatLibFi/finna-xml/issues", + "source": "https://github.com/NatLibFi/finna-xml/tree/v1.6.0" + }, + "time": "2026-02-03T13:15:21+00:00" + }, { "name": "nette/schema", "version": "v1.3.3", diff --git a/module/VuFind/src/VuFind/RecordDriver/Feature/XmlTrait.php b/module/VuFind/src/VuFind/RecordDriver/Feature/XmlTrait.php new file mode 100644 index 00000000000..2f37817eb9a --- /dev/null +++ b/module/VuFind/src/VuFind/RecordDriver/Feature/XmlTrait.php @@ -0,0 +1,93 @@ +. + * + * @category VuFind + * @package RecordDrivers + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki + */ + +namespace VuFind\RecordDriver\Feature; + +/** + * Functions for reading XML records. + * + * Assumption: raw XML data can be found in $this->fields['fullrecord']. + * + * @category VuFind + * @package RecordDrivers + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki + */ +trait XmlTrait +{ + /** + * The XML namespace. + * + * Note: this is a property instead of a constant to make use of it in strings cleaner. + * + * @var string + */ + protected $xmlNs = 'http://www.w3.org/2000/xmlns/'; + + /** + * XML class to use. + * + * @var string + */ + protected $xmlClass = \FinnaXml\XmlDoc::class; + + /** + * XML instance. Access only via getXmlReader() as this is initialized lazily. + */ + protected $lazyXmlReader = null; + + /** + * Get access to the XML object. + * + * @return object + */ + public function getXmlReader() + { + if (null === $this->lazyXmlReader) { + $this->lazyXmlReader = new $this->xmlClass(); + $this->lazyXmlReader->parse($this->fields['fullrecord']); + } + + return $this->lazyXmlReader; + } + + /** + * Get lang attribute from xml namespace with fallback to default namespace. + * + * @param array $node XmlDoc node + * + * @return ?string + */ + protected function getLangAttr(array $node): ?string + { + $xml = $this->getXmlReader(); + return $xml->attr($node, '{{$this->xmlNs}}lang') ?? $xml->attr($node, 'lang'); + } +} diff --git a/module/VuFind/src/VuFind/RecordDriver/PluginManager.php b/module/VuFind/src/VuFind/RecordDriver/PluginManager.php index e18fd93767b..665d98ee3bb 100644 --- a/module/VuFind/src/VuFind/RecordDriver/PluginManager.php +++ b/module/VuFind/src/VuFind/RecordDriver/PluginManager.php @@ -69,6 +69,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'solrmarc' => SolrMarc::class, 'solrmarcremote' => SolrMarcRemote::class, 'solroverdrive' => SolrOverdrive::class, + 'solrqdc' => SolrQdc::class, 'solrreserves' => SolrReserves::class, 'solrweb' => SolrWeb::class, 'summon' => Summon::class, @@ -109,6 +110,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager SolrMarc::class => SolrDefaultFactory::class, SolrMarcRemote::class => SolrDefaultFactory::class, SolrOverdrive::class => SolrOverdriveFactory::class, + SolrQdc::class => SolrDefaultFactory::class, SolrReserves::class => SolrDefaultWithoutSearchServiceFactory::class, SolrWeb::class => SolrWebFactory::class, Summon::class => SummonFactory::class, diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php b/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php index 645f680397a..a81ed9125b5 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php @@ -32,6 +32,7 @@ namespace VuFind\RecordDriver; +use VuFind\I18n\Locale\LocaleSettings; use VuFindSearch\Command\SearchCommand; use function count; @@ -129,6 +130,13 @@ class SolrDefault extends DefaultRecord implements */ protected $explainEnabled = false; + /** + * Locale settings, if available + * + * @var ?LocaleSettings + */ + protected ?LocaleSettings $localeSettings = null; + /** * Constructor * @@ -295,6 +303,18 @@ public function attachSearchService(\VuFindSearch\Service $service) $this->searchService = $service; } + /** + * Attach locale settings. + * + * @param LocaleSettings $localeSettings Locale settings + * + * @return void + */ + public function attachLocaleSettings(LocaleSettings $localeSettings): void + { + $this->localeSettings = $localeSettings; + } + /** * Get the number of child records belonging to this record * diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php b/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php index eafdc4c6f89..f10c318b22b 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php @@ -33,6 +33,7 @@ use Laminas\ServiceManager\Exception\ServiceNotFoundException; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; +use VuFind\I18n\Locale\LocaleSettings; /** * Factory for SolrDefault record drivers. @@ -66,6 +67,7 @@ public function __invoke( ) { $driver = parent::__invoke($container, $requestedName, $options); $driver->attachSearchService($container->get(\VuFindSearch\Service::class)); + $driver->attachLocaleSettings($container->get(LocaleSettings::class)); return $driver; } } diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php b/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php new file mode 100644 index 00000000000..f491e1dc517 --- /dev/null +++ b/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php @@ -0,0 +1,154 @@ +. + * + * @category VuFind + * @package RecordDrivers + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki + */ + +namespace VuFind\RecordDriver; + +use VuFind\RecordDriver\Feature\XmlTrait; + +/** + * Model for "Qualified Dublin Core" (using the DCMI Metadata Terms) records in Solr. + * + * @category VuFind + * @package RecordDrivers + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki + */ +class SolrQdc extends SolrDefault +{ + use XmlTrait; + + /** + * Dublin Core XML namespace + * + * Note: this is a property instead of a constant to make use of it in strings cleaner. + * + * @var string + */ + protected string $dcNs = 'http://purl.org/dc/elements/1.1/'; + + /** + * Dublin Core Terms vocabulary namespace + * + * Note: this is a property instead of a constant to make use of it in strings cleaner. + * + * @var string + */ + protected string $dcTermsNs = 'http://purl.org/dc/terms/'; + + /** + * Get the abstract notes. + * + * @return array + */ + public function getAbstractNotes(): array + { + $allAbstracts = []; + $localeAbstracts = []; + $xml = $this->getXmlReader(); + foreach ($this->getDcTermsElements('abstract') as $node) { + $abstract = $xml->value($node); + if ($lang = $this->getLangAttr($node)) { + $localeAbstracts[$lang][] = $abstract; + } + $allAbstracts[] = $abstract; + } + + return $this->getLocaleSpecificResults($localeAbstracts, $allAbstracts); + } + + /** + * Get elements from the terms or elements namespaces with fallback to default namespace. + * + * @param string $nodeName Node name + * @param bool $valuesOnly Return only values? + * + * @return array + */ + protected function getElements(string $nodeName, bool $valuesOnly = false): array + { + $xml = $this->getXmlReader(); + // Prefer elements in the terms namespace: + $method = $valuesOnly ? 'allValues' : 'all'; + return $this->getDcTermsElements($nodeName, $valuesOnly) + ?: $xml->$method(path: "{{$this->dcNs}}$nodeName"); + } + + /** + * Get elements from the DcTerms namespace with fallback to default namespace. + * + * @param string $nodeName Node name + * @param bool $valuesOnly Return only values? + * + * @return array + */ + protected function getDcTermsElements(string $nodeName, bool $valuesOnly = false): array + { + $xml = $this->getXmlReader(); + $method = $valuesOnly ? 'allValues' : 'all'; + return $xml->$method(path: "{{$this->dcTermsNs}}$nodeName") ?: $xml->$method(path: $nodeName); + } + + /** + * Pick correct results from locale-specific results with fallback to all results. + * + * @param array $localeResults Result(s) keyed by locale + * @param array|string $allResults All results + * + * @return array|string + */ + protected function getLocaleSpecificResults(array $localeResults, array|string $allResults): array|string + { + if (null === $this->localeSettings) { + return $allResults; + } + $userLocale = $this->localeSettings->getUserLocale(); + [$userLanguage] = explode('-', $userLocale); + if (null !== ($results = $localeResults[$userLocale] ?? $localeResults[$userLanguage] ?? null)) { + return $results; + } + // Check for matching language in locale-specific results: + foreach ($localeResults as $locale => $results) { + [$lang] = explode('-', $locale); + if ($lang === $userLanguage) { + return $results; + } + } + // Check for match in default and fallback locales: + $locales = [$this->localeSettings->getDefaultLocale(), ...$this->localeSettings->getFallbackLocales()]; + foreach ($locales as $locale) { + [$language] = explode('-', $locale); + if (null !== ($results = $localeResults[$locale] ?? $localeResults[$language] ?? null)) { + return $results; + } + } + // Could not find anything else, so return all: + return $allResults; + } +} diff --git a/module/VuFind/tests/fixtures/qdc/qdc.xml b/module/VuFind/tests/fixtures/qdc/qdc.xml new file mode 100644 index 00000000000..1fd029c83c3 --- /dev/null +++ b/module/VuFind/tests/fixtures/qdc/qdc.xml @@ -0,0 +1,7 @@ + + + Test + Abstrakti suomeksi. + Abstract in English. + Another abstract in English. + diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/SolrQdcTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/SolrQdcTest.php new file mode 100644 index 00000000000..50fe47b36b8 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/SolrQdcTest.php @@ -0,0 +1,182 @@ +. + * + * @category VuFind + * @package Tests + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ + +namespace VuFindTest\RecordDriver; + +use PHPUnit\Framework\Attributes\DataProvider; +use VuFind\I18n\Locale\LocaleSettings; +use VuFind\RecordDriver\SolrQdc; + +/** + * SolrQdc Record Driver Test Class + * + * @category VuFind + * @package Tests + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class SolrQdcTest extends \PHPUnit\Framework\TestCase +{ + use \VuFindTest\Feature\FixtureTrait; + + /** + * Data provider for testMethods. + * + * @return \Iterator + */ + public static function methodsProvider(): \Iterator + { + yield 'en' => [ + 'getAbstractNotes', + 'en', + 'en', + [], + [ + 'Abstract in English.', + 'Another abstract in English.', + ], + ]; + + yield 'fi' => [ + 'getAbstractNotes', + 'fi', + 'fi', + [], + [ + 'Abstrakti suomeksi.', + ], + ]; + + yield 'sv with en-gb as fallback' => [ + 'getAbstractNotes', + 'sv', + 'sv', + ['en-gb', 'fi'], + [ + 'Abstract in English.', + 'Another abstract in English.', + ], + ]; + + yield 'sv with en as default' => [ + 'getAbstractNotes', + 'sv', + 'en', + ['fi'], + [ + 'Abstract in English.', + 'Another abstract in English.', + ], + ]; + + yield 'sv with fi as default' => [ + 'getAbstractNotes', + 'sv', + 'fi', + ['en'], + [ + 'Abstrakti suomeksi.', + ], + ]; + + yield 'no fallback' => [ + 'getAbstractNotes', + 'sv', + 'sv', + [], + [ + 'Abstrakti suomeksi.', + 'Abstract in English.', + 'Another abstract in English.', + ], + ]; + } + + /** + * Test driver methods that return locale-specific data. + * + * @param string $method Method + * @param string $language Language + * @param string $defaultLanguage Default language + * @param array $fallbackLanguages Fallback languages + * @param mixed $expected Expected result + * + * @return void + */ + #[DataProvider('methodsProvider')] + public function testLocaleSpecificMethods( + string $method, + string $language, + string $defaultLanguage, + array $fallbackLanguages, + mixed $expected + ): void { + $driver = $this->getDriver($language, $defaultLanguage, $fallbackLanguages); + $this->assertSame( + $expected, + $driver->$method() + ); + } + + /** + * Get a record driver. + * + * @param string $language Language + * @param string $defaultLanguage Default language + * @param array $fallbackLanguages Fallback languages + * @param ?string $fixture Metadata fixture + * + * @return SolrQdc + */ + protected function getDriver( + string $language, + string $defaultLanguage, + array $fallbackLanguages, + ?string $fixture = 'qdc/qdc.xml' + ): SolrQdc { + $fixture = $this->getFixture($fixture); + $record = new SolrQdc(); + $record->setRawData(['id' => '12345', 'fullrecord' => $fixture]); + + $localeSettings = $this->createMock(LocaleSettings::class); + $localeSettings + ->method('getUserLocale') + ->willReturn($language); + $localeSettings + ->method('getDefaultLocale') + ->willReturn($defaultLanguage); + $localeSettings + ->method('getFallbackLocales') + ->willReturn($fallbackLanguages); + $record->attachLocaleSettings($localeSettings); + + return $record; + } +} From 350d8d9f7f8bf594186998c177b357fa9ac44efa Mon Sep 17 00:00:00 2001 From: Ere Maijala Date: Thu, 5 Feb 2026 10:56:05 +0200 Subject: [PATCH 2/6] Tweaks, --- .../VuFind/RecordDriver/Feature/XmlTrait.php | 14 ++++++++----- .../src/VuFind/RecordDriver/SolrQdc.php | 21 +++++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/module/VuFind/src/VuFind/RecordDriver/Feature/XmlTrait.php b/module/VuFind/src/VuFind/RecordDriver/Feature/XmlTrait.php index 2f37817eb9a..2c2c18ef078 100644 --- a/module/VuFind/src/VuFind/RecordDriver/Feature/XmlTrait.php +++ b/module/VuFind/src/VuFind/RecordDriver/Feature/XmlTrait.php @@ -29,6 +29,8 @@ namespace VuFind\RecordDriver\Feature; +use FinnaXml\XmlDoc; + /** * Functions for reading XML records. * @@ -49,26 +51,28 @@ trait XmlTrait * * @var string */ - protected $xmlNs = 'http://www.w3.org/2000/xmlns/'; + protected string $xmlNs = 'http://www.w3.org/2000/xmlns/'; /** * XML class to use. * * @var string */ - protected $xmlClass = \FinnaXml\XmlDoc::class; + protected string $xmlClass = \FinnaXml\XmlDoc::class; /** * XML instance. Access only via getXmlReader() as this is initialized lazily. + * + * @var XmlDoc */ - protected $lazyXmlReader = null; + protected ?XmlDoc $lazyXmlReader = null; /** * Get access to the XML object. * - * @return object + * @return XmlDoc */ - public function getXmlReader() + public function getXmlReader(): XmlDoc { if (null === $this->lazyXmlReader) { $this->lazyXmlReader = new $this->xmlClass(); diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php b/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php index f491e1dc517..c2bbc8fb92b 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php @@ -129,11 +129,11 @@ protected function getLocaleSpecificResults(array $localeResults, array|string $ return $allResults; } $userLocale = $this->localeSettings->getUserLocale(); - [$userLanguage] = explode('-', $userLocale); - if (null !== ($results = $localeResults[$userLocale] ?? $localeResults[$userLanguage] ?? null)) { + if (null !== ($results = $this->getBestLocaleMatch($userLocale, $localeResults))) { return $results; } // Check for matching language in locale-specific results: + [$userLanguage] = explode('-', $userLocale); foreach ($localeResults as $locale => $results) { [$lang] = explode('-', $locale); if ($lang === $userLanguage) { @@ -143,12 +143,25 @@ protected function getLocaleSpecificResults(array $localeResults, array|string $ // Check for match in default and fallback locales: $locales = [$this->localeSettings->getDefaultLocale(), ...$this->localeSettings->getFallbackLocales()]; foreach ($locales as $locale) { - [$language] = explode('-', $locale); - if (null !== ($results = $localeResults[$locale] ?? $localeResults[$language] ?? null)) { + if (null !== ($results = $this->getBestLocaleMatch($locale, $localeResults))) { return $results; } } // Could not find anything else, so return all: return $allResults; } + + /** + * Pick best match for a locale from the results. + * + * @param string $locale Locale + * @param array $localeResults Result(s) keyed by locale + * + * @return mixed + */ + protected function getBestLocaleMatch(string $locale, array $localeResults): mixed + { + [$language] = explode('-', $locale); + return $localeResults[$locale] ?? $localeResults[$language] ?? null; + } } From d00761269cd3fca25710d913859c315408200d36 Mon Sep 17 00:00:00 2001 From: Ere Maijala Date: Thu, 5 Feb 2026 12:17:49 +0200 Subject: [PATCH 3/6] Move locale methods to a trait. --- .../Feature/LocaleSupportTrait.php | 92 +++++++++++++++++++ .../src/VuFind/RecordDriver/SolrQdc.php | 52 +---------- 2 files changed, 94 insertions(+), 50 deletions(-) create mode 100644 module/VuFind/src/VuFind/RecordDriver/Feature/LocaleSupportTrait.php diff --git a/module/VuFind/src/VuFind/RecordDriver/Feature/LocaleSupportTrait.php b/module/VuFind/src/VuFind/RecordDriver/Feature/LocaleSupportTrait.php new file mode 100644 index 00000000000..b8d931b3f42 --- /dev/null +++ b/module/VuFind/src/VuFind/RecordDriver/Feature/LocaleSupportTrait.php @@ -0,0 +1,92 @@ +. + * + * @category VuFind + * @package RecordDrivers + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki + */ + +namespace VuFind\RecordDriver\Feature; + +/** + * Functions for locale-specific processing in record drivers. + * + * @category VuFind + * @package RecordDrivers + * @author Ere Maijala + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki + */ +trait LocaleSupportTrait +{ + /** + * Pick correct results from locale-specific results with fallback to all results. + * + * @param array $localeResults Result(s) keyed by locale + * @param array|string $allResults All results + * + * @return array|string + */ + protected function getLocaleSpecificResults(array $localeResults, array|string $allResults): array|string + { + if (null === $this->localeSettings) { + return $allResults; + } + $userLocale = $this->localeSettings->getUserLocale(); + if (null !== ($results = $this->getBestLocaleMatch($userLocale, $localeResults))) { + return $results; + } + // Check for matching language in locale-specific results: + [$userLanguage] = explode('-', $userLocale); + foreach ($localeResults as $locale => $results) { + [$lang] = explode('-', $locale); + if ($lang === $userLanguage) { + return $results; + } + } + // Check for match in default and fallback locales: + $locales = [$this->localeSettings->getDefaultLocale(), ...$this->localeSettings->getFallbackLocales()]; + foreach ($locales as $locale) { + if (null !== ($results = $this->getBestLocaleMatch($locale, $localeResults))) { + return $results; + } + } + // Could not find anything else, so return all: + return $allResults; + } + + /** + * Pick best match for a locale from the results. + * + * @param string $locale Locale + * @param array $localeResults Result(s) keyed by locale + * + * @return mixed + */ + protected function getBestLocaleMatch(string $locale, array $localeResults): mixed + { + [$language] = explode('-', $locale); + return $localeResults[$locale] ?? $localeResults[$language] ?? null; + } +} diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php b/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php index c2bbc8fb92b..2015521c95c 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php @@ -29,6 +29,7 @@ namespace VuFind\RecordDriver; +use VuFind\RecordDriver\Feature\LocaleSupportTrait; use VuFind\RecordDriver\Feature\XmlTrait; /** @@ -42,6 +43,7 @@ */ class SolrQdc extends SolrDefault { + use LocaleSupportTrait; use XmlTrait; /** @@ -114,54 +116,4 @@ protected function getDcTermsElements(string $nodeName, bool $valuesOnly = false $method = $valuesOnly ? 'allValues' : 'all'; return $xml->$method(path: "{{$this->dcTermsNs}}$nodeName") ?: $xml->$method(path: $nodeName); } - - /** - * Pick correct results from locale-specific results with fallback to all results. - * - * @param array $localeResults Result(s) keyed by locale - * @param array|string $allResults All results - * - * @return array|string - */ - protected function getLocaleSpecificResults(array $localeResults, array|string $allResults): array|string - { - if (null === $this->localeSettings) { - return $allResults; - } - $userLocale = $this->localeSettings->getUserLocale(); - if (null !== ($results = $this->getBestLocaleMatch($userLocale, $localeResults))) { - return $results; - } - // Check for matching language in locale-specific results: - [$userLanguage] = explode('-', $userLocale); - foreach ($localeResults as $locale => $results) { - [$lang] = explode('-', $locale); - if ($lang === $userLanguage) { - return $results; - } - } - // Check for match in default and fallback locales: - $locales = [$this->localeSettings->getDefaultLocale(), ...$this->localeSettings->getFallbackLocales()]; - foreach ($locales as $locale) { - if (null !== ($results = $this->getBestLocaleMatch($locale, $localeResults))) { - return $results; - } - } - // Could not find anything else, so return all: - return $allResults; - } - - /** - * Pick best match for a locale from the results. - * - * @param string $locale Locale - * @param array $localeResults Result(s) keyed by locale - * - * @return mixed - */ - protected function getBestLocaleMatch(string $locale, array $localeResults): mixed - { - [$language] = explode('-', $locale); - return $localeResults[$locale] ?? $localeResults[$language] ?? null; - } } From 9f863557297b84aaa5823cdb18f09af540398ba2 Mon Sep 17 00:00:00 2001 From: Ere Maijala Date: Thu, 5 Feb 2026 14:46:19 +0200 Subject: [PATCH 4/6] Switch to LocaleSettingsAwareInterface/Trait. --- .../src/VuFind/RecordDriver/SolrDefault.php | 19 ------------------- .../RecordDriver/SolrDefaultFactory.php | 1 - .../src/VuFind/RecordDriver/SolrQdc.php | 5 ++++- .../VuFindTest/RecordDriver/SolrQdcTest.php | 2 +- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php b/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php index a81ed9125b5..b918ad1a95f 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php @@ -130,13 +130,6 @@ class SolrDefault extends DefaultRecord implements */ protected $explainEnabled = false; - /** - * Locale settings, if available - * - * @var ?LocaleSettings - */ - protected ?LocaleSettings $localeSettings = null; - /** * Constructor * @@ -303,18 +296,6 @@ public function attachSearchService(\VuFindSearch\Service $service) $this->searchService = $service; } - /** - * Attach locale settings. - * - * @param LocaleSettings $localeSettings Locale settings - * - * @return void - */ - public function attachLocaleSettings(LocaleSettings $localeSettings): void - { - $this->localeSettings = $localeSettings; - } - /** * Get the number of child records belonging to this record * diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php b/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php index f10c318b22b..80dc1ff6b40 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php @@ -67,7 +67,6 @@ public function __invoke( ) { $driver = parent::__invoke($container, $requestedName, $options); $driver->attachSearchService($container->get(\VuFindSearch\Service::class)); - $driver->attachLocaleSettings($container->get(LocaleSettings::class)); return $driver; } } diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php b/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php index 2015521c95c..233f8a2b8e7 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrQdc.php @@ -29,6 +29,8 @@ namespace VuFind\RecordDriver; +use VuFind\I18n\Locale\LocaleSettingsAwareInterface; +use VuFind\I18n\Locale\LocaleSettingsAwareTrait; use VuFind\RecordDriver\Feature\LocaleSupportTrait; use VuFind\RecordDriver\Feature\XmlTrait; @@ -41,8 +43,9 @@ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki */ -class SolrQdc extends SolrDefault +class SolrQdc extends SolrDefault implements LocaleSettingsAwareInterface { + use LocaleSettingsAwareTrait; use LocaleSupportTrait; use XmlTrait; diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/SolrQdcTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/SolrQdcTest.php index 50fe47b36b8..d664e53f2cf 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/SolrQdcTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/SolrQdcTest.php @@ -175,7 +175,7 @@ protected function getDriver( $localeSettings ->method('getFallbackLocales') ->willReturn($fallbackLanguages); - $record->attachLocaleSettings($localeSettings); + $record->setLocaleSettings($localeSettings); return $record; } From 99022a322c4a59a504c0e9cee109e89f3ed0ab8a Mon Sep 17 00:00:00 2001 From: Ere Maijala Date: Thu, 5 Feb 2026 14:51:17 +0200 Subject: [PATCH 5/6] Tweak locale matching. --- .../Feature/LocaleSupportTrait.php | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/module/VuFind/src/VuFind/RecordDriver/Feature/LocaleSupportTrait.php b/module/VuFind/src/VuFind/RecordDriver/Feature/LocaleSupportTrait.php index b8d931b3f42..88e16b63aaf 100644 --- a/module/VuFind/src/VuFind/RecordDriver/Feature/LocaleSupportTrait.php +++ b/module/VuFind/src/VuFind/RecordDriver/Feature/LocaleSupportTrait.php @@ -57,14 +57,6 @@ protected function getLocaleSpecificResults(array $localeResults, array|string $ if (null !== ($results = $this->getBestLocaleMatch($userLocale, $localeResults))) { return $results; } - // Check for matching language in locale-specific results: - [$userLanguage] = explode('-', $userLocale); - foreach ($localeResults as $locale => $results) { - [$lang] = explode('-', $locale); - if ($lang === $userLanguage) { - return $results; - } - } // Check for match in default and fallback locales: $locales = [$this->localeSettings->getDefaultLocale(), ...$this->localeSettings->getFallbackLocales()]; foreach ($locales as $locale) { @@ -87,6 +79,19 @@ protected function getLocaleSpecificResults(array $localeResults, array|string $ protected function getBestLocaleMatch(string $locale, array $localeResults): mixed { [$language] = explode('-', $locale); - return $localeResults[$locale] ?? $localeResults[$language] ?? null; + if ($results = $localeResults[$locale] ?? $localeResults[$language] ?? null) { + return $results; + } + + // Check for matching language in locale-specific results: + [$language] = explode('-', $locale); + foreach ($localeResults as $resultLocale => $results) { + [$resultLanguage] = explode('-', $resultLocale); + if ($resultLanguage === $language) { + return $results; + } + } + + return null; } } From 76dad0e6ed1c3f86d6f13b6307c9d0452fbaa14e Mon Sep 17 00:00:00 2001 From: Ere Maijala Date: Thu, 5 Feb 2026 14:53:20 +0200 Subject: [PATCH 6/6] Fix code style. --- module/VuFind/src/VuFind/RecordDriver/SolrDefault.php | 1 - module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php | 1 - 2 files changed, 2 deletions(-) diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php b/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php index b918ad1a95f..645f680397a 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrDefault.php @@ -32,7 +32,6 @@ namespace VuFind\RecordDriver; -use VuFind\I18n\Locale\LocaleSettings; use VuFindSearch\Command\SearchCommand; use function count; diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php b/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php index 80dc1ff6b40..eafdc4c6f89 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrDefaultFactory.php @@ -33,7 +33,6 @@ use Laminas\ServiceManager\Exception\ServiceNotFoundException; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; -use VuFind\I18n\Locale\LocaleSettings; /** * Factory for SolrDefault record drivers.