From 31c68743ab61ea05c86feaeda35d0b3d544da825 Mon Sep 17 00:00:00 2001 From: Demian Katz Date: Thu, 19 Dec 2024 10:51:09 -0500 Subject: [PATCH] Add ISSN support to BrowZine link handler. --- config/vufind/BrowZine.ini | 18 ++- languages/en.ini | 1 + .../src/VuFind/IdentifierLinker/BrowZine.php | 145 +++++++++++------- .../IdentifierLinker/BrowZineFactory.php | 16 +- .../VuFind/tests/fixtures/browzine/issn.json | 1 + .../IdentifierLinker/BrowZineTest.php | 115 +++++++++----- .../Backend/BrowZine/Connector.php | 4 +- 7 files changed, 197 insertions(+), 103 deletions(-) create mode 100644 module/VuFind/tests/fixtures/browzine/issn.json diff --git a/config/vufind/BrowZine.ini b/config/vufind/BrowZine.ini index dce09baa526..d78f06b4597 100644 --- a/config/vufind/BrowZine.ini +++ b/config/vufind/BrowZine.ini @@ -30,17 +30,18 @@ load_results_with_js = true ; full Full pagination alike to the one at the bottom of results ;top_paginator = simple -; This section controls the behavior of the BrowZine DOI handler; see also -; the [DOI] section of config.ini to activate the handler. -[DOI] +; This section controls the behavior of the BrowZine identifier link handler; see also +; the [IdentifierLinks] section of config.ini to activate the handler. +[IdentifierLinks] ; Can be set to "include" to include only the options listed in the "filter" ; setting, or to "exclude" to filter out the options listed in the "filter" ; setting. The default is "none," which performs no filtering. ;filterType = exclude ; This repeatable section can be used to filter based on link type; legal -; options are the keys defined in DOIServices section. +; options are the keys defined in DOIServices and ISSNServices sections. ; Note that this setting is available for convenience and compatibility with previous -; versions. You can get the same results by changing the DOIServices section. +; versions. You can get the same results by changing the DOIServices and/or +; ISSNServices sections. ;filter[] = "browzineWebLink" ;filter[] = "fullTextFile" @@ -49,7 +50,7 @@ load_results_with_js = true ; used. Default is false. local_icons = false -; This section defines the services to display from BrowZine and their display order. +; This section defines the DOI services to display from BrowZine and their display order. ; Each key is a service name (see https://thirdiron.atlassian.net/wiki/x/WIDqAw for ; more information) and value contains translation key, local icon and Third Iron's ; icon asset (optional) separated by a pipe character. @@ -58,6 +59,11 @@ browzineWebLink = "View Complete Issue|browzine-issue|https://assets.thirdiron.c fullTextFile = "PDF Full Text|browzine-pdf|https://assets.thirdiron.com/images/integrations/browzine-pdf-download-icon.svg" retractionNoticeUrl = "View Retraction Notice|browzine-retraction" +; This section defines the ISSN services to display from BrowZine and their display order. +; The format is the same as [DOIServices] above. +[ISSNServices] +browzineWebLink = "Browse Available Issues|browzine-issue|https://assets.thirdiron.com/images/integrations/browzine-open-book-icon.svg" + ; This section defines the view options available on standard search results. ; If only one view is required, set default_view under [General] above, and ; leave this section commented out. diff --git a/languages/en.ini b/languages/en.ini index ebce30e55ef..158ff3858b2 100644 --- a/languages/en.ini +++ b/languages/en.ini @@ -145,6 +145,7 @@ Breadcrumbs = "Breadcrumbs" Brief View = "Brief View" Browse = "Browse" Browse Alphabetically = "Browse Alphabetically" +Browse Available Issues = "Browse Available Issues" Browse for Authors = "Browse for Authors" Browse Home = "Browse Home" Browse the Catalog = "Browse the Catalog" diff --git a/module/VuFind/src/VuFind/IdentifierLinker/BrowZine.php b/module/VuFind/src/VuFind/IdentifierLinker/BrowZine.php index ca07cc29aed..54948ff1f34 100644 --- a/module/VuFind/src/VuFind/IdentifierLinker/BrowZine.php +++ b/module/VuFind/src/VuFind/IdentifierLinker/BrowZine.php @@ -29,8 +29,10 @@ namespace VuFind\IdentifierLinker; +use Exception; use VuFind\I18n\Translator\TranslatorAwareInterface; use VuFindSearch\Backend\BrowZine\Command\LookupDoiCommand; +use VuFindSearch\Backend\BrowZine\Command\LookupIssnsCommand; use VuFindSearch\Service; use function in_array; @@ -48,42 +50,20 @@ class BrowZine implements IdentifierLinkerInterface, TranslatorAwareInterface { use \VuFind\I18n\Translator\TranslatorAwareTrait; - /** - * Search service - * - * @var Service - */ - protected $searchService; - - /** - * Configuration options - * - * @var array - */ - protected $config; - - /** - * Configured DOI services - * - * @var array - */ - protected $doiServices; - /** * Constructor * * @param Service $searchService Search service * @param array $config Configuration settings * @param array $doiServices Configured DOI services + * @param array $issnServices Configured ISSN services */ public function __construct( - Service $searchService, - array $config = [], - array $doiServices = [] + protected Service $searchService, + protected array $config = [], + protected array $doiServices = [], + protected array $issnServices = [] ) { - $this->searchService = $searchService; - $this->config = $config; - $this->doiServices = $doiServices; } /** @@ -110,6 +90,32 @@ protected function arrayKeyAvailable(string $key, ?array $data): bool return true; } + /** + * Format a single service link. + * + * @param array $data Raw API response data + * @param string $serviceKey Key being extracted from response + * @param array $config Service-specific configuration settings + * + * @return array{link: string, label: string, data: array, localIcon: ?string, icon: ?string} + * @throws Exception + */ + protected function processServiceLink(array $data, string $serviceKey, array $config): array + { + $result = [ + 'link' => $data[$serviceKey], + 'label' => $this->translate($config['linkText']), + 'data' => $data, + ]; + $localIcons = !empty($this->config['local_icons']); + if (!$localIcons && !empty($config['icon'])) { + $result['icon'] = $config['icon']; + } else { + $result['localIcon'] = $config['localIcon']; + } + return $result; + } + /** * Given an array of identifier arrays, perform a lookup and return an associative array * of arrays, matching the keys of the input array. Each output array contains one or more @@ -124,33 +130,53 @@ protected function arrayKeyAvailable(string $key, ?array $data): bool public function getLinks(array $idArray): array { $response = []; - $localIcons = !empty($this->config['local_icons']); foreach ($idArray as $idKey => $ids) { - if (!isset($ids['doi'])) { - continue; - } - $command = new LookupDoiCommand('BrowZine', $ids['doi']); - $result = $this->searchService->invoke($command)->getResult(); - $data = $result['data'] ?? null; - foreach ($this->getDoiServices() as $serviceKey => $config) { - if ($this->arrayKeyAvailable($serviceKey, $data)) { - $result = [ - 'link' => $data[$serviceKey], - 'label' => $this->translate($config['linkText']), - 'data' => $data, - ]; - if (!$localIcons && !empty($config['icon'])) { - $result['icon'] = $config['icon']; - } else { - $result['localIcon'] = $config['localIcon']; + // If we have a DOI, that gets priority because it is more specific; otherwise we'll + // fall back and attempt the ISSN: + if (isset($ids['doi'])) { + $command = new LookupDoiCommand('BrowZine', $ids['doi']); + $result = $this->searchService->invoke($command)->getResult(); + $data = $result['data'] ?? null; + foreach ($this->getDoiServices() as $serviceKey => $config) { + if ($this->arrayKeyAvailable($serviceKey, $data)) { + $response[$idKey][] = $this->processServiceLink($data, $serviceKey, $config); + } + } + } elseif (isset($ids['issn'])) { + $command = new LookupIssnsCommand('BrowZine', $ids['issn']); + $result = $this->searchService->invoke($command)->getResult(); + $data = $result['data'][0] ?? null; + foreach ($this->getIssnServices() as $serviceKey => $config) { + if ($this->arrayKeyAvailable($serviceKey, $data)) { + $response[$idKey][] = $this->processServiceLink($data, $serviceKey, $config); } - $response[$idKey][] = $result; } } } return $response; } + /** + * Unpack service configuration into more useful array format. + * + * @param array $config Raw (pipe-delimited) configuration from BrowZine.ini + * + * @return array + */ + protected function unpackServiceConfig(array $config): array + { + $result = []; + foreach ($config as $key => $config) { + $parts = explode('|', $config); + $result[$key] = [ + 'linkText' => $parts[0], + 'localIcon' => $parts[1], + 'icon' => $parts[2] ?? null, + ]; + } + return $result; + } + /** * Get an array of DOI services and their configuration * @@ -173,15 +199,26 @@ protected function getDoiServices(): array ], ]; } - $result = []; - foreach ($this->doiServices as $key => $config) { - $parts = explode('|', $config); - $result[$key] = [ - 'linkText' => $parts[0], - 'localIcon' => $parts[1], - 'icon' => $parts[2] ?? null, + return $this->unpackServiceConfig($this->doiServices); + } + + /** + * Get an array of ISSN services and their configuration + * + * @return array + */ + protected function getIssnServices(): array + { + if (empty($this->issnServices)) { + $baseIconUrl = 'https://assets.thirdiron.com/images/integrations/'; + return [ + 'browzineWebLink' => [ + 'linkText' => 'Browse Available Issues', + 'localIcon' => 'browzine-issue', + 'icon' => $baseIconUrl . 'browzine-open-book-icon.svg', + ], ]; } - return $result; + return $this->unpackServiceConfig($this->issnServices); } } diff --git a/module/VuFind/src/VuFind/IdentifierLinker/BrowZineFactory.php b/module/VuFind/src/VuFind/IdentifierLinker/BrowZineFactory.php index 6b3f80d0bba..0fc551be1f1 100644 --- a/module/VuFind/src/VuFind/IdentifierLinker/BrowZineFactory.php +++ b/module/VuFind/src/VuFind/IdentifierLinker/BrowZineFactory.php @@ -70,12 +70,14 @@ public function __invoke( throw new \Exception('Unexpected options passed to factory.'); } $search = $container->get(\VuFindSearch\Service::class); - $fullConfig = $container->get(\VuFind\Config\PluginManager::class) - ->get('BrowZine'); - $config = isset($fullConfig->DOI) ? $fullConfig->DOI->toArray() : []; - $doiServices = isset($fullConfig->DOIServices) - ? $fullConfig->DOIServices->toArray() - : []; - return new $requestedName($search, $config, $doiServices); + $fullConfig = $container->get(\VuFind\Config\PluginManager::class)->get('BrowZine')->toArray(); + // DOI config section is supported as a fallback for back-compatibility: + $config = $fullConfig['IdentifierLinks'] ?? $fullConfig['DOI'] ?? []; + return new $requestedName( + $search, + $config, + $fullConfig['DOIServices'] ?? [], + $fullConfig['ISSNServices'] ?? [] + ); } } diff --git a/module/VuFind/tests/fixtures/browzine/issn.json b/module/VuFind/tests/fixtures/browzine/issn.json new file mode 100644 index 00000000000..fa7ab87c447 --- /dev/null +++ b/module/VuFind/tests/fixtures/browzine/issn.json @@ -0,0 +1 @@ +{"data":[{"id":5006,"type":"journals","title":"Biochemical Pharmacology","issn":"00062952","sjrValue":1.365,"coverImageUrl":"https://coverurl","browzineEnabled":true,"browzineWebLink":"https://weblink"}]} \ No newline at end of file diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/IdentifierLinker/BrowZineTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/IdentifierLinker/BrowZineTest.php index 31d1d73967f..321d66a5b2d 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/IdentifierLinker/BrowZineTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/IdentifierLinker/BrowZineTest.php @@ -29,6 +29,7 @@ namespace VuFindTest\IdentifierLinker; +use PHPUnit\Framework\MockObject\MockObject; use VuFind\IdentifierLinker\BrowZine; use VuFind\Search\BackendManager; use VuFindSearch\Backend\BrowZine\Connector; @@ -65,87 +66,131 @@ protected function getBackendManager(Connector $connector): BackendManager /** * Get a mock connector * - * @param string $doi DOI expected by connector - * @param array $response Response for connector to return + * @param array $ids IDs expected by connector + * @param array $response Response for connector to return * * @return Connector */ - protected function getMockConnector($doi, $response) + protected function getMockConnector(array $ids, array $response): MockObject&Connector { $connector = $this->createMock(Connector::class); - $connector->expects($this->once()) - ->method('lookupDoi') - ->with($this->equalTo($doi)) - ->willReturn($response); + if (isset($ids['doi'])) { + $connector->expects($this->once()) + ->method('lookupDoi') + ->with($this->equalTo($ids['doi'])) + ->willReturn($response); + } + if (isset($ids['issn'])) { + $connector->expects($this->once()) + ->method('lookupIssns') + ->with($this->equalTo($ids['issn'])) + ->willReturn($response); + } return $connector; } /** - * Test an API response. + * Data provider for testDOIApiSuccess() * - * @return void + * @return array[] */ - public function testApiSuccess() + public static function doiProvider(): array { - $rawData = $this->getJsonFixture('browzine/doi.json'); - $testData = [ - [ - 'config' => [], - 'response' => [ + return [ + 'unfiltered' => [ + [], + [ 0 => [ [ 'link' => 'https://weblink', 'label' => 'View Complete Issue', 'icon' => 'https://assets.thirdiron.com/images/integrations/browzine-open-book-icon.svg', - 'data' => $rawData['data'], ], [ 'link' => 'https://fulltext', 'label' => 'PDF Full Text', 'icon' => 'https://assets.thirdiron.com/images/integrations/browzine-pdf-download-icon.svg', - 'data' => $rawData['data'], ], ], ], ], - [ - 'config' => ['filterType' => 'exclude', 'filter' => ['browzineWebLink']], - 'response' => [ + 'exclude filter' => [ + ['filterType' => 'exclude', 'filter' => ['browzineWebLink']], + [ 0 => [ [ 'link' => 'https://fulltext', 'label' => 'PDF Full Text', 'icon' => 'https://assets.thirdiron.com/images/integrations/browzine-pdf-download-icon.svg', - 'data' => $rawData['data'], ], ], ], ], - [ - 'config' => ['filterType' => 'include', 'filter' => ['browzineWebLink']], - 'response' => [ + 'include filter' => [ + ['filterType' => 'include', 'filter' => ['browzineWebLink']], + [ 0 => [ [ 'link' => 'https://weblink', 'label' => 'View Complete Issue', 'icon' => 'https://assets.thirdiron.com/images/integrations/browzine-open-book-icon.svg', - 'data' => $rawData['data'], ], ], ], ], ]; + } - $doi = '10.1155/2020/8690540'; - $ids = [['doi' => $doi]]; - foreach ($testData as $data) { - $connector = $this->getMockConnector($doi, $rawData); - $ss = $this->getSearchService($this->getBackendManager($connector)); - $browzine = new BrowZine($ss, $data['config']); - $this->assertEquals( - $data['response'], - $browzine->getLinks($ids) - ); + /** + * Test a DOI API response. + * + * @param array $config Configuration + * @param array $expectedResponse Expected response + * + * @return void + * + * @dataProvider doiProvider + */ + public function testDOIApiSuccess(array $config, array $expectedResponse): void + { + $rawData = $this->getJsonFixture('browzine/doi.json'); + + $ids = [['doi' => '10.1155/2020/8690540']]; + $connector = $this->getMockConnector($ids[0], $rawData); + $ss = $this->getSearchService($this->getBackendManager($connector)); + $browzine = new BrowZine($ss, $config); + foreach ($expectedResponse[0] as & $current) { + $current['data'] = $rawData['data']; } + unset($current); + $this->assertEquals($expectedResponse, $browzine->getLinks($ids)); + } + + /** + * Test an ISSN API response. + * + * @return void + */ + public function testISSNApiSuccess(): void + { + $rawData = $this->getJsonFixture('browzine/issn.json'); + + $ids = [['issn' => '0006-2952']]; + $connector = $this->getMockConnector($ids[0], $rawData); + $ss = $this->getSearchService($this->getBackendManager($connector)); + $browzine = new BrowZine($ss, []); + $this->assertEquals( + [ + 0 => [ + [ + 'link' => 'https://weblink', + 'label' => 'Browse Available Issues', + 'data' => $rawData['data'][0], + 'icon' => 'https://assets.thirdiron.com/images/integrations/browzine-open-book-icon.svg', + ], + ], + ], + $browzine->getLinks($ids) + ); } } diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/BrowZine/Connector.php b/module/VuFindSearch/src/VuFindSearch/Backend/BrowZine/Connector.php index 1cc287a717d..a2efb37bc35 100644 --- a/module/VuFindSearch/src/VuFindSearch/Backend/BrowZine/Connector.php +++ b/module/VuFindSearch/src/VuFindSearch/Backend/BrowZine/Connector.php @@ -114,7 +114,9 @@ public function lookupDoi($doi, $includeJournal = false) */ public function lookupIssns($issns) { - return $this->request('search', ['issns' => implode(',', (array)$issns)]); + $processCallback = fn ($issn) => str_replace('-', '', $issn); + $processedIssns = array_map($processCallback, (array)$issns); + return $this->request('search', ['issns' => implode(',', $processedIssns)]); } /**