Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
CachingProxy
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 6
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 fetch
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 fetchCache
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setCache
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 hasLegalHost
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getCacheFile
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * Caching Proxy for Cover Images
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2015.
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   Demian Katz <demian.katz@villanova.edu>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/configuration:external_content Wiki
28 */
29
30namespace VuFind\Cover;
31
32use Laminas\Http\Client;
33use Laminas\Http\Response;
34
35use function dirname;
36
37/**
38 * Caching Proxy for Cover Images
39 *
40 * @category VuFind
41 * @package  Cover_Generator
42 * @author   Demian Katz <demian.katz@villanova.edu>
43 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
44 * @link     https://vufind.org/wiki/configuration:external_content Wiki
45 */
46class CachingProxy
47{
48    /**
49     * HTTP client
50     *
51     * @var Client
52     */
53    protected $client;
54
55    /**
56     * Base directory for cache
57     *
58     * @var string
59     */
60    protected $cache;
61
62    /**
63     * Array of regular expressions for hosts to cache
64     *
65     * @var array
66     */
67    protected $allowedHosts;
68
69    /**
70     * Constructor
71     *
72     * @param Client  $client       HTTP client
73     * @param ?string $cache        Base directory for cache (null to disable caching)
74     * @param array   $allowedHosts Array of regular expressions for hosts to cache
75     */
76    public function __construct(Client $client, $cache, array $allowedHosts = [])
77    {
78        $this->client = $client;
79        $this->cache = $cache;
80        $this->allowedHosts = $allowedHosts;
81    }
82
83    /**
84     * Fetch an image from either a URL or the cache (as appropriate).
85     *
86     * @param string $url URL to fetch
87     *
88     * @return Response
89     */
90    public function fetch($url)
91    {
92        $file = $this->getCacheFile($url);
93        $cacheAllowed = $this->cache && $this->hasLegalHost($url);
94        if (!$cacheAllowed || !($response = $this->fetchCache($file))) {
95            $response = $this->client->setUri($url)->send();
96            if ($cacheAllowed) {
97                $this->setCache($file, $response);
98            }
99        }
100        return $response;
101    }
102
103    /**
104     * Load a response from cache (or return false if cache is missing).
105     *
106     * @param string $file Cache file to load
107     *
108     * @return bool|Response
109     */
110    protected function fetchCache($file)
111    {
112        return file_exists($file)
113            ? unserialize(file_get_contents($file))
114            : false;
115    }
116
117    /**
118     * Save a response to the cache.
119     *
120     * @param string   $file     Filename to update
121     * @param Response $response Response to write
122     *
123     * @return void
124     */
125    protected function setCache($file, Response $response)
126    {
127        if (!$this->cache) {
128            return; // don't write if cache is disabled
129        }
130        if (!file_exists($this->cache)) {
131            mkdir($this->cache);
132        }
133        if (!file_exists(dirname($file))) {
134            mkdir(dirname($file));
135        }
136        file_put_contents($file, serialize($response));
137    }
138
139    /**
140     * Check if the URL is on the configured list for caching.
141     *
142     * @param string $url URL to check
143     *
144     * @return bool
145     */
146    protected function hasLegalHost($url)
147    {
148        $host = parse_url($url, PHP_URL_HOST);
149        foreach ($this->allowedHosts as $current) {
150            if (preg_match($current, $host)) {
151                return true;
152            }
153        }
154        return false;
155    }
156
157    /**
158     * Get the cache filename corresponding with the provided URL.
159     *
160     * @param string $url URL
161     *
162     * @return string
163     * @throws \Exception
164     */
165    protected function getCacheFile($url)
166    {
167        if (!$this->cache) {
168            throw new \Exception('Unexpected call to getCacheFile -- cache is disabled.');
169        }
170        $hash = md5($url);
171        return $this->cache . '/' . substr($hash, 0, 3) . '/' . substr($hash, 3);
172    }
173}