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