Image   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 355
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 9
dl 0
loc 355
rs 9.6
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 30 4
A renderHTMLLink() 0 16 4
B setFilename() 0 36 9
A getFilename() 0 4 1
C renderImage() 0 70 11
A cache() 0 10 3
A checkCache() 0 4 1
A loadCache() 0 4 1
A convertImageURLToPath() 0 8 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) 2019, 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;
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, 0777, true);
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
                if (!$success) {
254
                    throw new AlphaException('Unable to set write permissions on the folder ['.$cacheDir.'].');
255
                }
256
            }
257
258
            // now set the filename to include the new cache directory
259
            if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
260
                $this->filename = $cacheDir.'/'.basename($this->source, '.'.$this->sourceType->getValue()).'_'.$this->width->getValue().'x'.$this->height->getValue().'.png';
261
            } else {
262
                $this->filename = $cacheDir.'/'.basename($this->source, '.'.$this->sourceType->getValue()).'_'.$this->width->getValue().'x'.$this->height->getValue().'.jpg';
263
            }
264
        }
265
266
        self::$logger->debug('Image filename is ['.$this->filename.']');
267
    }
268
269
    /**
270
     * Gets the auto-generated filename for the image in the /cache directory.
271
     *
272
     * @since 1.0
273
     */
274
    public function getFilename()
275
    {
276
        return $this->filename;
277
    }
278
279
    /**
280
     * Renders the actual binary image using GD library calls.
281
     *
282
     * @since 1.0
283
     */
284
    public function renderImage()
285
    {
286
        $config = ConfigProvider::getInstance();
287
288
        // check the image cache first before we proceed
289
        if ($this->checkCache()) {
290
            $this->loadCache();
291
        } else {
292
            // now get the old image
293
            switch ($this->sourceType->getValue()) {
294
                case 'gif':
295
                    $oldImage = imagecreatefromgif($this->source);
296
                break;
297
                case 'jpg':
298
                    $oldImage = imagecreatefromjpeg($this->source);
299
                break;
300
                default:
301
                    $oldImage = imagecreatefrompng($this->source);
302
            }
303
304
            if (!$oldImage) {
305
                $im = imagecreatetruecolor($this->width->getValue(), $this->height->getValue());
306
                $bgc = imagecolorallocate($im, 255, 255, 255);
307
                $tc = imagecolorallocate($im, 0, 0, 0);
308
                imagefilledrectangle($im, 0, 0, $this->width->getValue(), $this->height->getValue(), $bgc);
309
310
                imagestring($im, 1, 5, 5, "Error loading $this->source", $tc);
311
                if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
312
                    imagepng($im);
313
                } else {
314
                    imagejpeg($im);
315
                }
316
                imagedestroy($im);
317
            } else {
318
                // the dimensions of the source image
319
                $oldWidth = imagesx($oldImage);
320
                $oldHeight = imagesy($oldImage);
321
322
                // now create the new image
323
                $newImage = imagecreatetruecolor($this->width->getValue(), $this->height->getValue());
324
325
                // set a transparent background for PNGs
326
                if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
327
                    // Turn off transparency blending (temporarily)
328
                    imagealphablending($newImage, false);
329
330
                    // Create a new transparent color for image
331
                    $color = imagecolorallocatealpha($newImage, 255, 0, 0, 0);
332
333
                    // Completely fill the background of the new image with allocated color.
334
                    imagefill($newImage, 0, 0, $color);
335
336
                    // Restore transparency blending
337
                    imagesavealpha($newImage, true);
338
                }
339
                // copy the old image to the new image (in memory, not the file!)
340
                imagecopyresampled($newImage, $oldImage, 0, 0, 0, 0, $this->width->getValue(), $this->height->getValue(), $oldWidth, $oldHeight);
341
342
                if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
343
                    imagepng($newImage);
344
                } else {
345
                    imagejpeg($newImage, null, 100*$this->quality->getValue());
346
                }
347
348
                $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...
349
                imagedestroy($oldImage);
350
                imagedestroy($newImage);
351
            }
352
        }
353
    }
354
355
    /**
356
     * Caches the image to the cache directory.
357
     *
358
     * @param image $image the binary GD image stream to save
359
     *
360
     * @since 1.0
361
     */
362
    private function cache($image)
363
    {
364
        $config = ConfigProvider::getInstance();
365
366
        if ($this->sourceType->getValue() == 'png' && $config->get('cms.images.perserve.png')) {
367
            imagepng($image, $this->filename);
368
        } else {
369
            imagejpeg($image, $this->filename, 100*$this->quality->getValue());
370
        }
371
    }
372
373
    /**
374
     * Used to check the image cache for the image jpeg cache file.
375
     *
376
     * @return bool
377
     *
378
     * @since 1.0
379
     */
380
    private function checkCache()
381
    {
382
        return file_exists($this->filename);
383
    }
384
385
    /**
386
     * Method to load the content of the image cache file to the standard output stream (the browser).
387
     *
388
     * @since 1.0
389
     */
390
    private function loadCache()
391
    {
392
        readfile($this->filename);
393
    }
394
395
    /**
396
     * Converts a URL for an image to a relative file system path for the image, assuming it is
397
     * hosted on the same server as the application.
398
     *
399
     * @param string $imgURL
400
     *
401
     * @return string the path of the image
402
     *
403
     * @since 1.0
404
     */
405
    public static function convertImageURLToPath($imgURL)
406
    {
407
        $config = ConfigProvider::getInstance();
408
409
        $imgPath = str_replace($config->get('app.url').'/', '', $imgURL);
410
411
        return $imgPath;
412
    }
413
}
414