Completed
Push — master ( 29743b...f250f7 )
by Mikael
03:04
created

CRemoteImage.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Get a image from a remote server using HTTP GET and If-Modified-Since.
4
 *
5
 */
6
class CRemoteImage
7
{
8
    /**
9
     * Path to cache files.
10
     */
11
    private $saveFolder = null;
12
13
14
15
    /**
16
     * Use cache or not.
17
     */
18
    private $useCache = true;
19
20
21
22
    /**
23
     * HTTP object to aid in download file.
24
     */
25
    private $http;
26
27
28
29
    /**
30
     * Status of the HTTP request.
31
     */
32
    private $status;
33
34
35
36
    /**
37
     * Defalt age for cached items 60*60*24*7.
38
     */
39
    private $defaultMaxAge = 604800;
40
41
42
43
    /**
44
     * Url of downloaded item.
45
     */
46
    private $url;
47
48
49
50
    /**
51
     * Base name of cache file for downloaded item and name of image.
52
     */
53
    private $fileName;
54
55
56
57
    /**
58
     * Filename for json-file with details of cached item.
59
     */
60
    private $fileJson;
61
62
63
64
    /**
65
     * Cache details loaded from file.
66
     */
67
    private $cache;
68
69
70
71
    /**
72
     * Get status of last HTTP request.
73
     *
74
     * @return int as status
75
     */
76
    public function getStatus()
77
    {
78
        return $this->status;
79
    }
80
81
82
83
    /**
84
     * Get JSON details for cache item.
85
     *
86
     * @return array with json details on cache.
87
     */
88
    public function getDetails()
89
    {
90
        return $this->cache;
91
    }
92
93
94
95
    /**
96
     * Set the path to the cache directory.
97
     *
98
     * @param boolean $use true to use the cache and false to ignore cache.
99
     *
100
     * @return $this
101
     */
102
    public function setCache($path)
103
    {
104
        $this->saveFolder = rtrim($path, "/") . "/";
105
        return $this;
106
    }
107
108
109
110
    /**
111
     * Check if cache is writable or throw exception.
112
     *
113
     * @return $this
114
     *
115
     * @throws Exception if cahce folder is not writable.
116
     */
117
    public function isCacheWritable()
118
    {
119
        if (!is_writable($this->saveFolder)) {
120
            throw new Exception("Cache folder is not writable for downloaded files.");
121
        }
122
        return $this;
123
    }
124
125
126
127
    /**
128
     * Decide if the cache should be used or not before trying to download
129
     * a remote file.
130
     *
131
     * @param boolean $use true to use the cache and false to ignore cache.
132
     *
133
     * @return $this
134
     */
135
    public function useCache($use = true)
136
    {
137
        $this->useCache = $use;
138
        return $this;
139
    }
140
141
142
143
    /**
144
     * Set header fields.
145
     *
146
     * @return $this
147
     */
148
    public function setHeaderFields()
149
    {
150
        $cimageVersion = "CImage";
151
        if (defined("CIMAGE_USER_AGENT")) {
152
            $cimageVersion = CIMAGE_USER_AGENT;
153
        }
154
        
155
        $this->http->setHeader("User-Agent", "$cimageVersion (PHP/". phpversion() . " cURL)");
156
        $this->http->setHeader("Accept", "image/jpeg,image/png,image/gif");
157
158
        if ($this->useCache) {
159
            $this->http->setHeader("Cache-Control", "max-age=0");
160
        } else {
161
            $this->http->setHeader("Cache-Control", "no-cache");
162
            $this->http->setHeader("Pragma", "no-cache");
163
        }
164
    }
165
166
167
168
    /**
169
     * Save downloaded resource to cache.
170
     *
171
     * @return string as path to saved file or false if not saved.
172
     */
173
    public function save()
174
    {
175
        $this->cache = array();
176
        $date         = $this->http->getDate(time());
177
        $maxAge       = $this->http->getMaxAge($this->defaultMaxAge);
178
        $lastModified = $this->http->getLastModified();
179
        $type         = $this->http->getContentType();
180
181
        $this->cache['Date']           = gmdate("D, d M Y H:i:s T", $date);
182
        $this->cache['Max-Age']        = $maxAge;
183
        $this->cache['Content-Type']   = $type;
184
        $this->cache['Url']            = $this->url;
185
186
        if ($lastModified) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastModified of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
187
            $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified);
188
        }
189
190
        // Save only if body is a valid image
191
        $body = $this->http->getBody();
192
        $img = imagecreatefromstring($body);
193
194
        if ($img !== false) {
195
            file_put_contents($this->fileName, $body);
196
            file_put_contents($this->fileJson, json_encode($this->cache));
197
            return $this->fileName;
198
        }
199
200
        return false;
201
    }
202
203
204
205
    /**
206
     * Got a 304 and updates cache with new age.
207
     *
208
     * @return string as path to cached file.
209
     */
210
    public function updateCacheDetails()
211
    {
212
        $date         = $this->http->getDate(time());
213
        $maxAge       = $this->http->getMaxAge($this->defaultMaxAge);
214
        $lastModified = $this->http->getLastModified();
215
216
        $this->cache['Date']    = gmdate("D, d M Y H:i:s T", $date);
217
        $this->cache['Max-Age'] = $maxAge;
218
219
        if ($lastModified) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastModified of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
220
            $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified);
221
        }
222
223
        file_put_contents($this->fileJson, json_encode($this->cache));
224
        return $this->fileName;
225
    }
226
227
228
229
    /**
230
     * Download a remote file and keep a cache of downloaded files.
231
     *
232
     * @param string $url a remote url.
233
     *
234
     * @throws Exception when status code does not match 200 or 304.
235
     *
236
     * @return string as path to downloaded file or false if failed.
237
     */
238
    public function download($url)
239
    {
240
        $this->http = new CHttpGet();
241
        $this->url = $url;
242
243
        // First check if the cache is valid and can be used
244
        $this->loadCacheDetails();
245
246
        if ($this->useCache) {
247
            $src = $this->getCachedSource();
248
            if ($src) {
249
                $this->status = 1;
250
                return $src;
251
            }
252
        }
253
254
        // Do a HTTP request to download item
255
        $this->setHeaderFields();
256
        $this->http->setUrl($this->url);
257
        $this->http->doGet();
258
259
        $this->status = $this->http->getStatus();
260
        if ($this->status === 200) {
261
            $this->isCacheWritable();
262
            return $this->save();
263
        } elseif ($this->status === 304) {
264
            $this->isCacheWritable();
265
            return $this->updateCacheDetails();
266
        }
267
268
        throw new Exception("Unknown statuscode when downloading remote image: " . $this->status);
269
    }
270
271
272
273
    /**
274
     * Get the path to the cached image file if the cache is valid.
275
     *
276
     * @return $this
277
     */
278
    public function loadCacheDetails()
279
    {
280
        $cacheFile = md5($this->url);
281
        $this->fileName = $this->saveFolder . $cacheFile;
282
        $this->fileJson = $this->fileName . ".json";
283
        if (is_readable($this->fileJson)) {
284
            $this->cache = json_decode(file_get_contents($this->fileJson), true);
285
        }
286
    }
287
288
289
290
    /**
291
     * Get the path to the cached image file if the cache is valid.
292
     *
293
     * @return string as the path ot the image file or false if no cache.
294
     */
295
    public function getCachedSource()
296
    {
297
        $imageExists = is_readable($this->fileName);
298
299
        // Is cache valid?
300
        $date   = strtotime($this->cache['Date']);
301
        $maxAge = $this->cache['Max-Age'];
302
        $now    = time();
303
        
304
        if ($imageExists && $date + $maxAge > $now) {
305
            return $this->fileName;
306
        }
307
308
        // Prepare for a 304 if available
309
        if ($imageExists && isset($this->cache['Last-Modified'])) {
310
            $this->http->setHeader("If-Modified-Since", $this->cache['Last-Modified']);
311
        }
312
313
        return false;
314
    }
315
}
316