Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize DOI linker system to support other identifier types #3918

Open
wants to merge 41 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
491d424
Work in progress: DOI to generic ID system.
demiankatz Sep 6, 2024
20e57fd
Merge branch 'dev' into doi-to-id-refactor
demiankatz Sep 9, 2024
a0f9e42
Progress.
demiankatz Sep 9, 2024
e988638
Style fix.
demiankatz Sep 9, 2024
23427ad
Finish proof-of-concept demo.
demiankatz Sep 9, 2024
45c885c
Improve naming.
demiankatz Sep 9, 2024
92261cb
Style fix.
demiankatz Sep 9, 2024
cef12d7
Fix end-of-file line break.
demiankatz Sep 9, 2024
b5c51c3
Merge branch 'dev' into doi-to-id-refactor
demiankatz Dec 16, 2024
032027f
Fix typo.
demiankatz Dec 16, 2024
9359623
Remove unused param.
demiankatz Dec 16, 2024
de703b0
Fix types and ID determination.
demiankatz Dec 16, 2024
7d78911
Merge branch 'dev' into doi-to-id-refactor
demiankatz Dec 16, 2024
83463bd
Switch from GET to POST.
demiankatz Dec 16, 2024
5be1f9c
Rename [DOI] config section.
demiankatz Dec 16, 2024
47d5f10
Eliminate object-based configs from view helper.
demiankatz Dec 16, 2024
644549b
Make ID types configurable.
demiankatz Dec 16, 2024
1de9bfd
Rename doi.js.
demiankatz Dec 16, 2024
07a4911
Rename module/function.
demiankatz Dec 16, 2024
94d9faf
Merge branch 'dev' into doi-to-id-refactor
demiankatz Dec 18, 2024
2b71a0a
Rename DOI-specific CSS classes; fix innerHtml logic.
demiankatz Dec 18, 2024
7293036
Rename template.
demiankatz Dec 18, 2024
9926c7d
Rename AJAX handler.
demiankatz Dec 18, 2024
c2aaf3c
Rename view helper.
demiankatz Dec 18, 2024
6ead6ba
Rename plugins.
demiankatz Dec 18, 2024
58f38a2
Remove redundant file.
demiankatz Dec 18, 2024
8299fec
Fix test.
demiankatz Dec 18, 2024
3cd191e
Make interface consistent; fix tests.
demiankatz Dec 18, 2024
3f8e4ad
Fix last test.
demiankatz Dec 18, 2024
9b4e2b9
Fix broken mink test.
demiankatz Dec 18, 2024
43d8cfd
Update DOI-related language.
demiankatz Dec 18, 2024
824035e
Merge branch 'dev' into doi-to-id-refactor
demiankatz Dec 19, 2024
e1eddb6
Add method comments.
demiankatz Dec 19, 2024
7ba08e1
JSDoc style fixes.
demiankatz Dec 19, 2024
8134253
Refactor link rendering to template.
demiankatz Dec 19, 2024
03c1054
Style: add line breaks at end of file.
demiankatz Dec 19, 2024
31c6874
Add ISSN support to BrowZine link handler.
demiankatz Dec 19, 2024
5118e63
Remove unnecessary include.
demiankatz Dec 19, 2024
576be35
Merge branch 'dev' into doi-to-id-refactor
demiankatz Dec 20, 2024
d3662ea
Fix comments; incorporate review feedback.
demiankatz Dec 20, 2024
5beb8ac
Add more types.
demiankatz Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.jsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ module.exports = {
"themes/bootstrap5/js/openurl.js",
"themes/bootstrap5/js/ill.js",
"themes/bootstrap5/js/record.js",
"themes/bootstrap5/js/doi.js",
"themes/bootstrap5/js/keep_alive.js",
"themes/bootstrap5/js/embedGBS.js",
"themes/bootstrap5/js/lib/ajax_request_queue.js",
Expand Down
18 changes: 12 additions & 6 deletions config/vufind/BrowZine.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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.
Expand All @@ -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.
Expand Down
20 changes: 13 additions & 7 deletions config/vufind/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1427,10 +1427,10 @@ url = "https://api.booksite.com"
;[DPLA]
;apiKey = http://dp.la/info/developers/codex/policies/#get-a-key

; These settings affect dynamic DOI-based link inclusion; this can provide links
; to full text or contextual information.
[DOI]
; This setting controls whether or not DOI-based links are enabled, and which
; These settings affect dynamic identifier-based link inclusion; this can provide links
; to full text or contextual information based on identifiers like DOI, ISBN or ISSN.
[IdentifierLinks]
; This setting controls whether or not ID-based links are enabled, and which
; API is used to fetch the data. Currently supported options: BrowZine (requires
; credentials to be configured in BrowZine.ini), Demo (which generates fake data
; to simulate use of a real service, for testing), Unpaywall or false (to disable).
Expand All @@ -1439,7 +1439,7 @@ url = "https://api.booksite.com"
;resolver = BrowZine

; If you use multiple values in the resolver setting above, you can determine how the
; software should behave when multiple resolvers return results for the same DOI.
; software should behave when multiple resolvers return results for the same ID.
; You can choose "first" (only return results from the first matching resolver --
; the default behavior) or "merge" (merge together all results and show them all).
;multi_resolver_mode = first
Expand All @@ -1448,12 +1448,18 @@ url = "https://api.booksite.com"
; Unpaywall needs an email adress, see https://unpaywall.org/products/api
;unpaywall_email = "[email protected]"

; The following settings control where DOI-based links are displayed:
; The following settings control where ID-based links are displayed:
show_in_results = true ; include in search results
show_in_record = false ; include in core record metadata
show_in_holdings = false ; include in holdings tab of record view

; Whether to load any third-party icons for the DOI services via VuFind's cover
; This setting controls which types of identifiers are used to generate links; if
; the setting is omitted, all supported types (DOI, ISBN and ISSN) will be used:
supportedIdentifiers[] = doi
supportedIdentifiers[] = isbn
supportedIdentifiers[] = issn

; Whether to load any third-party icons for the ID services via VuFind's cover
; loader proxy to avoid any privacy implications. Ensure that the necessary domains
; are allowed in Content/coverproxyCache[] setting. Default is false.
proxy_icons = true
Expand Down
1 change: 1 addition & 0 deletions languages/en.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions module/VuFind/config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,6 @@
'VuFind\Db\Service\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
'VuFind\Db\Table\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
'VuFind\DigitalContent\OverdriveConnector' => 'VuFind\DigitalContent\OverdriveConnectorFactory',
'VuFind\DoiLinker\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
'VuFind\Escaper\Escaper' => 'VuFind\Escaper\EscaperFactory',
'VuFind\Export' => 'VuFind\ExportFactory',
'VuFind\Favorites\FavoritesService' => 'VuFind\Favorites\FavoritesServiceFactory',
Expand All @@ -472,6 +471,7 @@
'VuFind\Http\PhpEnvironment\Request' => 'Laminas\ServiceManager\Factory\InvokableFactory',
'VuFind\I18n\Locale\LocaleSettings' => 'VuFind\Service\ServiceWithConfigIniFactory',
'VuFind\I18n\Sorter' => 'VuFind\I18n\SorterFactory',
'VuFind\IdentifierLinker\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
'VuFind\ILS\Connection' => 'VuFind\ILS\ConnectionFactory',
'VuFind\ILS\Driver\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory',
'VuFind\ILS\Logic\AvailabilityStatusManager' => 'Laminas\ServiceManager\Factory\InvokableFactory',
Expand Down Expand Up @@ -694,12 +694,12 @@
'db_row' => [ /* see VuFind\Db\Row\PluginManager for defaults */ ],
'db_service' => [ /* see VuFind\Db\Service\PluginManager for defaults */ ],
'db_table' => [ /* see VuFind\Db\Table\PluginManager for defaults */ ],
'doilinker' => [ /* see VuFind\DoiLinker\PluginManager for defaults */ ],
'form_handler' => [ /* see VuFind\Form\Handler\PluginManager for defaults */],
'hierarchy_driver' => [ /* see VuFind\Hierarchy\Driver\PluginManager for defaults */ ],
'hierarchy_treedataformatter' => [ /* see VuFind\Hierarchy\TreeDataFormatter\PluginManager for defaults */ ],
'hierarchy_treedatasource' => [ /* see VuFind\Hierarchy\TreeDataSource\PluginManager for defaults */ ],
'hierarchy_treerenderer' => [ /* see VuFind\Hierarchy\TreeRenderer\PluginManager for defaults */ ],
'identifierlinker' => [ /* see VuFind\IdentifierLinker\PluginManager for defaults */ ],
'ils_driver' => [ /* See VuFind\ILS\Driver\PluginManager for defaults */ ],
'metadatavocabulary' => [ /* See VuFind\MetadataVocabulary\PluginManager for defaults */],
'navigation' => [ /* See VuFind\Navigation\PluginManager for defaults */],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* AJAX handler to look up DOI data.
* AJAX handler to look up identifier-based link data.
*
* PHP version 8
*
Expand Down Expand Up @@ -31,37 +31,30 @@

use Laminas\Mvc\Controller\Plugin\Params;
use Laminas\View\Renderer\RendererInterface;
use VuFind\DoiLinker\PluginManager;
use VuFind\IdentifierLinker\PluginManager;

use function count;

/**
* AJAX handler to look up DOI data.
* AJAX handler to look up identifier-based link data.
*
* @category VuFind
* @package AJAX
* @author Demian Katz <[email protected]>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development Wiki
*/
class DoiLookup extends AbstractBase
class IdentifierLinksLookup extends AbstractBase
{
/**
* DOI Linker Plugin Manager
*
* @var PluginManager
*/
protected $pluginManager;

/**
* DOI resolver configuration value, exploded into an array of options
* Identifier link resolver configuration value, exploded into an array of options
*
* @var string[]
*/
protected $resolvers;

/**
* Behavior to use when multiple resolvers find results for the same DOI (may
* Behavior to use when multiple resolvers find results for the same identifier set (may
* be 'first' -- use first match, or 'merge' -- use all results)
*
* @var string
Expand All @@ -82,36 +75,29 @@ class DoiLookup extends AbstractBase
*/
protected $openInNewWindow = false;

/**
* View renderer
*
* @var RendererInterface
*/
protected $viewRenderer = null;

/**
* Constructor
*
* @param PluginManager $pluginManager DOI Linker Plugin Manager
* @param PluginManager $pluginManager Identifier Linker Plugin Manager
* @param RendererInterface $viewRenderer View renderer
* @param array $config Main configuration
*/
public function __construct(
PluginManager $pluginManager,
RendererInterface $viewRenderer,
protected PluginManager $pluginManager,
protected RendererInterface $viewRenderer,
array $config
) {
$this->pluginManager = $pluginManager;
// DOI config section is supported as a fallback for back-compatibility:
$idConfig = $config['IdentifierLinks'] ?? $config['DOI'] ?? [];
$this->resolvers
= array_map('trim', explode(',', $config['DOI']['resolver'] ?? ''));
= array_map('trim', explode(',', $idConfig['resolver'] ?? ''));
// Behavior to use when multiple resolvers to find results for the same
// DOI (may be 'first' -- use first match, or 'merge' -- use all
// identifier set (may be 'first' -- use first match, or 'merge' -- use all
// results):
$this->multiMode
= trim(strtolower($config['DOI']['multi_resolver_mode'] ?? 'first'));
$this->proxyIcons = !empty($config['DOI']['proxy_icons']);
$this->openInNewWindow = !empty($config['DOI']['new_window']);
$this->viewRenderer = $viewRenderer;
= trim(strtolower($idConfig['multi_resolver_mode'] ?? 'first'));
$this->proxyIcons = !empty($idConfig['proxy_icons']);
$this->openInNewWindow = !empty($idConfig['new_window']);
}

/**
Expand All @@ -123,67 +109,77 @@ public function __construct(
*/
public function handleRequest(Params $params)
{
$response = [];
$dois = (array)$params->fromQuery('doi', []);
$gatheredData = [];
$ids = json_decode($params->getController()->getRequest()->getContent(), true);
foreach ($this->resolvers as $resolver) {
if ($this->pluginManager->has($resolver)) {
$next = $this->pluginManager->get($resolver)->getLinks($dois);
$next = $this->pluginManager->get($resolver)->getLinks($ids);
$next = $this->processIconLinks($next);
foreach ($next as $doi => $data) {
foreach ($data as &$current) {
$current['newWindow'] = $this->openInNewWindow;
}
unset($current);
if (!isset($response[$doi])) {
$response[$doi] = $data;
foreach ($next as $key => $data) {
if (!isset($gatheredData[$key])) {
$gatheredData[$key] = $data;
} elseif ($this->multiMode == 'merge') {
$response[$doi] = array_merge($response[$doi], $data);
$gatheredData[$key] = array_merge($gatheredData[$key], $data);
}
}
// If all DOIs have been found and we're not in merge mode, we
// If all keys have been found and we're not in merge mode, we
// can short circuit out of here.
if (
$this->multiMode !== 'merge'
&& count(array_diff($dois, array_keys($response))) == 0
&& count(array_diff(array_keys($ids), array_keys($gatheredData))) == 0
) {
break;
}
}
}
$response = array_map([$this, 'renderResponseChunk'], $gatheredData);
return $this->formatResponse($response);
}

/**
* Proxify external DOI icon links and render local icons
* Render the links for a single record.
*
* @param array $data Data to render
*
* @return string
*/
protected function renderResponseChunk(array $data): string
{
$newWindow = $this->openInNewWindow;
return $this->viewRenderer->render('ajax/identifierLinks.phtml', compact('data', 'newWindow'));
}

/**
* Proxify external icon links and render local icons
*
* @param array $dois DOIs
* @param array $data Identifier plugin data
*
* @return array
*/
protected function processIconLinks(array $dois): array
protected function processIconLinks(array $data): array
{
$serverHelper = $this->viewRenderer->plugin('serverurl');
$urlHelper = $this->viewRenderer->plugin('url');
$iconHelper = $this->viewRenderer->plugin('icon');

foreach ($dois as &$doiLinks) {
foreach ($doiLinks as &$doi) {
if ($this->proxyIcons && !empty($doi['icon'])) {
$doi['icon'] = $serverHelper(
foreach ($data as &$links) {
foreach ($links as &$link) {
if ($this->proxyIcons && !empty($link['icon'])) {
$link['icon'] = $serverHelper(
$urlHelper(
'cover-show',
[],
['query' => ['proxy' => $doi['icon']]]
['query' => ['proxy' => $link['icon']]]
)
);
}
if (!empty($doi['localIcon'])) {
$doi['localIcon'] = $iconHelper($doi['localIcon']);
if (!empty($link['localIcon'])) {
$link['localIcon'] = $iconHelper($link['localIcon'], 'icon-link__icon');
}
}
unset($doi);
unset($link);
}
unset($doiLinks);
return $dois;
unset($links);
return $data;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Factory for DoiLookup AJAX handler.
* Factory for IdentifierLinksLookup AJAX handler.
*
* PHP version 8
*
Expand Down Expand Up @@ -35,15 +35,15 @@
use Psr\Container\ContainerInterface;

/**
* Factory for DoiLookup AJAX handler.
* Factory for IdentifierLinksLookup AJAX handler.
*
* @category VuFind
* @package AJAX
* @author Demian Katz <[email protected]>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development Wiki
*/
class DoiLookupFactory implements \Laminas\ServiceManager\Factory\FactoryInterface
class IdentifierLinksLookupFactory implements \Laminas\ServiceManager\Factory\FactoryInterface
{
/**
* Create an object
Expand Down Expand Up @@ -72,7 +72,7 @@ public function __invoke(
$config = $container->get(\VuFind\Config\PluginManager::class)
->get('config')->toArray();
return new $requestedName(
$container->get(\VuFind\DoiLinker\PluginManager::class),
$container->get(\VuFind\IdentifierLinker\PluginManager::class),
$container->get('ViewRenderer'),
$config
);
Expand Down
4 changes: 2 additions & 2 deletions module/VuFind/src/VuFind/AjaxHandler/PluginManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
'checkRequestIsValid' => CheckRequestIsValid::class,
'commentRecord' => CommentRecord::class,
'deleteRecordComment' => DeleteRecordComment::class,
'doiLookup' => DoiLookup::class,
'identifierLinksLookup' => IdentifierLinksLookup::class,
'getACSuggestions' => GetACSuggestions::class,
'getIlsStatus' => GetIlsStatus::class,
'getItemStatuses' => GetItemStatuses::class,
Expand Down Expand Up @@ -90,7 +90,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
CheckRequestIsValid::class => AbstractIlsAndUserActionFactory::class,
CommentRecord::class => CommentRecordFactory::class,
DeleteRecordComment::class => DeleteRecordCommentFactory::class,
DoiLookup::class => DoiLookupFactory::class,
IdentifierLinksLookup::class => IdentifierLinksLookupFactory::class,
GetACSuggestions::class => GetACSuggestionsFactory::class,
GetIlsStatus::class => GetIlsStatusFactory::class,
GetItemStatuses::class => GetItemStatusesFactory::class,
Expand Down
Loading
Loading