diff --git a/module/VuFind/src/VuFind/Controller/QRCodeController.php b/module/VuFind/src/VuFind/Controller/QRCodeController.php index 1064045555d8b68a7e219a6c7751a31e31256a7c..0967300e7e32e59b9c171dd50db79dc54b830859 100644 --- a/module/VuFind/src/VuFind/Controller/QRCodeController.php +++ b/module/VuFind/src/VuFind/Controller/QRCodeController.php @@ -113,7 +113,7 @@ class QRCodeController extends AbstractBase $headers->addHeaderLine( 'Content-type', $this->getLoader()->getContentType() ); - $response->setContent($this->getLoader()->getQrCode()); + $response->setContent($this->getLoader()->getImage()); return $response; } } diff --git a/module/VuFind/src/VuFind/Cover/Loader.php b/module/VuFind/src/VuFind/Cover/Loader.php index c9b3bbc0768aa2a1e9646b76d1689bcfa5b0abea..05c480c3e9a4793afa954ce15917e5fe534d6b4a 100644 --- a/module/VuFind/src/VuFind/Cover/Loader.php +++ b/module/VuFind/src/VuFind/Cover/Loader.php @@ -28,8 +28,7 @@ */ namespace VuFind\Cover; use VuFind\Code\ISBN, - VuFind\Content\Covers\PluginManager as ApiManager, - Zend\Log\LoggerInterface; + VuFind\Content\Covers\PluginManager as ApiManager; /** * Book Cover Generator @@ -41,7 +40,7 @@ use VuFind\Code\ISBN, * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link http://vufind.org/wiki/use_of_external_content Wiki */ -class Loader implements \Zend\Log\LoggerAwareInterface +class Loader extends \VuFind\ImageLoader { /** * filename constructed from ISBN @@ -127,34 +126,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface */ protected $type; - /** - * Property for storing raw image data; may be null if image is unavailable - * - * @var string - */ - protected $image = null; - - /** - * Content type of data in $image property - * - * @var string - */ - protected $contentType = null; - - /** - * Logger (or false for none) - * - * @var LoggerInterface|bool - */ - protected $logger = false; - - /** - * Theme tools - * - * @var \VuFindTheme\ThemeInfo - */ - protected $themeTools; - /** * Constructor * @@ -168,69 +139,17 @@ class Loader implements \Zend\Log\LoggerAwareInterface public function __construct($config, ApiManager $manager, \VuFindTheme\ThemeInfo $theme, \Zend\Http\Client $client, $baseDir = null ) { + $this->setThemeInfo($theme); $this->config = $config; + $this->configuredFailImage = isset($config->Content->noCoverAvailableImage) + ? $config->Content->noCoverAvailableImage : null; $this->apiManager = $manager; - $this->themeTools = $theme; $this->client = $client; $this->baseDir = (null === $baseDir) ? rtrim(sys_get_temp_dir(), '\\/') . '/covers' : rtrim($baseDir, '\\/'); } - /** - * Set the logger - * - * @param LoggerInterface $logger Logger to use. - * - * @return void - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - } - - /** - * Log a debug message. - * - * @param string $msg Message to log. - * - * @return void - */ - protected function debug($msg) - { - if ($this->logger) { - $this->logger->debug($msg); - } - } - - /** - * Get the image data (usually called after loadImage) - * - * @return string - */ - public function getImage() - { - // No image loaded? Use "unavailable" as default: - if (null === $this->image) { - $this->loadUnavailable(); - } - return $this->image; - } - - /** - * Get the content type of the current image (usually called after loadImage) - * - * @return string - */ - public function getContentType() - { - // No content type loaded? Use "unavailable" as default: - if (null === $this->contentType) { - $this->loadUnavailable(); - } - return $this->contentType; - } - /** * Get Cover Generator Object * @@ -449,93 +368,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface return false; } - /** - * Find a file in the themes (return false if no file exists). - * - * @param string $path Relative path of file to find. - * @param array $formats Optional array of suffixes to add to $path while - * searching theme (used to check multiple extensions in each theme). - * - * @return string|bool - */ - protected function searchTheme($path, $formats = array('')) - { - // Check all supported image formats: - $filenames = array(); - foreach ($formats as $format) { - $filenames[] = $path . $format; - } - $fileMatch = $this->themeTools->findContainingTheme($filenames, true); - return empty($fileMatch) ? false : $fileMatch; - } - - /** - * Load the user-specified "cover unavailable" graphic (or default if none - * specified). - * - * @return void - * @author Thomas Schwaerzler <vufind-tech@lists.sourceforge.net> - */ - public function loadUnavailable() - { - // No setting -- use default, and don't log anything: - if (!isset($this->config->Content->noCoverAvailableImage)) { - return $this->loadDefaultFailImage(); - } - - // Setting found -- get "no cover" image from config.ini: - $configuredImage = $this->config->Content->noCoverAvailableImage; - $noCoverImage = $this->searchTheme($configuredImage); - - // If file is blank/inaccessible, log error and display default: - if (empty($noCoverImage) || !file_exists($noCoverImage) - || !is_readable($noCoverImage) - ) { - $this->debug("Cannot access '{$configuredImage}'"); - return $this->loadDefaultFailImage(); - } - - // Array containing map of allowed file extensions to mimetypes - // (to be extended) - $allowedFileExtensions = array( - "gif" => "image/gif", - "jpeg" => "image/jpeg", "jpg" => "image/jpeg", - "png" => "image/png", - "tiff" => "image/tiff", "tif" => "image/tiff" - ); - - // Log error and bail out if file lacks a known image extension: - $parts = explode('.', $noCoverImage); - $fileExtension = strtolower(end($parts)); - if (!array_key_exists($fileExtension, $allowedFileExtensions)) { - $this->debug( - "Illegal file-extension '$fileExtension' for image '$noCoverImage'" - ); - return $this->loadDefaultFailImage(); - } - - // Get mime type from file extension: - $this->contentType = $allowedFileExtensions[$fileExtension]; - - // Load the image data: - $this->image = file_get_contents($noCoverImage); - } - - /** - * Display the default "cover unavailable" graphic. - * - * @return void - */ - protected function loadDefaultFailImage() - { - $this->contentType = 'image/gif'; - $file = $this->searchTheme('images/noCover2.gif'); - if (!file_exists($file)) { - throw new \Exception('Could not load default fail image.'); - } - $this->image = file_get_contents($file); - } - /** * Support method for validateAndMoveTempFile -- convert non-JPEG image data to a * JPEG file. diff --git a/module/VuFind/src/VuFind/ImageLoader.php b/module/VuFind/src/VuFind/ImageLoader.php new file mode 100644 index 0000000000000000000000000000000000000000..46cfeb39e5d341d2b1e39d313fce6be8b5b53fc0 --- /dev/null +++ b/module/VuFind/src/VuFind/ImageLoader.php @@ -0,0 +1,264 @@ +<?php +/** + * Base class for loading images (shared by Cover\Loader and QRCode\Loader) + * + * PHP version 5 + * + * Copyright (C) Villanova University 2007. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category VuFind2 + * @package Cover_Generator + * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/use_of_external_content Wiki + */ +namespace VuFind; +use Zend\Log\LoggerInterface; + +/** + * Base class for loading images (shared by Cover\Loader and QRCode\Loader) + * + * @category VuFind2 + * @package Cover_Generator + * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/use_of_external_content Wiki + */ +class ImageLoader implements \Zend\Log\LoggerAwareInterface +{ + /** + * Property for storing raw image data; may be null if image is unavailable + * + * @var string + */ + protected $image = null; + + /** + * Content type of data in $image property + * + * @var string + */ + protected $contentType = null; + + /** + * Logger (or false for none) + * + * @var LoggerInterface|bool + */ + protected $logger = false; + + /** + * Theme tools + * + * @var \VuFindTheme\ThemeInfo + */ + protected $themeTools = null; + + /** + * User-configured image to load from theme on error. + * + * @var string + */ + protected $configuredFailImage = null; + + /** + * Default image to load from theme if user-configured option fails. + * + * @var string + */ + protected $defaultFailImage = 'images/noCover2.gif'; + + /** + * Array containing map of allowed file extensions to mimetypes + * (to be extended) + * + * @var array + */ + protected $allowedFileExtensions = array( + "gif" => "image/gif", + "jpeg" => "image/jpeg", "jpg" => "image/jpeg", + "png" => "image/png", + "tiff" => "image/tiff", "tif" => "image/tiff" + ); + + /** + * Setter for dependency + * + * @param \VuFindTheme\ThemeInfo $theme VuFind theme tools + * + * @return void + */ + public function setThemeInfo(\VuFindTheme\ThemeInfo $theme) + { + $this->themeTools = $theme; + } + + /** + * Set the logger + * + * @param LoggerInterface $logger Logger to use. + * + * @return void + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Log a debug message. + * + * @param string $msg Message to log. + * + * @return void + */ + protected function debug($msg) + { + if ($this->logger) { + $this->logger->debug($msg); + } + } + + /** + * Get the image data (not meant to be called until after image is populated) + * + * @return string + */ + public function getImage() + { + // No image loaded? Use "unavailable" as default: + if (null === $this->image) { + $this->loadUnavailable(); + } + return $this->image; + } + + /** + * Get the content type of the current image (not meant to be called until after + * contentType is populated) + * + * @return string + */ + public function getContentType() + { + // No content type loaded? Use "unavailable" as default: + if (null === $this->contentType) { + $this->loadUnavailable(); + } + return $this->contentType; + } + + /** + * Find a file in the themes (return false if no file exists). + * + * @param string $path Relative path of file to find. + * @param array $formats Optional array of suffixes to add to $path while + * searching theme (used to check multiple extensions in each theme). + * + * @return string|bool + */ + protected function searchTheme($path, $formats = array('')) + { + // Check all supported image formats: + $filenames = array(); + foreach ($formats as $format) { + $filenames[] = $path . $format; + } + if (null === $this->themeTools) { + throw new \Exception('\VuFindTheme\ThemeInfo object missing'); + } + $fileMatch = $this->themeTools->findContainingTheme($filenames, true); + return empty($fileMatch) ? false : $fileMatch; + } + + /** + * Load the user-specified "cover unavailable" graphic (or default if none + * specified). + * + * @return void + * @author Thomas Schwaerzler <vufind-tech@lists.sourceforge.net> + */ + public function loadUnavailable() + { + // No setting -- use default, and don't log anything: + if (empty($this->configuredFailImage)) { + return $this->loadDefaultFailImage(); + } + + // Setting found -- get "no cover" image from config.ini: + $noCoverImage = $this->searchTheme($this->configuredFailImage); + + // If file is blank/inaccessible, log error and display default: + if (empty($noCoverImage) || !file_exists($noCoverImage) + || !is_readable($noCoverImage) + ) { + $this->debug("Cannot access '{$this->configuredFailImage}'"); + return $this->loadDefaultFailImage(); + } + + try { + // Get mime type from file extension: + $this->contentType = $this->getContentTypeFromExtension($noCoverImage); + } catch (\Exception $e) { + // Log error and bail out if file lacks a known image extension: + $this->debug($e->getMessage()); + return $this->loadDefaultFailImage(); + } + + // Load the image data: + $this->image = file_get_contents($noCoverImage); + } + + /** + * Display the default "cover unavailable" graphic. + * + * @return void + */ + protected function loadDefaultFailImage() + { + $file = $this->searchTheme($this->defaultFailImage); + if (!file_exists($file)) { + throw new \Exception('Could not load default fail image.'); + } + $this->contentType = $this->getContentTypeFromExtension($file); + $this->image = file_get_contents($file); + } + + /** + * Get the content-type for a file based on extension. Throw an exception if + * an illegal extension is provided. + * + * @param string $filename Filename to analyze. + * + * @return string + * @throws \Exception + */ + protected function getContentTypeFromExtension($filename) + { + $parts = explode('.', $filename); + $fileExtension = strtolower(end($parts)); + if (!array_key_exists($fileExtension, $this->allowedFileExtensions)) { + throw new \Exception( + "Illegal file-extension '$fileExtension' for image '$filename'" + ); + } + + // Get mime type from file extension: + return $this->allowedFileExtensions[$fileExtension]; + } +} diff --git a/module/VuFind/src/VuFind/QRCode/Loader.php b/module/VuFind/src/VuFind/QRCode/Loader.php index 2bf41a2c275bc110d6b907cf8e6efe00a2c7e592..a9b91287334cc88e9452dfc1011b1c489bf77dd9 100644 --- a/module/VuFind/src/VuFind/QRCode/Loader.php +++ b/module/VuFind/src/VuFind/QRCode/Loader.php @@ -20,7 +20,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * @category VuFind2 - * @package Cover_Generator + * @package QRCode_Generator * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> * @author Demian Katz <demian.katz@villanova.edu> * @author Luke O'Sullivan <l.osullivan@swansea.ac.uk> @@ -28,10 +28,10 @@ * @link http://vufind.org/wiki/use_of_external_content Wiki */ namespace VuFind\QRCode; -use \PHPQRCode, Zend\Log\LoggerInterface; +use \PHPQRCode; /** - * Book Cover Generator + * QR Code Generator * * @category VuFind2 * @package QRCode_Generator @@ -41,7 +41,7 @@ use \PHPQRCode, Zend\Log\LoggerInterface; * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link http://vufind.org/wiki/use_of_external_content Wiki */ -class Loader implements \Zend\Log\LoggerAwareInterface +class Loader extends \VuFind\ImageLoader { /** * property to hold VuFind configuration settings @@ -50,20 +50,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface */ protected $config; - /** - * Property for storing raw qrcode data; may be null if image is unavailable - * - * @var string - */ - protected $qrcode = null; - - /** - * Content type of data in $qrcode property - * - * @var string - */ - protected $contentType = null; - /** * The text used to generate the QRCode * @@ -80,20 +66,6 @@ class Loader implements \Zend\Log\LoggerAwareInterface 'level' => "L", 'size' => "3", 'margin' => "4" ); - /** - * Logger (or false for none) - * - * @var LoggerInterface|bool - */ - protected $logger = false; - - /** - * Theme tools - * - * @var \VuFindTheme\ThemeInfo - */ - protected $themeTools; - /** * Constructor * @@ -102,65 +74,15 @@ class Loader implements \Zend\Log\LoggerAwareInterface */ public function __construct($config, \VuFindTheme\ThemeInfo $theme) { $this->config = $config; - $this->themeTools = $theme; + $this->setThemeInfo($theme); + $this->configuredFailImage + = isset($this->config->QRCode->noQRCodeAvailableImage) + ? $this->config->QRCode->noQRCodeAvailableImage : null; + $this->defaultFailImage = 'images/noQRCode.gif'; } /** - * Set the logger - * - * @param LoggerInterface $logger Logger to use. - * - * @return void - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - } - - /** - * Log a debug message. - * - * @param string $msg Message to log. - * - * @return void - */ - protected function debug($msg) - { - if ($this->logger) { - $this->logger->debug($msg); - } - } - - /** - * Get the QrCode data (usually called after loadQrCode) - * - * @return string - */ - public function getQRCode() - { - // No image loaded? Use "unavailable" as default: - if (is_null($this->qrcode)) { - $this->loadUnavailable(); - } - return $this->image; - } - - /** - * Get the content type of the current image (usually called after loadImage) - * - * @return string - */ - public function getContentType() - { - // No content type loaded? Use "unavailable" as default: - if (is_null($this->contentType)) { - $this->loadUnavailable(); - } - return $this->contentType; - } - - /** - * Load an image given an ISBN and/or content type. + * Set up a QR code image * * @param string $text The QR code text * @param array $params QR code parameters (level/size/margin) @@ -179,9 +101,9 @@ class Loader implements \Zend\Log\LoggerAwareInterface } /** - * Load bookcover fom URL from cache or remote provider and display if possible. + * Generate a QR code image * - * @return bool True if image displayed, false on failure. + * @return bool True if image displayed, false on failure. */ protected function fetchQRCode() { @@ -189,93 +111,10 @@ class Loader implements \Zend\Log\LoggerAwareInterface return false; } $this->contentType = 'image/png'; - $this->qrcode = PHPQRCode\QRcode::PNG( + $this->image = PHPQRCode\QRcode::PNG( $this->text, false, $this->params['level'], $this->params['size'], $this->params['margin'] ); return true; } - - /** - * Find a file in the themes (return false if no file exists). - * - * @param string $path Relative path of file to find. - * @param array $formats Optional array of suffixes to add to $path while - * searching theme (used to check multiple extensions in each theme). - * - * @return string|bool - */ - protected function searchTheme($path, $formats = array('')) - { - // Check all supported image formats: - $filenames = array(); - foreach ($formats as $format) { - $filenames[] = $path . $format; - } - $fileMatch = $this->themeTools->findContainingTheme($filenames, true); - return empty($fileMatch) ? false : $fileMatch; - } - - /** - * Load the user-specified "cover unavailable" graphic (or default if none - * specified). - * - * @return void - * @author Thomas Schwaerzler <vufind-tech@lists.sourceforge.net> - */ - public function loadUnavailable() - { - // Get "no qrcode" image from config.ini: - $noQRCodeImage = isset($this->config->QRCode->noQRCodeAvailableImage ) - ? $this->searchTheme($this->config->QRCode->noQRCodeAvailableImage) - : null; - - // No setting -- use default, and don't log anything: - if (empty($noQRCodeImage)) { - // log? - return $this->loadDefaultFailImage(); - } - - // If file defined but does not exist, log error and display default: - if (!file_exists($noQRCodeImage) || !is_readable($noQRCodeImage)) { - $this->debug("Cannot access file: '$noQRCodeImage'"); - return $this->loadDefaultFailImage(); - } - - // Array containing map of allowed file extensions to mimetypes - // (to be extended) - $allowedFileExtensions = array( - "gif" => "image/gif", - "jpeg" => "image/jpeg", "jpg" => "image/jpeg", - "png" => "image/png", - "tiff" => "image/tiff", "tif" => "image/tiff" - ); - - // Log error and bail out if file lacks a known image extension: - $parts = explode('.', $noQRCodeImage); - $fileExtension = strtolower(end($parts)); - if (!array_key_exists($fileExtension, $allowedFileExtensions)) { - $this->debug( - "Illegal file-extension '$fileExtension' for image '$noQRCodeImage'" - ); - return $this->loadDefaultFailImage(); - } - - // Get mime type from file extension: - $this->contentType = $allowedFileExtensions[$fileExtension]; - - // Load the image data: - $this->image = file_get_contents($noQRCodeImage); - } - - /** - * Display the default "qrcode unavailable" graphic and terminate execution. - * - * @return void - */ - protected function loadDefaultFailImage() - { - $this->contentType = 'image/gif'; - $this->image = file_get_contents($this->searchTheme('images/noQRCode.gif')); - } } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/QRCode/LoaderTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/QRCode/LoaderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fa67a1b2b0ff13c5209697748e38ba6d506c92cb --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/QRCode/LoaderTest.php @@ -0,0 +1,108 @@ +<?php +/** + * QR Code Loader Test Class + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category VuFind2 + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/vufind2:unit_tests Wiki + */ +namespace VuFindTest\QRCode; +use VuFind\QRCode\Loader; +use VuFindTheme\ThemeInfo; +use Zend\Config\Config; + +/** + * QR Code Loader Test Class + * + * @category VuFind2 + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/vufind2:unit_tests Wiki + */ +class LoaderTest extends \VuFindTest\Unit\TestCase +{ + /** + * Theme to use for testing purposes. + * + * @var string + */ + protected $testTheme = 'bootstrap3'; + + /** + * Test that failure to load even the baseline image causes an exception. + * + * @return void + * @expectedException Exception + * @expectedExceptionMessage Could not load default fail image. + */ + public function testUtterFailure() + { + $theme = $this->getMock('VuFindTheme\ThemeInfo', array(), array('foo', 'bar')); + $theme->expects($this->once())->method('findContainingTheme')->with($this->equalTo(array('images/noQRCode.gif')))->will($this->returnValue(false)); + $loader = $this->getLoader(array(), $theme); + $loader->getImage(); + } + + /** + * Test that requesting a blank QR code returns the fail image. + * + * @return void + */ + public function testDefaultLoadingForBlankText() + { + $loader = $this->getLoader(); + $this->assertEquals('image/gif', $loader->getContentType()); + $this->assertEquals('483', strlen($loader->getImage())); + } + + /** + * Get a loader object to test. + * + * @param array $config Configuration + * @param ThemeInfo $theme Theme info object (null to create default) + * @param array|bool $mock Array of functions to mock, or false for real object + * + * @return void + */ + protected function getLoader($config = array(), $theme = null, $mock = false) + { + $config = new Config($config); + if (null === $theme) { + $theme = new ThemeInfo($this->getThemeDir(), $this->testTheme); + } + if ($mock) { + return $this->getMock('VuFind\QRCode\Loader', $mock, array($config, $theme)); + } + return new Loader($config, $theme); + } + + /** + * Get the theme directory. + * + * @return string + */ + protected function getThemeDir() + { + return realpath(__DIR__ . '/../../../../../../../themes'); + } +} \ No newline at end of file