Completed
Push — 079 ( c009f4...b069e3 )
by Mikael
04:32
created

webroot/img.php (4 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * 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.9 (2015-12-07)";
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
    if ($mode == "strict") {
48
        $header = "404 Not Found";
49
    }
50
51
    error_log("[img.php] $msg");
52
    die("HTTP/1.0 $header");
53
}
54
55
56
57
/**
58
 * Custom exception handler.
59
 */
60
set_exception_handler(function ($exception) {
61
    errorPage(
62
        "<p><b>img.php: Uncaught exception:</b> <p>"
63
        . $exception->getMessage()
64
        . "</p><pre>"
65
        . $exception->getTraceAsString()
66
        . "</pre>",
67
        500
68
    );
69
});
70
71
72
73
/**
74
 * Get input from query string or return default value if not set.
75
 *
76
 * @param mixed $key     as string or array of string values to look for in $_GET.
77
 * @param mixed $default value to return when $key is not set in $_GET.
78
 *
79
 * @return mixed value from $_GET or default value.
80
 */
81
function get($key, $default = null)
82
{
83
    if (is_array($key)) {
84
        foreach ($key as $val) {
85
            if (isset($_GET[$val])) {
86
                return $_GET[$val];
87
            }
88
        }
89
    } elseif (isset($_GET[$key])) {
90
        return $_GET[$key];
91
    }
92
    return $default;
93
}
94
95
96
97
/**
98
 * Get input from query string and set to $defined if defined or else $undefined.
99
 *
100
 * @param mixed $key       as string or array of string values to look for in $_GET.
101
 * @param mixed $defined   value to return when $key is set in $_GET.
102
 * @param mixed $undefined value to return when $key is not set in $_GET.
103
 *
104
 * @return mixed value as $defined or $undefined.
105
 */
106
function getDefined($key, $defined, $undefined)
107
{
108
    return get($key) === null ? $undefined : $defined;
109
}
110
111
112
113
/**
114
 * Get value from config array or default if key is not set in config array.
115
 *
116
 * @param string $key    the key in the config array.
117
 * @param mixed $default value to be default if $key is not set in config.
118
 *
119
 * @return mixed value as $config[$key] or $default.
120
 */
121
function getConfig($key, $default)
122
{
123
    global $config;
124
    return isset($config[$key])
125
        ? $config[$key]
126
        : $default;
127
}
128
129
130
131
/**
132
 * Log when verbose mode, when used without argument it returns the result.
133
 *
134
 * @param string $msg to log.
135
 *
136
 * @return void or array.
137
 */
138
function verbose($msg = null)
139
{
140
    global $verbose, $verboseFile;
141
    static $log = array();
142
143
    if (!($verbose || $verboseFile)) {
144
        return;
145
    }
146
147
    if (is_null($msg)) {
148
        return $log;
149
    }
150
151
    $log[] = $msg;
152
}
153
154
155
156
/**
157
 * Get configuration options from file, if the file exists, else use $config
158
 * if its defined or create an empty $config.
159
 */
160
$configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php';
161
162
if (is_file($configFile)) {
163
    $config = require $configFile;
164
} elseif (!isset($config)) {
165
    $config = array();
166
}
167
168
169
170
/**
171
* verbose, v - do a verbose dump of what happens
172
* vf - do verbose dump to file
173
*/
174
$verbose = getDefined(array('verbose', 'v'), true, false);
175
$verboseFile = getDefined('vf', true, false);
176
verbose("img.php version = $version");
177
178
179
180
/**
181
* status - do a verbose dump of the configuration
182
*/
183
$status = getDefined('status', true, false);
184
185
186
187
/**
188
 * Set mode as strict, production or development.
189
 * Default is production environment.
190
 */
191
$mode = getConfig('mode', 'production');
192
193
// Settings for any mode
194
set_time_limit(20);
195
ini_set('gd.jpeg_ignore_warning', 1);
196
197
if (!extension_loaded('gd')) {
198
    errorPage("Extension gd is not loaded.", 500);
199
}
200
201
// Specific settings for each mode
202
if ($mode == 'strict') {
203
204
    error_reporting(0);
205
    ini_set('display_errors', 0);
206
    ini_set('log_errors', 1);
207
    $verbose = false;
208
    $status = false;
209
    $verboseFile = false;
210
211
} elseif ($mode == 'production') {
212
213
    error_reporting(-1);
214
    ini_set('display_errors', 0);
215
    ini_set('log_errors', 1);
216
    $verbose = false;
217
    $status = false;
218
    $verboseFile = false;
219
220
} elseif ($mode == 'development') {
221
222
    error_reporting(-1);
223
    ini_set('display_errors', 1);
224
    ini_set('log_errors', 0);
225
    $verboseFile = false;
226
227
} elseif ($mode == 'test') {
228
229
    error_reporting(-1);
230
    ini_set('display_errors', 1);
231
    ini_set('log_errors', 0);
232
233
} else {
234
    errorPage("Unknown mode: $mode", 500);
235
}
236
237
verbose("mode = $mode");
238
verbose("error log = " . ini_get('error_log'));
239
240
241
242
/**
243
 * Set default timezone if not set or if its set in the config-file.
244
 */
245
$defaultTimezone = getConfig('default_timezone', null);
246
247
if ($defaultTimezone) {
248
    date_default_timezone_set($defaultTimezone);
249
} elseif (!ini_get('default_timezone')) {
250
    date_default_timezone_set('UTC');
251
}
252
253
254
255
/**
256
 * Check if passwords are configured, used and match.
257
 * Options decide themself if they require passwords to be used.
258
 */
259
$pwdConfig   = getConfig('password', false);
260
$pwdAlways   = getConfig('password_always', false);
261
$pwdType     = getConfig('password_type', 'text');
262
$pwd         = get(array('password', 'pwd'), null);
263
264
// Check if passwords match, if configured to use passwords
265
$passwordMatch = null;
266
if ($pwd) {
267
    switch ($pwdType) {
268
        case 'md5':
269
            $passwordMatch = ($pwdConfig === md5($pwd));
270
            break;
271
        case 'hash':
272
            $passwordMatch = password_verify($pwd, $pwdConfig);
273
            break;
274
        case 'text':
275
            $passwordMatch = ($pwdConfig === $pwd);
276
            break;
277
        default:
278
            $passwordMatch = false;
279
    }
280
}
281
282
if ($pwdAlways && $passwordMatch !== true) {
283
    errorPage("Password required and does not match or exists.", 403);
284
}
285
286
verbose("password match = $passwordMatch");
287
288
289
290
/**
291
 * Prevent hotlinking, leeching, of images by controlling who access them
292
 * from where.
293
 *
294
 */
295
$allowHotlinking = getConfig('allow_hotlinking', true);
296
$hotlinkingWhitelist = getConfig('hotlinking_whitelist', array());
297
298
$serverName  = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
299
$referer     = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
300
$refererHost = parse_url($referer, PHP_URL_HOST);
301
302
if (!$allowHotlinking) {
303
    if ($passwordMatch) {
304
        ; // Always allow when password match
305
        verbose("Hotlinking since passwordmatch");
306
    } elseif ($passwordMatch === false) {
307
        errorPage("Hotlinking/leeching not allowed when password missmatch.", 403);
308
    } elseif (!$referer) {
309
        errorPage("Hotlinking/leeching not allowed and referer is missing.", 403);
310
    } elseif (strcmp($serverName, $refererHost) == 0) {
311
        ; // Allow when serverName matches refererHost
312
        verbose("Hotlinking disallowed but serverName matches refererHost.");
313
    } elseif (!empty($hotlinkingWhitelist)) {
314
        $whitelist = new CWhitelist();
315
        $allowedByWhitelist = $whitelist->check($refererHost, $hotlinkingWhitelist);
0 ignored issues
show
It seems like $refererHost defined by parse_url($referer, PHP_URL_HOST) on line 300 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...
316
317
        if ($allowedByWhitelist) {
318
            verbose("Hotlinking/leeching allowed by whitelist.");
319
        } else {
320
            errorPage("Hotlinking/leeching not allowed by whitelist. Referer: $referer.", 403);
321
        }
322
323
    } else {
324
        errorPage("Hotlinking/leeching not allowed.", 403);
325
    }
326
}
327
328
verbose("allow_hotlinking = $allowHotlinking");
329
verbose("referer = $referer");
330
verbose("referer host = $refererHost");
331
332
333
334
/**
335
 * Get the source files.
336
 */
337
$autoloader  = getConfig('autoloader', false);
338
$cimageClass = getConfig('cimage_class', false);
339
340
if ($autoloader) {
341
    require $autoloader;
342
} elseif ($cimageClass) {
343
    require $cimageClass;
344
}
345
346
347
348
/**
349
 * Create the class for the image.
350
 */
351
$img = new CImage();
352
$img->setVerbose($verbose || $verboseFile);
353
354
355
356
/**
357
 * Get the cachepath from config.
358
 */
359
$cachePath = getConfig('cache_path', __DIR__ . '/../cache/');
360
$cache = new CCache();
361
$cache->setDir($cachePath);
362
363
364
365
366
/**
367
 * Allow or disallow remote download of images from other servers.
368
 * Passwords apply if used.
369
 *
370
 */
371
$allowRemote = getConfig('remote_allow', false);
372
373
if ($allowRemote && $passwordMatch !== false) {
374
    $cacheRemote = $cache->getPathToSubdir("remote");
375
    
376
    $pattern = getConfig('remote_pattern', null);
377
    $img->setRemoteDownload($allowRemote, $cacheRemote, $pattern);
0 ignored issues
show
It seems like $cacheRemote defined by $cache->getPathToSubdir('remote') on line 374 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...
378
379
    $whitelist = getConfig('remote_whitelist', null);
380
    $img->setRemoteHostWhitelist($whitelist);
381
}
382
383
384
385
/**
386
 * shortcut, sc - extend arguments with a constant value, defined
387
 * in config-file.
388
 */
389
$shortcut       = get(array('shortcut', 'sc'), null);
390
$shortcutConfig = getConfig('shortcut', array(
391
    'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen",
392
));
393
394
verbose("shortcut = $shortcut");
395
396
if (isset($shortcut)
397
    && isset($shortcutConfig[$shortcut])) {
398
399
    parse_str($shortcutConfig[$shortcut], $get);
400
    verbose("shortcut-constant = {$shortcutConfig[$shortcut]}");
401
    $_GET = array_merge($_GET, $get);
402
}
403
404
405
406
/**
407
 * src - the source image file.
408
 */
409
$srcImage = urldecode(get('src'))
410
    or errorPage('Must set src-attribute.', 404);
411
412
// Check for valid/invalid characters
413
$imagePath           = getConfig('image_path', __DIR__ . '/img/');
414
$imagePathConstraint = getConfig('image_path_constraint', true);
415
$validFilename       = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#');
416
417
// Dummy image feature
418
$dummyEnabled  = getConfig('dummy_enabled', true);
419
$dummyFilename = getConfig('dummy_filename', 'dummy');
420
$dummyImage = false;
421
422
preg_match($validFilename, $srcImage)
423
    or errorPage('Filename contains invalid characters.', 404);
424
425
if ($dummyEnabled && $srcImage === $dummyFilename) {
426
427
    // Prepare to create a dummy image and use it as the source image.
428
    $dummyImage = true;
429
430
} elseif ($allowRemote && $img->isRemoteSource($srcImage)) {
431
432
    // If source is a remote file, ignore local file checks.
433
434
} elseif ($imagePathConstraint) {
435
436
    // Check that the image is a file below the directory 'image_path'.
437
    $pathToImage = realpath($imagePath . $srcImage);
438
    $imageDir    = realpath($imagePath);
439
440
    is_file($pathToImage)
441
        or errorPage(
442
            'Source image is not a valid file, check the filename and that a
443
            matching file exists on the filesystem.',
444
            404
445
        );
446
447
    substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0
448
        or errorPage(
449
            'Security constraint: Source image is not below the directory "image_path"
450
            as specified in the config file img_config.php.',
451
            404
452
        );
453
}
454
455
verbose("src = $srcImage");
456
457
458
459
/**
460
 * Manage size constants from config file, use constants to replace values
461
 * for width and height.
462
 */
463
$sizeConstant = getConfig('size_constant', function () {
464
465
    // Set sizes to map constant to value, easier to use with width or height
466
    $sizes = array(
467
        'w1' => 613,
468
        'w2' => 630,
469
    );
470
471
    // Add grid column width, useful for use as predefined size for width (or height).
472
    $gridColumnWidth = 30;
473
    $gridGutterWidth = 10;
474
    $gridColumns     = 24;
475
476
    for ($i = 1; $i <= $gridColumns; $i++) {
477
        $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth;
478
    }
479
480
    return $sizes;
481
});
482
483
$sizes = call_user_func($sizeConstant);
484
485
486
487
/**
488
 * width, w - set target width, affecting the resulting image width, height and resize options
489
 */
490
$newWidth     = get(array('width', 'w'));
491
$maxWidth     = getConfig('max_width', 2000);
492
493
// Check to replace predefined size
494
if (isset($sizes[$newWidth])) {
495
    $newWidth = $sizes[$newWidth];
496
}
497
498
// Support width as % of original width
499
if ($newWidth[strlen($newWidth)-1] == '%') {
500
    is_numeric(substr($newWidth, 0, -1))
501
        or errorPage('Width % not numeric.', 404);
502
} else {
503
    is_null($newWidth)
504
        or ($newWidth > 10 && $newWidth <= $maxWidth)
505
        or errorPage('Width out of range.', 404);
506
}
507
508
verbose("new width = $newWidth");
509
510
511
512
/**
513
 * height, h - set target height, affecting the resulting image width, height and resize options
514
 */
515
$newHeight = get(array('height', 'h'));
516
$maxHeight = getConfig('max_height', 2000);
517
518
// Check to replace predefined size
519
if (isset($sizes[$newHeight])) {
520
    $newHeight = $sizes[$newHeight];
521
}
522
523
// height
524
if ($newHeight[strlen($newHeight)-1] == '%') {
525
    is_numeric(substr($newHeight, 0, -1))
526
        or errorPage('Height % out of range.', 404);
527
} else {
528
    is_null($newHeight)
529
        or ($newHeight > 10 && $newHeight <= $maxHeight)
530
        or errorPage('Height out of range.', 404);
531
}
532
533
verbose("new height = $newHeight");
534
535
536
537
/**
538
 * aspect-ratio, ar - affecting the resulting image width, height and resize options
539
 */
540
$aspectRatio         = get(array('aspect-ratio', 'ar'));
541
$aspectRatioConstant = getConfig('aspect_ratio_constant', function () {
542
    return array(
543
        '3:1'    => 3/1,
544
        '3:2'    => 3/2,
545
        '4:3'    => 4/3,
546
        '8:5'    => 8/5,
547
        '16:10'  => 16/10,
548
        '16:9'   => 16/9,
549
        'golden' => 1.618,
550
    );
551
});
552
553
// Check to replace predefined aspect ratio
554
$aspectRatios = call_user_func($aspectRatioConstant);
555
$negateAspectRatio = ($aspectRatio[0] == '!') ? true : false;
556
$aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio;
557
558
if (isset($aspectRatios[$aspectRatio])) {
559
    $aspectRatio = $aspectRatios[$aspectRatio];
560
}
561
562
if ($negateAspectRatio) {
563
    $aspectRatio = 1 / $aspectRatio;
564
}
565
566
is_null($aspectRatio)
567
    or is_numeric($aspectRatio)
568
    or errorPage('Aspect ratio out of range', 404);
569
570
verbose("aspect ratio = $aspectRatio");
571
572
573
574
/**
575
 * crop-to-fit, cf - affecting the resulting image width, height and resize options
576
 */
577
$cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false);
578
579
verbose("crop to fit = $cropToFit");
580
581
582
583
/**
584
 * Set default background color from config file.
585
 */
586
$backgroundColor = getConfig('background_color', null);
587
588
if ($backgroundColor) {
589
    $img->setDefaultBackgroundColor($backgroundColor);
590
    verbose("Using default background_color = $backgroundColor");
591
}
592
593
594
595
/**
596
 * bgColor - Default background color to use
597
 */
598
$bgColor = get(array('bgColor', 'bg-color', 'bgc'), null);
599
600
verbose("bgColor = $bgColor");
601
602
603
604
/**
605
 * Do or do not resample image when resizing.
606
 */
607
$resizeStrategy = getDefined(array('no-resample'), true, false);
608
609
if ($resizeStrategy) {
610
    $img->setCopyResizeStrategy($img::RESIZE);
611
    verbose("Setting = Resize instead of resample");
612
}
613
614
615
616
617
/**
618
 * fill-to-fit, ff - affecting the resulting image width, height and resize options
619
 */
620
$fillToFit = get(array('fill-to-fit', 'ff'), null);
621
622
verbose("fill-to-fit = $fillToFit");
623
624
if ($fillToFit !== null) {
625
626
    if (!empty($fillToFit)) {
627
        $bgColor   = $fillToFit;
628
        verbose("fillToFit changed bgColor to = $bgColor");
629
    }
630
631
    $fillToFit = true;
632
    verbose("fill-to-fit (fixed) = $fillToFit");
633
}
634
635
636
637
/**
638
 * no-ratio, nr, stretch - affecting the resulting image width, height and resize options
639
 */
640
$keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true);
641
642
verbose("keep ratio = $keepRatio");
643
644
645
646
/**
647
 * crop, c - affecting the resulting image width, height and resize options
648
 */
649
$crop = get(array('crop', 'c'));
650
651
verbose("crop = $crop");
652
653
654
655
/**
656
 * area, a - affecting the resulting image width, height and resize options
657
 */
658
$area = get(array('area', 'a'));
659
660
verbose("area = $area");
661
662
663
664
/**
665
 * skip-original, so - skip the original image and always process a new image
666
 */
667
$useOriginal = getDefined(array('skip-original', 'so'), false, true);
668
$useOriginalDefault = getConfig('skip_original', false);
669
670
if ($useOriginalDefault === true) {
671
    verbose("use original is default ON");
672
    $useOriginal = true;
673
}
674
675
verbose("use original = $useOriginal");
676
677
678
679
/**
680
 * no-cache, nc - skip the cached version and process and create a new version in cache.
681
 */
682
$useCache = getDefined(array('no-cache', 'nc'), false, true);
683
684
verbose("use cache = $useCache");
685
686
687
688
/**
689
 * quality, q - set level of quality for jpeg images
690
 */
691
$quality = get(array('quality', 'q'));
692
$qualityDefault = getConfig('jpg_quality', null);
693
694
is_null($quality)
695
    or ($quality > 0 and $quality <= 100)
696
    or errorPage('Quality out of range', 404);
697
698
if (is_null($quality) && !is_null($qualityDefault)) {
699
    $quality = $qualityDefault;
700
}
701
702
verbose("quality = $quality");
703
704
705
706
/**
707
 * compress, co - what strategy to use when compressing png images
708
 */
709
$compress = get(array('compress', 'co'));
710
$compressDefault = getConfig('png_compression', null);
711
712
is_null($compress)
713
    or ($compress > 0 and $compress <= 9)
714
    or errorPage('Compress out of range', 404);
715
716
if (is_null($compress) && !is_null($compressDefault)) {
717
    $compress = $compressDefault;
718
}
719
720
verbose("compress = $compress");
721
722
723
724
/**
725
 * save-as, sa - what type of image to save
726
 */
727
$saveAs = get(array('save-as', 'sa'));
728
729
verbose("save as = $saveAs");
730
731
732
733
/**
734
 * scale, s - Processing option, scale up or down the image prior actual resize
735
 */
736
$scale = get(array('scale', 's'));
737
738
is_null($scale)
739
    or ($scale >= 0 and $scale <= 400)
740
    or errorPage('Scale out of range', 404);
741
742
verbose("scale = $scale");
743
744
745
746
/**
747
 * palette, p - Processing option, create a palette version of the image
748
 */
749
$palette = getDefined(array('palette', 'p'), true, false);
750
751
verbose("palette = $palette");
752
753
754
755
/**
756
 * sharpen - Processing option, post filter for sharpen effect
757
 */
758
$sharpen = getDefined('sharpen', true, null);
759
760
verbose("sharpen = $sharpen");
761
762
763
764
/**
765
 * emboss - Processing option, post filter for emboss effect
766
 */
767
$emboss = getDefined('emboss', true, null);
768
769
verbose("emboss = $emboss");
770
771
772
773
/**
774
 * blur - Processing option, post filter for blur effect
775
 */
776
$blur = getDefined('blur', true, null);
777
778
verbose("blur = $blur");
779
780
781
782
/**
783
 * rotateBefore - Rotate the image with an angle, before processing
784
 */
785
$rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb'));
786
787
is_null($rotateBefore)
788
    or ($rotateBefore >= -360 and $rotateBefore <= 360)
789
    or errorPage('RotateBefore out of range', 404);
790
791
verbose("rotateBefore = $rotateBefore");
792
793
794
795
/**
796
 * rotateAfter - Rotate the image with an angle, before processing
797
 */
798
$rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r'));
799
800
is_null($rotateAfter)
801
    or ($rotateAfter >= -360 and $rotateAfter <= 360)
802
    or errorPage('RotateBefore out of range', 404);
803
804
verbose("rotateAfter = $rotateAfter");
805
806
807
808
/**
809
 * autoRotate - Auto rotate based on EXIF information
810
 */
811
$autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false);
812
813
verbose("autoRotate = $autoRotate");
814
815
816
817
/**
818
 * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter()
819
 */
820
$filters = array();
821
$filter = get(array('filter', 'f'));
822
if ($filter) {
823
    $filters[] = $filter;
824
}
825
826
for ($i = 0; $i < 10; $i++) {
827
    $filter = get(array("filter{$i}", "f{$i}"));
828
    if ($filter) {
829
        $filters[] = $filter;
830
    }
831
}
832
833
verbose("filters = " . print_r($filters, 1));
834
835
836
837
/**
838
* json -  output the image as a JSON object with details on the image.
839
* ascii - output the image as ASCII art.
840
 */
841
$outputFormat = getDefined('json', 'json', null);
842
$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
843
844
verbose("outputformat = $outputFormat");
845
846
if ($outputFormat == 'ascii') {
847
    $defaultOptions = getConfig(
848
        'ascii-options',
849
        array(
850
            "characterSet" => 'two',
851
            "scale" => 14,
852
            "luminanceStrategy" => 3,
853
            "customCharacterSet" => null,
854
        )
855
    );
856
    $options = get('ascii');
857
    $options = explode(',', $options);
858
859
    if (isset($options[0]) && !empty($options[0])) {
860
        $defaultOptions['characterSet'] = $options[0];
861
    }
862
863
    if (isset($options[1]) && !empty($options[1])) {
864
        $defaultOptions['scale'] = $options[1];
865
    }
866
867
    if (isset($options[2]) && !empty($options[2])) {
868
        $defaultOptions['luminanceStrategy'] = $options[2];
869
    }
870
871
    if (count($options) > 3) {
872
        // Last option is custom character string
873
        unset($options[0]);
874
        unset($options[1]);
875
        unset($options[2]);
876
        $characterString = implode($options);
877
        $defaultOptions['customCharacterSet'] = $characterString;
878
    }
879
880
    $img->setAsciiOptions($defaultOptions);
881
}
882
883
884
885
886
/**
887
 * dpr - change to get larger image to easier support larger dpr, such as retina.
888
 */
889
$dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1);
890
891
verbose("dpr = $dpr");
892
893
894
895
/**
896
 * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php
897
 */
898
$convolve = get('convolve', null);
899
$convolutionConstant = getConfig('convolution_constant', array());
900
901
// Check if the convolve is matching an existing constant
902
if ($convolve && isset($convolutionConstant)) {
903
    $img->addConvolveExpressions($convolutionConstant);
904
    verbose("convolve constant = " . print_r($convolutionConstant, 1));
905
}
906
907
verbose("convolve = " . print_r($convolve, 1));
908
909
910
911
/**
912
 * no-upscale, nu - Do not upscale smaller image to larger dimension.
913
 */
914
$upscale = getDefined(array('no-upscale', 'nu'), false, true);
915
916
verbose("upscale = $upscale");
917
918
919
920
/**
921
 * Get details for post processing
922
 */
923
$postProcessing = getConfig('postprocessing', array(
924
    'png_filter'        => false,
925
    'png_filter_cmd'    => '/usr/local/bin/optipng -q',
926
927
    'png_deflate'       => false,
928
    'png_deflate_cmd'   => '/usr/local/bin/pngout -q',
929
930
    'jpeg_optimize'     => false,
931
    'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize',
932
));
933
934
935
936
/**
937
 * alias - Save resulting image to another alias name.
938
 * Password always apply, must be defined.
939
 */
940
$alias          = get('alias', null);
941
$aliasPath      = getConfig('alias_path', null);
942
$validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#');
943
$aliasTarget    = null;
944
945
if ($alias && $aliasPath && $passwordMatch) {
946
947
    $aliasTarget = $aliasPath . $alias;
948
    $useCache    = false;
949
950
    is_writable($aliasPath)
951
        or errorPage("Directory for alias is not writable.", 403);
952
953
    preg_match($validAliasname, $alias)
954
        or errorPage('Filename for alias contains invalid characters. Do not add extension.', 404);
955
956
} elseif ($alias) {
957
    errorPage('Alias is not enabled in the config file or password not matching.', 403);
958
}
959
960
verbose("alias = $alias");
961
962
963
964
/**
965
 * Add cache control HTTP header.
966
 */
967
$cacheControl = getConfig('cache_control', null);
968
969
if ($cacheControl) {
970
    verbose("cacheControl = $cacheControl");
971
    $img->addHTTPHeader("Cache-Control", $cacheControl);
972
}
973
974
975
976
/**
977
 * Prepare a dummy image and use it as source image.
978
 */
979
if ($dummyImage === true) {
980
    $dummyDir = $cache->getPathToSubdir("dummy");
981
982
    $img->setSaveFolder($dummyDir)
0 ignored issues
show
It seems like $dummyDir defined by $cache->getPathToSubdir('dummy') on line 980 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...
983
        ->setSource($dummyFilename, $dummyDir)
0 ignored issues
show
It seems like $dummyDir defined by $cache->getPathToSubdir('dummy') on line 980 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...
984
        ->setOptions(
985
            array(
986
                'newWidth'  => $newWidth,
987
                'newHeight' => $newHeight,
988
                'bgColor'   => $bgColor,
989
            )
990
        )
991
        ->setJpegQuality($quality)
992
        ->setPngCompression($compress)
993
        ->createDummyImage()
994
        ->generateFilename(null, false)
995
        ->save(null, null, false);
996
997
    $srcImage = $img->getTarget();
998
    $imagePath = null;
999
1000
    verbose("src (updated) = $srcImage");
1001
}
1002
1003
1004
1005
/**
1006
 * Prepare a sRGB version of the image and use it as source image.
1007
 */
1008
$srgbDefault = getConfig('srgb_default', false);
1009
$srgbColorProfile = getConfig('srgb_colorprofile', __DIR__ . '/../icc/sRGB_IEC61966-2-1_black_scaled.icc');
1010
$srgb = getDefined('srgb', true, null);
1011
1012
if ($srgb || $srgbDefault) {
1013
1014
    $filename = $img->convert2sRGBColorSpace(
1015
        $srcImage,
1016
        $imagePath,
1017
        $cache->getPathToSubdir("srgb"),
1018
        $srgbColorProfile,
1019
        $useCache
1020
    );
1021
1022
    if ($filename) {
1023
        $srcImage = $img->getTarget();
1024
        $imagePath = null;
1025
        verbose("srgb conversion and saved to cache = $srcImage");
1026
    } else {
1027
        verbose("srgb not op");
1028
    }
1029
}
1030
1031
1032
1033
/**
1034
 * Display status
1035
 */
1036
if ($status) {
1037
    $text  = "img.php version = $version\n";
1038
    $text .= "PHP version = " . PHP_VERSION . "\n";
1039
    $text .= "Running on: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
1040
    $text .= "Allow remote images = $allowRemote\n";
1041
1042
    $res = $cache->getStatusOfSubdir("");
1043
    $text .= "Cache $res\n";
1044
1045
    $res = $cache->getStatusOfSubdir("remote");
1046
    $text .= "Cache remote $res\n";
1047
1048
    $res = $cache->getStatusOfSubdir("dummy");
1049
    $text .= "Cache dummy $res\n";
1050
1051
    $res = $cache->getStatusOfSubdir("srgb");
1052
    $text .= "Cache srgb $res\n";
1053
1054
    $text .= "Alias path writable = " . is_writable($aliasPath) . "\n";
1055
1056
    $no = extension_loaded('exif') ? null : 'NOT';
1057
    $text .= "Extension exif is $no loaded.<br>";
1058
1059
    $no = extension_loaded('curl') ? null : 'NOT';
1060
    $text .= "Extension curl is $no loaded.<br>";
1061
1062
    $no = extension_loaded('imagick') ? null : 'NOT';
1063
    $text .= "Extension imagick is $no loaded.<br>";
1064
1065
    $no = extension_loaded('gd') ? null : 'NOT';
1066
    $text .= "Extension gd is $no loaded.<br>";
1067
1068
    if (!$no) {
1069
        $text .= print_r(gd_info(), 1);
1070
    }
1071
1072
    echo <<<EOD
1073
<!doctype html>
1074
<html lang=en>
1075
<meta charset=utf-8>
1076
<title>CImage status</title>
1077
<pre>$text</pre>
1078
EOD;
1079
    exit;
1080
}
1081
1082
1083
1084
/**
1085
 * Log verbose details to file
1086
 */
1087
if ($verboseFile) {
1088
    $img->setVerboseToFile("$cachePath/log.txt");
1089
}
1090
1091
1092
1093
/**
1094
 * Hook after img.php configuration and before processing with CImage
1095
 */
1096
$hookBeforeCImage = getConfig('hook_before_CImage', null);
1097
1098
if (is_callable($hookBeforeCImage)) {
1099
    verbose("hookBeforeCImage activated");
1100
1101
    $allConfig = $hookBeforeCImage($img, array(
1102
            // Options for calculate dimensions
1103
            'newWidth'  => $newWidth,
1104
            'newHeight' => $newHeight,
1105
            'aspectRatio' => $aspectRatio,
1106
            'keepRatio' => $keepRatio,
1107
            'cropToFit' => $cropToFit,
1108
            'fillToFit' => $fillToFit,
1109
            'crop'      => $crop,
1110
            'area'      => $area,
1111
            'upscale'   => $upscale,
1112
1113
            // Pre-processing, before resizing is done
1114
            'scale'        => $scale,
1115
            'rotateBefore' => $rotateBefore,
1116
            'autoRotate'   => $autoRotate,
1117
1118
            // General processing options
1119
            'bgColor'    => $bgColor,
1120
1121
            // Post-processing, after resizing is done
1122
            'palette'   => $palette,
1123
            'filters'   => $filters,
1124
            'sharpen'   => $sharpen,
1125
            'emboss'    => $emboss,
1126
            'blur'      => $blur,
1127
            'convolve'  => $convolve,
1128
            'rotateAfter' => $rotateAfter,
1129
1130
            // Output format
1131
            'outputFormat' => $outputFormat,
1132
            'dpr'          => $dpr,
1133
1134
            // Other
1135
            'postProcessing' => $postProcessing,
1136
    ));
1137
    verbose(print_r($allConfig, 1));
1138
    extract($allConfig);
1139
}
1140
1141
1142
1143
/**
1144
 * Display image if verbose mode
1145
 */
1146
if ($verbose) {
1147
    $query = array();
1148
    parse_str($_SERVER['QUERY_STRING'], $query);
1149
    unset($query['verbose']);
1150
    unset($query['v']);
1151
    unset($query['nocache']);
1152
    unset($query['nc']);
1153
    unset($query['json']);
1154
    $url1 = '?' . htmlentities(urldecode(http_build_query($query)));
1155
    $url2 = '?' . urldecode(http_build_query($query));
1156
    echo <<<EOD
1157
<!doctype html>
1158
<html lang=en>
1159
<meta charset=utf-8>
1160
<title>CImage verbose output</title>
1161
<style>body{background-color: #ddd}</style>
1162
<a href=$url1><code>$url1</code></a><br>
1163
<img src='{$url1}' />
1164
<pre id="json"></pre>
1165
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
1166
<script type="text/javascript">
1167
window.getDetails = function (url, id) {
1168
  $.getJSON(url, function(data) {
1169
    element = document.getElementById(id);
1170
    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 : '');
1171
  });
1172
}
1173
</script>
1174
<script type="text/javascript">window.getDetails("{$url2}&json", "json")</script>
1175
EOD;
1176
}
1177
1178
1179
1180
/**
1181
 * Load, process and output the image
1182
 */
1183
$img->log("Incoming arguments: " . print_r(verbose(), 1))
1184
    ->setSaveFolder($cachePath)
1185
    ->useCache($useCache)
1186
    ->setSource($srcImage, $imagePath)
1187
    ->setOptions(
1188
        array(
1189
            // Options for calculate dimensions
1190
            'newWidth'  => $newWidth,
1191
            'newHeight' => $newHeight,
1192
            'aspectRatio' => $aspectRatio,
1193
            'keepRatio' => $keepRatio,
1194
            'cropToFit' => $cropToFit,
1195
            'fillToFit' => $fillToFit,
1196
            'crop'      => $crop,
1197
            'area'      => $area,
1198
            'upscale'   => $upscale,
1199
1200
            // Pre-processing, before resizing is done
1201
            'scale'        => $scale,
1202
            'rotateBefore' => $rotateBefore,
1203
            'autoRotate'   => $autoRotate,
1204
1205
            // General processing options
1206
            'bgColor'    => $bgColor,
1207
1208
            // Post-processing, after resizing is done
1209
            'palette'   => $palette,
1210
            'filters'   => $filters,
1211
            'sharpen'   => $sharpen,
1212
            'emboss'    => $emboss,
1213
            'blur'      => $blur,
1214
            'convolve'  => $convolve,
1215
            'rotateAfter' => $rotateAfter,
1216
1217
            // Output format
1218
            'outputFormat' => $outputFormat,
1219
            'dpr'          => $dpr,
1220
        )
1221
    )
1222
    ->loadImageDetails()
1223
    ->initDimensions()
1224
    ->calculateNewWidthAndHeight()
1225
    ->setSaveAsExtension($saveAs)
1226
    ->setJpegQuality($quality)
1227
    ->setPngCompression($compress)
1228
    ->useOriginalIfPossible($useOriginal)
1229
    ->generateFilename($cachePath)
1230
    ->useCacheIfPossible($useCache)
1231
    ->load()
1232
    ->preResize()
1233
    ->resize()
1234
    ->postResize()
1235
    ->setPostProcessingOptions($postProcessing)
1236
    ->save()
1237
    ->linkToCacheFile($aliasTarget)
1238
    ->output();
1239