Issues (47)

htdocs/cimage/img.php (6 issues)

Labels
Severity
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
/**
12
 * Custom exception handler.
13
 */
14
set_exception_handler(function ($exception) {
15
    errorPage(
16
        "<p><b>img.php: Uncaught exception:</b> <p>"
17
        . $exception->getMessage()
18
        . "</p><pre>"
19
        . $exception->getTraceAsString()
20
        . "</pre>",
21
        500
22
    );
23
});
24
25
26
27
/**
28
 * Get configuration options from file, if the file exists, else use $config
29
 * if its defined or create an empty $config.
30
 */
31
$configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php';
32
33
if (is_file($configFile)) {
34
    $config = require $configFile;
35
} elseif (!isset($config)) {
36
    $config = array();
37
}
38
39
// Make CIMAGE_DEBUG false by default, if not already defined
40
if (!defined("CIMAGE_DEBUG")) {
41
    define("CIMAGE_DEBUG", false);
42
}
43
44
45
46
/**
47
 * Setup the autoloader, but not when using a bundle.
48
 */
49
if (!defined("CIMAGE_BUNDLE")) {
50
    if (!isset($config["autoloader"])) {
51
        die("CImage: Missing autoloader.");
52
    }
53
54
    require $config["autoloader"];
55
}
56
57
58
59
/**
60
* verbose, v - do a verbose dump of what happens
61
* vf - do verbose dump to file
62
*/
63
$verbose = getDefined(array('verbose', 'v'), true, false);
64
$verboseFile = getDefined('vf', true, false);
65
verbose("img.php version = " . CIMAGE_VERSION);
66
67
68
69
/**
70
* status - do a verbose dump of the configuration
71
*/
72
$status = getDefined('status', true, false);
73
74
75
76
/**
77
 * Set mode as strict, production or development.
78
 * Default is production environment.
79
 */
80
$mode = getConfig('mode', 'production');
81
82
// Settings for any mode
83
set_time_limit(20);
84
ini_set('gd.jpeg_ignore_warning', 1);
85
86
if (!extension_loaded('gd')) {
87
    errorPage("Extension gd is not loaded.", 500);
88
}
89
90
// Specific settings for each mode
91
if ($mode == 'strict') {
92
93
    error_reporting(0);
94
    ini_set('display_errors', 0);
95
    ini_set('log_errors', 1);
96
    $verbose = false;
97
    $status = false;
98
    $verboseFile = false;
99
100
} elseif ($mode == 'production') {
101
102
    error_reporting(-1);
103
    ini_set('display_errors', 0);
104
    ini_set('log_errors', 1);
105
    $verbose = false;
106
    $status = false;
107
    $verboseFile = false;
108
109
} elseif ($mode == 'development') {
110
111
    error_reporting(-1);
112
    ini_set('display_errors', 1);
113
    ini_set('log_errors', 0);
114
    $verboseFile = false;
115
116
} elseif ($mode == 'test') {
117
118
    error_reporting(-1);
119
    ini_set('display_errors', 1);
120
    ini_set('log_errors', 0);
121
122
} else {
123
    errorPage("Unknown mode: $mode", 500);
124
}
125
126
verbose("mode = $mode");
127
verbose("error log = " . ini_get('error_log'));
128
129
130
131
/**
132
 * Set default timezone if not set or if its set in the config-file.
133
 */
134
$defaultTimezone = getConfig('default_timezone', null);
135
136
if ($defaultTimezone) {
137
    date_default_timezone_set($defaultTimezone);
138
} elseif (!ini_get('default_timezone')) {
139
    date_default_timezone_set('UTC');
140
}
141
142
143
144
/**
145
 * Check if passwords are configured, used and match.
146
 * Options decide themself if they require passwords to be used.
147
 */
148
$pwdConfig   = getConfig('password', false);
149
$pwdAlways   = getConfig('password_always', false);
150
$pwdType     = getConfig('password_type', 'text');
151
$pwd         = get(array('password', 'pwd'), null);
152
153
// Check if passwords match, if configured to use passwords
154
$passwordMatch = null;
155
if ($pwd) {
156
    switch ($pwdType) {
157
        case 'md5':
158
            $passwordMatch = ($pwdConfig === md5($pwd));
159
            break;
160
        case 'hash':
161
            $passwordMatch = password_verify($pwd, $pwdConfig);
0 ignored issues
show
It seems like $pwdConfig can also be of type false; however, parameter $hash of password_verify() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

161
            $passwordMatch = password_verify($pwd, /** @scrutinizer ignore-type */ $pwdConfig);
Loading history...
162
            break;
163
        case 'text':
164
            $passwordMatch = ($pwdConfig === $pwd);
165
            break;
166
        default:
167
            $passwordMatch = false;
168
    }
169
}
170
171
if ($pwdAlways && $passwordMatch !== true) {
172
    errorPage("Password required and does not match or exists.", 403);
173
}
174
175
verbose("password match = $passwordMatch");
176
177
178
179
/**
180
 * Prevent hotlinking, leeching, of images by controlling who access them
181
 * from where.
182
 *
183
 */
184
$allowHotlinking = getConfig('allow_hotlinking', true);
185
$hotlinkingWhitelist = getConfig('hotlinking_whitelist', array());
186
187
$serverName  = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
188
$referer     = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
189
$refererHost = parse_url($referer, PHP_URL_HOST);
190
191
if (!$allowHotlinking) {
192
    if ($passwordMatch) {
193
        ; // Always allow when password match
194
        verbose("Hotlinking since passwordmatch");
195
    } elseif ($passwordMatch === false) {
196
        errorPage("Hotlinking/leeching not allowed when password missmatch.", 403);
197
    } elseif (!$referer) {
198
        errorPage("Hotlinking/leeching not allowed and referer is missing.", 403);
199
    } elseif (strcmp($serverName, $refererHost) == 0) {
200
        ; // Allow when serverName matches refererHost
201
        verbose("Hotlinking disallowed but serverName matches refererHost.");
202
    } elseif (!empty($hotlinkingWhitelist)) {
203
        $whitelist = new CWhitelist();
204
        $allowedByWhitelist = $whitelist->check($refererHost, $hotlinkingWhitelist);
205
206
        if ($allowedByWhitelist) {
207
            verbose("Hotlinking/leeching allowed by whitelist.");
208
        } else {
209
            errorPage("Hotlinking/leeching not allowed by whitelist. Referer: $referer.", 403);
210
        }
211
212
    } else {
213
        errorPage("Hotlinking/leeching not allowed.", 403);
214
    }
215
}
216
217
verbose("allow_hotlinking = $allowHotlinking");
218
verbose("referer = $referer");
219
verbose("referer host = $refererHost");
220
221
222
223
/**
224
 * Create the class for the image.
225
 */
226
$CImage = getConfig('CImage', 'CImage');
227
$img = new $CImage();
228
$img->setVerbose($verbose || $verboseFile);
229
230
231
232
/**
233
 * Get the cachepath from config.
234
 */
235
$CCache = getConfig('CCache', 'CCache');
236
$cachePath = getConfig('cache_path', __DIR__ . '/../cache/');
237
$cache = new $CCache();
238
$cache->setDir($cachePath);
239
240
241
242
/**
243
 * no-cache, nc - skip the cached version and process and create a new version in cache.
244
 */
245
$useCache = getDefined(array('no-cache', 'nc'), false, true);
246
247
verbose("use cache = $useCache");
248
249
250
251
/**
252
 * Prepare fast track cache for swriting cache items.
253
 */
254
$fastTrackCache = "fasttrack";
255
$allowFastTrackCache = getConfig('fast_track_allow', false);
256
257
$CFastTrackCache = getConfig('CFastTrackCache', 'CFastTrackCache');
258
$ftc = new $CFastTrackCache();
259
$ftc->setCacheDir($cache->getPathToSubdir($fastTrackCache))
260
    ->enable($allowFastTrackCache)
261
    ->setFilename(array('no-cache', 'nc'));
262
$img->injectDependency("fastTrackCache", $ftc);
263
264
265
266
/**
267
 *  Load and output images from fast track cache, if items are available
268
 * in cache.
269
 */
270
if ($useCache && $allowFastTrackCache) {
271
    if (CIMAGE_DEBUG) {
272
        trace("img.php fast track cache enabled and used");
273
    }
274
    $ftc->output();
275
}
276
277
278
279
/**
280
 * Allow or disallow remote download of images from other servers.
281
 * Passwords apply if used.
282
 *
283
 */
284
$allowRemote = getConfig('remote_allow', false);
285
286
if ($allowRemote && $passwordMatch !== false) {
287
    $cacheRemote = $cache->getPathToSubdir("remote");
288
    
289
    $pattern = getConfig('remote_pattern', null);
290
    $img->setRemoteDownload($allowRemote, $cacheRemote, $pattern);
291
292
    $whitelist = getConfig('remote_whitelist', null);
293
    $img->setRemoteHostWhitelist($whitelist);
294
}
295
296
297
298
/**
299
 * shortcut, sc - extend arguments with a constant value, defined
300
 * in config-file.
301
 */
302
$shortcut       = get(array('shortcut', 'sc'), null);
303
$shortcutConfig = getConfig('shortcut', array(
304
    'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen",
305
));
306
307
verbose("shortcut = $shortcut");
308
309
if (isset($shortcut)
310
    && isset($shortcutConfig[$shortcut])) {
311
312
    parse_str($shortcutConfig[$shortcut], $get);
313
    verbose("shortcut-constant = {$shortcutConfig[$shortcut]}");
314
    $_GET = array_merge($_GET, $get);
315
}
316
317
318
319
/**
320
 * src - the source image file.
321
 */
322
$srcImage = urldecode(get('src'))
323
    or errorPage('Must set src-attribute.', 404);
324
325
// Get settings for src-alt as backup image
326
$srcAltImage = urldecode(get('src-alt', null));
327
$srcAltConfig = getConfig('src_alt', null);
328
if (empty($srcAltImage)) {
329
    $srcAltImage = $srcAltConfig;
330
}
331
332
// Check for valid/invalid characters
333
$imagePath           = getConfig('image_path', __DIR__ . '/img/');
334
$imagePathConstraint = getConfig('image_path_constraint', true);
335
$validFilename       = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#');
336
337
// Source is remote
338
$remoteSource = false;
339
340
// Dummy image feature
341
$dummyEnabled  = getConfig('dummy_enabled', true);
342
$dummyFilename = getConfig('dummy_filename', 'dummy');
343
$dummyImage = false;
344
345
preg_match($validFilename, $srcImage)
346
    or errorPage('Source filename contains invalid characters.', 404);
347
348
if ($dummyEnabled && $srcImage === $dummyFilename) {
349
350
    // Prepare to create a dummy image and use it as the source image.
351
    $dummyImage = true;
352
353
} elseif ($allowRemote && $img->isRemoteSource($srcImage)) {
354
355
    // If source is a remote file, ignore local file checks.
356
    $remoteSource = true;
357
358
} else {
359
360
    // Check if file exists on disk or try using src-alt
361
    $pathToImage = realpath($imagePath . $srcImage);
362
363
    if (!is_file($pathToImage) && !empty($srcAltImage)) {
364
        // Try using the src-alt instead
365
        $srcImage = $srcAltImage;
366
        $pathToImage = realpath($imagePath . $srcImage);
367
368
        preg_match($validFilename, $srcImage)
369
            or errorPage('Source (alt) filename contains invalid characters.', 404);
370
371
        if ($dummyEnabled && $srcImage === $dummyFilename) {
372
            // Check if src-alt is the dummy image
373
            $dummyImage = true;
374
        }
375
    }
376
377
    if (!$dummyImage) {
378
        is_file($pathToImage)
379
            or errorPage(
380
                'Source image is not a valid file, check the filename and that a
381
                matching file exists on the filesystem.',
382
                404
383
            );
384
    } 
385
}
386
387
if ($imagePathConstraint && !$dummyImage && !$remoteSource) {
388
    // Check that the image is a file below the directory 'image_path'.
389
    $imageDir = realpath($imagePath);
390
391
    substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0
392
        or errorPage(
393
            'Security constraint: Source image is not below the directory "image_path"
394
            as specified in the config file img_config.php.',
395
            404
396
        );
397
}
398
399
verbose("src = $srcImage");
400
401
402
403
/**
404
 * Manage size constants from config file, use constants to replace values
405
 * for width and height.
406
 */
407
$sizeConstant = getConfig('size_constant', function () {
408
409
    // Set sizes to map constant to value, easier to use with width or height
410
    $sizes = array(
411
        'w1' => 613,
412
        'w2' => 630,
413
    );
414
415
    // Add grid column width, useful for use as predefined size for width (or height).
416
    $gridColumnWidth = 30;
417
    $gridGutterWidth = 10;
418
    $gridColumns     = 24;
419
420
    for ($i = 1; $i <= $gridColumns; $i++) {
421
        $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth;
422
    }
423
424
    return $sizes;
425
});
426
427
$sizes = call_user_func($sizeConstant);
428
429
430
431
/**
432
 * width, w - set target width, affecting the resulting image width, height and resize options
433
 */
434
$newWidth     = get(array('width', 'w'));
435
$maxWidth     = getConfig('max_width', 2000);
436
437
// Check to replace predefined size
438
if (isset($sizes[$newWidth])) {
439
    $newWidth = $sizes[$newWidth];
440
}
441
442
// Support width as % of original width
443
if ($newWidth[strlen($newWidth)-1] == '%') {
444
    is_numeric(substr($newWidth, 0, -1))
445
        or errorPage('Width % not numeric.', 404);
446
} else {
447
    is_null($newWidth)
448
        or ($newWidth > 10 && $newWidth <= $maxWidth)
449
        or errorPage('Width out of range.', 404);
450
}
451
452
verbose("new width = $newWidth");
453
454
455
456
/**
457
 * height, h - set target height, affecting the resulting image width, height and resize options
458
 */
459
$newHeight = get(array('height', 'h'));
460
$maxHeight = getConfig('max_height', 2000);
461
462
// Check to replace predefined size
463
if (isset($sizes[$newHeight])) {
464
    $newHeight = $sizes[$newHeight];
465
}
466
467
// height
468
if ($newHeight[strlen($newHeight)-1] == '%') {
469
    is_numeric(substr($newHeight, 0, -1))
470
        or errorPage('Height % out of range.', 404);
471
} else {
472
    is_null($newHeight)
473
        or ($newHeight > 10 && $newHeight <= $maxHeight)
474
        or errorPage('Height out of range.', 404);
475
}
476
477
verbose("new height = $newHeight");
478
479
480
481
/**
482
 * aspect-ratio, ar - affecting the resulting image width, height and resize options
483
 */
484
$aspectRatio         = get(array('aspect-ratio', 'ar'));
485
$aspectRatioConstant = getConfig('aspect_ratio_constant', function () {
486
    return array(
487
        '3:1'    => 3/1,
488
        '3:2'    => 3/2,
489
        '4:3'    => 4/3,
490
        '8:5'    => 8/5,
491
        '16:10'  => 16/10,
492
        '16:9'   => 16/9,
493
        'golden' => 1.618,
494
    );
495
});
496
497
// Check to replace predefined aspect ratio
498
$aspectRatios = call_user_func($aspectRatioConstant);
499
$negateAspectRatio = ($aspectRatio[0] == '!') ? true : false;
500
$aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio;
501
502
if (isset($aspectRatios[$aspectRatio])) {
503
    $aspectRatio = $aspectRatios[$aspectRatio];
504
}
505
506
if ($negateAspectRatio) {
507
    $aspectRatio = 1 / $aspectRatio;
508
}
509
510
is_null($aspectRatio)
511
    or is_numeric($aspectRatio)
512
    or errorPage('Aspect ratio out of range', 404);
513
514
verbose("aspect ratio = $aspectRatio");
515
516
517
518
/**
519
 * crop-to-fit, cf - affecting the resulting image width, height and resize options
520
 */
521
$cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false);
522
523
verbose("crop to fit = $cropToFit");
524
525
526
527
/**
528
 * Set default background color from config file.
529
 */
530
$backgroundColor = getConfig('background_color', null);
531
532
if ($backgroundColor) {
533
    $img->setDefaultBackgroundColor($backgroundColor);
534
    verbose("Using default background_color = $backgroundColor");
535
}
536
537
538
539
/**
540
 * bgColor - Default background color to use
541
 */
542
$bgColor = get(array('bgColor', 'bg-color', 'bgc'), null);
543
544
verbose("bgColor = $bgColor");
545
546
547
548
/**
549
 * Do or do not resample image when resizing.
550
 */
551
$resizeStrategy = getDefined(array('no-resample'), true, false);
552
553
if ($resizeStrategy) {
554
    $img->setCopyResizeStrategy($img::RESIZE);
555
    verbose("Setting = Resize instead of resample");
556
}
557
558
559
560
561
/**
562
 * fill-to-fit, ff - affecting the resulting image width, height and resize options
563
 */
564
$fillToFit = get(array('fill-to-fit', 'ff'), null);
565
566
verbose("fill-to-fit = $fillToFit");
567
568
if ($fillToFit !== null) {
569
570
    if (!empty($fillToFit)) {
571
        $bgColor   = $fillToFit;
572
        verbose("fillToFit changed bgColor to = $bgColor");
573
    }
574
575
    $fillToFit = true;
576
    verbose("fill-to-fit (fixed) = $fillToFit");
577
}
578
579
580
581
/**
582
 * no-ratio, nr, stretch - affecting the resulting image width, height and resize options
583
 */
584
$keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true);
585
586
verbose("keep ratio = $keepRatio");
587
588
589
590
/**
591
 * crop, c - affecting the resulting image width, height and resize options
592
 */
593
$crop = get(array('crop', 'c'));
594
595
verbose("crop = $crop");
596
597
598
599
/**
600
 * area, a - affecting the resulting image width, height and resize options
601
 */
602
$area = get(array('area', 'a'));
603
604
verbose("area = $area");
605
606
607
608
/**
609
 * skip-original, so - skip the original image and always process a new image
610
 */
611
$useOriginal = getDefined(array('skip-original', 'so'), false, true);
612
$useOriginalDefault = getConfig('skip_original', false);
613
614
if ($useOriginalDefault === true) {
615
    verbose("skip original is default ON");
616
    $useOriginal = false;
617
}
618
619
verbose("use original = $useOriginal");
620
621
622
623
/**
624
 * quality, q - set level of quality for jpeg images
625
 */
626
$quality = get(array('quality', 'q'));
627
$qualityDefault = getConfig('jpg_quality', null);
628
629
is_null($quality)
630
    or ($quality > 0 and $quality <= 100)
631
    or errorPage('Quality out of range', 404);
632
633
if (is_null($quality) && !is_null($qualityDefault)) {
634
    $quality = $qualityDefault;
635
}
636
637
verbose("quality = $quality");
638
639
640
641
/**
642
 * compress, co - what strategy to use when compressing png images
643
 */
644
$compress = get(array('compress', 'co'));
645
$compressDefault = getConfig('png_compression', null);
646
647
is_null($compress)
648
    or ($compress > 0 and $compress <= 9)
649
    or errorPage('Compress out of range', 404);
650
651
if (is_null($compress) && !is_null($compressDefault)) {
652
    $compress = $compressDefault;
653
}
654
655
verbose("compress = $compress");
656
657
658
659
/**
660
 * save-as, sa - what type of image to save
661
 */
662
$saveAs = get(array('save-as', 'sa'));
663
664
verbose("save as = $saveAs");
665
666
667
668
/**
669
 * scale, s - Processing option, scale up or down the image prior actual resize
670
 */
671
$scale = get(array('scale', 's'));
672
673
is_null($scale)
674
    or ($scale >= 0 and $scale <= 400)
675
    or errorPage('Scale out of range', 404);
676
677
verbose("scale = $scale");
678
679
680
681
/**
682
 * palette, p - Processing option, create a palette version of the image
683
 */
684
$palette = getDefined(array('palette', 'p'), true, false);
685
686
verbose("palette = $palette");
687
688
689
690
/**
691
 * sharpen - Processing option, post filter for sharpen effect
692
 */
693
$sharpen = getDefined('sharpen', true, null);
694
695
verbose("sharpen = $sharpen");
696
697
698
699
/**
700
 * emboss - Processing option, post filter for emboss effect
701
 */
702
$emboss = getDefined('emboss', true, null);
703
704
verbose("emboss = $emboss");
705
706
707
708
/**
709
 * blur - Processing option, post filter for blur effect
710
 */
711
$blur = getDefined('blur', true, null);
712
713
verbose("blur = $blur");
714
715
716
717
/**
718
 * rotateBefore - Rotate the image with an angle, before processing
719
 */
720
$rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb'));
721
722
is_null($rotateBefore)
723
    or ($rotateBefore >= -360 and $rotateBefore <= 360)
724
    or errorPage('RotateBefore out of range', 404);
725
726
verbose("rotateBefore = $rotateBefore");
727
728
729
730
/**
731
 * rotateAfter - Rotate the image with an angle, before processing
732
 */
733
$rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r'));
734
735
is_null($rotateAfter)
736
    or ($rotateAfter >= -360 and $rotateAfter <= 360)
737
    or errorPage('RotateBefore out of range', 404);
738
739
verbose("rotateAfter = $rotateAfter");
740
741
742
743
/**
744
 * autoRotate - Auto rotate based on EXIF information
745
 */
746
$autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false);
747
748
verbose("autoRotate = $autoRotate");
749
750
751
752
/**
753
 * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter()
754
 */
755
$filters = array();
756
$filter = get(array('filter', 'f'));
757
if ($filter) {
758
    $filters[] = $filter;
759
}
760
761
for ($i = 0; $i < 10; $i++) {
762
    $filter = get(array("filter{$i}", "f{$i}"));
763
    if ($filter) {
764
        $filters[] = $filter;
765
    }
766
}
767
768
verbose("filters = " . print_r($filters, 1));
769
770
771
772
/**
773
* json -  output the image as a JSON object with details on the image.
774
* ascii - output the image as ASCII art.
775
 */
776
$outputFormat = getDefined('json', 'json', null);
777
$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
778
779
verbose("outputformat = $outputFormat");
780
781
if ($outputFormat == 'ascii') {
782
    $defaultOptions = getConfig(
783
        'ascii-options',
784
        array(
785
            "characterSet" => 'two',
786
            "scale" => 14,
787
            "luminanceStrategy" => 3,
788
            "customCharacterSet" => null,
789
        )
790
    );
791
    $options = get('ascii');
792
    $options = explode(',', $options);
793
794
    if (isset($options[0]) && !empty($options[0])) {
795
        $defaultOptions['characterSet'] = $options[0];
796
    }
797
798
    if (isset($options[1]) && !empty($options[1])) {
799
        $defaultOptions['scale'] = $options[1];
800
    }
801
802
    if (isset($options[2]) && !empty($options[2])) {
803
        $defaultOptions['luminanceStrategy'] = $options[2];
804
    }
805
806
    if (count($options) > 3) {
807
        // Last option is custom character string
808
        unset($options[0]);
809
        unset($options[1]);
810
        unset($options[2]);
811
        $characterString = implode($options);
812
        $defaultOptions['customCharacterSet'] = $characterString;
813
    }
814
815
    $img->setAsciiOptions($defaultOptions);
816
}
817
818
819
820
821
/**
822
 * dpr - change to get larger image to easier support larger dpr, such as retina.
823
 */
824
$dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1);
825
826
verbose("dpr = $dpr");
827
828
829
830
/**
831
 * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php
832
 */
833
$convolve = get('convolve', null);
834
$convolutionConstant = getConfig('convolution_constant', array());
835
836
// Check if the convolve is matching an existing constant
837
if ($convolve && isset($convolutionConstant)) {
838
    $img->addConvolveExpressions($convolutionConstant);
839
    verbose("convolve constant = " . print_r($convolutionConstant, 1));
840
}
841
842
verbose("convolve = " . print_r($convolve, 1));
843
844
845
846
/**
847
 * no-upscale, nu - Do not upscale smaller image to larger dimension.
848
 */
849
$upscale = getDefined(array('no-upscale', 'nu'), false, true);
850
851
verbose("upscale = $upscale");
852
853
854
855
/**
856
 * Get details for post processing
857
 */
858
$postProcessing = getConfig('postprocessing', array(
859
    'png_lossy'        => false,
860
    'png_lossy_cmd'    => '/usr/local/bin/pngquant --force --output',
861
862
    'png_filter'        => false,
863
    'png_filter_cmd'    => '/usr/local/bin/optipng -q',
864
865
    'png_deflate'       => false,
866
    'png_deflate_cmd'   => '/usr/local/bin/pngout -q',
867
868
    'jpeg_optimize'     => false,
869
    'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize',
870
));
871
872
873
874
/**
875
 * lossy - Do lossy postprocessing, if available.
876
 */
877
$lossy = getDefined(array('lossy'), true, null);
878
879
verbose("lossy = $lossy");
880
881
882
883
/**
884
 * alias - Save resulting image to another alias name.
885
 * Password always apply, must be defined.
886
 */
887
$alias          = get('alias', null);
888
$aliasPath      = getConfig('alias_path', null);
889
$validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#');
890
$aliasTarget    = null;
891
892
if ($alias && $aliasPath && $passwordMatch) {
893
894
    $aliasTarget = $aliasPath . $alias;
895
    $useCache    = false;
896
897
    is_writable($aliasPath)
898
        or errorPage("Directory for alias is not writable.", 403);
899
900
    preg_match($validAliasname, $alias)
901
        or errorPage('Filename for alias contains invalid characters. Do not add extension.', 404);
902
903
} elseif ($alias) {
904
    errorPage('Alias is not enabled in the config file or password not matching.', 403);
905
}
906
907
verbose("alias = $alias");
908
909
910
911
/**
912
 * Add cache control HTTP header.
913
 */
914
$cacheControl = getConfig('cache_control', null);
915
916
if ($cacheControl) {
917
    verbose("cacheControl = $cacheControl");
918
    $img->addHTTPHeader("Cache-Control", $cacheControl);
919
}
920
921
922
923
/**
924
 * Prepare a dummy image and use it as source image.
925
 */
926
if ($dummyImage === true) {
927
    $dummyDir = $cache->getPathToSubdir("dummy");
928
929
    $img->setSaveFolder($dummyDir)
930
        ->setSource($dummyFilename, $dummyDir)
931
        ->setOptions(
932
            array(
933
                'newWidth'  => $newWidth,
934
                'newHeight' => $newHeight,
935
                'bgColor'   => $bgColor,
936
            )
937
        )
938
        ->setJpegQuality($quality)
939
        ->setPngCompression($compress)
940
        ->createDummyImage()
941
        ->generateFilename(null, false)
942
        ->save(null, null, false);
943
944
    $srcImage = $img->getTarget();
945
    $imagePath = null;
946
947
    verbose("src (updated) = $srcImage");
948
}
949
950
951
952
/**
953
 * Prepare a sRGB version of the image and use it as source image.
954
 */
955
$srgbDefault = getConfig('srgb_default', false);
956
$srgbColorProfile = getConfig('srgb_colorprofile', __DIR__ . '/../icc/sRGB_IEC61966-2-1_black_scaled.icc');
957
$srgb = getDefined('srgb', true, null);
958
959
if ($srgb || $srgbDefault) {
960
961
    $filename = $img->convert2sRGBColorSpace(
962
        $srcImage,
963
        $imagePath,
964
        $cache->getPathToSubdir("srgb"),
965
        $srgbColorProfile,
966
        $useCache
967
    );
968
969
    if ($filename) {
970
        $srcImage = $img->getTarget();
971
        $imagePath = null;
972
        verbose("srgb conversion and saved to cache = $srcImage");
973
    } else {
974
        verbose("srgb not op");
975
    }
976
}
977
978
979
980
/**
981
 * Display status
982
 */
983
if ($status) {
984
    $text  = "img.php version = " . CIMAGE_VERSION . "\n";
985
    $text .= "PHP version = " . PHP_VERSION . "\n";
986
    $text .= "Running on: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
987
    $text .= "Allow remote images = $allowRemote\n";
988
989
    $res = $cache->getStatusOfSubdir("");
990
    $text .= "Cache $res\n";
991
992
    $res = $cache->getStatusOfSubdir("remote");
993
    $text .= "Cache remote $res\n";
994
995
    $res = $cache->getStatusOfSubdir("dummy");
996
    $text .= "Cache dummy $res\n";
997
998
    $res = $cache->getStatusOfSubdir("srgb");
999
    $text .= "Cache srgb $res\n";
1000
1001
    $res = $cache->getStatusOfSubdir($fastTrackCache);
1002
    $text .= "Cache fasttrack $res\n";
1003
1004
    $text .= "Alias path writable = " . is_writable($aliasPath) . "\n";
1005
1006
    $no = extension_loaded('exif') ? null : 'NOT';
1007
    $text .= "Extension exif is $no loaded.<br>";
1008
1009
    $no = extension_loaded('curl') ? null : 'NOT';
1010
    $text .= "Extension curl is $no loaded.<br>";
1011
1012
    $no = extension_loaded('imagick') ? null : 'NOT';
1013
    $text .= "Extension imagick is $no loaded.<br>";
1014
1015
    $no = extension_loaded('gd') ? null : 'NOT';
1016
    $text .= "Extension gd is $no loaded.<br>";
1017
1018
    $text .= checkExternalCommand("PNG LOSSY", $postProcessing["png_lossy"], $postProcessing["png_lossy_cmd"]);
0 ignored issues
show
Are you sure the usage of checkExternalCommand('PN...ssing['png_lossy_cmd']) is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1019
    $text .= checkExternalCommand("PNG FILTER", $postProcessing["png_filter"], $postProcessing["png_filter_cmd"]);
0 ignored issues
show
Are you sure the usage of checkExternalCommand('PN...sing['png_filter_cmd']) is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1020
    $text .= checkExternalCommand("PNG DEFLATE", $postProcessing["png_deflate"], $postProcessing["png_deflate_cmd"]);
0 ignored issues
show
Are you sure the usage of checkExternalCommand('PN...ing['png_deflate_cmd']) is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1021
    $text .= checkExternalCommand("JPEG OPTIMIZE", $postProcessing["jpeg_optimize"], $postProcessing["jpeg_optimize_cmd"]);
0 ignored issues
show
Are you sure the usage of checkExternalCommand('JP...g['jpeg_optimize_cmd']) is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1022
1023
    if (!$no) {
1024
        $text .= print_r(gd_info(), 1);
1025
    }
1026
1027
    echo <<<EOD
1028
<!doctype html>
1029
<html lang=en>
1030
<meta charset=utf-8>
1031
<title>CImage status</title>
1032
<pre>$text</pre>
1033
EOD;
1034
    exit;
1035
}
1036
1037
1038
1039
/**
1040
 * Log verbose details to file
1041
 */
1042
if ($verboseFile) {
1043
    $img->setVerboseToFile("$cachePath/log.txt");
1044
}
1045
1046
1047
1048
/**
1049
 * Hook after img.php configuration and before processing with CImage
1050
 */
1051
$hookBeforeCImage = getConfig('hook_before_CImage', null);
1052
1053
if (is_callable($hookBeforeCImage)) {
1054
    verbose("hookBeforeCImage activated");
1055
1056
    $allConfig = $hookBeforeCImage($img, array(
1057
            // Options for calculate dimensions
1058
            'newWidth'  => $newWidth,
1059
            'newHeight' => $newHeight,
1060
            'aspectRatio' => $aspectRatio,
1061
            'keepRatio' => $keepRatio,
1062
            'cropToFit' => $cropToFit,
1063
            'fillToFit' => $fillToFit,
1064
            'crop'      => $crop,
1065
            'area'      => $area,
1066
            'upscale'   => $upscale,
1067
1068
            // Pre-processing, before resizing is done
1069
            'scale'        => $scale,
1070
            'rotateBefore' => $rotateBefore,
1071
            'autoRotate'   => $autoRotate,
1072
1073
            // General processing options
1074
            'bgColor'    => $bgColor,
1075
1076
            // Post-processing, after resizing is done
1077
            'palette'   => $palette,
1078
            'filters'   => $filters,
1079
            'sharpen'   => $sharpen,
1080
            'emboss'    => $emboss,
1081
            'blur'      => $blur,
1082
            'convolve'  => $convolve,
1083
            'rotateAfter' => $rotateAfter,
1084
1085
            // Output format
1086
            'outputFormat' => $outputFormat,
1087
            'dpr'          => $dpr,
1088
1089
            // Other
1090
            'postProcessing' => $postProcessing,
1091
            'lossy' => $lossy,
1092
    ));
1093
    verbose(print_r($allConfig, 1));
1094
    extract($allConfig);
1095
}
1096
1097
1098
1099
/**
1100
 * Display image if verbose mode
1101
 */
1102
if ($verbose) {
1103
    $query = array();
1104
    parse_str($_SERVER['QUERY_STRING'], $query);
1105
    unset($query['verbose']);
1106
    unset($query['v']);
1107
    unset($query['nocache']);
1108
    unset($query['nc']);
1109
    unset($query['json']);
1110
    $url1 = '?' . htmlentities(urldecode(http_build_query($query)));
1111
    $url2 = '?' . urldecode(http_build_query($query));
1112
    echo <<<EOD
1113
<!doctype html>
1114
<html lang=en>
1115
<meta charset=utf-8>
1116
<title>CImage verbose output</title>
1117
<style>body{background-color: #ddd}</style>
1118
<a href=$url1><code>$url1</code></a><br>
1119
<img src='{$url1}' />
1120
<pre id="json"></pre>
1121
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
1122
<script type="text/javascript">
1123
window.getDetails = function (url, id) {
1124
  $.getJSON(url, function(data) {
1125
    element = document.getElementById(id);
1126
    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 : '');
1127
  });
1128
}
1129
</script>
1130
<script type="text/javascript">window.getDetails("{$url2}&json", "json")</script>
1131
EOD;
1132
}
1133
1134
1135
1136
/**
1137
 * Load, process and output the image
1138
 */
1139
$img->log("Incoming arguments: " . print_r(verbose(), 1))
0 ignored issues
show
Are you sure the usage of verbose() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1140
    ->setSaveFolder($cachePath)
1141
    ->useCache($useCache)
1142
    ->setSource($srcImage, $imagePath)
1143
    ->setOptions(
1144
        array(
1145
            // Options for calculate dimensions
1146
            'newWidth'  => $newWidth,
1147
            'newHeight' => $newHeight,
1148
            'aspectRatio' => $aspectRatio,
1149
            'keepRatio' => $keepRatio,
1150
            'cropToFit' => $cropToFit,
1151
            'fillToFit' => $fillToFit,
1152
            'crop'      => $crop,
1153
            'area'      => $area,
1154
            'upscale'   => $upscale,
1155
1156
            // Pre-processing, before resizing is done
1157
            'scale'        => $scale,
1158
            'rotateBefore' => $rotateBefore,
1159
            'autoRotate'   => $autoRotate,
1160
1161
            // General processing options
1162
            'bgColor'    => $bgColor,
1163
1164
            // Post-processing, after resizing is done
1165
            'palette'   => $palette,
1166
            'filters'   => $filters,
1167
            'sharpen'   => $sharpen,
1168
            'emboss'    => $emboss,
1169
            'blur'      => $blur,
1170
            'convolve'  => $convolve,
1171
            'rotateAfter' => $rotateAfter,
1172
1173
            // Output format
1174
            'outputFormat' => $outputFormat,
1175
            'dpr'          => $dpr,
1176
1177
            // Postprocessing using external tools
1178
            'lossy' => $lossy,
1179
        )
1180
    )
1181
    ->loadImageDetails()
1182
    ->initDimensions()
1183
    ->calculateNewWidthAndHeight()
1184
    ->setSaveAsExtension($saveAs)
1185
    ->setJpegQuality($quality)
1186
    ->setPngCompression($compress)
1187
    ->useOriginalIfPossible($useOriginal)
1188
    ->generateFilename($cachePath)
1189
    ->useCacheIfPossible($useCache)
1190
    ->load()
1191
    ->preResize()
1192
    ->resize()
1193
    ->postResize()
1194
    ->setPostProcessingOptions($postProcessing)
1195
    ->save()
1196
    ->linkToCacheFile($aliasTarget)
1197
    ->output();
1198