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 = $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
        $this->http->setHeader("User-Agent", "CImage/0.7.2 (PHP/". phpversion() . " cURL)");
151
        $this->http->setHeader("Accept", "image/jpeg,image/png,image/gif");
152
153
        if ($this->useCache) {
154
            $this->http->setHeader("Cache-Control", "max-age=0");
155
        } else {
156
            $this->http->setHeader("Cache-Control", "no-cache");
157
            $this->http->setHeader("Pragma", "no-cache");
158
        }
159
    }
160
161
162
163
    /**
164
     * Save downloaded resource to cache.
165
     *
166
     * @return string as path to saved file or false if not saved.
167
     */
168
    public function save()
169
    {
170
        $this->cache = array();
171
        $date         = $this->http->getDate(time());
172
        $maxAge       = $this->http->getMaxAge($this->defaultMaxAge);
173
        $lastModified = $this->http->getLastModified();
174
        $type         = $this->http->getContentType();
175
176
        $this->cache['Date']           = gmdate("D, d M Y H:i:s T", $date);
177
        $this->cache['Max-Age']        = $maxAge;
178
        $this->cache['Content-Type']   = $type;
179
        $this->cache['Url']            = $this->url;
180
181
        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...
182
            $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified);
183
        }
184
185
        // Save only if body is a valid image
186
        $body = $this->http->getBody();
187
        $img = imagecreatefromstring($body);
188
189
        if ($img !== false) {
190
            file_put_contents($this->fileName, $body);
191
            file_put_contents($this->fileJson, json_encode($this->cache));
192
            return $this->fileName;
193
        }
194
195
        return false;
196
    }
197
198
199
200
    /**
201
     * Got a 304 and updates cache with new age.
202
     *
203
     * @return string as path to cached file.
204
     */
205
    public function updateCacheDetails()
206
    {
207
        $date         = $this->http->getDate(time());
208
        $maxAge       = $this->http->getMaxAge($this->defaultMaxAge);
209
        $lastModified = $this->http->getLastModified();
210
211
        $this->cache['Date']    = gmdate("D, d M Y H:i:s T", $date);
212
        $this->cache['Max-Age'] = $maxAge;
213
214
        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...
215
            $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified);
216
        }
217
218
        file_put_contents($this->fileJson, json_encode($this->cache));
219
        return $this->fileName;
220
    }
221
222
223
224
    /**
225
     * Download a remote file and keep a cache of downloaded files.
226
     *
227
     * @param string $url a remote url.
228
     *
229
     * @throws Exception when status code does not match 200 or 304.
230
     *
231
     * @return string as path to downloaded file or false if failed.
232
     */
233
    public function download($url)
234
    {
235
        $this->http = new CHttpGet();
236
        $this->url = $url;
237
238
        // First check if the cache is valid and can be used
239
        $this->loadCacheDetails();
240
241
        if ($this->useCache) {
242
            $src = $this->getCachedSource();
243
            if ($src) {
244
                $this->status = 1;
245
                return $src;
246
            }
247
        }
248
249
        // Do a HTTP request to download item
250
        $this->setHeaderFields();
251
        $this->http->setUrl($this->url);
252
        $this->http->doGet();
253
254
        $this->status = $this->http->getStatus();
255
        if ($this->status === 200) {
256
            $this->isCacheWritable();
257
            return $this->save();
258
        } elseif ($this->status === 304) {
259
            $this->isCacheWritable();
260
            return $this->updateCacheDetails();
261
        }
262
263
        throw new Exception("Unknown statuscode when downloading remote image: " . $this->status);
264
    }
265
266
267
268
    /**
269
     * Get the path to the cached image file if the cache is valid.
270
     *
271
     * @return $this
272
     */
273
    public function loadCacheDetails()
274
    {
275
        $cacheFile = md5($this->url);
276
        $this->fileName = $this->saveFolder . $cacheFile;
277
        $this->fileJson = $this->fileName . ".json";
278
        if (is_readable($this->fileJson)) {
279
            $this->cache = json_decode(file_get_contents($this->fileJson), true);
280
        }
281
    }
282
283
284
285
    /**
286
     * Get the path to the cached image file if the cache is valid.
287
     *
288
     * @return string as the path ot the image file or false if no cache.
289
     */
290
    public function getCachedSource()
291
    {
292
        $imageExists = is_readable($this->fileName);
293
294
        // Is cache valid?
295
        $date   = strtotime($this->cache['Date']);
296
        $maxAge = $this->cache['Max-Age'];
297
        $now    = time();
298
        
299
        if ($imageExists && $date + $maxAge > $now) {
300
            return $this->fileName;
301
        }
302
303
        // Prepare for a 304 if available
304
        if ($imageExists && isset($this->cache['Last-Modified'])) {
305
            $this->http->setHeader("If-Modified-Since", $this->cache['Last-Modified']);
306
        }
307
308
        return false;
309
    }
310
}
311