Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions config/vufind/EDS.ini
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,12 @@ CatalogDatabaseId = ""
; CatalogANReplace[] = "-"

[Cover]
; If you want the cover image URL to be used directly in page templates, set loadDirectly to
; true, but when set to false it will use the EDS cover loader to proxy the image URL
; through the application server providing additional privacy for end-users (since the image request
; will not be from the client IP). Note: when setting this to false, you must also update the
; coverimages setting in the config.ini to include EDS.
loadDirectly = true
; Normally EDS results display only EDS-supplied cover images, which are only available for
; some ebook data sources. Otherwise, EDS displays custom icons based on content type.
; Enable this setting to fallback to standard VuFind cover image loading behavior when no
Expand Down
5 changes: 3 additions & 2 deletions config/vufind/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,7 @@ verify_server_certificate = false
; coversize setting to false:
;coversize = false

; You can select Syndetics, LibraryThing, Summon, OpenLibrary,
; You can select Syndetics, LibraryThing, Summon, OpenLibrary, EDS,
; Contentcafe, Buchhandel, Google, BrowZine, ObalkyKnih, Orb, Koha, Demo,
; and/or LocalFile. Service-specific notes:
; - BrowZine requires you to have BrowZine.ini configured appropriately.
Expand Down Expand Up @@ -1136,7 +1136,8 @@ verify_server_certificate = false
; - Orb requires that you complete the [Orb] section. Cache settings can be
; adjusted in the [Cache_OrbCover] section.
; - Summon service takes a Serials Solutions client key, NOT Summon API key!
;coverimages = Syndetics:MySyndeticsId,LibraryThing:MyLibraryThingId,Google,ObalkyKnih,OpenLibrary,Summon:MySerialsSolutionsClientKey,Buchhandel,Contentcafe:MyContentCafeID,BrowZine,LocalFile:PathToFile,Koha,Orb
; - EDS should be used when the EDS.ini is provided to use cover images from the EDS API
;coverimages = Syndetics:MySyndeticsId,LibraryThing:MyLibraryThingId,Google,ObalkyKnih,OpenLibrary,EDS,Summon:MySerialsSolutionsClientKey,Buchhandel,Contentcafe:MyContentCafeID,BrowZine,LocalFile:PathToFile,Koha,Orb

; When using the Koha cover provider, you should fill in this setting:
;koha_cover_url = "https://localhost/cgi-bin/koha/opac-image.pl"
Expand Down
112 changes: 112 additions & 0 deletions module/VuFind/src/VuFind/Content/Covers/EDS.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

/**
* EDS cover content loader.
*
* PHP version 8
*
* Copyright (C) Michigan State University Board of Trustees 2026.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see
* <https://www.gnu.org/licenses/>.
*
* @category VuFind
* @package Content
* @author Megan Schanz <schamzme@msu.edu>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development Wiki
*/

namespace VuFind\Content\Covers;

use Laminas\Cache\Storage\StorageInterface;

/**
* EDS cover content loader.
*
* @category VuFind
* @package Content
* @author Megan Schanz <schamzme@msu.edu>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development Wiki
*/
class EDS extends \VuFind\Content\AbstractCover implements \Psr\Log\LoggerAwareInterface
{
use \VuFind\Log\LoggerAwareTrait;
use \VuFind\Cache\CacheTrait;

/**
* Constructor.
*
* @param StorageInterface $cache Cache
*/
public function __construct(StorageInterface $cache)
{
$this->supportsRecordid = true;
$this->setCacheStorage($cache);
}

/**
* Set the key to store in the cache to share between EDS cover loader and record driver.
*
* @param string $key Key to put in the cache
*
* @return string The determined key
*/
protected function getCacheKey($key = '')
{
return 'EDS_Shared_' . md5($key);
}

/**
* Determine if this handler supports the provided identifiers.
*
* In this case, we look for the recordid and source keys
* that are required for proxying the image and making sure the source
* is EDS.
*
* @param array $ids Array of identifiers (recordid, isbn, etc.)
*
* @return bool
*/
public function supports($ids)
{
return isset($ids['recordid'])
&& isset($ids['source'])
&& $ids['source'] === 'EDS';
}

/**
* Get an image URL for the specific record.
*
* This is the primary method used by the Cover Loader manager.
*
* @param string $key Cover provider key (e.g. 'eds')
* @param string $size Size of image requested
* @param array $ids Array of identifiers
*
* @return string|bool URL of the image or false if unavailable
*/
public function getUrl($key, $size, $ids)
{
$recordId = $ids['recordid'] ?? '';
$url = $this->getCachedData($recordId);

if (filter_var($url, FILTER_VALIDATE_URL)) {
$this->debug('Returning EDS cover image URL: ' . $url);
return $url;
}

return false;
}
}
70 changes: 70 additions & 0 deletions module/VuFind/src/VuFind/Content/Covers/EDSFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

/**
* EDS cover loader factory.
*
* PHP version 8
*
* Copyright (C) Michigan State University Board of Trustees 2026.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see
* <https://www.gnu.org/licenses/>.
*
* @category VuFind
* @package Content
* @author Megan Schanz <schamzme@msu.edu>
* @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\Content\Covers;

use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;

/**
* Factory for EDS cover loader.
*
* PHP version 8
*
* @category VuFind
* @package Content
* @author Megan Schanz <schamzme@msu.edu>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/vufind/ Main page
*/
class EDSFactory implements FactoryInterface
{
/**
* Create an object.
*
* @param ContainerInterface $container Service manager
* @param string $requestedName Service being created
* @param null|array $options Extra options (optional)
*
* @return object
*
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException&\Throwable if any other error occurs
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
if (!empty($options)) {
throw new \Exception('Unexpected options passed to factory.');
}

return new $requestedName($container->get(\VuFind\Cache\Manager::class)->getCache('object'));
}
}
1 change: 1 addition & 0 deletions module/VuFind/src/VuFind/Content/Covers/PluginManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
'browzine' => BrowZine::class,
'contentcafe' => ContentCafe::class,
'demo' => Demo::class,
'eds' => EDS::class,
'google' => Google::class,
'koha' => Koha::class,
'librarything' => LibraryThing::class,
Expand Down
54 changes: 51 additions & 3 deletions module/VuFind/src/VuFind/RecordDriver/EDS.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

namespace VuFind\RecordDriver;

use Laminas\Cache\Storage\StorageInterface;

use function count;
use function floatval;
use function in_array;
Expand All @@ -48,6 +50,8 @@
class EDS extends DefaultRecord
{
use Feature\IlsAwareTrait;
use \VuFind\Http\CachingDownloaderAwareTrait;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not using the caching downloader, so why are you using this trait?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not; good catch. That was leftover from when I was experimenting with caching the images. I removed it.

use \VuFind\Cache\CacheTrait;

/**
* Document types that are treated as ePub links.
Expand All @@ -63,6 +67,33 @@ class EDS extends DefaultRecord
*/
protected $pdfTypes = ['ebook-pdf', 'pdflink'];

/**
* Constructor.
*
* @param \VuFind\Config\Config $mainConfig VuFind main configuration (omit
* for built-in defaults)
* @param StorageInterface $cache Cache
*/
public function __construct(
$mainConfig = null,
StorageInterface $cache = null,
) {
$this->setCacheStorage($cache);
parent::__construct($mainConfig);
}

/**
* Set the key to store in the cache to share between EDS cover loader and record driver.
*
* @param string $key Key to put in the cache
*
* @return string The determined key
*/
protected function getCacheKey($key = '')
{
return 'EDS_Shared_' . md5($key);
}

/**
* Return the unique identifier of this record within EDS API;
* As Accession Numbers (AN) could be repetitive, we use Database ID
Expand Down Expand Up @@ -653,6 +684,7 @@ function ($data) {
*/
public function getThumbnail($size = 'small')
{
$thumbnail = null;
// Create a ranked list of sizes so we can use "best available" when appropriate.
// Note that "thumb" is a value used by EBSCO, not by VuFind; it is included so
// it can be matched up with requests for "small."
Expand All @@ -665,7 +697,8 @@ public function getThumbnail($size = 'small')
$target = $image['Target'] ?? '';
if ($target) {
if ($currentFit === $desiredFit) {
return $target;
$thumbnail = $target;
break;
}
// Aim for the best match that is smaller than the requested size; we
// don't want to overflow, but something small is better than nothing.
Expand All @@ -676,10 +709,25 @@ public function getThumbnail($size = 'small')
}
}

if (!$thumbnail && $closestMatch) {
$thumbnail = $closestMatch;
}

// If EDS actually returned cover image data, use it. EDS only provides this data
// for certain ebook packages.
if ($closestMatch) {
return $closestMatch;
if ($thumbnail) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be value in adding an EDS.ini setting to control whether thumbnails are returned directly or through the cache? Some might prefer the old approach, and it doesn't look like adding flexibility would be too difficult.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I made a new setting under [Cover] called loadDirectly and set it to true by default to be consistent with existing functionality. The comment indicates that when it is false the coverimages setting in the config.ini also needs to be updated to include EDS. I'm open to other names for the setting or suggestions to improve the comment around it.

// Determine if we are using the cover loader method or direct load
$loadDirectly = $this->recordConfig?->Cover?->loadDirectly ?? true;
if ($loadDirectly) {
return $thumbnail;
} else {
$this->putCachedData($this->getUniqueID(), $thumbnail);
return [
'recordid' => $this->getUniqueID(),
'size' => $size,
'source' => 'EDS',
];
}
}

// Optionally use VuFind's default cover loader
Expand Down
73 changes: 73 additions & 0 deletions module/VuFind/src/VuFind/RecordDriver/EDSFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/**
* EDS factory for record drivers.
*
* PHP version 8
*
* Copyright (C) Michigan State University Board of Trustees 2026.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see
* <https://www.gnu.org/licenses/>.
*
* @category VuFind
* @package RecordDrivers
* @author Megan Schanz <schanzme@msu.edu>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development Wiki
*/

namespace VuFind\RecordDriver;

use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;

/**
* Factory for EDS RecordDriver.
*
* PHP version 8
*
* @category VuFind
* @package RecordDrivers
* @author Megan Schanz <schanzme@msu.edu>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/vufind/ Main page
*/
class EDSFactory implements FactoryInterface
{
/**
* Create an object.
*
* @param ContainerInterface $container Service manager
* @param string $requestedName Service being created
* @param null|array $options Extra options (optional)
*
* @return object
*
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException&\Throwable if any other error occurs
*/
public function __invoke(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this factory should be simplified or made to extend one of the other existing driver factories -- but the best approach may depend on the answer to the EPF issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It didn't seem like any of the existing driver factories injected the \VuFind\Cache\Manager already. But working with the factories is not my strong-suite, so there certainly may be a way to simplify it that I'm just not seeing. And yes we do need to keep in mind that both EDS and EPF will be using it.

ContainerInterface $container,
$requestedName,
?array $options = null
) {
$parts = explode('\\', $requestedName);
$configName = array_pop($parts);
$config = $container->get(\VuFind\Config\ConfigManagerInterface::class)->getConfigObject($configName);
$cache = $container->get(\VuFind\Cache\Manager::class)->getCache('object');
return new $requestedName($config, $cache);
}
}
Loading