Completed
Push — 079 ( 1dc04e...c009f4 )
by Mikael
06:56 queued 03:18
created

webroot/img.php (7 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 14 and the first side effect is on line 11.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Resize and crop images on the fly, store generated images in a cache.
4
 *
5
 * @author  Mikael Roos [email protected]
6
 * @example http://dbwebb.se/opensource/cimage
7
 * @link    https://github.com/mosbth/cimage
8
 *
9
 */
10
11
$version = "v0.7.8* (2015-12-06)";
12
13
// For CRemoteImage
14
define("CIMAGE_USER_AGENT", "CImage/$version");
15
16
17
18
/**
19
 * Display error message.
20
 *
21
 * @param string $msg to display.
22
 * @param int $type of HTTP error to display.
23
 *
24
 * @return void
25
 */
26
function errorPage($msg, $type = 500)
27
{
28
    global $mode;
29
30
    switch ($type) {
31
        case 403:
32
            $header = "403 Forbidden";
33
        break;
34
        case 404:
35
            $header = "404 Not Found";
36
        break;
37
        default:
38
            $header = "500 Internal Server Error";
39
    }
40
41
    header("HTTP/1.0 $header");
42
43
    if ($mode == 'development') {
44
        die("[img.php] $msg");
45
    }
46
47
    error_log("[img.php] $msg");
48
    die("HTTP/1.0 $header");
49
}
50
51
52
53
/**
54
 * Custom exception handler.
55
 */
56
set_exception_handler(function ($exception) {
57
    errorPage(
58
        "<p><b>img.php: Uncaught exception:</b> <p>"
59
        . $exception->getMessage()
60
        . "</p><pre>"
61
        . $exception->getTraceAsString()
62
        . "</pre>"
63
    , 500);
64
});
65
66
67
68
/**
69
 * Get input from query string or return default value if not set.
70
 *
71
 * @param mixed $key     as string or array of string values to look for in $_GET.
72
 * @param mixed $default value to return when $key is not set in $_GET.
73
 *
74
 * @return mixed value from $_GET or default value.
75
 */
76
function get($key, $default = null)
77
{
78
    if (is_array($key)) {
79
        foreach ($key as $val) {
80
            if (isset($_GET[$val])) {
81
                return $_GET[$val];
82
            }
83
        }
84
    } elseif (isset($_GET[$key])) {
85
        return $_GET[$key];
86
    }
87
    return $default;
88
}
89
90
91
92
/**
93
 * Get input from query string and set to $defined if defined or else $undefined.
94
 *
95
 * @param mixed $key       as string or array of string values to look for in $_GET.
96
 * @param mixed $defined   value to return when $key is set in $_GET.
97
 * @param mixed $undefined value to return when $key is not set in $_GET.
98
 *
99
 * @return mixed value as $defined or $undefined.
100
 */
101
function getDefined($key, $defined, $undefined)
102
{
103
    return get($key) === null ? $undefined : $defined;
104
}
105
106
107
108
/**
109
 * Get value from config array or default if key is not set in config array.
110
 *
111
 * @param string $key    the key in the config array.
112
 * @param mixed $default value to be default if $key is not set in config.
113
 *
114
 * @return mixed value as $config[$key] or $default.
115
 */
116
function getConfig($key, $default)
117
{
118
    global $config;
119
    return isset($config[$key])
120
        ? $config[$key]
121
        : $default;
122
}
123
124
125
126
/**
127
 * Log when verbose mode, when used without argument it returns the result.
128
 *
129
 * @param string $msg to log.
130
 *
131
 * @return void or array.
132
 */
133
function verbose($msg = null)
134
{
135
    global $verbose, $verboseFile;
136
    static $log = array();
137
138
    if (!($verbose || $verboseFile)) {
139
        return;
140
    }
141
142
    if (is_null($msg)) {
143
        return $log;
144
    }
145
146
    $log[] = $msg;
147
}
148
149
150
151
/**
152
 * Get configuration options from file, if the file exists, else use $config
153
 * if its defined or create an empty $config.
154
 */
155
$configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php';
156
157
if (is_file($configFile)) {
158
    $config = require $configFile;
159
} elseif (!isset($config)) {
160
    $config = array();
161
}
162
163
164
165
/**
166
* verbose, v - do a verbose dump of what happens
167
* vf - do verbose dump to file
168
*/
169
$verbose = getDefined(array('verbose', 'v'), true, false);
170
$verboseFile = getDefined('vf', true, false);
171
verbose("img.php version = $version");
172
173
174
175
/**
176
* status - do a verbose dump of the configuration
177
*/
178
$status = getDefined('status', true, false);
179
180
181
182
/**
183
 * Set mode as strict, production or development.
184
 * Default is production environment.
185
 */
186
$mode = getConfig('mode', 'production');
187
188
// Settings for any mode
189
set_time_limit(20);
190
ini_set('gd.jpeg_ignore_warning', 1);
191
192
if (!extension_loaded('gd')) {
193
    errorPage("Extension gd is not loaded.", 500);
194
}
195
196
// Specific settings for each mode
197
if ($mode == 'strict') {
198
199
    error_reporting(0);
200
    ini_set('display_errors', 0);
201
    ini_set('log_errors', 1);
202
    $verbose = false;
203
    $status = false;
204
    $verboseFile = false;
205
206
} elseif ($mode == 'production') {
207
208
    error_reporting(-1);
209
    ini_set('display_errors', 0);
210
    ini_set('log_errors', 1);
211
    $verbose = false;
212
    $status = false;
213
    $verboseFile = false;
214
215
} elseif ($mode == 'development') {
216
217
    error_reporting(-1);
218
    ini_set('display_errors', 1);
219
    ini_set('log_errors', 0);
220
    $verboseFile = false;
221
222
} elseif ($mode == 'test') {
223
224
    error_reporting(-1);
225
    ini_set('display_errors', 1);
226
    ini_set('log_errors', 0);
227
228
} else {
229
    errorPage("Unknown mode: $mode", 500);
230
}
231
232
verbose("mode = $mode");
233
verbose("error log = " . ini_get('error_log'));
234
235
236
237
/**
238
 * Set default timezone if not set or if its set in the config-file.
239
 */
240
$defaultTimezone = getConfig('default_timezone', null);
241
242
if ($defaultTimezone) {
243
    date_default_timezone_set($defaultTimezone);
244
} elseif (!ini_get('default_timezone')) {
245
    date_default_timezone_set('UTC');
246
}
247
248
249
250
/**
251
 * Check if passwords are configured, used and match.
252
 * Options decide themself if they require passwords to be used.
253
 */
254
$pwdConfig   = getConfig('password', false);
255
$pwdAlways   = getConfig('password_always', false);
256
$pwdType     = getConfig('password_type', 'text');
257
$pwd         = get(array('password', 'pwd'), null);
258
259
// Check if passwords match, if configured to use passwords
260
$passwordMatch = null;
261
if ($pwd) {
262
    switch($pwdType) {
263
        case 'md5':
264
            $passwordMatch = ($pwdConfig === md5($pwd));
265
            break;
266
        case 'hash':
267
            $passwordMatch = password_verify($pwd, $pwdConfig);
268
            break;
269
        case 'text':
270
            $passwordMatch = ($pwdConfig === $pwd);
271
            break;
272
        default:
273
            $passwordMatch = false;
274
    }
275
}
276
277
if ($pwdAlways && $passwordMatch !== true) {
278
    errorPage("Password required and does not match or exists.", 403);
279
}
280
281
verbose("password match = $passwordMatch");
282
283
284
285
/**
286
 * Prevent hotlinking, leeching, of images by controlling who access them
287
 * from where.
288
 *
289
 */
290
$allowHotlinking = getConfig('allow_hotlinking', true);
291
$hotlinkingWhitelist = getConfig('hotlinking_whitelist', array());
292
293
$serverName  = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
294
$referer     = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
295
$refererHost = parse_url($referer, PHP_URL_HOST);
296
297
if (!$allowHotlinking) {
298
    if ($passwordMatch) {
299
        ; // Always allow when password match
300
        verbose("Hotlinking since passwordmatch");
301
    } elseif ($passwordMatch === false) {
302
        errorPage("Hotlinking/leeching not allowed when password missmatch.", 403);
303
    } elseif (!$referer) {
304
        errorPage("Hotlinking/leeching not allowed and referer is missing.", 403);
305
    } elseif (strcmp($serverName, $refererHost) == 0) {
306
        ; // Allow when serverName matches refererHost
307
        verbose("Hotlinking disallowed but serverName matches refererHost.");
308
    } elseif (!empty($hotlinkingWhitelist)) {
309
        $whitelist = new CWhitelist();
310
        $allowedByWhitelist = $whitelist->check($refererHost, $hotlinkingWhitelist);
0 ignored issues
show
It seems like $refererHost defined by parse_url($referer, PHP_URL_HOST) on line 295 can also be of type false; however, CWhitelist::check() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
311
312
        if ($allowedByWhitelist) {
313
            verbose("Hotlinking/leeching allowed by whitelist.");
314
        } else {
315
            errorPage("Hotlinking/leeching not allowed by whitelist. Referer: $referer.", 403);
316
        }
317
318
    } else {
319
        errorPage("Hotlinking/leeching not allowed.", 403);
320
    }
321
}
322
323
verbose("allow_hotlinking = $allowHotlinking");
324
verbose("referer = $referer");
325
verbose("referer host = $refererHost");
326
327
328
329
/**
330
 * Get the source files.
331
 */
332
$autoloader  = getConfig('autoloader', false);
333
$cimageClass = getConfig('cimage_class', false);
334
335
if ($autoloader) {
336
    require $autoloader;
337
} elseif ($cimageClass) {
338
    require $cimageClass;
339
}
340
341
342
343
/**
344
 * Create the class for the image.
345
 */
346
$img = new CImage();
347
$img->setVerbose($verbose || $verboseFile);
348
349
350
351
/**
352
 * Get the cachepath from config.
353
 */
354
$cachePath = getConfig('cache_path', __DIR__ . '/../cache/');
355
$cache = new CCache();
356
$cache->setDir($cachePath);
357
358
359
360
361
/**
362
 * Allow or disallow remote download of images from other servers.
363
 * Passwords apply if used.
364
 *
365
 */
366
$allowRemote = getConfig('remote_allow', false);
367
368
if ($allowRemote && $passwordMatch !== false) {
369
    $cacheRemote = $cache->getPathToSubdir("remote");
370
    
371
    $pattern = getConfig('remote_pattern', null);
372
    $img->setRemoteDownload($allowRemote, $cacheRemote, $pattern);
0 ignored issues
show
It seems like $cacheRemote defined by $cache->getPathToSubdir('remote') on line 369 can also be of type false; however, CImage::setRemoteDownload() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
373
374
    $whitelist = getConfig('remote_whitelist', null);
375
    $img->setRemoteHostWhitelist($whitelist);
376
}
377
378
379
380
/**
381
 * shortcut, sc - extend arguments with a constant value, defined
382
 * in config-file.
383
 */
384
$shortcut       = get(array('shortcut', 'sc'), null);
385
$shortcutConfig = getConfig('shortcut', array(
386
    'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen",
387
));
388
389
verbose("shortcut = $shortcut");
390
391
if (isset($shortcut)
392
    && isset($shortcutConfig[$shortcut])) {
393
394
    parse_str($shortcutConfig[$shortcut], $get);
395
    verbose("shortcut-constant = {$shortcutConfig[$shortcut]}");
396
    $_GET = array_merge($_GET, $get);
397
}
398
399
400
401
/**
402
 * src - the source image file.
403
 */
404
$srcImage = urldecode(get('src'))
405
    or errorPage('Must set src-attribute.', 404);
406
407
// Check for valid/invalid characters
408
$imagePath           = getConfig('image_path', __DIR__ . '/img/');
409
$imagePathConstraint = getConfig('image_path_constraint', true);
410
$validFilename       = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#');
411
412
// Dummy image feature
413
$dummyEnabled  = getConfig('dummy_enabled', true);
414
$dummyFilename = getConfig('dummy_filename', 'dummy');
415
$dummyImage = false;
416
417
preg_match($validFilename, $srcImage)
418
    or errorPage('Filename contains invalid characters.', 404);
419
420
if ($dummyEnabled && $srcImage === $dummyFilename) {
421
422
    // Prepare to create a dummy image and use it as the source image.
423
    $dummyImage = true;
424
425
} elseif ($allowRemote && $img->isRemoteSource($srcImage)) {
426
427
    // If source is a remote file, ignore local file checks.
428
429
} elseif ($imagePathConstraint) {
430
431
    // Check that the image is a file below the directory 'image_path'.
432
    $pathToImage = realpath($imagePath . $srcImage);
433
    $imageDir    = realpath($imagePath);
434
435
    is_file($pathToImage)
436
        or errorPage(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
437
            'Source image is not a valid file, check the filename and that a
438
            matching file exists on the filesystem.'
439
        , 404);
440
441
    substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0
442
        or errorPage(
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
443
            'Security constraint: Source image is not below the directory "image_path"
444
            as specified in the config file img_config.php.'
445
        , 404);
446
}
447
448
verbose("src = $srcImage");
449
450
451
452
/**
453
 * Manage size constants from config file, use constants to replace values
454
 * for width and height.
455
 */
456
$sizeConstant = getConfig('size_constant', function () {
457
458
    // Set sizes to map constant to value, easier to use with width or height
459
    $sizes = array(
460
        'w1' => 613,
461
        'w2' => 630,
462
    );
463
464
    // Add grid column width, useful for use as predefined size for width (or height).
465
    $gridColumnWidth = 30;
466
    $gridGutterWidth = 10;
467
    $gridColumns     = 24;
468
469
    for ($i = 1; $i <= $gridColumns; $i++) {
470
        $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth;
471
    }
472
473
    return $sizes;
474
});
475
476
$sizes = call_user_func($sizeConstant);
477
478
479
480
/**
481
 * width, w - set target width, affecting the resulting image width, height and resize options
482
 */
483
$newWidth     = get(array('width', 'w'));
484
$maxWidth     = getConfig('max_width', 2000);
485
486
// Check to replace predefined size
487
if (isset($sizes[$newWidth])) {
488
    $newWidth = $sizes[$newWidth];
489
}
490
491
// Support width as % of original width
492
if ($newWidth[strlen($newWidth)-1] == '%') {
493
    is_numeric(substr($newWidth, 0, -1))
494
        or errorPage('Width % not numeric.', 404);
495
} else {
496
    is_null($newWidth)
497
        or ($newWidth > 10 && $newWidth <= $maxWidth)
498
        or errorPage('Width out of range.', 404);
499
}
500
501
verbose("new width = $newWidth");
502
503
504
505
/**
506
 * height, h - set target height, affecting the resulting image width, height and resize options
507
 */
508
$newHeight = get(array('height', 'h'));
509
$maxHeight = getConfig('max_height', 2000);
510
511
// Check to replace predefined size
512
if (isset($sizes[$newHeight])) {
513
    $newHeight = $sizes[$newHeight];
514
}
515
516
// height
517
if ($newHeight[strlen($newHeight)-1] == '%') {
518
    is_numeric(substr($newHeight, 0, -1))
519
        or errorPage('Height % out of range.', 404);
520
} else {
521
    is_null($newHeight)
522
        or ($newHeight > 10 && $newHeight <= $maxHeight)
523
        or errorPage('Height out of range.', 404);
524
}
525
526
verbose("new height = $newHeight");
527
528
529
530
/**
531
 * aspect-ratio, ar - affecting the resulting image width, height and resize options
532
 */
533
$aspectRatio         = get(array('aspect-ratio', 'ar'));
534
$aspectRatioConstant = getConfig('aspect_ratio_constant', function () {
535
    return array(
536
        '3:1'    => 3/1,
537
        '3:2'    => 3/2,
538
        '4:3'    => 4/3,
539
        '8:5'    => 8/5,
540
        '16:10'  => 16/10,
541
        '16:9'   => 16/9,
542
        'golden' => 1.618,
543
    );
544
});
545
546
// Check to replace predefined aspect ratio
547
$aspectRatios = call_user_func($aspectRatioConstant);
548
$negateAspectRatio = ($aspectRatio[0] == '!') ? true : false;
549
$aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio;
550
551
if (isset($aspectRatios[$aspectRatio])) {
552
    $aspectRatio = $aspectRatios[$aspectRatio];
553
}
554
555
if ($negateAspectRatio) {
556
    $aspectRatio = 1 / $aspectRatio;
557
}
558
559
is_null($aspectRatio)
560
    or is_numeric($aspectRatio)
561
    or errorPage('Aspect ratio out of range', 404);
562
563
verbose("aspect ratio = $aspectRatio");
564
565
566
567
/**
568
 * crop-to-fit, cf - affecting the resulting image width, height and resize options
569
 */
570
$cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false);
571
572
verbose("crop to fit = $cropToFit");
573
574
575
576
/**
577
 * Set default background color from config file.
578
 */
579
$backgroundColor = getConfig('background_color', null);
580
581
if ($backgroundColor) {
582
    $img->setDefaultBackgroundColor($backgroundColor);
583
    verbose("Using default background_color = $backgroundColor");
584
}
585
586
587
588
/**
589
 * bgColor - Default background color to use
590
 */
591
$bgColor = get(array('bgColor', 'bg-color', 'bgc'), null);
592
593
verbose("bgColor = $bgColor");
594
595
596
597
/**
598
 * Do or do not resample image when resizing.
599
 */
600
$resizeStrategy = getDefined(array('no-resample'), true, false);
601
602
if ($resizeStrategy) {
603
    $img->setCopyResizeStrategy($img::RESIZE);
604
    verbose("Setting = Resize instead of resample");
605
}
606
607
608
609
610
/**
611
 * fill-to-fit, ff - affecting the resulting image width, height and resize options
612
 */
613
$fillToFit = get(array('fill-to-fit', 'ff'), null);
614
615
verbose("fill-to-fit = $fillToFit");
616
617
if ($fillToFit !== null) {
618
619
    if (!empty($fillToFit)) {
620
        $bgColor   = $fillToFit;
621
        verbose("fillToFit changed bgColor to = $bgColor");
622
    }
623
624
    $fillToFit = true;
625
    verbose("fill-to-fit (fixed) = $fillToFit");
626
}
627
628
629
630
/**
631
 * no-ratio, nr, stretch - affecting the resulting image width, height and resize options
632
 */
633
$keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true);
634
635
verbose("keep ratio = $keepRatio");
636
637
638
639
/**
640
 * crop, c - affecting the resulting image width, height and resize options
641
 */
642
$crop = get(array('crop', 'c'));
643
644
verbose("crop = $crop");
645
646
647
648
/**
649
 * area, a - affecting the resulting image width, height and resize options
650
 */
651
$area = get(array('area', 'a'));
652
653
verbose("area = $area");
654
655
656
657
/**
658
 * skip-original, so - skip the original image and always process a new image
659
 */
660
$useOriginal = getDefined(array('skip-original', 'so'), false, true);
661
$useOriginalDefault = getConfig('skip_original', false);
662
663
if ($useOriginalDefault === true) {
664
    verbose("use original is default ON");
665
    $useOriginal = true;
666
}
667
668
verbose("use original = $useOriginal");
669
670
671
672
/**
673
 * no-cache, nc - skip the cached version and process and create a new version in cache.
674
 */
675
$useCache = getDefined(array('no-cache', 'nc'), false, true);
676
677
verbose("use cache = $useCache");
678
679
680
681
/**
682
 * quality, q - set level of quality for jpeg images
683
 */
684
$quality = get(array('quality', 'q'));
685
$qualityDefault = getConfig('jpg_quality', null);
686
687
is_null($quality)
688
    or ($quality > 0 and $quality <= 100)
689
    or errorPage('Quality out of range', 404);
690
691
if (is_null($quality) && !is_null($qualityDefault)) {
692
    $quality = $qualityDefault;
693
}
694
695
verbose("quality = $quality");
696
697
698
699
/**
700
 * compress, co - what strategy to use when compressing png images
701
 */
702
$compress = get(array('compress', 'co'));
703
$compressDefault = getConfig('png_compression', null);
704
705
is_null($compress)
706
    or ($compress > 0 and $compress <= 9)
707
    or errorPage('Compress out of range', 404);
708
709
if (is_null($compress) && !is_null($compressDefault)) {
710
    $compress = $compressDefault;
711
}
712
713
verbose("compress = $compress");
714
715
716
717
/**
718
 * save-as, sa - what type of image to save
719
 */
720
$saveAs = get(array('save-as', 'sa'));
721
722
verbose("save as = $saveAs");
723
724
725
726
/**
727
 * scale, s - Processing option, scale up or down the image prior actual resize
728
 */
729
$scale = get(array('scale', 's'));
730
731
is_null($scale)
732
    or ($scale >= 0 and $scale <= 400)
733
    or errorPage('Scale out of range', 404);
734
735
verbose("scale = $scale");
736
737
738
739
/**
740
 * palette, p - Processing option, create a palette version of the image
741
 */
742
$palette = getDefined(array('palette', 'p'), true, false);
743
744
verbose("palette = $palette");
745
746
747
748
/**
749
 * sharpen - Processing option, post filter for sharpen effect
750
 */
751
$sharpen = getDefined('sharpen', true, null);
752
753
verbose("sharpen = $sharpen");
754
755
756
757
/**
758
 * emboss - Processing option, post filter for emboss effect
759
 */
760
$emboss = getDefined('emboss', true, null);
761
762
verbose("emboss = $emboss");
763
764
765
766
/**
767
 * blur - Processing option, post filter for blur effect
768
 */
769
$blur = getDefined('blur', true, null);
770
771
verbose("blur = $blur");
772
773
774
775
/**
776
 * rotateBefore - Rotate the image with an angle, before processing
777
 */
778
$rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb'));
779
780
is_null($rotateBefore)
781
    or ($rotateBefore >= -360 and $rotateBefore <= 360)
782
    or errorPage('RotateBefore out of range', 404);
783
784
verbose("rotateBefore = $rotateBefore");
785
786
787
788
/**
789
 * rotateAfter - Rotate the image with an angle, before processing
790
 */
791
$rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r'));
792
793
is_null($rotateAfter)
794
    or ($rotateAfter >= -360 and $rotateAfter <= 360)
795
    or errorPage('RotateBefore out of range', 404);
796
797
verbose("rotateAfter = $rotateAfter");
798
799
800
801
/**
802
 * autoRotate - Auto rotate based on EXIF information
803
 */
804
$autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false);
805
806
verbose("autoRotate = $autoRotate");
807
808
809
810
/**
811
 * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter()
812
 */
813
$filters = array();
814
$filter = get(array('filter', 'f'));
815
if ($filter) {
816
    $filters[] = $filter;
817
}
818
819
for ($i = 0; $i < 10; $i++) {
820
    $filter = get(array("filter{$i}", "f{$i}"));
821
    if ($filter) {
822
        $filters[] = $filter;
823
    }
824
}
825
826
verbose("filters = " . print_r($filters, 1));
827
828
829
830
/**
831
* json -  output the image as a JSON object with details on the image.
832
* ascii - output the image as ASCII art.
833
 */
834
$outputFormat = getDefined('json', 'json', null);
835
$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
836
837
verbose("outputformat = $outputFormat");
838
839
if ($outputFormat == 'ascii') {
840
    $defaultOptions = getConfig(
841
        'ascii-options',
842
        array(
843
            "characterSet" => 'two',
844
            "scale" => 14,
845
            "luminanceStrategy" => 3,
846
            "customCharacterSet" => null,
847
        )
848
    );
849
    $options = get('ascii');
850
    $options = explode(',', $options);
851
852
    if (isset($options[0]) && !empty($options[0])) {
853
        $defaultOptions['characterSet'] = $options[0];
854
    }
855
856
    if (isset($options[1]) && !empty($options[1])) {
857
        $defaultOptions['scale'] = $options[1];
858
    }
859
860
    if (isset($options[2]) && !empty($options[2])) {
861
        $defaultOptions['luminanceStrategy'] = $options[2];
862
    }
863
864
    if (count($options) > 3) {
865
        // Last option is custom character string
866
        unset($options[0]);
867
        unset($options[1]);
868
        unset($options[2]);
869
        $characterString = implode($options);
870
        $defaultOptions['customCharacterSet'] = $characterString;
871
    }
872
873
    $img->setAsciiOptions($defaultOptions);
874
}
875
876
877
878
879
/**
880
 * dpr - change to get larger image to easier support larger dpr, such as retina.
881
 */
882
$dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1);
883
884
verbose("dpr = $dpr");
885
886
887
888
/**
889
 * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php
890
 */
891
$convolve = get('convolve', null);
892
$convolutionConstant = getConfig('convolution_constant', array());
893
894
// Check if the convolve is matching an existing constant
895
if ($convolve && isset($convolutionConstant)) {
896
    $img->addConvolveExpressions($convolutionConstant);
897
    verbose("convolve constant = " . print_r($convolutionConstant, 1));
898
}
899
900
verbose("convolve = " . print_r($convolve, 1));
901
902
903
904
/**
905
 * no-upscale, nu - Do not upscale smaller image to larger dimension.
906
 */
907
$upscale = getDefined(array('no-upscale', 'nu'), false, true);
908
909
verbose("upscale = $upscale");
910
911
912
913
/**
914
 * Get details for post processing
915
 */
916
$postProcessing = getConfig('postprocessing', array(
917
    'png_filter'        => false,
918
    'png_filter_cmd'    => '/usr/local/bin/optipng -q',
919
920
    'png_deflate'       => false,
921
    'png_deflate_cmd'   => '/usr/local/bin/pngout -q',
922
923
    'jpeg_optimize'     => false,
924
    'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize',
925
));
926
927
928
929
/**
930
 * alias - Save resulting image to another alias name.
931
 * Password always apply, must be defined.
932
 */
933
$alias          = get('alias', null);
934
$aliasPath      = getConfig('alias_path', null);
935
$validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#');
936
$aliasTarget    = null;
937
938
if ($alias && $aliasPath && $passwordMatch) {
939
940
    $aliasTarget = $aliasPath . $alias;
941
    $useCache    = false;
942
943
    is_writable($aliasPath)
944
        or errorPage("Directory for alias is not writable.", 403);
945
946
    preg_match($validAliasname, $alias)
947
        or errorPage('Filename for alias contains invalid characters. Do not add extension.', 404);
948
949
} elseif ($alias) {
950
    errorPage('Alias is not enabled in the config file or password not matching.', 403);
951
}
952
953
verbose("alias = $alias");
954
955
956
957
/**
958
 * Add cache control HTTP header.
959
 */
960
$cacheControl = getConfig('cache_control', null);
961
962
if ($cacheControl) {
963
    verbose("cacheControl = $cacheControl");
964
    $img->addHTTPHeader("Cache-Control", $cacheControl);
965
}
966
967
968
969
/**
970
 * Prepare a dummy image and use it as source image.
971
 */
972
if ($dummyImage === true) {
973
    $dummyDir = $cache->getPathToSubdir("dummy");
974
975
    $img->setSaveFolder($dummyDir)
0 ignored issues
show
It seems like $dummyDir defined by $cache->getPathToSubdir('dummy') on line 973 can also be of type false; however, CImage::setSaveFolder() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
976
        ->setSource($dummyFilename, $dummyDir)
0 ignored issues
show
It seems like $dummyDir defined by $cache->getPathToSubdir('dummy') on line 973 can also be of type false; however, CImage::setSource() does only seem to accept string|null, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
977
        ->setOptions(
978
            array(
979
                'newWidth'  => $newWidth,
980
                'newHeight' => $newHeight,
981
                'bgColor'   => $bgColor,
982
            )
983
        )
984
        ->setJpegQuality($quality)
985
        ->setPngCompression($compress)
986
        ->createDummyImage()
987
        ->generateFilename(null, false)
988
        ->save(null, null, false);
989
990
    $srcImage = $img->getTarget();
991
    $imagePath = null;
992
993
    verbose("src (updated) = $srcImage");
994
}
995
996
997
998
/**
999
 * Prepare a sRGB version of the image and use it as source image.
1000
 */
1001
$srgbDefault = getConfig('srgb_default', false);
1002
$srgbColorProfile = getConfig('srgb_colorprofile', __DIR__ . '/../icc/sRGB_IEC61966-2-1_black_scaled.icc');
1003
$srgb = getDefined('srgb', true, null);
1004
1005
if ($srgb || $srgbDefault) {
1006
1007
    $filename = $img->convert2sRGBColorSpace(
1008
        $srcImage,
1009
        $imagePath,
1010
        $cache->getPathToSubdir("srgb"),
1011
        $srgbColorProfile,
1012
        $useCache
1013
    );
1014
1015
    if ($filename) {
1016
        $srcImage = $img->getTarget();
1017
        $imagePath = null;
1018
        verbose("srgb conversion and saved to cache = $srcImage");
1019
    } else {
1020
        verbose("srgb not op");
1021
    }
1022
}
1023
1024
1025
1026
/**
1027
 * Display status
1028
 */
1029
if ($status) {
1030
    $text  = "img.php version = $version\n";
1031
    $text .= "PHP version = " . PHP_VERSION . "\n";
1032
    $text .= "Running on: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
1033
    $text .= "Allow remote images = $allowRemote\n";
1034
1035
    $res = $cache->getStatusOfSubdir("");
1036
    $text .= "Cache $res\n";
1037
1038
    $res = $cache->getStatusOfSubdir("remote");
1039
    $text .= "Cache remote $res\n";
1040
1041
    $res = $cache->getStatusOfSubdir("dummy");
1042
    $text .= "Cache dummy $res\n";
1043
1044
    $res = $cache->getStatusOfSubdir("srgb");
1045
    $text .= "Cache srgb $res\n";
1046
1047
    $text .= "Alias path writable = " . is_writable($aliasPath) . "\n";
1048
1049
    $no = extension_loaded('exif') ? null : 'NOT';
1050
    $text .= "Extension exif is $no loaded.<br>";
1051
1052
    $no = extension_loaded('curl') ? null : 'NOT';
1053
    $text .= "Extension curl is $no loaded.<br>";
1054
1055
    $no = extension_loaded('imagick') ? null : 'NOT';
1056
    $text .= "Extension imagick is $no loaded.<br>";
1057
1058
    $no = extension_loaded('gd') ? null : 'NOT';
1059
    $text .= "Extension gd is $no loaded.<br>";
1060
1061
    if (!$no) {
1062
        $text .= print_r(gd_info(), 1);
1063
    }
1064
1065
    echo <<<EOD
1066
<!doctype html>
1067
<html lang=en>
1068
<meta charset=utf-8>
1069
<title>CImage status</title>
1070
<pre>$text</pre>
1071
EOD;
1072
    exit;
1073
}
1074
1075
1076
1077
/**
1078
 * Log verbose details to file
1079
 */
1080
if ($verboseFile) {
1081
    $img->setVerboseToFile("$cachePath/log.txt");
1082
}
1083
1084
1085
1086
/**
1087
 * Hook after img.php configuration and before processing with CImage
1088
 */
1089
$hookBeforeCImage = getConfig('hook_before_CImage', null);
1090
1091
if (is_callable($hookBeforeCImage)) {
1092
    verbose("hookBeforeCImage activated");
1093
1094
    $allConfig = $hookBeforeCImage($img, array(
1095
            // Options for calculate dimensions
1096
            'newWidth'  => $newWidth,
1097
            'newHeight' => $newHeight,
1098
            'aspectRatio' => $aspectRatio,
1099
            'keepRatio' => $keepRatio,
1100
            'cropToFit' => $cropToFit,
1101
            'fillToFit' => $fillToFit,
1102
            'crop'      => $crop,
1103
            'area'      => $area,
1104
            'upscale'   => $upscale,
1105
1106
            // Pre-processing, before resizing is done
1107
            'scale'        => $scale,
1108
            'rotateBefore' => $rotateBefore,
1109
            'autoRotate'   => $autoRotate,
1110
1111
            // General processing options
1112
            'bgColor'    => $bgColor,
1113
1114
            // Post-processing, after resizing is done
1115
            'palette'   => $palette,
1116
            'filters'   => $filters,
1117
            'sharpen'   => $sharpen,
1118
            'emboss'    => $emboss,
1119
            'blur'      => $blur,
1120
            'convolve'  => $convolve,
1121
            'rotateAfter' => $rotateAfter,
1122
1123
            // Output format
1124
            'outputFormat' => $outputFormat,
1125
            'dpr'          => $dpr,
1126
1127
            // Other
1128
            'postProcessing' => $postProcessing,
1129
    ));
1130
    verbose(print_r($allConfig, 1));
1131
    extract($allConfig);
1132
}
1133
1134
1135
1136
/**
1137
 * Display image if verbose mode
1138
 */
1139
if ($verbose) {
1140
    $query = array();
1141
    parse_str($_SERVER['QUERY_STRING'], $query);
1142
    unset($query['verbose']);
1143
    unset($query['v']);
1144
    unset($query['nocache']);
1145
    unset($query['nc']);
1146
    unset($query['json']);
1147
    $url1 = '?' . htmlentities(urldecode(http_build_query($query)));
1148
    $url2 = '?' . urldecode(http_build_query($query));
1149
    echo <<<EOD
1150
<!doctype html>
1151
<html lang=en>
1152
<meta charset=utf-8>
1153
<title>CImage verbose output</title>
1154
<style>body{background-color: #ddd}</style>
1155
<a href=$url1><code>$url1</code></a><br>
1156
<img src='{$url1}' />
1157
<pre id="json"></pre>
1158
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
1159
<script type="text/javascript">
1160
window.getDetails = function (url, id) {
1161
  $.getJSON(url, function(data) {
1162
    element = document.getElementById(id);
1163
    element.innerHTML = "filename: " + data.filename + "\\nmime type: " + data.mimeType + "\\ncolors: " + data.colors + "\\nsize: " + data.size + "\\nwidth: " + data.width + "\\nheigh: " + data.height + "\\naspect-ratio: " + data.aspectRatio + ( data.pngType ? "\\npng-type: " + data.pngType : '');
1164
  });
1165
}
1166
</script>
1167
<script type="text/javascript">window.getDetails("{$url2}&json", "json")</script>
1168
EOD;
1169
}
1170
1171
1172
1173
/**
1174
 * Load, process and output the image
1175
 */
1176
$img->log("Incoming arguments: " . print_r(verbose(), 1))
1177
    ->setSaveFolder($cachePath)
1178
    ->useCache($useCache)
1179
    ->setSource($srcImage, $imagePath)
1180
    ->setOptions(
1181
        array(
1182
            // Options for calculate dimensions
1183
            'newWidth'  => $newWidth,
1184
            'newHeight' => $newHeight,
1185
            'aspectRatio' => $aspectRatio,
1186
            'keepRatio' => $keepRatio,
1187
            'cropToFit' => $cropToFit,
1188
            'fillToFit' => $fillToFit,
1189
            'crop'      => $crop,
1190
            'area'      => $area,
1191
            'upscale'   => $upscale,
1192
1193
            // Pre-processing, before resizing is done
1194
            'scale'        => $scale,
1195
            'rotateBefore' => $rotateBefore,
1196
            'autoRotate'   => $autoRotate,
1197
1198
            // General processing options
1199
            'bgColor'    => $bgColor,
1200
1201
            // Post-processing, after resizing is done
1202
            'palette'   => $palette,
1203
            'filters'   => $filters,
1204
            'sharpen'   => $sharpen,
1205
            'emboss'    => $emboss,
1206
            'blur'      => $blur,
1207
            'convolve'  => $convolve,
1208
            'rotateAfter' => $rotateAfter,
1209
1210
            // Output format
1211
            'outputFormat' => $outputFormat,
1212
            'dpr'          => $dpr,
1213
        )
1214
    )
1215
    ->loadImageDetails()
1216
    ->initDimensions()
1217
    ->calculateNewWidthAndHeight()
1218
    ->setSaveAsExtension($saveAs)
1219
    ->setJpegQuality($quality)
1220
    ->setPngCompression($compress)
1221
    ->useOriginalIfPossible($useOriginal)
1222
    ->generateFilename($cachePath)
1223
    ->useCacheIfPossible($useCache)
1224
    ->load()
1225
    ->preResize()
1226
    ->resize()
1227
    ->postResize()
1228
    ->setPostProcessingOptions($postProcessing)
1229
    ->save()
1230
    ->linkToCacheFile($aliasTarget)
1231
    ->output();
1232