Completed
Push — 0713 ( 6118f2 )
by Mikael
02:49
created

webroot/img.php (1 issue)

Severity

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.12 (2016-06-01)";
12
13
// For CRemoteImage
14
define("CIMAGE_USER_AGENT", "CImage/$version");
15
16
17
// Include debug functions
18
function debug($msg)
0 ignored issues
show
debug uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

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