Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
23.20% |
58 / 250 |
|
14.29% |
3 / 21 |
CRAP | |
0.00% |
0 / 1 |
Loader | |
23.20% |
58 / 250 |
|
14.29% |
3 / 21 |
4721.90 | |
0.00% |
0 / 1 |
__construct | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
2.01 | |||
getCoverGeneratorSettings | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
42 | |||
setCoverGenerator | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultSettings | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
1 | |||
getImageSettingsFromLegacyArgs | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
storeSanitizedSettings | |
90.00% |
18 / 20 |
|
0.00% |
0 / 1 |
3.01 | |||
loadImage | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
42 | |||
loadUnavailable | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
hasLoadedUnavailable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
determineLocalFile | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
156 | |||
getIdentifiers | |
52.38% |
11 / 21 |
|
0.00% |
0 / 1 |
52.99 | |||
fetchFromAPI | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
getCachePath | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
fetchFromContentType | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
convertNonJpeg | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
validateAndMoveTempFile | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
processImageURLForSource | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
132 | |||
processImageURL | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
72 | |||
getCoverUrls | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
30 | |||
getHandlers | |
16.67% |
2 / 12 |
|
0.00% |
0 / 1 |
13.26 | |||
getIdentifiersForSettings | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * Book Cover 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 Cover_Generator |
25 | * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> |
26 | * @author Demian Katz <demian.katz@villanova.edu> |
27 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
28 | * @link https://vufind.org/wiki/configuration:external_content Wiki |
29 | */ |
30 | |
31 | namespace VuFind\Cover; |
32 | |
33 | use VuFind\Content\Covers\PluginManager as ApiManager; |
34 | use VuFindCode\ISBN; |
35 | use VuFindCode\ISMN; |
36 | |
37 | use function func_get_args; |
38 | use function in_array; |
39 | use function is_array; |
40 | use function is_callable; |
41 | use function strlen; |
42 | |
43 | /** |
44 | * Book Cover Generator |
45 | * |
46 | * @category VuFind |
47 | * @package Cover_Generator |
48 | * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> |
49 | * @author Demian Katz <demian.katz@villanova.edu> |
50 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
51 | * @link https://vufind.org/wiki/configuration:external_content Wiki |
52 | */ |
53 | class Loader extends \VuFind\ImageLoader |
54 | { |
55 | /** |
56 | * Class for rendering cover images dynamically if no API match found. Omit |
57 | * to disable functionality. |
58 | * |
59 | * @var Generator |
60 | */ |
61 | protected $generator = null; |
62 | |
63 | /** |
64 | * Filename constructed from ISBN |
65 | * |
66 | * @var string |
67 | */ |
68 | protected $localFile = ''; |
69 | |
70 | /** |
71 | * Valid image sizes to request |
72 | * |
73 | * @var array |
74 | */ |
75 | protected $validSizes = ['small', 'medium', 'large']; |
76 | |
77 | /** |
78 | * VuFind configuration settings |
79 | * |
80 | * @var \Laminas\Config\Config |
81 | */ |
82 | protected $config; |
83 | |
84 | /** |
85 | * Plugin manager for API handlers |
86 | * |
87 | * @var ApiManager |
88 | */ |
89 | protected $apiManager; |
90 | |
91 | /** |
92 | * HTTP client factory |
93 | * |
94 | * @var \VuFindHttp\HttpService |
95 | */ |
96 | protected $httpService; |
97 | |
98 | /** |
99 | * Directory to store downloaded images |
100 | * |
101 | * @var string |
102 | */ |
103 | protected $baseDir; |
104 | |
105 | /** |
106 | * User ISBNs parameter |
107 | * |
108 | * @var ISBN[] |
109 | */ |
110 | protected $isbns = null; |
111 | |
112 | /** |
113 | * User ISSN parameter |
114 | * |
115 | * @var string |
116 | */ |
117 | protected $issn = null; |
118 | |
119 | /** |
120 | * User OCLC number parameter |
121 | * |
122 | * @var string |
123 | */ |
124 | protected $oclc = null; |
125 | |
126 | /** |
127 | * User UPC number parameter |
128 | * |
129 | * @var string |
130 | */ |
131 | protected $upc = null; |
132 | |
133 | /** |
134 | * User National bibliography number parameter |
135 | * |
136 | * @var array |
137 | */ |
138 | protected $nbn = null; |
139 | |
140 | /** |
141 | * User ISMN parameter |
142 | * |
143 | * @var ISMN |
144 | */ |
145 | protected $ismn = null; |
146 | |
147 | /** |
148 | * User UUID parameter |
149 | * |
150 | * @var string |
151 | */ |
152 | protected $uuid = null; |
153 | |
154 | /** |
155 | * User record id number parameter |
156 | * |
157 | * @var string |
158 | */ |
159 | protected $recordid = null; |
160 | |
161 | /** |
162 | * User record source parameter |
163 | * |
164 | * @var string |
165 | */ |
166 | protected $source = null; |
167 | |
168 | /** |
169 | * User size parameter |
170 | * |
171 | * @var string |
172 | */ |
173 | protected $size; |
174 | |
175 | /** |
176 | * User type parameter |
177 | * |
178 | * @var string |
179 | */ |
180 | protected $type; |
181 | |
182 | /** |
183 | * Flag denoting the last loaded image was a FailImage |
184 | * |
185 | * @var bool |
186 | */ |
187 | protected $hasLoadedUnavailable = false; |
188 | |
189 | /** |
190 | * Constructor |
191 | * |
192 | * @param \Laminas\Config\Config $config VuFind configuration |
193 | * @param ApiManager $manager Plugin manager for API handlers |
194 | * @param \VuFindTheme\ThemeInfo $theme VuFind theme tools |
195 | * @param \VuFindHttp\HttpService $httpService HTTP client factory |
196 | * @param string $baseDir Directory to store downloaded |
197 | * images (set to system temp dir if not otherwise specified) |
198 | */ |
199 | public function __construct( |
200 | $config, |
201 | ApiManager $manager, |
202 | \VuFindTheme\ThemeInfo $theme, |
203 | \VuFindHttp\HttpService $httpService, |
204 | $baseDir = null |
205 | ) { |
206 | $this->setThemeInfo($theme); |
207 | $this->config = $config; |
208 | $this->configuredFailImage = $config->Content->noCoverAvailableImage ?? null; |
209 | $this->apiManager = $manager; |
210 | $this->httpService = $httpService; |
211 | $this->baseDir = (null === $baseDir) |
212 | ? rtrim(sys_get_temp_dir(), '\\/') . '/covers' |
213 | : rtrim($baseDir, '\\/'); |
214 | } |
215 | |
216 | /** |
217 | * Get settings for the cover generator. |
218 | * |
219 | * @return array |
220 | */ |
221 | protected function getCoverGeneratorSettings() |
222 | { |
223 | $settings = isset($this->config->DynamicCovers) |
224 | ? $this->config->DynamicCovers->toArray() : []; |
225 | if ( |
226 | !isset($settings['backgroundMode']) |
227 | && isset($this->config->Content->makeDynamicCovers) |
228 | ) { |
229 | $settings['backgroundMode'] = $this->config->Content->makeDynamicCovers; |
230 | } |
231 | $size = $this->size; |
232 | $pickSize = function ($setting) use ($size) { |
233 | if (isset($setting[$size])) { |
234 | return $setting[$size]; |
235 | } |
236 | if (isset($setting['*'])) { |
237 | return $setting['*']; |
238 | } |
239 | return $setting; |
240 | }; |
241 | return array_map($pickSize, $settings); |
242 | } |
243 | |
244 | /** |
245 | * Set Cover Generator Object |
246 | * |
247 | * @param Generator $generator Cover generator |
248 | * |
249 | * @return void |
250 | */ |
251 | public function setCoverGenerator(Generator $generator) |
252 | { |
253 | $this->generator = $generator; |
254 | } |
255 | |
256 | /** |
257 | * Get default settings for loadImage(). |
258 | * |
259 | * @return array |
260 | */ |
261 | protected function getDefaultSettings() |
262 | { |
263 | return [ |
264 | 'isbns' => null, |
265 | 'size' => 'small', |
266 | 'type' => null, |
267 | 'title' => null, |
268 | 'author' => null, |
269 | 'callnumber' => null, |
270 | 'issn' => null, |
271 | 'oclc' => null, |
272 | 'upc' => null, |
273 | 'recordid' => null, |
274 | 'source' => null, |
275 | 'nbn' => null, |
276 | 'ismn' => null, |
277 | 'uuid' => null, |
278 | ]; |
279 | } |
280 | |
281 | /** |
282 | * Translate legacy function arguments into new-style array. |
283 | * |
284 | * @param array $args Function arguments |
285 | * |
286 | * @return array |
287 | */ |
288 | protected function getImageSettingsFromLegacyArgs($args) |
289 | { |
290 | return [ |
291 | 'isbn' => $args[0], |
292 | 'size' => $args[1], |
293 | 'type' => $args[2], |
294 | 'title' => $args[3], |
295 | 'author' => $args[4], |
296 | 'callnumber' => $args[5], |
297 | 'issn' => $args[6], |
298 | 'oclc' => $args[7], |
299 | 'upc' => $args[8], |
300 | ]; |
301 | } |
302 | |
303 | /** |
304 | * Support method for loadImage() -- sanitize and store some key values. |
305 | * |
306 | * @param array $settings Settings from loadImage |
307 | * |
308 | * @return void |
309 | */ |
310 | protected function storeSanitizedSettings($settings) |
311 | { |
312 | $settings = array_merge($this->getDefaultSettings(), $settings); |
313 | $this->isbns = array_map( |
314 | function ($isbn) { |
315 | return new ISBN($isbn); |
316 | }, |
317 | $settings['isbns'] |
318 | ?? (empty($settings['isbn']) ? [] : [$settings['isbn']]) |
319 | ); |
320 | $this->ismn = new ISMN($settings['ismn'] ?? ''); |
321 | if (!empty($settings['issn'])) { |
322 | $rawissn = preg_replace('/[^0-9X]/', '', strtoupper($settings['issn'])); |
323 | $this->issn = substr($rawissn, 0, 8); |
324 | } else { |
325 | $this->issn = null; |
326 | } |
327 | $this->oclc = $settings['oclc']; |
328 | $this->upc = $settings['upc']; |
329 | $this->recordid = $settings['recordid']; |
330 | $this->source = $settings['source']; |
331 | $this->nbn = $settings['nbn']; |
332 | $this->uuid = $settings['uuid']; |
333 | $this->type = preg_replace('/[^a-zA-Z]/', '', $settings['type'] ?? ''); |
334 | $this->size = $settings['size']; |
335 | } |
336 | |
337 | /** |
338 | * Load an image given an ISBN and/or content type. |
339 | * |
340 | * @param array $settings Array of settings used to calculate a cover; may |
341 | * contain any or all of these keys: 'isbns' (array of ISBNs), 'size' (requested |
342 | * size), 'type' (content type), 'title' (title of book, for dynamic covers), |
343 | * 'author' (author of book, for dynamic covers), 'callnumber' (unique ID, for |
344 | * dynamic covers), 'issn' (ISSN), 'oclc' (OCLC number), 'upc' (UPC number), |
345 | * 'nbn' (national bibliography number), 'ismn' (ISMN), 'uuid' (Universally |
346 | * unique identifier). |
347 | * |
348 | * @return void |
349 | */ |
350 | public function loadImage($settings = []) |
351 | { |
352 | // reset to normal |
353 | $this->hasLoadedUnavailable = false; |
354 | // Load settings from legacy function parameters if they are not passed |
355 | // in as an array: |
356 | $settings = is_array($settings) |
357 | ? $settings |
358 | : $this->getImageSettingsFromLegacyArgs(func_get_args()); |
359 | |
360 | // Store sanitized versions of some parameters for future reference: |
361 | $this->storeSanitizedSettings($settings); |
362 | |
363 | // Display a fail image unless our parameters pass inspection and we |
364 | // are able to display an ISBN or content-type-based image. |
365 | if (!in_array($this->size, $this->validSizes)) { |
366 | $this->loadUnavailable(); |
367 | } elseif ( |
368 | !$this->fetchFromAPI() |
369 | && !$this->fetchFromContentType() |
370 | ) { |
371 | if ($this->generator) { |
372 | $this->generator->setOptions($this->getCoverGeneratorSettings()); |
373 | $this->image = $this->generator->generate( |
374 | $settings['title'], |
375 | $settings['author'], |
376 | $settings['callnumber'] |
377 | ); |
378 | $this->contentType = 'image/png'; |
379 | } else { |
380 | $this->loadUnavailable(); |
381 | } |
382 | } |
383 | } |
384 | |
385 | /** |
386 | * {@inheritdoc} |
387 | * Adds @see self::$hasLoadedUnavailable flag |
388 | * |
389 | * @return void |
390 | */ |
391 | public function loadUnavailable() |
392 | { |
393 | $this->hasLoadedUnavailable = true; |
394 | parent::loadUnavailable(); |
395 | } |
396 | |
397 | /** |
398 | * Returns true if the last loaded image was the FailImage |
399 | * |
400 | * @return bool |
401 | */ |
402 | public function hasLoadedUnavailable() |
403 | { |
404 | return $this->hasLoadedUnavailable; |
405 | } |
406 | |
407 | /** |
408 | * Support method for fetchFromAPI() -- set the localFile property. |
409 | * |
410 | * @param array $ids IDs returned by getIdentifiers() method |
411 | * |
412 | * @return string |
413 | */ |
414 | protected function determineLocalFile($ids) |
415 | { |
416 | // We should check whether we have cached images for the 13- or 10-digit |
417 | // ISBNs. If no file exists, we'll favor the 10-digit number if |
418 | // available for the sake of brevity. |
419 | if (isset($ids['isbn'])) { |
420 | $file = $this->getCachePath($this->size, $ids['isbn']->get13()); |
421 | if (!is_readable($file) && $ids['isbn']->get10()) { |
422 | return $this->getCachePath($this->size, $ids['isbn']->get10()); |
423 | } |
424 | return $file; |
425 | } elseif (isset($ids['issn'])) { |
426 | return $this->getCachePath($this->size, $ids['issn']); |
427 | } elseif (isset($ids['oclc'])) { |
428 | return $this->getCachePath($this->size, 'OCLC' . $ids['oclc']); |
429 | } elseif (isset($ids['upc'])) { |
430 | return $this->getCachePath($this->size, 'UPC' . $ids['upc']); |
431 | } elseif (isset($ids['nbn'])) { |
432 | return $this->getCachePath($this->size, 'NBN' . $ids['nbn']); |
433 | } elseif (isset($ids['ismn'])) { |
434 | return $this->getCachePath($this->size, 'ISMN' . $ids['ismn']->get13()); |
435 | } elseif (isset($ids['uuid'])) { |
436 | return $this->getCachePath($this->size, 'UUID' . $ids['uuid']); |
437 | } elseif (isset($ids['recordid']) && isset($ids['source'])) { |
438 | return $this->getCachePath( |
439 | $this->size, |
440 | 'ID' . md5($ids['source'] . '|' . $ids['recordid']) |
441 | ); |
442 | } |
443 | throw new \Exception('Cannot determine local file path.'); |
444 | } |
445 | |
446 | /** |
447 | * Get all valid identifiers as an associative array. |
448 | * |
449 | * @return array |
450 | */ |
451 | protected function getIdentifiers() |
452 | { |
453 | $ids = []; |
454 | if (!empty($this->isbns)) { |
455 | $ids['isbn'] = $this->isbns[0]; |
456 | $ids['isbns'] = $this->isbns; |
457 | } |
458 | if ($this->issn && strlen($this->issn) == 8) { |
459 | $ids['issn'] = $this->issn; |
460 | } |
461 | if ($this->oclc && strlen($this->oclc) > 0) { |
462 | $ids['oclc'] = $this->oclc; |
463 | } |
464 | if ($this->upc && strlen($this->upc) > 0) { |
465 | $ids['upc'] = $this->upc; |
466 | } |
467 | if ($this->nbn && strlen($this->nbn) > 0) { |
468 | $ids['nbn'] = $this->nbn; |
469 | } |
470 | if ($this->ismn && $this->ismn->isValid()) { |
471 | $ids['ismn'] = $this->ismn; |
472 | } |
473 | if ($this->uuid && strlen($this->uuid) > 0) { |
474 | $ids['uuid'] = $this->uuid; |
475 | } |
476 | if ($this->recordid && strlen($this->recordid) > 0) { |
477 | $ids['recordid'] = $this->recordid; |
478 | } |
479 | if ($this->source && strlen($this->source) > 0) { |
480 | $ids['source'] = $this->source; |
481 | } |
482 | return $ids; |
483 | } |
484 | |
485 | /** |
486 | * Load bookcover from cache or remote provider and display if possible. |
487 | * |
488 | * @return bool True if image loaded, false on failure. |
489 | */ |
490 | protected function fetchFromAPI() |
491 | { |
492 | // Check that we have at least one valid identifier: |
493 | $ids = $this->getIdentifiers(); |
494 | if (empty($ids)) { |
495 | return false; |
496 | } |
497 | |
498 | // Set up local file path: |
499 | $this->localFile = $this->determineLocalFile($ids); |
500 | if (is_readable($this->localFile)) { |
501 | // Load local cache if available |
502 | $this->contentType = 'image/jpeg'; |
503 | $this->image = file_get_contents($this->localFile); |
504 | return true; |
505 | } else { |
506 | $urls = $this->getCoverUrls(); |
507 | foreach ($urls as $url) { |
508 | $success = $this->processImageURLForSource( |
509 | $url['url'], |
510 | $url['handler']->isCacheAllowed(), |
511 | $url['apiName'] |
512 | ); |
513 | if ($success) { |
514 | return true; |
515 | } |
516 | } |
517 | } |
518 | return false; |
519 | } |
520 | |
521 | /** |
522 | * Return a path to the image cache for the given size and ID; ensure that |
523 | * directories are created as needed. |
524 | * |
525 | * @param string $size Size category |
526 | * @param string $id Unique identifier (ISBN / ISSN) |
527 | * @param string $extension File extension to use (default = jpg) |
528 | * |
529 | * @return string Cache path |
530 | */ |
531 | protected function getCachePath($size, $id, $extension = 'jpg') |
532 | { |
533 | $base = $this->baseDir; |
534 | if (!is_dir($base)) { |
535 | mkdir($base); |
536 | } |
537 | $base .= '/' . $size; |
538 | if (!is_dir($base)) { |
539 | mkdir($base); |
540 | } |
541 | return $base . '/' . $id . '.' . $extension; |
542 | } |
543 | |
544 | /** |
545 | * Load content type icon image from URL from theme images and display if |
546 | * possible. |
547 | * |
548 | * @return bool True if image loaded, false on failure. |
549 | */ |
550 | protected function fetchFromContentType() |
551 | { |
552 | // Give up if no content type was passed in: |
553 | if (empty($this->type)) { |
554 | return false; |
555 | } |
556 | |
557 | // Try to find an icon: |
558 | $iconFile = $this->searchTheme( |
559 | 'images/' . $this->size . '/' . $this->type, |
560 | ['.png', '.gif', '.jpg'] |
561 | ); |
562 | if ($iconFile !== false) { |
563 | // Most content-type headers match file extensions... but |
564 | // include a special case for jpg vs. jpeg: |
565 | $format = substr($iconFile, -3); |
566 | $this->contentType |
567 | = 'image/' . ($format == 'jpg' ? 'jpeg' : $format); |
568 | $this->image = file_get_contents($iconFile); |
569 | return true; |
570 | } |
571 | |
572 | // If we got this far, no icon was found: |
573 | return false; |
574 | } |
575 | |
576 | /** |
577 | * Support method for validateAndMoveTempFile -- convert non-JPEG image data to a |
578 | * JPEG file. |
579 | * |
580 | * @param string $imageData Raw image data |
581 | * @param string $jpeg JPEG file (output) |
582 | * |
583 | * @return bool Did we succeed? |
584 | */ |
585 | protected function convertNonJpeg($imageData, $jpeg) |
586 | { |
587 | // We can't proceed if we don't have image conversion functions: |
588 | if (!is_callable('imagecreatefromstring')) { |
589 | return false; |
590 | } |
591 | |
592 | // Try to create a GD image and rewrite as JPEG, fail if we can't: |
593 | if (!($imageGD = @imagecreatefromstring($imageData))) { |
594 | return false; |
595 | } |
596 | if (!@imagejpeg($imageGD, $jpeg)) { |
597 | return false; |
598 | } |
599 | |
600 | return true; |
601 | } |
602 | |
603 | /** |
604 | * This method either moves the temporary file to its final location (true) |
605 | * or detects an error and deletes it (false). |
606 | * |
607 | * @param string $image Raw image data |
608 | * @param string $tempFile Temporary file |
609 | * @param string $finalFile Final file location |
610 | * |
611 | * @return bool |
612 | */ |
613 | protected function validateAndMoveTempFile($image, $tempFile, $finalFile) |
614 | { |
615 | [$width, $height, $type] = @getimagesize($tempFile); |
616 | |
617 | // File too small -- delete it and report failure. |
618 | if ($width < 2 && $height < 2) { |
619 | @unlink($tempFile); |
620 | return false; |
621 | } |
622 | |
623 | // Conversion needed -- do some normalization for non-JPEG images: |
624 | if ($type != IMAGETYPE_JPEG) { |
625 | // We no longer need the temp file: |
626 | @unlink($tempFile); |
627 | return $this->convertNonJpeg($image, $finalFile); |
628 | } |
629 | |
630 | // If $tempFile is already a JPEG, let's store it in the cache. |
631 | return @rename($tempFile, $finalFile); |
632 | } |
633 | |
634 | /** |
635 | * Wrapper around processImageURL to determine cache setting based on |
636 | * image source. |
637 | * |
638 | * @param string $url URL to load image from |
639 | * @param bool $allowCache Is caching allowed by the service? |
640 | * @param string $source Service being used for image loading |
641 | * |
642 | * @return bool True if image loaded, false on failure. |
643 | */ |
644 | protected function processImageURLForSource($url, $allowCache, $source) |
645 | { |
646 | // If caching is allowed at the source level, let's see if it's locally |
647 | // configured.... |
648 | if ($allowCache) { |
649 | // All other services cache based on configuration: |
650 | $conf = isset($this->config->Content->coverimagesCache) |
651 | ? trim(strtolower($this->config->Content->coverimagesCache)) : true; |
652 | if ($conf === true || $conf === 1 || $conf === '1' || $conf === 'true') { |
653 | $cache = true; |
654 | } elseif ( |
655 | $conf === false || $conf === 0 || $conf === '0' |
656 | || $conf === 'false' |
657 | ) { |
658 | $cache = false; |
659 | } else { |
660 | $conf = array_map('trim', explode(',', $conf)); |
661 | $source = strtolower($source); |
662 | $cache = in_array($source, $conf); |
663 | } |
664 | } else { |
665 | $cache = false; |
666 | } |
667 | return $this->processImageURL($url, $cache); |
668 | } |
669 | |
670 | /** |
671 | * Load image from URL, store in cache if requested, display if possible. |
672 | * |
673 | * @param string $url URL to load image from |
674 | * @param string $cache Boolean -- should we store in local cache? |
675 | * |
676 | * @return bool True if image loaded, false on failure. |
677 | */ |
678 | protected function processImageURL($url, $cache = true) |
679 | { |
680 | // Check to see if url is a file path |
681 | if (str_starts_with($url, 'file://')) { |
682 | $imagePath = substr($url, 7); |
683 | |
684 | // Display the image: |
685 | $this->contentType = mime_content_type($imagePath); |
686 | $this->image = file_get_contents($imagePath); |
687 | return true; |
688 | } else { |
689 | // Attempt to pull down the image: |
690 | $result = $this->httpService->createClient($url)->send(); |
691 | if (!$result->isSuccess()) { |
692 | $this->debug('Failed to retrieve image from ' . $url); |
693 | return false; |
694 | } |
695 | $image = $result->getBody(); |
696 | |
697 | if ('' == $image) { |
698 | return false; |
699 | } |
700 | |
701 | // Figure out file paths -- $tempFile will be used to store the |
702 | // image for analysis. $finalFile will be used for long-term storage if |
703 | // $cache is true or for temporary display purposes if $cache is false. |
704 | $tempFile = str_replace('.jpg', uniqid(), $this->localFile); |
705 | $finalFile = $cache ? $this->localFile : $tempFile . '.jpg'; |
706 | |
707 | // Write image data to disk: |
708 | if (!@file_put_contents($tempFile, $image)) { |
709 | throw new \Exception('Unable to write to image directory.'); |
710 | } |
711 | |
712 | // Move temporary file to final location: |
713 | if (!$this->validateAndMoveTempFile($image, $tempFile, $finalFile)) { |
714 | return false; |
715 | } |
716 | |
717 | // Display the image: |
718 | $this->contentType = 'image/jpeg'; |
719 | $this->image = file_get_contents($finalFile); |
720 | |
721 | // If we don't want to cache the image, delete it now that we're done. |
722 | if (!$cache) { |
723 | @unlink($finalFile); |
724 | } |
725 | return true; |
726 | } |
727 | } |
728 | |
729 | /** |
730 | * Get urls for defined provider, works as generator |
731 | * |
732 | * @return array |
733 | */ |
734 | protected function getCoverUrls() |
735 | { |
736 | $ids = $this->getIdentifiers(); |
737 | $handlers = $this->getHandlers(); |
738 | foreach ($handlers as $handler) { |
739 | try { |
740 | // Is the current provider appropriate for the available data? |
741 | if ($handler['handler']->supports($ids)) { |
742 | $url = $handler['handler'] |
743 | ->getUrl($handler['key'], $this->size, $ids); |
744 | if ($url) { |
745 | yield [ |
746 | 'url' => $url, |
747 | 'apiName' => $handler['apiName'], |
748 | 'handler' => $handler['handler'], |
749 | ]; |
750 | } |
751 | } |
752 | } catch (\Exception $e) { |
753 | $this->debug( |
754 | $e::class . ' during processing of ' . $handler['apiName'] |
755 | . ': ' . $e->getMessage() |
756 | ); |
757 | } |
758 | } |
759 | } |
760 | |
761 | /** |
762 | * Return API handlers |
763 | * |
764 | * @return \Generator Array with keys: key - API key, apiName - api name from |
765 | * configuration, handler - handler object |
766 | */ |
767 | public function getHandlers() |
768 | { |
769 | if (!isset($this->config->Content->coverimages)) { |
770 | return []; |
771 | } |
772 | $providers = explode(',', $this->config->Content->coverimages); |
773 | foreach ($providers as $provider) { |
774 | $provider = explode(':', trim($provider)); |
775 | $apiName = strtolower(trim($provider[0])); |
776 | $key = isset($provider[1]) ? trim($provider[1]) : null; |
777 | yield [ |
778 | 'key' => $key, |
779 | 'apiName' => $apiName, |
780 | 'handler' => $this->apiManager->get($apiName), |
781 | ]; |
782 | } |
783 | } |
784 | |
785 | /** |
786 | * Get identifiers for given settings |
787 | * |
788 | * @param array $settings Settings from loadImage |
789 | * |
790 | * @return array |
791 | */ |
792 | public function getIdentifiersForSettings($settings) |
793 | { |
794 | $this->storeSanitizedSettings($settings); |
795 | return $this->getIdentifiers(); |
796 | } |
797 | } |