OwlCyberSecurity - MANAGER
Edit File: readlesstextthumb.php
<?php /** * @package readlesstext * @copyright 2008-2014 Parvus * @license http://www.gnu.org/licenses/gpl-3.0.html * @link http://joomlacode.org/gf/project/cutoff/ * @author Parvus * * readless is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * readless 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. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with readless. If not, see <http://www.gnu.org/licenses/>. */ /** * @version $Id$ */ defined( '_JEXEC' ) or die; jimport( 'joomla.plugin.plugin' ); jimport( 'joomla.utilities.date' ); class ReadLessTextThumb { /** * Checks if the given url is still a valid thumbnail url. * Determines the correct url to the corresponding thumbnail. * If the thumbnail does not exist, it is created * @param string $url The url to the thumbnail image * @param dict $minimum Associative array as given to @c GetThumbnail(). * used to recreate the expected thumbnail url. * @param dict $crop Associative array as given to @c GetThumbnail(). * used to recreate the expected thumbnail url. * @param int $thumbWidth A number. May be zero or negative. When positive, it * indicates the maximum width of the resized thumbnail. Else, there is no * restriction on image width, and will be chosen in function of the * height. * OUT: If @c true is returned, this variable will contain the extact thumbnail width, in pixels. * @param int $thumbHeight A number. May be zero or negative. When positive, it * indicates the maximum height of the resized thumbnail. Else, there is * no restriction on image height, and will be chosen in function of the * width. * OUT: If @c true is returned, this variable will contain the exact thumbnail height, in pixels. * @return @c true if the given thumbnail url is still correct. @c false * when some checks failed or could not be performed. * @see GetThumbnail */ public static function ValidateThumbnail( $imageUrl, $thumbnailUrl, $minimum, $crop, & $thumbWidth, & $thumbHeight ) { $success = false; $ext = strrchr( $thumbnailUrl, '.'); /* e.g.: .png */ $expectedThumbnailPath = self::_DetermineThumbnailPath( $imageUrl, $minimum, $crop, $thumbWidth, $thumbHeight, $ext ); $expectedThumbnailUrl = self::_DetermineUrlFromPath( $expectedThumbnailPath ); if ( $thumbnailUrl == $expectedThumbnailUrl ) { if ( @file_exists( $expectedThumbnailPath ) ) { /* The given thumbnail exists, is located in the cache, and * the minimum configuration settings haven't been changed since. * we can re-use it. */ $sizeArray = @getimagesize( $expectedThumbnailPath ); $thumbWidth = $sizeArray[ 0 ]; $thumbHeight = $sizeArray[ 1 ]; $success = true; } } return $success; } private static function _DetermineImageType( $url ) { $type = false; if ( function_exists( 'exif_imagetype' ) ) { $type = @exif_imagetype( $url ); } else if ( function_exists( 'getimagesize' ) ) { $list = @getimagesize( $url ); if ( $list ) { $type = $list[2]; } } if ( !/*NOT*/$type ) { /* Fallback: try to determine the correct image type by checking for an extension in the url. */ $ext = ''; $parts = explode( '.', $url ); if ( count( $parts ) > 1 ) { $parts = explode( '?', $parts[ count( $parts ) - 1 ], 2 ); $ext = JString::strtolower( $parts[ 0 ] ); } if ( array_key_exists( $ext, ReadLessTextThumb::$_extToType ) ) { $type = ReadLessTextThumb::$_extToType[ $ext ]; } } return $type; } private static function _DetermineThumbnailPath( $imageUrl, $minimum, $crop, $thumbWidth, $thumbHeight, $ext ) { $path = JPATH_CACHE . '/plg_readlesstext/'; if ( !/*NOT*/@file_exists( $path ) ) { @mkdir( $path ); } if ( !/*NOT*/@is_dir( $path ) or !/*NOT*/@is_writable( $path ) ) { /* Insufficient write permissions. Use fall-back. */ $thumbnailUrl = $imageUrl; } else { $string = $imageUrl . $minimum[ 'width' ] . $minimum[ 'height' ] . $minimum[ 'ratio' ] . $crop[ 'horizontal_position' ] . $crop[ 'vertical_position' ] . $thumbWidth . $thumbHeight . 'v5.2 (r274)'; $thumbnailPath = $path . md5( $string ) . $ext; } return $thumbnailPath; } private static function _DetermineUrlFromPath( $path ) { $host = parse_url( $path, PHP_URL_HOST ); if ( $host ) { /* $path is already a url. */ $url = false; } else if ( JString::strpos( $path, JPATH_BASE . '/' ) === 0 ) { $url = JString::str_ireplace( JPATH_BASE . '/', JURI::base(), $path ); } else { $url = JURI::base() . $path; } return $url; } private static function _DeterminePathFromUrl( $url ) { $host = parse_url( $url, PHP_URL_HOST ); if ( $host ) { if ( JString::strpos( $url, JURI::base() . '/' ) === 0 ) { $url = JString::str_ireplace( JURI::base(), JPATH_BASE . '/', $path ); } else { $path = false; /* Can not be converted to a local path. */ } } else { $path = JPATH_BASE . '/' . $url; $path = JString::str_ireplace( '//', '/', $path ); } return $path; } /** * Find the resized dimensions, keeping the proportions. * @param uint $width * @param uint $height * @param uint $thumbWidth * @param uint $thumbHeight * @param dict $crop Associative array, with keys 'horizontal_position', * 'vertical_position' and values 'left', 'right', 'center' or 'no'. */ private static function _DetermineResizeFactor( $width, $height, $thumbWidth, $thumbHeight, $crop ) { /* There are four different ways to resize: * A: resize full width to thumbnail width, * resize height with same ratio, * crop height to thumbnail height (top, bottom, evenly both) * B: resize full height to thumbnail height, * resize width with same ratio, * crop width to thumbnail width (left, right, evenly both) * C: resize full width to thumbnail width, * resize height with same ratio, * resized height <= thumbnail height * D: resize full height to thumbnail height, * resize width with same ratio, * resized width <= thumbnail width * * Based on * - the actual image dimensions: ix, iy * - the desired thumbnail dimensions: tx, ty * - the crop options: * crop horizontal: yes (left/right/evenly) or no (do not crop horizontally) * crop vertical: yes (left/right/evenly) or no (do not crop vertically) * we need to determine the resize factor. * * Determine the horizontal and vertical ratio's: rx, ry * - If rx < ry: If resized using ry, the horizontal width will be * greater than the thumbnail width. So either the width must * be cropped (if allowed), either the image must be resized * using rx (and thus the resized height will be less than the * desired thumbnail height ty). * - If rx == ry: highly unlikely. Crop options are not needed here. * Just resize using the single resize factor that was calculated. * - If rx > ry: Similar to the first case. * Replace width <> height, rx <> ry and ty <> tx * Thus: * rx, ry = tx/ix, ty/iy * rx < ry * ? crop horizontal ? r = ry : r = rx * : crop vertical ? r = rx : r = ry */ if ( $thumbWidth > 0 ) { $resizeFactorWidth = min( 1, $thumbWidth / $width ); } else { $thumbWidth = $width; $resizeFactorWidth = 1; } if ( $thumbHeight > 0 ) { $resizeFactorHeight = min( 1, $thumbHeight / $height ); } else { $thumbHeight = $height; $resizeFactorHeight = 1; } $resizeFactor = 1; /* Default value */ if ( $resizeFactorWidth < $resizeFactorHeight ) { /* Width is (relatively) greater than the height. */ if ( in_array( $crop[ 'horizontal_position' ], array( 'left', 'right', 'center' ) ) ) { /* Horizontal cropping is allowed. We may resize less, * and crop the extraneous part. */ $resizeFactor = $resizeFactorHeight; } else { /* Horizontal cropping is not allowed. The full width must be * resized: the resized height will be less than the intended * thumbnail height. */ $resizeFactor = $resizeFactorWidth; } } else { /* Vertical cropping is allowed. We may resize less, * and crop the extraneous part. */ if ( in_array( $crop[ 'vertical_position' ], array( 'top', 'bottom', 'center' ) ) ) { $resizeFactor = $resizeFactorWidth; } else { /* Vertical cropping is not allowed. The full height must be * resized: the resized width will be less than the intended * thumbnail width. */ $resizeFactor = $resizeFactorHeight; } } return $resizeFactor; } /** * Determine the start positions sx, sy: everything lower and * everything higher than that plus the image width/height will be * thrown away (cropped). * @param uint $width * @param uint $height * @param uint $thumbWidth * @param uint $thumbHeight * @param dict $crop Associative array, with keys 'horizontal_position', * 'vertical_position' and values 'left', 'right', 'center' or 'no'. * @param unknown_type $resizeFactor */ private static function _DetermineCroppedRectangle( $width, $height, & $thumbWidth, & $thumbHeight, $crop, $resizeFactor ) { /* * Default value is 0, 0, to be used when cutting on the * right/bottom, or when cropping is disabled. * * If there is something to be thrown away, i.e. * if r * ix > tx * cut left, retain right ? sx = ix - tx / r * cut right, retain left ? sx = 0 * cut evenly ? sx = (ix - tx / r) / 2 * * Likewise for sy */ $horizontalStart = 0; /* Default value */ $usedWidth = min( $width, $thumbWidth / $resizeFactor ); $thumbWidth = intval( ( $usedWidth * $resizeFactor ) + 0.01 ); if ( $usedWidth + 1 < $width ) { switch ( $crop[ 'horizontal_position' ] ) { case 'center': $horizontalStart = max( 0, ( $width - $usedWidth ) / 2 ); break; case 'right': $horizontalStart = max( 0, $width - $usedWidth ); break; case 'left': /* $horizontalStart remains 0 */ break; default: /* Do not crop the width after all. We should never get here! */ break; } } $verticalStart = 0; /* Default value */ $usedHeight = min( $height, $thumbHeight / $resizeFactor ); $thumbHeight = intval( ( $usedHeight * $resizeFactor ) + 0.01 ); if ( $usedHeight + 1 < $height ) { switch ( $crop[ 'vertical_position' ] ) { case 'center': $verticalStart = max( 0, ( $height - $usedHeight ) / 2 ); break; case 'bottom': $verticalStart = max( 0, $height - $usedHeight ); break; case 'top': /* $verticalStart remains 0 */ break; default: /* Do not crop the height after all. We should never get here! */ break; } } return array( $horizontalStart, $verticalStart, $usedWidth, $usedHeight ); } private static function _LoadImageUsingCurl( $url, $maxImageLoadTime ) { $image = false; $curl = false; if ( function_exists( 'curl_init' ) ) { $curl = curl_init(); } if ( $curl ) { curl_setopt( $curl, CURLOPT_URL, $url ); curl_setopt( $curl, CURLOPT_HEADER, false ); curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $curl, CURLOPT_CONNECTTIMEOUT, $maxImageLoadTime ); if ( ( ini_get( 'open_basedir' ) == '') and ( ( ini_get( 'safe_mode' ) == 'Off' ) or ( !/*NOT*/ini_get( 'safe_mode' ) ) ) ) { /* The follow location option can not be activated when either dafe_mode or open_basedir * is set in php.ini - as a security measure. Trying to set it anyway results in a warning. * @todo If this is not set, redirection is not possible. A workaround for this is * described here: http://stackoverflow.com/a/6918742/911550 * To implement? */ curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true ); } curl_setopt( $curl, CURLOPT_MAXREDIRS, 11/*just a number that seems plenty enough*/ ); curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, FALSE ); $contents = curl_exec( $curl ); curl_close( $curl ); $image = @imagecreatefromstring( $contents ); } return $image; } /** * Tries various ways to open an image and read its contents. * @param string $url References a local image with relative or full path, or a remote image. * @param function $loadFunction * @return resource image upon success, false on failure. */ private static function _LoadImage( $url, $loadFunction, $maxImageLoadTime ) { $originalDefaultSocketTimeoutValue = false; /* Always first try the url as given. * If that fails, try to convert the url to a local absolute path. * If that can not be done, or loading fails again, try to convert to a full url. * Try loading both using imagecreatefrom.+, and using curl combined with imagecreatefromstring. * If that all fails, give up. */ $fullPath = self::_DeterminePathFromUrl( $url ); $fullUrl = self::_DetermineUrlFromPath( $url ); $tries = array(); $tries[] = 'curl'; $tries[] = 'normal'; if ( $fullPath ) { $tries[] = 'fullPath'; } /* Always include at least one way to fetch an image based on the full url. I had a report from a user who * apparently couldn't load images referenced locally, but could load images referenced via a url (???). */ if ( $fullUrl and ini_get( 'allow_url_fopen' ) ) { $tries[] = 'fullUrl'; if ( $maxImageLoadTime > 0 ) { $originalDefaultSocketTimeoutValue = ini_set( 'default_socket_timeout', $maxImageLoadTime ); } } $tries[] = 'curl'; $image = false; foreach ( $tries as $try ) { if ( !/*NOT*/$image ) { switch ( $try ) { case 'normal': $image = @call_user_func( $loadFunction, $url ); break; case 'fullPath': $image = @call_user_func( $loadFunction, $fullPath ); break; case 'fullUrl': $image = @call_user_func( $loadFunction, $fullUrl ); break; case 'curl': if ( $fullUrl ) { $image = self::_LoadImageUsingCurl( $fullUrl, $maxImageLoadTime ); } else { $image = self::_LoadImageUsingCurl( $url, $maxImageLoadTime ); } break; default: /* May never come here. */ break; } } } if ( $originalDefaultSocketTimeoutValue /* is FALSE when ini_set failed or was not executed */ ) { ini_set( 'default_socket_timeout', $originalDefaultSocketTimeoutValue ); } return $image; } /** * Determines the correct url to the corresponding thumbnail. * If the thumbnail does not exist, it is created * @param string $url The path to the image * @param dict $minimum Associative array, with keys 'width', 'height', 'ratio', * and values 0 or positive numbers, expressed in pixels. * Looked at both to find a previously created thumbnail; and when the * thumbnail does not exist yet and has to be created. * @param dict $crop Associative array, with keys 'horizontal_position', * 'vertical_position' and values 'left', 'right', 'center' or 'no'. * @param int $thumbWidth A number. May be zero or negative. When positive, it * indicates the maximum width of the resized thumbnail. Else, there is no * restriction on image width, and will be chosen in function of the * height. * OUT: If the path to the thumbnail is returned, this variable will contain * the extact thumbnail width, in pixels. * @param int $thumbHeight A number. May be zero or negative. When positive, it * indicates the maximum height of the resized thumbnail. Else, there is * no restriction on image height, and will be chosen in function of the * width. * OUT: If the path to the thumbnail is returned, this variable will contain * the extact thumbnail height, in pixels. * @param int $lifetime The lifetime of the thumbnail in seconds to set when it * is created by calling this function. Not used to check if the existing * thumbnail is still valid. Default: 4 weeks (2419200 seconds). * @return false if an error occurred or if the given $url is incorrect. The * path to the thumbnail otherwise. */ public static function GetThumbnail( $url, $minimum, $crop, & $thumbWidth, & $thumbHeight, $lifetime = 2419200, $maxImageLoadTime = 60 ) { $type = self::_DetermineImageType( $url ); if ( $type and array_key_exists( $type, ReadLessTextThumb::$_image ) ) { $ext = ReadLessTextThumb::$_image[ $type ][ 'ext' ]; $thumbnailPath = self::_DetermineThumbnailPath( $url, $minimum, $crop, $thumbWidth, $thumbHeight, $ext ); $thumbnailUrl = self::_DetermineUrlFromPath( $thumbnailPath ); if ( @file_exists( $thumbnailPath ) ) { /* Thumbnail already exists. * The image resource $url has been examined during a previous execution; * and according to the settings, it is fit to serve as a thumbnail. */ $sizeArray = @getimagesize( $thumbnailPath ); $thumbWidth = $sizeArray[ 0 ]; $thumbHeight = $sizeArray[ 1 ]; } else { $image = self::_LoadImage( $url, ReadLessTextThumb::$_image[ $type ][ 'load' ], $maxImageLoadTime ); $width = -1; $height = -1; if ( $image ) { $width = max( 1, @imagesx( $image ) ); /* Ensure a division is possible. */ $height = max( 1, @imagesy( $image ) ); /* Ensure a division is possible. */ } $ratio = min( $width / $height, $height / $width ); if ( !/*NOT*/$image or ( $width < $minimum[ 'width' ] ) or ( $height < $minimum[ 'height' ] ) or ( $ratio < $minimum[ 'ratio' ] ) ) { /* Thumbnail may not be created. * According to the settings, it is not fit to serve as a thumbnail. */ $thumbnailUrl = false; } else { $resizeFactor = self::_DetermineResizeFactor( $width, $height, $thumbWidth, $thumbHeight, $crop ); /* $thumbWidth and $thumbHeight is now updated as well. */ /* The image width to use is equal to r * tx * The image height to use is equal to r * ty */ $start = self::_DetermineCroppedRectangle( $width, $height, $thumbWidth, $thumbHeight, $crop, $resizeFactor ); $horizontalStart = $start[0]; $verticalStart = $start[1]; $usedWidth = $start[2]; $usedHeight = $start[3]; /* $thumbWidth and $thumbHeight is now updated as well. */ /* Create thumbnail */ $thumbnail = call_user_func( ReadLessTextThumb::$_image[ $type ][ 'create' ], $thumbWidth, $thumbHeight ); if ( $type == 1 /* IMAGETYPE_GIF */ ) { /* Make the thumbnail initially transparent if the original was transparent too. * Otherwise, fill it initially up with all white. */ $transparentColorIdentifier = @imagecolortransparent( $image ); if ( $transparentColorIdentifier >= 0 ) { $colors = @imagecolorsforindex( $image, $transparentColorIdentifier ); $transcolorindex = @imagecolorallocate( $thumbnail, $colors[ 'red' ], $colors[ 'green' ], $colors[ 'blue' ] ); @imagefill( $thumbnail, 0, 0, $transcolorindex ); @imagecolortransparent( $thumbnail, $transcolorindex ); /* Needed? */ } else { $whiteColorIdentifier = @imagecolorallocate( $thumbnail, 255, 255, 255 ); @imagefill( $thumbnail, 0, 0, $whitecolorindex); } } if ( ReadLessTextThumb::$_image[ $type ][ 'create_alpha' ] ) { call_user_func( ReadLessTextThumb::$_image[ $type ][ 'create_alpha' ], $thumbnail, false ); } call_user_func( ReadLessTextThumb::$_image[ $type ][ 'copy' ], $thumbnail, $image, 0, 0, $horizontalStart, $verticalStart, $thumbWidth, $thumbHeight, $usedWidth, $usedHeight ); if ( ReadLessTextThumb::$_image[ $type ][ 'save_alpha' ] ) { call_user_func( ReadLessTextThumb::$_image[ $type ][ 'save_alpha' ], $thumbnail, true ); } call_user_func( ReadLessTextThumb::$_image[ $type ][ 'save' ], $thumbnail, $thumbnailPath ); /* The expiration information is not used directly, but it is still * added to allow Joomla's core garbage collection functionality to work. */ $expirePath = $thumbnailPath . '_expire'; @file_put_contents( $expirePath, ( time() + $lifetime) ); } } } else { /* To me, the remaining image types are esoteric. Some of them I never * even heard of. * OR * Determining the image type failed. */ $thumbnailUrl = false; } return $thumbnailUrl; } private static $_extToType = array( 'gif' => 1 /* IMAGETYPE_GIF */, 'jpg' => 2 /* IMAGETYPE_JPEG */, 'jpeg' => 2 /* IMAGETYPE_JPEG */, 'png' => 3 /* IMAGETYPE_PNG */, 'bmp' => 6 /* IMAGETYPE_PNG */ ); private static $_image = array( 1 /* IMAGETYPE_GIF */ => array( 'ext' => '.gif', 'load' => 'imagecreatefromgif', 'create' => 'imagecreate', 'create_alpha' => '', 'copy' => 'imagecopyresampled', 'save_alpha' => '', 'save' => 'imagegif' ), 2 /* IMAGETYPE_JPEG */ => array( 'ext' => '.jpg', 'load' => 'imagecreatefromjpeg', 'create' => 'imagecreatetruecolor', 'create_alpha' => '', 'copy' => 'imagecopyresampled', 'save_alpha' => '', 'save' => 'imagejpeg' ), 3 /* IMAGETYPE_PNG */ => array( 'ext' => '.png', 'load' => 'imagecreatefrompng', 'create' => 'imagecreatetruecolor', 'create_alpha' => 'imagealphablending', 'copy' => 'imagecopyresampled', 'save_alpha' => 'imagesavealpha', 'save' => 'imagepng' ), 6000 /* IMAGETYPE_BMP */ => array( 'ext' => '.bmp', 'load' => 'imagecreatefromwbmp', 'create' => 'imagecreate', 'create_alpha' => '', 'copy' => 'imagecopyresampled', 'save_alpha' => '', 'save' => 'imagewbmp' ) ); } ?>