PHP Classes

File: htdocs/image.php

Recommend this page to a friend!
  Classes of Michael Beck   Xoops 2.5   htdocs/image.php   Download  
File: htdocs/image.php
Role: Example script
Content type: text/plain
Description: Example script
Class: Xoops 2.5
Modular content management publication system
Author: By
Last change: non-canonical order

In docblock var tags, use "type variableName" more consistently
Date: 5 years ago
Size: 25,385 bytes
 

Contents

Class file image Download
<?php /** * XOOPS image access/edit * * You may not change or alter any portion of this comment or credits * of supporting developers from this source code or any supporting source code * which is considered copyrighted (c) material of the original comment or credit authors. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org) * @license GNU GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html) * @package core * @since 2.5.7 * @author luciorota <lucio.rota@gmail.com>, Joe Lencioni <joe@shiftingpixel.com> * * Enhanced image access/edit * This enhanced version is very useful in many cases, for example when you need a * smallest version of an image. This script uses Xoops cache to minimize server load. * * * Parameters need to be passed in through the URL's query string: * @param int id Xoops image id; * @param string url relative to XOOPS_ROOT_PATH, path of local image starting with "/" * (e.g. /images/toast.jpg); * @param string src relative to XOOPS_ROOT_PATH, path of local image starting with "/" * @param int width (optional) maximum width of final image in pixels (e.g. 700); * @param int height (optional) maximum height of final image in pixels (e.g. 700); * @param string color (optional) background hex color for filling transparent PNGs (e.g. 900 or 16a942); * @param string cropratio (optional) ratio of width to height to crop final image (e.g. 1:1 or 3:2); * @param boolean nocache (optional) don't read image from the cache; * @param boolean noservercache (optional) don't read image from the server cache; * @param boolean nobrowsercache (optional) don't read image from the browser cache; * @param int quality (optional, 0-100, default: 90) quality of output image; * @param mixed filter (optional, imagefilter 2nd, 3rd, 4th, 5th arguments, more info on php.net * manual) a filter or an array of filters; * @param int radius (optional, 1, 2, 3 or 4 integer values, CW) round corner radius * @param float angle (optional), rotation angle) * */ /* @example image.php * Resizing a JPEG: * <img src="/image.php?url=image-name.jpg&width=100&height=100" alt="Don't forget your alt text" /> * Resizing and cropping a JPEG into a square: * <img src="/image.php?url=image-name.jpg?width=100&height=100&cropratio=1:1" alt="Don't forget your alt text" /> * Matting a PNG with #990000: * <img src="/image.php?url=image-name.png?color=900&image=/path/to/image.png" alt="Don't forget your alt text" /> * Apply a filter: * <img src="/image.php?url=/path/to/image.png&filter=IMG_FILTER_COLORIZE,128,60,256" alt="Don't forget the alt text" /> * Apply more filters (array) : * <img src="/image.php?url=/path/to/image.png&filter[]=IMG_FILTER_GRAYSCALE&filter[]=IMG_FILTER_COLORIZE,128,60,256" /> * Round the image corners: * All corners with same radius: * <img src="/image.php?url=/path/to/image.png&radius=20" alt="Don't forget your alt text" /> * Left and right corners with different radius (20 for left corners and 40 for right corners) * <img src="/image.php?url=/path/to/image.png&radius=20,40" alt="Don't forget your alt text" /> * 4 corners, 4 radius, clockwise order * <img src="/image.php?url=/path/to/image.png&radius=20,40,0,10" alt="Don't forget your alt text" /> * */ define('MEMORY_TO_ALLOCATE', '100M'); define('DEFAULT_IMAGE_QUALITY', 90); define('DEFAULT_BACKGROUND_COLOR', '000000'); define('ONLY_LOCAL_IMAGES', true); define('ENABLE_IMAGEFILTER', true); // Set to false to avoid excessive server load define('ENABLE_ROUNDCORNER', true); // Set to false to avoid excessive server load define('ENABLE_IMAGEROTATE', true); // Set to false to avoid excessive server load if (get_magic_quotes_runtime()) { set_magic_quotes_runtime(false); // will never get called on PHP 5.4+ } if (function_exists('mb_http_output')) { mb_http_output('pass'); } $xoopsOption['nocommon'] = true; require_once __DIR__ . '/mainfile.php'; include_once __DIR__ . '/include/defines.php'; include_once __DIR__ . '/include/functions.php'; include_once __DIR__ . '/include/version.php'; include_once __DIR__ . '/kernel/object.php'; include_once __DIR__ . '/class/xoopsload.php'; include_once __DIR__ . '/class/preload.php'; include_once __DIR__ . '/class/module.textsanitizer.php'; include_once __DIR__ . '/class/database/databasefactory.php'; require_once __DIR__ . '/class/criteria.php'; XoopsLoad::load('xoopslogger'); $xoopsLogger = XoopsLogger::getInstance(); $xoopsLogger->startTime(); error_reporting(0); /** * @param $etag * @param $lastModified * @return null */ function doConditionalGet($etag, $lastModified) { header("Last-Modified: $lastModified"); header("ETag: \"{$etag}\""); $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false; $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false; if (!$ifModifiedSince && !$ifNoneMatch) { return null; } if ($ifNoneMatch && $ifNoneMatch != $etag && $ifNoneMatch != '"' . $etag . '"') { return null; } // etag is there but doesn't match if ($ifModifiedSince && $ifModifiedSince != $lastModified) { return null; } // if-modified-since is there but doesn't match // Nothing has changed since their last request - serve a 304 and exit header('HTTP/1.1 304 Not Modified'); exit(); } /** * ref: http://www.tricksofit.com/2014/08/round-corners-on-image-using-php-and-gd-library * * @param resource $sourceImage GD Image resource * @param int[] $radii array(top left, top right, bottom left, bottom right) of pixel radius * for each corner. A 0 disables rounding on a corner. * * @return resource */ function imageCreateCorners($sourceImage, $radii) { $q = 2; // quality - improve alpha blending by using larger (*$q) image size // find a unique color $tryCounter = 0; do { if (++$tryCounter > 255) { $r = 2; $g = 254; $b = 0; break; } $r = rand(0, 255); $g = rand(0, 255); $b = rand(0, 255); } while (imagecolorexact($sourceImage, $r, $g, $b) < 0); $imageWidth = imagesx($sourceImage); $imageHeight = imagesy($sourceImage); $workingWidth = $imageWidth * $q; $workingHeight = $imageHeight * $q; $workingImage= imagecreatetruecolor($workingWidth, $workingHeight); $alphaColor = imagecolorallocatealpha($workingImage, $r, $g, $b, 127); imagealphablending($workingImage, false); imagesavealpha($workingImage, true); imagefilledrectangle($workingImage, 0, 0, $workingWidth, $workingHeight, $alphaColor); imagefill($workingImage, 0, 0, $alphaColor); imagecopyresampled($workingImage, $sourceImage, 0, 0, 0, 0, $workingWidth, $workingHeight, $imageWidth, $imageHeight); if (0 < ($radius = $radii[0] * $q)) { // left top imagearc($workingImage, $radius - 1, $radius - 1, $radius * 2, $radius * 2, 180, 270, $alphaColor); imagefilltoborder($workingImage, 0, 0, $alphaColor, $alphaColor); } if (0 < ($radius = $radii[1] * $q)) { // right top imagearc($workingImage, $workingWidth - $radius, $radius - 1, $radius * 2, $radius * 2, 270, 0, $alphaColor); imagefilltoborder($workingImage, $workingWidth - 1, 0, $alphaColor, $alphaColor); } if (0 < ($radius = $radii[2] * $q)) { // left bottom imagearc($workingImage, $radius - 1, $workingHeight - $radius, $radius * 2, $radius * 2, 90, 180, $alphaColor); imagefilltoborder($workingImage, 0, $workingHeight - 1, $alphaColor, $alphaColor); } if (0 < ($radius = $radii[3] * $q)) { // right bottom imagearc($workingImage, $workingWidth - $radius, $workingHeight - $radius, $radius * 2, $radius * 2, 0, 90, $alphaColor); imagefilltoborder($workingImage, $workingWidth - 1, $workingHeight - 1, $alphaColor, $alphaColor); } imagealphablending($workingImage, true); imagecolortransparent($workingImage, $alphaColor); // scale back down to original size $destinationImage = imagecreatetruecolor($imageWidth, $imageHeight); imagealphablending($destinationImage, false); imagesavealpha($destinationImage, true); imagefilledrectangle($destinationImage, 0, 0, $imageWidth, $imageHeight, $alphaColor); imagecopyresampled($destinationImage, $workingImage, 0, 0, 0, 0, $imageWidth, $imageHeight, $workingWidth, $workingHeight); // imagedestroy($sourceImage); imagedestroy($workingImage); return $destinationImage; } /** * @param $orig * @param $final * * @return mixed */ function findSharp($orig, $final) { // Function from Ryan Rud (http://adryrun.com) $final *= (750.0 / $orig); $a = 52; $b = -0.27810650887573124; $c = .00047337278106508946; $result = $a + $b * $final + $c * $final * $final; return max(round($result), 0); } /** * issue an error for bad request * * Many different issues end up here, so message is generic 404. This keeps us from leaking info by probing */ function exit404BadReq() { header('HTTP/1.1 404 Not Found'); exit(); } /** * check local image url for possible issues * * @param string $imageUrl url to local image starting at site root with a '/' * * @return bool true if name is acceptable, exit if not */ function imageFilenameCheck($imageUrl) { if ($imageUrl[0] !== '/') { // must start with slash exit404BadReq(); } if ($imageUrl === '/') { // can't be empty exit404BadReq(); } if (preg_match('/(\.\.|<|>|\:|[[:cntrl:]])/', $imageUrl)) { // no "..", "<", ">", ":" or controls exit404BadReq(); } $fullPath = XOOPS_ROOT_PATH . $imageUrl; if (strpos($fullPath, XOOPS_VAR_PATH) === 0) { // no access to data (shouldn't be in root, but...) exit404BadReq(); } if (strpos($fullPath, XOOPS_PATH) === 0) { // no access to lib (shouldn't be in root, but...) exit404BadReq(); } return true; } /* * Get image */ // Get id (Xoops image) or url or src (standard image) $imageId = isset($_GET['id']) ? (int)$_GET['id'] : false; $imageUrl = isset($_GET['url']) ? (string)$_GET['url'] : (isset($_GET['src']) ? (string)$_GET['src'] : false); if (!empty($imageId)) { // If image is a Xoops image /* @var XoopsImageHandler $imageHandler */ $imageHandler = xoops_getHandler('image'); $criteria = new CriteriaCompo(new Criteria('i.image_display', true)); $criteria->add(new Criteria('i.image_id', $imageId)); $images = $imageHandler->getObjects($criteria, false, true); if (count($images) != 1) { // No Xoops images or to many Xoops images header('Content-type: image/gif'); readfile(XOOPS_UPLOAD_PATH . '/blank.gif'); exit(); } $image = $images[0]; // Get image category $imgcatId = $image->getVar('imgcat_id'); $imgcatHandler = xoops_getHandler('imagecategory'); if (!$imgcat = $imgcatHandler->get($imgcatId)) { // No Image category header('Content-type: image/gif'); readfile(XOOPS_UPLOAD_PATH . '/blank.gif'); exit(); } // Get image data $imageFilename = $image->getVar('image_name'); // image filename $imageMimetype = $image->getVar('image_mimetype'); $imageCreatedTime = $image->getVar('image_created'); // image creation date if ($imgcat->getVar('imgcat_storetype') === 'db') { $imagePath = null; $imageData = $image->getVar('image_body'); } else { $imagePath = XOOPS_UPLOAD_PATH . '/' . $image->getVar('image_name'); $imageData = file_get_contents($imagePath); } $sourceImage = imagecreatefromstring($imageData); $imageWidth = imagesx($sourceImage); $imageHeight = imagesy($sourceImage); } elseif (!empty($imageUrl)) { // If image is a standard image if (ONLY_LOCAL_IMAGES) { // Images must be local files, so for convenience we strip the domain if it's there $imageUrl = str_replace(XOOPS_URL, '', $imageUrl); // will exit on any unacceptable urls imageFilenameCheck($imageUrl); $imagePath = XOOPS_ROOT_PATH . $imageUrl; if (!file_exists($imagePath)) { exit404BadReq(); } } else { if ($imageUrl{0} === '/') { $imageUrl = substr($imageUrl, 0, 1); } $imagePath = $imageUrl; } // Get the size and MIME type of the requested image $imageFilename = basename($imagePath); // image filename $imagesize = getimagesize($imagePath); $imageWidth = $imagesize[0]; $imageHeight = $imagesize[1]; $imageMimetype = $imagesize['mime']; $imageCreatedTime = filemtime($imagePath); // image creation date $imageData = file_get_contents($imagePath); switch ($imageMimetype) { case 'image/gif': $sourceImage = imagecreatefromgif($imagePath); break; case 'image/png': $sourceImage = imagecreatefrompng($imagePath); break; case 'image/jpeg': $sourceImage = imagecreatefromjpeg($imagePath); break; default: exit404BadReq(); break; } } else { // No id, no url, no src parameters header('Content-type: image/gif'); readfile(XOOPS_ROOT_PATH . '/uploads/blank.gif'); exit(); } /* * Use Xoops cache */ // Get image_data from the Xoops cache only if the edited image has been cached after the latest modification // of the original image xoops_load('XoopsCache'); $edited_image_filename = 'editedimage_' . md5($_SERVER['REQUEST_URI']) . '_' . $imageFilename; $cached_image = XoopsCache::read($edited_image_filename); if (!isset($_GET['nocache']) && !isset($_GET['noservercache']) && !empty($cached_image) && ($cached_image['cached_time'] >= $imageCreatedTime)) { header("Content-type: {$imageMimetype}"); header('Content-Length: ' . strlen($cached_image['image_data'])); echo $cached_image['image_data']; exit(); } /* * Get/check editing parameters */ // width, height $max_width = isset($_GET['width']) ? (int)$_GET['width'] : false; $max_height = isset($_GET['height']) ? (int)$_GET['height'] : false; // If either a max width or max height are not specified, we default to something large so the unspecified // dimension isn't a constraint on our resized image. // If neither are specified but the color is, we aren't going to be resizing at all, just coloring. if (!$max_width && $max_height) { $max_width = PHP_INT_MAX; } elseif ($max_width && !$max_height) { $max_height = PHP_INT_MAX; } elseif (!$max_width && !$max_height) { $max_width = $imageWidth; $max_height = $imageHeight; } // color $color = isset($_GET['color']) ? preg_replace('/[^0-9a-fA-F]/', '', (string)$_GET['color']) : false; // filter, radius, angle $filter = isset($_GET['filter']) ? $_GET['filter'] : false; $radius = isset($_GET['radius']) ? (string)$_GET['radius'] : false; $angle = isset($_GET['angle']) ? (float)$_GET['angle'] : false; // If we don't have a width or height or color or filter or radius or rotate we simply output the original // image and exit if (empty($_GET['width']) && empty($_GET['height']) && empty($_GET['color']) && empty($_GET['filter']) && empty($_GET['radius']) && empty($_GET['angle'])) { $last_modified_string = gmdate('D, d M Y H:i:s', $imageCreatedTime) . ' GMT'; $etag = md5($imageData); doConditionalGet($etag, $last_modified_string); header("Content-type: {$imageMimetype}"); header('Content-Length: ' . strlen($imageData)); echo $imageData; exit(); } // cropratio $offset_x = 0; $offset_y = 0; if (isset($_GET['cropratio'])) { $crop_ratio = explode(':', (string)$_GET['cropratio']); if (count($crop_ratio) == 2) { $ratio_computed = $imageWidth / $imageHeight; $crop_radio_computed = (float)$crop_ratio[0] / (float)$crop_ratio[1]; if ($ratio_computed < $crop_radio_computed) { // Image is too tall so we will crop the top and bottom $orig_height = $imageHeight; $imageHeight = $imageWidth / $crop_radio_computed; $offset_y = ($orig_height - $imageHeight) / 2; } elseif ($ratio_computed > $crop_radio_computed) { // Image is too wide so we will crop off the left and right sides $orig_width = $imageWidth; $imageWidth = $imageHeight * $crop_radio_computed; $offset_x = ($orig_width - $imageWidth) / 2; } } } // Setting up the ratios needed for resizing. We will compare these below to determine how to resize the image // (based on height or based on width) $xRatio = $max_width / $imageWidth; $yRatio = $max_height / $imageHeight; if ($xRatio * $imageHeight < $max_height) { // Resize the image based on width $tn_height = ceil($xRatio * $imageHeight); $tn_width = $max_width; } else { // Resize the image based on height $tn_width = ceil($yRatio * $imageWidth); $tn_height = $max_height; } // quality $quality = isset($_GET['quality']) ? (int)$_GET['quality'] : DEFAULT_IMAGE_QUALITY; /* * Start image editing */ // We don't want to run out of memory ini_set('memory_limit', MEMORY_TO_ALLOCATE); // Set up a blank canvas for our resized image (destination) $destination_image = imagecreatetruecolor($tn_width, $tn_height); imagealphablending($destination_image, false); imagesavealpha($destination_image, true); $transparent = imagecolorallocatealpha($destination_image, 255, 255, 255, 127); imagefilledrectangle($destination_image, 0, 0, $tn_width, $tn_height, $transparent); // Set up the appropriate image handling functions based on the original image's mime type switch ($imageMimetype) { case 'image/gif': // We will be converting GIFs to PNGs to avoid transparency issues when resizing GIFs // This is maybe not the ideal solution, but IE6 can suck it $output_function = 'imagepng'; $imageMimetype = 'image/png'; // We need to convert GIFs to PNGs $do_sharpen = false; $quality = round(10 - ($quality / 10)); // We are converting the GIF to a PNG and PNG needs a compression // level of 0 (no compression) through 9 (max) break; case 'image/png': case 'image/x-png': $output_function = 'imagepng'; $do_sharpen = false; $quality = round(10 - ($quality / 10)); // PNG needs a compression level of 0 (no compression) through 9 break; case 'image/jpeg': case 'image/pjpeg': $output_function = 'imagejpeg'; $do_sharpen = true; break; default: exit404BadReq(); break; } // Resample the original image into the resized canvas we set up earlier imagecopyresampled($destination_image, $sourceImage, 0, 0, $offset_x, $offset_y, $tn_width, $tn_height, $imageWidth, $imageHeight); // Set background color if (in_array($imageMimetype, array('image/gif', 'image/png'))) { if (!$color) { // If this is a GIF or a PNG, we need to set up transparency imagealphablending($destination_image, false); imagesavealpha($destination_image, true); $png_transparency = imagecolorallocatealpha($destination_image, 0, 0, 0, 127); imagefill($destination_image, 0, 0, $png_transparency); } else { // Fill the background with the specified color for matting purposes if ($color[0] === '#') { $color = substr($color, 1); } $background = false; if (strlen($color) == 6) { $background = imagecolorallocate( $destination_image, intval($color[0] . $color[1], 16), intval($color[2] . $color[3], 16), intval($color[4] . $color[5], 16) ); } elseif (strlen($color) == 3) { $background = imagecolorallocate( $destination_image, intval($color[0] . $color[0], 16), intval($color[1] . $color[1], 16), intval($color[2] . $color[2], 16) ); } if ($background) { imagefill($destination_image, 0, 0, $background); } } } else { if (!$color) { $color = DEFAULT_BACKGROUND_COLOR; } // Fill the background with the specified color for matting purposes if ($color[0] === '#') { $color = substr($color, 1); } $background = false; if (strlen($color) == 6) { $background = imagecolorallocate( $destination_image, intval($color[0] . $color[1], 16), intval($color[2] . $color[3], 16), intval($color[4] . $color[5], 16) ); } elseif (strlen($color) == 3) { $background = imagecolorallocate( $destination_image, intval($color[0] . $color[0], 16), intval($color[1] . $color[1], 16), intval($color[2] . $color[2], 16) ); } if ($background) { imagefill($destination_image, 0, 0, $background); } } // Imagefilter if (ENABLE_IMAGEFILTER && !empty($filter)) { $filterSet = (array) $filter; foreach ($filterSet as $currentFilter) { $rawFilterArgs = explode(',', $currentFilter); $filterConst = constant(array_shift($rawFilterArgs)); if (null !== $filterConst) { // skip if unknown constant $filterArgs = array(); $filterArgs[] = $destination_image; $filterArgs[] = $filterConst; foreach ($rawFilterArgs as $tempValue) { $filterArgs[] = trim($tempValue); } call_user_func_array('imagefilter', $filterArgs); } } } // Round corners if (ENABLE_ROUNDCORNER && !empty($radius)) { $radii = explode(',', $radius); switch (count($radii)) { case 1: $radii[3] = $radii[2] = $radii[1] = $radii[0]; break; case 2: $radii[3] = $radii[0]; $radii[2] = $radii[1]; break; case 3: $radii[3] = $radii[0]; break; case 4: // NOP break; } $destination_image = imageCreateCorners($destination_image, $radii); // we need png to support the alpha corners correctly if ($imageMimetype === 'image/jpeg') { $output_function = 'imagepng'; $imageMimetype = 'image/png'; $do_sharpen = false; $quality = round(10 - ($quality / 10)); } } // Imagerotate if (ENABLE_IMAGEROTATE && !empty($angle)) { $destination_image = imagerotate($destination_image, $angle, $background, 0); } if ($do_sharpen) { // Sharpen the image based on two things: // (1) the difference between the original size and the final size // (2) the final size $sharpness = findSharp($imageWidth, $tn_width); $sharpen_matrix = array( array(-1, -2, -1), array(-2, $sharpness + 12, -2), array(-1, -2, -1)); $divisor = $sharpness; $offset = 0; imageconvolution($destination_image, $sharpen_matrix, $divisor, $offset); } // Put the data of the resized image into a variable ob_start(); $output_function($destination_image, null, $quality); $imageData = ob_get_contents(); ob_end_clean(); // Update $image_created_time $imageCreatedTime = time(); // Clean up the memory imagedestroy($sourceImage); imagedestroy($destination_image); /* * Write the just edited image into the Xoops cache */ $cached_image['edited_image_filename'] = $edited_image_filename; $cached_image['image_data'] = $imageData; $cached_image['cached_time'] = $imageCreatedTime; XoopsCache::write($edited_image_filename, $cached_image); /* * Send the edited image to the browser */ // See if the browser already has the image $last_modified_string = gmdate('D, d M Y H:i:s', $imageCreatedTime) . ' GMT'; $etag = md5($imageData); doConditionalGet($etag, $last_modified_string); header('HTTP/1.1 200 OK'); // if image is cacheable if (!isset($_GET['nocache']) && !isset($_GET['nobrowsercache'])) { header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $imageCreatedTime) . 'GMT'); header('Cache-control: max-age=31536000'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . 'GMT'); } else { // "Kill" the browser cache header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // past date header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1 header('Cache-Control: post-check=0, pre-check=0', false); header('Pragma: no-cache'); // HTTP/1.0 } header("Content-type: {$imageMimetype}"); header("Content-disposition: filename={$imageFilename}"); header('Content-Length: ' . strlen($imageData)); echo $imageData;