Completed
Push — develop ( a57607...19d170 )
by John
02:55
created

Image::cache()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 3
eloc 6
nc 2
nop 1
1
<?php
2
3
namespace Alpha\View\Widget;
4
5
use Alpha\Util\Logging\Logger;
6
use Alpha\Util\Config\ConfigProvider;
7
use Alpha\Model\Type\Integer;
8
use Alpha\Model\Type\Enum;
9
use Alpha\Model\Type\Boolean;
10
use Alpha\Model\Type\Double;
11
use Alpha\Exception\IllegalArguementException;
12
use Alpha\Controller\Controller;
13
use Alpha\Controller\Front\FrontController;
14
15
/**
16
 * A widget that can generate an image which is scaled to the screen resolution of the
17
 * user, and can be made secured to prevent hot-linking from remote sites.  Note that by
18
 * default, a jpg file will be returned (the source file can be jpg, png, or gif).
19
 *
20
 * @since 1.0
21
 *
22
 * @author John Collins <[email protected]>
23
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
24
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
25
 * All rights reserved.
26
 *
27
 * <pre>
28
 * Redistribution and use in source and binary forms, with or
29
 * without modification, are permitted provided that the
30
 * following conditions are met:
31
 *
32
 * * Redistributions of source code must retain the above
33
 *   copyright notice, this list of conditions and the
34
 *   following disclaimer.
35
 * * Redistributions in binary form must reproduce the above
36
 *   copyright notice, this list of conditions and the
37
 *   following disclaimer in the documentation and/or other
38
 *   materials provided with the distribution.
39
 * * Neither the name of the Alpha Framework nor the names
40
 *   of its contributors may be used to endorse or promote
41
 *   products derived from this software without specific
42
 *   prior written permission.
43
 *
44
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
45
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
46
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
47
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
48
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
49
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
50
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
51
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
52
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
53
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
54
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
55
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
56
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
57
 * </pre>
58
 */
59
class Image
60
{
61
    /**
62
     * The title of the image for alt text (optional).
63
     *
64
     * @var string
65
     *
66
     * @since 1.0
67
     */
68
    private $title;
0 ignored issues
show
Unused Code introduced by
The property $title is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
69
70
    /**
71
     * The absolute path to the source image.
72
     *
73
     * @var string
74
     *
75
     * @since 1.0
76
     */
77
    private $source;
78
79
    /**
80
     * The width of the image (can differ from the source file when scale=true).
81
     *
82
     * @var \Alpha\Model\Type\Integer
83
     *
84
     * @since 1.0
85
     */
86
    private $width;
87
88
    /**
89
     * The height of the image (can differ from the source file when scale=true).
90
     *
91
     * @var \Alpha\Model\Type\Integer
92
     *
93
     * @since 1.0
94
     */
95
    private $height;
96
97
    /**
98
     * The file type of the source image (gif, jpg, or png supported).
99
     *
100
     * @var \Alpha\Model\Type\Enum
101
     *
102
     * @since 1.0
103
     */
104
    private $sourceType;
105
106
    /**
107
     * The quality of the jpg image generated (0.00 to 1.00, 0.75 by default).
108
     *
109
     * @var \Alpha\Model\Type\Double
110
     *
111
     * @since 1.0
112
     */
113
    private $quality;
114
115
    /**
116
     * Flag to determine if the image will scale to match the target resolution (false
117
     * by default).
118
     *
119
     * @var \Alpha\Model\Type\Boolean
120
     *
121
     * @since 1.0
122
     */
123
    private $scale;
124
125
    /**
126
     * Flag to determine if the link to the image will change every 24hrs, making hot-linking
127
     * to the image difficult (false by default).
128
     *
129
     * @var \Alpha\Model\Type\Boolean
130
     *
131
     * @since 1.0
132
     */
133
    private $secure;
134
135
    /**
136
     * The auto-generated name of the cache file for the image.
137
     *
138
     * @var string
139
     *
140
     * @since 1.0
141
     */
142
    private $filename;
143
144
    /**
145
     * Trace logger.
146
     *
147
     * @var \Alpha\Util\Logging\Logger
148
     *
149
     * @since 1.0
150
     */
151
    private static $logger = null;
152
153
    /**
154
     * The constructor.
155
     *
156
     * @param string $source
157
     * @param $width
158
     * @param $height
159
     * @param $sourceType
160
     * @param $quality
161
     * @param $scale
162
     *
163
     * @throws \Alpha\Exception\IllegalArguementException
164
     *
165
     * @since 1.0
166
     */
167
    public function __construct($source, $width, $height, $sourceType, $quality = 0.75, $scale = false, $secure = false)
168
    {
169
        self::$logger = new Logger('Image');
170
        self::$logger->debug('>>__construct(source=['.$source.'], width=['.$width.'], height=['.$height.'], sourceType=['.$sourceType.'], quality=['.$quality.'], scale=['.$scale.'], secure=['.$secure.'])');
171
172
        if (file_exists($source)) {
173
            $this->source = $source;
174
        } else {
175
            throw new IllegalArguementException('The source file for the Image widget ['.$source.'] cannot be found!');
176
        }
177
178
        $this->sourceType = new Enum();
179
        $this->sourceType->setOptions(array('jpg', 'png', 'gif'));
180
        $this->sourceType->setValue($sourceType);
181
182
        if ($quality < 0.0 || $quality > 1.0) {
183
            throw new IllegalArguementException('The quality setting of ['.$quality.'] is outside of the allowable range of 0.0 to 1.0');
184
        }
185
186
        $this->quality = new Double($quality);
187
        $this->scale = new Boolean($scale);
188
        $this->secure = new Boolean($secure);
189
190
        $this->width = new Integer($width);
191
        $this->height = new Integer($height);
192
193
        $this->setFilename();
194
195
        self::$logger->debug('<<__construct');
196
    }
197
198
    /**
199
     * Renders the HTML <img> tag to the ViewImage controller, with all of the correct params to render the source
200
     * image in the desired resolution.
201
     *
202
     * @param $altText Set this value to render alternate text as part of the HTML link (defaults to no alternate text)
203
     *
204
     * @return string
205
     *
206
     * @since 1.0
207
     */
208
    public function renderHTMLLink($altText = '')
209
    {
210
        $config = ConfigProvider::getInstance();
211
212
        $url = '';
0 ignored issues
show
Unused Code introduced by
$url is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
213
214
        if ($this->secure->getBooleanValue()) {
215
            $params = Controller::generateSecurityFields();
216
217
            $url = FrontController::generateSecureURL('act=Alpha\Controller\ImageController&source='.$this->source.'&width='.$this->width->getValue().'&height='.$this->height->getValue().'&type='.$this->sourceType->getValue().'&quality='.$this->quality->getValue().'&scale='.$this->scale->getValue().'&secure='.$this->secure->getValue().'&var1='.$params[0].'&var2='.$params[1]);
218
        } else {
219
            $url = FrontController::generateSecureURL('act=Alpha\Controller\ImageController&source='.$this->source.'&width='.$this->width->getValue().'&height='.$this->height->getValue().'&type='.$this->sourceType->getValue().'&quality='.$this->quality->getValue().'&scale='.$this->scale->getValue().'&secure='.$this->secure->getValue());
220
        }
221
222
        return '<img src="'.$url.'"'.(empty($altText) ? '' : ' alt="'.$altText.'"').($config->get('cms.images.widget.bootstrap.responsive') ? ' class="img-responsive"' : '').'/>';
223
    }
224
225
    /**
226
     * Setter for the filename, which also creates a sub-directory under /cache for images when required.
227
     *
228
     * @since 1.0
229
     *
230
     * @throws \Alpha\Exception\AlphaException
231
     */
232
    private function setFilename()
233
    {
234
        $config = ConfigProvider::getInstance();
235
236
        if (!strpos($this->source, 'attachments/article_')) {
237
            // checking to see if we will write a jpg or png to the cache
238
            if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
239
                $this->filename = $config->get('app.file.store.dir').'cache/images/'.basename($this->source, '.'.$this->sourceType->getValue()).'_'.$this->width->getValue().'x'.$this->height->getValue().'.png';
240
            } else {
241
                $this->filename = $config->get('app.file.store.dir').'cache/images/'.basename($this->source, '.'.$this->sourceType->getValue()).'_'.$this->width->getValue().'x'.$this->height->getValue().'.jpg';
242
            }
243
        } else {
244
            // make a cache dir for the article
245
            $cacheDir = $config->get('app.file.store.dir').'cache/images/article_'.mb_substr($this->source, mb_strpos($this->source, 'attachments/article_')+20, 11);
246
            if (!file_exists($cacheDir)) {
247
                $success = mkdir($cacheDir);
248
249
                if (!$success) {
250
                    throw new AlphaException('Unable to create the folder '.$cacheDir.' for the cache image, source file is '.$this->source);
251
                }
252
253
                // ...and set write permissions on the folder
254
                $success = chmod($cacheDir, 0777);
255
256
                if (!$success) {
257
                    throw new AlphaException('Unable to set write permissions on the folder ['.$cacheDir.'].');
258
                }
259
            }
260
261
            // now set the filename to include the new cache directory
262
            if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
263
                $this->filename = $cacheDir.'/'.basename($this->source, '.'.$this->sourceType->getValue()).'_'.$this->width->getValue().'x'.$this->height->getValue().'.png';
264
            } else {
265
                $this->filename = $cacheDir.'/'.basename($this->source, '.'.$this->sourceType->getValue()).'_'.$this->width->getValue().'x'.$this->height->getValue().'.jpg';
266
            }
267
        }
268
269
        self::$logger->debug('Image filename is ['.$this->filename.']');
270
    }
271
272
    /**
273
     * Gets the auto-generated filename for the image in the /cache directory.
274
     *
275
     * @since 1.0
276
     */
277
    public function getFilename()
278
    {
279
        return $this->filename;
280
    }
281
282
    /**
283
     * Renders the actual binary image using GD library calls.
284
     *
285
     * @param $screenSize The optional size of the target screen to scale to in the format [X-pixels]x[Y-pixels]
286
     *
287
     * @since 1.0
288
     */
289
    public function renderImage($screenSize = null)
290
    {
291
        $config = ConfigProvider::getInstance();
292
293
        // if scaled, we need to compute the target image size
294
        if ($this->scale->getBooleanValue() && $screenSize !== null) {
295
            $originalScreenResolution = explode('x', $config->get('sysCMSImagesWidgetScreenResolution'));
296
            $originalScreenX = $originalScreenResolution[0];
297
            $originalScreenY = $originalScreenResolution[1];
298
299
            $targetScreenResolution = explode('x', $screenSize);
300
            $targetScreenX = $targetScreenResolution[0];
301
            $targetScreenY = $targetScreenResolution[1];
302
303
            // calculate the new units we will scale by
304
            $xu = $targetScreenX/$originalScreenX;
305
            $yu = $targetScreenY/$originalScreenY;
306
307
            $this->width = new Integer(intval($this->width->getValue()*$xu));
308
            $this->height = new Integer(intval($this->height->getValue()*$yu));
309
310
            // need to update the cache filename as the dimensions have changed
311
            $this->setFilename();
312
        }
313
314
        // check the image cache first before we proceed
315
        if ($this->checkCache()) {
316
            $this->loadCache();
317
        } else {
318
            // now get the old image
319
            switch ($this->sourceType->getValue()) {
320
                case 'gif':
321
                    $oldImage = imagecreatefromgif($this->source);
322
                break;
323
                case 'jpg':
324
                    $oldImage = imagecreatefromjpeg($this->source);
325
                break;
326
                default:
327
                    $oldImage = imagecreatefrompng($this->source);
328
            }
329
330
            if (!$oldImage) {
331
                $im = imagecreatetruecolor($this->width->getValue(), $this->height->getValue());
332
                $bgc = imagecolorallocate($im, 255, 255, 255);
333
                $tc = imagecolorallocate($im, 0, 0, 0);
334
                imagefilledrectangle($im, 0, 0, $this->width->getValue(), $this->height->getValue(), $bgc);
335
336
                imagestring($im, 1, 5, 5, "Error loading $this->source", $tc);
337
                if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
338
                    imagepng($im);
339
                } else {
340
                    imagejpeg($im);
341
                }
342
                imagedestroy($im);
343
            } else {
344
                // the dimensions of the source image
345
                $oldWidth = imagesx($oldImage);
346
                $oldHeight = imagesy($oldImage);
347
348
                // now create the new image
349
                $newImage = imagecreatetruecolor($this->width->getValue(), $this->height->getValue());
350
351
                // set a transparent background for PNGs
352
                if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
353
                    // Turn off transparency blending (temporarily)
354
                    imagealphablending($newImage, false);
355
356
                    // Create a new transparent color for image
357
                    $color = imagecolorallocatealpha($newImage, 255, 0, 0, 0);
358
359
                    // Completely fill the background of the new image with allocated color.
360
                    imagefill($newImage, 0, 0, $color);
361
362
                    // Restore transparency blending
363
                    imagesavealpha($newImage, true);
364
                }
365
                // copy the old image to the new image (in memory, not the file!)
366
                imagecopyresampled($newImage, $oldImage, 0, 0, 0, 0, $this->width->getValue(), $this->height->getValue(), $oldWidth, $oldHeight);
367
368
                if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
369
                    imagepng($newImage);
370
                } else {
371
                    imagejpeg($newImage, null, 100*$this->quality->getValue());
372
                }
373
374
                $this->cache($newImage);
0 ignored issues
show
Documentation introduced by
$newImage is of type resource, but the function expects a object<Alpha\View\Widget\Image>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
375
                imagedestroy($oldImage);
376
                imagedestroy($newImage);
377
            }
378
        }
379
    }
380
381
    /**
382
     * Caches the image to the cache directory.
383
     *
384
     * @param image $image the binary GD image stream to save
385
     *
386
     * @since 1.0
387
     */
388
    private function cache($image)
389
    {
390
        $config = ConfigProvider::getInstance();
391
392
        if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
393
            imagepng($image, $this->filename);
394
        } else {
395
            imagejpeg($image, $this->filename, 100*$this->quality->getValue());
396
        }
397
    }
398
399
    /**
400
     * Used to check the image cache for the image jpeg cache file.
401
     *
402
     * @return bool
403
     *
404
     * @since 1.0
405
     */
406
    private function checkCache()
407
    {
408
        return file_exists($this->filename);
409
    }
410
411
    /**
412
     * Method to load the content of the image cache file to the standard output stream (the browser).
413
     *
414
     * @since 1.0
415
     */
416
    private function loadCache()
417
    {
418
        readfile($this->filename);
419
    }
420
421
    /**
422
     * Converts a URL for an image to a relative file system path for the image, assuming it is
423
     * hosted on the same server as the application.
424
     *
425
     * @param string $imgURL
426
     *
427
     * @return string the path of the image
428
     *
429
     * @since 1.0
430
     */
431
    public static function convertImageURLToPath($imgURL)
432
    {
433
        $config = ConfigProvider::getInstance();
434
435
        $imgPath = str_replace($config->get('app.url').'/', '', $imgURL);
436
437
        return $imgPath;
438
    }
439
}
440