diff --git a/config/vufind/config.ini b/config/vufind/config.ini index 1f522830d1e97c25355a814cea110834775a0383..8bff5f8b935436c624081648fe4811e12425d8b8 100644 --- a/config/vufind/config.ini +++ b/config/vufind/config.ini @@ -852,6 +852,11 @@ coverimagesCache = true ; default; to enable it, just uncomment the line below. ;ajaxcovers = true +; When ajaxcovers is set to true, this setting controls whether the AJAX Handler +; GetRecordCover renders a fallback template (record/coverReplacement.phtml) in case +; no cover image could be loaded. +;useCoverFallbacksOnFail = false + ; These settings control the image to display when no book cover is available. ; If makeDynamicCovers is not false and the GD library is installed, VuFind will draw ; cover images on the fly. See [DynamicCovers] below for more settings. If set to diff --git a/module/VuFind/src/VuFind/AjaxHandler/GetRecordCover.php b/module/VuFind/src/VuFind/AjaxHandler/GetRecordCover.php index c49a7fd39f97b45c8d17e074501d2de6585804f3..9d4d3814d14db5977d485714ca1ae5be065f5032 100644 --- a/module/VuFind/src/VuFind/AjaxHandler/GetRecordCover.php +++ b/module/VuFind/src/VuFind/AjaxHandler/GetRecordCover.php @@ -28,6 +28,7 @@ namespace VuFind\AjaxHandler; use Laminas\Mvc\Controller\Plugin\Params; +use Laminas\View\Renderer\PhpRenderer; use VuFind\Cover\Router as CoverRouter; use VuFind\Exception\RecordMissing as RecordMissingException; use VuFind\ILS\Driver\CacheTrait; @@ -60,17 +61,40 @@ class GetRecordCover extends AbstractBase implements AjaxHandlerInterface */ protected $coverRouter; + /** + * PHP renderer + * + * @var PhpRenderer + */ + protected $renderer; + + /** + * If true we will render a fallback html template in case no image could be + * loaded + * + * @var bool + */ + protected $useCoverFallbacksOnFail = false; + /** * GetRecordCover constructor. * - * @param RecordLoader $recordLoader Record loader - * @param CoverRouter $coverRouter Cover router + * @param RecordLoader $recordLoader Record loader + * @param CoverRouter $coverRouter Cover router + * @param PhpRenderer $renderer PHP renderer (only required if + * $userCoverFallbacksOnFail is set to true) + * @param bool $useCoverFallbacksOnFail If true we will render a + * fallback html template in case no image could be loaded */ public function __construct(RecordLoader $recordLoader, - CoverRouter $coverRouter + CoverRouter $coverRouter, + ?PhpRenderer $renderer = null, + $useCoverFallbacksOnFail = false ) { $this->recordLoader = $recordLoader; $this->coverRouter = $coverRouter; + $this->renderer = $renderer; + $this->useCoverFallbacksOnFail = $useCoverFallbacksOnFail; } /** @@ -102,11 +126,19 @@ class GetRecordCover extends AbstractBase implements AjaxHandlerInterface ); } - return $this->formatResponse( - [ - 'url' => $this->coverRouter->getUrl($record, $size ?? 'small'), - 'size' => $size, - ] + $url = $this->coverRouter->getUrl( + $record, $size ?? 'small', true, $this->useCoverFallbacksOnFail ); + + return ($url || !$this->renderer || !$this->useCoverFallbacksOnFail) + ? $this->formatResponse(compact('url', 'size')) + : $this->formatResponse( + [ + 'html' => $this->renderer->render( + 'record/coverReplacement', + ['driver' => $record] + ) + ] + ); } } diff --git a/module/VuFind/src/VuFind/AjaxHandler/GetRecordCoverFactory.php b/module/VuFind/src/VuFind/AjaxHandler/GetRecordCoverFactory.php index ba99980495c29249ef504569baad84d337eceaa6..b66289308b0fb29a75c1dbca1692bfbe69c22f1b 100644 --- a/module/VuFind/src/VuFind/AjaxHandler/GetRecordCoverFactory.php +++ b/module/VuFind/src/VuFind/AjaxHandler/GetRecordCoverFactory.php @@ -63,9 +63,15 @@ class GetRecordCoverFactory implements FactoryInterface public function __invoke(ContainerInterface $container, $requestedName, array $options = null ) { + $config + = $container->get(\VuFind\Config\PluginManager::class)->get('config'); + $useFallbacks = $config->Content->useCoverFallbacksOnFail ?? false; return new $requestedName( $container->get(\VuFind\Record\Loader::class), - $container->get(\VuFind\Cover\Router::class) + $container->get(\VuFind\Cover\Router::class), + // We only need the view renderer if we're going to use fallbacks: + $useFallbacks ? $container->get('ViewRenderer') : null, + $useFallbacks ); } } diff --git a/module/VuFind/src/VuFind/Cover/Loader.php b/module/VuFind/src/VuFind/Cover/Loader.php index 3ebcd631988ceff2ff9ed2b915a4e8a1eafb3c99..d4c04ad43d77bebccf3febe71cd436eaa45b6f5c 100644 --- a/module/VuFind/src/VuFind/Cover/Loader.php +++ b/module/VuFind/src/VuFind/Cover/Loader.php @@ -164,6 +164,13 @@ class Loader extends \VuFind\ImageLoader */ protected $type; + /** + * Flag denoting the last loaded image was a FailImage + * + * @var bool + */ + protected $hasLoadedUnavailable = false; + /** * Constructor * @@ -315,6 +322,8 @@ class Loader extends \VuFind\ImageLoader */ public function loadImage($settings = []) { + // reset to normal + $this->hasLoadedUnavailable = false; // Load settings from legacy function parameters if they are not passed // in as an array: $settings = is_array($settings) @@ -343,6 +352,28 @@ class Loader extends \VuFind\ImageLoader } } + /** + * {@inheritdoc} + * Adds @see self::$hasLoadedUnavailable flag + * + * @return void + */ + public function loadUnavailable() + { + $this->hasLoadedUnavailable = true; + return parent::loadUnavailable(); + } + + /** + * Returns true if the last loaded image was the FailImage + * + * @return bool + */ + public function hasLoadedUnavailable() + { + return $this->hasLoadedUnavailable; + } + /** * Support method for fetchFromAPI() -- set the localFile property. * diff --git a/module/VuFind/src/VuFind/Cover/Router.php b/module/VuFind/src/VuFind/Cover/Router.php index b96bfa4e10b47e6a57ec52284b9543c0b6b8752a..fdeabb536191ae39408fde27eadf55cff4cf31f2 100644 --- a/module/VuFind/src/VuFind/Cover/Router.php +++ b/module/VuFind/src/VuFind/Cover/Router.php @@ -78,11 +78,13 @@ class Router implements \Laminas\Log\LoggerAwareInterface * small is default). * @param bool $resolveDynamic Should we resolve dynamic cover data into * a URL (true) or simply return false (false)? + * @param bool $testLoadImage If true the function will try to load the + * cover image in advance and returns false in case no image could be loaded * * @return string|bool */ public function getUrl(RecordDriver $driver, $size = 'small', - $resolveDynamic = true + $resolveDynamic = true, $testLoadImage = false ) { // Try to build thumbnail: $thumb = $driver->tryMethod('getThumbnail', [$size]); @@ -127,6 +129,19 @@ class Router implements \Laminas\Log\LoggerAwareInterface ); } } - return $directUrl ?? $dynamicUrl ?? false; + + if (isset($directUrl)) { + return $directUrl; + } elseif (isset($dynamicUrl)) { + if ($testLoadImage) { + $this->coverLoader->loadImage($settings); + if ($this->coverLoader->hasLoadedUnavailable()) { + return false; + } + } + return $dynamicUrl; + } + + return false; } } diff --git a/themes/bootstrap3/js/covers.js b/themes/bootstrap3/js/covers.js index fa8b46f1dfb1cb3d62c8321f4d4df73813ef4396..4a9b58c1fabb4bca25c16b6391263f39522613bc 100644 --- a/themes/bootstrap3/js/covers.js +++ b/themes/bootstrap3/js/covers.js @@ -7,9 +7,16 @@ function loadCoverByElement(data, element) { function coverCallback(response) { spinner.hide(); container.show(); - if (response.data.url !== false) { + if (typeof response.data.url !== 'undefined' && response.data.url !== false) { img.attr("src", response.data.url); container.children().not("img").hide(); + } else { + img.remove(); + if (typeof response.data.html !== 'undefined') { + container.html(response.data.html); + } else { + container.html(); + } } } $.ajax({ diff --git a/themes/bootstrap3/templates/record/coverReplacement.phtml b/themes/bootstrap3/templates/record/coverReplacement.phtml new file mode 100644 index 0000000000000000000000000000000000000000..6abe01a3db6eb68f47ec211d432aa0ff69847644 --- /dev/null +++ b/themes/bootstrap3/templates/record/coverReplacement.phtml @@ -0,0 +1 @@ +<i class="fa fa-question"></i> \ No newline at end of file