1<?php
2/**
3 * XOOPS image access/edit
4 *
5 * You may not change or alter any portion of this comment or credits
6 * of supporting developers from this source code or any supporting source code
7 * which is considered copyrighted (c) material of the original comment or credit authors.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * @copyright       (c) 2000-2016 XOOPS Project (www.xoops.org)
13 * @license             GNU GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html)
14 * @package             core
15 * @since               2.5.7
16 * @author              luciorota <lucio.rota@gmail.com>, Joe Lencioni <joe@shiftingpixel.com>
17 *
18 * Enhanced image access/edit
19 * This enhanced version is very useful in many cases, for example when you need a
20 * smallest version of an image. This script uses Xoops cache to minimize server load.
21 *
22 *
23 * Parameters need to be passed in through the URL's query string:
24 * @param int      id             Xoops image id;
25 * @param string   url            relative to XOOPS_ROOT_PATH, path of local image starting with "/"
26 *                                 (e.g. /images/toast.jpg);
27 * @param string   src            relative to XOOPS_ROOT_PATH, path of local image starting with "/"
28 * @param int      width          (optional) maximum width of final image in pixels (e.g. 700);
29 * @param int      height         (optional) maximum height of final image in pixels (e.g. 700);
30 * @param string   color          (optional) background hex color for filling transparent PNGs (e.g. 900 or 16a942);
31 * @param string   cropratio      (optional) ratio of width to height to crop final image (e.g. 1:1 or 3:2);
32 * @param boolean  nocache        (optional) don't read image from the cache;
33 * @param boolean  noservercache  (optional) don't read image from the server cache;
34 * @param boolean  nobrowsercache (optional) don't read image from the browser cache;
35 * @param int      quality        (optional, 0-100, default: 90) quality of output image;
36 * @param mixed    filter         (optional, imagefilter 2nd, 3rd, 4th, 5th arguments, more info on php.net
37 *                                 manual) a filter or an array of filters;
38 * @param int      radius         (optional, 1, 2, 3 or 4 integer values, CW) round corner radius
39 * @param float    angle          (optional), rotation angle)
40 *
41 */
42
43/* @example         image.php
44 * Resizing a JPEG:
45 * <img src="/image.php?url=image-name.jpg&width=100&height=100" alt="Don't forget your alt text" />
46 * Resizing and cropping a JPEG into a square:
47 * <img src="/image.php?url=image-name.jpg?width=100&height=100&cropratio=1:1" alt="Don't forget your alt text" />
48 * Matting a PNG with #990000:
49 * <img src="/image.php?url=image-name.png?color=900&image=/path/to/image.png" alt="Don't forget your alt text" />
50 * Apply a filter:
51 * <img src="/image.php?url=/path/to/image.png&filter=IMG_FILTER_COLORIZE,128,60,256" alt="Don't forget the alt text" />
52 * Apply more filters (array) :
53 * <img src="/image.php?url=/path/to/image.png&filter[]=IMG_FILTER_GRAYSCALE&filter[]=IMG_FILTER_COLORIZE,128,60,256" />
54 * Round the image corners:
55 * All corners with same radius:
56 * <img src="/image.php?url=/path/to/image.png&radius=20" alt="Don't forget your alt text" />
57 * Left and right corners with different radius (20 for left corners and 40 for right corners)
58 * <img src="/image.php?url=/path/to/image.png&radius=20,40" alt="Don't forget your alt text" />
59 * 4 corners, 4 radius, clockwise order
60 * <img src="/image.php?url=/path/to/image.png&radius=20,40,0,10" alt="Don't forget your alt text" />
61 *
62 */
63define('MEMORY_TO_ALLOCATE', '100M');
64define('DEFAULT_IMAGE_QUALITY', 90);
65define('DEFAULT_BACKGROUND_COLOR', '000000');
66define('ONLY_LOCAL_IMAGES', true);
67define('ENABLE_IMAGEFILTER', true); // Set to false to avoid excessive server load
68define('ENABLE_ROUNDCORNER', true); // Set to false to avoid excessive server load
69define('ENABLE_IMAGEROTATE', true); // Set to false to avoid excessive server load
70
71if (get_magic_quotes_runtime()) {
72    set_magic_quotes_runtime(false); // will never get called on PHP 5.4+
73}
74if (function_exists('mb_http_output')) {
75    mb_http_output('pass');
76}
77
78$xoopsOption['nocommon'] = true;
79require_once __DIR__ . '/mainfile.php';
80
81include_once __DIR__ . '/include/defines.php';
82include_once __DIR__ . '/include/functions.php';
83include_once __DIR__ . '/include/version.php';
84include_once __DIR__ . '/kernel/object.php';
85include_once __DIR__ . '/class/xoopsload.php';
86include_once __DIR__ . '/class/preload.php';
87include_once __DIR__ . '/class/module.textsanitizer.php';
88include_once __DIR__ . '/class/database/databasefactory.php';
89require_once __DIR__ . '/class/criteria.php';
90XoopsLoad::load('xoopslogger');
91$xoopsLogger = XoopsLogger::getInstance();
92$xoopsLogger->startTime();
93error_reporting(0);
94
95/**
96 * @param $etag
97 * @param $lastModified
98 * @return null
99 */
100function doConditionalGet($etag, $lastModified)
101{
102    header("Last-Modified: $lastModified");
103    header("ETag: \"{$etag}\"");
104    $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false;
105    $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
106        ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
107    if (!$ifModifiedSince && !$ifNoneMatch) {
108        return null;
109    }
110    if ($ifNoneMatch && $ifNoneMatch != $etag && $ifNoneMatch != '"' . $etag . '"') {
111        return null;
112    } // etag is there but doesn't match
113    if ($ifModifiedSince && $ifModifiedSince != $lastModified) {
114        return null;
115    } // if-modified-since is there but doesn't match
116    // Nothing has changed since their last request - serve a 304 and exit
117    header('HTTP/1.1 304 Not Modified');
118    exit();
119}
120
121/**
122 * ref: http://www.tricksofit.com/2014/08/round-corners-on-image-using-php-and-gd-library
123 *
124 * @param resource $sourceImage GD Image resource
125 * @param int[]    $radii       array(top left, top right, bottom left, bottom right) of pixel radius
126 *                               for each corner. A 0 disables rounding on a corner.
127 *
128 * @return resource
129 */
130function imageCreateCorners($sourceImage, $radii)
131{
132    $q = 2; // quality - improve alpha blending by using larger (*$q) image size
133
134    // find a unique color
135    $tryCounter = 0;
136    do {
137        if (++$tryCounter > 255) {
138            $r = 2;
139            $g = 254;
140            $b = 0;
141            break;
142        }
143        $r = rand(0, 255);
144        $g = rand(0, 255);
145        $b = rand(0, 255);
146    } while (imagecolorexact($sourceImage, $r, $g, $b) < 0);
147
148    $imageWidth = imagesx($sourceImage);
149    $imageHeight = imagesy($sourceImage);
150
151    $workingWidth = $imageWidth * $q;
152    $workingHeight = $imageHeight * $q;
153
154    $workingImage= imagecreatetruecolor($workingWidth, $workingHeight);
155    $alphaColor = imagecolorallocatealpha($workingImage, $r, $g, $b, 127);
156    imagealphablending($workingImage, false);
157    imagesavealpha($workingImage, true);
158    imagefilledrectangle($workingImage, 0, 0, $workingWidth, $workingHeight, $alphaColor);
159
160    imagefill($workingImage, 0, 0, $alphaColor);
161    imagecopyresampled($workingImage, $sourceImage, 0, 0, 0, 0, $workingWidth, $workingHeight, $imageWidth, $imageHeight);
162    if (0 < ($radius = $radii[0] * $q)) { // left top
163        imagearc($workingImage, $radius - 1, $radius - 1, $radius * 2, $radius * 2, 180, 270, $alphaColor);
164        imagefilltoborder($workingImage, 0, 0, $alphaColor, $alphaColor);
165    }
166    if (0 < ($radius = $radii[1] * $q)) { // right top
167        imagearc($workingImage, $workingWidth - $radius, $radius - 1, $radius * 2, $radius * 2, 270, 0, $alphaColor);
168        imagefilltoborder($workingImage, $workingWidth - 1, 0, $alphaColor, $alphaColor);
169    }
170    if (0 < ($radius = $radii[2] * $q)) { // left bottom
171        imagearc($workingImage, $radius - 1, $workingHeight - $radius, $radius * 2, $radius * 2, 90, 180, $alphaColor);
172        imagefilltoborder($workingImage, 0, $workingHeight - 1, $alphaColor, $alphaColor);
173    }
174    if (0 < ($radius = $radii[3] * $q)) { // right bottom
175        imagearc($workingImage, $workingWidth - $radius, $workingHeight - $radius, $radius * 2, $radius * 2, 0, 90, $alphaColor);
176        imagefilltoborder($workingImage, $workingWidth - 1, $workingHeight - 1, $alphaColor, $alphaColor);
177    }
178    imagealphablending($workingImage, true);
179    imagecolortransparent($workingImage, $alphaColor);
180
181    // scale back down to original size
182    $destinationImage = imagecreatetruecolor($imageWidth, $imageHeight);
183    imagealphablending($destinationImage, false);
184    imagesavealpha($destinationImage, true);
185    imagefilledrectangle($destinationImage, 0, 0, $imageWidth, $imageHeight, $alphaColor);
186    imagecopyresampled($destinationImage, $workingImage, 0, 0, 0, 0, $imageWidth, $imageHeight, $workingWidth, $workingHeight);
187
188    // imagedestroy($sourceImage);
189    imagedestroy($workingImage);
190
191    return $destinationImage;
192}
193
194/**
195 * @param $orig
196 * @param $final
197 *
198 * @return mixed
199 */
200function findSharp($orig, $final)
201{
202    // Function from Ryan Rud (http://adryrun.com)
203    $final *= (750.0 / $orig);
204    $a = 52;
205    $b = -0.27810650887573124;
206    $c = .00047337278106508946;
207    $result = $a + $b * $final + $c * $final * $final;
208
209    return max(round($result), 0);
210}
211
212/**
213 * issue an error for bad request
214 *
215 * Many different issues end up here, so message is generic 404. This keeps us from leaking info by probing
216 */
217function exit404BadReq()
218{
219    header('HTTP/1.1 404 Not Found');
220    exit();
221}
222
223/**
224 * check local image url for possible issues
225 *
226 * @param string $imageUrl url to local image starting at site root with a '/'
227 *
228 * @return bool true if name is acceptable, exit if not
229 */
230function imageFilenameCheck($imageUrl)
231{
232    if ($imageUrl[0] !== '/') { // must start with slash
233        exit404BadReq();
234    }
235
236    if ($imageUrl === '/') { // can't be empty
237        exit404BadReq();
238    }
239
240    if (preg_match('/(\.\.|<|>|\:|[[:cntrl:]])/', $imageUrl)) { // no "..", "<", ">", ":" or controls
241        exit404BadReq();
242    }
243
244    $fullPath = XOOPS_ROOT_PATH . $imageUrl;
245    if (strpos($fullPath, XOOPS_VAR_PATH) === 0) { // no access to data (shouldn't be in root, but...)
246        exit404BadReq();
247    }
248    if (strpos($fullPath, XOOPS_PATH) === 0) { // no access to lib (shouldn't be in root, but...)
249        exit404BadReq();
250    }
251
252    return true;
253}
254
255/*
256 * Get image
257 */
258// Get id (Xoops image) or url or src (standard image)
259$imageId = isset($_GET['id']) ? (int)$_GET['id'] : false;
260$imageUrl = isset($_GET['url']) ? (string)$_GET['url'] : (isset($_GET['src']) ? (string)$_GET['src'] : false);
261if (!empty($imageId)) {
262    // If image is a Xoops image
263    /* @var XoopsImageHandler $imageHandler */
264    $imageHandler = xoops_getHandler('image');
265    $criteria = new CriteriaCompo(new Criteria('i.image_display', true));
266    $criteria->add(new Criteria('i.image_id', $imageId));
267    $images = $imageHandler->getObjects($criteria, false, true);
268    if (count($images) != 1) {
269        // No Xoops images or to many Xoops images
270        header('Content-type: image/gif');
271        readfile(XOOPS_UPLOAD_PATH . '/blank.gif');
272        exit();
273    }
274    $image = $images[0];
275    // Get image category
276    $imgcatId = $image->getVar('imgcat_id');
277    $imgcatHandler = xoops_getHandler('imagecategory');
278    if (!$imgcat = $imgcatHandler->get($imgcatId)) {
279        // No Image category
280        header('Content-type: image/gif');
281        readfile(XOOPS_UPLOAD_PATH . '/blank.gif');
282        exit();
283    }
284    // Get image data
285    $imageFilename = $image->getVar('image_name'); // image filename
286    $imageMimetype = $image->getVar('image_mimetype');
287    $imageCreatedTime = $image->getVar('image_created'); // image creation date
288    if ($imgcat->getVar('imgcat_storetype') === 'db') {
289        $imagePath = null;
290        $imageData = $image->getVar('image_body');
291    } else {
292        $imagePath = XOOPS_UPLOAD_PATH . '/' . $image->getVar('image_name');
293        $imageData = file_get_contents($imagePath);
294    }
295    $sourceImage = imagecreatefromstring($imageData);
296    $imageWidth = imagesx($sourceImage);
297    $imageHeight = imagesy($sourceImage);
298} elseif (!empty($imageUrl)) {
299    // If image is a standard image
300    if (ONLY_LOCAL_IMAGES) {
301        // Images must be local files, so for convenience we strip the domain if it's there
302        $imageUrl = str_replace(XOOPS_URL, '', $imageUrl);
303
304        // will exit on any unacceptable urls
305        imageFilenameCheck($imageUrl);
306
307        $imagePath = XOOPS_ROOT_PATH . $imageUrl;
308        if (!file_exists($imagePath)) {
309            exit404BadReq();
310        }
311    } else {
312        if ($imageUrl{0} === '/') {
313            $imageUrl = substr($imageUrl, 0, 1);
314        }
315        $imagePath = $imageUrl;
316    }
317    // Get the size and MIME type of the requested image
318    $imageFilename = basename($imagePath);  // image filename
319    $imagesize = getimagesize($imagePath);
320    $imageWidth = $imagesize[0];
321    $imageHeight = $imagesize[1];
322    $imageMimetype = $imagesize['mime'];
323    $imageCreatedTime = filemtime($imagePath); // image creation date
324    $imageData = file_get_contents($imagePath);
325    switch ($imageMimetype) {
326        case 'image/gif':
327            $sourceImage = imagecreatefromgif($imagePath);
328            break;
329        case 'image/png':
330            $sourceImage = imagecreatefrompng($imagePath);
331            break;
332        case 'image/jpeg':
333            $sourceImage = imagecreatefromjpeg($imagePath);
334            break;
335        default:
336            exit404BadReq();
337            break;
338    }
339} else {
340    // No id, no url, no src parameters
341    header('Content-type: image/gif');
342    readfile(XOOPS_ROOT_PATH . '/uploads/blank.gif');
343    exit();
344}
345
346/*
347 * Use Xoops cache
348 */
349// Get image_data from the Xoops cache only if the edited image has been cached after the latest modification
350// of the original image
351xoops_load('XoopsCache');
352$edited_image_filename = 'editedimage_' . md5($_SERVER['REQUEST_URI']) . '_' . $imageFilename;
353$cached_image = XoopsCache::read($edited_image_filename);
354if (!isset($_GET['nocache']) && !isset($_GET['noservercache']) && !empty($cached_image)
355    && ($cached_image['cached_time'] >= $imageCreatedTime)) {
356    header("Content-type: {$imageMimetype}");
357    header('Content-Length: ' . strlen($cached_image['image_data']));
358    echo $cached_image['image_data'];
359    exit();
360}
361
362/*
363 * Get/check editing parameters
364 */
365// width, height
366$max_width = isset($_GET['width']) ? (int)$_GET['width'] : false;
367$max_height = isset($_GET['height']) ? (int)$_GET['height'] : false;
368// If either a max width or max height are not specified, we default to something large so the unspecified
369// dimension isn't a constraint on our resized image.
370// If neither are specified but the color is, we aren't going to be resizing at all, just coloring.
371if (!$max_width && $max_height) {
372    $max_width = PHP_INT_MAX;
373} elseif ($max_width && !$max_height) {
374    $max_height = PHP_INT_MAX;
375} elseif (!$max_width && !$max_height) {
376    $max_width = $imageWidth;
377    $max_height = $imageHeight;
378}
379
380// color
381$color = isset($_GET['color']) ? preg_replace('/[^0-9a-fA-F]/', '', (string)$_GET['color']) : false;
382
383// filter, radius, angle
384$filter = isset($_GET['filter']) ? $_GET['filter'] : false;
385$radius = isset($_GET['radius']) ? (string)$_GET['radius'] : false;
386$angle = isset($_GET['angle']) ? (float)$_GET['angle'] : false;
387
388// If we don't have a width or height or color or filter or radius or rotate we simply output the original
389// image and exit
390if (empty($_GET['width']) && empty($_GET['height']) && empty($_GET['color']) && empty($_GET['filter'])
391    && empty($_GET['radius']) && empty($_GET['angle'])) {
392    $last_modified_string = gmdate('D, d M Y H:i:s', $imageCreatedTime) . ' GMT';
393    $etag = md5($imageData);
394    doConditionalGet($etag, $last_modified_string);
395    header("Content-type: {$imageMimetype}");
396    header('Content-Length: ' . strlen($imageData));
397    echo $imageData;
398    exit();
399}
400
401// cropratio
402$offset_x = 0;
403$offset_y = 0;
404if (isset($_GET['cropratio'])) {
405    $crop_ratio = explode(':', (string)$_GET['cropratio']);
406    if (count($crop_ratio) == 2) {
407        $ratio_computed = $imageWidth / $imageHeight;
408        $crop_radio_computed = (float)$crop_ratio[0] / (float)$crop_ratio[1];
409        if ($ratio_computed < $crop_radio_computed) {
410            // Image is too tall so we will crop the top and bottom
411            $orig_height = $imageHeight;
412            $imageHeight = $imageWidth / $crop_radio_computed;
413            $offset_y = ($orig_height - $imageHeight) / 2;
414        } elseif ($ratio_computed > $crop_radio_computed) {
415            // Image is too wide so we will crop off the left and right sides
416            $orig_width = $imageWidth;
417            $imageWidth = $imageHeight * $crop_radio_computed;
418            $offset_x = ($orig_width - $imageWidth) / 2;
419        }
420    }
421}
422// Setting up the ratios needed for resizing. We will compare these below to determine how to resize the image
423// (based on height or based on width)
424$xRatio = $max_width / $imageWidth;
425$yRatio = $max_height / $imageHeight;
426if ($xRatio * $imageHeight < $max_height) {
427    // Resize the image based on width
428    $tn_height = ceil($xRatio * $imageHeight);
429    $tn_width = $max_width;
430} else {
431    // Resize the image based on height
432    $tn_width = ceil($yRatio * $imageWidth);
433    $tn_height = $max_height;
434}
435
436// quality
437$quality = isset($_GET['quality']) ? (int)$_GET['quality'] : DEFAULT_IMAGE_QUALITY;
438
439/*
440 * Start image editing
441 */
442// We don't want to run out of memory
443ini_set('memory_limit', MEMORY_TO_ALLOCATE);
444
445// Set up a blank canvas for our resized image (destination)
446$destination_image = imagecreatetruecolor($tn_width, $tn_height);
447
448imagealphablending($destination_image, false);
449imagesavealpha($destination_image, true);
450$transparent = imagecolorallocatealpha($destination_image, 255, 255, 255, 127);
451imagefilledrectangle($destination_image, 0, 0, $tn_width, $tn_height, $transparent);
452
453// Set up the appropriate image handling functions based on the original image's mime type
454switch ($imageMimetype) {
455    case 'image/gif':
456        // We will be converting GIFs to PNGs to avoid transparency issues when resizing GIFs
457        // This is maybe not the ideal solution, but IE6 can suck it
458        $output_function = 'imagepng';
459        $imageMimetype = 'image/png'; // We need to convert GIFs to PNGs
460        $do_sharpen = false;
461        $quality = round(10 - ($quality / 10)); // We are converting the GIF to a PNG and PNG needs a compression
462                                                // level of 0 (no compression) through 9 (max)
463        break;
464    case 'image/png':
465    case 'image/x-png':
466        $output_function = 'imagepng';
467        $do_sharpen = false;
468        $quality = round(10 - ($quality / 10)); // PNG needs a compression level of 0 (no compression) through 9
469        break;
470    case 'image/jpeg':
471    case 'image/pjpeg':
472        $output_function = 'imagejpeg';
473        $do_sharpen = true;
474        break;
475    default:
476        exit404BadReq();
477        break;
478}
479
480// Resample the original image into the resized canvas we set up earlier
481imagecopyresampled($destination_image, $sourceImage, 0, 0, $offset_x, $offset_y, $tn_width, $tn_height, $imageWidth, $imageHeight);
482
483// Set background color
484if (in_array($imageMimetype, array('image/gif', 'image/png'))) {
485    if (!$color) {
486        // If this is a GIF or a PNG, we need to set up transparency
487        imagealphablending($destination_image, false);
488        imagesavealpha($destination_image, true);
489        $png_transparency = imagecolorallocatealpha($destination_image, 0, 0, 0, 127);
490        imagefill($destination_image, 0, 0, $png_transparency);
491    } else {
492        // Fill the background with the specified color for matting purposes
493        if ($color[0] === '#') {
494            $color = substr($color, 1);
495        }
496        $background = false;
497        if (strlen($color) == 6) {
498            $background = imagecolorallocate(
499                $destination_image,
500                intval($color[0] . $color[1], 16),
501                intval($color[2] . $color[3], 16),
502                intval($color[4] . $color[5], 16)
503            );
504        } elseif (strlen($color) == 3) {
505            $background = imagecolorallocate(
506                $destination_image,
507                intval($color[0] . $color[0], 16),
508                intval($color[1] . $color[1], 16),
509                intval($color[2] . $color[2], 16)
510            );
511        }
512        if ($background) {
513            imagefill($destination_image, 0, 0, $background);
514        }
515    }
516} else {
517    if (!$color) {
518        $color = DEFAULT_BACKGROUND_COLOR;
519    }
520    // Fill the background with the specified color for matting purposes
521    if ($color[0] === '#') {
522        $color = substr($color, 1);
523    }
524    $background = false;
525    if (strlen($color) == 6) {
526        $background = imagecolorallocate(
527            $destination_image,
528            intval($color[0] . $color[1], 16),
529            intval($color[2] . $color[3], 16),
530            intval($color[4] . $color[5], 16)
531        );
532    } elseif (strlen($color) == 3) {
533        $background = imagecolorallocate(
534            $destination_image,
535            intval($color[0] . $color[0], 16),
536            intval($color[1] . $color[1], 16),
537            intval($color[2] . $color[2], 16)
538        );
539    }
540    if ($background) {
541        imagefill($destination_image, 0, 0, $background);
542    }
543}
544
545// Imagefilter
546if (ENABLE_IMAGEFILTER && !empty($filter)) {
547    $filterSet = (array) $filter;
548    foreach ($filterSet as $currentFilter) {
549        $rawFilterArgs = explode(',', $currentFilter);
550        $filterConst = constant(array_shift($rawFilterArgs));
551        if (null !== $filterConst) { // skip if unknown constant
552            $filterArgs = array();
553            $filterArgs[] = $destination_image;
554            $filterArgs[] = $filterConst;
555            foreach ($rawFilterArgs as $tempValue) {
556                $filterArgs[] = trim($tempValue);
557            }
558            call_user_func_array('imagefilter', $filterArgs);
559        }
560    }
561}
562
563// Round corners
564if (ENABLE_ROUNDCORNER && !empty($radius)) {
565    $radii = explode(',', $radius);
566    switch (count($radii)) {
567        case 1:
568            $radii[3] = $radii[2] = $radii[1] = $radii[0];
569            break;
570        case 2:
571            $radii[3] = $radii[0];
572            $radii[2] = $radii[1];
573            break;
574        case 3:
575            $radii[3] = $radii[0];
576            break;
577        case 4:
578            // NOP
579            break;
580    }
581
582    $destination_image = imageCreateCorners($destination_image, $radii);
583    // we need png to support the alpha corners correctly
584    if ($imageMimetype === 'image/jpeg') {
585        $output_function = 'imagepng';
586        $imageMimetype = 'image/png';
587        $do_sharpen = false;
588        $quality = round(10 - ($quality / 10));
589    }
590}
591
592// Imagerotate
593if (ENABLE_IMAGEROTATE && !empty($angle)) {
594    $destination_image = imagerotate($destination_image, $angle, $background, 0);
595}
596
597if ($do_sharpen) {
598    // Sharpen the image based on two things:
599    // (1) the difference between the original size and the final size
600    // (2) the final size
601    $sharpness = findSharp($imageWidth, $tn_width);
602    $sharpen_matrix = array(
603        array(-1, -2, -1),
604        array(-2, $sharpness + 12, -2),
605        array(-1, -2, -1));
606    $divisor = $sharpness;
607    $offset = 0;
608    imageconvolution($destination_image, $sharpen_matrix, $divisor, $offset);
609}
610
611// Put the data of the resized image into a variable
612ob_start();
613$output_function($destination_image, null, $quality);
614$imageData = ob_get_contents();
615ob_end_clean();
616// Update $image_created_time
617$imageCreatedTime = time();
618
619// Clean up the memory
620imagedestroy($sourceImage);
621imagedestroy($destination_image);
622
623/*
624 * Write the just edited image into the Xoops cache
625 */
626$cached_image['edited_image_filename'] = $edited_image_filename;
627$cached_image['image_data'] = $imageData;
628$cached_image['cached_time'] = $imageCreatedTime;
629XoopsCache::write($edited_image_filename, $cached_image);
630
631/*
632 * Send the edited image to the browser
633 */
634// See if the browser already has the image
635$last_modified_string = gmdate('D, d M Y H:i:s', $imageCreatedTime) . ' GMT';
636$etag = md5($imageData);
637doConditionalGet($etag, $last_modified_string);
638
639header('HTTP/1.1 200 OK');
640// if image is cacheable
641if (!isset($_GET['nocache']) && !isset($_GET['nobrowsercache'])) {
642    header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $imageCreatedTime) . 'GMT');
643    header('Cache-control: max-age=31536000');
644    header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . 'GMT');
645} else {
646    // "Kill" the browser cache
647    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // past date
648    header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
649    header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1
650    header('Cache-Control: post-check=0, pre-check=0', false);
651    header('Pragma: no-cache'); // HTTP/1.0
652}
653header("Content-type: {$imageMimetype}");
654header("Content-disposition: filename={$imageFilename}");
655header('Content-Length: ' . strlen($imageData));
656echo $imageData;
657