phpthumb   F
last analyzed

Complexity

Total Complexity 1107

Size/Duplication

Total Lines 4687
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3053
dl 0
loc 4687
rs 0.8
c 1
b 0
f 0
wmc 1107

58 Methods

Rating   Name   Duplication   Size   Complexity  
B resolvePath() 0 72 11
A setSourceFilename() 0 18 3
A ImageMagickWhichConvert() 0 18 4
B applyPathSegment() 0 36 10
A __construct() 0 3 1
A DebugTimingMessage() 0 8 5
F CalculateThumbnailDimensions() 0 87 22
F SetCacheFilename() 0 120 33
B setSourceData() 0 23 7
F ApplyFilters() 0 426 149
B ImageMagickVersion() 0 32 8
A InitializeTempDirSetting() 0 9 6
D OutputThumbnail() 0 95 20
A getParameter() 0 4 1
C AlphaChannelFlatten() 0 100 17
F ExtractEXIFgetImageSize() 0 152 42
C phpThumb() 0 34 12
A setSourceImageResource() 0 6 1
F ErrorImage() 0 94 30
F ImageCreateFromFilename() 0 87 26
C ResolveSource() 0 42 13
C ImageMagickCommandlineBase() 0 64 14
B CreateGDoutput() 0 27 7
A FatalError() 0 7 2
A resetObject() 0 12 3
D RenderOutput() 0 134 23
A OffsiteDomainIsAllowed() 0 24 6
F setOutputFormat() 0 80 20
D ImageCreateFromStringReplacement() 0 115 24
B phpThumbDebugVarDump() 0 22 8
A setParameter() 0 17 5
B ImageMagickSwitchAvailable() 0 31 8
B file_exists_ignoreopenbasedir() 0 20 7
F setCacheDirectory() 0 53 18
A SourceDataToTempFile() 0 23 3
A DebugMessage() 0 5 4
F phpThumbDebug() 0 246 24
C GenerateThumbnail() 0 76 17
D Rotate() 0 88 26
A SetOrientationDependantWidthHeight() 0 21 3
B FixedAspectRatio() 0 35 10
D AntiOffsiteLinking() 0 80 18
C MaxFileSize() 0 65 13
A phpThumb_tempnam() 0 8 1
A realPathSafe() 0 36 4
A matchPath() 0 11 4
A purgeTempFiles() 0 11 3
A isInOpenBasedir() 0 15 5
D ResolveFilenameToAbsolute() 0 95 27
B RenderToFile() 0 38 11
F CleanUpCacheDirectory() 0 166 44
A ImageMagickFormatsList() 0 14 3
A __destruct() 0 3 1
A ImageResizeFunction() 0 15 5
F SourceImageToGD() 0 285 73
F ImageMagickThumbnailToGD() 0 709 236
A SourceImageIsTooLarge() 0 12 4
A normalizePath() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like phpthumb often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use phpthumb, and based on these observations, apply Extract Interface, too.

1
<?php
2
//////////////////////////////////////////////////////////////
3
//   phpThumb() by James Heinrich <[email protected]>   //
4
//        available at http://phpthumb.sourceforge.net      //
5
//         and/or https://github.com/JamesHeinrich/phpThumb //
6
//////////////////////////////////////////////////////////////
7
///                                                         //
8
// See: phpthumb.readme.txt for usage instructions          //
9
//                                                         ///
10
//////////////////////////////////////////////////////////////
11
12
ob_start();
13
if (!require_once __DIR__ . '/phpthumb.functions.php') {
14
    ob_end_flush();
15
    exit('failed to include_once("' . __DIR__ . '/phpthumb.functions.php")');
16
}
17
ob_end_clean();
18
19
/**
20
 * Class phpthumb
21
 */
22
class phpthumb
23
{
24
    // public:
25
    // START PARAMETERS (for object mode and phpThumb.php)
26
    // See phpthumb.readme.txt for descriptions of what each of these values are
27
    public $src           = null;     // SouRCe filename
28
    public $new           = null;     // NEW image (phpThumb.php only)
29
    public $w             = null;     // Width
30
    public $h             = null;     // Height
31
    public $wp            = null;     // Width  (Portrait Images Only)
32
    public $hp            = null;     // Height (Portrait Images Only)
33
    public $wl            = null;     // Width  (Landscape Images Only)
34
    public $hl            = null;     // Height (Landscape Images Only)
35
    public $ws            = null;     // Width  (Square Images Only)
36
    public $hs            = null;     // Height (Square Images Only)
37
    public $f             = null;     // output image Format
38
    public $q             = 75;       // jpeg output Quality
39
    public $sx            = null;     // Source crop top-left X position
40
    public $sy            = null;     // Source crop top-left Y position
41
    public $sw            = null;     // Source crop Width
42
    public $sh            = null;     // Source crop Height
43
    public $zc            = null;     // Zoom Crop
44
    public $bc            = null;     // Border Color
45
    public $bg            = null;     // BackGround color
46
    public $fltr          = [];  // FiLTeRs
47
    public $goto          = null;     // GO TO url after processing
48
    public $err           = null;     // default ERRor image filename
49
    public $xto           = null;     // extract eXif Thumbnail Only
50
    public $ra            = null;     // Rotate by Angle
51
    public $ar            = null;     // Auto Rotate
52
    public $aoe           = null;     // Allow Output Enlargement
53
    public $far           = null;     // Fixed Aspect Ratio
54
    public $iar           = null;     // Ignore Aspect Ratio
55
    public $maxb          = null;     // MAXimum Bytes
56
    public $down          = null;     // DOWNload thumbnail filename
57
    public $md5s          = null;     // MD5 hash of Source image
58
    public $sfn           = 0;        // Source Frame Number
59
    public $dpi           = 150;      // Dots Per Inch for vector source formats
60
    public $sia           = null;     // Save Image As filename
61
    public $file          = null;     // >>>deprecated, DO NOT USE, will be removed in future versions<<<
62
    public $phpThumbDebug = null;
63
    // END PARAMETERS
64
65
    // public:
66
    // START CONFIGURATION OPTIONS (for object mode only)
67
    // See phpThumb.config.php for descriptions of what each of these settings do
68
69
    // * Directory Configuration
70
    public $config_cache_directory        = null;
71
    public $config_cache_directory_depth  = 0;
72
    public $config_cache_disable_warning  = true;
73
    public $config_cache_source_enabled   = false;
74
    public $config_cache_source_directory = null;
75
    public $config_temp_directory         = null;
76
    public $config_document_root          = null;
77
    // * Default output configuration:
78
    public $config_output_format    = 'jpeg';
79
    public $config_output_maxwidth  = 0;
80
    public $config_output_maxheight = 0;
81
    public $config_output_interlace = true;
82
    // * Error message configuration
83
    public $config_error_image_width           = 400;
84
    public $config_error_image_height          = 100;
85
    public $config_error_message_image_default = '';
86
    public $config_error_bgcolor               = 'CCCCFF';
87
    public $config_error_textcolor             = 'FF0000';
88
    public $config_error_fontsize              = 1;
89
    public $config_error_die_on_error          = false;
90
    public $config_error_silent_die_on_error   = false;
91
    public $config_error_die_on_source_failure = true;
92
    // * Anti-Hotlink Configuration:
93
    public $config_nohotlink_enabled       = true;
94
    public $config_nohotlink_valid_domains = [];
95
    public $config_nohotlink_erase_image   = true;
96
    public $config_nohotlink_text_message  = 'Off-server thumbnailing is not allowed';
97
    // * Off-server Linking Configuration:
98
    public $config_nooffsitelink_enabled       = false;
99
    public $config_nooffsitelink_valid_domains = [];
100
    public $config_nooffsitelink_require_refer = false;
101
    public $config_nooffsitelink_erase_image   = true;
102
    public $config_nooffsitelink_watermark_src = '';
103
    public $config_nooffsitelink_text_message  = 'Off-server linking is not allowed';
104
    // * Border & Background default colors
105
    public $config_border_hexcolor     = '000000';
106
    public $config_background_hexcolor = 'FFFFFF';
107
    // * TrueType Fonts
108
    public $config_ttf_directory                        = './fonts';
109
    public $config_max_source_pixels                    = null;
110
    public $config_use_exif_thumbnail_for_speed         = false;
111
    public $allow_local_http_src                        = false;
112
    public $config_imagemagick_path                     = null;
113
    public $config_prefer_imagemagick                   = true;
114
    public $config_imagemagick_use_thumbnail            = true;
115
    public $config_cache_maxage                         = null;
116
    public $config_cache_maxsize                        = null;
117
    public $config_cache_maxfiles                       = null;
118
    public $config_cache_source_filemtime_ignore_local  = false;
119
    public $config_cache_source_filemtime_ignore_remote = true;
120
    public $config_cache_default_only_suffix            = false;
121
    public $config_cache_force_passthru                 = true;
122
    public $config_cache_prefix                         = '';    // default value set in the constructor below
123
    // * MySQL
124
    public $config_mysql_extension = null;
125
    public $config_mysql_query     = null;
126
    public $config_mysql_hostname  = null;
127
    public $config_mysql_username  = null;
128
    public $config_mysql_password  = null;
129
    public $config_mysql_database  = null;
130
    // * Security
131
    public $config_high_security_enabled       = true;
132
    public $config_high_security_password      = null;
133
    public $config_high_security_url_separator = '&';
134
    public $config_disable_debug               = true;
135
    public $config_allow_src_above_docroot     = false;
136
    public $config_allow_src_above_phpthumb    = true;
137
    public $config_auto_allow_symlinks         = true;    // allow symlink target directories without explicitly whitelisting them
138
    public $config_additional_allowed_dirs     = []; // additional directories to allow source images to be read from
139
    // * HTTP fopen
140
    public $config_http_fopen_timeout   = 10;
141
    public $config_http_follow_redirect = true;
142
    // * Compatability
143
    public $config_disable_pathinfo_parsing        = false;
144
    public $config_disable_imagecopyresampled      = false;
145
    public $config_disable_onlycreateable_passthru = false;
146
    public $config_disable_realpath                = false;
147
    public $config_http_user_agent                 = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7';
148
    // END CONFIGURATION OPTIONS
149
150
    // public: error messages (read-only; persistant)
151
    public $debugmessages = [];
152
    public $debugtiming   = [];
153
    public $fatalerror    = null;
154
    // private: (should not be modified directly)
155
    public $thumbnailQuality       = 75;
156
    public $thumbnailFormat        = null;
157
    public $sourceFilename         = null;
158
    public $rawImageData           = null;
159
    public $IMresizedData          = null;
160
    public $outputImageData        = null;
161
    public $useRawIMoutput         = false;
162
    public $gdimg_output           = null;
163
    public $gdimg_source           = null;
164
    public $getimagesizeinfo       = null;
165
    public $source_width           = null;
166
    public $source_height          = null;
167
    public $thumbnailCropX         = null;
168
    public $thumbnailCropY         = null;
169
    public $thumbnailCropW         = null;
170
    public $thumbnailCropH         = null;
171
    public $exif_thumbnail_width   = null;
172
    public $exif_thumbnail_height  = null;
173
    public $exif_thumbnail_type    = null;
174
    public $exif_thumbnail_data    = null;
175
    public $exif_raw_data          = null;
176
    public $thumbnail_width        = null;
177
    public $thumbnail_height       = null;
178
    public $thumbnail_image_width  = null;
179
    public $thumbnail_image_height = null;
180
    public $tempFilesToDelete      = [];
181
    public $cache_filename         = null;
182
    public $AlphaCapableFormats    = ['png', 'ico', 'gif'];
183
    public $is_alpha               = false;
184
    public $iswindows              = null;
185
    public $issafemode             = null;
186
    public $php_memory_limit       = null;
187
    public $phpthumb_version       = '1.7.14-201607141354';
188
    //////////////////////////////////////////////////////////////////////
189
190
    // public: constructor
191
    public function __construct()
192
    {
193
        $this->phpThumb();
194
    }
195
196
    public function phpThumb()
197
    {
198
        $this->DebugTimingMessage('phpThumb() constructor', __FILE__, __LINE__);
199
        $this->DebugMessage('phpThumb() v' . $this->phpthumb_version, __FILE__, __LINE__);
200
201
        foreach ([ini_get('memory_limit'), get_cfg_var('memory_limit')] as $php_config_memory_limit) {
202
            if (mb_strlen($php_config_memory_limit)) {
0 ignored issues
show
Bug introduced by
It seems like $php_config_memory_limit can also be of type array; however, parameter $string of mb_strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

202
            if (mb_strlen(/** @scrutinizer ignore-type */ $php_config_memory_limit)) {
Loading history...
203
                if ('G' === mb_substr($php_config_memory_limit, -1, 1)) { // PHP memory limit expressed in Gigabytes
0 ignored issues
show
Bug introduced by
It seems like $php_config_memory_limit can also be of type array; however, parameter $string of mb_substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

203
                if ('G' === mb_substr(/** @scrutinizer ignore-type */ $php_config_memory_limit, -1, 1)) { // PHP memory limit expressed in Gigabytes
Loading history...
204
                    $php_config_memory_limit = (int)mb_substr($php_config_memory_limit, 0, -1) * 1073741824;
205
                } elseif ('M' === mb_substr($php_config_memory_limit, -1, 1)) { // PHP memory limit expressed in Megabytes
206
                    $php_config_memory_limit = (int)mb_substr($php_config_memory_limit, 0, -1) * 1048576;
207
                }
208
                $this->php_memory_limit = max($this->php_memory_limit, $php_config_memory_limit);
209
            }
210
        }
211
        if ($this->php_memory_limit > 0) { // could be "-1" for "no limit"
212
            $this->config_max_source_pixels = round($this->php_memory_limit * 0.20); // 20% of memory_limit
213
        }
214
215
        $this->iswindows            = ('WIN' === mb_strtoupper(mb_substr(PHP_OS, 0, 3)));
216
        $this->issafemode           = (bool)preg_match('#(1|ON)#i', ini_get('safe_mode'));
217
        $this->config_document_root = (!empty($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : $this->config_document_root);
218
        $this->config_cache_prefix  = (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] . '_' : '');
219
220
        $this->purgeTempFiles(); // purge existing temp files if re-initializing object
221
222
        $php_sapi_name = mb_strtolower(function_exists('php_sapi_name') ? php_sapi_name() : '');
223
        if ('cli' === $php_sapi_name) {
224
            $this->config_allow_src_above_docroot = true;
225
        }
226
227
        if (!$this->config_disable_debug) {
228
            // if debug mode is enabled, force phpThumbDebug output, do not allow normal thumbnails to be generated
229
            $this->phpThumbDebug = (null === $this->phpThumbDebug ? 9 : max(1, (int)$this->phpThumbDebug));
230
        }
231
    }
232
233
    public function __destruct()
234
    {
235
        $this->purgeTempFiles();
236
    }
237
238
    // public:
239
240
    /**
241
     * @return bool
242
     */
243
    public function purgeTempFiles()
244
    {
245
        foreach ($this->tempFilesToDelete as $tempFileToDelete) {
246
            if (file_exists($tempFileToDelete)) {
247
                $this->DebugMessage('Deleting temp file "' . $tempFileToDelete . '"', __FILE__, __LINE__);
248
                @unlink($tempFileToDelete);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

248
                /** @scrutinizer ignore-unhandled */ @unlink($tempFileToDelete);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
249
            }
250
        }
251
        $this->tempFilesToDelete = [];
252
253
        return true;
254
    }
255
256
    // public:
257
258
    /**
259
     * @param $sourceFilename
260
     * @return bool
261
     */
262
    public function setSourceFilename($sourceFilename)
263
    {
264
        //$this->resetObject();
265
        //$this->rawImageData   = null;
266
        $this->sourceFilename = $sourceFilename;
267
        $this->src            = $sourceFilename;
268
        if (null === $this->config_output_format) {
269
            $sourceFileExtension = mb_strtolower(mb_substr(mb_strrchr($sourceFilename, '.'), 1));
270
            if (preg_match('#^[a-z]{3,4}$#', $sourceFileExtension)) {
271
                $this->config_output_format = $sourceFileExtension;
272
                $this->DebugMessage('setSourceFilename(' . $sourceFilename . ') set $this->config_output_format to "' . $sourceFileExtension . '"', __FILE__, __LINE__);
273
            } else {
274
                $this->DebugMessage('setSourceFilename(' . $sourceFilename . ') did NOT set $this->config_output_format to "' . $sourceFileExtension . '" because it did not seem like an appropriate image format', __FILE__, __LINE__);
275
            }
276
        }
277
        $this->DebugMessage('setSourceFilename(' . $sourceFilename . ') set $this->sourceFilename to "' . $this->sourceFilename . '"', __FILE__, __LINE__);
278
279
        return true;
280
    }
281
282
    // public:
283
284
    /**
285
     * @param        $rawImageData
286
     * @param string $sourceFilename
287
     * @return bool
288
     */
289
    public function setSourceData($rawImageData, $sourceFilename = '')
290
    {
291
        //$this->resetObject();
292
        //$this->sourceFilename = null;
293
        $this->rawImageData = $rawImageData;
294
        $this->DebugMessage('setSourceData() setting $this->rawImageData (' . mb_strlen($this->rawImageData) . ' bytes; magic="' . mb_substr($this->rawImageData, 0, 4) . '" (' . phpthumb_functions::HexCharDisplay(mb_substr($this->rawImageData, 0, 4)) . '))', __FILE__, __LINE__);
295
        if ($this->config_cache_source_enabled) {
296
            $sourceFilename = ($sourceFilename ?: md5($rawImageData));
297
            if (!is_dir($this->config_cache_source_directory)) {
298
                $this->ErrorImage('$this->config_cache_source_directory (' . $this->config_cache_source_directory . ') is not a directory');
299
            } elseif (!@is_writable($this->config_cache_source_directory)) {
300
                $this->ErrorImage('$this->config_cache_source_directory (' . $this->config_cache_source_directory . ') is not writable');
301
            }
302
            $this->DebugMessage('setSourceData() attempting to save source image to "' . $this->config_cache_source_directory . DIRECTORY_SEPARATOR . urlencode($sourceFilename) . '"', __FILE__, __LINE__);
303
            if ($fp = @fopen($this->config_cache_source_directory . DIRECTORY_SEPARATOR . urlencode($sourceFilename), 'wb')) {
304
                fwrite($fp, $rawImageData);
305
                fclose($fp);
306
            } elseif (!$this->phpThumbDebug) {
307
                $this->ErrorImage('setSourceData() failed to write to source cache (' . $this->config_cache_source_directory . DIRECTORY_SEPARATOR . urlencode($sourceFilename) . ')');
308
            }
309
        }
310
311
        return true;
312
    }
313
314
    // public:
315
316
    /**
317
     * @param $gdimg
318
     * @return bool
319
     */
320
    public function setSourceImageResource($gdimg)
321
    {
322
        //$this->resetObject();
323
        $this->gdimg_source = $gdimg;
324
325
        return true;
326
    }
327
328
    // public:
329
330
    /**
331
     * @param $param
332
     * @param $value
333
     * @return bool
334
     */
335
    public function setParameter($param, $value)
336
    {
337
        if ('src' === $param) {
338
            $this->setSourceFilename($this->ResolveFilenameToAbsolute($value));
339
        } elseif (@is_array($this->$param)) {
340
            if (is_array($value)) {
341
                foreach ($value as $arraykey => $arrayvalue) {
342
                    array_push($this->$param, $arrayvalue);
343
                }
344
            } else {
345
                array_push($this->$param, $value);
346
            }
347
        } else {
348
            $this->$param = $value;
349
        }
350
351
        return true;
352
    }
353
354
    // public:
355
356
    /**
357
     * @param $param
358
     * @return mixed
359
     */
360
    public function getParameter($param)
361
    {
362
        //if (property_exists('phpThumb', $param)) {
363
        return $this->$param;
364
        //}
365
        //$this->DebugMessage('setParameter() attempting to get non-existant parameter "'.$param.'"', __FILE__, __LINE__);
366
        //return false;
367
    }
368
369
    // public:
370
371
    /**
372
     * @return bool
373
     */
374
    public function GenerateThumbnail()
375
    {
376
        $this->setOutputFormat();
377
        $this->phpThumbDebug('8a');
378
        $this->ResolveSource();
379
        $this->phpThumbDebug('8b');
380
        $this->SetCacheFilename();
381
        $this->phpThumbDebug('8c');
382
        $this->ExtractEXIFgetImageSize();
383
        $this->phpThumbDebug('8d');
384
        if ($this->useRawIMoutput) {
385
            $this->DebugMessage('Skipping rest of GenerateThumbnail() because ($this->useRawIMoutput === true)', __FILE__, __LINE__);
386
387
            return true;
388
        }
389
        $this->phpThumbDebug('8e');
390
        if (!$this->SourceImageToGD()) {
391
            $this->DebugMessage('SourceImageToGD() failed', __FILE__, __LINE__);
392
393
            return false;
394
        }
395
        $this->phpThumbDebug('8f');
396
        $this->Rotate();
397
        $this->phpThumbDebug('8g');
398
        $this->CreateGDoutput();
399
        $this->phpThumbDebug('8h');
400
401
        // default values, also applicable for far="C"
402
        $destination_offset_x = round(($this->thumbnail_width - $this->thumbnail_image_width) / 2);
403
        $destination_offset_y = round(($this->thumbnail_height - $this->thumbnail_image_height) / 2);
404
        if (('L' === $this->far) || ('TL' === $this->far) || ('BL' === $this->far)) {
405
            $destination_offset_x = 0;
406
        }
407
        if (('R' === $this->far) || ('TR' === $this->far) || ('BR' === $this->far)) {
408
            $destination_offset_x = round($this->thumbnail_width - $this->thumbnail_image_width);
409
        }
410
        if (('T' === $this->far) || ('TL' === $this->far) || ('TR' === $this->far)) {
411
            $destination_offset_y = 0;
412
        }
413
        if (('B' === $this->far) || ('BL' === $this->far) || ('BR' === $this->far)) {
414
            $destination_offset_y = round($this->thumbnail_height - $this->thumbnail_image_height);
415
        }
416
417
        //      // copy/resize image to appropriate dimensions
418
        //      $borderThickness = 0;
419
        //      if (!empty($this->fltr)) {
420
        //          foreach ($this->fltr as $key => $value) {
421
        //              if (preg_match('#^bord\|([0-9]+)#', $value, $matches)) {
422
        //                  $borderThickness = $matches[1];
423
        //                  break;
424
        //              }
425
        //          }
426
        //      }
427
        //      if ($borderThickness > 0) {
428
        //          //$this->DebugMessage('Skipping ImageResizeFunction() because BorderThickness="'.$borderThickness.'"', __FILE__, __LINE__);
429
        //          $this->thumbnail_image_height /= 2;
430
        //      }
431
        $this->ImageResizeFunction($this->gdimg_output, $this->gdimg_source, $destination_offset_x, $destination_offset_y, $this->thumbnailCropX, $this->thumbnailCropY, $this->thumbnail_image_width, $this->thumbnail_image_height, $this->thumbnailCropW, $this->thumbnailCropH);
432
433
        $this->DebugMessage('memory_get_usage() after copy-resize = ' . (function_exists('memory_get_usage') ? @memory_get_usage() : 'n/a'), __FILE__, __LINE__);
434
        imagedestroy($this->gdimg_source);
435
        $this->DebugMessage('memory_get_usage() after imagedestroy = ' . (function_exists('memory_get_usage') ? @memory_get_usage() : 'n/a'), __FILE__, __LINE__);
436
437
        $this->phpThumbDebug('8i');
438
        $this->AntiOffsiteLinking();
439
        $this->phpThumbDebug('8j');
440
        $this->ApplyFilters();
441
        $this->phpThumbDebug('8k');
442
        $this->AlphaChannelFlatten();
443
        $this->phpThumbDebug('8l');
444
        $this->MaxFileSize();
445
        $this->phpThumbDebug('8m');
446
447
        $this->DebugMessage('GenerateThumbnail() completed successfully', __FILE__, __LINE__);
448
449
        return true;
450
    }
451
452
    // public:
453
454
    /**
455
     * @return bool
456
     */
457
    public function RenderOutput()
458
    {
459
        if (!$this->useRawIMoutput && !is_resource($this->gdimg_output)) {
460
            $this->DebugMessage('RenderOutput() failed because !is_resource($this->gdimg_output)', __FILE__, __LINE__);
461
462
            return false;
463
        }
464
        if (!$this->thumbnailFormat) {
465
            $this->DebugMessage('RenderOutput() failed because $this->thumbnailFormat is empty', __FILE__, __LINE__);
466
467
            return false;
468
        }
469
        if ($this->useRawIMoutput) {
470
            $this->DebugMessage('RenderOutput copying $this->IMresizedData (' . mb_strlen($this->IMresizedData) . ' bytes) to $this->outputImage', __FILE__, __LINE__);
471
            $this->outputImageData = $this->IMresizedData;
472
473
            return true;
474
        }
475
476
        $builtin_formats = [];
477
        if (function_exists('imagetypes')) {
478
            $imagetypes              = imagetypes();
479
            $builtin_formats['wbmp'] = (bool)($imagetypes & IMG_WBMP);
480
            $builtin_formats['jpg']  = (bool)($imagetypes & IMG_JPG);
481
            $builtin_formats['gif']  = (bool)($imagetypes & IMG_GIF);
482
            $builtin_formats['png']  = (bool)($imagetypes & IMG_PNG);
483
        }
484
485
        $this->DebugMessage('imageinterlace($this->gdimg_output, ' . (int)$this->config_output_interlace . ')', __FILE__, __LINE__);
486
        imageinterlace($this->gdimg_output, (int)$this->config_output_interlace);
487
488
        $this->DebugMessage('RenderOutput() attempting image' . mb_strtolower(@$this->thumbnailFormat) . '($this->gdimg_output)', __FILE__, __LINE__);
489
        ob_start();
490
        switch ($this->thumbnailFormat) {
491
            case 'wbmp':
492
                if (empty($builtin_formats['wbmp'])) {
493
                    $this->DebugMessage('GD does not have required built-in support for WBMP output', __FILE__, __LINE__);
494
                    ob_end_clean();
495
496
                    return false;
497
                }
498
                imagejpeg($this->gdimg_output, null, $this->thumbnailQuality);
499
                $this->outputImageData = ob_get_contents();
500
                break;
501
            case 'jpeg':
502
            case 'jpg':  // should be "jpeg" not "jpg" but just in case...
503
                if (empty($builtin_formats['jpg'])) {
504
                    $this->DebugMessage('GD does not have required built-in support for JPEG output', __FILE__, __LINE__);
505
                    ob_end_clean();
506
507
                    return false;
508
                }
509
                imagejpeg($this->gdimg_output, null, $this->thumbnailQuality);
510
                $this->outputImageData = ob_get_contents();
511
                break;
512
            case 'png':
513
                if (empty($builtin_formats['png'])) {
514
                    $this->DebugMessage('GD does not have required built-in support for PNG output', __FILE__, __LINE__);
515
                    ob_end_clean();
516
517
                    return false;
518
                }
519
                if (phpthumb_functions::version_compare_replacement(PHP_VERSION, '5.1.2', '>=')) {
520
                    // https://github.com/JamesHeinrich/phpThumb/issues/24
521
522
                    /* http://php.net/manual/en/function.imagepng.php:
523
                    from php source (gd.h):
524
                    2.0.12: Compression level: 0-9 or -1, where 0 is NO COMPRESSION at all,
525
                    :: 1 is FASTEST but produces larger files, 9 provides the best
526
                    :: compression (smallest files) but takes a long time to compress, and
527
                    :: -1 selects the default compiled into the zlib library.
528
                    Conclusion: Based on the Zlib manual (http://www.zlib.net/manual.html) the default compression level is set to 6.
529
                    */
530
                    if (($this->thumbnailQuality >= -1) && ($this->thumbnailQuality <= 9)) {
531
                        $PNGquality = $this->thumbnailQuality;
532
                    } else {
533
                        $this->DebugMessage('Specified thumbnailQuality "' . $this->thumbnailQuality . '" is outside the accepted range (0-9, or -1). Using 6 as default value.', __FILE__, __LINE__);
534
                        $PNGquality = 6;
535
                    }
536
                    imagepng($this->gdimg_output, null, $PNGquality);
537
                } else {
538
                    imagepng($this->gdimg_output);
539
                }
540
                $this->outputImageData = ob_get_contents();
541
                break;
542
            case 'gif':
543
                if (empty($builtin_formats['gif'])) {
544
                    $this->DebugMessage('GD does not have required built-in support for GIF output', __FILE__, __LINE__);
545
                    ob_end_clean();
546
547
                    return false;
548
                }
549
                imagegif($this->gdimg_output);
550
                $this->outputImageData = ob_get_contents();
551
                break;
552
            case 'bmp':
553
                if (!@require_once __DIR__ . '/phpthumb.bmp.php') {
554
                    $this->DebugMessage('Error including "' . __DIR__ . '/phpthumb.bmp.php" which is required for BMP format output', __FILE__, __LINE__);
555
                    ob_end_clean();
556
557
                    return false;
558
                }
559
                $phpthumb_bmp          = new phpthumb_bmp();
560
                $this->outputImageData = $phpthumb_bmp->GD2BMPstring($this->gdimg_output);
561
                unset($phpthumb_bmp);
562
                break;
563
            case 'ico':
564
                if (!@require_once __DIR__ . '/phpthumb.ico.php') {
565
                    $this->DebugMessage('Error including "' . __DIR__ . '/phpthumb.ico.php" which is required for ICO format output', __FILE__, __LINE__);
566
                    ob_end_clean();
567
568
                    return false;
569
                }
570
                $phpthumb_ico          = new phpthumb_ico();
571
                $arrayOfOutputImages   = [$this->gdimg_output];
572
                $this->outputImageData = $phpthumb_ico->GD2ICOstring($arrayOfOutputImages);
573
                unset($phpthumb_ico);
574
                break;
575
            default:
576
                $this->DebugMessage('RenderOutput failed because $this->thumbnailFormat "' . $this->thumbnailFormat . '" is not valid', __FILE__, __LINE__);
577
                ob_end_clean();
578
579
                return false;
580
        }
581
        ob_end_clean();
582
        if (!$this->outputImageData) {
583
            $this->DebugMessage('RenderOutput() for "' . $this->thumbnailFormat . '" failed', __FILE__, __LINE__);
584
            ob_end_clean();
585
586
            return false;
587
        }
588
        $this->DebugMessage('RenderOutput() completing with $this->outputImageData = ' . mb_strlen($this->outputImageData) . ' bytes', __FILE__, __LINE__);
589
590
        return true;
591
    }
592
593
    // public:
594
595
    /**
596
     * @param $filename
597
     * @return bool
598
     */
599
    public function RenderToFile($filename)
600
    {
601
        if (preg_match('#^[a-z0-9]+://#i', $filename)) {
602
            $this->DebugMessage('RenderToFile() failed because $filename (' . $filename . ') is a URL', __FILE__, __LINE__);
603
604
            return false;
605
        }
606
        // render thumbnail to this file only, do not cache, do not output to browser
607
        //$renderfilename = $this->ResolveFilenameToAbsolute(dirname($filename)).DIRECTORY_SEPARATOR.basename($filename);
608
        $renderfilename = $filename;
609
        if (('/' !== $filename[0]) && ('\\' !== $filename[0]) && (':' !== $filename[1])) {
610
            $renderfilename = $this->ResolveFilenameToAbsolute($renderfilename);
611
        }
612
        if (!@is_writable(dirname($renderfilename))) {
613
            $this->DebugMessage('RenderToFile() failed because "' . \dirname($renderfilename) . '/" is not writable', __FILE__, __LINE__);
614
615
            return false;
616
        }
617
        if (@is_file($renderfilename) && !@is_writable($renderfilename)) {
618
            $this->DebugMessage('RenderToFile() failed because "' . $renderfilename . '" is not writable', __FILE__, __LINE__);
619
620
            return false;
621
        }
622
623
        if ($this->RenderOutput()) {
624
            if (file_put_contents($renderfilename, $this->outputImageData)) {
625
                $this->DebugMessage('RenderToFile(' . $renderfilename . ') succeeded', __FILE__, __LINE__);
626
627
                return true;
628
            }
629
            if (!@file_exists($renderfilename)) {
630
                $this->DebugMessage('RenderOutput [' . $this->thumbnailFormat . '(' . $renderfilename . ')] did not appear to fail, but the output image does not exist either...', __FILE__, __LINE__);
631
            }
632
        } else {
633
            $this->DebugMessage('RenderOutput [' . $this->thumbnailFormat . '(' . $renderfilename . ')] failed', __FILE__, __LINE__);
634
        }
635
636
        return false;
637
    }
638
639
    // public:
640
641
    /**
642
     * @return bool
643
     */
644
    public function OutputThumbnail()
645
    {
646
        $this->purgeTempFiles();
647
648
        if (!$this->useRawIMoutput && !is_resource($this->gdimg_output)) {
649
            $this->DebugMessage('OutputThumbnail() failed because !is_resource($this->gdimg_output)', __FILE__, __LINE__);
650
651
            return false;
652
        }
653
        if (headers_sent()) {
654
            return $this->ErrorImage('OutputThumbnail() failed - headers already sent');
655
        }
656
657
        $downloadfilename = phpthumb_functions::SanitizeFilename(is_string($this->sia) ? $this->sia : ($this->down ?: 'phpThumb_generated_thumbnail' . '.' . $this->thumbnailFormat));
658
        $this->DebugMessage('Content-Disposition header filename set to "' . $downloadfilename . '"', __FILE__, __LINE__);
659
        if ($downloadfilename) {
660
            header('Content-Disposition: ' . ($this->down ? 'attachment' : 'inline') . '; filename="' . $downloadfilename . '"');
661
        } else {
662
            $this->DebugMessage('failed to send Content-Disposition header because $downloadfilename is empty', __FILE__, __LINE__);
663
        }
664
665
        if ($this->useRawIMoutput) {
666
            header('Content-Type: ' . phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
0 ignored issues
show
Bug introduced by
Are you sure phpthumb_functions::Imag...$this->thumbnailFormat) of type false|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

666
            header('Content-Type: ' . /** @scrutinizer ignore-type */ phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
Loading history...
667
            echo $this->IMresizedData;
668
        } else {
669
            $this->DebugMessage('imageinterlace($this->gdimg_output, ' . (int)$this->config_output_interlace . ')', __FILE__, __LINE__);
670
            imageinterlace($this->gdimg_output, (int)$this->config_output_interlace);
671
            switch ($this->thumbnailFormat) {
672
                case 'jpeg':
673
                    header('Content-Type: ' . phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
674
                    $ImageOutFunction = 'image' . $this->thumbnailFormat;
675
                    @$ImageOutFunction($this->gdimg_output, null, $this->thumbnailQuality);
676
                    break;
677
                case 'png':
678
                case 'gif':
679
                    header('Content-Type: ' . phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
680
                    $ImageOutFunction = 'image' . $this->thumbnailFormat;
681
                    @$ImageOutFunction($this->gdimg_output);
682
                    break;
683
                case 'bmp':
684
                    if (!@require_once __DIR__ . '/phpthumb.bmp.php') {
685
                        $this->DebugMessage('Error including "' . __DIR__ . '/phpthumb.bmp.php" which is required for BMP format output', __FILE__, __LINE__);
686
687
                        return false;
688
                    }
689
                    $phpthumb_bmp = new phpthumb_bmp();
690
                    if (is_object($phpthumb_bmp)) {
691
                        $bmp_data = $phpthumb_bmp->GD2BMPstring($this->gdimg_output);
692
                        unset($phpthumb_bmp);
693
                        if (!$bmp_data) {
694
                            $this->DebugMessage('$phpthumb_bmp->GD2BMPstring() failed', __FILE__, __LINE__);
695
696
                            return false;
697
                        }
698
                        header('Content-Type: ' . phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
699
                        echo $bmp_data;
700
                    } else {
701
                        $this->DebugMessage('new phpthumb_bmp() failed', __FILE__, __LINE__);
702
703
                        return false;
704
                    }
705
                    break;
706
                case 'ico':
707
                    if (!@require_once __DIR__ . '/phpthumb.ico.php') {
708
                        $this->DebugMessage('Error including "' . __DIR__ . '/phpthumb.ico.php" which is required for ICO format output', __FILE__, __LINE__);
709
710
                        return false;
711
                    }
712
                    $phpthumb_ico = new phpthumb_ico();
713
                    if (is_object($phpthumb_ico)) {
714
                        $arrayOfOutputImages = [$this->gdimg_output];
715
                        $ico_data            = $phpthumb_ico->GD2ICOstring($arrayOfOutputImages);
716
                        unset($phpthumb_ico);
717
                        if (!$ico_data) {
718
                            $this->DebugMessage('$phpthumb_ico->GD2ICOstring() failed', __FILE__, __LINE__);
719
720
                            return false;
721
                        }
722
                        header('Content-Type: ' . phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
723
                        echo $ico_data;
724
                    } else {
725
                        $this->DebugMessage('new phpthumb_ico() failed', __FILE__, __LINE__);
726
727
                        return false;
728
                    }
729
                    break;
730
                default:
731
                    $this->DebugMessage('OutputThumbnail failed because $this->thumbnailFormat "' . $this->thumbnailFormat . '" is not valid', __FILE__, __LINE__);
732
733
                    return false;
734
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
735
            }
736
        }
737
738
        return true;
739
    }
740
741
    // public:
742
743
    /**
744
     * @return bool
745
     */
746
    public function CleanUpCacheDirectory()
747
    {
748
        $this->DebugMessage(
749
            'CleanUpCacheDirectory() set to purge (' . (null === $this->config_cache_maxage ? 'NULL' : number_format($this->config_cache_maxage / 86400, 1)) . ' days; ' . (null === $this->config_cache_maxsize ? 'NULL' : number_format($this->config_cache_maxsize / 1048576, 2)) . ' MB; ' . (null
750
                                                                                                                                                                                                                                                                                                  === $this->config_cache_maxfiles ? 'NULL' : number_format(
751
                $this->config_cache_maxfiles
752
            )) . ' files)',
753
            __FILE__,
754
            __LINE__
755
        );
756
757
        if (!is_writable($this->config_cache_directory)) {
758
            $this->DebugMessage('CleanUpCacheDirectory() skipped because "' . $this->config_cache_directory . '" is not writable', __FILE__, __LINE__);
759
760
            return true;
761
        }
762
763
        // cache status of cache directory for 1 hour to avoid hammering the filesystem functions
764
        $phpThumbCacheStats_filename = $this->config_cache_directory . DIRECTORY_SEPARATOR . 'phpThumbCacheStats.txt';
765
        if (file_exists($phpThumbCacheStats_filename) && is_readable($phpThumbCacheStats_filename)
766
            && (filemtime($phpThumbCacheStats_filename) >= (time() - 3600))) {
767
            $this->DebugMessage('CleanUpCacheDirectory() skipped because "' . $phpThumbCacheStats_filename . '" is recently modified', __FILE__, __LINE__);
768
769
            return true;
770
        }
771
        if (!@touch($phpThumbCacheStats_filename)) {
772
            $this->DebugMessage('touch(' . $phpThumbCacheStats_filename . ') failed', __FILE__, __LINE__);
773
        }
774
775
        $DeletedKeys              = [];
776
        $AllFilesInCacheDirectory = [];
777
        if (($this->config_cache_maxage > 0) || ($this->config_cache_maxsize > 0)
778
            || ($this->config_cache_maxfiles > 0)) {
779
            $CacheDirOldFilesAge      = [];
780
            $CacheDirOldFilesSize     = [];
781
            $AllFilesInCacheDirectory = phpthumb_functions::GetAllFilesInSubfolders($this->config_cache_directory);
782
            foreach ($AllFilesInCacheDirectory as $fullfilename) {
783
                if (preg_match('#' . preg_quote($this->config_cache_prefix) . '#i', $fullfilename)
784
                    && file_exists($fullfilename)) {
785
                    $CacheDirOldFilesAge[$fullfilename] = @fileatime($fullfilename);
786
                    if (0 == $CacheDirOldFilesAge[$fullfilename]) {
787
                        $CacheDirOldFilesAge[$fullfilename] = @filemtime($fullfilename);
788
                    }
789
                    $CacheDirOldFilesSize[$fullfilename] = @filesize($fullfilename);
790
                }
791
            }
792
            if (empty($CacheDirOldFilesSize)) {
793
                $this->DebugMessage('CleanUpCacheDirectory() skipped because $CacheDirOldFilesSize is empty (phpthumb_functions::GetAllFilesInSubfolders(' . $this->config_cache_directory . ') found no files)', __FILE__, __LINE__);
794
795
                return true;
796
            }
797
            $DeletedKeys['zerobyte'] = [];
798
            foreach ($CacheDirOldFilesSize as $fullfilename => $filesize) {
799
                // purge all zero-size files more than an hour old (to prevent trying to delete just-created and/or in-use files)
800
                $cutofftime = time() - 3600;
801
                if ((0 == $filesize) && ($CacheDirOldFilesAge[$fullfilename] < $cutofftime)) {
802
                    $this->DebugMessage('deleting "' . $fullfilename . '"', __FILE__, __LINE__);
803
                    if (@unlink($fullfilename)) {
804
                        $DeletedKeys['zerobyte'][] = $fullfilename;
805
                        unset($CacheDirOldFilesSize[$fullfilename]);
806
                        unset($CacheDirOldFilesAge[$fullfilename]);
807
                    }
808
                }
809
            }
810
            $this->DebugMessage('CleanUpCacheDirectory() purged ' . count($DeletedKeys['zerobyte']) . ' zero-byte files', __FILE__, __LINE__);
811
            asort($CacheDirOldFilesAge);
812
813
            if ($this->config_cache_maxfiles > 0) {
814
                $TotalCachedFiles        = count($CacheDirOldFilesAge);
815
                $DeletedKeys['maxfiles'] = [];
816
                foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
817
                    if ($TotalCachedFiles > $this->config_cache_maxfiles) {
818
                        $this->DebugMessage('deleting "' . $fullfilename . '"', __FILE__, __LINE__);
819
                        if (@unlink($fullfilename)) {
820
                            $TotalCachedFiles--;
821
                            $DeletedKeys['maxfiles'][] = $fullfilename;
822
                        }
823
                    } else {
824
                        // there are few enough files to keep the rest
825
                        break;
826
                    }
827
                }
828
                $this->DebugMessage('CleanUpCacheDirectory() purged ' . count($DeletedKeys['maxfiles']) . ' files based on (config_cache_maxfiles=' . $this->config_cache_maxfiles . ')', __FILE__, __LINE__);
829
                foreach ($DeletedKeys['maxfiles'] as $fullfilename) {
830
                    unset($CacheDirOldFilesAge[$fullfilename]);
831
                    unset($CacheDirOldFilesSize[$fullfilename]);
832
                }
833
            }
834
835
            if ($this->config_cache_maxage > 0) {
836
                $mindate               = time() - $this->config_cache_maxage;
837
                $DeletedKeys['maxage'] = [];
838
                foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
839
                    if ($filedate > 0) {
840
                        if ($filedate < $mindate) {
841
                            $this->DebugMessage('deleting "' . $fullfilename . '"', __FILE__, __LINE__);
842
                            if (@unlink($fullfilename)) {
843
                                $DeletedKeys['maxage'][] = $fullfilename;
844
                            }
845
                        } else {
846
                            // the rest of the files are new enough to keep
847
                            break;
848
                        }
849
                    }
850
                }
851
                $this->DebugMessage('CleanUpCacheDirectory() purged ' . count($DeletedKeys['maxage']) . ' files based on (config_cache_maxage=' . $this->config_cache_maxage . ')', __FILE__, __LINE__);
852
                foreach ($DeletedKeys['maxage'] as $fullfilename) {
853
                    unset($CacheDirOldFilesAge[$fullfilename]);
854
                    unset($CacheDirOldFilesSize[$fullfilename]);
855
                }
856
            }
857
858
            if ($this->config_cache_maxsize > 0) {
859
                $TotalCachedFileSize    = array_sum($CacheDirOldFilesSize);
860
                $DeletedKeys['maxsize'] = [];
861
                foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
862
                    if ($TotalCachedFileSize > $this->config_cache_maxsize) {
863
                        $this->DebugMessage('deleting "' . $fullfilename . '"', __FILE__, __LINE__);
864
                        if (@unlink($fullfilename)) {
865
                            $TotalCachedFileSize      -= $CacheDirOldFilesSize[$fullfilename];
866
                            $DeletedKeys['maxsize'][] = $fullfilename;
867
                        }
868
                    } else {
869
                        // the total filesizes are small enough to keep the rest of the files
870
                        break;
871
                    }
872
                }
873
                $this->DebugMessage('CleanUpCacheDirectory() purged ' . count($DeletedKeys['maxsize']) . ' files based on (config_cache_maxsize=' . $this->config_cache_maxsize . ')', __FILE__, __LINE__);
874
                foreach ($DeletedKeys['maxsize'] as $fullfilename) {
875
                    unset($CacheDirOldFilesAge[$fullfilename]);
876
                    unset($CacheDirOldFilesSize[$fullfilename]);
877
                }
878
            }
879
        } else {
880
            $this->DebugMessage('skipping CleanUpCacheDirectory() because config set to not use it', __FILE__, __LINE__);
881
        }
882
        $totalpurged = 0;
883
        foreach ($DeletedKeys as $key => $value) {
884
            $totalpurged += count($value);
885
        }
886
        $this->DebugMessage('CleanUpCacheDirectory() purged ' . $totalpurged . ' files (from ' . count($AllFilesInCacheDirectory) . ') based on config settings', __FILE__, __LINE__);
887
        if ($totalpurged > 0) {
888
            $empty_dirs = [];
889
            foreach ($AllFilesInCacheDirectory as $fullfilename) {
890
                if (is_dir($fullfilename)) {
891
                    $empty_dirs[$this->realPathSafe($fullfilename)] = 1;
892
                } else {
893
                    unset($empty_dirs[$this->realPathSafe(dirname($fullfilename))]);
894
                }
895
            }
896
            krsort($empty_dirs);
897
            $totalpurgeddirs = 0;
898
            foreach ($empty_dirs as $empty_dir => $dummy) {
899
                if ($empty_dir == $this->config_cache_directory) {
900
                    // shouldn't happen, but just in case, don't let it delete actual cache directory
901
                    continue;
902
                } elseif (@rmdir($empty_dir)) {
903
                    $totalpurgeddirs++;
904
                } else {
905
                    $this->DebugMessage('failed to rmdir(' . $empty_dir . ')', __FILE__, __LINE__);
906
                }
907
            }
908
            $this->DebugMessage('purged ' . $totalpurgeddirs . ' empty directories', __FILE__, __LINE__);
909
        }
910
911
        return true;
912
    }
913
914
    //////////////////////////////////////////////////////////////////////
915
916
    // private: re-initializator (call between rendering multiple images with one object)
917
918
    /**
919
     * @return bool
920
     */
921
    public function resetObject()
922
    {
923
        $class_vars = get_class_vars(get_class($this));
924
        foreach ($class_vars as $key => $value) {
925
            // do not clobber debug or config info
926
            if (!preg_match('#^(config_|debug|fatalerror)#i', $key)) {
927
                $this->$key = $value;
928
            }
929
        }
930
        $this->phpThumb(); // re-initialize some class variables
931
932
        return true;
933
    }
934
935
    //////////////////////////////////////////////////////////////////////
936
937
    /**
938
     * @return bool
939
     */
940
    public function ResolveSource()
941
    {
942
        if (is_resource($this->gdimg_source)) {
943
            $this->DebugMessage('ResolveSource() exiting because is_resource($this->gdimg_source)', __FILE__, __LINE__);
944
945
            return true;
946
        }
947
        if ($this->rawImageData) {
948
            $this->sourceFilename = null;
949
            $this->DebugMessage('ResolveSource() exiting because $this->rawImageData is set (' . number_format(mb_strlen($this->rawImageData)) . ' bytes)', __FILE__, __LINE__);
950
951
            return true;
952
        }
953
        if ($this->sourceFilename) {
954
            $this->sourceFilename = $this->ResolveFilenameToAbsolute($this->sourceFilename);
955
            $this->DebugMessage('$this->sourceFilename set to "' . $this->sourceFilename . '"', __FILE__, __LINE__);
956
        } elseif ($this->src) {
957
            $this->sourceFilename = $this->ResolveFilenameToAbsolute($this->src);
958
            $this->DebugMessage('$this->sourceFilename set to "' . $this->sourceFilename . '" from $this->src (' . $this->src . ')', __FILE__, __LINE__);
959
        } else {
960
            return $this->ErrorImage('$this->sourceFilename and $this->src are both empty');
961
        }
962
        if ($this->iswindows
963
            && (('//' === mb_substr($this->sourceFilename, 0, 2))
0 ignored issues
show
Bug introduced by
It seems like $this->sourceFilename can also be of type false and null; however, parameter $string of mb_substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

963
            && (('//' === mb_substr(/** @scrutinizer ignore-type */ $this->sourceFilename, 0, 2))
Loading history...
964
                || ('\\\\' === mb_substr($this->sourceFilename, 0, 2)))) {
965
            // Windows \\share\filename.ext
966
        } elseif (preg_match('#^[a-z0-9]+://#i', $this->sourceFilename, $protocol_matches)) {
0 ignored issues
show
Bug introduced by
It seems like $this->sourceFilename can also be of type false and null; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

966
        } elseif (preg_match('#^[a-z0-9]+://#i', /** @scrutinizer ignore-type */ $this->sourceFilename, $protocol_matches)) {
Loading history...
967
            if (preg_match('#^(f|ht)tps?\://#i', $this->sourceFilename)) {
968
                // URL
969
                if ($this->config_http_user_agent) {
970
                    ini_set('user_agent', $this->config_http_user_agent);
971
                }
972
            } else {
973
                return $this->ErrorImage('only FTP and HTTP/HTTPS protocols are allowed, "' . $protocol_matches[1] . '" is not');
974
            }
975
        } elseif (!@file_exists($this->sourceFilename)) {
0 ignored issues
show
Bug introduced by
It seems like $this->sourceFilename can also be of type false and null; however, parameter $filename of file_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

975
        } elseif (!@file_exists(/** @scrutinizer ignore-type */ $this->sourceFilename)) {
Loading history...
976
            return $this->ErrorImage('"' . $this->sourceFilename . '" does not exist');
977
        } elseif (!@is_file($this->sourceFilename)) {
0 ignored issues
show
Bug introduced by
It seems like $this->sourceFilename can also be of type false and null; however, parameter $filename of is_file() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

977
        } elseif (!@is_file(/** @scrutinizer ignore-type */ $this->sourceFilename)) {
Loading history...
978
            return $this->ErrorImage('"' . $this->sourceFilename . '" is not a file');
979
        }
980
981
        return true;
982
    }
983
984
    /**
985
     * @return bool
986
     */
987
    public function setOutputFormat()
988
    {
989
        static $alreadyCalled = false;
990
        if ($this->thumbnailFormat && $alreadyCalled) {
991
            return true;
992
        }
993
        $alreadyCalled = true;
994
995
        $AvailableImageOutputFormats   = [];
996
        $AvailableImageOutputFormats[] = 'text';
997
        if (@is_readable(__DIR__ . '/phpthumb.ico.php')) {
998
            $AvailableImageOutputFormats[] = 'ico';
999
        }
1000
        if (@is_readable(__DIR__ . '/phpthumb.bmp.php')) {
1001
            $AvailableImageOutputFormats[] = 'bmp';
1002
        }
1003
1004
        $this->thumbnailFormat = 'ico';
1005
1006
        // Set default output format based on what image types are available
1007
        if (function_exists('imagetypes')) {
1008
            $imagetypes = imagetypes();
1009
            if ($imagetypes & IMG_WBMP) {
1010
                $this->thumbnailFormat         = 'wbmp';
1011
                $AvailableImageOutputFormats[] = 'wbmp';
1012
            }
1013
            if ($imagetypes & IMG_GIF) {
1014
                $this->thumbnailFormat         = 'gif';
1015
                $AvailableImageOutputFormats[] = 'gif';
1016
            }
1017
            if ($imagetypes & IMG_PNG) {
1018
                $this->thumbnailFormat         = 'png';
1019
                $AvailableImageOutputFormats[] = 'png';
1020
            }
1021
            if ($imagetypes & IMG_JPG) {
1022
                $this->thumbnailFormat         = 'jpeg';
1023
                $AvailableImageOutputFormats[] = 'jpeg';
1024
            }
1025
        } else {
1026
            $this->DebugMessage('imagetypes() does not exist - GD support might not be enabled?', __FILE__, __LINE__);
1027
        }
1028
        if ($this->ImageMagickVersion()) {
1029
            $IMformats = ['jpeg', 'png', 'gif', 'bmp', 'ico', 'wbmp'];
1030
            $this->DebugMessage('Addding ImageMagick formats to $AvailableImageOutputFormats (' . implode(';', $AvailableImageOutputFormats) . ')', __FILE__, __LINE__);
1031
            foreach ($IMformats as $key => $format) {
1032
                $AvailableImageOutputFormats[] = $format;
1033
            }
1034
        }
1035
        $AvailableImageOutputFormats = array_unique($AvailableImageOutputFormats);
1036
        $this->DebugMessage('$AvailableImageOutputFormats = array(' . implode(';', $AvailableImageOutputFormats) . ')', __FILE__, __LINE__);
1037
1038
        $this->f = preg_replace('#[^a-z]#', '', mb_strtolower($this->f));
1039
        if ('jpg' === mb_strtolower($this->config_output_format)) {
1040
            $this->config_output_format = 'jpeg';
1041
        }
1042
        if ('jpg' === mb_strtolower($this->f)) {
1043
            $this->f = 'jpeg';
1044
        }
1045
        if (phpthumb_functions::CaseInsensitiveInArray($this->config_output_format, $AvailableImageOutputFormats)) {
1046
            // set output format to config default if that format is available
1047
            $this->DebugMessage('$this->thumbnailFormat set to $this->config_output_format "' . mb_strtolower($this->config_output_format) . '"', __FILE__, __LINE__);
1048
            $this->thumbnailFormat = mb_strtolower($this->config_output_format);
1049
        } elseif ($this->config_output_format) {
1050
            $this->DebugMessage('$this->thumbnailFormat staying as "' . $this->thumbnailFormat . '" because $this->config_output_format (' . mb_strtolower($this->config_output_format) . ') is not in $AvailableImageOutputFormats', __FILE__, __LINE__);
1051
        }
1052
        if ($this->f && phpthumb_functions::CaseInsensitiveInArray($this->f, $AvailableImageOutputFormats)) {
1053
            // override output format if $this->f is set and that format is available
1054
            $this->DebugMessage('$this->thumbnailFormat set to $this->f "' . mb_strtolower($this->f) . '"', __FILE__, __LINE__);
1055
            $this->thumbnailFormat = mb_strtolower($this->f);
1056
        } elseif ($this->f) {
1057
            $this->DebugMessage('$this->thumbnailFormat staying as "' . $this->thumbnailFormat . '" because $this->f (' . mb_strtolower($this->f) . ') is not in $AvailableImageOutputFormats', __FILE__, __LINE__);
1058
        }
1059
1060
        // for JPEG images, quality 1 (worst) to 99 (best)
1061
        // quality < 25 is nasty, with not much size savings - not recommended
1062
        // problems with 100 - invalid JPEG?
1063
        $this->thumbnailQuality = max(1, min(99, ($this->q ? (int)$this->q : 75)));
1064
        $this->DebugMessage('$this->thumbnailQuality set to "' . $this->thumbnailQuality . '"', __FILE__, __LINE__);
1065
1066
        return true;
1067
    }
1068
1069
    /**
1070
     * @return bool
1071
     */
1072
    public function setCacheDirectory()
1073
    {
1074
        // resolve cache directory to absolute pathname
1075
        $this->DebugMessage('setCacheDirectory() starting with config_cache_directory = "' . $this->config_cache_directory . '"', __FILE__, __LINE__);
1076
        if ('.' === mb_substr($this->config_cache_directory, 0, 1)) {
1077
            if (preg_match('#^(f|ht)tps?\://#i', $this->src)) {
1078
                if (!$this->config_cache_disable_warning) {
1079
                    $this->ErrorImage('$this->config_cache_directory (' . $this->config_cache_directory . ') cannot be used for remote images. Adjust "cache_directory" or "cache_disable_warning" in phpThumb.config.php');
1080
                }
1081
            } elseif ($this->src) {
1082
                // resolve relative cache directory to source image
1083
                $this->config_cache_directory = \dirname($this->ResolveFilenameToAbsolute($this->src)) . DIRECTORY_SEPARATOR . $this->config_cache_directory;
0 ignored issues
show
Bug introduced by
It seems like $this->ResolveFilenameToAbsolute($this->src) can also be of type false and null; however, parameter $path of dirname() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1083
                $this->config_cache_directory = \dirname(/** @scrutinizer ignore-type */ $this->ResolveFilenameToAbsolute($this->src)) . DIRECTORY_SEPARATOR . $this->config_cache_directory;
Loading history...
1084
            }
1085
            // $this->new is probably set
1086
        }
1087
        if ('/' === mb_substr($this->config_cache_directory, -1)) {
1088
            $this->config_cache_directory = mb_substr($this->config_cache_directory, 0, -1);
1089
        }
1090
        if ($this->iswindows) {
1091
            $this->config_cache_directory = str_replace('/', DIRECTORY_SEPARATOR, $this->config_cache_directory);
1092
        }
1093
        if ($this->config_cache_directory) {
1094
            $real_cache_path = $this->realPathSafe($this->config_cache_directory);
1095
            if (!$real_cache_path) {
1096
                $this->DebugMessage('$this->realPathSafe($this->config_cache_directory) failed for "' . $this->config_cache_directory . '"', __FILE__, __LINE__);
1097
                if (!is_dir($this->config_cache_directory)) {
1098
                    $this->DebugMessage('!is_dir(' . $this->config_cache_directory . ')', __FILE__, __LINE__);
1099
                }
1100
            }
1101
            if ($real_cache_path) {
1102
                $this->DebugMessage('setting config_cache_directory to $this->realPathSafe(' . $this->config_cache_directory . ') = "' . $real_cache_path . '"', __FILE__, __LINE__);
1103
                $this->config_cache_directory = $real_cache_path;
1104
            }
1105
        }
1106
        if (!is_dir($this->config_cache_directory)) {
1107
            if (!$this->config_cache_disable_warning) {
1108
                $this->ErrorImage('$this->config_cache_directory (' . $this->config_cache_directory . ') does not exist. Adjust "cache_directory" or "cache_disable_warning" in phpThumb.config.php');
1109
            }
1110
            $this->DebugMessage('$this->config_cache_directory (' . $this->config_cache_directory . ') is not a directory', __FILE__, __LINE__);
1111
            $this->config_cache_directory = null;
1112
        } elseif (!@is_writable($this->config_cache_directory)) {
1113
            $this->DebugMessage('$this->config_cache_directory is not writable (' . $this->config_cache_directory . ')', __FILE__, __LINE__);
1114
        }
1115
1116
        $this->InitializeTempDirSetting();
1117
        if (!@is_dir($this->config_temp_directory) && !@is_writable($this->config_temp_directory)
1118
            && @is_dir($this->config_cache_directory)
1119
            && @is_writable($this->config_cache_directory)) {
1120
            $this->DebugMessage('setting $this->config_temp_directory = $this->config_cache_directory (' . $this->config_cache_directory . ')', __FILE__, __LINE__);
1121
            $this->config_temp_directory = $this->config_cache_directory;
1122
        }
1123
1124
        return true;
1125
    }
1126
1127
    /* Takes the array of path segments up to now, and the next segment (maybe a modifier: empty, . or ..)
1128
       Applies it, adding or removing from $segments as a result. Returns nothing. */
1129
    // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1130
1131
    /**
1132
     * @param $segments
1133
     * @param $segment
1134
     */
1135
    public function applyPathSegment(&$segments, $segment)
1136
    {
1137
        if ('.' === $segment) {
1138
            return; // always remove
1139
        }
1140
        if ('' == $segment) {
1141
            $test = array_pop($segments);
1142
            if (null === $test) {
1143
                $segments[] = $segment; // keep the first empty block
1144
            } elseif ('' == $test) {
1145
                $test = array_pop($segments);
1146
                if (null === $test) {
1147
                    $segments[] = $test;
1148
                    $segments[] = $segment; // keep the second one too
1149
                } else { // put both back and ignore segment
1150
                    $segments[] = $test;
1151
                    $segments[] = $test;
1152
                }
1153
            } else {
1154
                $segments[] = $test; // ignore empty blocks
1155
            }
1156
        } else {
1157
            if ('..' === $segment) {
1158
                $test = array_pop($segments);
1159
                if (null === $test) {
1160
                    $segments[] = $segment;
1161
                } elseif ('..' === $test) {
1162
                    $segments[] = $test;
1163
                    $segments[] = $segment;
1164
                } else {
1165
                    if ('' == $test) {
1166
                        $segments[] = $test;
1167
                    } // else nothing, remove both
1168
                }
1169
            } else {
1170
                $segments[] = $segment;
1171
            }
1172
        }
1173
    }
1174
1175
    /* Takes array of path components, normalizes it: removes empty slots and '.', collapses '..' and folder names.  Returns array. */
1176
    // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1177
1178
    /**
1179
     * @param $segments
1180
     * @return array
1181
     */
1182
    public function normalizePath($segments)
1183
    {
1184
        $parts = [];
1185
        foreach ($segments as $segment) {
1186
            $this->applyPathSegment($parts, $segment);
1187
        }
1188
1189
        return $parts;
1190
    }
1191
1192
    /* True if the provided path points (without resolving symbolic links) into one of the allowed directories. */
1193
    // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1194
1195
    /**
1196
     * @param $path
1197
     * @param $allowed_dirs
1198
     * @return bool
1199
     */
1200
    public function matchPath($path, $allowed_dirs)
1201
    {
1202
        if (!empty($allowed_dirs)) {
1203
            foreach ($allowed_dirs as $one_dir) {
1204
                if (preg_match('#^' . preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', $this->realPathSafe($one_dir))) . '#', $path)) {
1205
                    return true;
1206
                }
1207
            }
1208
        }
1209
1210
        return false;
1211
    }
1212
1213
    /* True if the provided path points inside one of open_basedirs (or if open_basedirs are disabled) */
1214
    // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1215
1216
    /**
1217
     * @param $path
1218
     * @return bool
1219
     */
1220
    public function isInOpenBasedir($path)
1221
    {
1222
        static $open_basedirs = null;
1223
        if (null === $open_basedirs) {
1224
            $ini_text = ini_get('open_basedir');
1225
            $this->DebugMessage('open_basedir: "' . $ini_text . '"', __FILE__, __LINE__);
1226
            $open_basedirs = [];
1227
            if (mb_strlen($ini_text) > 0) {
1228
                foreach (preg_split('#[;:]#', $ini_text) as $key => $value) {
1229
                    $open_basedirs[$key] = $this->realPathSafe($value);
1230
                }
1231
            }
1232
        }
1233
1234
        return (empty($open_basedirs) || $this->matchPath($path, $open_basedirs));
1235
    }
1236
1237
    /* Resolves all symlinks in $path, checking that each continuous part ends in an allowed zone. Returns null, if any component leads outside of allowed zone. */
1238
    // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1239
1240
    /**
1241
     * @param $path
1242
     * @param $allowed_dirs
1243
     * @return null|string
1244
     */
1245
    public function resolvePath($path, $allowed_dirs)
1246
    {
1247
        $this->DebugMessage('resolvePath: ' . $path . ' (allowed_dirs: ' . print_r($allowed_dirs, true) . ')', __FILE__, __LINE__);
0 ignored issues
show
Bug introduced by
Are you sure print_r($allowed_dirs, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1247
        $this->DebugMessage('resolvePath: ' . $path . ' (allowed_dirs: ' . /** @scrutinizer ignore-type */ print_r($allowed_dirs, true) . ')', __FILE__, __LINE__);
Loading history...
1248
1249
        // add base path to the top of the list
1250
        if (!$this->config_allow_src_above_docroot) {
1251
            array_unshift($allowed_dirs, $this->realPathSafe($this->config_document_root));
1252
        } else {
1253
            if (!$this->config_allow_src_above_phpthumb) {
1254
                array_unshift($allowed_dirs, $this->realPathSafe(__DIR__));
1255
            } else {
1256
                // no checks are needed, offload the work to realpath and forget about it
1257
                $this->DebugMessage('resolvePath: checks disabled, returning ' . $this->realPathSafe($path), __FILE__, __LINE__);
1258
1259
                return $this->realPathSafe($path);
1260
            }
1261
        }
1262
        if ('' == $path) {
1263
            return null; // save us trouble
1264
        }
1265
1266
        do {
1267
            $this->DebugMessage('resolvePath: iteration, path=' . $path . ', base path = ' . $allowed_dirs[0], __FILE__, __LINE__);
1268
1269
            $parts = [];
1270
            // do not use "cleaner" foreach version of this loop as later code relies on both $segments and $i
1271
            // http://support.silisoftware.com/phpBB3/viewtopic.php?t=964
1272
            $segments = explode(DIRECTORY_SEPARATOR, $path);
1273
            for ($i = 0; $i < count($segments); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1274
                $this->applyPathSegment($parts, $segments[$i]);
1275
                $thispart = implode(DIRECTORY_SEPARATOR, $parts);
1276
                if ($this->isInOpenBasedir($thispart)) {
1277
                    if (is_link($thispart)) {
1278
                        break;
1279
                    }
1280
                }
1281
            }
1282
1283
            $this->DebugMessage('resolvePath: stop at component ' . $i, __FILE__, __LINE__);
1284
            // test the part up to here
1285
            $path = implode(DIRECTORY_SEPARATOR, $parts);
1286
            $this->DebugMessage('resolvePath: stop at path=' . $path, __FILE__, __LINE__);
1287
            if (!$this->matchPath($path, $allowed_dirs)) {
1288
                $this->DebugMessage('resolvePath: no match, returning null', __FILE__, __LINE__);
1289
1290
                return null;
1291
            }
1292
            if ($i >= count($segments)) { // reached end
1293
                $this->DebugMessage('resolvePath: path parsed, over', __FILE__, __LINE__);
1294
                break;
1295
            }
1296
            // else it's symlink, rewrite path
1297
            $path = readlink($path);
1298
            $this->DebugMessage('resolvePath: symlink matched, target=' . $path, __FILE__, __LINE__);
1299
1300
            /*
1301
            Replace base path with symlink target.
1302
            Assuming:
1303
              /www/img/external -> /external
1304
            This is allowed:
1305
              GET /www/img/external/../external/test/pic.jpg
1306
            This isn't:
1307
              GET /www/img/external/../www/img/pic.jpg
1308
            So there's only one base path which is the last symlink target, but any number of stable whitelisted paths.
1309
            */
1310
            if ($this->config_auto_allow_symlinks) {
1311
                $allowed_dirs[0] = $path;
1312
            }
1313
            $path = $path . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, array_slice($segments, $i + 1));
1314
        } while (true);
1315
1316
        return $path;
1317
    }
1318
1319
    /**
1320
     * @param $filename
1321
     * @return bool|null|string|string[]
1322
     */
1323
    public function realPathSafe($filename)
1324
    {
1325
        // http://php.net/manual/en/function.realpath.php -- "Note: The running script must have executable permissions on all directories in the hierarchy, otherwise realpath() will return FALSE"
1326
        // realPathSafe() provides a reasonable facsimile of realpath() but does not resolve symbolic links, nor does it check that the file/path actually exists
1327
        if (!$this->config_disable_realpath) {
1328
            return realpath($filename);
1329
        }
1330
1331
        // http://stackoverflow.com/questions/21421569
1332
        $newfilename = preg_replace('#[\\/]+#', DIRECTORY_SEPARATOR, $filename);
1333
        if (!preg_match('#^' . DIRECTORY_SEPARATOR . '#', $newfilename)) {
1334
            $newfilename = __DIR__ . DIRECTORY_SEPARATOR . $newfilename;
1335
        }
1336
        do {
1337
            $beforeloop = $newfilename;
1338
1339
            // Replace all sequences of more than one / with a single one [[ If you're working on a system that treats // at the start of a path as special, make sure you replace multiple / characters at the start with two of them. This is the only place where POSIX allows (but does not mandate) special handling for multiples, in all other cases, multiple / characters are equivalent to a single one.]]
1340
            $newfilename = preg_replace('#' . DIRECTORY_SEPARATOR . '+#', DIRECTORY_SEPARATOR, $newfilename);
1341
1342
            // Replace all occurrences of /./ with /
1343
            $newfilename = preg_replace('#' . DIRECTORY_SEPARATOR . '\\.' . DIRECTORY_SEPARATOR . '#', DIRECTORY_SEPARATOR, $newfilename);
1344
1345
            // Remove ./ if at the start
1346
            $newfilename = preg_replace('#^\\.' . DIRECTORY_SEPARATOR . '#', '', $newfilename);
1347
1348
            // Remove /. if at the end
1349
            $newfilename = preg_replace('#' . DIRECTORY_SEPARATOR . '\\.$#', '', $newfilename);
1350
1351
            // Replace /anything/../ with /
1352
            $newfilename = preg_replace('#' . DIRECTORY_SEPARATOR . '[^' . DIRECTORY_SEPARATOR . ']+' . DIRECTORY_SEPARATOR . '\\.\\.' . DIRECTORY_SEPARATOR . '#', DIRECTORY_SEPARATOR, $newfilename);
1353
1354
            // Remove /anything/.. if at the end
1355
            $newfilename = preg_replace('#' . DIRECTORY_SEPARATOR . '[^' . DIRECTORY_SEPARATOR . ']+' . DIRECTORY_SEPARATOR . '\\.\\.$#', '', $newfilename);
1356
        } while ($newfilename != $beforeloop);
1357
1358
        return $newfilename;
1359
    }
1360
1361
    /**
1362
     * @param $filename
1363
     * @return bool|null|string|string[]
1364
     */
1365
    public function ResolveFilenameToAbsolute($filename)
1366
    {
1367
        if (empty($filename)) {
1368
            return false;
1369
        }
1370
1371
        if (preg_match('#^[a-z0-9]+\:/{1,2}#i', $filename)) {
1372
            // eg: http://host/path/file.jpg (HTTP URL)
1373
            // eg: ftp://host/path/file.jpg  (FTP URL)
1374
            // eg: data1:/path/file.jpg      (Netware path)
1375
1376
            //$AbsoluteFilename = $filename;
1377
            return $filename;
1378
        } elseif ($this->iswindows && isset($filename[1]) && (':' === $filename[1])) {
1379
            // absolute pathname (Windows)
1380
            $AbsoluteFilename = $filename;
1381
        } elseif ($this->iswindows && (('//' === mb_substr($filename, 0, 2)) || ('\\\\' === mb_substr($filename, 0, 2)))) {
1382
            // absolute pathname (Windows)
1383
            $AbsoluteFilename = $filename;
1384
        } elseif ('/' === $filename[0]) {
1385
            if (@is_readable($filename) && !@is_readable($this->config_document_root . $filename)) {
0 ignored issues
show
Bug introduced by
Are you sure $this->config_document_root of type mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1385
            if (@is_readable($filename) && !@is_readable(/** @scrutinizer ignore-type */ $this->config_document_root . $filename)) {
Loading history...
1386
                // absolute filename (*nix)
1387
                $AbsoluteFilename = $filename;
1388
            } elseif (isset($filename[1]) && ('~' === $filename[1])) {
1389
                // /~user/path
1390
                if ($ApacheLookupURIarray = phpthumb_functions::ApacheLookupURIarray($filename)) {
1391
                    $AbsoluteFilename = $ApacheLookupURIarray['filename'];
1392
                } else {
1393
                    $AbsoluteFilename = $this->realPathSafe($filename);
1394
                    if (@is_readable($AbsoluteFilename)) {
1395
                        $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "' . $filename . '", but the correct filename (' . $AbsoluteFilename . ') seems to have been resolved with $this->realPathSafe($filename)', __FILE__, __LINE__);
1396
                    } elseif (is_dir(dirname($AbsoluteFilename))) {
1397
                        $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "' . \dirname($filename) . '", but the correct directory (' . \dirname($AbsoluteFilename) . ') seems to have been resolved with $this->realPathSafe(.)', __FILE__, __LINE__);
1398
                    } else {
1399
                        return $this->ErrorImage('phpthumb_functions::ApacheLookupURIarray() failed for "' . $filename . '". This has been known to fail on Apache2 - try using the absolute filename for the source image (ex: "/home/user/httpdocs/image.jpg" instead of "/~user/image.jpg")');
1400
                    }
1401
                }
1402
            } else {
1403
                // relative filename (any OS)
1404
                if (preg_match('#^' . preg_quote($this->config_document_root) . '#', $filename)) {
0 ignored issues
show
Bug introduced by
It seems like $this->config_document_root can also be of type mixed; however, parameter $str of preg_quote() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1404
                if (preg_match('#^' . preg_quote(/** @scrutinizer ignore-type */ $this->config_document_root) . '#', $filename)) {
Loading history...
1405
                    $AbsoluteFilename = $filename;
1406
                    $this->DebugMessage('ResolveFilenameToAbsolute() NOT prepending $this->config_document_root (' . $this->config_document_root . ') to $filename (' . $filename . ') resulting in ($AbsoluteFilename = "' . $AbsoluteFilename . '")', __FILE__, __LINE__);
1407
                } else {
1408
                    $AbsoluteFilename = $this->config_document_root . $filename;
1409
                    $this->DebugMessage('ResolveFilenameToAbsolute() prepending $this->config_document_root (' . $this->config_document_root . ') to $filename (' . $filename . ') resulting in ($AbsoluteFilename = "' . $AbsoluteFilename . '")', __FILE__, __LINE__);
1410
                }
1411
            }
1412
        } else {
1413
            // relative to current directory (any OS)
1414
            $AbsoluteFilename = __DIR__ . DIRECTORY_SEPARATOR . preg_replace('#[/\\\\]#', DIRECTORY_SEPARATOR, $filename);
1415
1416
            if ('/~' === mb_substr(dirname(@$_SERVER['PHP_SELF']), 0, 2)) {
1417
                if ($ApacheLookupURIarray = phpthumb_functions::ApacheLookupURIarray(dirname(@$_SERVER['PHP_SELF']))) {
1418
                    $AbsoluteFilename = $ApacheLookupURIarray['filename'] . DIRECTORY_SEPARATOR . $filename;
1419
                } else {
1420
                    $AbsoluteFilename = $this->realPathSafe('.') . DIRECTORY_SEPARATOR . $filename;
1421
                    if (@is_readable($AbsoluteFilename)) {
1422
                        $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "' . \dirname(@$_SERVER['PHP_SELF']) . '", but the correct filename (' . $AbsoluteFilename . ') seems to have been resolved with $this->realPathSafe(.)/$filename', __FILE__, __LINE__);
1423
                    } elseif (is_dir(dirname($AbsoluteFilename))) {
1424
                        $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "' . \dirname(@$_SERVER['PHP_SELF']) . '", but the correct directory (' . \dirname($AbsoluteFilename) . ') seems to have been resolved with $this->realPathSafe(.)', __FILE__, __LINE__);
1425
                    } else {
1426
                        return $this->ErrorImage('phpthumb_functions::ApacheLookupURIarray() failed for "' . \dirname(@$_SERVER['PHP_SELF']) . '". This has been known to fail on Apache2 - try using the absolute filename for the source image');
1427
                    }
1428
                }
1429
            }
1430
        }
1431
        /*
1432
        // removed 2014-May-30: http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1433
        if (is_link($AbsoluteFilename)) {
1434
            $this->DebugMessage('is_link()==true, changing "'.$AbsoluteFilename.'" to "'.readlink($AbsoluteFilename).'"', __FILE__, __LINE__);
1435
            $AbsoluteFilename = readlink($AbsoluteFilename);
1436
        }
1437
        if ($this->realPathSafe($AbsoluteFilename)) {
1438
            $AbsoluteFilename = $this->realPathSafe($AbsoluteFilename);
1439
        }
1440
        */
1441
        if ($this->iswindows) {
1442
            $AbsoluteFilename = preg_replace('#^' . preg_quote($this->realPathSafe($this->config_document_root)) . '#i', str_replace('\\', '\\\\', $this->realPathSafe($this->config_document_root)), $AbsoluteFilename);
1443
            $AbsoluteFilename = str_replace(DIRECTORY_SEPARATOR, '/', $AbsoluteFilename);
1444
        }
1445
        $AbsoluteFilename = $this->resolvePath($AbsoluteFilename, $this->config_additional_allowed_dirs);
1446
        if (!$this->config_allow_src_above_docroot
1447
            && !preg_match('#^' . preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', $this->realPathSafe($this->config_document_root))) . '#', $AbsoluteFilename)) {
0 ignored issues
show
Bug introduced by
It seems like $AbsoluteFilename can also be of type null; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1447
            && !preg_match('#^' . preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', $this->realPathSafe($this->config_document_root))) . '#', /** @scrutinizer ignore-type */ $AbsoluteFilename)) {
Loading history...
1448
            $this->DebugMessage('!$this->config_allow_src_above_docroot therefore setting "' . $AbsoluteFilename . '" (outside "' . $this->realPathSafe($this->config_document_root) . '") to null', __FILE__, __LINE__);
1449
1450
            return false;
1451
        }
1452
        if (!$this->config_allow_src_above_phpthumb
1453
            && !preg_match('#^' . preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', __DIR__)) . '#', $AbsoluteFilename)) {
1454
            $this->DebugMessage('!$this->config_allow_src_above_phpthumb therefore setting "' . $AbsoluteFilename . '" (outside "' . __DIR__ . '") to null', __FILE__, __LINE__);
1455
1456
            return false;
1457
        }
1458
1459
        return $AbsoluteFilename;
1460
    }
1461
1462
    /**
1463
     * @param      $filename
1464
     * @param bool $cached
1465
     * @return mixed
1466
     */
1467
    public function file_exists_ignoreopenbasedir($filename, $cached = true)
1468
    {
1469
        static $open_basedirs = null;
1470
        static $file_exists_cache = [];
1471
        if (!$cached || !isset($file_exists_cache[$filename])) {
1472
            if (null === $open_basedirs) {
1473
                $open_basedirs = preg_split('#[;:]#', ini_get('open_basedir'));
1474
            }
1475
            if (empty($open_basedirs) || in_array(dirname($filename), $open_basedirs, true)) {
1476
                $file_exists_cache[$filename] = file_exists($filename);
1477
            } elseif ($this->iswindows) {
1478
                $ls_filename                  = trim(phpthumb_functions::SafeExec('dir /b ' . phpthumb_functions::escapeshellarg_replacement($filename)));
0 ignored issues
show
Bug introduced by
It seems like phpthumb_functions::Safe...replacement($filename)) can also be of type false; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1478
                $ls_filename                  = trim(/** @scrutinizer ignore-type */ phpthumb_functions::SafeExec('dir /b ' . phpthumb_functions::escapeshellarg_replacement($filename)));
Loading history...
1479
                $file_exists_cache[$filename] = ($ls_filename == basename($filename));  // command dir /b return only filename without path
1480
            } else {
1481
                $ls_filename                  = trim(phpthumb_functions::SafeExec('ls ' . phpthumb_functions::escapeshellarg_replacement($filename)));
1482
                $file_exists_cache[$filename] = ($ls_filename == $filename);
1483
            }
1484
        }
1485
1486
        return $file_exists_cache[$filename];
1487
    }
1488
1489
    /**
1490
     * @return bool|null|string
1491
     */
1492
    public function ImageMagickWhichConvert()
1493
    {
1494
        static $WhichConvert = null;
1495
        if (null === $WhichConvert) {
1496
            if ($this->iswindows) {
1497
                $WhichConvert = false;
1498
            } else {
1499
                $IMwhichConvertCacheFilename = $this->config_cache_directory . DIRECTORY_SEPARATOR . 'phpThumbCacheIMwhichConvert.txt';
1500
                if (false !== ($cachedwhichconvertstring = @file_get_contents($IMwhichConvertCacheFilename))) {
1501
                    $WhichConvert = $cachedwhichconvertstring;
1502
                } else {
1503
                    $WhichConvert = trim(phpthumb_functions::SafeExec('which convert'));
0 ignored issues
show
Bug introduced by
It seems like phpthumb_functions::SafeExec('which convert') can also be of type false; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1503
                    $WhichConvert = trim(/** @scrutinizer ignore-type */ phpthumb_functions::SafeExec('which convert'));
Loading history...
1504
                    @file_put_contents($IMwhichConvertCacheFilename, $WhichConvert);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for file_put_contents(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1504
                    /** @scrutinizer ignore-unhandled */ @file_put_contents($IMwhichConvertCacheFilename, $WhichConvert);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1505
                }
1506
            }
1507
        }
1508
1509
        return $WhichConvert;
1510
    }
1511
1512
    /**
1513
     * @return bool|null|string
1514
     */
1515
    public function ImageMagickCommandlineBase()
1516
    {
1517
        static $commandline = null;
1518
        if (null === $commandline) {
1519
            if ($this->issafemode) {
1520
                $commandline = '';
1521
1522
                return $commandline;
1523
            }
1524
1525
            $IMcommandlineBaseCacheFilename = $this->config_cache_directory . DIRECTORY_SEPARATOR . 'phpThumbCacheIMcommandlineBase.txt';
1526
            if (false !== ($commandline = @file_get_contents($IMcommandlineBaseCacheFilename))) {
1527
                return $commandline;
1528
            }
1529
1530
            $commandline = (null !== $this->config_imagemagick_path ? $this->config_imagemagick_path : '');
0 ignored issues
show
Unused Code introduced by
The assignment to $commandline is dead and can be removed.
Loading history...
1531
1532
            if ($this->config_imagemagick_path
1533
                && ($this->config_imagemagick_path != $this->realPathSafe($this->config_imagemagick_path))) {
1534
                if (@is_executable($this->realPathSafe($this->config_imagemagick_path))) {
1535
                    $this->DebugMessage('Changing $this->config_imagemagick_path (' . $this->config_imagemagick_path . ') to $this->realPathSafe($this->config_imagemagick_path) (' . $this->realPathSafe($this->config_imagemagick_path) . ')', __FILE__, __LINE__);
1536
                    $this->config_imagemagick_path = $this->realPathSafe($this->config_imagemagick_path);
1537
                } else {
1538
                    $this->DebugMessage('Leaving $this->config_imagemagick_path as (' . $this->config_imagemagick_path . ') because !is_execuatable($this->realPathSafe($this->config_imagemagick_path)) (' . $this->realPathSafe($this->config_imagemagick_path) . ')', __FILE__, __LINE__);
1539
                }
1540
            }
1541
            $this->DebugMessage('                  file_exists(' . $this->config_imagemagick_path . ') = ' . (int)(@file_exists($this->config_imagemagick_path)), __FILE__, __LINE__);
1542
            $this->DebugMessage('file_exists_ignoreopenbasedir(' . $this->config_imagemagick_path . ') = ' . (int)$this->file_exists_ignoreopenbasedir($this->config_imagemagick_path), __FILE__, __LINE__);
1543
            $this->DebugMessage('                      is_file(' . $this->config_imagemagick_path . ') = ' . (int)(@is_file($this->config_imagemagick_path)), __FILE__, __LINE__);
1544
            $this->DebugMessage('                is_executable(' . $this->config_imagemagick_path . ') = ' . (int)(@is_executable($this->config_imagemagick_path)), __FILE__, __LINE__);
1545
1546
            if ($this->file_exists_ignoreopenbasedir($this->config_imagemagick_path)) {
1547
                $this->DebugMessage('using ImageMagick path from $this->config_imagemagick_path (' . $this->config_imagemagick_path . ')', __FILE__, __LINE__);
1548
                if ($this->iswindows) {
1549
                    $commandline = mb_substr($this->config_imagemagick_path, 0, 2) . ' && cd ' . phpthumb_functions::escapeshellarg_replacement(str_replace('/', DIRECTORY_SEPARATOR, mb_substr(dirname($this->config_imagemagick_path), 2))) . ' && ' . phpthumb_functions::escapeshellarg_replacement(
1550
                            basename($this->config_imagemagick_path)
1551
                        );
1552
                } else {
1553
                    $commandline = phpthumb_functions::escapeshellarg_replacement($this->config_imagemagick_path);
1554
                }
1555
            } else {
1556
                $which_convert = $this->ImageMagickWhichConvert();
1557
                $IMversion     = $this->ImageMagickVersion();
1558
1559
                if ($which_convert && ('/' === $which_convert[0])
1560
                    && $this->file_exists_ignoreopenbasedir($which_convert)) {
1561
                    // `which convert` *should* return the path if "convert" exist, or nothing if it doesn't
1562
                    // other things *may* get returned, like "sh: convert: not found" or "no convert in /usr/local/bin /usr/sbin /usr/bin /usr/ccs/bin"
1563
                    // so only do this if the value returned exists as a file
1564
                    $this->DebugMessage('using ImageMagick path from `which convert` (' . $which_convert . ')', __FILE__, __LINE__);
1565
                    $commandline = 'convert';
1566
                } elseif ($IMversion) {
1567
                    $this->DebugMessage('setting ImageMagick path to $this->config_imagemagick_path (' . $this->config_imagemagick_path . ') [' . $IMversion . ']', __FILE__, __LINE__);
1568
                    $commandline = $this->config_imagemagick_path;
1569
                } else {
1570
                    $this->DebugMessage('ImageMagickThumbnailToGD() aborting because cannot find convert in $this->config_imagemagick_path (' . $this->config_imagemagick_path . '), and `which convert` returned (' . $which_convert . ')', __FILE__, __LINE__);
1571
                    $commandline = '';
1572
                }
1573
            }
1574
1575
            @file_put_contents($IMcommandlineBaseCacheFilename, $commandline);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for file_put_contents(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1575
            /** @scrutinizer ignore-unhandled */ @file_put_contents($IMcommandlineBaseCacheFilename, $commandline);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1576
        }
1577
1578
        return $commandline;
1579
    }
1580
1581
    /**
1582
     * @param bool $returnRAW
1583
     * @return mixed
1584
     */
1585
    public function ImageMagickVersion($returnRAW = false)
1586
    {
1587
        static $versionstring = null;
1588
        if (null === $versionstring) {
1589
            $versionstring = [0 => false, 1 => false];
1590
1591
            $IMversionCacheFilename = $this->config_cache_directory . DIRECTORY_SEPARATOR . 'phpThumbCacheIMversion.txt';
1592
            if ($cachedversionstring = @file_get_contents($IMversionCacheFilename)) {
1593
                $versionstring    = explode("\n", $cachedversionstring, 2);
1594
                $versionstring[0] = ($versionstring[0] ?: false); // "false" is stored as an empty string in the cache file
1595
                $versionstring[1] = ($versionstring[1] ?: false); // "false" is stored as an empty string in the cache file
1596
            } else {
1597
                $commandline = $this->ImageMagickCommandlineBase();
1598
                $commandline = (null !== $commandline ? $commandline : '');
1599
                if ($commandline) {
1600
                    $commandline .= ' --version';
1601
                    $this->DebugMessage('ImageMagick version checked with "' . $commandline . '"', __FILE__, __LINE__);
1602
                    $versionstring[1] = trim(phpthumb_functions::SafeExec($commandline));
0 ignored issues
show
Bug introduced by
It seems like phpthumb_functions::SafeExec($commandline) can also be of type false; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1602
                    $versionstring[1] = trim(/** @scrutinizer ignore-type */ phpthumb_functions::SafeExec($commandline));
Loading history...
1603
                    if (preg_match('#^Version: [^0-9]*([ 0-9\\.\\:Q/\\-]+)#i', $versionstring[1], $matches)) {
1604
                        $versionstring[0] = trim($matches[1]);
1605
                    } else {
1606
                        $versionstring[0] = false;
1607
                        $this->DebugMessage('ImageMagick did not return recognized version string (' . $versionstring[1] . ')', __FILE__, __LINE__);
1608
                    }
1609
                    $this->DebugMessage('ImageMagick convert --version says "' . @$matches[0] . '"', __FILE__, __LINE__);
1610
                }
1611
1612
                @file_put_contents($IMversionCacheFilename, $versionstring[0] . "\n" . $versionstring[1]);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for file_put_contents(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1612
                /** @scrutinizer ignore-unhandled */ @file_put_contents($IMversionCacheFilename, $versionstring[0] . "\n" . $versionstring[1]);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1613
            }
1614
        }
1615
1616
        return $versionstring[(int)$returnRAW];
1617
    }
1618
1619
    /**
1620
     * @param $switchname
1621
     * @return bool
1622
     */
1623
    public function ImageMagickSwitchAvailable($switchname)
1624
    {
1625
        static $IMoptions = null;
1626
        if (null === $IMoptions) {
1627
            $IMoptions   = [];
1628
            $commandline = $this->ImageMagickCommandlineBase();
1629
            if (null !== $commandline) {
1630
                $commandline  .= ' -help';
1631
                $IMhelp_lines = explode("\n", phpthumb_functions::SafeExec($commandline));
0 ignored issues
show
Bug introduced by
It seems like phpthumb_functions::SafeExec($commandline) can also be of type false; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1631
                $IMhelp_lines = explode("\n", /** @scrutinizer ignore-type */ phpthumb_functions::SafeExec($commandline));
Loading history...
1632
                foreach ($IMhelp_lines as $line) {
1633
                    if (preg_match('#^[\\+\\-]([a-z\\-]+) #', trim($line), $matches)) {
1634
                        $IMoptions[$matches[1]] = true;
1635
                    }
1636
                }
1637
            }
1638
        }
1639
        if (is_array($switchname)) {
1640
            $allOK = true;
1641
            foreach ($switchname as $key => $value) {
1642
                if (!isset($IMoptions[$value])) {
1643
                    $allOK = false;
1644
                    break;
1645
                }
1646
            }
1647
            $this->DebugMessage('ImageMagickSwitchAvailable(' . implode(';', $switchname) . ') = ' . (int)$allOK . '', __FILE__, __LINE__);
1648
        } else {
1649
            $allOK = isset($IMoptions[$switchname]);
1650
            $this->DebugMessage('ImageMagickSwitchAvailable(' . $switchname . ') = ' . (int)$allOK . '', __FILE__, __LINE__);
1651
        }
1652
1653
        return $allOK;
1654
    }
1655
1656
    /**
1657
     * @return bool|null|string
1658
     */
1659
    public function ImageMagickFormatsList()
1660
    {
1661
        static $IMformatsList = null;
1662
        if (null === $IMformatsList) {
1663
            $IMformatsList = '';
1664
            $commandline   = $this->ImageMagickCommandlineBase();
1665
            if (null !== $commandline) {
1666
                $commandline   = \dirname($commandline) . DIRECTORY_SEPARATOR . str_replace('convert', 'identify', basename($commandline));
1667
                $commandline   .= ' -list format';
1668
                $IMformatsList = phpthumb_functions::SafeExec($commandline);
1669
            }
1670
        }
1671
1672
        return $IMformatsList;
1673
    }
1674
1675
    /**
1676
     * @return bool
1677
     */
1678
    public function SourceDataToTempFile()
1679
    {
1680
        if ($IMtempSourceFilename = $this->phpThumb_tempnam()) {
1681
            $IMtempSourceFilename = $this->realPathSafe($IMtempSourceFilename);
1682
            ob_start();
1683
            $fp_tempfile         = fopen($IMtempSourceFilename, 'wb');
1684
            $tempfile_open_error = ob_get_contents();
1685
            ob_end_clean();
1686
            if ($fp_tempfile) {
0 ignored issues
show
introduced by
$fp_tempfile is of type resource, thus it always evaluated to false.
Loading history...
1687
                fwrite($fp_tempfile, $this->rawImageData);
1688
                fclose($fp_tempfile);
1689
                $this->sourceFilename = $IMtempSourceFilename;
1690
                $this->DebugMessage('ImageMagickThumbnailToGD() setting $this->sourceFilename to "' . $IMtempSourceFilename . '" from $this->rawImageData (' . mb_strlen($this->rawImageData) . ' bytes)', __FILE__, __LINE__);
1691
            } else {
1692
                $this->DebugMessage('ImageMagickThumbnailToGD() FAILED setting $this->sourceFilename to "' . $IMtempSourceFilename . '" (failed to open for writing: "' . $tempfile_open_error . '")', __FILE__, __LINE__);
1693
            }
1694
            unset($tempfile_open_error, $IMtempSourceFilename);
1695
1696
            return true;
1697
        }
1698
        $this->DebugMessage('SourceDataToTempFile() FAILED because $this->phpThumb_tempnam() failed', __FILE__, __LINE__);
1699
1700
        return false;
1701
    }
1702
1703
    /**
1704
     * @return bool
1705
     */
1706
    public function ImageMagickThumbnailToGD()
1707
    {
1708
        // http://www.imagemagick.org/script/command-line-options.php
1709
1710
        $this->useRawIMoutput = true;
1711
        if (phpthumb_functions::gd_version()) {
1712
            // if GD is not available, must use whatever ImageMagick can output
1713
1714
            // $UnAllowedParameters contains options that can only be processed in GD, not ImageMagick
1715
            // note: 'fltr' *may* need to be processed by GD, but we'll check that in more detail below
1716
            $UnAllowedParameters = ['xto', 'ar', 'bg', 'bc'];
1717
            // 'ra' may be part of this list, if not a multiple of 90 degrees
1718
            foreach ($UnAllowedParameters as $parameter) {
1719
                if (isset($this->$parameter)) {
1720
                    $this->DebugMessage('$this->useRawIMoutput=false because "' . $parameter . '" is set', __FILE__, __LINE__);
1721
                    $this->useRawIMoutput = false;
1722
                    break;
1723
                }
1724
            }
1725
        }
1726
        $this->DebugMessage('$this->useRawIMoutput=' . ($this->useRawIMoutput ? 'true' : 'false') . ' after checking $UnAllowedParameters', __FILE__, __LINE__);
1727
        $ImageCreateFunction = '';
1728
        $outputFormat        = $this->thumbnailFormat;
1729
        if (phpthumb_functions::gd_version()) {
1730
            if ($this->useRawIMoutput) {
1731
                switch ($this->thumbnailFormat) {
1732
                    case 'gif':
1733
                        $ImageCreateFunction = 'imagecreatefromgif';
1734
                        $this->is_alpha      = true;
1735
                        break;
1736
                    case 'png':
1737
                        $ImageCreateFunction = 'imagecreatefrompng';
1738
                        $this->is_alpha      = true;
1739
                        break;
1740
                    case 'jpg':
1741
                    case 'jpeg':
1742
                        $ImageCreateFunction = 'imagecreatefromjpeg';
1743
                        break;
1744
                    default:
1745
                        $this->DebugMessage('Forcing output to PNG because $this->thumbnailFormat (' . $this->thumbnailFormat . ' is not a GD-supported format)', __FILE__, __LINE__);
1746
                        $outputFormat         = 'png';
1747
                        $ImageCreateFunction  = 'imagecreatefrompng';
1748
                        $this->is_alpha       = true;
1749
                        $this->useRawIMoutput = false;
1750
                        break;
1751
                }
1752
                if (!function_exists(@$ImageCreateFunction)) {
1753
                    // ImageMagickThumbnailToGD() depends on imagecreatefrompng/imagecreatefromgif
1754
                    //$this->DebugMessage('ImageMagickThumbnailToGD() aborting because '.@$ImageCreateFunction.'() is not available', __FILE__, __LINE__);
1755
                    $this->useRawIMoutput = true;
1756
                    //return false;
1757
                }
1758
            } else {
1759
                $outputFormat         = 'png';
1760
                $ImageCreateFunction  = 'imagecreatefrompng';
1761
                $this->is_alpha       = true;
1762
                $this->useRawIMoutput = false;
1763
            }
1764
        }
1765
1766
        // http://freealter.org/doc_distrib/ImageMagick-5.1.1/www/convert.html
1767
        if (!$this->sourceFilename && $this->rawImageData) {
1768
            $this->SourceDataToTempFile();
1769
        }
1770
        if (!$this->sourceFilename) {
1771
            $this->DebugMessage('ImageMagickThumbnailToGD() aborting because $this->sourceFilename is empty', __FILE__, __LINE__);
1772
            $this->useRawIMoutput = false;
1773
1774
            return false;
1775
        }
1776
        if ($this->issafemode) {
1777
            $this->DebugMessage('ImageMagickThumbnailToGD() aborting because safe_mode is enabled', __FILE__, __LINE__);
1778
            $this->useRawIMoutput = false;
1779
1780
            return false;
1781
        }
1782
        // TO BE FIXED
1783
        //if (true) {
1784
        //  $this->DebugMessage('ImageMagickThumbnailToGD() aborting it is broken right now', __FILE__, __LINE__);
1785
        //  $this->useRawIMoutput = false;
1786
        //  return false;
1787
        //}
1788
1789
        $commandline = $this->ImageMagickCommandlineBase();
1790
        if ($commandline) {
1791
            if ($IMtempfilename = $this->phpThumb_tempnam()) {
1792
                $IMtempfilename = $this->realPathSafe($IMtempfilename);
1793
1794
                $IMuseExplicitImageOutputDimensions = false;
1795
                if ($this->ImageMagickSwitchAvailable('thumbnail') && $this->config_imagemagick_use_thumbnail) {
1796
                    $IMresizeParameter = 'thumbnail';
1797
                } else {
1798
                    $IMresizeParameter = 'resize';
1799
1800
                    // some (older? around 2002) versions of IM won't accept "-resize 100x" but require "-resize 100x100"
1801
                    $commandline_test                   = $this->ImageMagickCommandlineBase() . ' logo: -resize 1x ' . phpthumb_functions::escapeshellarg_replacement($IMtempfilename) . ' 2>&1';
1802
                    $IMresult_test                      = phpthumb_functions::SafeExec($commandline_test);
1803
                    $IMuseExplicitImageOutputDimensions = preg_match('#image dimensions are zero#i', $IMresult_test);
0 ignored issues
show
Bug introduced by
It seems like $IMresult_test can also be of type false; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1803
                    $IMuseExplicitImageOutputDimensions = preg_match('#image dimensions are zero#i', /** @scrutinizer ignore-type */ $IMresult_test);
Loading history...
1804
                    $this->DebugMessage('IMuseExplicitImageOutputDimensions = ' . (int)$IMuseExplicitImageOutputDimensions, __FILE__, __LINE__);
1805
                    if ($fp_im_temp = @fopen($IMtempfilename, 'wb')) {
1806
                        // erase temp image so ImageMagick logo doesn't get output if other processing fails
1807
                        fclose($fp_im_temp);
1808
                    }
1809
                }
1810
1811
                if (null !== $this->dpi && $this->ImageMagickSwitchAvailable('density')) {
1812
                    // for vector source formats only (WMF, PDF, etc)
1813
                    $commandline .= ' -flatten -density ' . phpthumb_functions::escapeshellarg_replacement($this->dpi);
1814
                }
1815
                ob_start();
1816
                $getimagesize      = getimagesize($this->sourceFilename);
1817
                $GetImageSizeError = ob_get_contents();
1818
                ob_end_clean();
1819
                if (is_array($getimagesize)) {
0 ignored issues
show
introduced by
The condition is_array($getimagesize) is always true.
Loading history...
1820
                    $this->DebugMessage('getimagesize(' . $this->sourceFilename . ') SUCCEEDED: ' . print_r($getimagesize, true), __FILE__, __LINE__);
0 ignored issues
show
Bug introduced by
Are you sure print_r($getimagesize, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1820
                    $this->DebugMessage('getimagesize(' . $this->sourceFilename . ') SUCCEEDED: ' . /** @scrutinizer ignore-type */ print_r($getimagesize, true), __FILE__, __LINE__);
Loading history...
1821
                } else {
1822
                    $this->DebugMessage('getimagesize(' . $this->sourceFilename . ') FAILED with error "' . $GetImageSizeError . '"', __FILE__, __LINE__);
1823
                }
1824
                if (is_array($getimagesize)) {
0 ignored issues
show
introduced by
The condition is_array($getimagesize) is always true.
Loading history...
1825
                    $this->DebugMessage('getimagesize(' . $this->sourceFilename . ') returned [w=' . $getimagesize[0] . ';h=' . $getimagesize[1] . ';f=' . $getimagesize[2] . ']', __FILE__, __LINE__);
1826
                    $this->source_width  = $getimagesize[0];
1827
                    $this->source_height = $getimagesize[1];
1828
                    $this->DebugMessage('source dimensions set to ' . $this->source_width . 'x' . $this->source_height, __FILE__, __LINE__);
1829
                    $this->SetOrientationDependantWidthHeight();
1830
1831
                    if (!preg_match('#(' . implode('|', $this->AlphaCapableFormats) . ')#i', $outputFormat)) {
1832
                        // not a transparency-capable format
1833
                        $commandline .= ' -background ' . phpthumb_functions::escapeshellarg_replacement('#' . ($this->bg ?: 'FFFFFF'));
1834
                        if (IMAGETYPE_GIF == $getimagesize[2]) {
1835
                            $commandline .= ' -flatten';
1836
                        }
1837
                    }
1838
                    if (IMAGETYPE_GIF == $getimagesize[2]) {
1839
                        $commandline .= ' -coalesce'; // may be needed for animated GIFs
1840
                    }
1841
                    if ($this->source_width || $this->source_height) {
1842
                        if ($this->zc) {
1843
                            $borderThickness = 0;
1844
                            if (!empty($this->fltr)) {
1845
                                foreach ($this->fltr as $key => $value) {
1846
                                    if (preg_match('#^bord\|([0-9]+)#', $value, $matches)) {
1847
                                        $borderThickness = $matches[1];
1848
                                        break;
1849
                                    }
1850
                                }
1851
                            }
1852
                            $wAll  = (int)max($this->w, $this->wp, $this->wl, $this->ws) - (2 * $borderThickness);
1853
                            $hAll  = (int)max($this->h, $this->hp, $this->hl, $this->hs) - (2 * $borderThickness);
1854
                            $imAR  = $this->source_width / $this->source_height;
1855
                            $zcAR  = (($wAll && $hAll) ? $wAll / $hAll : 1);
1856
                            $side  = phpthumb_functions::nonempty_min($this->source_width, $this->source_height, max($wAll, $hAll));
1857
                            $sideX = phpthumb_functions::nonempty_min($this->source_width, $wAll, round($hAll * $zcAR));
0 ignored issues
show
Unused Code introduced by
The assignment to $sideX is dead and can be removed.
Loading history...
1858
                            $sideY = phpthumb_functions::nonempty_min($this->source_height, $hAll, round($wAll / $zcAR));
1859
1860
                            $thumbnailH  = round(max($sideY, ($sideY * $zcAR) / $imAR));
1861
                            $commandline .= ' -' . $IMresizeParameter . ' ' . phpthumb_functions::escapeshellarg_replacement(($IMuseExplicitImageOutputDimensions ? $thumbnailH : '') . 'x' . $thumbnailH);
1862
1863
                            switch (mb_strtoupper($this->zc)) {
1864
                                case 'T':
1865
                                    $commandline .= ' -gravity north';
1866
                                    break;
1867
                                case 'B':
1868
                                    $commandline .= ' -gravity south';
1869
                                    break;
1870
                                case 'L':
1871
                                    $commandline .= ' -gravity west';
1872
                                    break;
1873
                                case 'R':
1874
                                    $commandline .= ' -gravity east';
1875
                                    break;
1876
                                case 'TL':
1877
                                    $commandline .= ' -gravity northwest';
1878
                                    break;
1879
                                case 'TR':
1880
                                    $commandline .= ' -gravity northeast';
1881
                                    break;
1882
                                case 'BL':
1883
                                    $commandline .= ' -gravity southwest';
1884
                                    break;
1885
                                case 'BR':
1886
                                    $commandline .= ' -gravity southeast';
1887
                                    break;
1888
                                case '1':
1889
                                case 'C':
1890
                                default:
1891
                                    $commandline .= ' -gravity center';
1892
                                    break;
1893
                            }
1894
1895
                            if (($wAll > 0) && ($hAll > 0)) {
1896
                                $commandline .= ' -crop ' . phpthumb_functions::escapeshellarg_replacement($wAll . 'x' . $hAll . '+0+0');
1897
                            } else {
1898
                                $commandline .= ' -crop ' . phpthumb_functions::escapeshellarg_replacement($side . 'x' . $side . '+0+0');
1899
                            }
1900
                            if ($this->ImageMagickSwitchAvailable('repage')) {
1901
                                $commandline .= ' +repage';
1902
                            } else {
1903
                                $this->DebugMessage('Skipping "+repage" because ImageMagick (v' . $this->ImageMagickVersion() . ') does not support it', __FILE__, __LINE__);
1904
                            }
1905
                        } elseif ($this->sw || $this->sh || $this->sx || $this->sy) {
1906
                            $crop_param = '';
1907
                            $crop_param .= ($this->sw ? (($this->sw < 2) ? round($this->sw * $this->source_width) : $this->sw) : $this->source_width);
1908
                            $crop_param .= 'x' . ($this->sh ? (($this->sh < 2) ? round($this->sh * $this->source_height) : $this->sh) : $this->source_height);
1909
                            $crop_param .= '+' . (($this->sx < 2) ? round($this->sx * $this->source_width) : $this->sx);
1910
                            $crop_param .= '+' . (($this->sy < 2) ? round($this->sy * $this->source_height) : $this->sy);
1911
                            // TO BE FIXED
1912
                            // makes 1x1 output
1913
                            // http://trainspotted.com/phpThumb/phpThumb.php?src=/content/CNR/47/CNR-4728-LD-L-20110723-898.jpg&w=100&h=100&far=1&f=png&fltr[]=lvl&sx=0.05&sy=0.25&sw=0.92&sh=0.42
1914
                            // '/usr/bin/convert' -density 150 -thumbnail 100x100 -contrast-stretch '0.1%' '/var/www/vhosts/trainspotted.com/httpdocs/content/CNR/47/CNR-4728-LD-L-20110723-898.jpg[0]' png:'/var/www/vhosts/trainspotted.com/httpdocs/phpThumb/_cache/pThumbIIUlvj'
1915
                            $commandline .= ' -crop ' . phpthumb_functions::escapeshellarg_replacement($crop_param);
1916
1917
                            // this is broken for aoe=1, but unsure how to fix. Send advice to [email protected]
1918
                            if ($this->w || $this->h) {
1919
                                //if ($this->ImageMagickSwitchAvailable('repage')) {
1920
                                if (false) {
1921
                                    // TO BE FIXED
1922
                                    // newer versions of ImageMagick require -repage <geometry>
1923
                                    $commandline .= ' -repage';
1924
                                } else {
1925
                                    $this->DebugMessage('Skipping "-repage" because ImageMagick (v' . $this->ImageMagickVersion() . ') does not support it', __FILE__, __LINE__);
1926
                                }
1927
                                if ($IMuseExplicitImageOutputDimensions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $IMuseExplicitImageOutputDimensions of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false 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...
1928
                                    if ($this->w && !$this->h) {
1929
                                        $this->h = ceil($this->w / ($this->source_width / $this->source_height));
1930
                                    } elseif ($this->h && !$this->w) {
1931
                                        $this->w = ceil($this->h * ($this->source_width / $this->source_height));
1932
                                    }
1933
                                }
1934
                                $commandline .= ' -' . $IMresizeParameter . ' ' . phpthumb_functions::escapeshellarg_replacement($this->w . 'x' . $this->h);
1935
                            }
1936
                        } else {
1937
                            if ($this->iar && ((int)$this->w > 0) && ((int)$this->h > 0)) {
1938
                                [$nw, $nh] = phpthumb_functions::TranslateWHbyAngle($this->w, $this->h, $this->ra);
1939
                                $nw          = ((0 != round($nw)) ? round($nw) : '');
1940
                                $nh          = ((0 != round($nh)) ? round($nh) : '');
1941
                                $commandline .= ' -' . $IMresizeParameter . ' ' . phpthumb_functions::escapeshellarg_replacement($nw . 'x' . $nh . '!');
1942
                            } else {
1943
                                $this->w = ((($this->aoe || $this->far)
1944
                                             && $this->w) ? $this->w : ($this->w ? phpthumb_functions::nonempty_min($this->w, $getimagesize[0]) : ''));
1945
                                $this->h = ((($this->aoe || $this->far)
1946
                                             && $this->h) ? $this->h : ($this->h ? phpthumb_functions::nonempty_min($this->h, $getimagesize[1]) : ''));
1947
                                if ($this->w || $this->h) {
1948
                                    if ($IMuseExplicitImageOutputDimensions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $IMuseExplicitImageOutputDimensions of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false 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...
1949
                                        if ($this->w && !$this->h) {
1950
                                            $this->h = ceil($this->w / ($this->source_width / $this->source_height));
1951
                                        } elseif ($this->h && !$this->w) {
1952
                                            $this->w = ceil($this->h * ($this->source_width / $this->source_height));
1953
                                        }
1954
                                    }
1955
                                    [$nw, $nh] = phpthumb_functions::TranslateWHbyAngle($this->w, $this->h, $this->ra);
1956
                                    $nw          = ((0 != round($nw)) ? round($nw) : '');
1957
                                    $nh          = ((0 != round($nh)) ? round($nh) : '');
1958
                                    $commandline .= ' -' . $IMresizeParameter . ' ' . phpthumb_functions::escapeshellarg_replacement($nw . 'x' . $nh);
1959
                                }
1960
                            }
1961
                        }
1962
                    }
1963
                } else {
1964
                    $this->DebugMessage('getimagesize(' . $this->sourceFilename . ') failed', __FILE__, __LINE__);
1965
                    if ($this->w || $this->h) {
1966
                        $exactDimensionsBang = (($this->iar && ((int)$this->w > 0)
1967
                                                 && ((int)$this->h > 0)) ? '!' : '');
1968
                        if ($IMuseExplicitImageOutputDimensions) {
1969
                            // unknown source aspect ratio, just put large number and hope IM figures it out
1970
                            $commandline .= ' -' . $IMresizeParameter . ' ' . phpthumb_functions::escapeshellarg_replacement(($this->w ?: '9999') . 'x' . ($this->h ?: '9999') . $exactDimensionsBang);
1971
                        } else {
1972
                            $commandline .= ' -' . $IMresizeParameter . ' ' . phpthumb_functions::escapeshellarg_replacement($this->w . 'x' . $this->h . $exactDimensionsBang);
1973
                        }
1974
                    }
1975
                }
1976
1977
                if ($this->ra) {
1978
                    $this->ra = (int)$this->ra;
1979
                    if ($this->ImageMagickSwitchAvailable('rotate')) {
1980
                        if (!preg_match('#(' . implode('|', $this->AlphaCapableFormats) . ')#i', $outputFormat)
1981
                            || phpthumb_functions::version_compare_replacement($this->ImageMagickVersion(), '6.3.7', '>=')) {
1982
                            $this->DebugMessage('Using ImageMagick rotate', __FILE__, __LINE__);
1983
                            $commandline .= ' -rotate ' . phpthumb_functions::escapeshellarg_replacement($this->ra);
1984
                            if (0 != ($this->ra % 90)) {
1985
                                if (preg_match('#(' . implode('|', $this->AlphaCapableFormats) . ')#i', $outputFormat)) {
1986
                                    // alpha-capable format
1987
                                    $commandline .= ' -background rgba(255,255,255,0)';
1988
                                } else {
1989
                                    $commandline .= ' -background ' . phpthumb_functions::escapeshellarg_replacement('#' . ($this->bg ?: 'FFFFFF'));
1990
                                }
1991
                            }
1992
                            $this->ra = 0;
1993
                        } else {
1994
                            $this->DebugMessage('Not using ImageMagick rotate because alpha background buggy before v6.3.7', __FILE__, __LINE__);
1995
                        }
1996
                    } else {
1997
                        $this->DebugMessage('Not using ImageMagick rotate because not supported', __FILE__, __LINE__);
1998
                    }
1999
                }
2000
2001
                $successfullyProcessedFilters = [];
2002
                foreach ($this->fltr as $filterkey => $filtercommand) {
2003
                    @list($command, $parameter) = explode('|', $filtercommand, 2);
2004
                    switch ($command) {
2005
                        case 'brit':
2006
                            if ($this->ImageMagickSwitchAvailable('modulate')) {
2007
                                $commandline                    .= ' -modulate ' . phpthumb_functions::escapeshellarg_replacement((100 + (int)$parameter) . ',100,100');
2008
                                $successfullyProcessedFilters[] = $filterkey;
2009
                            }
2010
                            break;
2011
                        case 'cont':
2012
                            if ($this->ImageMagickSwitchAvailable('contrast')) {
2013
                                $contDiv10 = round((int)$parameter / 10);
2014
                                if ($contDiv10 > 0) {
2015
                                    $contDiv10 = min($contDiv10, 100);
2016
                                    for ($i = 0; $i < $contDiv10; ++$i) {
2017
                                        $commandline .= ' -contrast'; // increase contrast by 10%
2018
                                    }
2019
                                } elseif ($contDiv10 < 0) {
2020
                                    $contDiv10 = max($contDiv10, -100);
2021
                                    for ($i = $contDiv10; $i < 0; ++$i) {
2022
                                        $commandline .= ' +contrast'; // decrease contrast by 10%
2023
                                    }
2024
                                }
2025
                                // do nothing
2026
2027
                                $successfullyProcessedFilters[] = $filterkey;
2028
                            }
2029
                            break;
2030
                        case 'ds':
2031
                            if ($this->ImageMagickSwitchAvailable(['colorspace', 'modulate'])) {
2032
                                if (100 == $parameter) {
2033
                                    $commandline .= ' -colorspace GRAY';
2034
                                    $commandline .= ' -modulate 100,0,100';
2035
                                } else {
2036
                                    $commandline .= ' -modulate ' . phpthumb_functions::escapeshellarg_replacement('100,' . (100 - (int)$parameter) . ',100');
2037
                                }
2038
                                $successfullyProcessedFilters[] = $filterkey;
2039
                            }
2040
                            break;
2041
                        case 'sat':
2042
                            if ($this->ImageMagickSwitchAvailable(['colorspace', 'modulate'])) {
2043
                                if (-100 == $parameter) {
2044
                                    $commandline .= ' -colorspace GRAY';
2045
                                    $commandline .= ' -modulate 100,0,100';
2046
                                } else {
2047
                                    $commandline .= ' -modulate ' . phpthumb_functions::escapeshellarg_replacement('100,' . (100 + (int)$parameter) . ',100');
2048
                                }
2049
                                $successfullyProcessedFilters[] = $filterkey;
2050
                            }
2051
                            break;
2052
                        case 'gray':
2053
                            if ($this->ImageMagickSwitchAvailable(['colorspace', 'modulate'])) {
2054
                                $commandline                    .= ' -colorspace GRAY';
2055
                                $commandline                    .= ' -modulate 100,0,100';
2056
                                $successfullyProcessedFilters[] = $filterkey;
2057
                            }
2058
                            break;
2059
                        case 'clr':
2060
                            if ($this->ImageMagickSwitchAvailable(['fill', 'colorize'])) {
2061
                                @list($amount, $color) = explode('|', $parameter);
2062
                                $commandline .= ' -fill ' . phpthumb_functions::escapeshellarg_replacement('#' . preg_replace('#[^0-9A-F]#i', '', $color));
2063
                                $commandline .= ' -colorize ' . phpthumb_functions::escapeshellarg_replacement(min(max((int)$amount, 0), 100));
2064
                            }
2065
                            break;
2066
                        case 'sep':
2067
                            if ($this->ImageMagickSwitchAvailable('sepia-tone')) {
2068
                                @list($amount, $color) = explode('|', $parameter);
2069
                                $amount = ($amount ?: 80);
2070
                                if (!$color) {
2071
                                    $commandline                    .= ' -sepia-tone ' . phpthumb_functions::escapeshellarg_replacement(min(max($amount, 0), 100) . '%');
2072
                                    $successfullyProcessedFilters[] = $filterkey;
2073
                                }
2074
                            }
2075
                            break;
2076
                        case 'gam':
2077
                            @list($amount) = explode('|', $parameter);
2078
                            $amount = min(max((float)$amount, 0.001), 10);
2079
                            if ('1.000' !== number_format($amount, 3)) {
2080
                                if ($this->ImageMagickSwitchAvailable('gamma')) {
2081
                                    $commandline                    .= ' -gamma ' . phpthumb_functions::escapeshellarg_replacement($amount);
2082
                                    $successfullyProcessedFilters[] = $filterkey;
2083
                                }
2084
                            }
2085
                            break;
2086
                        case 'neg':
2087
                            if ($this->ImageMagickSwitchAvailable('negate')) {
2088
                                $commandline                    .= ' -negate';
2089
                                $successfullyProcessedFilters[] = $filterkey;
2090
                            }
2091
                            break;
2092
                        case 'th':
2093
                            @list($amount) = explode('|', $parameter);
2094
                            if ($this->ImageMagickSwitchAvailable(['threshold', 'dither', 'monochrome'])) {
2095
                                $commandline                    .= ' -threshold ' . phpthumb_functions::escapeshellarg_replacement(round(min(max((int)$amount, 0), 255) / 2.55) . '%');
2096
                                $commandline                    .= ' -dither';
2097
                                $commandline                    .= ' -monochrome';
2098
                                $successfullyProcessedFilters[] = $filterkey;
2099
                            }
2100
                            break;
2101
                        case 'rcd':
2102
                            if ($this->ImageMagickSwitchAvailable(['colors', 'dither'])) {
2103
                                @list($colors, $dither) = explode('|', $parameter);
2104
                                $colors                         = ($colors ? (int)$colors : 256);
2105
                                $dither                         = ((mb_strlen($dither) > 0) ? (bool)$dither : true);
2106
                                $commandline                    .= ' -colors ' . phpthumb_functions::escapeshellarg_replacement(max($colors, 8)); // ImageMagick will otherwise fail with "cannot quantize to fewer than 8 colors"
2107
                                $commandline                    .= ($dither ? ' -dither' : ' +dither');
2108
                                $successfullyProcessedFilters[] = $filterkey;
2109
                            }
2110
                            break;
2111
                        case 'flip':
2112
                            if ($this->ImageMagickSwitchAvailable(['flip', 'flop'])) {
2113
                                if (false !== mb_strpos(mb_strtolower($parameter), 'x')) {
2114
                                    $commandline .= ' -flop';
2115
                                }
2116
                                if (false !== mb_strpos(mb_strtolower($parameter), 'y')) {
2117
                                    $commandline .= ' -flip';
2118
                                }
2119
                                $successfullyProcessedFilters[] = $filterkey;
2120
                            }
2121
                            break;
2122
                        case 'edge':
2123
                            if ($this->ImageMagickSwitchAvailable('edge')) {
2124
                                $parameter                      = (!empty($parameter) ? $parameter : 2);
2125
                                $commandline                    .= ' -edge ' . phpthumb_functions::escapeshellarg_replacement(!empty($parameter) ? $parameter : 1);
2126
                                $successfullyProcessedFilters[] = $filterkey;
2127
                            }
2128
                            break;
2129
                        case 'emb':
2130
                            if ($this->ImageMagickSwitchAvailable(['emboss', 'negate'])) {
2131
                                $parameter   = (!empty($parameter) ? $parameter : 2);
2132
                                $commandline .= ' -emboss ' . phpthumb_functions::escapeshellarg_replacement($parameter);
2133
                                if ($parameter < 2) {
2134
                                    $commandline .= ' -negate'; // ImageMagick negates the image for some reason with '-emboss 1';
2135
                                }
2136
                                $successfullyProcessedFilters[] = $filterkey;
2137
                            }
2138
                            break;
2139
                        case 'lvl':
2140
                            @list($band, $method, $threshold) = explode('|', $parameter);
2141
                            $band      = ($band ? preg_replace('#[^RGBA\\*]#', '', mb_strtoupper($band)) : '*');
2142
                            $method    = ((mb_strlen($method) > 0) ? (int)$method : 2);
2143
                            $threshold = ((mb_strlen($threshold) > 0) ? min(max((float)$threshold, 0), 100) : 0.1);
2144
2145
                            $band = preg_replace('#[^RGBA\\*]#', '', mb_strtoupper($band));
2146
2147
                            if (($method > 1)
2148
                                && !$this->ImageMagickSwitchAvailable([
2149
                                                                          'channel',
2150
                                                                          'contrast-stretch',
2151
                                                                      ])) {
2152
                                // Because ImageMagick processing happens before PHP-GD filters, and because some
2153
                                // clipping is involved in the "lvl" filter, if "lvl" happens before "wb" then the
2154
                                // "wb" filter will have (almost) no effect. Therefore, if "wb" is enabled then
2155
                                // force the "lvl" filter to be processed by GD, not ImageMagick.
2156
                                foreach ($this->fltr as $fltr_key => $fltr_value) {
2157
                                    [$fltr_cmd] = explode('|', $fltr_value);
2158
                                    if ('wb' === $fltr_cmd) {
2159
                                        $this->DebugMessage('Setting "lvl" filter method to "0" (from "' . $method . '") because white-balance filter also enabled', __FILE__, __LINE__);
2160
                                        $method = 0;
2161
                                    }
2162
                                }
2163
                            }
2164
2165
                            switch ($method) {
2166
                                case 0: // internal RGB
2167
                                case 1: // internal grayscale
2168
                                    break;
2169
                                case 2: // ImageMagick "contrast-stretch"
2170
                                    if ($this->ImageMagickSwitchAvailable('contrast-stretch')) {
2171
                                        if ('*' !== $band) {
2172
                                            $commandline .= ' -channel ' . phpthumb_functions::escapeshellarg_replacement(mb_strtoupper($band));
2173
                                        }
2174
                                        $threshold = preg_replace('#[^0-9\\.]#', '', $threshold); // should be unneccesary, but just to be double-sure
2175
                                        //$commandline .= ' -contrast-stretch '.phpthumb_functions::escapeshellarg_replacement($threshold.'%');
2176
                                        $commandline .= ' -contrast-stretch \'' . $threshold . '%\'';
2177
                                        if ('*' !== $band) {
2178
                                            $commandline .= ' +channel';
2179
                                        }
2180
                                        $successfullyProcessedFilters[] = $filterkey;
2181
                                    }
2182
                                    break;
2183
                                case 3: // ImageMagick "normalize"
2184
                                    if ($this->ImageMagickSwitchAvailable('normalize')) {
2185
                                        if ('*' !== $band) {
2186
                                            $commandline .= ' -channel ' . phpthumb_functions::escapeshellarg_replacement(mb_strtoupper($band));
2187
                                        }
2188
                                        $commandline .= ' -normalize';
2189
                                        if ('*' !== $band) {
2190
                                            $commandline .= ' +channel';
2191
                                        }
2192
                                        $successfullyProcessedFilters[] = $filterkey;
2193
                                    }
2194
                                    break;
2195
                                default:
2196
                                    $this->DebugMessage('unsupported method (' . $method . ') for "lvl" filter', __FILE__, __LINE__);
2197
                                    break;
2198
                            }
2199
                            if (isset($this->fltr[$filterkey]) && ($method > 1)) {
2200
                                $this->fltr[$filterkey] = $command . '|' . $band . '|0|' . $threshold;
2201
                                $this->DebugMessage('filter "lvl" remapped from method "' . $method . '" to method "0" because ImageMagick support is missing', __FILE__, __LINE__);
2202
                            }
2203
                            break;
2204
                        case 'wb':
2205
                            if ($this->ImageMagickSwitchAvailable(['channel', 'contrast-stretch'])) {
2206
                                @list($threshold) = explode('|', $parameter);
2207
                                $threshold = (!empty($threshold) ? min(max((float)$threshold, 0), 100) : 0.1);
2208
                                $threshold = preg_replace('#[^0-9\\.]#', '', $threshold); // should be unneccesary, but just to be double-sure
2209
                                //$commandline .= ' -channel R -contrast-stretch '.phpthumb_functions::escapeshellarg_replacement($threshold.'%'); // doesn't work on Windows because most versions of PHP do not properly
2210
                                //$commandline .= ' -channel G -contrast-stretch '.phpthumb_functions::escapeshellarg_replacement($threshold.'%'); // escape special characters (such as %) and just replace them with spaces
2211
                                //$commandline .= ' -channel B -contrast-stretch '.phpthumb_functions::escapeshellarg_replacement($threshold.'%'); // https://bugs.php.net/bug.php?id=43261
2212
                                $commandline                    .= ' -channel R -contrast-stretch \'' . $threshold . '%\'';
2213
                                $commandline                    .= ' -channel G -contrast-stretch \'' . $threshold . '%\'';
2214
                                $commandline                    .= ' -channel B -contrast-stretch \'' . $threshold . '%\'';
2215
                                $commandline                    .= ' +channel';
2216
                                $successfullyProcessedFilters[] = $filterkey;
2217
                            }
2218
                            break;
2219
                        case 'blur':
2220
                            if ($this->ImageMagickSwitchAvailable('blur')) {
2221
                                @list($radius) = explode('|', $parameter);
2222
                                $radius                         = (!empty($radius) ? min(max((int)$radius, 0), 25) : 1);
2223
                                $commandline                    .= ' -blur ' . phpthumb_functions::escapeshellarg_replacement($radius);
2224
                                $successfullyProcessedFilters[] = $filterkey;
2225
                            }
2226
                            break;
2227
                        case 'gblr':
2228
                            @list($radius) = explode('|', $parameter);
2229
                            $radius = (!empty($radius) ? min(max((int)$radius, 0), 25) : 1);
2230
                            // "-gaussian" changed to "-gaussian-blur" sometime around 2009
2231
                            if ($this->ImageMagickSwitchAvailable('gaussian-blur')) {
2232
                                $commandline                    .= ' -gaussian-blur ' . phpthumb_functions::escapeshellarg_replacement($radius);
2233
                                $successfullyProcessedFilters[] = $filterkey;
2234
                            } elseif ($this->ImageMagickSwitchAvailable('gaussian')) {
2235
                                $commandline                    .= ' -gaussian ' . phpthumb_functions::escapeshellarg_replacement($radius);
2236
                                $successfullyProcessedFilters[] = $filterkey;
2237
                            }
2238
                            break;
2239
                        case 'usm':
2240
                            if ($this->ImageMagickSwitchAvailable('unsharp')) {
2241
                                @list($amount, $radius, $threshold) = explode('|', $parameter);
2242
                                $amount                         = ($amount ? min(max((int)$radius, 0), 255) : 80);
2243
                                $radius                         = ($radius ? min(max((int)$radius, 0), 10) : 0.5);
2244
                                $threshold                      = (mb_strlen($threshold) ? min(max((int)$radius, 0), 50) : 3);
2245
                                $commandline                    .= ' -unsharp ' . phpthumb_functions::escapeshellarg_replacement(number_format(($radius * 2) - 1, 2, '.', '') . 'x1+' . number_format($amount / 100, 2, '.', '') . '+' . number_format($threshold / 100, 2, '.', ''));
2246
                                $successfullyProcessedFilters[] = $filterkey;
2247
                            }
2248
                            break;
2249
                        case 'bord':
2250
                            if ($this->ImageMagickSwitchAvailable([
2251
                                                                      'border',
2252
                                                                      'bordercolor',
2253
                                                                      'thumbnail',
2254
                                                                      'crop',
2255
                                                                  ])) {
2256
                                if (!$this->zc) {
2257
                                    @list($width, $rX, $rY, $color) = explode('|', $parameter);
2258
                                    $width = (int)$width;
2259
                                    $rX    = (int)$rX;
2260
                                    $rY    = (int)$rY;
2261
                                    if ($width && !$rX && !$rY) {
2262
                                        if (!phpthumb_functions::IsHexColor($color)) {
2263
                                            $color = ((!empty($this->bc)
2264
                                                       && phpthumb_functions::IsHexColor($this->bc)) ? $this->bc : '000000');
2265
                                        }
2266
                                        $commandline .= ' -border ' . phpthumb_functions::escapeshellarg_replacement($width);
2267
                                        $commandline .= ' -bordercolor ' . phpthumb_functions::escapeshellarg_replacement('#' . $color);
2268
2269
                                        if (preg_match('# \\-crop "([0-9]+)x([0-9]+)\\+0\\+0" #', $commandline, $matches)) {
2270
                                            $commandline = str_replace(' -crop "' . $matches[1] . 'x' . $matches[2] . '+0+0" ', ' -crop ' . phpthumb_functions::escapeshellarg_replacement(($matches[1] - (2 * $width)) . 'x' . ($matches[2] - (2 * $width)) . '+0+0') . ' ', $commandline);
2271
                                        } elseif (preg_match('# \\-' . $IMresizeParameter . ' "([0-9]+)x([0-9]+)" #', $commandline, $matches)) {
2272
                                            $commandline = str_replace(
2273
                                                ' -' . $IMresizeParameter . ' "' . $matches[1] . 'x' . $matches[2] . '" ',
2274
                                                ' -' . $IMresizeParameter . ' ' . phpthumb_functions::escapeshellarg_replacement(($matches[1] - (2 * $width)) . 'x' . ($matches[2] - (2 * $width))) . ' ',
2275
                                                $commandline
2276
                                            );
2277
                                        }
2278
                                        $successfullyProcessedFilters[] = $filterkey;
2279
                                    }
2280
                                }
2281
                            }
2282
                            break;
2283
                        case 'crop':
2284
                            break;
2285
                        case 'sblr':
2286
                            break;
2287
                        case 'mean':
2288
                            break;
2289
                        case 'smth':
2290
                            break;
2291
                        case 'bvl':
2292
                            break;
2293
                        case 'wmi':
2294
                            break;
2295
                        case 'wmt':
2296
                            break;
2297
                        case 'over':
2298
                            break;
2299
                        case 'hist':
2300
                            break;
2301
                        case 'fram':
2302
                            break;
2303
                        case 'drop':
2304
                            break;
2305
                        case 'mask':
2306
                            break;
2307
                        case 'elip':
2308
                            break;
2309
                        case 'ric':
2310
                            break;
2311
                        case 'stc':
2312
                            break;
2313
                        case 'size':
2314
                            break;
2315
                        default:
2316
                            $this->DebugMessage('Unknown $this->fltr[' . $filterkey . '] (' . $filtercommand . ') -- deleting filter command', __FILE__, __LINE__);
2317
                            $successfullyProcessedFilters[] = $filterkey;
2318
                            break;
2319
                    }
2320
                    if (!isset($this->fltr[$filterkey])) {
2321
                        $this->DebugMessage('Processed $this->fltr[' . $filterkey . '] (' . $filtercommand . ') with ImageMagick', __FILE__, __LINE__);
2322
                    } else {
2323
                        $this->DebugMessage('Skipping $this->fltr[' . $filterkey . '] (' . $filtercommand . ') with ImageMagick', __FILE__, __LINE__);
2324
                    }
2325
                }
2326
                $this->DebugMessage('Remaining $this->fltr after ImageMagick: (' . $this->phpThumbDebugVarDump($this->fltr) . ')', __FILE__, __LINE__);
2327
                if (count($this->fltr) > 0) {
2328
                    $this->useRawIMoutput = false;
2329
                }
2330
2331
                if (preg_match('#jpe?g#i', $outputFormat) && $this->q) {
2332
                    if ($this->ImageMagickSwitchAvailable(['quality', 'interlace'])) {
2333
                        $commandline .= ' -quality ' . phpthumb_functions::escapeshellarg_replacement($this->thumbnailQuality);
2334
                        if ($this->config_output_interlace) {
2335
                            // causes weird things with animated GIF... leave for JPEG only
2336
                            $commandline .= ' -interlace line '; // Use Line or Plane to create an interlaced PNG or GIF or progressive JPEG image
2337
                        }
2338
                    }
2339
                }
2340
                $commandline .= ' ' . phpthumb_functions::escapeshellarg_replacement(preg_replace('#[/\\\\]#', DIRECTORY_SEPARATOR, $this->sourceFilename) . (('gif' === $outputFormat) ? '' : '[' . (int)$this->sfn . ']')); // [0] means first frame of (GIF) animation, can be ignored
2341
                $commandline .= ' ' . $outputFormat . ':' . phpthumb_functions::escapeshellarg_replacement($IMtempfilename);
2342
                if (!$this->iswindows) {
2343
                    $commandline .= ' 2>&1';
2344
                }
2345
                $this->DebugMessage('ImageMagick called as (' . $commandline . ')', __FILE__, __LINE__);
2346
                $IMresult = phpthumb_functions::SafeExec($commandline);
2347
                clearstatcache();
2348
                if (!@file_exists($IMtempfilename) || !@filesize($IMtempfilename)) {
2349
                    $this->FatalError('ImageMagick failed with message (' . trim($IMresult) . ')');
0 ignored issues
show
Bug introduced by
It seems like $IMresult can also be of type false; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2349
                    $this->FatalError('ImageMagick failed with message (' . trim(/** @scrutinizer ignore-type */ $IMresult) . ')');
Loading history...
2350
                    $this->DebugMessage('ImageMagick failed with message (' . trim($IMresult) . ')', __FILE__, __LINE__);
2351
                    if ($this->iswindows && !$IMresult) {
2352
                        $this->DebugMessage('Check to make sure that PHP has read+write permissions to "' . \dirname($IMtempfilename) . '"', __FILE__, __LINE__);
2353
                    }
2354
                } else {
2355
                    foreach ($successfullyProcessedFilters as $dummy => $filterkey) {
2356
                        unset($this->fltr[$filterkey]);
2357
                    }
2358
                    $this->IMresizedData    = file_get_contents($IMtempfilename);
2359
                    $getimagesize_imresized = @getimagesize($IMtempfilename);
2360
                    $this->DebugMessage('getimagesize(' . $IMtempfilename . ') returned [w=' . $getimagesize_imresized[0] . ';h=' . $getimagesize_imresized[1] . ';f=' . $getimagesize_imresized[2] . ']', __FILE__, __LINE__);
2361
                    if (($this->config_max_source_pixels > 0)
2362
                        && ($this->config_max_source_pixels < ($getimagesize_imresized[0] * $getimagesize_imresized[1]))) {
2363
                        $this->DebugMessage(
2364
                            'skipping ImageMagickThumbnailToGD::'
2365
                            . $ImageCreateFunction
2366
                            . '() because IM output is too large ('
2367
                            . $getimagesize_imresized[0]
2368
                            . 'x'
2369
                            . $getimagesize_imresized[0]
2370
                            . ' = '
2371
                            . ($getimagesize_imresized[0] * $getimagesize_imresized[1])
2372
                            . ' > '
2373
                            . $this->config_max_source_pixels
2374
                            . ')',
2375
                            __FILE__,
2376
                            __LINE__
2377
                        );
2378
                    } elseif (function_exists(@$ImageCreateFunction)
2379
                              && ($this->gdimg_source = @$ImageCreateFunction($IMtempfilename))) {
2380
                        $this->source_width  = imagesx($this->gdimg_source);
2381
                        $this->source_height = imagesy($this->gdimg_source);
2382
                        $this->DebugMessage('ImageMagickThumbnailToGD::' . $ImageCreateFunction . '() succeeded, $this->gdimg_source is now (' . $this->source_width . 'x' . $this->source_height . ')', __FILE__, __LINE__);
2383
                        $this->DebugMessage('ImageMagickThumbnailToGD() returning $this->IMresizedData (' . mb_strlen($this->IMresizedData) . ' bytes)', __FILE__, __LINE__);
2384
                    } else {
2385
                        $this->useRawIMoutput = true;
2386
                        $this->DebugMessage('$this->useRawIMoutput set to TRUE because ' . @$ImageCreateFunction . '(' . $IMtempfilename . ') failed', __FILE__, __LINE__);
2387
                    }
2388
                    if (file_exists($IMtempfilename)) {
2389
                        $this->DebugMessage('deleting "' . $IMtempfilename . '"', __FILE__, __LINE__);
2390
                        @unlink($IMtempfilename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2390
                        /** @scrutinizer ignore-unhandled */ @unlink($IMtempfilename);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2391
                    }
2392
2393
                    return true;
2394
                }
2395
                if (file_exists($IMtempfilename)) {
2396
                    $this->DebugMessage('deleting "' . $IMtempfilename . '"', __FILE__, __LINE__);
2397
                    @unlink($IMtempfilename);
2398
                }
2399
            } elseif ($this->issafemode) {
2400
                $this->DebugMessage('ImageMagickThumbnailToGD() aborting because PHP safe_mode is enabled and phpThumb_tempnam() failed', __FILE__, __LINE__);
2401
                $this->useRawIMoutput = false;
2402
            } else {
2403
                if (file_exists($IMtempfilename)) {
2404
                    $this->DebugMessage('deleting "' . $IMtempfilename . '"', __FILE__, __LINE__);
2405
                    @unlink($IMtempfilename);
2406
                }
2407
                $this->DebugMessage('ImageMagickThumbnailToGD() aborting, phpThumb_tempnam() failed', __FILE__, __LINE__);
2408
            }
2409
        } else {
2410
            $this->DebugMessage('ImageMagickThumbnailToGD() aborting because ImageMagickCommandlineBase() failed', __FILE__, __LINE__);
2411
        }
2412
        $this->useRawIMoutput = false;
2413
2414
        return false;
2415
    }
2416
2417
    /**
2418
     * @return bool
2419
     */
2420
    public function Rotate()
2421
    {
2422
        if ($this->ra || $this->ar) {
2423
            if (!function_exists('imagerotate')) {
2424
                $this->DebugMessage('!function_exists(imagerotate)', __FILE__, __LINE__);
2425
2426
                return false;
2427
            }
2428
            if (!require_once __DIR__ . '/phpthumb.filters.php') {
2429
                $this->DebugMessage('Error including "' . __DIR__ . '/phpthumb.filters.php" which is required for applying filters (' . implode(';', $this->fltr) . ')', __FILE__, __LINE__);
2430
2431
                return false;
2432
            }
2433
2434
            $this->config_background_hexcolor = ($this->bg ?: $this->config_background_hexcolor);
2435
            if (!phpthumb_functions::IsHexColor($this->config_background_hexcolor)) {
2436
                return $this->ErrorImage('Invalid hex color string "' . $this->config_background_hexcolor . '" for parameter "bg"');
2437
            }
2438
2439
            $rotate_angle = 0;
2440
            if ($this->ra) {
2441
                $rotate_angle = (float)$this->ra;
2442
            } else {
2443
                if ('x' === $this->ar) {
2444
                    if (phpthumb_functions::version_compare_replacement(PHP_VERSION, '4.2.0', '>=')) {
2445
                        if ($this->sourceFilename) {
2446
                            if (function_exists('exif_read_data')) {
2447
                                if ($exif_data = @exif_read_data($this->sourceFilename, 'IFD0')) {
2448
                                    // http://sylvana.net/jpegcrop/exif_orientation.html
2449
                                    switch (@$exif_data['Orientation']) {
2450
                                        case 1:
2451
                                            $rotate_angle = 0;
2452
                                            break;
2453
                                        case 3:
2454
                                            $rotate_angle = 180;
2455
                                            break;
2456
                                        case 6:
2457
                                            $rotate_angle = 270;
2458
                                            break;
2459
                                        case 8:
2460
                                            $rotate_angle = 90;
2461
                                            break;
2462
                                        default:
2463
                                            $this->DebugMessage('EXIF auto-rotate failed because unknown $exif_data[Orientation] "' . @$exif_data['Orientation'] . '"', __FILE__, __LINE__);
2464
2465
                                            return false;
2466
                                            break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
2467
                                    }
2468
                                    $this->DebugMessage('EXIF auto-rotate set to ' . $rotate_angle . ' degrees ($exif_data[Orientation] = "' . @$exif_data['Orientation'] . '")', __FILE__, __LINE__);
2469
                                } else {
2470
                                    $this->DebugMessage('failed: exif_read_data(' . $this->sourceFilename . ')', __FILE__, __LINE__);
2471
2472
                                    return false;
2473
                                }
2474
                            } else {
2475
                                $this->DebugMessage('!function_exists(exif_read_data)', __FILE__, __LINE__);
2476
2477
                                return false;
2478
                            }
2479
                        } else {
2480
                            $this->DebugMessage('Cannot auto-rotate from EXIF data because $this->sourceFilename is empty', __FILE__, __LINE__);
2481
2482
                            return false;
2483
                        }
2484
                    } else {
2485
                        $this->DebugMessage('Cannot auto-rotate from EXIF data because PHP is less than v4.2.0 (' . PHP_VERSION . ')', __FILE__, __LINE__);
2486
2487
                        return false;
2488
                    }
2489
                } elseif (('l' === $this->ar) && ($this->source_height > $this->source_width)) {
2490
                    $rotate_angle = 270;
2491
                } elseif (('L' === $this->ar) && ($this->source_height > $this->source_width)) {
2492
                    $rotate_angle = 90;
2493
                } elseif (('p' === $this->ar) && ($this->source_width > $this->source_height)) {
2494
                    $rotate_angle = 90;
2495
                } elseif (('P' === $this->ar) && ($this->source_width > $this->source_height)) {
2496
                    $rotate_angle = 270;
2497
                }
2498
            }
2499
            if ($rotate_angle % 90) {
2500
                $this->is_alpha = true;
2501
            }
2502
            phpthumb_filters::ImprovedImageRotate($this->gdimg_source, $rotate_angle, $this->config_background_hexcolor, $this->bg, $this);
2503
            $this->source_width  = imagesx($this->gdimg_source);
2504
            $this->source_height = imagesy($this->gdimg_source);
2505
        }
2506
2507
        return true;
2508
    }
2509
2510
    /**
2511
     * @return bool
2512
     */
2513
    public function FixedAspectRatio()
2514
    {
2515
        // optional fixed-dimension images (regardless of aspect ratio)
2516
2517
        if (!$this->far) {
2518
            // do nothing
2519
            return true;
2520
        }
2521
2522
        if (!$this->w || !$this->h) {
2523
            return false;
2524
        }
2525
        $this->thumbnail_width  = $this->w;
2526
        $this->thumbnail_height = $this->h;
2527
        $this->is_alpha         = true;
2528
        if ($this->thumbnail_image_width >= $this->thumbnail_width) {
2529
            $aspectratio = $this->thumbnail_image_height / $this->thumbnail_image_width;
2530
            if ($this->w) {
2531
                $this->thumbnail_image_height = round($this->thumbnail_image_width * $aspectratio);
2532
                $this->thumbnail_height       = ($this->h ?: $this->thumbnail_image_height);
2533
            } elseif ($this->thumbnail_image_height < $this->thumbnail_height) {
2534
                $this->thumbnail_image_height = $this->thumbnail_height;
2535
                $this->thumbnail_image_width  = round($this->thumbnail_image_height / $aspectratio);
2536
            }
2537
        } else {
2538
            $aspectratio = $this->thumbnail_image_width / $this->thumbnail_image_height;
2539
            if ($this->h) {
2540
                $this->thumbnail_image_width = round($this->thumbnail_image_height * $aspectratio);
2541
            } elseif ($this->thumbnail_image_width < $this->thumbnail_width) {
2542
                $this->thumbnail_image_width  = $this->thumbnail_width;
2543
                $this->thumbnail_image_height = round($this->thumbnail_image_width / $aspectratio);
2544
            }
2545
        }
2546
2547
        return true;
2548
    }
2549
2550
    /**
2551
     * @param $hostname
2552
     * @param $allowed_domains
2553
     * @return mixed
2554
     */
2555
    public function OffsiteDomainIsAllowed($hostname, $allowed_domains)
2556
    {
2557
        static $domain_is_allowed = [];
2558
        $hostname = mb_strtolower($hostname);
2559
        if (!isset($domain_is_allowed[$hostname])) {
2560
            $domain_is_allowed[$hostname] = false;
2561
            foreach ($allowed_domains as $valid_domain) {
2562
                $starpos = mb_strpos($valid_domain, '*');
2563
                if (false !== $starpos) {
2564
                    $valid_domain = mb_substr($valid_domain, $starpos + 1);
2565
                    if (preg_match('#' . preg_quote($valid_domain) . '$#', $hostname)) {
2566
                        $domain_is_allowed[$hostname] = true;
2567
                        break;
2568
                    }
2569
                } else {
2570
                    if (mb_strtolower($valid_domain) === $hostname) {
2571
                        $domain_is_allowed[$hostname] = true;
2572
                        break;
2573
                    }
2574
                }
2575
            }
2576
        }
2577
2578
        return $domain_is_allowed[$hostname];
2579
    }
2580
2581
    /**
2582
     * @return bool
2583
     */
2584
    public function AntiOffsiteLinking()
2585
    {
2586
        // Optional anti-offsite hijacking of the thumbnail script
2587
        $allow = true;
2588
        if ($allow && $this->config_nooffsitelink_enabled
2589
            && (@\Xmf\Request::getString('HTTP_REFERER', '', 'SERVER')
2590
                || $this->config_nooffsitelink_require_refer)) {
2591
            $this->DebugMessage('AntiOffsiteLinking() checking $_SERVER[HTTP_REFERER] "' . @\Xmf\Request::getString('HTTP_REFERER', '', 'SERVER') . '"', __FILE__, __LINE__);
2592
            foreach ($this->config_nooffsitelink_valid_domains as $key => $valid_domain) {
2593
                // $_SERVER['HTTP_HOST'] contains the port number, so strip it out here to make default configuration work
2594
                [$clean_domain] = explode(':', $valid_domain);
2595
                $this->config_nooffsitelink_valid_domains[$key] = $clean_domain;
2596
            }
2597
            $parsed_url = phpthumb_functions::ParseURLbetter(@\Xmf\Request::getString('HTTP_REFERER', '', 'SERVER'));
2598
            if (!$this->OffsiteDomainIsAllowed(@$parsed_url['host'], $this->config_nooffsitelink_valid_domains)) {
2599
                $allow = false;
2600
                //$this->DebugMessage('AntiOffsiteLinking() - "'.@$parsed_url['host'].'" is NOT in $this->config_nooffsitelink_valid_domains ('.implode(';', $this->config_nooffsitelink_valid_domains).')', __FILE__, __LINE__);
2601
                $this->ErrorImage('AntiOffsiteLinking() - "' . @$parsed_url['host'] . '" is NOT in $this->config_nooffsitelink_valid_domains (' . implode(';', $this->config_nooffsitelink_valid_domains) . ')');
2602
            } else {
2603
                $this->DebugMessage('AntiOffsiteLinking() - "' . @$parsed_url['host'] . '" is in $this->config_nooffsitelink_valid_domains (' . implode(';', $this->config_nooffsitelink_valid_domains) . ')', __FILE__, __LINE__);
2604
            }
2605
        }
2606
2607
        if ($allow && $this->config_nohotlink_enabled && preg_match('#^(f|ht)tps?\://#i', $this->src)) {
2608
            $parsed_url = phpthumb_functions::ParseURLbetter($this->src);
2609
            //if (!phpthumb_functions::CaseInsensitiveInArray(@$parsed_url['host'], $this->config_nohotlink_valid_domains)) {
2610
            if (!$this->OffsiteDomainIsAllowed(@$parsed_url['host'], $this->config_nohotlink_valid_domains)) {
2611
                // This domain is not allowed
2612
                $allow = false;
2613
                $this->DebugMessage('AntiOffsiteLinking() - "' . $parsed_url['host'] . '" is NOT in $this->config_nohotlink_valid_domains (' . implode(';', $this->config_nohotlink_valid_domains) . ')', __FILE__, __LINE__);
2614
            } else {
2615
                $this->DebugMessage('AntiOffsiteLinking() - "' . $parsed_url['host'] . '" is in $this->config_nohotlink_valid_domains (' . implode(';', $this->config_nohotlink_valid_domains) . ')', __FILE__, __LINE__);
2616
            }
2617
        }
2618
2619
        if ($allow) {
2620
            $this->DebugMessage('AntiOffsiteLinking() says this is allowed', __FILE__, __LINE__);
2621
2622
            return true;
2623
        }
2624
2625
        if (!phpthumb_functions::IsHexColor($this->config_error_bgcolor)) {
2626
            return $this->ErrorImage('Invalid hex color string "' . $this->config_error_bgcolor . '" for $this->config_error_bgcolor');
2627
        }
2628
        if (!phpthumb_functions::IsHexColor($this->config_error_textcolor)) {
2629
            return $this->ErrorImage('Invalid hex color string "' . $this->config_error_textcolor . '" for $this->config_error_textcolor');
2630
        }
2631
        if ($this->config_nooffsitelink_erase_image) {
2632
            return $this->ErrorImage($this->config_nooffsitelink_text_message, $this->thumbnail_width, $this->thumbnail_height);
2633
        }
2634
        $this->config_nooffsitelink_watermark_src = $this->ResolveFilenameToAbsolute($this->config_nooffsitelink_watermark_src);
2635
        if (is_file($this->config_nooffsitelink_watermark_src)) {
0 ignored issues
show
Bug introduced by
It seems like $this->config_nooffsitelink_watermark_src can also be of type false and null; however, parameter $filename of is_file() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2635
        if (is_file(/** @scrutinizer ignore-type */ $this->config_nooffsitelink_watermark_src)) {
Loading history...
2636
            if (!require_once __DIR__ . '/phpthumb.filters.php') {
2637
                $this->DebugMessage('Error including "' . __DIR__ . '/phpthumb.filters.php" which is required for applying watermark', __FILE__, __LINE__);
2638
2639
                return false;
2640
            }
2641
            $watermark_img                   = $this->ImageCreateFromStringReplacement(file_get_contents($this->config_nooffsitelink_watermark_src));
0 ignored issues
show
Bug introduced by
It seems like $this->config_nooffsitelink_watermark_src can also be of type false and null; however, parameter $filename of file_get_contents() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2641
            $watermark_img                   = $this->ImageCreateFromStringReplacement(file_get_contents(/** @scrutinizer ignore-type */ $this->config_nooffsitelink_watermark_src));
Loading history...
Bug introduced by
file_get_contents($this-...sitelink_watermark_src) cannot be passed to phpthumb::ImageCreateFromStringReplacement() as the parameter $RawImageData expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2641
            $watermark_img                   = $this->ImageCreateFromStringReplacement(/** @scrutinizer ignore-type */ file_get_contents($this->config_nooffsitelink_watermark_src));
Loading history...
2642
            $phpthumbFilters                 = new phpthumb_filters();
2643
            $phpthumbFilters->phpThumbObject = &$this;
2644
            $opacity                         = 50;
2645
            $margin                          = 5;
2646
            $phpthumbFilters->WatermarkOverlay($this->gdimg_output, $watermark_img, '*', $opacity, $margin);
2647
            imagedestroy($watermark_img);
2648
            unset($phpthumbFilters);
2649
        } else {
2650
            $nohotlink_text_array = explode("\n", wordwrap($this->config_nooffsitelink_text_message, floor($this->thumbnail_width / imagefontwidth($this->config_error_fontsize)), "\n"));
0 ignored issues
show
Bug introduced by
floor($this->thumbnail_w...config_error_fontsize)) of type double is incompatible with the type integer expected by parameter $width of wordwrap(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2650
            $nohotlink_text_array = explode("\n", wordwrap($this->config_nooffsitelink_text_message, /** @scrutinizer ignore-type */ floor($this->thumbnail_width / imagefontwidth($this->config_error_fontsize)), "\n"));
Loading history...
2651
            $nohotlink_text_color = phpthumb_functions::ImageHexColorAllocate($this->gdimg_output, $this->config_error_textcolor);
2652
2653
            $topoffset = round(($this->thumbnail_height - (count($nohotlink_text_array) * imagefontheight($this->config_error_fontsize))) / 2);
2654
2655
            $rowcounter = 0;
2656
            $this->DebugMessage('AntiOffsiteLinking() writing ' . count($nohotlink_text_array) . ' lines of text "' . $this->config_nooffsitelink_text_message . '" (in #' . $this->config_error_textcolor . ') on top of image', __FILE__, __LINE__);
2657
            foreach ($nohotlink_text_array as $textline) {
2658
                $leftoffset = max(0, round(($this->thumbnail_width - (mb_strlen($textline) * imagefontwidth($this->config_error_fontsize))) / 2));
2659
                imagestring($this->gdimg_output, $this->config_error_fontsize, $leftoffset, $topoffset + ($rowcounter++ * imagefontheight($this->config_error_fontsize)), $textline, $nohotlink_text_color);
2660
            }
2661
        }
2662
2663
        return true;
2664
    }
2665
2666
    /**
2667
     * @return bool
2668
     */
2669
    public function AlphaChannelFlatten()
2670
    {
2671
        if (!$this->is_alpha) {
2672
            // image doesn't have alpha transparency, no need to flatten
2673
            $this->DebugMessage('skipping AlphaChannelFlatten() because !$this->is_alpha', __FILE__, __LINE__);
2674
2675
            return false;
2676
        }
2677
        switch ($this->thumbnailFormat) {
2678
            case 'png':
2679
            case 'ico':
2680
                // image has alpha transparency, but output as PNG or ICO which can handle it
2681
                $this->DebugMessage('skipping AlphaChannelFlatten() because ($this->thumbnailFormat == "' . $this->thumbnailFormat . '")', __FILE__, __LINE__);
2682
2683
                return false;
2684
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
2685
            case 'gif':
2686
                // image has alpha transparency, but output as GIF which can handle only single-color transparency
2687
                $CurrentImageColorTransparent = imagecolortransparent($this->gdimg_output);
2688
                if (-1 == $CurrentImageColorTransparent) {
2689
                    // no transparent color defined
2690
2691
                    if (phpthumb_functions::gd_version() < 2.0) {
2692
                        $this->DebugMessage('AlphaChannelFlatten() failed because GD version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2693
2694
                        return false;
2695
                    }
2696
2697
                    if ($img_alpha_mixdown_dither = @imagecreatetruecolor(imagesx($this->gdimg_output), imagesy($this->gdimg_output))) {
2698
                        $dither_color = [];
2699
                        for ($i = 0; $i <= 255; ++$i) {
2700
                            $dither_color[$i] = imagecolorallocate($img_alpha_mixdown_dither, $i, $i, $i);
2701
                        }
2702
2703
                        // scan through current truecolor image copy alpha channel to temp image as grayscale
2704
                        for ($x = 0; $x < $this->thumbnail_width; $x++) {
2705
                            for ($y = 0; $y < $this->thumbnail_height; $y++) {
2706
                                $PixelColor = phpthumb_functions::GetPixelColor($this->gdimg_output, $x, $y);
2707
                                imagesetpixel($img_alpha_mixdown_dither, $x, $y, $dither_color[$PixelColor['alpha'] * 2]);
2708
                            }
2709
                        }
2710
2711
                        // dither alpha channel grayscale version down to 2 colors
2712
                        imagetruecolortopalette($img_alpha_mixdown_dither, true, 2);
2713
2714
                        // reduce color palette to 256-1 colors (leave one palette position for transparent color)
2715
                        imagetruecolortopalette($this->gdimg_output, true, 255);
2716
2717
                        // allocate a new color for transparent color index
2718
                        $TransparentColor = imagecolorallocate($this->gdimg_output, 1, 254, 253);
2719
                        imagecolortransparent($this->gdimg_output, $TransparentColor);
2720
2721
                        // scan through alpha channel image and note pixels with >50% transparency
2722
                        for ($x = 0; $x < $this->thumbnail_width; $x++) {
2723
                            for ($y = 0; $y < $this->thumbnail_height; $y++) {
2724
                                $AlphaChannelPixel = phpthumb_functions::GetPixelColor($img_alpha_mixdown_dither, $x, $y);
2725
                                if ($AlphaChannelPixel['red'] > 127) {
2726
                                    imagesetpixel($this->gdimg_output, $x, $y, $TransparentColor);
2727
                                }
2728
                            }
2729
                        }
2730
                        imagedestroy($img_alpha_mixdown_dither);
2731
2732
                        $this->DebugMessage('AlphaChannelFlatten() set image to 255+1 colors with transparency for GIF output', __FILE__, __LINE__);
2733
2734
                        return true;
2735
                    }
2736
                    $this->DebugMessage('AlphaChannelFlatten() failed imagecreate(' . imagesx($this->gdimg_output) . ', ' . imagesy($this->gdimg_output) . ')', __FILE__, __LINE__);
2737
2738
                    return false;
2739
                }
2740
                // a single transparent color already defined, leave as-is
2741
                $this->DebugMessage('skipping AlphaChannelFlatten() because ($this->thumbnailFormat == "' . $this->thumbnailFormat . '") and imagecolortransparent() returned "' . $CurrentImageColorTransparent . '"', __FILE__, __LINE__);
2742
2743
                return true;
2744
                break;
2745
        }
2746
        $this->DebugMessage('continuing AlphaChannelFlatten() for output format "' . $this->thumbnailFormat . '"', __FILE__, __LINE__);
2747
        // image has alpha transparency, and is being output in a format that doesn't support it -- flatten
2748
        if ($gdimg_flatten_temp = phpthumb_functions::ImageCreateFunction($this->thumbnail_width, $this->thumbnail_height)) {
2749
            $this->config_background_hexcolor = ($this->bg ?: $this->config_background_hexcolor);
2750
            if (!phpthumb_functions::IsHexColor($this->config_background_hexcolor)) {
2751
                return $this->ErrorImage('Invalid hex color string "' . $this->config_background_hexcolor . '" for parameter "bg"');
2752
            }
2753
            $background_color = phpthumb_functions::ImageHexColorAllocate($this->gdimg_output, $this->config_background_hexcolor);
2754
            imagefilledrectangle($gdimg_flatten_temp, 0, 0, $this->thumbnail_width, $this->thumbnail_height, $background_color);
2755
            imagecopy($gdimg_flatten_temp, $this->gdimg_output, 0, 0, 0, 0, $this->thumbnail_width, $this->thumbnail_height);
2756
2757
            imagealphablending($this->gdimg_output, true);
2758
            imagesavealpha($this->gdimg_output, false);
2759
            imagecolortransparent($this->gdimg_output, -1);
2760
            imagecopy($this->gdimg_output, $gdimg_flatten_temp, 0, 0, 0, 0, $this->thumbnail_width, $this->thumbnail_height);
2761
2762
            imagedestroy($gdimg_flatten_temp);
2763
2764
            return true;
2765
        }
2766
        $this->DebugMessage('ImageCreateFunction() failed', __FILE__, __LINE__);
2767
2768
        return false;
2769
    }
2770
2771
    /**
2772
     * @return bool
2773
     */
2774
    public function ApplyFilters()
2775
    {
2776
        if ($this->fltr && is_array($this->fltr)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->fltr of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2777
            if (!require_once __DIR__ . '/phpthumb.filters.php') {
2778
                $this->DebugMessage('Error including "' . __DIR__ . '/phpthumb.filters.php" which is required for applying filters (' . implode(';', $this->fltr) . ')', __FILE__, __LINE__);
2779
2780
                return false;
2781
            }
2782
            $phpthumbFilters                 = new phpthumb_filters();
2783
            $phpthumbFilters->phpThumbObject = &$this;
2784
            foreach ($this->fltr as $filtercommand) {
2785
                @list($command, $parameter) = explode('|', $filtercommand, 2);
2786
                $this->DebugMessage('Attempting to process filter command "' . $command . '(' . $parameter . ')"', __FILE__, __LINE__);
2787
                switch ($command) {
2788
                    case 'brit': // Brightness
2789
                        $phpthumbFilters->Brightness($this->gdimg_output, $parameter);
2790
                        break;
2791
                    case 'cont': // Contrast
2792
                        $phpthumbFilters->Contrast($this->gdimg_output, $parameter);
2793
                        break;
2794
                    case 'ds': // Desaturation
2795
                        $phpthumbFilters->Desaturate($this->gdimg_output, $parameter, '');
2796
                        break;
2797
                    case 'sat': // Saturation
2798
                        $phpthumbFilters->Saturation($this->gdimg_output, $parameter, '');
2799
                        break;
2800
                    case 'gray': // Grayscale
2801
                        $phpthumbFilters->Grayscale($this->gdimg_output);
2802
                        break;
2803
                    case 'clr': // Colorize
2804
                        if (phpthumb_functions::gd_version() < 2) {
2805
                            $this->DebugMessage('Skipping Colorize() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2806
                            break;
2807
                        }
2808
                        @list($amount, $color) = explode('|', $parameter, 2);
2809
                        $phpthumbFilters->Colorize($this->gdimg_output, $amount, $color);
2810
                        break;
2811
                    case 'sep': // Sepia
2812
                        if (phpthumb_functions::gd_version() < 2) {
2813
                            $this->DebugMessage('Skipping Sepia() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2814
                            break;
2815
                        }
2816
                        @list($amount, $color) = explode('|', $parameter, 2);
2817
                        $phpthumbFilters->Sepia($this->gdimg_output, $amount, $color);
2818
                        break;
2819
                    case 'gam': // Gamma correction
2820
                        $phpthumbFilters->Gamma($this->gdimg_output, $parameter);
2821
                        break;
2822
                    case 'neg': // Negative colors
2823
                        $phpthumbFilters->Negative($this->gdimg_output);
2824
                        break;
2825
                    case 'th': // Threshold
2826
                        $phpthumbFilters->Threshold($this->gdimg_output, $parameter);
2827
                        break;
2828
                    case 'rcd': // ReduceColorDepth
2829
                        if (phpthumb_functions::gd_version() < 2) {
2830
                            $this->DebugMessage('Skipping ReduceColorDepth() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2831
                            break;
2832
                        }
2833
                        @list($colors, $dither) = explode('|', $parameter, 2);
2834
                        $colors = ($colors ? (int)$colors : 256);
2835
                        $dither = ((mb_strlen($dither) > 0) ? (bool)$dither : true);
2836
                        $phpthumbFilters->ReduceColorDepth($this->gdimg_output, $colors, $dither);
2837
                        break;
2838
                    case 'flip': // Flip
2839
                        $phpthumbFilters->Flip($this->gdimg_output, false !== mb_strpos(mb_strtolower($parameter), 'x'), false !== mb_strpos(mb_strtolower($parameter), 'y'));
2840
                        break;
2841
                    case 'edge': // EdgeDetect
2842
                        $phpthumbFilters->EdgeDetect($this->gdimg_output);
2843
                        break;
2844
                    case 'emb': // Emboss
2845
                        $phpthumbFilters->Emboss($this->gdimg_output);
2846
                        break;
2847
                    case 'bvl': // Bevel
2848
                        @list($width, $color1, $color2) = explode('|', $parameter, 3);
2849
                        $phpthumbFilters->Bevel($this->gdimg_output, $width, $color1, $color2);
2850
                        break;
2851
                    case 'lvl': // autoLevels
2852
                        @list($band, $method, $threshold) = explode('|', $parameter, 3);
2853
                        $band      = ($band ? preg_replace('#[^RGBA\\*]#', '', mb_strtoupper($band)) : '*');
2854
                        $method    = ((mb_strlen($method) > 0) ? (int)$method : 2);
2855
                        $threshold = ((mb_strlen($threshold) > 0) ? (float)$threshold : 0.1);
2856
2857
                        $phpthumbFilters->HistogramStretch($this->gdimg_output, $band, $method, $threshold);
2858
                        break;
2859
                    case 'wb': // WhiteBalance
2860
                        $phpthumbFilters->WhiteBalance($this->gdimg_output, $parameter);
2861
                        break;
2862
                    case 'hist': // Histogram overlay
2863
                        if (phpthumb_functions::gd_version() < 2) {
2864
                            $this->DebugMessage('Skipping HistogramOverlay() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2865
                            break;
2866
                        }
2867
                        @list($bands, $colors, $width, $height, $alignment, $opacity, $margin_x, $margin_y) = explode('|', $parameter, 8);
2868
                        $bands     = ($bands ?: '*');
2869
                        $colors    = ($colors ?: '');
2870
                        $width     = ($width ?: 0.25);
2871
                        $height    = ($height ?: 0.25);
2872
                        $alignment = ($alignment ?: 'BR');
2873
                        $opacity   = ($opacity ?: 50);
2874
                        $margin_x  = ($margin_x ?: 5);
2875
                        // $margin_y -- it wasn't forgotten, let the value always pass unchanged
2876
                        $phpthumbFilters->HistogramOverlay($this->gdimg_output, $bands, $colors, $width, $height, $alignment, $opacity, $margin_x, $margin_y);
2877
                        break;
2878
                    case 'fram': // Frame
2879
                        @list($frame_width, $edge_width, $color_frame, $color1, $color2) = explode('|', $parameter, 5);
2880
                        $phpthumbFilters->Frame($this->gdimg_output, $frame_width, $edge_width, $color_frame, $color1, $color2);
2881
                        break;
2882
                    case 'drop': // DropShadow
2883
                        if (phpthumb_functions::gd_version() < 2) {
2884
                            $this->DebugMessage('Skipping DropShadow() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2885
2886
                            return false;
2887
                        }
2888
                        $this->is_alpha = true;
2889
                        @list($distance, $width, $color, $angle, $fade) = explode('|', $parameter, 5);
2890
                        $phpthumbFilters->DropShadow($this->gdimg_output, $distance, $width, $color, $angle, $fade);
2891
                        break;
2892
                    case 'mask': // Mask cropping
2893
                        if (phpthumb_functions::gd_version() < 2) {
2894
                            $this->DebugMessage('Skipping Mask() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2895
2896
                            return false;
2897
                        }
2898
                        @list($mask_filename, $invert) = explode('|', $parameter, 2);
2899
                        $mask_filename = $this->ResolveFilenameToAbsolute($mask_filename);
2900
                        if (@is_readable($mask_filename) && ($fp_mask = @fopen($mask_filename, 'rb'))) {
0 ignored issues
show
Bug introduced by
It seems like $mask_filename can also be of type false and null; however, parameter $filename of is_readable() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2900
                        if (@is_readable(/** @scrutinizer ignore-type */ $mask_filename) && ($fp_mask = @fopen($mask_filename, 'rb'))) {
Loading history...
Bug introduced by
It seems like $mask_filename can also be of type false and null; however, parameter $filename of fopen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2900
                        if (@is_readable($mask_filename) && ($fp_mask = @fopen(/** @scrutinizer ignore-type */ $mask_filename, 'rb'))) {
Loading history...
2901
                            $MaskImageData = '';
2902
                            do {
2903
                                $buffer        = fread($fp_mask, 8192);
2904
                                $MaskImageData .= $buffer;
2905
                            } while (mb_strlen($buffer) > 0);
2906
                            fclose($fp_mask);
2907
                            if ($gdimg_mask = $this->ImageCreateFromStringReplacement($MaskImageData)) {
2908
                                if ($invert
2909
                                    && phpthumb_functions::version_compare_replacement(PHP_VERSION, '5.0.0', '>=')
2910
                                    && phpthumb_functions::gd_is_bundled()) {
2911
                                    imagefilter($gdimg_mask, IMG_FILTER_NEGATE);
2912
                                }
2913
                                $this->is_alpha = true;
2914
                                $phpthumbFilters->ApplyMask($gdimg_mask, $this->gdimg_output);
2915
                                imagedestroy($gdimg_mask);
2916
                            } else {
2917
                                $this->DebugMessage('ImageCreateFromStringReplacement() failed for "' . $mask_filename . '"', __FILE__, __LINE__);
2918
                            }
2919
                        } else {
2920
                            $this->DebugMessage('Cannot open mask file "' . $mask_filename . '"', __FILE__, __LINE__);
2921
                        }
2922
                        break;
2923
                    case 'elip': // Ellipse cropping
2924
                        if (phpthumb_functions::gd_version() < 2) {
2925
                            $this->DebugMessage('Skipping Ellipse() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2926
2927
                            return false;
2928
                        }
2929
                        $this->is_alpha = true;
2930
                        $phpthumbFilters->Ellipse($this->gdimg_output);
2931
                        break;
2932
                    case 'ric': // RoundedImageCorners
2933
                        if (phpthumb_functions::gd_version() < 2) {
2934
                            $this->DebugMessage('Skipping RoundedImageCorners() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
2935
2936
                            return false;
2937
                        }
2938
                        @list($radius_x, $radius_y) = explode('|', $parameter, 2);
2939
                        if (($radius_x < 1) || ($radius_y < 1)) {
2940
                            $this->DebugMessage('Skipping RoundedImageCorners(' . $radius_x . ', ' . $radius_y . ') because x/y radius is less than 1', __FILE__, __LINE__);
2941
                            break;
2942
                        }
2943
                        $this->is_alpha = true;
2944
                        $phpthumbFilters->RoundedImageCorners($this->gdimg_output, $radius_x, $radius_y);
2945
                        break;
2946
                    case 'crop': // Crop
2947
                        @list($left, $right, $top, $bottom) = explode('|', $parameter, 4);
2948
                        $phpthumbFilters->Crop($this->gdimg_output, $left, $right, $top, $bottom);
2949
                        break;
2950
                    case 'bord': // Border
2951
                        @list($border_width, $radius_x, $radius_y, $hexcolor_border) = explode('|', $parameter, 4);
2952
                        $this->is_alpha = true;
2953
                        $phpthumbFilters->ImageBorder($this->gdimg_output, $border_width, $radius_x, $radius_y, $hexcolor_border);
2954
                        break;
2955
                    case 'over': // Overlay
2956
                        @list($filename, $underlay, $margin, $opacity) = explode('|', $parameter, 4);
2957
                        $underlay = ($underlay ?: false);
2958
                        $margin   = ((mb_strlen($margin) > 0) ? $margin : ($underlay ? 0.1 : 0.0));
2959
                        $opacity  = ((mb_strlen($opacity) > 0) ? $opacity : 100);
2960
                        if (($margin > 0) && ($margin < 1)) {
2961
                            $margin = min(0.499, $margin);
2962
                        } elseif (($margin > -1) && ($margin < 0)) {
2963
                            $margin = max(-0.499, $margin);
2964
                        }
2965
2966
                        $filename = $this->ResolveFilenameToAbsolute($filename);
2967
                        if (@is_readable($filename) && ($fp_watermark = @fopen($filename, 'rb'))) {
2968
                            $WatermarkImageData = '';
2969
                            do {
2970
                                $buffer             = fread($fp_watermark, 8192);
2971
                                $WatermarkImageData .= $buffer;
2972
                            } while (mb_strlen($buffer) > 0);
2973
                            fclose($fp_watermark);
2974
                            if ($img_watermark = $this->ImageCreateFromStringReplacement($WatermarkImageData)) {
2975
                                if (($margin > 0) && ($margin < 1)) {
2976
                                    $resized_x = max(1, imagesx($this->gdimg_output) - round(2 * (imagesx($this->gdimg_output) * $margin)));
2977
                                    $resized_y = max(1, imagesy($this->gdimg_output) - round(2 * (imagesy($this->gdimg_output) * $margin)));
2978
                                } else {
2979
                                    $resized_x = max(1, imagesx($this->gdimg_output) - round(2 * $margin));
2980
                                    $resized_y = max(1, imagesy($this->gdimg_output) - round(2 * $margin));
2981
                                }
2982
2983
                                if ($underlay) {
2984
                                    if ($img_watermark_resized = phpthumb_functions::ImageCreateFunction(imagesx($this->gdimg_output), imagesy($this->gdimg_output))) {
2985
                                        imagealphablending($img_watermark_resized, false);
2986
                                        imagesavealpha($img_watermark_resized, true);
2987
                                        $this->ImageResizeFunction($img_watermark_resized, $img_watermark, 0, 0, 0, 0, imagesx($img_watermark_resized), imagesy($img_watermark_resized), imagesx($img_watermark), imagesy($img_watermark));
2988
                                        if ($img_source_resized = phpthumb_functions::ImageCreateFunction($resized_x, $resized_y)) {
2989
                                            imagealphablending($img_source_resized, false);
2990
                                            imagesavealpha($img_source_resized, true);
2991
                                            $this->ImageResizeFunction($img_source_resized, $this->gdimg_output, 0, 0, 0, 0, imagesx($img_source_resized), imagesy($img_source_resized), imagesx($this->gdimg_output), imagesy($this->gdimg_output));
2992
                                            $phpthumbFilters->WatermarkOverlay($img_watermark_resized, $img_source_resized, 'C', $opacity, $margin);
2993
                                            imagecopy($this->gdimg_output, $img_watermark_resized, 0, 0, 0, 0, imagesx($this->gdimg_output), imagesy($this->gdimg_output));
2994
                                        } else {
2995
                                            $this->DebugMessage('phpthumb_functions::ImageCreateFunction(' . $resized_x . ', ' . $resized_y . ')', __FILE__, __LINE__);
2996
                                        }
2997
                                        imagedestroy($img_watermark_resized);
2998
                                    } else {
2999
                                        $this->DebugMessage('phpthumb_functions::ImageCreateFunction(' . imagesx($this->gdimg_output) . ', ' . imagesy($this->gdimg_output) . ')', __FILE__, __LINE__);
3000
                                    }
3001
                                } else { // overlay
3002
                                    if ($img_watermark_resized = phpthumb_functions::ImageCreateFunction($resized_x, $resized_y)) {
3003
                                        imagealphablending($img_watermark_resized, false);
3004
                                        imagesavealpha($img_watermark_resized, true);
3005
                                        $this->ImageResizeFunction($img_watermark_resized, $img_watermark, 0, 0, 0, 0, imagesx($img_watermark_resized), imagesy($img_watermark_resized), imagesx($img_watermark), imagesy($img_watermark));
3006
                                        $phpthumbFilters->WatermarkOverlay($this->gdimg_output, $img_watermark_resized, 'C', $opacity, $margin);
3007
                                        imagedestroy($img_watermark_resized);
3008
                                    } else {
3009
                                        $this->DebugMessage('phpthumb_functions::ImageCreateFunction(' . $resized_x . ', ' . $resized_y . ')', __FILE__, __LINE__);
3010
                                    }
3011
                                }
3012
                                imagedestroy($img_watermark);
3013
                            } else {
3014
                                $this->DebugMessage('ImageCreateFromStringReplacement() failed for "' . $filename . '"', __FILE__, __LINE__);
3015
                            }
3016
                        } else {
3017
                            $this->DebugMessage('Cannot open overlay file "' . $filename . '"', __FILE__, __LINE__);
3018
                        }
3019
                        break;
3020
                    case 'wmi': // WaterMarkImage
3021
                        @list($filename, $alignment, $opacity, $margin['x'], $margin['y'], $rotate_angle) = explode('|', $parameter, 6);
3022
                        // $margin can be pixel margin or percent margin if $alignment is text, or max width/height if $alignment is position like "50x75"
3023
                        $alignment    = ($alignment ?: 'BR');
3024
                        $opacity      = (mb_strlen($opacity) ? (int)$opacity : 50);
3025
                        $rotate_angle = (mb_strlen($rotate_angle) ? (int)$rotate_angle : 0);
3026
                        if (!preg_match('#^([0-9\\.\\-]*)x([0-9\\.\\-]*)$#i', $alignment, $matches)) {
3027
                            $margins = ['x', 'y'];
3028
                            foreach ($margins as $xy) {
3029
                                $margin[$xy] = (mb_strlen($margin[$xy]) ? $margin[$xy] : 5);
3030
                                if (($margin[$xy] > 0) && ($margin[$xy] < 1)) {
3031
                                    $margin[$xy] = min(0.499, $margin[$xy]);
3032
                                } elseif (($margin[$xy] > -1) && ($margin[$xy] < 0)) {
3033
                                    $margin[$xy] = max(-0.499, $margin[$xy]);
3034
                                }
3035
                            }
3036
                        }
3037
3038
                        $filename = $this->ResolveFilenameToAbsolute($filename);
3039
                        if (@is_readable($filename)) {
3040
                            if ($img_watermark = $this->ImageCreateFromFilename($filename)) {
3041
                                if (0 !== $rotate_angle) {
3042
                                    $phpthumbFilters->ImprovedImageRotate($img_watermark, $rotate_angle, 'FFFFFF', null, $this);
3043
                                }
3044
                                if (preg_match('#^([0-9\\.\\-]*)x([0-9\\.\\-]*)$#i', $alignment, $matches)) {
3045
                                    $watermark_max_width  = ($margin['x'] ?: imagesx($img_watermark));
3046
                                    $watermark_max_height = ($margin['y'] ?: imagesy($img_watermark));
3047
                                    $scale                = phpthumb_functions::ScaleToFitInBox(imagesx($img_watermark), imagesy($img_watermark), $watermark_max_width, $watermark_max_height, true, true);
3048
                                    $this->DebugMessage('Scaling watermark by a factor of ' . number_format($scale, 4), __FILE__, __LINE__);
3049
                                    if (($scale > 1) || ($scale < 1)) {
3050
                                        if ($img_watermark2 = phpthumb_functions::ImageCreateFunction($scale * imagesx($img_watermark), $scale * imagesy($img_watermark))) {
3051
                                            imagealphablending($img_watermark2, false);
3052
                                            imagesavealpha($img_watermark2, true);
3053
                                            $this->ImageResizeFunction($img_watermark2, $img_watermark, 0, 0, 0, 0, imagesx($img_watermark2), imagesy($img_watermark2), imagesx($img_watermark), imagesy($img_watermark));
3054
                                            $img_watermark = $img_watermark2;
3055
                                        } else {
3056
                                            $this->DebugMessage('ImageCreateFunction(' . ($scale * imagesx($img_watermark)) . ', ' . ($scale * imagesx($img_watermark)) . ') failed', __FILE__, __LINE__);
3057
                                        }
3058
                                    }
3059
                                    $watermark_dest_x = round($matches[1] - (imagesx($img_watermark) / 2));
3060
                                    $watermark_dest_y = round($matches[2] - (imagesy($img_watermark) / 2));
3061
                                    $alignment        = $watermark_dest_x . 'x' . $watermark_dest_y;
3062
                                }
3063
                                $phpthumbFilters->WatermarkOverlay($this->gdimg_output, $img_watermark, $alignment, $opacity, $margin['x'], $margin['y']);
3064
                                imagedestroy($img_watermark);
3065
                                if (isset($img_watermark2) && is_resource($img_watermark2)) {
3066
                                    imagedestroy($img_watermark2);
3067
                                }
3068
                            } else {
3069
                                $this->DebugMessage('ImageCreateFromFilename() failed for "' . $filename . '"', __FILE__, __LINE__);
3070
                            }
3071
                        } else {
3072
                            $this->DebugMessage('!is_readable(' . $filename . ')', __FILE__, __LINE__);
3073
                        }
3074
                        break;
3075
                    case 'wmt': // WaterMarkText
3076
                        @list($text, $size, $alignment, $hex_color, $ttffont, $opacity, $margin, $angle, $bg_color, $bg_opacity, $fillextend) = explode('|', $parameter, 11);
3077
                        $text       = ($text ?: '');
3078
                        $size       = ($size ?: 3);
3079
                        $alignment  = ($alignment ?: 'BR');
3080
                        $hex_color  = ($hex_color ?: '000000');
3081
                        $ttffont    = ($ttffont ?: '');
3082
                        $opacity    = (mb_strlen($opacity) ? $opacity : 50);
3083
                        $margin     = (mb_strlen($margin) ? $margin : 5);
3084
                        $angle      = (mb_strlen($angle) ? $angle : 0);
3085
                        $bg_color   = ($bg_color ?: false);
3086
                        $bg_opacity = ($bg_opacity ?: 0);
3087
                        $fillextend = ($fillextend ?: '');
3088
3089
                        if (basename($ttffont) == $ttffont) {
3090
                            $ttffont = $this->realPathSafe($this->config_ttf_directory . DIRECTORY_SEPARATOR . $ttffont);
3091
                        } else {
3092
                            $ttffont = $this->ResolveFilenameToAbsolute($ttffont);
3093
                        }
3094
                        $phpthumbFilters->WatermarkText($this->gdimg_output, $text, $size, $alignment, $hex_color, $ttffont, $opacity, $margin, $angle, $bg_color, $bg_opacity, $fillextend);
3095
                        break;
3096
                    case 'blur': // Blur
3097
                        @list($radius) = explode('|', $parameter, 1);
3098
                        $radius = ($radius ?: 1);
3099
                        if (phpthumb_functions::gd_version() >= 2) {
3100
                            $phpthumbFilters->Blur($this->gdimg_output, $radius);
3101
                        } else {
3102
                            $this->DebugMessage('Skipping Blur() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
3103
                        }
3104
                        break;
3105
                    case 'gblr': // Gaussian Blur
3106
                        $phpthumbFilters->BlurGaussian($this->gdimg_output);
3107
                        break;
3108
                    case 'sblr': // Selective Blur
3109
                        $phpthumbFilters->BlurSelective($this->gdimg_output);
3110
                        break;
3111
                    case 'mean': // MeanRemoval blur
3112
                        $phpthumbFilters->MeanRemoval($this->gdimg_output);
3113
                        break;
3114
                    case 'smth': // Smooth blur
3115
                        $phpthumbFilters->Smooth($this->gdimg_output, $parameter);
3116
                        break;
3117
                    case 'usm': // UnSharpMask sharpening
3118
                        @list($amount, $radius, $threshold) = explode('|', $parameter, 3);
3119
                        $amount    = ($amount ?: 80);
3120
                        $radius    = ($radius ?: 0.5);
3121
                        $threshold = (mb_strlen($threshold) ? $threshold : 3);
3122
                        if (phpthumb_functions::gd_version() >= 2.0) {
3123
                            ob_start();
3124
                            if (!@require_once __DIR__ . '/phpthumb.unsharp.php') {
3125
                                $include_error = ob_get_contents();
3126
                                if ($include_error) {
3127
                                    $this->DebugMessage('include_once("' . __DIR__ . '/phpthumb.unsharp.php") generated message: "' . $include_error . '"', __FILE__, __LINE__);
3128
                                }
3129
                                $this->DebugMessage('Error including "' . __DIR__ . '/phpthumb.unsharp.php" which is required for unsharp masking', __FILE__, __LINE__);
3130
                                ob_end_clean();
3131
3132
                                return false;
3133
                            }
3134
                            ob_end_clean();
3135
                            phpUnsharpMask::applyUnsharpMask($this->gdimg_output, $amount, $radius, $threshold);
3136
                        } else {
3137
                            $this->DebugMessage('Skipping unsharp mask because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
3138
3139
                            return false;
3140
                        }
3141
                        break;
3142
                    case 'size': // Resize
3143
                        @list($newwidth, $newheight, $stretch) = explode('|', $parameter);
3144
                        $newwidth  = (!$newwidth ? imagesx($this->gdimg_output) : ((($newwidth > 0)
3145
                                                                                    && ($newwidth < 1)) ? round($newwidth * imagesx($this->gdimg_output)) : round($newwidth)));
0 ignored issues
show
Bug introduced by
$newwidth of type string is incompatible with the type double|integer expected by parameter $num of round(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3145
                                                                                    && ($newwidth < 1)) ? round($newwidth * imagesx($this->gdimg_output)) : round(/** @scrutinizer ignore-type */ $newwidth)));
Loading history...
3146
                        $newheight = (!$newheight ? imagesy($this->gdimg_output) : ((($newheight > 0)
3147
                                                                                     && ($newheight < 1)) ? round($newheight * imagesy($this->gdimg_output)) : round($newheight)));
3148
                        $stretch   = ($stretch ? true : false);
3149
                        if ($stretch) {
3150
                            $scale_x = phpthumb_functions::ScaleToFitInBox(imagesx($this->gdimg_output), imagesx($this->gdimg_output), $newwidth, $newwidth, true, true);
3151
                            $scale_y = phpthumb_functions::ScaleToFitInBox(imagesy($this->gdimg_output), imagesy($this->gdimg_output), $newheight, $newheight, true, true);
3152
                        } else {
3153
                            $scale_x = phpthumb_functions::ScaleToFitInBox(imagesx($this->gdimg_output), imagesy($this->gdimg_output), $newwidth, $newheight, true, true);
3154
                            $scale_y = $scale_x;
3155
                        }
3156
                        $this->DebugMessage('Scaling watermark (' . ($stretch ? 'with' : 'without') . ' stretch) by a factor of "' . number_format($scale_x, 4) . ' x ' . number_format($scale_y, 4) . '"', __FILE__, __LINE__);
3157
                        if (($scale_x > 1) || ($scale_x < 1) || ($scale_y > 1) || ($scale_y < 1)) {
3158
                            if ($img_temp = phpthumb_functions::ImageCreateFunction(imagesx($this->gdimg_output), imagesy($this->gdimg_output))) {
3159
                                imagecopy($img_temp, $this->gdimg_output, 0, 0, 0, 0, imagesx($this->gdimg_output), imagesy($this->gdimg_output));
3160
                                if ($this->gdimg_output = phpthumb_functions::ImageCreateFunction($scale_x * imagesx($img_temp), $scale_y * imagesy($img_temp))) {
3161
                                    imagealphablending($this->gdimg_output, false);
3162
                                    imagesavealpha($this->gdimg_output, true);
3163
                                    $this->ImageResizeFunction($this->gdimg_output, $img_temp, 0, 0, 0, 0, imagesx($this->gdimg_output), imagesy($this->gdimg_output), imagesx($img_temp), imagesy($img_temp));
3164
                                } else {
3165
                                    $this->DebugMessage('ImageCreateFunction(' . ($scale_x * imagesx($img_temp)) . ', ' . ($scale_y * imagesy($img_temp)) . ') failed', __FILE__, __LINE__);
3166
                                }
3167
                                imagedestroy($img_temp);
3168
                            } else {
3169
                                $this->DebugMessage('ImageCreateFunction(' . imagesx($this->gdimg_output) . ', ' . imagesy($this->gdimg_output) . ') failed', __FILE__, __LINE__);
3170
                            }
3171
                        }
3172
                        break;
3173
                    case 'rot': // ROTate
3174
                        @list($angle, $bgcolor) = explode('|', $parameter, 2);
3175
                        $phpthumbFilters->ImprovedImageRotate($this->gdimg_output, $angle, $bgcolor, null, $this);
3176
                        break;
3177
                    case 'stc': // Source Transparent Color
3178
                        @list($hexcolor, $min_limit, $max_limit) = explode('|', $parameter, 3);
3179
                        if (!phpthumb_functions::IsHexColor($hexcolor)) {
3180
                            $this->DebugMessage('Skipping SourceTransparentColor hex color is invalid (' . $hexcolor . ')', __FILE__, __LINE__);
3181
3182
                            return false;
3183
                        }
3184
                        $min_limit = (mb_strlen($min_limit) ? $min_limit : 5);
3185
                        $max_limit = (mb_strlen($max_limit) ? $max_limit : 10);
3186
                        if ($gdimg_mask = $phpthumbFilters->SourceTransparentColorMask($this->gdimg_output, $hexcolor, $min_limit, $max_limit)) {
3187
                            $this->is_alpha = true;
3188
                            $phpthumbFilters->ApplyMask($gdimg_mask, $this->gdimg_output);
3189
                            imagedestroy($gdimg_mask);
3190
                        } else {
3191
                            $this->DebugMessage('SourceTransparentColorMask() failed for "' . $hexcolor . ',' . $min_limit . ',' . $max_limit . '"', __FILE__, __LINE__);
3192
                        }
3193
                        break;
3194
                }
3195
                $this->DebugMessage('Finished processing filter command "' . $command . '(' . $parameter . ')"', __FILE__, __LINE__);
3196
            }
3197
        }
3198
3199
        return true;
3200
    }
3201
3202
    /**
3203
     * @return bool
3204
     */
3205
    public function MaxFileSize()
3206
    {
3207
        if (phpthumb_functions::gd_version() < 2) {
3208
            $this->DebugMessage('Skipping MaxFileSize() because gd_version is "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
3209
3210
            return false;
3211
        }
3212
        if ($this->maxb > 0) {
3213
            switch ($this->thumbnailFormat) {
3214
                case 'png':
3215
                case 'gif':
3216
                    $imgRenderFunction = 'image' . $this->thumbnailFormat;
3217
3218
                    ob_start();
3219
                    $imgRenderFunction($this->gdimg_output);
3220
                    $imgdata = ob_get_contents();
3221
                    ob_end_clean();
3222
3223
                    if (mb_strlen($imgdata) > $this->maxb) {
3224
                        for ($i = 8; $i >= 1; $i--) {
3225
                            $tempIMG = imagecreatetruecolor(imagesx($this->gdimg_output), imagesy($this->gdimg_output));
3226
                            imagecopy($tempIMG, $this->gdimg_output, 0, 0, 0, 0, imagesx($this->gdimg_output), imagesy($this->gdimg_output));
3227
                            imagetruecolortopalette($tempIMG, true, 2 ** $i);
3228
                            ob_start();
3229
                            $imgRenderFunction($tempIMG);
3230
                            $imgdata = ob_get_contents();
3231
                            ob_end_clean();
3232
3233
                            if (mb_strlen($imgdata) <= $this->maxb) {
3234
                                imagetruecolortopalette($this->gdimg_output, true, 2 ** $i);
3235
                                break;
3236
                            }
3237
                        }
3238
                    }
3239
                    break;
3240
                case 'jpeg':
3241
                    ob_start();
3242
                    imagejpeg($this->gdimg_output);
3243
                    $imgdata = ob_get_contents();
3244
                    ob_end_clean();
3245
3246
                    if (mb_strlen($imgdata) > $this->maxb) {
3247
                        for ($i = 3; $i < 20; ++$i) {
3248
                            $q = round(100 * (1 - log10($i / 2)));
3249
                            ob_start();
3250
                            imagejpeg($this->gdimg_output, null, $q);
0 ignored issues
show
Bug introduced by
$q of type double is incompatible with the type integer expected by parameter $quality of imagejpeg(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3250
                            imagejpeg($this->gdimg_output, null, /** @scrutinizer ignore-type */ $q);
Loading history...
3251
                            $imgdata = ob_get_contents();
3252
                            ob_end_clean();
3253
3254
                            $this->thumbnailQuality = $q;
3255
                            if (mb_strlen($imgdata) <= $this->maxb) {
3256
                                break;
3257
                            }
3258
                        }
3259
                    }
3260
                    if (mb_strlen($imgdata) > $this->maxb) {
3261
                        return false;
3262
                    }
3263
                    break;
3264
                default:
3265
                    return false;
3266
            }
3267
        }
3268
3269
        return true;
3270
    }
3271
3272
    /**
3273
     * @return bool
3274
     */
3275
    public function CalculateThumbnailDimensions()
3276
    {
3277
        $this->DebugMessage('CalculateThumbnailDimensions() starting with [W,H,sx,sy,sw,sh] initially set to [' . $this->source_width . ',' . $this->source_height . ',' . $this->sx . ',' . $this->sy . ',' . $this->sw . ',' . $this->sh . ']', __FILE__, __LINE__);
3278
        //echo $this->source_width.'x'.$this->source_height.'<hr>';
3279
        $this->thumbnailCropX = ($this->sx ? (($this->sx >= 2) ? $this->sx : round($this->sx * $this->source_width)) : 0);
3280
        //echo $this->thumbnailCropX.'<br>';
3281
        $this->thumbnailCropY = ($this->sy ? (($this->sy >= 2) ? $this->sy : round($this->sy * $this->source_height)) : 0);
3282
        //echo $this->thumbnailCropY.'<br>';
3283
        $this->thumbnailCropW = ($this->sw ? (($this->sw >= 2) ? $this->sw : round($this->sw * $this->source_width)) : $this->source_width);
3284
        //echo $this->thumbnailCropW.'<br>';
3285
        $this->thumbnailCropH = ($this->sh ? (($this->sh >= 2) ? $this->sh : round($this->sh * $this->source_height)) : $this->source_height);
3286
        //echo $this->thumbnailCropH.'<hr>';
3287
3288
        // limit source area to original image area
3289
        $this->thumbnailCropW = max(1, min($this->thumbnailCropW, $this->source_width - $this->thumbnailCropX));
3290
        $this->thumbnailCropH = max(1, min($this->thumbnailCropH, $this->source_height - $this->thumbnailCropY));
3291
3292
        $this->DebugMessage('CalculateThumbnailDimensions() starting with [x,y,w,h] initially set to [' . $this->thumbnailCropX . ',' . $this->thumbnailCropY . ',' . $this->thumbnailCropW . ',' . $this->thumbnailCropH . ']', __FILE__, __LINE__);
3293
3294
        if ($this->zc && $this->w && $this->h) {
3295
            // Zoom Crop
3296
            // retain proportional resizing we did above, but crop off larger dimension so smaller
3297
            // dimension fully fits available space
3298
3299
            $scaling_X = $this->source_width / $this->w;
3300
            $scaling_Y = $this->source_height / $this->h;
3301
            if ($scaling_X > $scaling_Y) {
3302
                // some of the width will need to be cropped
3303
                $allowable_width      = $this->source_width / $scaling_X * $scaling_Y;
3304
                $this->thumbnailCropW = round($allowable_width);
3305
                $this->thumbnailCropX = round(($this->source_width - $allowable_width) / 2);
3306
            } elseif ($scaling_Y > $scaling_X) {
3307
                // some of the height will need to be cropped
3308
                $allowable_height     = $this->source_height / $scaling_Y * $scaling_X;
3309
                $this->thumbnailCropH = round($allowable_height);
3310
                $this->thumbnailCropY = round(($this->source_height - $allowable_height) / 2);
3311
            }
3312
            // image fits perfectly, no cropping needed
3313
3314
            $this->thumbnail_width        = $this->w;
3315
            $this->thumbnail_height       = $this->h;
3316
            $this->thumbnail_image_width  = $this->thumbnail_width;
3317
            $this->thumbnail_image_height = $this->thumbnail_height;
3318
        } elseif ($this->iar && $this->w && $this->h) {
3319
            // Ignore Aspect Ratio
3320
            // stretch image to fit exactly 'w' x 'h'
3321
            $this->thumbnail_width        = $this->w;
3322
            $this->thumbnail_height       = $this->h;
3323
            $this->thumbnail_image_width  = $this->thumbnail_width;
3324
            $this->thumbnail_image_height = $this->thumbnail_height;
3325
        } else {
3326
            $original_aspect_ratio = $this->thumbnailCropW / $this->thumbnailCropH;
3327
            if ($this->aoe) {
3328
                if ($this->w && $this->h) {
3329
                    $maxwidth  = min($this->w, $this->h * $original_aspect_ratio);
3330
                    $maxheight = min($this->h, $this->w / $original_aspect_ratio);
3331
                } elseif ($this->w) {
3332
                    $maxwidth  = $this->w;
3333
                    $maxheight = $this->w / $original_aspect_ratio;
3334
                } elseif ($this->h) {
3335
                    $maxwidth  = $this->h * $original_aspect_ratio;
3336
                    $maxheight = $this->h;
3337
                } else {
3338
                    $maxwidth  = $this->thumbnailCropW;
3339
                    $maxheight = $this->thumbnailCropH;
3340
                }
3341
            } else {
3342
                $maxwidth  = phpthumb_functions::nonempty_min($this->w, $this->thumbnailCropW, $this->config_output_maxwidth);
3343
                $maxheight = phpthumb_functions::nonempty_min($this->h, $this->thumbnailCropH, $this->config_output_maxheight);
3344
                //echo $maxwidth.'x'.$maxheight.'<br>';
3345
                $maxwidth  = min($maxwidth, $maxheight * $original_aspect_ratio);
3346
                $maxheight = min($maxheight, $maxwidth / $original_aspect_ratio);
3347
                //echo $maxwidth.'x'.$maxheight.'<hr>';
3348
            }
3349
3350
            $this->thumbnail_image_width  = $maxwidth;
3351
            $this->thumbnail_image_height = $maxheight;
3352
            $this->thumbnail_width        = $maxwidth;
3353
            $this->thumbnail_height       = $maxheight;
3354
3355
            $this->FixedAspectRatio();
3356
        }
3357
3358
        $this->thumbnail_width  = max(1, floor($this->thumbnail_width));
3359
        $this->thumbnail_height = max(1, floor($this->thumbnail_height));
3360
3361
        return true;
3362
    }
3363
3364
    /**
3365
     * @return bool
3366
     */
3367
    public function CreateGDoutput()
3368
    {
3369
        $this->CalculateThumbnailDimensions();
3370
3371
        // create the GD image (either true-color or 256-color, depending on GD version)
3372
        $this->gdimg_output = phpthumb_functions::ImageCreateFunction($this->thumbnail_width, $this->thumbnail_height);
3373
3374
        // images that have transparency must have the background filled with the configured 'bg' color otherwise the transparent color will appear as black
3375
        imagesavealpha($this->gdimg_output, true);
3376
        if ($this->is_alpha && phpthumb_functions::gd_version() >= 2) {
3377
            imagealphablending($this->gdimg_output, false);
3378
            $output_full_alpha = phpthumb_functions::ImageColorAllocateAlphaSafe($this->gdimg_output, 255, 255, 255, 127);
3379
            imagefilledrectangle($this->gdimg_output, 0, 0, $this->thumbnail_width, $this->thumbnail_height, $output_full_alpha);
3380
        } else {
3381
            $current_transparent_color = imagecolortransparent($this->gdimg_source);
3382
            if ($this->bg || (@$current_transparent_color >= 0)) {
3383
                $this->config_background_hexcolor = ($this->bg ?: $this->config_background_hexcolor);
3384
                if (!phpthumb_functions::IsHexColor($this->config_background_hexcolor)) {
3385
                    return $this->ErrorImage('Invalid hex color string "' . $this->config_background_hexcolor . '" for parameter "bg"');
3386
                }
3387
                $background_color = phpthumb_functions::ImageHexColorAllocate($this->gdimg_output, $this->config_background_hexcolor);
3388
                imagefilledrectangle($this->gdimg_output, 0, 0, $this->thumbnail_width, $this->thumbnail_height, $background_color);
3389
            }
3390
        }
3391
        $this->DebugMessage('CreateGDoutput() returning canvas "' . $this->thumbnail_width . 'x' . $this->thumbnail_height . '"', __FILE__, __LINE__);
3392
3393
        return true;
3394
    }
3395
3396
    /**
3397
     * @return bool
3398
     */
3399
    public function SetOrientationDependantWidthHeight()
3400
    {
3401
        $this->DebugMessage('SetOrientationDependantWidthHeight() starting with "' . $this->source_width . '"x"' . $this->source_height . '"', __FILE__, __LINE__);
3402
        if ($this->source_height > $this->source_width) {
3403
            // portrait
3404
            $this->w = phpthumb_functions::OneOfThese($this->wp, $this->w, $this->ws, $this->wl);
3405
            $this->h = phpthumb_functions::OneOfThese($this->hp, $this->h, $this->hs, $this->hl);
3406
        } elseif ($this->source_height < $this->source_width) {
3407
            // landscape
3408
            $this->w = phpthumb_functions::OneOfThese($this->wl, $this->w, $this->ws, $this->wp);
3409
            $this->h = phpthumb_functions::OneOfThese($this->hl, $this->h, $this->hs, $this->hp);
3410
        } else {
3411
            // square
3412
            $this->w = phpthumb_functions::OneOfThese($this->ws, $this->w, $this->wl, $this->wp);
3413
            $this->h = phpthumb_functions::OneOfThese($this->hs, $this->h, $this->hl, $this->hp);
3414
        }
3415
        //$this->w = round($this->w ? $this->w : (($this->h && $this->source_height) ? $this->h * $this->source_width  / $this->source_height : $this->w));
3416
        //$this->h = round($this->h ? $this->h : (($this->w && $this->source_width)  ? $this->w * $this->source_height / $this->source_width  : $this->h));
3417
        $this->DebugMessage('SetOrientationDependantWidthHeight() setting w="' . (int)$this->w . '", h="' . (int)$this->h . '"', __FILE__, __LINE__);
3418
3419
        return true;
3420
    }
3421
3422
    /**
3423
     * @return bool
3424
     */
3425
    public function ExtractEXIFgetImageSize()
3426
    {
3427
        $this->DebugMessage('starting ExtractEXIFgetImageSize()', __FILE__, __LINE__);
3428
3429
        if (preg_match('#^http:#i', $this->src) && !$this->sourceFilename && $this->rawImageData) {
3430
            $this->SourceDataToTempFile();
3431
        }
3432
        if (null === $this->getimagesizeinfo) {
3433
            if ($this->sourceFilename) {
3434
                $this->getimagesizeinfo = @getimagesize($this->sourceFilename);
3435
                $this->source_width     = $this->getimagesizeinfo[0];
3436
                $this->source_height    = $this->getimagesizeinfo[1];
3437
                $this->DebugMessage('getimagesize(' . $this->sourceFilename . ') says image is ' . $this->source_width . 'x' . $this->source_height, __FILE__, __LINE__);
3438
            } else {
3439
                $this->DebugMessage('skipping getimagesize() because $this->sourceFilename is empty', __FILE__, __LINE__);
3440
            }
3441
        } else {
3442
            $this->DebugMessage('skipping getimagesize() because !is_null($this->getimagesizeinfo)', __FILE__, __LINE__);
3443
        }
3444
3445
        if (is_resource($this->gdimg_source)) {
3446
            $this->source_width  = imagesx($this->gdimg_source);
3447
            $this->source_height = imagesy($this->gdimg_source);
3448
3449
            $this->SetOrientationDependantWidthHeight();
3450
        } elseif ($this->rawImageData && !$this->sourceFilename) {
3451
            if ($this->SourceImageIsTooLarge($this->source_width, $this->source_height)) {
3452
                $this->DebugMessage('NOT bypassing EXIF and getimagesize sections because source image is too large for GD (' . $this->source_width . 'x' . $this->source_width . '=' . ($this->source_width * $this->source_height * 5) . 'MB)', __FILE__, __LINE__);
3453
            } else {
3454
                $this->DebugMessage(
3455
                    'bypassing EXIF and getimagesize sections because $this->rawImageData is set, and $this->sourceFilename is not set, and source image is not too large for GD (' . $this->source_width . 'x' . $this->source_width . '=' . ($this->source_width * $this->source_height * 5) . 'MB)',
3456
                    __FILE__,
3457
                    __LINE__
3458
                );
3459
            }
3460
        }
3461
3462
        if (null === $this->getimagesizeinfo) {
3463
            $this->getimagesizeinfo = @getimagesize($this->sourceFilename);
3464
        }
3465
3466
        if (!empty($this->getimagesizeinfo)) {
3467
            // great
3468
            $this->getimagesizeinfo['filesize'] = @filesize($this->sourceFilename);
3469
        } elseif (!$this->rawImageData) {
3470
            $this->DebugMessage('getimagesize("' . $this->sourceFilename . '") failed', __FILE__, __LINE__);
3471
        }
3472
3473
        if ($this->config_prefer_imagemagick) {
3474
            if ($this->ImageMagickThumbnailToGD()) {
3475
                return true;
3476
            }
3477
            $this->DebugMessage('ImageMagickThumbnailToGD() failed', __FILE__, __LINE__);
3478
        }
3479
3480
        $this->source_width  = $this->getimagesizeinfo[0];
3481
        $this->source_height = $this->getimagesizeinfo[1];
3482
3483
        $this->SetOrientationDependantWidthHeight();
3484
3485
        if (phpthumb_functions::version_compare_replacement(PHP_VERSION, '4.2.0', '>=')
3486
            && function_exists('exif_read_data')) {
3487
            switch ($this->getimagesizeinfo[2]) {
3488
                case IMAGETYPE_JPEG:
3489
                case IMAGETYPE_TIFF_II:
3490
                case IMAGETYPE_TIFF_MM:
3491
                    $this->exif_raw_data = @exif_read_data($this->sourceFilename, 0, true);
3492
                    break;
3493
            }
3494
        }
3495
        if (function_exists('exif_thumbnail') && (IMAGETYPE_JPEG == $this->getimagesizeinfo[2])) {
3496
            // Extract EXIF info from JPEGs
3497
3498
            $this->exif_thumbnail_width  = '';
3499
            $this->exif_thumbnail_height = '';
3500
            $this->exif_thumbnail_type   = '';
3501
3502
            // The parameters width, height and imagetype are available since PHP v4.3.0
3503
            if (phpthumb_functions::version_compare_replacement(PHP_VERSION, '4.3.0', '>=')) {
3504
                $this->exif_thumbnail_data = @exif_thumbnail($this->sourceFilename, $this->exif_thumbnail_width, $this->exif_thumbnail_height, $this->exif_thumbnail_type);
0 ignored issues
show
Bug introduced by
$this->exif_thumbnail_width of type string is incompatible with the type integer expected by parameter $width of exif_thumbnail(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3504
                $this->exif_thumbnail_data = @exif_thumbnail($this->sourceFilename, /** @scrutinizer ignore-type */ $this->exif_thumbnail_width, $this->exif_thumbnail_height, $this->exif_thumbnail_type);
Loading history...
Bug introduced by
$this->exif_thumbnail_height of type string is incompatible with the type integer expected by parameter $height of exif_thumbnail(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3504
                $this->exif_thumbnail_data = @exif_thumbnail($this->sourceFilename, $this->exif_thumbnail_width, /** @scrutinizer ignore-type */ $this->exif_thumbnail_height, $this->exif_thumbnail_type);
Loading history...
Bug introduced by
$this->exif_thumbnail_type of type string is incompatible with the type integer expected by parameter $image_type of exif_thumbnail(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3504
                $this->exif_thumbnail_data = @exif_thumbnail($this->sourceFilename, $this->exif_thumbnail_width, $this->exif_thumbnail_height, /** @scrutinizer ignore-type */ $this->exif_thumbnail_type);
Loading history...
3505
            } else {
3506
                // older versions of exif_thumbnail output an error message but NOT return false on failure
3507
                ob_start();
3508
                $this->exif_thumbnail_data = exif_thumbnail($this->sourceFilename);
3509
                $exit_thumbnail_error      = ob_get_contents();
3510
                ob_end_clean();
3511
                if (!$exit_thumbnail_error && $this->exif_thumbnail_data) {
3512
                    if ($gdimg_exif_temp = $this->ImageCreateFromStringReplacement($this->exif_thumbnail_data, false)) {
3513
                        $this->exif_thumbnail_width  = imagesx($gdimg_exif_temp);
3514
                        $this->exif_thumbnail_height = imagesy($gdimg_exif_temp);
3515
                        $this->exif_thumbnail_type   = 2; // (2 == JPEG) before PHP v4.3.0 only JPEG format EXIF thumbnails are returned
3516
                        unset($gdimg_exif_temp);
3517
                    } else {
3518
                        return $this->ErrorImage('Failed - $this->ImageCreateFromStringReplacement($this->exif_thumbnail_data) in ' . __FILE__ . ' on line ' . __LINE__);
3519
                    }
3520
                }
3521
            }
3522
        } elseif (!function_exists('exif_thumbnail')) {
3523
            $this->DebugMessage('exif_thumbnail() does not exist, cannot extract EXIF thumbnail', __FILE__, __LINE__);
3524
        }
3525
3526
        $this->DebugMessage('EXIF thumbnail extraction: (size=' . mb_strlen($this->exif_thumbnail_data) . '; type="' . $this->exif_thumbnail_type . '"; ' . (int)$this->exif_thumbnail_width . 'x' . (int)$this->exif_thumbnail_height . ')', __FILE__, __LINE__);
3527
3528
        // see if EXIF thumbnail can be used directly with no processing
3529
        if ($this->config_use_exif_thumbnail_for_speed && $this->exif_thumbnail_data) {
3530
            while (true) {
3531
                if (!$this->xto) {
3532
                    $source_ar = $this->source_width / $this->source_height;
3533
                    $exif_ar   = $this->exif_thumbnail_width / $this->exif_thumbnail_height;
3534
                    if (number_format($source_ar, 2) != number_format($exif_ar, 2)) {
3535
                        $this->DebugMessage('not using EXIF thumbnail because $source_ar != $exif_ar (' . $source_ar . ' != ' . $exif_ar . ')', __FILE__, __LINE__);
3536
                        break;
3537
                    }
3538
                    if ($this->w && ($this->w != $this->exif_thumbnail_width)) {
3539
                        $this->DebugMessage('not using EXIF thumbnail because $this->w != $this->exif_thumbnail_width (' . $this->w . ' != ' . $this->exif_thumbnail_width . ')', __FILE__, __LINE__);
3540
                        break;
3541
                    }
3542
                    if ($this->h && ($this->h != $this->exif_thumbnail_height)) {
3543
                        $this->DebugMessage('not using EXIF thumbnail because $this->h != $this->exif_thumbnail_height (' . $this->h . ' != ' . $this->exif_thumbnail_height . ')', __FILE__, __LINE__);
3544
                        break;
3545
                    }
3546
                    $CannotBeSetParameters = ['sx', 'sy', 'sh', 'sw', 'far', 'bg', 'bc', 'fltr', 'phpThumbDebug'];
3547
                    foreach ($CannotBeSetParameters as $parameter) {
3548
                        if ($this->$parameter) {
3549
                            break 2;
3550
                        }
3551
                    }
3552
                }
3553
3554
                $this->DebugMessage('setting $this->gdimg_source = $this->ImageCreateFromStringReplacement($this->exif_thumbnail_data)', __FILE__, __LINE__);
3555
                $this->gdimg_source  = $this->ImageCreateFromStringReplacement($this->exif_thumbnail_data);
3556
                $this->source_width  = imagesx($this->gdimg_source);
3557
                $this->source_height = imagesy($this->gdimg_source);
3558
3559
                return true;
3560
            }
3561
        }
3562
3563
        if (($this->config_max_source_pixels > 0)
3564
            && (($this->source_width * $this->source_height) > $this->config_max_source_pixels)) {
3565
            // Source image is larger than would fit in available PHP memory.
3566
            // If ImageMagick is installed, use it to generate the thumbnail.
3567
            // Else, if an EXIF thumbnail is available, use that as the source image.
3568
            // Otherwise, no choice but to fail with an error message
3569
            $this->DebugMessage('image is ' . $this->source_width . 'x' . $this->source_height . ' and therefore contains more pixels (' . ($this->source_width * $this->source_height) . ') than $this->config_max_source_pixels setting (' . $this->config_max_source_pixels . ')', __FILE__, __LINE__);
3570
            if (!$this->config_prefer_imagemagick && $this->ImageMagickThumbnailToGD()) {
3571
                // excellent, we have a thumbnailed source image
3572
                return true;
3573
            }
3574
        }
3575
3576
        return true;
3577
    }
3578
3579
    /**
3580
     * @return bool
3581
     */
3582
    public function SetCacheFilename()
3583
    {
3584
        if (null !== $this->cache_filename) {
3585
            $this->DebugMessage('$this->cache_filename already set, skipping SetCacheFilename()', __FILE__, __LINE__);
3586
3587
            return true;
3588
        }
3589
        if (null === $this->config_cache_directory) {
3590
            $this->setCacheDirectory();
3591
            if (!$this->config_cache_directory) {
3592
                $this->DebugMessage('SetCacheFilename() failed because $this->config_cache_directory is empty', __FILE__, __LINE__);
3593
3594
                return false;
3595
            }
3596
        }
3597
        $this->setOutputFormat();
3598
3599
        if (!$this->sourceFilename && !$this->rawImageData && $this->src) {
3600
            $this->sourceFilename = $this->ResolveFilenameToAbsolute($this->src);
3601
        }
3602
3603
        if ($this->config_cache_default_only_suffix && $this->sourceFilename) {
3604
            // simplified cache filenames:
3605
            // only use default parameters in phpThumb.config.php
3606
            // substitute source filename into * in $this->config_cache_default_only_suffix
3607
            // (eg: '*_thumb' becomes 'picture_thumb.jpg')
3608
            if (false === mb_strpos($this->config_cache_default_only_suffix, '*')) {
0 ignored issues
show
Bug introduced by
$this->config_cache_default_only_suffix of type true is incompatible with the type string expected by parameter $haystack of mb_strpos(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3608
            if (false === mb_strpos(/** @scrutinizer ignore-type */ $this->config_cache_default_only_suffix, '*')) {
Loading history...
3609
                $this->DebugMessage('aborting simplified caching filename because no * in "' . $this->config_cache_default_only_suffix . '"', __FILE__, __LINE__);
0 ignored issues
show
Bug introduced by
Are you sure $this->config_cache_default_only_suffix of type true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3609
                $this->DebugMessage('aborting simplified caching filename because no * in "' . /** @scrutinizer ignore-type */ $this->config_cache_default_only_suffix . '"', __FILE__, __LINE__);
Loading history...
3610
            } else {
3611
                preg_match('#(.+)(\\.[a-z0-9]+)?$#i', basename($this->sourceFilename), $matches);
3612
                $this->cache_filename = $this->config_cache_directory . DIRECTORY_SEPARATOR . rawurlencode(str_replace('*', @$matches[1], $this->config_cache_default_only_suffix)) . '.' . mb_strtolower($this->thumbnailFormat);
3613
3614
                return true;
3615
            }
3616
        }
3617
3618
        $this->cache_filename = '';
3619
        if ($this->new) {
3620
            $broad_directory_name = mb_strtolower(md5($this->new));
3621
            $this->cache_filename .= '_new' . $broad_directory_name;
3622
        } elseif ($this->md5s) {
3623
            // source image MD5 hash provided
3624
            $this->DebugMessage('SetCacheFilename() _raw set from $this->md5s = "' . $this->md5s . '"', __FILE__, __LINE__);
3625
            $broad_directory_name = $this->md5s;
3626
            $this->cache_filename .= '_raw' . $this->md5s;
3627
        } elseif (!$this->src && $this->rawImageData) {
3628
            $this->DebugMessage('SetCacheFilename() _raw set from md5($this->rawImageData) = "' . md5($this->rawImageData) . '"', __FILE__, __LINE__);
3629
            $broad_directory_name = mb_strtolower(md5($this->rawImageData));
3630
            $this->cache_filename .= '_raw' . $broad_directory_name;
3631
        } else {
3632
            $this->DebugMessage('SetCacheFilename() _src set from md5($this->sourceFilename) "' . $this->sourceFilename . '" = "' . md5($this->sourceFilename) . '"', __FILE__, __LINE__);
3633
            $broad_directory_name = mb_strtolower(md5($this->sourceFilename));
3634
            $this->cache_filename .= '_src' . $broad_directory_name;
3635
        }
3636
        if (!empty(\Xmf\Request::getString('HTTP_REFERER', '', 'SERVER')) && $this->config_nooffsitelink_enabled) {
3637
            $parsed_url1 = @phpthumb_functions::ParseURLbetter(@\Xmf\Request::getString('HTTP_REFERER', '', 'SERVER'));
3638
            $parsed_url2 = @phpthumb_functions::ParseURLbetter('http://' . @$_SERVER['HTTP_HOST']);
3639
            if (@$parsed_url1['host'] && @$parsed_url2['host'] && ($parsed_url1['host'] != $parsed_url2['host'])) {
3640
                // include "_offsite" only if nooffsitelink_enabled and if referrer doesn't match the domain of the current server
3641
                $this->cache_filename .= '_offsite';
3642
            }
3643
        }
3644
3645
        $ParametersString = '';
3646
        if ($this->fltr && is_array($this->fltr)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->fltr of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
3647
            $ParametersString .= '_fltr' . implode('_fltr', $this->fltr);
3648
        }
3649
        $FilenameParameters1 = ['ar', 'bg', 'bc', 'far', 'sx', 'sy', 'sw', 'sh', 'zc'];
3650
        foreach ($FilenameParameters1 as $key) {
3651
            if ($this->$key) {
3652
                $ParametersString .= '_' . $key . $this->$key;
3653
            }
3654
        }
3655
        $FilenameParameters2 = [
3656
            'h',
3657
            'w',
3658
            'wl',
3659
            'wp',
3660
            'ws',
3661
            'hp',
3662
            'hs',
3663
            'xto',
3664
            'ra',
3665
            'iar',
3666
            'aoe',
3667
            'maxb',
3668
            'sfn',
3669
            'dpi',
3670
        ];
3671
        foreach ($FilenameParameters2 as $key) {
3672
            if ($this->$key) {
3673
                $ParametersString .= '_' . $key . (int)$this->$key;
3674
            }
3675
        }
3676
        if ('jpeg' === $this->thumbnailFormat) {
3677
            // only JPEG output has variable quality option
3678
            $ParametersString .= '_q' . (int)$this->thumbnailQuality;
3679
        }
3680
        $this->DebugMessage('SetCacheFilename() _par set from md5(' . $ParametersString . ')', __FILE__, __LINE__);
3681
        $this->cache_filename .= '_par' . mb_strtolower(md5($ParametersString));
3682
3683
        if ($this->md5s) {
3684
            // source image MD5 hash provided
3685
            // do not source image modification date --
3686
            // cached image will be used even if file was modified or removed
3687
        } elseif (!$this->config_cache_source_filemtime_ignore_remote && preg_match('#^(f|ht)tps?\://#i', $this->src)) {
3688
            $this->cache_filename .= '_dat' . (int)phpthumb_functions::filedate_remote($this->src);
3689
        } elseif (!$this->config_cache_source_filemtime_ignore_local && $this->src && !$this->rawImageData) {
3690
            $this->cache_filename .= '_dat' . (int)(@filemtime($this->sourceFilename));
3691
        }
3692
3693
        $this->cache_filename .= '.' . mb_strtolower($this->thumbnailFormat);
3694
        $broad_directories    = '';
3695
        for ($i = 0; $i < $this->config_cache_directory_depth; ++$i) {
3696
            $broad_directories .= DIRECTORY_SEPARATOR . mb_substr($broad_directory_name, 0, $i + 1);
3697
        }
3698
3699
        $this->cache_filename = $this->config_cache_directory . $broad_directories . DIRECTORY_SEPARATOR . $this->config_cache_prefix . rawurlencode($this->cache_filename);
3700
3701
        return true;
3702
    }
3703
3704
    /**
3705
     * @param $width
3706
     * @param $height
3707
     * @return bool
3708
     */
3709
    public function SourceImageIsTooLarge($width, $height)
3710
    {
3711
        if (!$this->config_max_source_pixels) {
3712
            return false;
3713
        }
3714
        if ($this->php_memory_limit && function_exists('memory_get_usage')) {
3715
            $available_memory = $this->php_memory_limit - memory_get_usage();
3716
3717
            return (($width * $height * 5) > $available_memory);
3718
        }
3719
3720
        return (($width * $height) > $this->config_max_source_pixels);
3721
    }
3722
3723
    /**
3724
     * @param $filename
3725
     * @return bool|resource
3726
     */
3727
    public function ImageCreateFromFilename($filename)
3728
    {
3729
        // try to create GD image source directly via GD, if possible,
3730
        // rather than buffering to memory and creating with imagecreatefromstring
3731
        $ImageCreateWasAttempted = false;
3732
        $gd_image                = false;
3733
3734
        $this->DebugMessage('starting ImageCreateFromFilename(' . $filename . ')', __FILE__, __LINE__);
3735
        if ($filename && ($getimagesizeinfo = @getimagesize($filename))) {
3736
            if (!$this->SourceImageIsTooLarge($getimagesizeinfo[0], $getimagesizeinfo[1])) {
3737
                $ImageCreateFromFunction = [
3738
                    1  => 'imagecreatefromgif',
3739
                    2  => 'imagecreatefromjpeg',
3740
                    3  => 'imagecreatefrompng',
3741
                    15 => 'imagecreatefromwbmp',
3742
                ];
3743
                $this->DebugMessage('ImageCreateFromFilename found ($getimagesizeinfo[2]==' . @$getimagesizeinfo[2] . ')', __FILE__, __LINE__);
3744
                switch (@$getimagesizeinfo[2]) {
3745
                    case 1:  // GIF
3746
                    case 2:  // JPEG
3747
                    case 3:  // PNG
3748
                    case 15: // WBMP
3749
                        $ImageCreateFromFunctionName = $ImageCreateFromFunction[$getimagesizeinfo[2]];
3750
                        if (function_exists($ImageCreateFromFunctionName)) {
3751
                            $this->DebugMessage('Calling ' . $ImageCreateFromFunctionName . '(' . $filename . ')', __FILE__, __LINE__);
3752
                            $ImageCreateWasAttempted = true;
3753
                            $gd_image                = $ImageCreateFromFunctionName($filename);
3754
                        } else {
3755
                            $this->DebugMessage('NOT calling ' . $ImageCreateFromFunctionName . '(' . $filename . ') because !function_exists(' . $ImageCreateFromFunctionName . ')', __FILE__, __LINE__);
3756
                        }
3757
                        break;
3758
                    case 4:  // SWF
3759
                    case 5:  // PSD
3760
                    case 6:  // BMP
3761
                    case 7:  // TIFF (LE)
3762
                    case 8:  // TIFF (BE)
3763
                    case 9:  // JPC
3764
                    case 10: // JP2
3765
                    case 11: // JPX
3766
                    case 12: // JB2
3767
                    case 13: // SWC
3768
                    case 14: // IFF
3769
                    case 16: // XBM
3770
                        $this->DebugMessage('No built-in image creation function for image type "' . @$getimagesizeinfo[2] . '" ($getimagesizeinfo[2])', __FILE__, __LINE__);
3771
                        break;
3772
                    default:
3773
                        $this->DebugMessage('Unknown value for $getimagesizeinfo[2]: "' . @$getimagesizeinfo[2] . '"', __FILE__, __LINE__);
3774
                        break;
3775
                }
3776
            } else {
3777
                $this->DebugMessage(
3778
                    'image is ' . $getimagesizeinfo[0] . 'x' . $getimagesizeinfo[1] . ' and therefore contains more pixels (' . ($getimagesizeinfo[0] * $getimagesizeinfo[1]) . ') than $this->config_max_source_pixels setting (' . $this->config_max_source_pixels . ')',
3779
                    __FILE__,
3780
                    __LINE__
3781
                );
3782
3783
                return false;
3784
            }
3785
        } else {
3786
            $this->DebugMessage('empty $filename or getimagesize(' . $filename . ') failed', __FILE__, __LINE__);
3787
        }
3788
3789
        if (!$gd_image) {
3790
            // cannot create from filename, attempt to create source image with imagecreatefromstring, if possible
3791
            if ($ImageCreateWasAttempted) {
3792
                $this->DebugMessage($ImageCreateFromFunctionName . '() was attempted but FAILED', __FILE__, __LINE__);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ImageCreateFromFunctionName does not seem to be defined for all execution paths leading up to this point.
Loading history...
3793
            }
3794
            $this->DebugMessage('Populating $rawimagedata', __FILE__, __LINE__);
3795
            $rawimagedata = '';
3796
            if ($fp = @fopen($filename, 'rb')) {
3797
                $filesize   = filesize($filename);
3798
                $blocksize  = 8192;
3799
                $blockreads = ceil($filesize / $blocksize);
3800
                for ($i = 0; $i < $blockreads; ++$i) {
3801
                    $rawimagedata .= fread($fp, $blocksize);
3802
                }
3803
                fclose($fp);
3804
            } else {
3805
                $this->DebugMessage('cannot fopen(' . $filename . ')', __FILE__, __LINE__);
3806
            }
3807
            if ($rawimagedata) {
3808
                $this->DebugMessage('attempting ImageCreateFromStringReplacement($rawimagedata (' . mb_strlen($rawimagedata) . ' bytes), true)', __FILE__, __LINE__);
3809
                $gd_image = $this->ImageCreateFromStringReplacement($rawimagedata, true);
3810
            }
3811
        }
3812
3813
        return $gd_image;
3814
    }
3815
3816
    /**
3817
     * @return bool
3818
     */
3819
    public function SourceImageToGD()
3820
    {
3821
        if (is_resource($this->gdimg_source)) {
3822
            $this->source_width  = imagesx($this->gdimg_source);
3823
            $this->source_height = imagesy($this->gdimg_source);
3824
            $this->DebugMessage('skipping SourceImageToGD() because $this->gdimg_source is already a resource (' . $this->source_width . 'x' . $this->source_height . ')', __FILE__, __LINE__);
3825
3826
            return true;
3827
        }
3828
        $this->DebugMessage('starting SourceImageToGD()', __FILE__, __LINE__);
3829
3830
        if ($this->config_prefer_imagemagick) {
3831
            if (empty($this->sourceFilename) && !empty($this->rawImageData)) {
3832
                $this->DebugMessage('Copying raw image data to temp file and trying again with ImageMagick', __FILE__, __LINE__);
3833
                if ($tempnam = $this->phpThumb_tempnam()) {
3834
                    if (file_put_contents($tempnam, $this->rawImageData)) {
3835
                        $this->sourceFilename = $tempnam;
3836
                        if ($this->ImageMagickThumbnailToGD()) {
3837
                            // excellent, we have a thumbnailed source image
3838
                            $this->DebugMessage('ImageMagickThumbnailToGD() succeeded', __FILE__, __LINE__);
3839
                        } else {
3840
                            $this->DebugMessage('ImageMagickThumbnailToGD() failed', __FILE__, __LINE__);
3841
                        }
3842
                    } else {
3843
                        $this->DebugMessage('failed to put $this->rawImageData into temp file "' . $tempnam . '"', __FILE__, __LINE__);
3844
                    }
3845
                } else {
3846
                    $this->DebugMessage('failed to generate temp file name', __FILE__, __LINE__);
3847
                }
3848
            }
3849
        }
3850
        if (!$this->gdimg_source && $this->rawImageData) {
3851
            if ($this->SourceImageIsTooLarge($this->source_width, $this->source_height)) {
3852
                $memory_get_usage = (function_exists('memory_get_usage') ? memory_get_usage() : 0);
3853
3854
                return $this->ErrorImage(
3855
                    'Source image is too large ('
3856
                    . $this->source_width
3857
                    . 'x'
3858
                    . $this->source_height
3859
                    . ' = '
3860
                    . number_format($this->source_width * $this->source_height / 1000000, 1)
3861
                    . 'Mpx, max='
3862
                    . number_format($this->config_max_source_pixels / 1000000, 1)
3863
                    . 'Mpx) for GD creation (either install ImageMagick or increase PHP memory_limit to at least '
3864
                    . ceil(($memory_get_usage + (5 * $this->source_width * $this->source_height)) / 1048576)
3865
                    . 'M).'
3866
                );
3867
            }
3868
            if ($this->md5s && ($this->md5s != md5($this->rawImageData))) {
3869
                return $this->ErrorImage('$this->md5s != md5($this->rawImageData)' . "\n" . '"' . $this->md5s . '" != ' . "\n" . '"' . md5($this->rawImageData) . '"');
3870
            }
3871
            //if ($this->issafemode) {
3872
            //  return $this->ErrorImage('Cannot generate thumbnails from raw image data when PHP SAFE_MODE enabled');
3873
            //}
3874
            $this->gdimg_source = $this->ImageCreateFromStringReplacement($this->rawImageData);
3875
            if (!$this->gdimg_source) {
3876
                if ('BM' === mb_substr($this->rawImageData, 0, 2)) {
3877
                    $this->getimagesizeinfo[2] = 6; // BMP
3878
                } elseif (mb_substr($this->rawImageData, 0, 4) === 'II' . "\x2A\x00") {
3879
                    $this->getimagesizeinfo[2] = 7; // TIFF (littlendian)
3880
                } elseif (mb_substr($this->rawImageData, 0, 4) === 'MM' . "\x00\x2A") {
3881
                    $this->getimagesizeinfo[2] = 8; // TIFF (bigendian)
3882
                }
3883
                $this->DebugMessage('SourceImageToGD.ImageCreateFromStringReplacement() failed with unknown image type "' . mb_substr($this->rawImageData, 0, 4) . '" (' . phpthumb_functions::HexCharDisplay(mb_substr($this->rawImageData, 0, 4)) . ')', __FILE__, __LINE__);
3884
                //              return $this->ErrorImage('Unknown image type identified by "'.substr($this->rawImageData, 0, 4).'" ('.phpthumb_functions::HexCharDisplay(substr($this->rawImageData, 0, 4)).') in SourceImageToGD()['.__LINE__.']');
3885
            }
3886
        } elseif (!$this->gdimg_source && $this->sourceFilename) {
3887
            if ($this->md5s && ($this->md5s != phpthumb_functions::md5_file_safe($this->sourceFilename))) {
3888
                return $this->ErrorImage('$this->md5s != md5(sourceFilename)' . "\n" . '"' . $this->md5s . '" != ' . "\n" . '"' . phpthumb_functions::md5_file_safe($this->sourceFilename) . '"');
0 ignored issues
show
Bug introduced by
Are you sure phpthumb_functions::md5_...($this->sourceFilename) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3888
                return $this->ErrorImage('$this->md5s != md5(sourceFilename)' . "\n" . '"' . $this->md5s . '" != ' . "\n" . '"' . /** @scrutinizer ignore-type */ phpthumb_functions::md5_file_safe($this->sourceFilename) . '"');
Loading history...
3889
            }
3890
            switch (@$this->getimagesizeinfo[2]) {
3891
                case 1:
3892
                case 3:
3893
                    // GIF or PNG input file may have transparency
3894
                    $this->is_alpha = true;
3895
                    break;
3896
            }
3897
            if (!$this->SourceImageIsTooLarge($this->source_width, $this->source_height)) {
3898
                $this->gdimg_source = $this->ImageCreateFromFilename($this->sourceFilename);
3899
            }
3900
        }
3901
3902
        while (true) {
3903
            if ($this->gdimg_source) {
3904
                $this->DebugMessage('Not using EXIF thumbnail data because $this->gdimg_source is already set', __FILE__, __LINE__);
3905
                break;
3906
            }
3907
            if (!$this->exif_thumbnail_data) {
3908
                $this->DebugMessage('Not using EXIF thumbnail data because $this->exif_thumbnail_data is empty', __FILE__, __LINE__);
3909
                break;
3910
            }
3911
3912
            if (!$this->config_use_exif_thumbnail_for_speed) {
3913
                $this->DebugMessage('Not using EXIF thumbnail data because $this->config_use_exif_thumbnail_for_speed is FALSE', __FILE__, __LINE__);
3914
                break;
3915
            }
3916
            if ((0 != $this->thumbnailCropX) || (0 != $this->thumbnailCropY)) {
3917
                $this->DebugMessage('Not using EXIF thumbnail data because source cropping is enabled (' . $this->thumbnailCropX . ',' . $this->thumbnailCropY . ')', __FILE__, __LINE__);
3918
                break;
3919
            }
3920
            if (($this->w > $this->exif_thumbnail_width) || ($this->h > $this->exif_thumbnail_height)) {
3921
                $this->DebugMessage('Not using EXIF thumbnail data because EXIF thumbnail is too small (' . $this->exif_thumbnail_width . 'x' . $this->exif_thumbnail_height . ' vs ' . $this->w . 'x' . $this->h . ')', __FILE__, __LINE__);
3922
                break;
3923
            }
3924
            $source_ar = $this->source_width / $this->source_height;
3925
            $exif_ar   = $this->exif_thumbnail_width / $this->exif_thumbnail_height;
3926
            if (number_format($source_ar, 2) != number_format($exif_ar, 2)) {
3927
                $this->DebugMessage('not using EXIF thumbnail because $source_ar != $exif_ar (' . $source_ar . ' != ' . $exif_ar . ')', __FILE__, __LINE__);
3928
                break;
3929
            }
3930
3931
            // EXIF thumbnail exists, and is equal to or larger than destination thumbnail, and will be use as source image
3932
            $this->DebugMessage('Trying to use EXIF thumbnail as source image', __FILE__, __LINE__);
3933
3934
            if ($gdimg_exif_temp = $this->ImageCreateFromStringReplacement($this->exif_thumbnail_data, false)) {
3935
                $this->DebugMessage('Successfully using EXIF thumbnail as source image', __FILE__, __LINE__);
3936
                $this->gdimg_source   = $gdimg_exif_temp;
3937
                $this->source_width   = $this->exif_thumbnail_width;
3938
                $this->source_height  = $this->exif_thumbnail_height;
3939
                $this->thumbnailCropW = $this->source_width;
3940
                $this->thumbnailCropH = $this->source_height;
3941
3942
                return true;
3943
            }
3944
            $this->DebugMessage('$this->ImageCreateFromStringReplacement($this->exif_thumbnail_data, false) failed', __FILE__, __LINE__);
3945
3946
            break;
3947
        }
3948
3949
        if (!$this->gdimg_source) {
3950
            $this->DebugMessage('$this->gdimg_source is still empty', __FILE__, __LINE__);
3951
3952
            $this->DebugMessage('ImageMagickThumbnailToGD() failed', __FILE__, __LINE__);
3953
3954
            $imageHeader   = '';
3955
            $gd_info       = gd_info();
3956
            $GDreadSupport = false;
3957
            switch (@$this->getimagesizeinfo[2]) {
3958
                case 1:
3959
                    $imageHeader   = 'Content-Type: image/gif';
3960
                    $GDreadSupport = (bool)@$gd_info['GIF Read Support'];
3961
                    break;
3962
                case 2:
3963
                    $imageHeader   = 'Content-Type: image/jpeg';
3964
                    $GDreadSupport = (bool)@$gd_info['JPG Support'];
3965
                    break;
3966
                case 3:
3967
                    $imageHeader   = 'Content-Type: image/png';
3968
                    $GDreadSupport = (bool)@$gd_info['PNG Support'];
3969
                    break;
3970
            }
3971
            if ($imageHeader) {
3972
                // cannot create image for whatever reason (maybe imagecreatefromjpeg et al are not available?)
3973
                // and ImageMagick is not available either, no choice but to output original (not resized/modified) data and exit
3974
                if ($this->config_error_die_on_source_failure) {
3975
                    $errormessages   = [];
3976
                    $errormessages[] = 'All attempts to create GD image source failed.';
3977
                    if ($this->fatalerror) {
3978
                        $errormessages[] = $this->fatalerror;
3979
                    }
3980
                    if ($this->issafemode) {
3981
                        $errormessages[] = 'Safe Mode enabled, therefore ImageMagick is unavailable. (disable Safe Mode if possible)';
3982
                    } elseif (!$this->ImageMagickVersion()) {
3983
                        $errormessages[] = 'ImageMagick is not installed (it is highly recommended that you install it).';
3984
                    }
3985
                    if ($this->SourceImageIsTooLarge($this->getimagesizeinfo[0], $this->getimagesizeinfo[1])) {
3986
                        $memory_get_usage = (function_exists('memory_get_usage') ? memory_get_usage() : 0);
3987
                        $errormessages[]  = 'Source image is too large (' . $this->getimagesizeinfo[0] . 'x' . $this->getimagesizeinfo[1] . ' = ' . number_format($this->getimagesizeinfo[0] * $this->getimagesizeinfo[1] / 1000000, 1) . 'Mpx, max=' . number_format(
3988
                                $this->config_max_source_pixels / 1000000,
3989
                                1
3990
                            ) . 'Mpx) for GD creation (either install ImageMagick or increase PHP memory_limit to at least ' . ceil(($memory_get_usage + (5 * $this->getimagesizeinfo[0] * $this->getimagesizeinfo[1])) / 1048576) . 'M).';
3991
                    } elseif (!$GDreadSupport) {
3992
                        $errormessages[] = 'GD does not have read support for "' . $imageHeader . '".';
3993
                    } else {
3994
                        $errormessages[] = 'Source image probably corrupt.';
3995
                    }
3996
                    $this->ErrorImage(implode("\n", $errormessages));
3997
                } else {
3998
                    $this->DebugMessage(
3999
                        'All attempts to create GD image source failed (' . (ini_get('safe_mode') ? 'Safe Mode enabled, ImageMagick unavailable and source image probably too large for GD' : ($GDreadSupport ? 'source image probably corrupt' : 'GD does not have read support for "'
4000
                                                                                                                                                                                                                                                  . $imageHeader
4001
                                                                                                                                                                                                                                                  . '"')) . '), cannot generate thumbnail'
4002
                    );
4003
                    //$this->DebugMessage('All attempts to create GD image source failed ('.($GDreadSupport ? 'source image probably corrupt' : 'GD does not have read support for "'.$imageHeader.'"').'), outputing raw image', __FILE__, __LINE__);
4004
                    //if (!$this->phpThumbDebug) {
4005
                    //  header($imageHeader);
4006
                    //  echo $this->rawImageData;
4007
                    //  exit;
4008
                    //}
4009
                    return false;
4010
                }
4011
            }
4012
4013
            //switch (substr($this->rawImageData, 0, 2)) {
4014
            //  case 'BM':
4015
            switch (@$this->getimagesizeinfo[2]) {
4016
                case 6:
4017
                    ob_start();
4018
                    if (!@require_once __DIR__ . '/phpthumb.bmp.php') {
4019
                        ob_end_clean();
4020
4021
                        return $this->ErrorImage('include_once(' . __DIR__ . '/phpthumb.bmp.php) failed');
4022
                    }
4023
                    ob_end_clean();
4024
                    if ($fp = @fopen($this->sourceFilename, 'rb')) {
4025
                        $this->rawImageData = '';
4026
                        while (!feof($fp)) {
4027
                            $this->rawImageData .= fread($fp, 32768);
4028
                        }
4029
                        fclose($fp);
4030
                    }
4031
                    $phpthumb_bmp       = new phpthumb_bmp();
4032
                    $this->gdimg_source = $phpthumb_bmp->phpthumb_bmp2gd($this->rawImageData, phpthumb_functions::gd_version() >= 2.0);
4033
                    unset($phpthumb_bmp);
4034
                    if ($this->gdimg_source) {
4035
                        $this->DebugMessage('$phpthumb_bmp->phpthumb_bmp2gd() succeeded', __FILE__, __LINE__);
4036
                    } else {
4037
                        return $this->ErrorImage($this->ImageMagickVersion() ? 'ImageMagick failed on BMP source conversion' : 'phpthumb_bmp2gd() failed');
4038
                    }
4039
                    break;
4040
                //}
4041
                //switch (substr($this->rawImageData, 0, 4)) {
4042
                //  case 'II'."\x2A\x00":
4043
                //  case 'MM'."\x00\x2A":
4044
                case 7:
4045
                case 8:
4046
                    return $this->ErrorImage($this->ImageMagickVersion() ? 'ImageMagick failed on TIFF source conversion' : 'ImageMagick is unavailable and phpThumb() does not support TIFF source images without it');
4047
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
4048
                //case "\xD7\xCD\xC6\x9A":
4049
                //  return $this->ErrorImage($this->ImageMagickVersion() ? 'ImageMagick failed on WMF source conversion' : 'ImageMagick is unavailable and phpThumb() does not support WMF source images without it');
4050
                //  break;
4051
            }
4052
4053
            if (!$this->gdimg_source) {
4054
                if ($this->rawImageData) {
4055
                    $HeaderFourBytes = mb_substr($this->rawImageData, 0, 4);
4056
                } elseif ($this->sourceFilename) {
4057
                    if ($fp = @fopen($this->sourceFilename, 'rb')) {
4058
                        $HeaderFourBytes = fread($fp, 4);
4059
                        fclose($fp);
4060
                    } else {
4061
                        return $this->ErrorImage('failed to open "' . $this->sourceFilename . '" SourceImageToGD() [' . __LINE__ . ']');
4062
                    }
4063
                } else {
4064
                    return $this->ErrorImage('Unable to create image, neither filename nor image data suppplied in SourceImageToGD() [' . __LINE__ . ']');
4065
                }
4066
                if (!$this->ImageMagickVersion() && !phpthumb_functions::gd_version()) {
4067
                    return $this->ErrorImage('Neither GD nor ImageMagick seem to be installed on this server. At least one (preferably GD), or better both, MUST be installed for phpThumb to work.');
4068
                } elseif ("\xD7\xCD\xC6\x9A" === $HeaderFourBytes) { // WMF
4069
                    return $this->ErrorImage($this->ImageMagickVersion() ? 'ImageMagick failed on WMF source conversion' : 'ImageMagick is unavailable and phpThumb() does not support WMF source images without it');
4070
                } elseif ('%PDF' === $HeaderFourBytes) { // "%PDF"
4071
                    return $this->ErrorImage(
4072
                        $this->ImageMagickVersion() ? 'ImageMagick and GhostScript are both required for PDF source images; GhostScript may not be properly configured' : 'ImageMagick and/or GhostScript are unavailable and phpThumb() does not support PDF source images without them'
4073
                    );
4074
                } elseif ("\xFF\xD8\xFF" === mb_substr($HeaderFourBytes, 0, 3)) { // JPEG
4075
                    return $this->ErrorImage('Image (JPEG) is too large for PHP-GD memory_limit, please install ImageMagick or increase php.ini memory_limit setting');
4076
                } elseif ('%PNG' === $HeaderFourBytes) { // "%PNG"
4077
                    return $this->ErrorImage('Image (PNG) is too large for PHP-GD memory_limit, please install ImageMagick or increase php.ini memory_limit setting');
4078
                } elseif ('GIF' === mb_substr($HeaderFourBytes, 0, 3)) { // GIF
4079
                    return $this->ErrorImage('Image (GIF) is too large for PHP-GD memory_limit, please install ImageMagick or increase php.ini memory_limit setting');
4080
                }
4081
4082
                return $this->ErrorImage('Unknown image type identified by "' . $HeaderFourBytes . '" (' . phpthumb_functions::HexCharDisplay($HeaderFourBytes) . ') in SourceImageToGD() [' . __LINE__ . ']');
4083
            }
4084
        }
4085
4086
        if (!$this->gdimg_source) {
4087
            if ($gdimg_exif_temp = $this->ImageCreateFromStringReplacement($this->exif_thumbnail_data, false)) {
4088
                $this->DebugMessage('All other attempts failed, but successfully using EXIF thumbnail as source image', __FILE__, __LINE__);
4089
                $this->gdimg_source = $gdimg_exif_temp;
4090
                // override allow-enlarging setting if EXIF thumbnail is the only source available
4091
                // otherwise thumbnails larger than the EXIF thumbnail will be created at EXIF size
4092
                $this->aoe = true;
4093
4094
                return true;
4095
            }
4096
4097
            return false;
4098
        }
4099
4100
        $this->source_width  = imagesx($this->gdimg_source);
4101
        $this->source_height = imagesy($this->gdimg_source);
4102
4103
        return true;
4104
    }
4105
4106
    /**
4107
     * @param $var
4108
     * @return string|void
4109
     */
4110
    public function phpThumbDebugVarDump($var)
4111
    {
4112
        if (null === $var) {
4113
            return 'NULL';
4114
        } elseif (is_bool($var)) {
4115
            return ($var ? 'TRUE' : 'FALSE');
4116
        } elseif (is_string($var)) {
4117
            return 'string(' . mb_strlen($var) . ')' . str_repeat(' ', max(0, 3 - mb_strlen(mb_strlen($var)))) . ' "' . $var . '"';
4118
        } elseif (is_int($var)) {
4119
            return 'integer     ' . $var;
4120
        } elseif (is_float($var)) {
4121
            return 'float       ' . $var;
4122
        } elseif (is_array($var)) {
4123
            ob_start();
4124
            var_dump($var);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($var) looks like debug code. Are you sure you do not want to remove it?
Loading history...
4125
            $vardumpoutput = ob_get_contents();
4126
            ob_end_clean();
4127
4128
            return strtr($vardumpoutput, "\n\r\t", '   ');
4129
        }
4130
4131
        return gettype($var);
4132
    }
4133
4134
    /**
4135
     * @param string $level
4136
     * @return bool
4137
     */
4138
    public function phpThumbDebug($level = '')
4139
    {
4140
        if ($level && ($this->phpThumbDebug !== $level)) {
4141
            return true;
4142
        }
4143
        if ($this->config_disable_debug) {
4144
            return $this->ErrorImage('phpThumbDebug disabled');
4145
        }
4146
4147
        $FunctionsExistance  = [
4148
            'exif_thumbnail',
4149
            'gd_info',
4150
            'image_type_to_mime_type',
4151
            'getimagesize',
4152
            'imagecopyresampled',
4153
            'imagecopyresized',
4154
            'imagecreate',
4155
            'imagecreatefromstring',
4156
            'imagecreatetruecolor',
4157
            'imageistruecolor',
4158
            'imagerotate',
4159
            'imagetypes',
4160
            'version_compare',
4161
            'imagecreatefromgif',
4162
            'imagecreatefromjpeg',
4163
            'imagecreatefrompng',
4164
            'imagecreatefromwbmp',
4165
            'imagecreatefromxbm',
4166
            'imagecreatefromxpm',
4167
            'imagecreatefromstring',
4168
            'imagecreatefromgd',
4169
            'imagecreatefromgd2',
4170
            'imagecreatefromgd2part',
4171
            'imagejpeg',
4172
            'imagegif',
4173
            'imagepng',
4174
            'imagewbmp',
4175
        ];
4176
        $ParameterNames      = [
4177
            'src',
4178
            'new',
4179
            'w',
4180
            'h',
4181
            'f',
4182
            'q',
4183
            'sx',
4184
            'sy',
4185
            'sw',
4186
            'sh',
4187
            'far',
4188
            'bg',
4189
            'bc',
4190
            'file',
4191
            'goto',
4192
            'err',
4193
            'xto',
4194
            'ra',
4195
            'ar',
4196
            'aoe',
4197
            'iar',
4198
            'maxb',
4199
        ];
4200
        $ConfigVariableNames = [
4201
            'document_root',
4202
            'temp_directory',
4203
            'output_format',
4204
            'output_maxwidth',
4205
            'output_maxheight',
4206
            'error_message_image_default',
4207
            'error_bgcolor',
4208
            'error_textcolor',
4209
            'error_fontsize',
4210
            'error_die_on_error',
4211
            'error_silent_die_on_error',
4212
            'error_die_on_source_failure',
4213
            'nohotlink_enabled',
4214
            'nohotlink_valid_domains',
4215
            'nohotlink_erase_image',
4216
            'nohotlink_text_message',
4217
            'nooffsitelink_enabled',
4218
            'nooffsitelink_valid_domains',
4219
            'nooffsitelink_require_refer',
4220
            'nooffsitelink_erase_image',
4221
            'nooffsitelink_text_message',
4222
            'high_security_enabled',
4223
            'allow_src_above_docroot',
4224
            'allow_src_above_phpthumb',
4225
            'max_source_pixels',
4226
            'use_exif_thumbnail_for_speed',
4227
            'border_hexcolor',
4228
            'background_hexcolor',
4229
            'ttf_directory',
4230
            'disable_pathinfo_parsing',
4231
            'disable_imagecopyresampled',
4232
        ];
4233
        $OtherVariableNames  = [
4234
            'phpThumbDebug',
4235
            'thumbnailQuality',
4236
            'thumbnailFormat',
4237
            'gdimg_output',
4238
            'gdimg_source',
4239
            'sourceFilename',
4240
            'source_width',
4241
            'source_height',
4242
            'thumbnailCropX',
4243
            'thumbnailCropY',
4244
            'thumbnailCropW',
4245
            'thumbnailCropH',
4246
            'exif_thumbnail_width',
4247
            'exif_thumbnail_height',
4248
            'exif_thumbnail_type',
4249
            'thumbnail_width',
4250
            'thumbnail_height',
4251
            'thumbnail_image_width',
4252
            'thumbnail_image_height',
4253
        ];
4254
4255
        $DebugOutput   = [];
4256
        $DebugOutput[] = 'phpThumb() version          = ' . $this->phpthumb_version;
4257
        $DebugOutput[] = 'PHP_VERSION                = ' . @PHP_VERSION;
4258
        $DebugOutput[] = 'PHP_OS                      = ' . PHP_OS;
4259
        $DebugOutput[] = '$_SERVER[SERVER_SOFTWARE]   = ' . @$_SERVER['SERVER_SOFTWARE'];
4260
        $DebugOutput[] = '__FILE__                    = ' . __FILE__;
4261
        $DebugOutput[] = 'realpath(.)                 = ' . @realpath('.');
0 ignored issues
show
Bug introduced by
Are you sure @realpath('.') of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4261
        $DebugOutput[] = 'realpath(.)                 = ' . /** @scrutinizer ignore-type */ @realpath('.');
Loading history...
4262
        $DebugOutput[] = '$_SERVER[PHP_SELF]          = ' . @$_SERVER['PHP_SELF'];
4263
        $DebugOutput[] = '$_SERVER[HOST_NAME]         = ' . @$_SERVER['HOST_NAME'];
4264
        $DebugOutput[] = '$_SERVER[HTTP_REFERER]      = ' . @\Xmf\Request::getString('HTTP_REFERER', '', 'SERVER');
4265
        $DebugOutput[] = '$_SERVER[QUERY_STRING]      = ' . @$_SERVER['QUERY_STRING'];
4266
        $DebugOutput[] = '$_SERVER[PATH_INFO]         = ' . @$_SERVER['PATH_INFO'];
4267
        $DebugOutput[] = '$_SERVER[DOCUMENT_ROOT]     = ' . @$_SERVER['DOCUMENT_ROOT'];
4268
        $DebugOutput[] = 'getenv(DOCUMENT_ROOT)       = ' . @getenv('DOCUMENT_ROOT');
4269
        $DebugOutput[] = '';
4270
4271
        $DebugOutput[] = 'get_magic_quotes_gpc()         = ' . $this->phpThumbDebugVarDump(@get_magic_quotes_gpc());
4272
        $DebugOutput[] = 'get_magic_quotes_runtime()     = ' . $this->phpThumbDebugVarDump(@get_magic_quotes_runtime());
4273
        $DebugOutput[] = 'error_reporting()              = ' . $this->phpThumbDebugVarDump(error_reporting());
4274
        $DebugOutput[] = 'ini_get(error_reporting)       = ' . $this->phpThumbDebugVarDump(@ini_get('error_reporting'));
4275
        $DebugOutput[] = 'ini_get(display_errors)        = ' . $this->phpThumbDebugVarDump(@ini_get('display_errors'));
4276
        $DebugOutput[] = 'ini_get(allow_url_fopen)       = ' . $this->phpThumbDebugVarDump(@ini_get('allow_url_fopen'));
4277
        $DebugOutput[] = 'ini_get(disable_functions)     = ' . $this->phpThumbDebugVarDump(@ini_get('disable_functions'));
4278
        $DebugOutput[] = 'get_cfg_var(disable_functions) = ' . $this->phpThumbDebugVarDump(@get_cfg_var('disable_functions'));
4279
        $DebugOutput[] = 'ini_get(safe_mode)             = ' . $this->phpThumbDebugVarDump(@ini_get('safe_mode'));
4280
        $DebugOutput[] = 'ini_get(open_basedir)          = ' . $this->phpThumbDebugVarDump(@ini_get('open_basedir'));
4281
        $DebugOutput[] = 'ini_get(max_execution_time)    = ' . $this->phpThumbDebugVarDump(@ini_get('max_execution_time'));
4282
        $DebugOutput[] = 'ini_get(memory_limit)          = ' . $this->phpThumbDebugVarDump(@ini_get('memory_limit'));
4283
        $DebugOutput[] = 'get_cfg_var(memory_limit)      = ' . $this->phpThumbDebugVarDump(@get_cfg_var('memory_limit'));
4284
        $DebugOutput[] = 'memory_get_usage()             = ' . (function_exists('memory_get_usage') ? $this->phpThumbDebugVarDump(@memory_get_usage()) : 'n/a');
4285
        $DebugOutput[] = '';
4286
4287
        $DebugOutput[] = '$this->config_prefer_imagemagick            = ' . $this->phpThumbDebugVarDump($this->config_prefer_imagemagick);
4288
        $DebugOutput[] = '$this->config_imagemagick_path              = ' . $this->phpThumbDebugVarDump($this->config_imagemagick_path);
4289
        $DebugOutput[] = '$this->ImageMagickWhichConvert()            = ' . $this->ImageMagickWhichConvert();
4290
        $IMpathUsed    = ($this->config_imagemagick_path ?: $this->ImageMagickWhichConvert());
4291
        $DebugOutput[] = '[actual ImageMagick path used]              = ' . $this->phpThumbDebugVarDump($IMpathUsed);
4292
        $DebugOutput[] = 'file_exists([actual ImageMagick path used]) = ' . $this->phpThumbDebugVarDump(@file_exists($IMpathUsed));
4293
        $DebugOutput[] = 'ImageMagickVersion(false)                   = ' . $this->ImageMagickVersion(false);
4294
        $DebugOutput[] = 'ImageMagickVersion(true)                    = ' . $this->ImageMagickVersion(true);
4295
        $DebugOutput[] = '';
4296
4297
        $DebugOutput[] = '$this->config_cache_directory               = ' . $this->phpThumbDebugVarDump($this->config_cache_directory);
4298
        $DebugOutput[] = '$this->config_cache_directory_depth         = ' . $this->phpThumbDebugVarDump($this->config_cache_directory_depth);
4299
        $DebugOutput[] = '$this->config_cache_disable_warning         = ' . $this->phpThumbDebugVarDump($this->config_cache_disable_warning);
4300
        $DebugOutput[] = '$this->config_cache_maxage                  = ' . $this->phpThumbDebugVarDump($this->config_cache_maxage);
4301
        $DebugOutput[] = '$this->config_cache_maxsize                 = ' . $this->phpThumbDebugVarDump($this->config_cache_maxsize);
4302
        $DebugOutput[] = '$this->config_cache_maxfiles                = ' . $this->phpThumbDebugVarDump($this->config_cache_maxfiles);
4303
        $DebugOutput[] = '$this->config_cache_force_passthru          = ' . $this->phpThumbDebugVarDump($this->config_cache_force_passthru);
4304
        $DebugOutput[] = '$this->cache_filename                       = ' . $this->phpThumbDebugVarDump($this->cache_filename);
4305
        $DebugOutput[] = 'is_readable($this->config_cache_directory)  = ' . $this->phpThumbDebugVarDump(@is_readable($this->config_cache_directory));
4306
        $DebugOutput[] = 'is_writable($this->config_cache_directory)  = ' . $this->phpThumbDebugVarDump(@is_writable($this->config_cache_directory));
4307
        $DebugOutput[] = 'is_readable($this->cache_filename)          = ' . $this->phpThumbDebugVarDump(@is_readable($this->cache_filename));
4308
        $DebugOutput[] = 'is_writable($this->cache_filename)          = ' . (@file_exists($this->cache_filename) ? $this->phpThumbDebugVarDump(@is_writable($this->cache_filename)) : 'n/a');
4309
        $DebugOutput[] = '';
4310
4311
        foreach ($ConfigVariableNames as $varname) {
4312
            $varname       = 'config_' . $varname;
4313
            $value         = $this->$varname;
4314
            $DebugOutput[] = '$this->' . str_pad($varname, 37, ' ', STR_PAD_RIGHT) . ' = ' . $this->phpThumbDebugVarDump($value);
4315
        }
4316
        $DebugOutput[] = '';
4317
        foreach ($OtherVariableNames as $varname) {
4318
            $value         = $this->$varname;
4319
            $DebugOutput[] = '$this->' . str_pad($varname, 27, ' ', STR_PAD_RIGHT) . ' = ' . $this->phpThumbDebugVarDump($value);
4320
        }
4321
        $DebugOutput[] = 'strlen($this->rawImageData)        = ' . mb_strlen(@$this->rawImageData);
4322
        $DebugOutput[] = 'strlen($this->exif_thumbnail_data) = ' . mb_strlen(@$this->exif_thumbnail_data);
4323
        $DebugOutput[] = '';
4324
4325
        foreach ($ParameterNames as $varname) {
4326
            $value         = $this->$varname;
4327
            $DebugOutput[] = '$this->' . str_pad($varname, 4, ' ', STR_PAD_RIGHT) . ' = ' . $this->phpThumbDebugVarDump($value);
4328
        }
4329
        $DebugOutput[] = '';
4330
4331
        foreach ($FunctionsExistance as $functionname) {
4332
            $DebugOutput[] = 'builtin_function_exists(' . $functionname . ')' . str_repeat(' ', 23 - mb_strlen($functionname)) . ' = ' . $this->phpThumbDebugVarDump(phpthumb_functions::builtin_function_exists($functionname));
4333
        }
4334
        $DebugOutput[] = '';
4335
4336
        $gd_info = gd_info();
4337
        foreach ($gd_info as $key => $value) {
4338
            $DebugOutput[] = 'gd_info.' . str_pad($key, 34, ' ', STR_PAD_RIGHT) . ' = ' . $this->phpThumbDebugVarDump($value);
4339
        }
4340
        $DebugOutput[] = '';
4341
4342
        $exif_info = phpthumb_functions::exif_info();
4343
        foreach ($exif_info as $key => $value) {
4344
            $DebugOutput[] = 'exif_info.' . str_pad($key, 26, ' ', STR_PAD_RIGHT) . ' = ' . $this->phpThumbDebugVarDump($value);
4345
        }
4346
        $DebugOutput[] = '';
4347
4348
        if ($ApacheLookupURIarray = phpthumb_functions::ApacheLookupURIarray(dirname(@$_SERVER['PHP_SELF']))) {
4349
            foreach ($ApacheLookupURIarray as $key => $value) {
4350
                $DebugOutput[] = 'ApacheLookupURIarray.' . str_pad($key, 15, ' ', STR_PAD_RIGHT) . ' = ' . $this->phpThumbDebugVarDump($value);
4351
            }
4352
        } else {
4353
            $DebugOutput[] = 'ApacheLookupURIarray() -- FAILED';
4354
        }
4355
        $DebugOutput[] = '';
4356
4357
        if (isset($_GET) && is_array($_GET)) {
4358
            foreach ($_GET as $key => $value) {
4359
                $DebugOutput[] = '$_GET[' . $key . ']' . str_repeat(' ', 30 - mb_strlen($key)) . '= ' . $this->phpThumbDebugVarDump($value);
4360
            }
4361
        }
4362
        if (isset($_POST) && is_array($_POST)) {
4363
            foreach ($_POST as $key => $value) {
4364
                $DebugOutput[] = '$_POST[' . $key . ']' . str_repeat(' ', 29 - mb_strlen($key)) . '= ' . $this->phpThumbDebugVarDump($value);
4365
            }
4366
        }
4367
        $DebugOutput[] = '';
4368
4369
        $DebugOutput[] = '$this->debugmessages:';
4370
        foreach ($this->debugmessages as $errorstring) {
4371
            $DebugOutput[] = '  * ' . $errorstring;
4372
        }
4373
        $DebugOutput[] = '';
4374
4375
        $DebugOutput[] = '$this->debugtiming:';
4376
        foreach ($this->debugtiming as $timestamp => $timingstring) {
4377
            $DebugOutput[] = '  * ' . $timestamp . ' ' . $timingstring;
4378
        }
4379
        $DebugOutput[] = '  * Total processing time: ' . number_format(max(array_keys($this->debugtiming)) - min(array_keys($this->debugtiming)), 6);
4380
4381
        $this->f = (isset($_GET['f']) ? $_GET['f'] : $this->f); // debug modes 0-2 don't recognize text mode otherwise
4382
4383
        return $this->ErrorImage(implode("\n", $DebugOutput), 700, 500, true);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->ErrorImage(implod...utput), 700, 500, true) targeting phpthumb::ErrorImage() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
4384
    }
4385
4386
    /**
4387
     * @param $text
4388
     * @return bool
4389
     */
4390
    public function FatalError($text)
4391
    {
4392
        if (null === $this->fatalerror) {
4393
            $this->fatalerror = $text;
4394
        }
4395
4396
        return true;
4397
    }
4398
4399
    /**
4400
     * @param      $text
4401
     * @param int  $width
4402
     * @param int  $height
4403
     * @param bool $forcedisplay
4404
     * @return bool
4405
     */
4406
    public function ErrorImage($text, $width = 0, $height = 0, $forcedisplay = false)
4407
    {
4408
        $width  = ($width ?: $this->config_error_image_width);
4409
        $height = ($height ?: $this->config_error_image_height);
4410
4411
        $text = 'phpThumb() v' . $this->phpthumb_version . "\n" . 'http://phpthumb.sourceforge.net' . "\n\n" . ($this->config_disable_debug ? 'Error messages disabled.'
4412
                                                                                                                                              . "\n\n"
4413
                                                                                                                                              . 'edit phpThumb.config.php and (temporarily) set'
4414
                                                                                                                                              . "\n"
4415
                                                                                                                                              . '$PHPTHUMB_CONFIG[\'disable_debug\'] = false;'
4416
                                                                                                                                              . "\n"
4417
                                                                                                                                              . 'to view the details of this error' : $text);
4418
4419
        $this->FatalError($text);
4420
        $this->DebugMessage($text, __FILE__, __LINE__);
4421
        $this->purgeTempFiles();
4422
        if ($this->config_error_silent_die_on_error) {
4423
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
4424
        }
4425
        if ($this->phpThumbDebug && !$forcedisplay) {
4426
            return false;
4427
        }
4428
        if (!$this->config_error_die_on_error && !$forcedisplay) {
4429
            return false;
4430
        }
4431
        if ($this->err || $this->config_error_message_image_default) {
4432
            // Show generic custom error image instead of error message
4433
            // for use on production sites where you don't want debug messages
4434
            if (('showerror' === $this->err) || $this->phpThumbDebug) {
4435
                // fall through and actually show error message even if default error image is set
4436
            } else {
4437
                header('Location: ' . ($this->err ?: $this->config_error_message_image_default));
4438
                exit;
4439
            }
4440
        }
4441
        $this->setOutputFormat();
4442
        if (!$this->thumbnailFormat || !$this->config_disable_debug || (phpthumb_functions::gd_version() < 1)) {
4443
            $this->thumbnailFormat = 'text';
4444
        }
4445
        if ('text' === @$this->thumbnailFormat) {
4446
            // bypass all GD functions and output text error message
4447
            if (!headers_sent()) {
4448
                header('Content-type: text/plain');
4449
                echo $text;
4450
            } else {
4451
                echo '<pre>' . htmlspecialchars($text, ENT_QUOTES | ENT_HTML5) . '</pre>';
4452
            }
4453
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
4454
        }
4455
4456
        $FontWidth  = imagefontwidth($this->config_error_fontsize);
4457
        $FontHeight = imagefontheight($this->config_error_fontsize);
4458
4459
        $LinesOfText = explode("\n", @wordwrap($text, floor($width / $FontWidth), "\n", true));
0 ignored issues
show
Bug introduced by
floor($width / $FontWidth) of type double is incompatible with the type integer expected by parameter $width of wordwrap(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4459
        $LinesOfText = explode("\n", @wordwrap($text, /** @scrutinizer ignore-type */ floor($width / $FontWidth), "\n", true));
Loading history...
4460
        $height      = max($height, count($LinesOfText) * $FontHeight);
4461
4462
        $headers_file = '';
4463
        $headers_line = '';
4464
        if (phpthumb_functions::version_compare_replacement(PHP_VERSION, '4.3.0', '>=')
4465
            && headers_sent($headers_file, $headers_line)) {
0 ignored issues
show
Bug introduced by
$headers_line of type string is incompatible with the type integer expected by parameter $line of headers_sent(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

4465
            && headers_sent($headers_file, /** @scrutinizer ignore-type */ $headers_line)) {
Loading history...
4466
            echo "\n" . '**Headers already sent in file "' . $headers_file . '" on line "' . $headers_line . '", dumping error message as text:**<br><pre>' . "\n\n" . $text . "\n" . '</pre>';
4467
        } elseif (headers_sent()) {
4468
            echo "\n" . '**Headers already sent, dumping error message as text:**<br><pre>' . "\n\n" . $text . "\n" . '</pre>';
4469
        } elseif ($gdimg_error = imagecreate($width, $height)) {
4470
            $background_color = phpthumb_functions::ImageHexColorAllocate($gdimg_error, $this->config_error_bgcolor, true);
4471
            $text_color       = phpthumb_functions::ImageHexColorAllocate($gdimg_error, $this->config_error_textcolor, true);
4472
            imagefilledrectangle($gdimg_error, 0, 0, $width, $height, $background_color);
4473
            $lineYoffset = 0;
4474
            foreach ($LinesOfText as $line) {
4475
                imagestring($gdimg_error, $this->config_error_fontsize, 2, $lineYoffset, $line, $text_color);
4476
                $lineYoffset += $FontHeight;
4477
            }
4478
            if (function_exists('imagetypes')) {
4479
                $imagetypes = imagetypes();
4480
                if ($imagetypes & IMG_PNG) {
4481
                    header('Content-Type: image/png');
4482
                    imagepng($gdimg_error);
4483
                } elseif ($imagetypes & IMG_GIF) {
4484
                    header('Content-Type: image/gif');
4485
                    imagegif($gdimg_error);
4486
                } elseif ($imagetypes & IMG_JPG) {
4487
                    header('Content-Type: image/jpeg');
4488
                    imagejpeg($gdimg_error);
4489
                } elseif ($imagetypes & IMG_WBMP) {
4490
                    header('Content-Type: image/vnd.wap.wbmp');
4491
                    imagewbmp($gdimg_error);
4492
                }
4493
            }
4494
            imagedestroy($gdimg_error);
4495
        }
4496
        if (!headers_sent()) {
4497
            echo "\n" . '**Failed to send graphical error image, dumping error message as text:**<br>' . "\n\n" . $text;
4498
        }
4499
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
4500
    }
4501
4502
    /**
4503
     * @param      $RawImageData
4504
     * @param bool $DieOnErrors
4505
     * @return bool|resource
4506
     */
4507
    public function ImageCreateFromStringReplacement(&$RawImageData, $DieOnErrors = false)
4508
    {
4509
        // there are serious bugs in the non-bundled versions of GD which may cause
4510
        // PHP to segfault when calling imagecreatefromstring() - avoid if at all possible
4511
        // when not using a bundled version of GD2
4512
        if (!phpthumb_functions::gd_version()) {
4513
            if ($DieOnErrors) {
4514
                if (!headers_sent()) {
4515
                    // base64-encoded error image in GIF format
4516
                    $ERROR_NOGD = 'R0lGODlhIAAgALMAAAAAABQUFCQkJDY2NkZGRldXV2ZmZnJycoaGhpSUlKWlpbe3t8XFxdXV1eTk5P7+/iwAAAAAIAAgAAAE/vDJSau9WILtTAACUinDNijZtAHfCojS4W5H+qxD8xibIDE9h0OwWaRWDIljJSkUJYsN4bihMB8th3IToAKs1VtYM75cyV8sZ8vygtOE5yMKmGbO4jRdICQCjHdlZzwzNW4qZSQmKDaNjhUMBX4BBAlmMywFSRWEmAI6b5gAlhNxokGhooAIK5o/pi9vEw4Lfj4OLTAUpj6IabMtCwlSFw0DCKBoFqwAB04AjI54PyZ+yY3TD0ss2YcVmN/gvpcu4TOyFivWqYJlbAHPpOntvxNAACcmGHjZzAZqzSzcq5fNjxFmAFw9iFRunD1epU6tsIPmFCAJnWYE0FURk7wJDA0MTKpEzoWAAskiAAA7';
4517
                    header('Content-Type: image/gif');
4518
                    echo base64_decode($ERROR_NOGD, true);
4519
                } else {
4520
                    echo '*** ERROR: No PHP-GD support available ***';
4521
                }
4522
                exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
4523
            }
4524
            $this->DebugMessage('ImageCreateFromStringReplacement() failed: gd_version says "' . phpthumb_functions::gd_version() . '"', __FILE__, __LINE__);
4525
4526
            return false;
4527
        }
4528
        if (phpthumb_functions::gd_is_bundled()) {
4529
            $this->DebugMessage('ImageCreateFromStringReplacement() calling built-in imagecreatefromstring()', __FILE__, __LINE__);
4530
4531
            return @imagecreatefromstring($RawImageData);
0 ignored issues
show
Bug Best Practice introduced by
The expression return @imagecreatefromstring($RawImageData) also could return the type GdImage which is incompatible with the documented return type boolean|resource.
Loading history...
4532
        }
4533
        if ($this->issafemode) {
4534
            $this->DebugMessage('ImageCreateFromStringReplacement() failed: cannot create temp file in SAFE_MODE', __FILE__, __LINE__);
4535
4536
            return false;
4537
        }
4538
4539
        switch (mb_substr($RawImageData, 0, 3)) {
4540
            case 'GIF':
4541
                $ICFSreplacementFunctionName = 'imagecreatefromgif';
4542
                break;
4543
            case "\xFF\xD8\xFF":
4544
                $ICFSreplacementFunctionName = 'imagecreatefromjpeg';
4545
                break;
4546
            case "\x89" . 'PN':
4547
                $ICFSreplacementFunctionName = 'imagecreatefrompng';
4548
                break;
4549
            default:
4550
                $this->DebugMessage('ImageCreateFromStringReplacement() failed: unknown fileformat signature "' . phpthumb_functions::HexCharDisplay(mb_substr($RawImageData, 0, 3)) . '"', __FILE__, __LINE__);
4551
4552
                return false;
4553
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
4554
        }
4555
        $ErrorMessage = '';
4556
        if ($tempnam = $this->phpThumb_tempnam()) {
4557
            if ($fp_tempnam = @fopen($tempnam, 'wb')) {
4558
                fwrite($fp_tempnam, $RawImageData);
4559
                fclose($fp_tempnam);
4560
                if (('imagecreatefromgif' === $ICFSreplacementFunctionName)
4561
                    && !function_exists($ICFSreplacementFunctionName)) {
4562
                    // Need to create from GIF file, but imagecreatefromgif does not exist
4563
                    ob_start();
4564
                    if (!@require_once __DIR__ . '/phpthumb.gif.php') {
4565
                        $ErrorMessage = 'Failed to include required file "' . __DIR__ . '/phpthumb.gif.php" in ' . __FILE__ . ' on line ' . __LINE__;
4566
                        $this->DebugMessage($ErrorMessage, __FILE__, __LINE__);
4567
                    }
4568
                    ob_end_clean();
4569
                    // gif_loadFileToGDimageResource() cannot read from raw data, write to file first
4570
                    if ($tempfilename = $this->phpThumb_tempnam()) {
4571
                        if ($fp_tempfile = @fopen($tempfilename, 'wb')) {
4572
                            fwrite($fp_tempfile, $RawImageData);
4573
                            fclose($fp_tempfile);
4574
                            $gdimg_source = gif_loadFileToGDimageResource($tempfilename);
4575
                            $this->DebugMessage('gif_loadFileToGDimageResource(' . $tempfilename . ') completed', __FILE__, __LINE__);
4576
                            $this->DebugMessage('deleting "' . $tempfilename . '"', __FILE__, __LINE__);
4577
                            unlink($tempfilename);
4578
4579
                            return $gdimg_source;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $gdimg_source also could return the type GdImage which is incompatible with the documented return type boolean|resource.
Loading history...
4580
                        }
4581
                        $ErrorMessage = 'Failed to open tempfile in ' . __FILE__ . ' on line ' . __LINE__;
4582
                        $this->DebugMessage($ErrorMessage, __FILE__, __LINE__);
4583
                    } else {
4584
                        $ErrorMessage = 'Failed to open generate tempfile name in ' . __FILE__ . ' on line ' . __LINE__;
4585
                        $this->DebugMessage($ErrorMessage, __FILE__, __LINE__);
4586
                    }
4587
                } elseif (function_exists($ICFSreplacementFunctionName)
4588
                          && ($gdimg_source = @$ICFSreplacementFunctionName($tempnam))) {
4589
                    // great
4590
                    $this->DebugMessage($ICFSreplacementFunctionName . '(' . $tempnam . ') succeeded', __FILE__, __LINE__);
4591
                    $this->DebugMessage('deleting "' . $tempnam . '"', __FILE__, __LINE__);
4592
                    unlink($tempnam);
4593
4594
                    return $gdimg_source;
4595
                } else {
4596
                    // GD functions not available, or failed to create image
4597
                    $this->DebugMessage($ICFSreplacementFunctionName . '(' . $tempnam . ') ' . (function_exists($ICFSreplacementFunctionName) ? 'failed' : 'does not exist'), __FILE__, __LINE__);
4598
                    if (isset($_GET['phpThumbDebug'])) {
4599
                        $this->phpThumbDebug();
4600
                    }
4601
                }
4602
            } else {
4603
                $ErrorMessage = 'Failed to fopen(' . $tempnam . ', "wb") in ' . __FILE__ . ' on line ' . __LINE__ . "\n" . 'You may need to set $PHPTHUMB_CONFIG[temp_directory] in phpThumb.config.php';
4604
                if ($this->issafemode) {
4605
                    $ErrorMessage = 'ImageCreateFromStringReplacement() failed in ' . __FILE__ . ' on line ' . __LINE__ . ': cannot create temp file in SAFE_MODE';
4606
                }
4607
                $this->DebugMessage($ErrorMessage, __FILE__, __LINE__);
4608
            }
4609
            $this->DebugMessage('deleting "' . $tempnam . '"', __FILE__, __LINE__);
4610
            @unlink($tempnam);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

4610
            /** @scrutinizer ignore-unhandled */ @unlink($tempnam);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
4611
        } else {
4612
            $ErrorMessage = 'Failed to generate phpThumb_tempnam() in ' . __FILE__ . ' on line ' . __LINE__ . "\n" . 'You may need to set $PHPTHUMB_CONFIG[temp_directory] in phpThumb.config.php';
4613
            if ($this->issafemode) {
4614
                $ErrorMessage = 'ImageCreateFromStringReplacement() failed in ' . __FILE__ . ' on line ' . __LINE__ . ': cannot create temp file in SAFE_MODE';
4615
            }
4616
        }
4617
        if ($DieOnErrors && $ErrorMessage) {
4618
            return $this->ErrorImage($ErrorMessage);
4619
        }
4620
4621
        return false;
4622
    }
4623
4624
    /**
4625
     * @param $dst_im
4626
     * @param $src_im
4627
     * @param $dstX
4628
     * @param $dstY
4629
     * @param $srcX
4630
     * @param $srcY
4631
     * @param $dstW
4632
     * @param $dstH
4633
     * @param $srcW
4634
     * @param $srcH
4635
     * @return bool
4636
     */
4637
    public function ImageResizeFunction(&$dst_im, &$src_im, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
4638
    {
4639
        $this->DebugMessage('ImageResizeFunction($o, $s, ' . $dstX . ', ' . $dstY . ', ' . $srcX . ', ' . $srcY . ', ' . $dstW . ', ' . $dstH . ', ' . $srcW . ', ' . $srcH . ')', __FILE__, __LINE__);
4640
        if (($dstW == $srcW) && ($dstH == $srcH)) {
4641
            return imagecopy($dst_im, $src_im, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH);
4642
        }
4643
        if (phpthumb_functions::gd_version() >= 2.0) {
4644
            if ($this->config_disable_imagecopyresampled) {
4645
                return phpthumb_functions::ImageCopyResampleBicubic($dst_im, $src_im, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH);
4646
            }
4647
4648
            return imagecopyresampled($dst_im, $src_im, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH);
4649
        }
4650
4651
        return imagecopyresized($dst_im, $src_im, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH);
4652
    }
4653
4654
    /**
4655
     * @return bool
4656
     */
4657
    public function InitializeTempDirSetting()
4658
    {
4659
        $this->config_temp_directory = ($this->config_temp_directory ?: (function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '')); // sys_get_temp_dir added in PHP v5.2.1
4660
        $this->config_temp_directory = ($this->config_temp_directory ?: getenv('TMPDIR'));
4661
        $this->config_temp_directory = ($this->config_temp_directory ?: getenv('TMP'));
4662
        $this->config_temp_directory = ($this->config_temp_directory ?: ini_get('upload_tmp_dir'));
4663
        $this->config_temp_directory = $this->realPathSafe($this->config_temp_directory);
4664
4665
        return true;
4666
    }
4667
4668
    /**
4669
     * @return bool|null|string|string[]
4670
     */
4671
    public function phpThumb_tempnam()
4672
    {
4673
        $this->InitializeTempDirSetting();
4674
        $tempnam                           = $this->realPathSafe(tempnam($this->config_temp_directory, 'pThumb'));
4675
        $this->tempFilesToDelete[$tempnam] = $tempnam;
4676
        $this->DebugMessage('phpThumb_tempnam() returning "' . $tempnam . '"', __FILE__, __LINE__);
4677
4678
        return $tempnam;
4679
    }
4680
4681
    /**
4682
     * @param        $message
4683
     * @param string $file
4684
     * @param string $line
4685
     * @return bool
4686
     */
4687
    public function DebugMessage($message, $file = '', $line = '')
4688
    {
4689
        $this->debugmessages[] = $message . ($file ? ' in file "' . (basename($file) ?: $file) . '"' : '') . ($line ? ' on line ' . $line : '');
4690
4691
        return true;
4692
    }
4693
4694
    /**
4695
     * @param        $message
4696
     * @param string $file
4697
     * @param string $line
4698
     * @param int    $timestamp
4699
     * @return bool
4700
     */
4701
    public function DebugTimingMessage($message, $file = '', $line = '', $timestamp = 0)
4702
    {
4703
        if (!$timestamp) {
4704
            $timestamp = array_sum(explode(' ', microtime()));
4705
        }
4706
        $this->debugtiming[number_format($timestamp, 6, '.', '')] = ': ' . $message . ($file ? ' in file "' . (basename($file) ?: $file) . '"' : '') . ($line ? ' on line ' . $line : '');
4707
4708
        return true;
4709
    }
4710
}
4711