Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.25% covered (warning)
81.25%
39 / 48
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Loader
81.25% covered (warning)
81.25%
39 / 48
20.00% covered (danger)
20.00%
1 / 5
22.64
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getDefaults
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 loadQRCode
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
5.06
 mapErrorLevel
76.92% covered (warning)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
11.23
 fetchQRCode
80.00% covered (warning)
80.00%
12 / 15
0.00% covered (danger)
0.00%
0 / 1
3.07
1<?php
2
3/**
4 * QR Code Generator
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2007.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  QRCode_Generator
25 * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
26 * @author   Demian Katz <demian.katz@villanova.edu>
27 * @author   Luke O'Sullivan <l.osullivan@swansea.ac.uk>
28 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
29 * @link     https://vufind.org/wiki/configuration:external_content Wiki
30 */
31
32namespace VuFind\QRCode;
33
34use Endroid\QrCode\ErrorCorrectionLevel;
35use Endroid\QrCode\QrCode;
36use Endroid\QrCode\Writer\PngWriter;
37
38use function intval;
39use function strlen;
40
41/**
42 * QR Code Generator
43 *
44 * @category VuFind
45 * @package  QRCode_Generator
46 * @author   Andrew S. Nagy <vufind-tech@lists.sourceforge.net>
47 * @author   Demian Katz <demian.katz@villanova.edu>
48 * @author   Luke O'Sullivan <l.osullivan@swansea.ac.uk>
49 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
50 * @link     https://vufind.org/wiki/configuration:external_content Wiki
51 */
52class Loader extends \VuFind\ImageLoader
53{
54    /**
55     * The default params used to generate the QRCode
56     *
57     * @var string
58     */
59    protected $defaultParams = ['level' => 'L', 'size' => '3', 'margin' => '4'];
60
61    /**
62     * Constructor
63     *
64     * @param \Laminas\Config\Config $config VuFind configuration
65     * @param \VuFindTheme\ThemeInfo $theme  VuFind theme tools
66     */
67    public function __construct($config, \VuFindTheme\ThemeInfo $theme)
68    {
69        $this->setThemeInfo($theme);
70        $this->configuredFailImage
71            = $config->QRCode->noQRCodeAvailableImage ?? null;
72        $this->defaultFailImage = 'images/noQRCode.gif';
73    }
74
75    /**
76     * Get default parameters.
77     *
78     * @return array
79     */
80    public function getDefaults()
81    {
82        return $this->defaultParams;
83    }
84
85    /**
86     * Set up a QR code image
87     *
88     * @param string $text      The QR code text
89     * @param array  $rawParams QR code parameters (level/size/margin)
90     *
91     * @return void
92     */
93    public function loadQRCode($text, $rawParams = [])
94    {
95        // Fill in defaults:
96        $params = $rawParams + $this->defaultParams;
97
98        // Normalize parameters; when the size setting is less than 30 pixels,
99        // do some math to try to map old PHPQRCode-style settings to new
100        // Endroid\QrCode equivalents. When the size setting is 30 or higher,
101        // treat 'size' and 'margin' as literal pixel sizes.
102        $size = intval($params['size']);
103        $margin = intval($params['margin']);
104        $level = $this->mapErrorLevel($params['level']);
105        if ($size < 30) {
106            // In the old system, the margin was multiplied by the size....
107            $margin *= $size;
108
109            // Do some magic math to adjust the QR code size to accommodate the
110            // length of the text and the quality level. This is probably not the
111            // smartest way to do this, but it seems good enough for VuFind's
112            // limited needs.
113            $sizeIncrement = ceil(ceil(sqrt(strlen($text))) / 10);
114            if ($level == ErrorCorrectionLevel::High) {
115                $sizeIncrement *= 38;
116            } elseif ($level == ErrorCorrectionLevel::Quartile) {
117                $sizeIncrement *= 34;
118            } else {
119                $sizeIncrement *= 30;
120            }
121
122            // Put it all together:
123            $size = $size * $sizeIncrement - $params['margin'];
124        }
125
126        // Fetch image:
127        if (!$this->fetchQRCode($text, $size, $margin, $level)) {
128            $this->loadUnavailable();
129        }
130    }
131
132    /**
133     * Map an incoming error correction level parameter to a valid constant.
134     *
135     * @param string $level Error correction level parameter
136     *
137     * @return ErrorCorrectionLevel
138     */
139    protected function mapErrorLevel($level): ErrorCorrectionLevel
140    {
141        switch (strtoupper(substr($level, 0, 1))) {
142            case '3':
143            case 'H':
144                return ErrorCorrectionLevel::High;
145            case '2':
146            case 'Q':
147                return ErrorCorrectionLevel::Quartile;
148            case '1':
149            case 'M':
150                return ErrorCorrectionLevel::Medium;
151            case '0':
152            case 'L':
153            default:
154                return ErrorCorrectionLevel::Low;
155        }
156    }
157
158    /**
159     * Generate a QR code image
160     *
161     * @param string                        $text   The QR code text
162     * @param int                           $size   QR code width/height (in pixels)
163     * @param int                           $margin QR code margin (in pixels)
164     * @param ErrorCorrectionLevelInterface $level  Error correction level object
165     *
166     * @return bool True if image displayed, false on failure.
167     */
168    protected function fetchQRCode($text, $size, $margin, $level)
169    {
170        if (strlen(trim($text)) == 0) {
171            return false;
172        }
173
174        // Build the code:
175        try {
176            $code = new QrCode($text);
177            $code->setMargin($margin);
178            $code->setErrorCorrectionLevel($level);
179            $code->setSize($size);
180            $code->setEncoding(new \Endroid\QrCode\Encoding\Encoding('UTF-8'));
181            $code->setRoundBlockSizeMode(\Endroid\QrCode\RoundBlockSizeMode::None);
182
183            // Save the values.
184            $writer = new PngWriter();
185            $result = $writer->write($code);
186            $this->contentType = $result->getMimeType();
187            $this->image = $result->getString();
188        } catch (\Exception $e) {
189            return false;
190        }
191        return true;
192    }
193}