1<?php 2////////////////////////////////////////////////////////////// 3// phpThumb() by James Heinrich <info@silisoftware.com> // 4// available at http://phpthumb.sourceforge.net // 5// and/or https://github.com/JamesHeinrich/phpThumb // 6////////////////////////////////////////////////////////////// 7/// // 8// See: phpthumb.readme.txt for usage instructions // 9// /// 10////////////////////////////////////////////////////////////// 11 12ob_start(); 13if (!include_once(dirname(__FILE__).'/phpthumb.functions.php')) { 14 ob_end_flush(); 15 die('failed to include_once("'.dirname(__FILE__).'/phpthumb.functions.php")'); 16} 17ob_end_clean(); 18 19class phpthumb { 20 21 // public: 22 // START PARAMETERS (for object mode and phpThumb.php) 23 // See phpthumb.readme.txt for descriptions of what each of these values are 24 var $src = null; // SouRCe filename 25 var $new = null; // NEW image (phpThumb.php only) 26 var $w = null; // Width 27 var $h = null; // Height 28 var $wp = null; // Width (Portrait Images Only) 29 var $hp = null; // Height (Portrait Images Only) 30 var $wl = null; // Width (Landscape Images Only) 31 var $hl = null; // Height (Landscape Images Only) 32 var $ws = null; // Width (Square Images Only) 33 var $hs = null; // Height (Square Images Only) 34 var $f = null; // output image Format 35 var $q = 75; // jpeg output Quality 36 var $sx = null; // Source crop top-left X position 37 var $sy = null; // Source crop top-left Y position 38 var $sw = null; // Source crop Width 39 var $sh = null; // Source crop Height 40 var $zc = null; // Zoom Crop 41 var $bc = null; // Border Color 42 var $bg = null; // BackGround color 43 var $fltr = array(); // FiLTeRs 44 var $goto = null; // GO TO url after processing 45 var $err = null; // default ERRor image filename 46 var $xto = null; // extract eXif Thumbnail Only 47 var $ra = null; // Rotate by Angle 48 var $ar = null; // Auto Rotate 49 var $aoe = null; // Allow Output Enlargement 50 var $far = null; // Fixed Aspect Ratio 51 var $iar = null; // Ignore Aspect Ratio 52 var $maxb = null; // MAXimum Bytes 53 var $down = null; // DOWNload thumbnail filename 54 var $md5s = null; // MD5 hash of Source image 55 var $sfn = 0; // Source Frame Number 56 var $dpi = 150; // Dots Per Inch for vector source formats 57 var $sia = null; // Save Image As filename 58 59 var $file = null; // >>>deprecated, DO NOT USE, will be removed in future versions<<< 60 61 var $phpThumbDebug = null; 62 // END PARAMETERS 63 64 65 // public: 66 // START CONFIGURATION OPTIONS (for object mode only) 67 // See phpThumb.config.php for descriptions of what each of these settings do 68 69 // * Directory Configuration 70 var $config_cache_directory = null; 71 var $config_cache_directory_depth = 0; 72 var $config_cache_disable_warning = true; 73 var $config_cache_source_enabled = false; 74 var $config_cache_source_directory = null; 75 var $config_temp_directory = null; 76 var $config_document_root = null; 77 78 // * Default output configuration: 79 var $config_output_format = 'jpeg'; 80 var $config_output_maxwidth = 0; 81 var $config_output_maxheight = 0; 82 var $config_output_interlace = true; 83 84 // * Error message configuration 85 var $config_error_image_width = 400; 86 var $config_error_image_height = 100; 87 var $config_error_message_image_default = ''; 88 var $config_error_bgcolor = 'CCCCFF'; 89 var $config_error_textcolor = 'FF0000'; 90 var $config_error_fontsize = 1; 91 var $config_error_die_on_error = false; 92 var $config_error_silent_die_on_error = false; 93 var $config_error_die_on_source_failure = true; 94 95 // * Anti-Hotlink Configuration: 96 var $config_nohotlink_enabled = true; 97 var $config_nohotlink_valid_domains = array(); 98 var $config_nohotlink_erase_image = true; 99 var $config_nohotlink_text_message = 'Off-server thumbnailing is not allowed'; 100 // * Off-server Linking Configuration: 101 var $config_nooffsitelink_enabled = false; 102 var $config_nooffsitelink_valid_domains = array(); 103 var $config_nooffsitelink_require_refer = false; 104 var $config_nooffsitelink_erase_image = true; 105 var $config_nooffsitelink_watermark_src = ''; 106 var $config_nooffsitelink_text_message = 'Off-server linking is not allowed'; 107 108 // * Border & Background default colors 109 var $config_border_hexcolor = '000000'; 110 var $config_background_hexcolor = 'FFFFFF'; 111 112 // * TrueType Fonts 113 var $config_ttf_directory = './fonts'; 114 115 var $config_max_source_pixels = null; 116 var $config_use_exif_thumbnail_for_speed = false; 117 var $allow_local_http_src = false; 118 119 var $config_imagemagick_path = null; 120 var $config_prefer_imagemagick = true; 121 var $config_imagemagick_use_thumbnail = true; 122 123 var $config_cache_maxage = null; 124 var $config_cache_maxsize = null; 125 var $config_cache_maxfiles = null; 126 var $config_cache_source_filemtime_ignore_local = false; 127 var $config_cache_source_filemtime_ignore_remote = true; 128 var $config_cache_default_only_suffix = false; 129 var $config_cache_force_passthru = true; 130 var $config_cache_prefix = ''; // default value set in the constructor below 131 132 // * MySQL 133 var $config_mysql_extension = null; 134 var $config_mysql_query = null; 135 var $config_mysql_hostname = null; 136 var $config_mysql_username = null; 137 var $config_mysql_password = null; 138 var $config_mysql_database = null; 139 140 // * Security 141 var $config_high_security_enabled = true; 142 var $config_high_security_password = null; 143 var $config_high_security_url_separator = '&'; 144 var $config_disable_debug = true; 145 var $config_allow_src_above_docroot = false; 146 var $config_allow_src_above_phpthumb = true; 147 var $config_auto_allow_symlinks = true; // allow symlink target directories without explicitly whitelisting them 148 var $config_additional_allowed_dirs = array(); // additional directories to allow source images to be read from 149 var $config_file_create_mask = 0755; 150 var $config_dir_create_mask = 0755; 151 152 // * HTTP fopen 153 var $config_http_fopen_timeout = 10; 154 var $config_http_follow_redirect = true; 155 156 // * Compatability 157 var $config_disable_pathinfo_parsing = false; 158 var $config_disable_imagecopyresampled = false; 159 var $config_disable_onlycreateable_passthru = false; 160 var $config_disable_realpath = false; 161 162 var $config_http_user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7'; 163 164 // END CONFIGURATION OPTIONS 165 166 167 // public: error messages (read-only; persistant) 168 var $debugmessages = array(); 169 var $debugtiming = array(); 170 var $fatalerror = null; 171 172 173 // private: (should not be modified directly) 174 var $thumbnailQuality = 75; 175 var $thumbnailFormat = null; 176 177 var $sourceFilename = null; 178 var $rawImageData = null; 179 var $IMresizedData = null; 180 var $outputImageData = null; 181 182 var $useRawIMoutput = false; 183 184 var $gdimg_output = null; 185 var $gdimg_source = null; 186 187 var $getimagesizeinfo = null; 188 189 var $source_width = null; 190 var $source_height = null; 191 192 var $thumbnailCropX = null; 193 var $thumbnailCropY = null; 194 var $thumbnailCropW = null; 195 var $thumbnailCropH = null; 196 197 var $exif_thumbnail_width = null; 198 var $exif_thumbnail_height = null; 199 var $exif_thumbnail_type = null; 200 var $exif_thumbnail_data = null; 201 var $exif_raw_data = null; 202 203 var $thumbnail_width = null; 204 var $thumbnail_height = null; 205 var $thumbnail_image_width = null; 206 var $thumbnail_image_height = null; 207 208 var $tempFilesToDelete = array(); 209 var $cache_filename = null; 210 211 var $AlphaCapableFormats = array('png', 'ico', 'gif'); 212 var $is_alpha = false; 213 214 var $iswindows = null; 215 var $issafemode = null; 216 var $php_memory_limit = null; 217 218 var $phpthumb_version = '1.7.15-201610261701'; 219 220 ////////////////////////////////////////////////////////////////////// 221 222 // public: constructor 223 function __construct() { 224 $this->phpThumb(); 225 } 226 227 function phpThumb() { 228 $this->DebugTimingMessage('phpThumb() constructor', __FILE__, __LINE__); 229 $this->DebugMessage('phpThumb() v'.$this->phpthumb_version, __FILE__, __LINE__); 230 231 foreach (array(ini_get('memory_limit'), get_cfg_var('memory_limit')) as $php_config_memory_limit) { 232 if (strlen($php_config_memory_limit)) { 233 if (strtoupper(substr($php_config_memory_limit, -1, 1)) == 'G') { // PHP memory limit expressed in Gigabytes 234 $php_config_memory_limit = intval(substr($php_config_memory_limit, 0, -1)) * 1073741824; 235 } elseif (strtoupper(substr($php_config_memory_limit, -1, 1)) == 'M') { // PHP memory limit expressed in Megabytes 236 $php_config_memory_limit = intval(substr($php_config_memory_limit, 0, -1)) * 1048576; 237 } 238 $this->php_memory_limit = max($this->php_memory_limit, $php_config_memory_limit); 239 } 240 } 241 if ($this->php_memory_limit > 0) { // could be "-1" for "no limit" 242 $this->config_max_source_pixels = round($this->php_memory_limit * 0.20); // 20% of memory_limit 243 } 244 245 $this->iswindows = (bool) (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN'); 246 $this->issafemode = (bool) preg_match('#(1|ON)#i', ini_get('safe_mode')); 247 $this->config_document_root = (!empty($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : $this->config_document_root); 248 $this->config_cache_prefix = ( isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'].'_' : ''); 249 250 $this->purgeTempFiles(); // purge existing temp files if re-initializing object 251 252 $php_sapi_name = strtolower(function_exists('php_sapi_name') ? php_sapi_name() : ''); 253 if ($php_sapi_name == 'cli') { 254 $this->config_allow_src_above_docroot = true; 255 } 256 257 if (!$this->config_disable_debug) { 258 // if debug mode is enabled, force phpThumbDebug output, do not allow normal thumbnails to be generated 259 $this->phpThumbDebug = (is_null($this->phpThumbDebug) ? 9 : max(1, intval($this->phpThumbDebug))); 260 } 261 } 262 263 function __destruct() { 264 $this->purgeTempFiles(); 265 } 266 267 // public: 268 function purgeTempFiles() { 269 foreach ($this->tempFilesToDelete as $tempFileToDelete) { 270 if (file_exists($tempFileToDelete)) { 271 $this->DebugMessage('Deleting temp file "'.$tempFileToDelete.'"', __FILE__, __LINE__); 272 @unlink($tempFileToDelete); 273 } 274 } 275 $this->tempFilesToDelete = array(); 276 return true; 277 } 278 279 // public: 280 function setSourceFilename($sourceFilename) { 281 //$this->resetObject(); 282 //$this->rawImageData = null; 283 $this->sourceFilename = $sourceFilename; 284 $this->src = $sourceFilename; 285 if (is_null($this->config_output_format)) { 286 $sourceFileExtension = strtolower(substr(strrchr($sourceFilename, '.'), 1)); 287 if (preg_match('#^[a-z]{3,4}$#', $sourceFileExtension)) { 288 $this->config_output_format = $sourceFileExtension; 289 $this->DebugMessage('setSourceFilename('.$sourceFilename.') set $this->config_output_format to "'.$sourceFileExtension.'"', __FILE__, __LINE__); 290 } else { 291 $this->DebugMessage('setSourceFilename('.$sourceFilename.') did NOT set $this->config_output_format to "'.$sourceFileExtension.'" because it did not seem like an appropriate image format', __FILE__, __LINE__); 292 } 293 } 294 $this->DebugMessage('setSourceFilename('.$sourceFilename.') set $this->sourceFilename to "'.$this->sourceFilename.'"', __FILE__, __LINE__); 295 return true; 296 } 297 298 // public: 299 function setSourceData($rawImageData, $sourceFilename='') { 300 //$this->resetObject(); 301 //$this->sourceFilename = null; 302 $this->rawImageData = $rawImageData; 303 $this->DebugMessage('setSourceData() setting $this->rawImageData ('.strlen($this->rawImageData).' bytes; magic="'.substr($this->rawImageData, 0, 4).'" ('.phpthumb_functions::HexCharDisplay(substr($this->rawImageData, 0, 4)).'))', __FILE__, __LINE__); 304 if ($this->config_cache_source_enabled) { 305 $sourceFilename = ($sourceFilename ? $sourceFilename : md5($rawImageData)); 306 if (!is_dir($this->config_cache_source_directory)) { 307 $this->ErrorImage('$this->config_cache_source_directory ('.$this->config_cache_source_directory.') is not a directory'); 308 } elseif (!@is_writable($this->config_cache_source_directory)) { 309 $this->ErrorImage('$this->config_cache_source_directory ('.$this->config_cache_source_directory.') is not writable'); 310 } 311 $this->DebugMessage('setSourceData() attempting to save source image to "'.$this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename).'"', __FILE__, __LINE__); 312 if ($fp = @fopen($this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename), 'wb')) { 313 fwrite($fp, $rawImageData); 314 fclose($fp); 315 } elseif (!$this->phpThumbDebug) { 316 $this->ErrorImage('setSourceData() failed to write to source cache ('.$this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename).')'); 317 } 318 } 319 return true; 320 } 321 322 // public: 323 function setSourceImageResource($gdimg) { 324 //$this->resetObject(); 325 $this->gdimg_source = $gdimg; 326 return true; 327 } 328 329 // public: 330 function setParameter($param, $value) { 331 if ($param == 'src') { 332 $this->setSourceFilename($this->ResolveFilenameToAbsolute($value)); 333 } elseif (@is_array($this->$param)) { 334 if (is_array($value)) { 335 foreach ($value as $arraykey => $arrayvalue) { 336 array_push($this->$param, $arrayvalue); 337 } 338 } else { 339 array_push($this->$param, $value); 340 } 341 } else { 342 $this->$param = $value; 343 } 344 return true; 345 } 346 347 // public: 348 function getParameter($param) { 349 //if (property_exists('phpThumb', $param)) { 350 return $this->$param; 351 //} 352 //$this->DebugMessage('setParameter() attempting to get non-existant parameter "'.$param.'"', __FILE__, __LINE__); 353 //return false; 354 } 355 356 357 // public: 358 function GenerateThumbnail() { 359 360 $this->setOutputFormat(); 361 $this->phpThumbDebug('8a'); 362 $this->ResolveSource(); 363 $this->phpThumbDebug('8b'); 364 $this->SetCacheFilename(); 365 $this->phpThumbDebug('8c'); 366 $this->ExtractEXIFgetImageSize(); 367 $this->phpThumbDebug('8d'); 368 if ($this->useRawIMoutput) { 369 $this->DebugMessage('Skipping rest of GenerateThumbnail() because ($this->useRawIMoutput == true)', __FILE__, __LINE__); 370 return true; 371 } 372 $this->phpThumbDebug('8e'); 373 if (!$this->SourceImageToGD()) { 374 $this->DebugMessage('SourceImageToGD() failed', __FILE__, __LINE__); 375 return false; 376 } 377 $this->phpThumbDebug('8f'); 378 $this->Rotate(); 379 $this->phpThumbDebug('8g'); 380 $this->CreateGDoutput(); 381 $this->phpThumbDebug('8h'); 382 383 // default values, also applicable for far="C" 384 $destination_offset_x = round(($this->thumbnail_width - $this->thumbnail_image_width) / 2); 385 $destination_offset_y = round(($this->thumbnail_height - $this->thumbnail_image_height) / 2); 386 if (($this->far == 'L') || ($this->far == 'TL') || ($this->far == 'BL')) { 387 $destination_offset_x = 0; 388 } 389 if (($this->far == 'R') || ($this->far == 'TR') || ($this->far == 'BR')) { 390 $destination_offset_x = round($this->thumbnail_width - $this->thumbnail_image_width); 391 } 392 if (($this->far == 'T') || ($this->far == 'TL') || ($this->far == 'TR')) { 393 $destination_offset_y = 0; 394 } 395 if (($this->far == 'B') || ($this->far == 'BL') || ($this->far == 'BR')) { 396 $destination_offset_y = round($this->thumbnail_height - $this->thumbnail_image_height); 397 } 398 399// // copy/resize image to appropriate dimensions 400// $borderThickness = 0; 401// if (!empty($this->fltr)) { 402// foreach ($this->fltr as $key => $value) { 403// if (preg_match('#^bord\|([0-9]+)#', $value, $matches)) { 404// $borderThickness = $matches[1]; 405// break; 406// } 407// } 408// } 409// if ($borderThickness > 0) { 410// //$this->DebugMessage('Skipping ImageResizeFunction() because BorderThickness="'.$borderThickness.'"', __FILE__, __LINE__); 411// $this->thumbnail_image_height /= 2; 412// } 413 $this->ImageResizeFunction( 414 $this->gdimg_output, 415 $this->gdimg_source, 416 $destination_offset_x, 417 $destination_offset_y, 418 $this->thumbnailCropX, 419 $this->thumbnailCropY, 420 $this->thumbnail_image_width, 421 $this->thumbnail_image_height, 422 $this->thumbnailCropW, 423 $this->thumbnailCropH 424 ); 425 426 $this->DebugMessage('memory_get_usage() after copy-resize = '.(function_exists('memory_get_usage') ? @memory_get_usage() : 'n/a'), __FILE__, __LINE__); 427 imagedestroy($this->gdimg_source); 428 $this->DebugMessage('memory_get_usage() after imagedestroy = '.(function_exists('memory_get_usage') ? @memory_get_usage() : 'n/a'), __FILE__, __LINE__); 429 430 $this->phpThumbDebug('8i'); 431 $this->AntiOffsiteLinking(); 432 $this->phpThumbDebug('8j'); 433 $this->ApplyFilters(); 434 $this->phpThumbDebug('8k'); 435 $this->AlphaChannelFlatten(); 436 $this->phpThumbDebug('8l'); 437 $this->MaxFileSize(); 438 $this->phpThumbDebug('8m'); 439 440 $this->DebugMessage('GenerateThumbnail() completed successfully', __FILE__, __LINE__); 441 return true; 442 } 443 444 445 // public: 446 function RenderOutput() { 447 if (!$this->useRawIMoutput && !is_resource($this->gdimg_output)) { 448 $this->DebugMessage('RenderOutput() failed because !is_resource($this->gdimg_output)', __FILE__, __LINE__); 449 return false; 450 } 451 if (!$this->thumbnailFormat) { 452 $this->DebugMessage('RenderOutput() failed because $this->thumbnailFormat is empty', __FILE__, __LINE__); 453 return false; 454 } 455 if ($this->useRawIMoutput) { 456 $this->DebugMessage('RenderOutput copying $this->IMresizedData ('.strlen($this->IMresizedData).' bytes) to $this->outputImage', __FILE__, __LINE__); 457 $this->outputImageData = $this->IMresizedData; 458 return true; 459 } 460 461 $builtin_formats = array(); 462 if (function_exists('imagetypes')) { 463 $imagetypes = imagetypes(); 464 $builtin_formats['wbmp'] = (bool) ($imagetypes & IMG_WBMP); 465 $builtin_formats['jpg'] = (bool) ($imagetypes & IMG_JPG); 466 $builtin_formats['gif'] = (bool) ($imagetypes & IMG_GIF); 467 $builtin_formats['png'] = (bool) ($imagetypes & IMG_PNG); 468 } 469 470 $this->DebugMessage('imageinterlace($this->gdimg_output, '.intval($this->config_output_interlace).')', __FILE__, __LINE__); 471 imageinterlace($this->gdimg_output, intval($this->config_output_interlace)); 472 473 $this->DebugMessage('RenderOutput() attempting image'.strtolower(@$this->thumbnailFormat).'($this->gdimg_output)', __FILE__, __LINE__); 474 ob_start(); 475 switch ($this->thumbnailFormat) { 476 case 'wbmp': 477 if (empty($builtin_formats['wbmp'])) { 478 $this->DebugMessage('GD does not have required built-in support for WBMP output', __FILE__, __LINE__); 479 ob_end_clean(); 480 return false; 481 } 482 imagejpeg($this->gdimg_output, null, $this->thumbnailQuality); 483 $this->outputImageData = ob_get_contents(); 484 break; 485 486 case 'jpeg': 487 case 'jpg': // should be "jpeg" not "jpg" but just in case... 488 if (empty($builtin_formats['jpg'])) { 489 $this->DebugMessage('GD does not have required built-in support for JPEG output', __FILE__, __LINE__); 490 ob_end_clean(); 491 return false; 492 } 493 imagejpeg($this->gdimg_output, null, $this->thumbnailQuality); 494 $this->outputImageData = ob_get_contents(); 495 break; 496 497 case 'png': 498 if (empty($builtin_formats['png'])) { 499 $this->DebugMessage('GD does not have required built-in support for PNG output', __FILE__, __LINE__); 500 ob_end_clean(); 501 return false; 502 } 503 if (phpthumb_functions::version_compare_replacement(phpversion(), '5.1.2', '>=')) { 504 // https://github.com/JamesHeinrich/phpThumb/issues/24 505 506 /* http://php.net/manual/en/function.imagepng.php: 507 from php source (gd.h): 508 2.0.12: Compression level: 0-9 or -1, where 0 is NO COMPRESSION at all, 509 :: 1 is FASTEST but produces larger files, 9 provides the best 510 :: compression (smallest files) but takes a long time to compress, and 511 :: -1 selects the default compiled into the zlib library. 512 Conclusion: Based on the Zlib manual (http://www.zlib.net/manual.html) the default compression level is set to 6. 513 */ 514 if (($this->thumbnailQuality >= -1) && ($this->thumbnailQuality <= 9)) { 515 $PNGquality = $this->thumbnailQuality; 516 } else { 517 $this->DebugMessage('Specified thumbnailQuality "'.$this->thumbnailQuality.'" is outside the accepted range (0-9, or -1). Using 6 as default value.', __FILE__, __LINE__); 518 $PNGquality = 6; 519 } 520 imagepng($this->gdimg_output, null, $PNGquality); 521 } else { 522 imagepng($this->gdimg_output); 523 } 524 $this->outputImageData = ob_get_contents(); 525 break; 526 527 case 'gif': 528 if (empty($builtin_formats['gif'])) { 529 $this->DebugMessage('GD does not have required built-in support for GIF output', __FILE__, __LINE__); 530 ob_end_clean(); 531 return false; 532 } 533 imagegif($this->gdimg_output); 534 $this->outputImageData = ob_get_contents(); 535 break; 536 537 case 'bmp': 538 if (!@include_once(dirname(__FILE__).'/phpthumb.bmp.php')) { 539 $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.bmp.php" which is required for BMP format output', __FILE__, __LINE__); 540 ob_end_clean(); 541 return false; 542 } 543 $phpthumb_bmp = new phpthumb_bmp(); 544 $this->outputImageData = $phpthumb_bmp->GD2BMPstring($this->gdimg_output); 545 unset($phpthumb_bmp); 546 break; 547 548 case 'ico': 549 if (!@include_once(dirname(__FILE__).'/phpthumb.ico.php')) { 550 $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.ico.php" which is required for ICO format output', __FILE__, __LINE__); 551 ob_end_clean(); 552 return false; 553 } 554 $phpthumb_ico = new phpthumb_ico(); 555 $arrayOfOutputImages = array($this->gdimg_output); 556 $this->outputImageData = $phpthumb_ico->GD2ICOstring($arrayOfOutputImages); 557 unset($phpthumb_ico); 558 break; 559 560 default: 561 $this->DebugMessage('RenderOutput failed because $this->thumbnailFormat "'.$this->thumbnailFormat.'" is not valid', __FILE__, __LINE__); 562 ob_end_clean(); 563 return false; 564 } 565 ob_end_clean(); 566 if (!$this->outputImageData) { 567 $this->DebugMessage('RenderOutput() for "'.$this->thumbnailFormat.'" failed', __FILE__, __LINE__); 568 ob_end_clean(); 569 return false; 570 } 571 $this->DebugMessage('RenderOutput() completing with $this->outputImageData = '.strlen($this->outputImageData).' bytes', __FILE__, __LINE__); 572 return true; 573 } 574 575 576 // public: 577 function RenderToFile($filename) { 578 if (preg_match('#^[a-z0-9]+://#i', $filename)) { 579 $this->DebugMessage('RenderToFile() failed because $filename ('.$filename.') is a URL', __FILE__, __LINE__); 580 return false; 581 } 582 // render thumbnail to this file only, do not cache, do not output to browser 583 //$renderfilename = $this->ResolveFilenameToAbsolute(dirname($filename)).DIRECTORY_SEPARATOR.basename($filename); 584 $renderfilename = $filename; 585 if (($filename{0} != '/') && ($filename{0} != '\\') && ($filename{1} != ':')) { 586 $renderfilename = $this->ResolveFilenameToAbsolute($renderfilename); 587 } 588 if (!@is_writable(dirname($renderfilename))) { 589 $this->DebugMessage('RenderToFile() failed because "'.dirname($renderfilename).'/" is not writable', __FILE__, __LINE__); 590 return false; 591 } 592 if (@is_file($renderfilename) && !@is_writable($renderfilename)) { 593 $this->DebugMessage('RenderToFile() failed because "'.$renderfilename.'" is not writable', __FILE__, __LINE__); 594 return false; 595 } 596 597 if ($this->RenderOutput()) { 598 if (file_put_contents($renderfilename, $this->outputImageData)) { 599 @chmod($renderfilename, $this->getParameter('config_file_create_mask')); 600 $this->DebugMessage('RenderToFile('.$renderfilename.') succeeded', __FILE__, __LINE__); 601 return true; 602 } 603 if (!@file_exists($renderfilename)) { 604 $this->DebugMessage('RenderOutput ['.$this->thumbnailFormat.'('.$renderfilename.')] did not appear to fail, but the output image does not exist either...', __FILE__, __LINE__); 605 } 606 } else { 607 $this->DebugMessage('RenderOutput ['.$this->thumbnailFormat.'('.$renderfilename.')] failed', __FILE__, __LINE__); 608 } 609 return false; 610 } 611 612 613 // public: 614 function OutputThumbnail() { 615 $this->purgeTempFiles(); 616 617 if (!$this->useRawIMoutput && !is_resource($this->gdimg_output)) { 618 $this->DebugMessage('OutputThumbnail() failed because !is_resource($this->gdimg_output)', __FILE__, __LINE__); 619 return false; 620 } 621 if (headers_sent()) { 622 return $this->ErrorImage('OutputThumbnail() failed - headers already sent'); 623 } 624 625 $downloadfilename = phpthumb_functions::SanitizeFilename(is_string($this->sia) ? $this->sia : ($this->down ? $this->down : 'phpThumb_generated_thumbnail'.'.'.$this->thumbnailFormat)); 626 $this->DebugMessage('Content-Disposition header filename set to "'.$downloadfilename.'"', __FILE__, __LINE__); 627 if ($downloadfilename) { 628 header('Content-Disposition: '.($this->down ? 'attachment' : 'inline').'; filename="'.$downloadfilename.'"'); 629 } else { 630 $this->DebugMessage('failed to send Content-Disposition header because $downloadfilename is empty', __FILE__, __LINE__); 631 } 632 633 if ($this->useRawIMoutput) { 634 635 header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat)); 636 echo $this->IMresizedData; 637 638 } else { 639 640 $this->DebugMessage('imageinterlace($this->gdimg_output, '.intval($this->config_output_interlace).')', __FILE__, __LINE__); 641 imageinterlace($this->gdimg_output, intval($this->config_output_interlace)); 642 switch ($this->thumbnailFormat) { 643 case 'jpeg': 644 header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat)); 645 $ImageOutFunction = 'image'.$this->thumbnailFormat; 646 @$ImageOutFunction($this->gdimg_output, null, $this->thumbnailQuality); 647 break; 648 649 case 'png': 650 case 'gif': 651 header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat)); 652 $ImageOutFunction = 'image'.$this->thumbnailFormat; 653 @$ImageOutFunction($this->gdimg_output); 654 break; 655 656 case 'bmp': 657 if (!@include_once(dirname(__FILE__).'/phpthumb.bmp.php')) { 658 $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.bmp.php" which is required for BMP format output', __FILE__, __LINE__); 659 return false; 660 } 661 $phpthumb_bmp = new phpthumb_bmp(); 662 if (is_object($phpthumb_bmp)) { 663 $bmp_data = $phpthumb_bmp->GD2BMPstring($this->gdimg_output); 664 unset($phpthumb_bmp); 665 if (!$bmp_data) { 666 $this->DebugMessage('$phpthumb_bmp->GD2BMPstring() failed', __FILE__, __LINE__); 667 return false; 668 } 669 header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat)); 670 echo $bmp_data; 671 } else { 672 $this->DebugMessage('new phpthumb_bmp() failed', __FILE__, __LINE__); 673 return false; 674 } 675 break; 676 677 case 'ico': 678 if (!@include_once(dirname(__FILE__).'/phpthumb.ico.php')) { 679 $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.ico.php" which is required for ICO format output', __FILE__, __LINE__); 680 return false; 681 } 682 $phpthumb_ico = new phpthumb_ico(); 683 if (is_object($phpthumb_ico)) { 684 $arrayOfOutputImages = array($this->gdimg_output); 685 $ico_data = $phpthumb_ico->GD2ICOstring($arrayOfOutputImages); 686 unset($phpthumb_ico); 687 if (!$ico_data) { 688 $this->DebugMessage('$phpthumb_ico->GD2ICOstring() failed', __FILE__, __LINE__); 689 return false; 690 } 691 header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat)); 692 echo $ico_data; 693 } else { 694 $this->DebugMessage('new phpthumb_ico() failed', __FILE__, __LINE__); 695 return false; 696 } 697 break; 698 699 default: 700 $this->DebugMessage('OutputThumbnail failed because $this->thumbnailFormat "'.$this->thumbnailFormat.'" is not valid', __FILE__, __LINE__); 701 return false; 702 break; 703 } 704 705 } 706 return true; 707 } 708 709 710 // public: 711 function CleanUpCacheDirectory() { 712 $this->DebugMessage('CleanUpCacheDirectory() set to purge ('.(is_null($this->config_cache_maxage) ? 'NULL' : number_format($this->config_cache_maxage / 86400, 1)).' days; '.(is_null($this->config_cache_maxsize) ? 'NULL' : number_format($this->config_cache_maxsize / 1048576, 2)).' MB; '.(is_null($this->config_cache_maxfiles) ? 'NULL' : number_format($this->config_cache_maxfiles)).' files)', __FILE__, __LINE__); 713 714 if (!is_writable($this->config_cache_directory)) { 715 $this->DebugMessage('CleanUpCacheDirectory() skipped because "'.$this->config_cache_directory.'" is not writable', __FILE__, __LINE__); 716 return true; 717 } 718 719 // cache status of cache directory for 1 hour to avoid hammering the filesystem functions 720 $phpThumbCacheStats_filename = $this->config_cache_directory.DIRECTORY_SEPARATOR.'phpThumbCacheStats.txt'; 721 if (file_exists($phpThumbCacheStats_filename) && is_readable($phpThumbCacheStats_filename) && (filemtime($phpThumbCacheStats_filename) >= (time() - 3600))) { 722 $this->DebugMessage('CleanUpCacheDirectory() skipped because "'.$phpThumbCacheStats_filename.'" is recently modified', __FILE__, __LINE__); 723 return true; 724 } 725 if (!@touch($phpThumbCacheStats_filename)) { 726 $this->DebugMessage('touch('.$phpThumbCacheStats_filename.') failed', __FILE__, __LINE__); 727 } 728 729 $DeletedKeys = array(); 730 $AllFilesInCacheDirectory = array(); 731 if (($this->config_cache_maxage > 0) || ($this->config_cache_maxsize > 0) || ($this->config_cache_maxfiles > 0)) { 732 $CacheDirOldFilesAge = array(); 733 $CacheDirOldFilesSize = array(); 734 $AllFilesInCacheDirectory = phpthumb_functions::GetAllFilesInSubfolders($this->config_cache_directory); 735 foreach ($AllFilesInCacheDirectory as $fullfilename) { 736 if (preg_match('#'.preg_quote($this->config_cache_prefix).'#i', $fullfilename) && file_exists($fullfilename)) { 737 $CacheDirOldFilesAge[$fullfilename] = @fileatime($fullfilename); 738 if ($CacheDirOldFilesAge[$fullfilename] == 0) { 739 $CacheDirOldFilesAge[$fullfilename] = @filemtime($fullfilename); 740 } 741 $CacheDirOldFilesSize[$fullfilename] = @filesize($fullfilename); 742 } 743 } 744 if (empty($CacheDirOldFilesSize)) { 745 $this->DebugMessage('CleanUpCacheDirectory() skipped because $CacheDirOldFilesSize is empty (phpthumb_functions::GetAllFilesInSubfolders('.$this->config_cache_directory.') found no files)', __FILE__, __LINE__); 746 return true; 747 } 748 $DeletedKeys['zerobyte'] = array(); 749 foreach ($CacheDirOldFilesSize as $fullfilename => $filesize) { 750 // purge all zero-size files more than an hour old (to prevent trying to delete just-created and/or in-use files) 751 $cutofftime = time() - 3600; 752 if (($filesize == 0) && ($CacheDirOldFilesAge[$fullfilename] < $cutofftime)) { 753 $this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__); 754 if (@unlink($fullfilename)) { 755 $DeletedKeys['zerobyte'][] = $fullfilename; 756 unset($CacheDirOldFilesSize[$fullfilename]); 757 unset($CacheDirOldFilesAge[$fullfilename]); 758 } 759 } 760 } 761 $this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['zerobyte']).' zero-byte files', __FILE__, __LINE__); 762 asort($CacheDirOldFilesAge); 763 764 if ($this->config_cache_maxfiles > 0) { 765 $TotalCachedFiles = count($CacheDirOldFilesAge); 766 $DeletedKeys['maxfiles'] = array(); 767 foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) { 768 if ($TotalCachedFiles > $this->config_cache_maxfiles) { 769 $this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__); 770 if (@unlink($fullfilename)) { 771 $TotalCachedFiles--; 772 $DeletedKeys['maxfiles'][] = $fullfilename; 773 } 774 } else { 775 // there are few enough files to keep the rest 776 break; 777 } 778 } 779 $this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxfiles']).' files based on (config_cache_maxfiles='.$this->config_cache_maxfiles.')', __FILE__, __LINE__); 780 foreach ($DeletedKeys['maxfiles'] as $fullfilename) { 781 unset($CacheDirOldFilesAge[$fullfilename]); 782 unset($CacheDirOldFilesSize[$fullfilename]); 783 } 784 } 785 786 if ($this->config_cache_maxage > 0) { 787 $mindate = time() - $this->config_cache_maxage; 788 $DeletedKeys['maxage'] = array(); 789 foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) { 790 if ($filedate > 0) { 791 if ($filedate < $mindate) { 792 $this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__); 793 if (@unlink($fullfilename)) { 794 $DeletedKeys['maxage'][] = $fullfilename; 795 } 796 } else { 797 // the rest of the files are new enough to keep 798 break; 799 } 800 } 801 } 802 $this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxage']).' files based on (config_cache_maxage='.$this->config_cache_maxage.')', __FILE__, __LINE__); 803 foreach ($DeletedKeys['maxage'] as $fullfilename) { 804 unset($CacheDirOldFilesAge[$fullfilename]); 805 unset($CacheDirOldFilesSize[$fullfilename]); 806 } 807 } 808 809 if ($this->config_cache_maxsize > 0) { 810 $TotalCachedFileSize = array_sum($CacheDirOldFilesSize); 811 $DeletedKeys['maxsize'] = array(); 812 foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) { 813 if ($TotalCachedFileSize > $this->config_cache_maxsize) { 814 $this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__); 815 if (@unlink($fullfilename)) { 816 $TotalCachedFileSize -= $CacheDirOldFilesSize[$fullfilename]; 817 $DeletedKeys['maxsize'][] = $fullfilename; 818 } 819 } else { 820 // the total filesizes are small enough to keep the rest of the files 821 break; 822 } 823 } 824 $this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxsize']).' files based on (config_cache_maxsize='.$this->config_cache_maxsize.')', __FILE__, __LINE__); 825 foreach ($DeletedKeys['maxsize'] as $fullfilename) { 826 unset($CacheDirOldFilesAge[$fullfilename]); 827 unset($CacheDirOldFilesSize[$fullfilename]); 828 } 829 } 830 831 } else { 832 $this->DebugMessage('skipping CleanUpCacheDirectory() because config set to not use it', __FILE__, __LINE__); 833 } 834 $totalpurged = 0; 835 foreach ($DeletedKeys as $key => $value) { 836 $totalpurged += count($value); 837 } 838 $this->DebugMessage('CleanUpCacheDirectory() purged '.$totalpurged.' files (from '.count($AllFilesInCacheDirectory).') based on config settings', __FILE__, __LINE__); 839 if ($totalpurged > 0) { 840 $empty_dirs = array(); 841 foreach ($AllFilesInCacheDirectory as $fullfilename) { 842 if (is_dir($fullfilename)) { 843 $empty_dirs[$this->realPathSafe($fullfilename)] = 1; 844 } else { 845 unset($empty_dirs[$this->realPathSafe(dirname($fullfilename))]); 846 } 847 } 848 krsort($empty_dirs); 849 $totalpurgeddirs = 0; 850 foreach ($empty_dirs as $empty_dir => $dummy) { 851 if ($empty_dir == $this->config_cache_directory) { 852 // shouldn't happen, but just in case, don't let it delete actual cache directory 853 continue; 854 } elseif (@rmdir($empty_dir)) { 855 $totalpurgeddirs++; 856 } else { 857 $this->DebugMessage('failed to rmdir('.$empty_dir.')', __FILE__, __LINE__); 858 } 859 } 860 $this->DebugMessage('purged '.$totalpurgeddirs.' empty directories', __FILE__, __LINE__); 861 } 862 return true; 863 } 864 865 ////////////////////////////////////////////////////////////////////// 866 867 // private: re-initializator (call between rendering multiple images with one object) 868 function resetObject() { 869 $class_vars = get_class_vars(get_class($this)); 870 foreach ($class_vars as $key => $value) { 871 // do not clobber debug or config info 872 if (!preg_match('#^(config_|debug|fatalerror)#i', $key)) { 873 $this->$key = $value; 874 } 875 } 876 $this->phpThumb(); // re-initialize some class variables 877 return true; 878 } 879 880 ////////////////////////////////////////////////////////////////////// 881 882 function ResolveSource() { 883 if (is_resource($this->gdimg_source)) { 884 $this->DebugMessage('ResolveSource() exiting because is_resource($this->gdimg_source)', __FILE__, __LINE__); 885 return true; 886 } 887 if ($this->rawImageData) { 888 $this->sourceFilename = null; 889 $this->DebugMessage('ResolveSource() exiting because $this->rawImageData is set ('.number_format(strlen($this->rawImageData)).' bytes)', __FILE__, __LINE__); 890 return true; 891 } 892 if ($this->sourceFilename) { 893 $this->sourceFilename = $this->ResolveFilenameToAbsolute($this->sourceFilename); 894 $this->DebugMessage('$this->sourceFilename set to "'.$this->sourceFilename.'"', __FILE__, __LINE__); 895 } elseif ($this->src) { 896 $this->sourceFilename = $this->ResolveFilenameToAbsolute($this->src); 897 $this->DebugMessage('$this->sourceFilename set to "'.$this->sourceFilename.'" from $this->src ('.$this->src.')', __FILE__, __LINE__); 898 } else { 899 return $this->ErrorImage('$this->sourceFilename and $this->src are both empty'); 900 } 901 if ($this->iswindows && ((substr($this->sourceFilename, 0, 2) == '//') || (substr($this->sourceFilename, 0, 2) == '\\\\'))) { 902 // Windows \\share\filename.ext 903 } elseif (preg_match('#^[a-z0-9]+://#i', $this->sourceFilename, $protocol_matches)) { 904 if (preg_match('#^(f|ht)tps?\://#i', $this->sourceFilename)) { 905 // URL 906 if ($this->config_http_user_agent) { 907 ini_set('user_agent', $this->config_http_user_agent); 908 } 909 } else { 910 return $this->ErrorImage('only FTP and HTTP/HTTPS protocols are allowed, "'.$protocol_matches[1].'" is not'); 911 } 912 } elseif (!@file_exists($this->sourceFilename)) { 913 return $this->ErrorImage('"'.$this->sourceFilename.'" does not exist'); 914 } elseif (!@is_file($this->sourceFilename)) { 915 return $this->ErrorImage('"'.$this->sourceFilename.'" is not a file'); 916 } 917 return true; 918 } 919 920 921 function setOutputFormat() { 922 static $alreadyCalled = false; 923 if ($this->thumbnailFormat && $alreadyCalled) { 924 return true; 925 } 926 $alreadyCalled = true; 927 928 $AvailableImageOutputFormats = array(); 929 $AvailableImageOutputFormats[] = 'text'; 930 if (@is_readable(dirname(__FILE__).'/phpthumb.ico.php')) { 931 $AvailableImageOutputFormats[] = 'ico'; 932 } 933 if (@is_readable(dirname(__FILE__).'/phpthumb.bmp.php')) { 934 $AvailableImageOutputFormats[] = 'bmp'; 935 } 936 937 $this->thumbnailFormat = 'ico'; 938 939 // Set default output format based on what image types are available 940 if (function_exists('imagetypes')) { 941 $imagetypes = imagetypes(); 942 if ($imagetypes & IMG_WBMP) { 943 $this->thumbnailFormat = 'wbmp'; 944 $AvailableImageOutputFormats[] = 'wbmp'; 945 } 946 if ($imagetypes & IMG_GIF) { 947 $this->thumbnailFormat = 'gif'; 948 $AvailableImageOutputFormats[] = 'gif'; 949 } 950 if ($imagetypes & IMG_PNG) { 951 $this->thumbnailFormat = 'png'; 952 $AvailableImageOutputFormats[] = 'png'; 953 } 954 if ($imagetypes & IMG_JPG) { 955 $this->thumbnailFormat = 'jpeg'; 956 $AvailableImageOutputFormats[] = 'jpeg'; 957 } 958 } else { 959 $this->DebugMessage('imagetypes() does not exist - GD support might not be enabled?', __FILE__, __LINE__); 960 } 961 if ($this->ImageMagickVersion()) { 962 $IMformats = array('jpeg', 'png', 'gif', 'bmp', 'ico', 'wbmp'); 963 $this->DebugMessage('Addding ImageMagick formats to $AvailableImageOutputFormats ('.implode(';', $AvailableImageOutputFormats).')', __FILE__, __LINE__); 964 foreach ($IMformats as $key => $format) { 965 $AvailableImageOutputFormats[] = $format; 966 } 967 } 968 $AvailableImageOutputFormats = array_unique($AvailableImageOutputFormats); 969 $this->DebugMessage('$AvailableImageOutputFormats = array('.implode(';', $AvailableImageOutputFormats).')', __FILE__, __LINE__); 970 971 $this->f = preg_replace('#[^a-z]#', '', strtolower($this->f)); 972 if (strtolower($this->config_output_format) == 'jpg') { 973 $this->config_output_format = 'jpeg'; 974 } 975 if (strtolower($this->f) == 'jpg') { 976 $this->f = 'jpeg'; 977 } 978 if (phpthumb_functions::CaseInsensitiveInArray($this->config_output_format, $AvailableImageOutputFormats)) { 979 // set output format to config default if that format is available 980 $this->DebugMessage('$this->thumbnailFormat set to $this->config_output_format "'.strtolower($this->config_output_format).'"', __FILE__, __LINE__); 981 $this->thumbnailFormat = strtolower($this->config_output_format); 982 } elseif ($this->config_output_format) { 983 $this->DebugMessage('$this->thumbnailFormat staying as "'.$this->thumbnailFormat.'" because $this->config_output_format ('.strtolower($this->config_output_format).') is not in $AvailableImageOutputFormats', __FILE__, __LINE__); 984 } 985 if ($this->f && (phpthumb_functions::CaseInsensitiveInArray($this->f, $AvailableImageOutputFormats))) { 986 // override output format if $this->f is set and that format is available 987 $this->DebugMessage('$this->thumbnailFormat set to $this->f "'.strtolower($this->f).'"', __FILE__, __LINE__); 988 $this->thumbnailFormat = strtolower($this->f); 989 } elseif ($this->f) { 990 $this->DebugMessage('$this->thumbnailFormat staying as "'.$this->thumbnailFormat.'" because $this->f ('.strtolower($this->f).') is not in $AvailableImageOutputFormats', __FILE__, __LINE__); 991 } 992 993 // for JPEG images, quality 1 (worst) to 99 (best) 994 // quality < 25 is nasty, with not much size savings - not recommended 995 // problems with 100 - invalid JPEG? 996 $this->thumbnailQuality = max(1, min(99, ($this->q ? intval($this->q) : 75))); 997 $this->DebugMessage('$this->thumbnailQuality set to "'.$this->thumbnailQuality.'"', __FILE__, __LINE__); 998 999 return true; 1000 } 1001 1002 1003 function setCacheDirectory() { 1004 // resolve cache directory to absolute pathname 1005 $this->DebugMessage('setCacheDirectory() starting with config_cache_directory = "'.$this->config_cache_directory.'"', __FILE__, __LINE__); 1006 if (substr($this->config_cache_directory, 0, 1) == '.') { 1007 if (preg_match('#^(f|ht)tps?\://#i', $this->src)) { 1008 if (!$this->config_cache_disable_warning) { 1009 $this->ErrorImage('$this->config_cache_directory ('.$this->config_cache_directory.') cannot be used for remote images. Adjust "cache_directory" or "cache_disable_warning" in phpThumb.config.php'); 1010 } 1011 } elseif ($this->src) { 1012 // resolve relative cache directory to source image 1013 $this->config_cache_directory = dirname($this->ResolveFilenameToAbsolute($this->src)).DIRECTORY_SEPARATOR.$this->config_cache_directory; 1014 } else { 1015 // $this->new is probably set 1016 } 1017 } 1018 if (substr($this->config_cache_directory, -1) == '/') { 1019 $this->config_cache_directory = substr($this->config_cache_directory, 0, -1); 1020 } 1021 if ($this->iswindows) { 1022 $this->config_cache_directory = str_replace('/', DIRECTORY_SEPARATOR, $this->config_cache_directory); 1023 } 1024 if ($this->config_cache_directory) { 1025 $real_cache_path = $this->realPathSafe($this->config_cache_directory); 1026 if (!$real_cache_path) { 1027 $this->DebugMessage('$this->realPathSafe($this->config_cache_directory) failed for "'.$this->config_cache_directory.'"', __FILE__, __LINE__); 1028 if (!is_dir($this->config_cache_directory)) { 1029 $this->DebugMessage('!is_dir('.$this->config_cache_directory.')', __FILE__, __LINE__); 1030 } 1031 } 1032 if ($real_cache_path) { 1033 $this->DebugMessage('setting config_cache_directory to $this->realPathSafe('.$this->config_cache_directory.') = "'.$real_cache_path.'"', __FILE__, __LINE__); 1034 $this->config_cache_directory = $real_cache_path; 1035 } 1036 } 1037 if (!is_dir($this->config_cache_directory)) { 1038 if (!$this->config_cache_disable_warning) { 1039 $this->ErrorImage('$this->config_cache_directory ('.$this->config_cache_directory.') does not exist. Adjust "cache_directory" or "cache_disable_warning" in phpThumb.config.php'); 1040 } 1041 $this->DebugMessage('$this->config_cache_directory ('.$this->config_cache_directory.') is not a directory', __FILE__, __LINE__); 1042 $this->config_cache_directory = null; 1043 } elseif (!@is_writable($this->config_cache_directory)) { 1044 $this->DebugMessage('$this->config_cache_directory is not writable ('.$this->config_cache_directory.')', __FILE__, __LINE__); 1045 } 1046 1047 $this->InitializeTempDirSetting(); 1048 if (!@is_dir($this->config_temp_directory) && !@is_writable($this->config_temp_directory) && @is_dir($this->config_cache_directory) && @is_writable($this->config_cache_directory)) { 1049 $this->DebugMessage('setting $this->config_temp_directory = $this->config_cache_directory ('.$this->config_cache_directory.')', __FILE__, __LINE__); 1050 $this->config_temp_directory = $this->config_cache_directory; 1051 } 1052 return true; 1053 } 1054 1055 /* Takes the array of path segments up to now, and the next segment (maybe a modifier: empty, . or ..) 1056 Applies it, adding or removing from $segments as a result. Returns nothing. */ 1057 // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961 1058 function applyPathSegment(&$segments, $segment) { 1059 if ($segment == '.') { 1060 return; // always remove 1061 } 1062 if ($segment == '') { 1063 $test = array_pop($segments); 1064 if (is_null($test)) { 1065 $segments[] = $segment; // keep the first empty block 1066 } elseif ($test == '') { 1067 $test = array_pop($segments); 1068 if (is_null($test)) { 1069 $segments[] = $test; 1070 $segments[] = $segment; // keep the second one too 1071 } else { // put both back and ignore segment 1072 $segments[] = $test; 1073 $segments[] = $test; 1074 } 1075 } else { 1076 $segments[] = $test; // ignore empty blocks 1077 } 1078 } else { 1079 if ($segment == '..') { 1080 $test = array_pop($segments); 1081 if (is_null($test)) { 1082 $segments[] = $segment; 1083 } elseif ($test == '..') { 1084 $segments[] = $test; 1085 $segments[] = $segment; 1086 } else { 1087 if ($test == '') { 1088 $segments[] = $test; 1089 } // else nothing, remove both 1090 } 1091 } else { 1092 $segments[] = $segment; 1093 } 1094 } 1095 } 1096 1097 /* Takes array of path components, normalizes it: removes empty slots and '.', collapses '..' and folder names. Returns array. */ 1098 // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961 1099 function normalizePath($segments) { 1100 $parts = array(); 1101 foreach ($segments as $segment) { 1102 $this->applyPathSegment($parts, $segment); 1103 } 1104 return $parts; 1105 } 1106 1107 /* True if the provided path points (without resolving symbolic links) into one of the allowed directories. */ 1108 // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961 1109 function matchPath($path, $allowed_dirs) { 1110 if (!empty($allowed_dirs)) { 1111 foreach ($allowed_dirs as $one_dir) { 1112 if (preg_match('#^'.preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', $this->realPathSafe($one_dir))).'#', $path)) { 1113 return true; 1114 } 1115 } 1116 } 1117 return false; 1118 } 1119 1120 /* True if the provided path points inside one of open_basedirs (or if open_basedirs are disabled) */ 1121 // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961 1122 function isInOpenBasedir($path) { 1123 static $open_basedirs = null; 1124 if (is_null($open_basedirs)) { 1125 $ini_text = ini_get('open_basedir'); 1126 $this->DebugMessage('open_basedir: "'.$ini_text.'"', __FILE__, __LINE__); 1127 $open_basedirs = array(); 1128 if (strlen($ini_text) > 0) { 1129 foreach (preg_split('#[;:]#', $ini_text) as $key => $value) { 1130 $open_basedirs[$key] = $this->realPathSafe($value); 1131 } 1132 } 1133 } 1134 return (empty($open_basedirs) || $this->matchPath($path, $open_basedirs)); 1135 } 1136 1137 /* Resolves all symlinks in $path, checking that each continuous part ends in an allowed zone. Returns null, if any component leads outside of allowed zone. */ 1138 // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961 1139 function resolvePath($path, $allowed_dirs) { 1140 $this->DebugMessage('resolvePath: '.$path.' (allowed_dirs: '.print_r($allowed_dirs, true).')', __FILE__, __LINE__); 1141 1142 // add base path to the top of the list 1143 if (!$this->config_allow_src_above_docroot) { 1144 array_unshift($allowed_dirs, $this->realPathSafe($this->config_document_root)); 1145 } else { 1146 if (!$this->config_allow_src_above_phpthumb) { 1147 array_unshift($allowed_dirs, $this->realPathSafe(dirname(__FILE__))); 1148 } else { 1149 // no checks are needed, offload the work to realpath and forget about it 1150 $this->DebugMessage('resolvePath: checks disabled, returning '.$this->realPathSafe($path), __FILE__, __LINE__); 1151 return $this->realPathSafe($path); 1152 } 1153 } 1154 if ($path == '') { 1155 return null; // save us trouble 1156 } 1157 1158 do { 1159 $this->DebugMessage('resolvePath: iteration, path='.$path.', base path = '.$allowed_dirs[0], __FILE__, __LINE__); 1160 1161 $parts = array(); 1162 // do not use "cleaner" foreach version of this loop as later code relies on both $segments and $i 1163 // http://support.silisoftware.com/phpBB3/viewtopic.php?t=964 1164 $segments = explode(DIRECTORY_SEPARATOR, $path); 1165 for ($i = 0; $i < count($segments); $i++) { 1166 $this->applyPathSegment($parts, $segments[$i]); 1167 $thispart = implode(DIRECTORY_SEPARATOR, $parts); 1168 if ($this->isInOpenBasedir($thispart)) { 1169 if (is_link($thispart)) { 1170 break; 1171 } 1172 } 1173 } 1174 1175 $this->DebugMessage('resolvePath: stop at component '.$i, __FILE__, __LINE__); 1176 // test the part up to here 1177 $path = implode(DIRECTORY_SEPARATOR, $parts); 1178 $this->DebugMessage('resolvePath: stop at path='.$path, __FILE__, __LINE__); 1179 if (!$this->matchPath($path, $allowed_dirs)) { 1180 $this->DebugMessage('resolvePath: no match, returning null', __FILE__, __LINE__); 1181 return null; 1182 } 1183 if ($i >= count($segments)) { // reached end 1184 $this->DebugMessage('resolvePath: path parsed, over', __FILE__, __LINE__); 1185 break; 1186 } 1187 // else it's symlink, rewrite path 1188 $path = readlink($path); 1189 $this->DebugMessage('resolvePath: symlink matched, target='.$path, __FILE__, __LINE__); 1190 1191 /* 1192 Replace base path with symlink target. 1193 Assuming: 1194 /www/img/external -> /external 1195 This is allowed: 1196 GET /www/img/external/../external/test/pic.jpg 1197 This isn't: 1198 GET /www/img/external/../www/img/pic.jpg 1199 So there's only one base path which is the last symlink target, but any number of stable whitelisted paths. 1200 */ 1201 if ($this->config_auto_allow_symlinks) { 1202 $allowed_dirs[0] = $path; 1203 } 1204 $path = $path.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, array_slice($segments,$i + 1)); 1205 } while (true); 1206 return $path; 1207 } 1208 1209 1210 function realPathSafe($filename) { 1211 // http://php.net/manual/en/function.realpath.php -- "Note: The running script must have executable permissions on all directories in the hierarchy, otherwise realpath() will return FALSE" 1212 // realPathSafe() provides a reasonable facsimile of realpath() but does not resolve symbolic links, nor does it check that the file/path actually exists 1213 if (!$this->config_disable_realpath) { 1214 return realpath($filename); 1215 } 1216 1217 // http://stackoverflow.com/questions/21421569 1218 $newfilename = preg_replace('#[\\/]+#', DIRECTORY_SEPARATOR, $filename); 1219 if (!preg_match('#^'.DIRECTORY_SEPARATOR.'#', $newfilename)) { 1220 $newfilename = dirname(__FILE__).DIRECTORY_SEPARATOR.$newfilename; 1221 } 1222 do { 1223 $beforeloop = $newfilename; 1224 1225 // Replace all sequences of more than one / with a single one [[ If you're working on a system that treats // at the start of a path as special, make sure you replace multiple / characters at the start with two of them. This is the only place where POSIX allows (but does not mandate) special handling for multiples, in all other cases, multiple / characters are equivalent to a single one.]] 1226 $newfilename = preg_replace('#'.DIRECTORY_SEPARATOR.'+#', DIRECTORY_SEPARATOR, $newfilename); 1227 1228 // Replace all occurrences of /./ with / 1229 $newfilename = preg_replace('#'.DIRECTORY_SEPARATOR.'\\.'.DIRECTORY_SEPARATOR.'#', DIRECTORY_SEPARATOR, $newfilename); 1230 1231 // Remove ./ if at the start 1232 $newfilename = preg_replace('#^\\.'.DIRECTORY_SEPARATOR.'#', '', $newfilename); 1233 1234 // Remove /. if at the end 1235 $newfilename = preg_replace('#'.DIRECTORY_SEPARATOR.'\\.$#', '', $newfilename); 1236 1237 // Replace /anything/../ with / 1238 $newfilename = preg_replace('#'.DIRECTORY_SEPARATOR.'[^'.DIRECTORY_SEPARATOR.']+'.DIRECTORY_SEPARATOR.'\\.\\.'.DIRECTORY_SEPARATOR.'#', DIRECTORY_SEPARATOR, $newfilename); 1239 1240 // Remove /anything/.. if at the end 1241 $newfilename = preg_replace('#'.DIRECTORY_SEPARATOR.'[^'.DIRECTORY_SEPARATOR.']+'.DIRECTORY_SEPARATOR.'\\.\\.$#', '', $newfilename); 1242 1243 } while ($newfilename != $beforeloop); 1244 return $newfilename; 1245 } 1246 1247 1248 function ResolveFilenameToAbsolute($filename) { 1249 if (empty($filename)) { 1250 return false; 1251 } 1252 1253 if (preg_match('#^[a-z0-9]+\:/{1,2}#i', $filename)) { 1254 // eg: http://host/path/file.jpg (HTTP URL) 1255 // eg: ftp://host/path/file.jpg (FTP URL) 1256 // eg: data1:/path/file.jpg (Netware path) 1257 1258 //$AbsoluteFilename = $filename; 1259 return $filename; 1260 1261 } elseif ($this->iswindows && isset($filename{1}) && ($filename{1} == ':')) { 1262 1263 // absolute pathname (Windows) 1264 $AbsoluteFilename = $filename; 1265 1266 } elseif ($this->iswindows && ((substr($filename, 0, 2) == '//') || (substr($filename, 0, 2) == '\\\\'))) { 1267 1268 // absolute pathname (Windows) 1269 $AbsoluteFilename = $filename; 1270 1271 } elseif ($filename{0} == '/') { 1272 1273 if (@is_readable($filename) && !@is_readable($this->config_document_root.$filename)) { 1274 1275 // absolute filename (*nix) 1276 $AbsoluteFilename = $filename; 1277 1278 } elseif (isset($filename{1}) && ($filename{1} == '~')) { 1279 1280 // /~user/path 1281 if ($ApacheLookupURIarray = phpthumb_functions::ApacheLookupURIarray($filename)) { 1282 $AbsoluteFilename = $ApacheLookupURIarray['filename']; 1283 } else { 1284 $AbsoluteFilename = $this->realPathSafe($filename); 1285 if (@is_readable($AbsoluteFilename)) { 1286 $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "'.$filename.'", but the correct filename ('.$AbsoluteFilename.') seems to have been resolved with $this->realPathSafe($filename)', __FILE__, __LINE__); 1287 } elseif (is_dir(dirname($AbsoluteFilename))) { 1288 $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "'.dirname($filename).'", but the correct directory ('.dirname($AbsoluteFilename).') seems to have been resolved with $this->realPathSafe(.)', __FILE__, __LINE__); 1289 } else { 1290 return $this->ErrorImage('phpthumb_functions::ApacheLookupURIarray() failed for "'.$filename.'". This has been known to fail on Apache2 - try using the absolute filename for the source image (ex: "/home/user/httpdocs/image.jpg" instead of "/~user/image.jpg")'); 1291 } 1292 } 1293 1294 } else { 1295 1296 // relative filename (any OS) 1297 if (preg_match('#^'.preg_quote($this->config_document_root).'#', $filename)) { 1298 $AbsoluteFilename = $filename; 1299 $this->DebugMessage('ResolveFilenameToAbsolute() NOT prepending $this->config_document_root ('.$this->config_document_root.') to $filename ('.$filename.') resulting in ($AbsoluteFilename = "'.$AbsoluteFilename.'")', __FILE__, __LINE__); 1300 } else { 1301 $AbsoluteFilename = $this->config_document_root.$filename; 1302 $this->DebugMessage('ResolveFilenameToAbsolute() prepending $this->config_document_root ('.$this->config_document_root.') to $filename ('.$filename.') resulting in ($AbsoluteFilename = "'.$AbsoluteFilename.'")', __FILE__, __LINE__); 1303 } 1304 1305 } 1306 1307 } else { 1308 1309 // relative to current directory (any OS) 1310 $AbsoluteFilename = dirname(__FILE__).DIRECTORY_SEPARATOR.preg_replace('#[/\\\\]#', DIRECTORY_SEPARATOR, $filename); 1311 1312 if (substr(dirname(@$_SERVER['PHP_SELF']), 0, 2) == '/~') { 1313 if ($ApacheLookupURIarray = phpthumb_functions::ApacheLookupURIarray(dirname(@$_SERVER['PHP_SELF']))) { 1314 $AbsoluteFilename = $ApacheLookupURIarray['filename'].DIRECTORY_SEPARATOR.$filename; 1315 } else { 1316 $AbsoluteFilename = $this->realPathSafe('.').DIRECTORY_SEPARATOR.$filename; 1317 if (@is_readable($AbsoluteFilename)) { 1318 $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "'.dirname(@$_SERVER['PHP_SELF']).'", but the correct filename ('.$AbsoluteFilename.') seems to have been resolved with $this->realPathSafe(.)/$filename', __FILE__, __LINE__); 1319 } elseif (is_dir(dirname($AbsoluteFilename))) { 1320 $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "'.dirname(@$_SERVER['PHP_SELF']).'", but the correct directory ('.dirname($AbsoluteFilename).') seems to have been resolved with $this->realPathSafe(.)', __FILE__, __LINE__); 1321 } else { 1322 return $this->ErrorImage('phpthumb_functions::ApacheLookupURIarray() failed for "'.dirname(@$_SERVER['PHP_SELF']).'". This has been known to fail on Apache2 - try using the absolute filename for the source image'); 1323 } 1324 } 1325 } 1326 1327 } 1328 /* 1329 // removed 2014-May-30: http://support.silisoftware.com/phpBB3/viewtopic.php?t=961 1330 if (is_link($AbsoluteFilename)) { 1331 $this->DebugMessage('is_link()==true, changing "'.$AbsoluteFilename.'" to "'.readlink($AbsoluteFilename).'"', __FILE__, __LINE__); 1332 $AbsoluteFilename = readlink($AbsoluteFilename); 1333 } 1334 if ($this->realPathSafe($AbsoluteFilename)) { 1335 $AbsoluteFilename = $this->realPathSafe($AbsoluteFilename); 1336 } 1337 */ 1338 if ($this->iswindows) { 1339 $AbsoluteFilename = preg_replace('#^'.preg_quote($this->realPathSafe($this->config_document_root)).'#i', str_replace('\\', '\\\\', $this->realPathSafe($this->config_document_root)), $AbsoluteFilename); 1340 $AbsoluteFilename = str_replace(DIRECTORY_SEPARATOR, '/', $AbsoluteFilename); 1341 } 1342 $AbsoluteFilename = $this->resolvePath($AbsoluteFilename, $this->config_additional_allowed_dirs); 1343 if (!$this->config_allow_src_above_docroot && !preg_match('#^'.preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', $this->realPathSafe($this->config_document_root))).'#', $AbsoluteFilename)) { 1344 $this->DebugMessage('!$this->config_allow_src_above_docroot therefore setting "'.$AbsoluteFilename.'" (outside "'.$this->realPathSafe($this->config_document_root).'") to null', __FILE__, __LINE__); 1345 return false; 1346 } 1347 if (!$this->config_allow_src_above_phpthumb && !preg_match('#^'.preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', dirname(__FILE__))).'#', $AbsoluteFilename)) { 1348 $this->DebugMessage('!$this->config_allow_src_above_phpthumb therefore setting "'.$AbsoluteFilename.'" (outside "'.dirname(__FILE__).'") to null', __FILE__, __LINE__); 1349 return false; 1350 } 1351 return $AbsoluteFilename; 1352 } 1353 1354 1355 function file_exists_ignoreopenbasedir($filename, $cached=true) { 1356 static $open_basedirs = null; 1357 static $file_exists_cache = array(); 1358 if (!$cached || !isset($file_exists_cache[$filename])) { 1359 if (is_null($open_basedirs)) { 1360 $open_basedirs = preg_split('#[;:]#', ini_get('open_basedir')); 1361 } 1362 if (empty($open_basedirs) || in_array(dirname($filename), $open_basedirs)) { 1363 $file_exists_cache[$filename] = file_exists($filename); 1364 } elseif ($this->iswindows) { 1365 $ls_filename = trim(phpthumb_functions::SafeExec('dir /b '.phpthumb_functions::escapeshellarg_replacement($filename))); 1366 $file_exists_cache[$filename] = ($ls_filename == basename($filename)); // command dir /b return only filename without path 1367 } else { 1368 $ls_filename = trim(phpthumb_functions::SafeExec('ls '.phpthumb_functions::escapeshellarg_replacement($filename))); 1369 $file_exists_cache[$filename] = ($ls_filename == $filename); 1370 } 1371 } 1372 return $file_exists_cache[$filename]; 1373 } 1374 1375 1376 function ImageMagickWhichConvert() { 1377 static $WhichConvert = null; 1378 if (is_null($WhichConvert)) { 1379 if ($this->iswindows) { 1380 $WhichConvert = false; 1381 } else { 1382 $IMwhichConvertCacheFilename = $this->config_cache_directory.DIRECTORY_SEPARATOR.'phpThumbCacheIMwhichConvert.txt'; 1383 if (($cachedwhichconvertstring = @file_get_contents($IMwhichConvertCacheFilename)) !== false) { 1384 $WhichConvert = $cachedwhichconvertstring; 1385 } else { 1386 $WhichConvert = trim(phpthumb_functions::SafeExec('which convert')); 1387 @file_put_contents($IMwhichConvertCacheFilename, $WhichConvert); 1388 @chmod($IMwhichConvertCacheFilename, $this->getParameter('config_file_create_mask')); 1389 } 1390 } 1391 } 1392 return $WhichConvert; 1393 } 1394 1395 1396 function ImageMagickCommandlineBase() { 1397 static $commandline = null; 1398 if (is_null($commandline)) { 1399 if ($this->issafemode) { 1400 $commandline = ''; 1401 return $commandline; 1402 } 1403 1404 $IMcommandlineBaseCacheFilename = $this->config_cache_directory.DIRECTORY_SEPARATOR.'phpThumbCacheIMcommandlineBase.txt'; 1405 if (($commandline = @file_get_contents($IMcommandlineBaseCacheFilename)) !== false) { 1406 return $commandline; 1407 } 1408 1409 $commandline = (!is_null($this->config_imagemagick_path) ? $this->config_imagemagick_path : ''); 1410 1411 if ($this->config_imagemagick_path && ($this->config_imagemagick_path != $this->realPathSafe($this->config_imagemagick_path))) { 1412 if (@is_executable($this->realPathSafe($this->config_imagemagick_path))) { 1413 $this->DebugMessage('Changing $this->config_imagemagick_path ('.$this->config_imagemagick_path.') to $this->realPathSafe($this->config_imagemagick_path) ('.$this->realPathSafe($this->config_imagemagick_path).')', __FILE__, __LINE__); 1414 $this->config_imagemagick_path = $this->realPathSafe($this->config_imagemagick_path); 1415 } else { 1416 $this->DebugMessage('Leaving $this->config_imagemagick_path as ('.$this->config_imagemagick_path.') because !is_execuatable($this->realPathSafe($this->config_imagemagick_path)) ('.$this->realPathSafe($this->config_imagemagick_path).')', __FILE__, __LINE__); 1417 } 1418 } 1419 $this->DebugMessage(' file_exists('.$this->config_imagemagick_path.') = '.intval( @file_exists($this->config_imagemagick_path)), __FILE__, __LINE__); 1420 $this->DebugMessage('file_exists_ignoreopenbasedir('.$this->config_imagemagick_path.') = '.intval($this->file_exists_ignoreopenbasedir($this->config_imagemagick_path)), __FILE__, __LINE__); 1421 $this->DebugMessage(' is_file('.$this->config_imagemagick_path.') = '.intval( @is_file($this->config_imagemagick_path)), __FILE__, __LINE__); 1422 $this->DebugMessage(' is_executable('.$this->config_imagemagick_path.') = '.intval( @is_executable($this->config_imagemagick_path)), __FILE__, __LINE__); 1423 1424 if ($this->file_exists_ignoreopenbasedir($this->config_imagemagick_path)) { 1425 1426 $this->DebugMessage('using ImageMagick path from $this->config_imagemagick_path ('.$this->config_imagemagick_path.')', __FILE__, __LINE__); 1427 if ($this->iswindows) { 1428 $commandline = substr($this->config_imagemagick_path, 0, 2).' && cd '.phpthumb_functions::escapeshellarg_replacement(str_replace('/', DIRECTORY_SEPARATOR, substr(dirname($this->config_imagemagick_path), 2))).' && '.phpthumb_functions::escapeshellarg_replacement(basename($this->config_imagemagick_path)); 1429 } else { 1430 $commandline = phpthumb_functions::escapeshellarg_replacement($this->config_imagemagick_path); 1431 } 1432 1433 } else { 1434 1435 $which_convert = $this->ImageMagickWhichConvert(); 1436 $IMversion = $this->ImageMagickVersion(); 1437 1438 if ($which_convert && ($which_convert{0} == '/') && $this->file_exists_ignoreopenbasedir($which_convert)) { 1439 1440 // `which convert` *should* return the path if "convert" exist, or nothing if it doesn't 1441 // other things *may* get returned, like "sh: convert: not found" or "no convert in /usr/local/bin /usr/sbin /usr/bin /usr/ccs/bin" 1442 // so only do this if the value returned exists as a file 1443 $this->DebugMessage('using ImageMagick path from `which convert` ('.$which_convert.')', __FILE__, __LINE__); 1444 $commandline = 'convert'; 1445 1446 } elseif ($IMversion) { 1447 1448 $this->DebugMessage('setting ImageMagick path to $this->config_imagemagick_path ('.$this->config_imagemagick_path.') ['.$IMversion.']', __FILE__, __LINE__); 1449 $commandline = $this->config_imagemagick_path; 1450 1451 } else { 1452 1453 $this->DebugMessage('ImageMagickThumbnailToGD() aborting because cannot find convert in $this->config_imagemagick_path ('.$this->config_imagemagick_path.'), and `which convert` returned ('.$which_convert.')', __FILE__, __LINE__); 1454 $commandline = ''; 1455 1456 } 1457 1458 } 1459 1460 @file_put_contents($IMcommandlineBaseCacheFilename, $commandline); 1461 @chmod($IMcommandlineBaseCacheFilename, $this->getParameter('config_file_create_mask')); 1462 } 1463 return $commandline; 1464 } 1465 1466 1467 function ImageMagickVersion($returnRAW=false) { 1468 static $versionstring = null; 1469 if (is_null($versionstring)) { 1470 $versionstring = array(0=>false, 1=>false); 1471 1472 $IMversionCacheFilename = $this->config_cache_directory.DIRECTORY_SEPARATOR.'phpThumbCacheIMversion.txt'; 1473 if ($cachedversionstring = @file_get_contents($IMversionCacheFilename)) { 1474 1475 $versionstring = explode("\n", $cachedversionstring, 2); 1476 $versionstring[0] = ($versionstring[0] ? $versionstring[0] : false); // "false" is stored as an empty string in the cache file 1477 $versionstring[1] = ($versionstring[1] ? $versionstring[1] : false); // "false" is stored as an empty string in the cache file 1478 1479 } else { 1480 1481 $commandline = $this->ImageMagickCommandlineBase(); 1482 $commandline = (!is_null($commandline) ? $commandline : ''); 1483 if ($commandline) { 1484 $commandline .= ' --version'; 1485 $this->DebugMessage('ImageMagick version checked with "'.$commandline.'"', __FILE__, __LINE__); 1486 $versionstring[1] = trim(phpthumb_functions::SafeExec($commandline)); 1487 if (preg_match('#^Version: [^0-9]*([ 0-9\\.\\:Q/\\-]+)#i', $versionstring[1], $matches)) { 1488 $versionstring[0] = trim($matches[1]); 1489 } else { 1490 $versionstring[0] = false; 1491 $this->DebugMessage('ImageMagick did not return recognized version string ('.$versionstring[1].')', __FILE__, __LINE__); 1492 } 1493 $this->DebugMessage('ImageMagick convert --version says "'.@$matches[0].'"', __FILE__, __LINE__); 1494 } 1495 1496 @file_put_contents($IMversionCacheFilename, $versionstring[0]."\n".$versionstring[1]); 1497 @chmod($IMversionCacheFilename, $this->getParameter('config_file_create_mask')); 1498 1499 } 1500 } 1501 return $versionstring[intval($returnRAW)]; 1502 } 1503 1504 1505 function ImageMagickSwitchAvailable($switchname) { 1506 static $IMoptions = null; 1507 if (is_null($IMoptions)) { 1508 $IMoptions = array(); 1509 $commandline = $this->ImageMagickCommandlineBase(); 1510 if (!is_null($commandline)) { 1511 $commandline .= ' -help'; 1512 $IMhelp_lines = explode("\n", phpthumb_functions::SafeExec($commandline)); 1513 foreach ($IMhelp_lines as $line) { 1514 if (preg_match('#^[\\+\\-]([a-z\\-]+) #', trim($line), $matches)) { 1515 $IMoptions[$matches[1]] = true; 1516 } 1517 } 1518 } 1519 } 1520 if (is_array($switchname)) { 1521 $allOK = true; 1522 foreach ($switchname as $key => $value) { 1523 if (!isset($IMoptions[$value])) { 1524 $allOK = false; 1525 break; 1526 } 1527 } 1528 $this->DebugMessage('ImageMagickSwitchAvailable('.implode(';', $switchname).') = '.intval($allOK).'', __FILE__, __LINE__); 1529 } else { 1530 $allOK = isset($IMoptions[$switchname]); 1531 $this->DebugMessage('ImageMagickSwitchAvailable('.$switchname.') = '.intval($allOK).'', __FILE__, __LINE__); 1532 } 1533 return $allOK; 1534 } 1535 1536 1537 function ImageMagickFormatsList() { 1538 static $IMformatsList = null; 1539 if (is_null($IMformatsList)) { 1540 $IMformatsList = ''; 1541 $commandline = $this->ImageMagickCommandlineBase(); 1542 if (!is_null($commandline)) { 1543 $commandline = dirname($commandline).DIRECTORY_SEPARATOR.str_replace('convert', 'identify', basename($commandline)); 1544 $commandline .= ' -list format'; 1545 $IMformatsList = phpthumb_functions::SafeExec($commandline); 1546 } 1547 } 1548 return $IMformatsList; 1549 } 1550 1551 1552 function SourceDataToTempFile() { 1553 if ($IMtempSourceFilename = $this->phpThumb_tempnam()) { 1554 $IMtempSourceFilename = $this->realPathSafe($IMtempSourceFilename); 1555 ob_start(); 1556 $fp_tempfile = fopen($IMtempSourceFilename, 'wb'); 1557 $tempfile_open_error = ob_get_contents(); 1558 ob_end_clean(); 1559 if ($fp_tempfile) { 1560 fwrite($fp_tempfile, $this->rawImageData); 1561 fclose($fp_tempfile); 1562 @chmod($IMtempSourceFilename, $this->getParameter('config_file_create_mask')); 1563 $this->sourceFilename = $IMtempSourceFilename; 1564 $this->DebugMessage('ImageMagickThumbnailToGD() setting $this->sourceFilename to "'.$IMtempSourceFilename.'" from $this->rawImageData ('.strlen($this->rawImageData).' bytes)', __FILE__, __LINE__); 1565 } else { 1566 $this->DebugMessage('ImageMagickThumbnailToGD() FAILED setting $this->sourceFilename to "'.$IMtempSourceFilename.'" (failed to open for writing: "'.$tempfile_open_error.'")', __FILE__, __LINE__); 1567 } 1568 unset($tempfile_open_error, $IMtempSourceFilename); 1569 return true; 1570 } 1571 $this->DebugMessage('SourceDataToTempFile() FAILED because $this->phpThumb_tempnam() failed', __FILE__, __LINE__); 1572 return false; 1573 } 1574 1575 1576 function ImageMagickThumbnailToGD() { 1577 // http://www.imagemagick.org/script/command-line-options.php 1578 1579 $this->useRawIMoutput = true; 1580 if (phpthumb_functions::gd_version()) { 1581 // if GD is not available, must use whatever ImageMagick can output 1582 1583 // $UnAllowedParameters contains options that can only be processed in GD, not ImageMagick 1584 // note: 'fltr' *may* need to be processed by GD, but we'll check that in more detail below 1585 $UnAllowedParameters = array('xto', 'ar', 'bg', 'bc'); 1586 // 'ra' may be part of this list, if not a multiple of 90 degrees 1587 foreach ($UnAllowedParameters as $parameter) { 1588 if (isset($this->$parameter)) { 1589 $this->DebugMessage('$this->useRawIMoutput=false because "'.$parameter.'" is set', __FILE__, __LINE__); 1590 $this->useRawIMoutput = false; 1591 break; 1592 } 1593 } 1594 } 1595 $this->DebugMessage('$this->useRawIMoutput='.($this->useRawIMoutput ? 'true' : 'false').' after checking $UnAllowedParameters', __FILE__, __LINE__); 1596 $ImageCreateFunction = ''; 1597 $outputFormat = $this->thumbnailFormat; 1598 if (phpthumb_functions::gd_version()) { 1599 if ($this->useRawIMoutput) { 1600 switch ($this->thumbnailFormat) { 1601 case 'gif': 1602 $ImageCreateFunction = 'imagecreatefromgif'; 1603 $this->is_alpha = true; 1604 break; 1605 case 'png': 1606 $ImageCreateFunction = 'imagecreatefrompng'; 1607 $this->is_alpha = true; 1608 break; 1609 case 'jpg': 1610 case 'jpeg': 1611 $ImageCreateFunction = 'imagecreatefromjpeg'; 1612 break; 1613 default: 1614 $this->DebugMessage('Forcing output to PNG because $this->thumbnailFormat ('.$this->thumbnailFormat.' is not a GD-supported format)', __FILE__, __LINE__); 1615 $outputFormat = 'png'; 1616 $ImageCreateFunction = 'imagecreatefrompng'; 1617 $this->is_alpha = true; 1618 $this->useRawIMoutput = false; 1619 break; 1620 } 1621 if (!function_exists(@$ImageCreateFunction)) { 1622 // ImageMagickThumbnailToGD() depends on imagecreatefrompng/imagecreatefromgif 1623 //$this->DebugMessage('ImageMagickThumbnailToGD() aborting because '.@$ImageCreateFunction.'() is not available', __FILE__, __LINE__); 1624 $this->useRawIMoutput = true; 1625 //return false; 1626 } 1627 } else { 1628 $outputFormat = 'png'; 1629 $ImageCreateFunction = 'imagecreatefrompng'; 1630 $this->is_alpha = true; 1631 $this->useRawIMoutput = false; 1632 } 1633 } 1634 1635 // http://freealter.org/doc_distrib/ImageMagick-5.1.1/www/convert.html 1636 if (!$this->sourceFilename && $this->rawImageData) { 1637 $this->SourceDataToTempFile(); 1638 } 1639 if (!$this->sourceFilename) { 1640 $this->DebugMessage('ImageMagickThumbnailToGD() aborting because $this->sourceFilename is empty', __FILE__, __LINE__); 1641 $this->useRawIMoutput = false; 1642 return false; 1643 } 1644 if ($this->issafemode) { 1645 $this->DebugMessage('ImageMagickThumbnailToGD() aborting because safe_mode is enabled', __FILE__, __LINE__); 1646 $this->useRawIMoutput = false; 1647 return false; 1648 } 1649// TO BE FIXED 1650//if (true) { 1651// $this->DebugMessage('ImageMagickThumbnailToGD() aborting it is broken right now', __FILE__, __LINE__); 1652// $this->useRawIMoutput = false; 1653// return false; 1654//} 1655 1656 $commandline = $this->ImageMagickCommandlineBase(); 1657 if ($commandline) { 1658 if ($IMtempfilename = $this->phpThumb_tempnam()) { 1659 $IMtempfilename = $this->realPathSafe($IMtempfilename); 1660 1661 $IMuseExplicitImageOutputDimensions = false; 1662 if ($this->ImageMagickSwitchAvailable('thumbnail') && $this->config_imagemagick_use_thumbnail) { 1663 $IMresizeParameter = 'thumbnail'; 1664 } else { 1665 $IMresizeParameter = 'resize'; 1666 1667 // some (older? around 2002) versions of IM won't accept "-resize 100x" but require "-resize 100x100" 1668 $commandline_test = $this->ImageMagickCommandlineBase().' logo: -resize 1x '.phpthumb_functions::escapeshellarg_replacement($IMtempfilename).' 2>&1'; 1669 $IMresult_test = phpthumb_functions::SafeExec($commandline_test); 1670 $IMuseExplicitImageOutputDimensions = preg_match('#image dimensions are zero#i', $IMresult_test); 1671 $this->DebugMessage('IMuseExplicitImageOutputDimensions = '.intval($IMuseExplicitImageOutputDimensions), __FILE__, __LINE__); 1672 if ($fp_im_temp = @fopen($IMtempfilename, 'wb')) { 1673 // erase temp image so ImageMagick logo doesn't get output if other processing fails 1674 fclose($fp_im_temp); 1675 @chmod($IMtempfilename, $this->getParameter('config_file_create_mask')); 1676 } 1677 } 1678 1679 1680 ob_start(); 1681 $getimagesize = getimagesize($this->sourceFilename); 1682 $GetImageSizeError = ob_get_contents(); 1683 ob_end_clean(); 1684 if (is_array($getimagesize)) { 1685 $this->DebugMessage('getimagesize('.$this->sourceFilename.') SUCCEEDED: '.print_r($getimagesize, true), __FILE__, __LINE__); 1686 } else { 1687 $this->DebugMessage('getimagesize('.$this->sourceFilename.') FAILED with error "'.$GetImageSizeError.'"', __FILE__, __LINE__); 1688 } 1689 if (!is_null($this->dpi) && $this->ImageMagickSwitchAvailable('density')) { 1690 // for vector source formats only (WMF, PDF, etc) 1691 if (is_array($getimagesize) && isset($getimagesize[2]) && ($getimagesize[2] == IMAGETYPE_PNG)) { 1692 // explicitly exclude PNG from "-flatten" to make sure transparency is preserved 1693 // https://github.com/JamesHeinrich/phpThumb/issues/65 1694 } else { 1695 $commandline .= ' -flatten'; 1696 $commandline .= ' -density '.phpthumb_functions::escapeshellarg_replacement($this->dpi); 1697 } 1698 } 1699 if (is_array($getimagesize)) { 1700 $this->DebugMessage('getimagesize('.$this->sourceFilename.') returned [w='.$getimagesize[0].';h='.$getimagesize[1].';f='.$getimagesize[2].']', __FILE__, __LINE__); 1701 $this->source_width = $getimagesize[0]; 1702 $this->source_height = $getimagesize[1]; 1703 $this->DebugMessage('source dimensions set to '.$this->source_width.'x'.$this->source_height, __FILE__, __LINE__); 1704 $this->SetOrientationDependantWidthHeight(); 1705 1706 if (!preg_match('#('.implode('|', $this->AlphaCapableFormats).')#i', $outputFormat)) { 1707 // not a transparency-capable format 1708 $commandline .= ' -background '.phpthumb_functions::escapeshellarg_replacement('#'.($this->bg ? $this->bg : 'FFFFFF')); 1709 if ($getimagesize[2] == IMAGETYPE_GIF) { 1710 $commandline .= ' -flatten'; 1711 } 1712 } 1713 if ($getimagesize[2] == IMAGETYPE_GIF) { 1714 $commandline .= ' -coalesce'; // may be needed for animated GIFs 1715 } 1716 if ($this->source_width || $this->source_height) { 1717 if ($this->zc) { 1718 1719 $borderThickness = 0; 1720 if (!empty($this->fltr)) { 1721 foreach ($this->fltr as $key => $value) { 1722 if (preg_match('#^bord\|([0-9]+)#', $value, $matches)) { 1723 $borderThickness = $matches[1]; 1724 break; 1725 } 1726 } 1727 } 1728 $wAll = intval(max($this->w, $this->wp, $this->wl, $this->ws)) - (2 * $borderThickness); 1729 $hAll = intval(max($this->h, $this->hp, $this->hl, $this->hs)) - (2 * $borderThickness); 1730 $imAR = $this->source_width / $this->source_height; 1731 $zcAR = (($wAll && $hAll) ? $wAll / $hAll : 1); 1732 $side = phpthumb_functions::nonempty_min($this->source_width, $this->source_height, max($wAll, $hAll)); 1733 $sideX = phpthumb_functions::nonempty_min($this->source_width, $wAll, round($hAll * $zcAR)); 1734 $sideY = phpthumb_functions::nonempty_min( $this->source_height, $hAll, round($wAll / $zcAR)); 1735 1736 $thumbnailH = round(max($sideY, ($sideY * $zcAR) / $imAR)); 1737 if($this->aoe == 1) { 1738 $commandline .= ' -'.$IMresizeParameter.' "'.$wAll.'x'.$hAll.'^"'; 1739 } else { 1740 $commandline .= ' -'.$IMresizeParameter.' '.phpthumb_functions::escapeshellarg_replacement(($IMuseExplicitImageOutputDimensions ? $thumbnailH : '').'x'.$thumbnailH); 1741 } 1742 1743 switch (strtoupper($this->zc)) { 1744 case 'T': 1745 $commandline .= ' -gravity north'; 1746 break; 1747 case 'B': 1748 $commandline .= ' -gravity south'; 1749 break; 1750 case 'L': 1751 $commandline .= ' -gravity west'; 1752 break; 1753 case 'R': 1754 $commandline .= ' -gravity east'; 1755 break; 1756 case 'TL': 1757 $commandline .= ' -gravity northwest'; 1758 break; 1759 case 'TR': 1760 $commandline .= ' -gravity northeast'; 1761 break; 1762 case 'BL': 1763 $commandline .= ' -gravity southwest'; 1764 break; 1765 case 'BR': 1766 $commandline .= ' -gravity southeast'; 1767 break; 1768 case '1': 1769 case 'C': 1770 default: 1771 $commandline .= ' -gravity center'; 1772 break; 1773 } 1774 1775 if (($wAll > 0) && ($hAll > 0)) { 1776 $commandline .= ' -crop '.phpthumb_functions::escapeshellarg_replacement($wAll.'x'.$hAll.'+0+0'); 1777 } else { 1778 $commandline .= ' -crop '.phpthumb_functions::escapeshellarg_replacement($side.'x'.$side.'+0+0'); 1779 } 1780 if ($this->ImageMagickSwitchAvailable('repage')) { 1781 $commandline .= ' +repage'; 1782 } else { 1783 $this->DebugMessage('Skipping "+repage" because ImageMagick (v'.$this->ImageMagickVersion().') does not support it', __FILE__, __LINE__); 1784 } 1785 1786 } elseif ($this->sw || $this->sh || $this->sx || $this->sy) { 1787 1788 $crop_param = ''; 1789 $crop_param .= ($this->sw ? (($this->sw < 2) ? round($this->sw * $this->source_width) : $this->sw) : $this->source_width); 1790 $crop_param .= 'x'.($this->sh ? (($this->sh < 2) ? round($this->sh * $this->source_height) : $this->sh) : $this->source_height); 1791 $crop_param .= '+'.(($this->sx < 2) ? round($this->sx * $this->source_width) : $this->sx); 1792 $crop_param .= '+'.(($this->sy < 2) ? round($this->sy * $this->source_height) : $this->sy); 1793// TO BE FIXED 1794// makes 1x1 output 1795// http://trainspotted.com/phpThumb/phpThumb.php?src=/content/CNR/47/CNR-4728-LD-L-20110723-898.jpg&w=100&h=100&far=1&f=png&fltr[]=lvl&sx=0.05&sy=0.25&sw=0.92&sh=0.42 1796// '/usr/bin/convert' -density 150 -thumbnail 100x100 -contrast-stretch '0.1%' '/var/www/vhosts/trainspotted.com/httpdocs/content/CNR/47/CNR-4728-LD-L-20110723-898.jpg[0]' png:'/var/www/vhosts/trainspotted.com/httpdocs/phpThumb/_cache/pThumbIIUlvj' 1797 $commandline .= ' -crop '.phpthumb_functions::escapeshellarg_replacement($crop_param); 1798 1799 // this is broken for aoe=1, but unsure how to fix. Send advice to info@silisoftware.com 1800 if ($this->w || $this->h) { 1801 //if ($this->ImageMagickSwitchAvailable('repage')) { 1802if (false) { 1803// TO BE FIXED 1804// newer versions of ImageMagick require -repage <geometry> 1805 $commandline .= ' -repage'; 1806 } else { 1807 $this->DebugMessage('Skipping "-repage" because ImageMagick (v'.$this->ImageMagickVersion().') does not support it', __FILE__, __LINE__); 1808 } 1809 if ($IMuseExplicitImageOutputDimensions) { 1810 if ($this->w && !$this->h) { 1811 $this->h = ceil($this->w / ($this->source_width / $this->source_height)); 1812 } elseif ($this->h && !$this->w) { 1813 $this->w = ceil($this->h * ($this->source_width / $this->source_height)); 1814 } 1815 } 1816 $commandline .= ' -'.$IMresizeParameter.' '.phpthumb_functions::escapeshellarg_replacement($this->w.'x'.$this->h); 1817 } 1818 1819 } else { 1820 1821 if ($this->iar && (intval($this->w) > 0) && (intval($this->h) > 0)) { 1822 list($nw, $nh) = phpthumb_functions::TranslateWHbyAngle($this->w, $this->h, $this->ra); 1823 $nw = ((round($nw) != 0) ? round($nw) : ''); 1824 $nh = ((round($nh) != 0) ? round($nh) : ''); 1825 $commandline .= ' -'.$IMresizeParameter.' '.phpthumb_functions::escapeshellarg_replacement($nw.'x'.$nh.'!'); 1826 } else { 1827 $this->w = ((($this->aoe || $this->far) && $this->w) ? $this->w : ($this->w ? phpthumb_functions::nonempty_min($this->w, $getimagesize[0]) : '')); 1828 $this->h = ((($this->aoe || $this->far) && $this->h) ? $this->h : ($this->h ? phpthumb_functions::nonempty_min($this->h, $getimagesize[1]) : '')); 1829 if ($this->w || $this->h) { 1830 if ($IMuseExplicitImageOutputDimensions) { 1831 if ($this->w && !$this->h) { 1832 $this->h = ceil($this->w / ($this->source_width / $this->source_height)); 1833 } elseif ($this->h && !$this->w) { 1834 $this->w = ceil($this->h * ($this->source_width / $this->source_height)); 1835 } 1836 } 1837 list($nw, $nh) = phpthumb_functions::TranslateWHbyAngle($this->w, $this->h, $this->ra); 1838 $nw = ((round($nw) != 0) ? round($nw) : ''); 1839 $nh = ((round($nh) != 0) ? round($nh) : ''); 1840 $commandline .= ' -'.$IMresizeParameter.' '.phpthumb_functions::escapeshellarg_replacement($nw.'x'.$nh); 1841 } 1842 } 1843 } 1844 } 1845 1846 } else { 1847 1848 $this->DebugMessage('getimagesize('.$this->sourceFilename.') failed', __FILE__, __LINE__); 1849 if ($this->w || $this->h) { 1850 $exactDimensionsBang = (($this->iar && (intval($this->w) > 0) && (intval($this->h) > 0)) ? '!' : ''); 1851 if ($IMuseExplicitImageOutputDimensions) { 1852 // unknown source aspect ratio, just put large number and hope IM figures it out 1853 $commandline .= ' -'.$IMresizeParameter.' '.phpthumb_functions::escapeshellarg_replacement(($this->w ? $this->w : '9999').'x'.($this->h ? $this->h : '9999').$exactDimensionsBang); 1854 } else { 1855 $commandline .= ' -'.$IMresizeParameter.' '.phpthumb_functions::escapeshellarg_replacement($this->w.'x'.$this->h.$exactDimensionsBang); 1856 } 1857 } 1858 1859 } 1860 1861 if ($this->ra) { 1862 $this->ra = intval($this->ra); 1863 if ($this->ImageMagickSwitchAvailable('rotate')) { 1864 if (!preg_match('#('.implode('|', $this->AlphaCapableFormats).')#i', $outputFormat) || phpthumb_functions::version_compare_replacement($this->ImageMagickVersion(), '6.3.7', '>=')) { 1865 $this->DebugMessage('Using ImageMagick rotate', __FILE__, __LINE__); 1866 $commandline .= ' -rotate '.phpthumb_functions::escapeshellarg_replacement($this->ra); 1867 if (($this->ra % 90) != 0) { 1868 if (preg_match('#('.implode('|', $this->AlphaCapableFormats).')#i', $outputFormat)) { 1869 // alpha-capable format 1870 $commandline .= ' -background rgba(255,255,255,0)'; 1871 } else { 1872 $commandline .= ' -background '.phpthumb_functions::escapeshellarg_replacement('#'.($this->bg ? $this->bg : 'FFFFFF')); 1873 } 1874 } 1875 $this->ra = 0; 1876 } else { 1877 $this->DebugMessage('Not using ImageMagick rotate because alpha background buggy before v6.3.7', __FILE__, __LINE__); 1878 } 1879 } else { 1880 $this->DebugMessage('Not using ImageMagick rotate because not supported', __FILE__, __LINE__); 1881 } 1882 } 1883 1884 $successfullyProcessedFilters = array(); 1885 foreach ($this->fltr as $filterkey => $filtercommand) { 1886 @list($command, $parameter) = explode('|', $filtercommand, 2); 1887 switch ($command) { 1888 case 'brit': 1889 if ($this->ImageMagickSwitchAvailable('modulate')) { 1890 $commandline .= ' -modulate '.phpthumb_functions::escapeshellarg_replacement((100 + intval($parameter)).',100,100'); 1891 $successfullyProcessedFilters[] = $filterkey; 1892 } 1893 break; 1894 1895 case 'cont': 1896 if ($this->ImageMagickSwitchAvailable('contrast')) { 1897 $contDiv10 = round(intval($parameter) / 10); 1898 if ($contDiv10 > 0) { 1899 $contDiv10 = min($contDiv10, 100); 1900 for ($i = 0; $i < $contDiv10; $i++) { 1901 $commandline .= ' -contrast'; // increase contrast by 10% 1902 } 1903 } elseif ($contDiv10 < 0) { 1904 $contDiv10 = max($contDiv10, -100); 1905 for ($i = $contDiv10; $i < 0; $i++) { 1906 $commandline .= ' +contrast'; // decrease contrast by 10% 1907 } 1908 } else { 1909 // do nothing 1910 } 1911 $successfullyProcessedFilters[] = $filterkey; 1912 } 1913 break; 1914 1915 case 'ds': 1916 if ($this->ImageMagickSwitchAvailable(array('colorspace', 'modulate'))) { 1917 if ($parameter == 100) { 1918 $commandline .= ' -colorspace GRAY'; 1919 $commandline .= ' -modulate 100,0,100'; 1920 } else { 1921 $commandline .= ' -modulate '.phpthumb_functions::escapeshellarg_replacement('100,'.(100 - intval($parameter)).',100'); 1922 } 1923 $successfullyProcessedFilters[] = $filterkey; 1924 } 1925 break; 1926 1927 case 'sat': 1928 if ($this->ImageMagickSwitchAvailable(array('colorspace', 'modulate'))) { 1929 if ($parameter == -100) { 1930 $commandline .= ' -colorspace GRAY'; 1931 $commandline .= ' -modulate 100,0,100'; 1932 } else { 1933 $commandline .= ' -modulate '.phpthumb_functions::escapeshellarg_replacement('100,'.(100 + intval($parameter)).',100'); 1934 } 1935 $successfullyProcessedFilters[] = $filterkey; 1936 } 1937 break; 1938 1939 case 'gray': 1940 if ($this->ImageMagickSwitchAvailable(array('colorspace', 'modulate'))) { 1941 $commandline .= ' -colorspace GRAY'; 1942 $commandline .= ' -modulate 100,0,100'; 1943 $successfullyProcessedFilters[] = $filterkey; 1944 } 1945 break; 1946 1947 case 'clr': 1948 if ($this->ImageMagickSwitchAvailable(array('fill', 'colorize'))) { 1949 @list($amount, $color) = explode('|', $parameter); 1950 $commandline .= ' -fill '.phpthumb_functions::escapeshellarg_replacement('#'.preg_replace('#[^0-9A-F]#i', '', $color)); 1951 $commandline .= ' -colorize '.phpthumb_functions::escapeshellarg_replacement(min(max(intval($amount), 0), 100)); 1952 } 1953 break; 1954 1955 case 'sep': 1956 if ($this->ImageMagickSwitchAvailable('sepia-tone')) { 1957 @list($amount, $color) = explode('|', $parameter); 1958 $amount = ($amount ? $amount : 80); 1959 if (!$color) { 1960 $commandline .= ' -sepia-tone '.phpthumb_functions::escapeshellarg_replacement(min(max(intval($amount), 0), 100).'%'); 1961 $successfullyProcessedFilters[] = $filterkey; 1962 } 1963 } 1964 break; 1965 1966 case 'gam': 1967 @list($amount) = explode('|', $parameter); 1968 $amount = min(max(floatval($amount), 0.001), 10); 1969 if (number_format($amount, 3) != '1.000') { 1970 if ($this->ImageMagickSwitchAvailable('gamma')) { 1971 $commandline .= ' -gamma '.phpthumb_functions::escapeshellarg_replacement($amount); 1972 $successfullyProcessedFilters[] = $filterkey; 1973 } 1974 } 1975 break; 1976 1977 case 'neg': 1978 if ($this->ImageMagickSwitchAvailable('negate')) { 1979 $commandline .= ' -negate'; 1980 $successfullyProcessedFilters[] = $filterkey; 1981 } 1982 break; 1983 1984 case 'th': 1985 @list($amount) = explode('|', $parameter); 1986 if ($this->ImageMagickSwitchAvailable(array('threshold', 'dither', 'monochrome'))) { 1987 $commandline .= ' -threshold '.phpthumb_functions::escapeshellarg_replacement(round(min(max(intval($amount), 0), 255) / 2.55).'%'); 1988 $commandline .= ' -dither'; 1989 $commandline .= ' -monochrome'; 1990 $successfullyProcessedFilters[] = $filterkey; 1991 } 1992 break; 1993 1994 case 'rcd': 1995 if ($this->ImageMagickSwitchAvailable(array('colors', 'dither'))) { 1996 @list($colors, $dither) = explode('|', $parameter); 1997 $colors = ($colors ? (int) $colors : 256); 1998 $dither = ((strlen($dither) > 0) ? (bool) $dither : true); 1999 $commandline .= ' -colors '.phpthumb_functions::escapeshellarg_replacement(max($colors, 8)); // ImageMagick will otherwise fail with "cannot quantize to fewer than 8 colors" 2000 $commandline .= ($dither ? ' -dither' : ' +dither'); 2001 $successfullyProcessedFilters[] = $filterkey; 2002 } 2003 break; 2004 2005 case 'flip': 2006 if ($this->ImageMagickSwitchAvailable(array('flip', 'flop'))) { 2007 if (strpos(strtolower($parameter), 'x') !== false) { 2008 $commandline .= ' -flop'; 2009 } 2010 if (strpos(strtolower($parameter), 'y') !== false) { 2011 $commandline .= ' -flip'; 2012 } 2013 $successfullyProcessedFilters[] = $filterkey; 2014 } 2015 break; 2016 2017 case 'edge': 2018 if ($this->ImageMagickSwitchAvailable('edge')) { 2019 $parameter = (!empty($parameter) ? $parameter : 2); 2020 $commandline .= ' -edge '.phpthumb_functions::escapeshellarg_replacement(!empty($parameter) ? intval($parameter) : 1); 2021 $successfullyProcessedFilters[] = $filterkey; 2022 } 2023 break; 2024 2025 case 'emb': 2026 if ($this->ImageMagickSwitchAvailable(array('emboss', 'negate'))) { 2027 $parameter = (!empty($parameter) ? $parameter : 2); 2028 $commandline .= ' -emboss '.phpthumb_functions::escapeshellarg_replacement(intval($parameter)); 2029 if ($parameter < 2) { 2030 $commandline .= ' -negate'; // ImageMagick negates the image for some reason with '-emboss 1'; 2031 } 2032 $successfullyProcessedFilters[] = $filterkey; 2033 } 2034 break; 2035 2036 case 'lvl': 2037 @list($band, $method, $threshold) = explode('|', $parameter); 2038 $band = ($band ? preg_replace('#[^RGBA\\*]#', '', strtoupper($band)) : '*'); 2039 $method = ((strlen($method) > 0) ? intval($method) : 2); 2040 $threshold = ((strlen($threshold) > 0) ? min(max(floatval($threshold), 0), 100) : 0.1); 2041 2042 $band = preg_replace('#[^RGBA\\*]#', '', strtoupper($band)); 2043 2044 if (($method > 1) && !$this->ImageMagickSwitchAvailable(array('channel', 'contrast-stretch'))) { 2045 // Because ImageMagick processing happens before PHP-GD filters, and because some 2046 // clipping is involved in the "lvl" filter, if "lvl" happens before "wb" then the 2047 // "wb" filter will have (almost) no effect. Therefore, if "wb" is enabled then 2048 // force the "lvl" filter to be processed by GD, not ImageMagick. 2049 foreach ($this->fltr as $fltr_key => $fltr_value) { 2050 list($fltr_cmd) = explode('|', $fltr_value); 2051 if ($fltr_cmd == 'wb') { 2052 $this->DebugMessage('Setting "lvl" filter method to "0" (from "'.$method.'") because white-balance filter also enabled', __FILE__, __LINE__); 2053 $method = 0; 2054 } 2055 } 2056 } 2057 2058 switch ($method) { 2059 case 0: // internal RGB 2060 case 1: // internal grayscale 2061 break; 2062 case 2: // ImageMagick "contrast-stretch" 2063 if ($this->ImageMagickSwitchAvailable('contrast-stretch')) { 2064 if ($band != '*') { 2065 $commandline .= ' -channel '.phpthumb_functions::escapeshellarg_replacement(strtoupper($band)); 2066 } 2067 $threshold = preg_replace('#[^0-9\\.]#', '', $threshold); // should be unneccesary, but just to be double-sure 2068 //$commandline .= ' -contrast-stretch '.phpthumb_functions::escapeshellarg_replacement($threshold.'%'); 2069 $commandline .= ' -contrast-stretch \''.$threshold.'%\''; 2070 if ($band != '*') { 2071 $commandline .= ' +channel'; 2072 } 2073 $successfullyProcessedFilters[] = $filterkey; 2074 } 2075 break; 2076 case 3: // ImageMagick "normalize" 2077 if ($this->ImageMagickSwitchAvailable('normalize')) { 2078 if ($band != '*') { 2079 $commandline .= ' -channel '.phpthumb_functions::escapeshellarg_replacement(strtoupper($band)); 2080 } 2081 $commandline .= ' -normalize'; 2082 if ($band != '*') { 2083 $commandline .= ' +channel'; 2084 } 2085 $successfullyProcessedFilters[] = $filterkey; 2086 } 2087 break; 2088 default: 2089 $this->DebugMessage('unsupported method ('.$method.') for "lvl" filter', __FILE__, __LINE__); 2090 break; 2091 } 2092 if (isset($this->fltr[$filterkey]) && ($method > 1)) { 2093 $this->fltr[$filterkey] = $command.'|'.$band.'|0|'.$threshold; 2094 $this->DebugMessage('filter "lvl" remapped from method "'.$method.'" to method "0" because ImageMagick support is missing', __FILE__, __LINE__); 2095 } 2096 break; 2097 2098 case 'wb': 2099 if ($this->ImageMagickSwitchAvailable(array('channel', 'contrast-stretch'))) { 2100 @list($threshold) = explode('|', $parameter); 2101 $threshold = (!empty($threshold) ? min(max(floatval($threshold), 0), 100) : 0.1); 2102 $threshold = preg_replace('#[^0-9\\.]#', '', $threshold); // should be unneccesary, but just to be double-sure 2103 //$commandline .= ' -channel R -contrast-stretch '.phpthumb_functions::escapeshellarg_replacement($threshold.'%'); // doesn't work on Windows because most versions of PHP do not properly 2104 //$commandline .= ' -channel G -contrast-stretch '.phpthumb_functions::escapeshellarg_replacement($threshold.'%'); // escape special characters (such as %) and just replace them with spaces 2105 //$commandline .= ' -channel B -contrast-stretch '.phpthumb_functions::escapeshellarg_replacement($threshold.'%'); // https://bugs.php.net/bug.php?id=43261 2106 $commandline .= ' -channel R -contrast-stretch \''.$threshold.'%\''; 2107 $commandline .= ' -channel G -contrast-stretch \''.$threshold.'%\''; 2108 $commandline .= ' -channel B -contrast-stretch \''.$threshold.'%\''; 2109 $commandline .= ' +channel'; 2110 $successfullyProcessedFilters[] = $filterkey; 2111 } 2112 break; 2113 2114 case 'blur': 2115 if ($this->ImageMagickSwitchAvailable('blur')) { 2116 @list($radius) = explode('|', $parameter); 2117 $radius = (!empty($radius) ? min(max(intval($radius), 0), 25) : 1); 2118 $commandline .= ' -blur '.phpthumb_functions::escapeshellarg_replacement($radius); 2119 $successfullyProcessedFilters[] = $filterkey; 2120 } 2121 break; 2122 2123 case 'gblr': 2124 @list($radius) = explode('|', $parameter); 2125 $radius = (!empty($radius) ? min(max(intval($radius), 0), 25) : 1); 2126 // "-gaussian" changed to "-gaussian-blur" sometime around 2009 2127 if ($this->ImageMagickSwitchAvailable('gaussian-blur')) { 2128 $commandline .= ' -gaussian-blur '.phpthumb_functions::escapeshellarg_replacement($radius); 2129 $successfullyProcessedFilters[] = $filterkey; 2130 } elseif ($this->ImageMagickSwitchAvailable('gaussian')) { 2131 $commandline .= ' -gaussian '.phpthumb_functions::escapeshellarg_replacement($radius); 2132 $successfullyProcessedFilters[] = $filterkey; 2133 } 2134 break; 2135 2136 case 'usm': 2137 if ($this->ImageMagickSwitchAvailable('unsharp')) { 2138 @list($amount, $radius, $threshold) = explode('|', $parameter); 2139 $amount = ($amount ? min(max(intval($radius), 0), 255) : 80); 2140 $radius = ($radius ? min(max(intval($radius), 0), 10) : 0.5); 2141 $threshold = (strlen($threshold) ? min(max(intval($radius), 0), 50) : 3); 2142 $commandline .= ' -unsharp '.phpthumb_functions::escapeshellarg_replacement(number_format(($radius * 2) - 1, 2, '.', '').'x1+'.number_format($amount / 100, 2, '.', '').'+'.number_format($threshold / 100, 2, '.', '')); 2143 $successfullyProcessedFilters[] = $filterkey; 2144 } 2145 break; 2146 2147 case 'bord': 2148 if ($this->ImageMagickSwitchAvailable(array('border', 'bordercolor', 'thumbnail', 'crop'))) { 2149 if (!$this->zc) { 2150 @list($width, $rX, $rY, $color) = explode('|', $parameter); 2151 $width = intval($width); 2152 $rX = intval($rX); 2153 $rY = intval($rY); 2154 if ($width && !$rX && !$rY) { 2155 if (!phpthumb_functions::IsHexColor($color)) { 2156 $color = ((!empty($this->bc) && phpthumb_functions::IsHexColor($this->bc)) ? $this->bc : '000000'); 2157 } 2158 $commandline .= ' -border '.phpthumb_functions::escapeshellarg_replacement(intval($width)); 2159 $commandline .= ' -bordercolor '.phpthumb_functions::escapeshellarg_replacement('#'.$color); 2160 2161 if (preg_match('# \\-crop "([0-9]+)x([0-9]+)\\+0\\+0" #', $commandline, $matches)) { 2162 $commandline = str_replace(' -crop "'.$matches[1].'x'.$matches[2].'+0+0" ', ' -crop '.phpthumb_functions::escapeshellarg_replacement(($matches[1] - (2 * $width)).'x'.($matches[2] - (2 * $width)).'+0+0').' ', $commandline); 2163 } elseif (preg_match('# \\-'.$IMresizeParameter.' "([0-9]+)x([0-9]+)" #', $commandline, $matches)) { 2164 $commandline = str_replace(' -'.$IMresizeParameter.' "'.$matches[1].'x'.$matches[2].'" ', ' -'.$IMresizeParameter.' '.phpthumb_functions::escapeshellarg_replacement(($matches[1] - (2 * $width)).'x'.($matches[2] - (2 * $width))).' ', $commandline); 2165 } 2166 $successfullyProcessedFilters[] = $filterkey; 2167 } 2168 } 2169 } 2170 break; 2171 2172 case 'crop': 2173 break; 2174 2175 case 'sblr': 2176 break; 2177 2178 case 'mean': 2179 break; 2180 2181 case 'smth': 2182 break; 2183 2184 case 'bvl': 2185 break; 2186 2187 case 'wmi': 2188 break; 2189 2190 case 'wmt': 2191 break; 2192 2193 case 'over': 2194 break; 2195 2196 case 'hist': 2197 break; 2198 2199 case 'fram': 2200 break; 2201 2202 case 'drop': 2203 break; 2204 2205 case 'mask': 2206 break; 2207 2208 case 'elip': 2209 break; 2210 2211 case 'ric': 2212 break; 2213 2214 case 'stc': 2215 break; 2216 2217 case 'size': 2218 break; 2219 2220 default: 2221 $this->DebugMessage('Unknown $this->fltr['.$filterkey.'] ('.$filtercommand.') -- deleting filter command', __FILE__, __LINE__); 2222 $successfullyProcessedFilters[] = $filterkey; 2223 break; 2224 } 2225 if (!isset($this->fltr[$filterkey])) { 2226 $this->DebugMessage('Processed $this->fltr['.$filterkey.'] ('.$filtercommand.') with ImageMagick', __FILE__, __LINE__); 2227 } else { 2228 $this->DebugMessage('Skipping $this->fltr['.$filterkey.'] ('.$filtercommand.') with ImageMagick', __FILE__, __LINE__); 2229 } 2230 } 2231 $this->DebugMessage('Remaining $this->fltr after ImageMagick: ('.$this->phpThumbDebugVarDump($this->fltr).')', __FILE__, __LINE__); 2232 if (count($this->fltr) > 0) { 2233 $this->useRawIMoutput = false; 2234 } 2235 2236 if (preg_match('#jpe?g#i', $outputFormat) && $this->q) { 2237 if ($this->ImageMagickSwitchAvailable(array('quality', 'interlace'))) { 2238 $commandline .= ' -quality '.phpthumb_functions::escapeshellarg_replacement($this->thumbnailQuality); 2239 if ($this->config_output_interlace) { 2240 // causes weird things with animated GIF... leave for JPEG only 2241 $commandline .= ' -interlace line '; // Use Line or Plane to create an interlaced PNG or GIF or progressive JPEG image 2242 } 2243 } 2244 } 2245 $commandline .= ' '.phpthumb_functions::escapeshellarg_replacement(preg_replace('#[/\\\\]#', DIRECTORY_SEPARATOR, $this->sourceFilename).(($outputFormat == 'gif') ? '' : '['.intval($this->sfn).']')); // [0] means first frame of (GIF) animation, can be ignored 2246 $commandline .= ' '.$outputFormat.':'.phpthumb_functions::escapeshellarg_replacement($IMtempfilename); 2247 if (!$this->iswindows) { 2248 $commandline .= ' 2>&1'; 2249 } 2250 $this->DebugMessage('ImageMagick called as ('.$commandline.')', __FILE__, __LINE__); 2251 $IMresult = phpthumb_functions::SafeExec($commandline); 2252 clearstatcache(); 2253 if (!@file_exists($IMtempfilename) || !@filesize($IMtempfilename)) { 2254 $this->FatalError('ImageMagick failed with message ('.trim($IMresult).')'); 2255 $this->DebugMessage('ImageMagick failed with message ('.trim($IMresult).')', __FILE__, __LINE__); 2256 if ($this->iswindows && !$IMresult) { 2257 $this->DebugMessage('Check to make sure that PHP has read+write permissions to "'.dirname($IMtempfilename).'"', __FILE__, __LINE__); 2258 } 2259 2260 } else { 2261 2262 foreach ($successfullyProcessedFilters as $dummy => $filterkey) { 2263 unset($this->fltr[$filterkey]); 2264 } 2265 $this->IMresizedData = file_get_contents($IMtempfilename); 2266 $getimagesize_imresized = @getimagesize($IMtempfilename); 2267 $this->DebugMessage('getimagesize('.$IMtempfilename.') returned [w='.$getimagesize_imresized[0].';h='.$getimagesize_imresized[1].';f='.$getimagesize_imresized[2].']', __FILE__, __LINE__); 2268 if (($this->config_max_source_pixels > 0) && (($getimagesize_imresized[0] * $getimagesize_imresized[1]) > $this->config_max_source_pixels)) { 2269 $this->DebugMessage('skipping ImageMagickThumbnailToGD::'.$ImageCreateFunction.'() because IM output is too large ('.$getimagesize_imresized[0].'x'.$getimagesize_imresized[0].' = '.($getimagesize_imresized[0] * $getimagesize_imresized[1]).' > '.$this->config_max_source_pixels.')', __FILE__, __LINE__); 2270 } elseif (function_exists(@$ImageCreateFunction) && ($this->gdimg_source = @$ImageCreateFunction($IMtempfilename))) { 2271 $this->source_width = imagesx($this->gdimg_source); 2272 $this->source_height = imagesy($this->gdimg_source); 2273 $this->DebugMessage('ImageMagickThumbnailToGD::'.$ImageCreateFunction.'() succeeded, $this->gdimg_source is now ('.$this->source_width.'x'.$this->source_height.')', __FILE__, __LINE__); 2274 $this->DebugMessage('ImageMagickThumbnailToGD() returning $this->IMresizedData ('.strlen($this->IMresizedData).' bytes)', __FILE__, __LINE__); 2275 } else { 2276 $this->useRawIMoutput = true; 2277 $this->DebugMessage('$this->useRawIMoutput set to TRUE because '.@$ImageCreateFunction.'('.$IMtempfilename.') failed', __FILE__, __LINE__); 2278 } 2279 if (file_exists($IMtempfilename)) { 2280 $this->DebugMessage('deleting "'.$IMtempfilename.'"', __FILE__, __LINE__); 2281 @unlink($IMtempfilename); 2282 } 2283 return true; 2284 2285 } 2286 if (file_exists($IMtempfilename)) { 2287 $this->DebugMessage('deleting "'.$IMtempfilename.'"', __FILE__, __LINE__); 2288 @unlink($IMtempfilename); 2289 } 2290 2291 } elseif ($this->issafemode) { 2292 $this->DebugMessage('ImageMagickThumbnailToGD() aborting because PHP safe_mode is enabled and phpThumb_tempnam() failed', __FILE__, __LINE__); 2293 $this->useRawIMoutput = false; 2294 } else { 2295 if (file_exists($IMtempfilename)) { 2296 $this->DebugMessage('deleting "'.$IMtempfilename.'"', __FILE__, __LINE__); 2297 @unlink($IMtempfilename); 2298 } 2299 $this->DebugMessage('ImageMagickThumbnailToGD() aborting, phpThumb_tempnam() failed', __FILE__, __LINE__); 2300 } 2301 } else { 2302 $this->DebugMessage('ImageMagickThumbnailToGD() aborting because ImageMagickCommandlineBase() failed', __FILE__, __LINE__); 2303 } 2304 $this->useRawIMoutput = false; 2305 return false; 2306 } 2307 2308 2309 function Rotate() { 2310 if ($this->ra || $this->ar) { 2311 if (!function_exists('imagerotate')) { 2312 $this->DebugMessage('!function_exists(imagerotate)', __FILE__, __LINE__); 2313 return false; 2314 } 2315 if (!include_once(dirname(__FILE__).'/phpthumb.filters.php')) { 2316 $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.filters.php" which is required for applying filters ('.implode(';', $this->fltr).')', __FILE__, __LINE__); 2317 return false; 2318 } 2319 2320 $this->config_background_hexcolor = ($this->bg ? $this->bg : $this->config_background_hexcolor); 2321 if (!phpthumb_functions::IsHexColor($this->config_background_hexcolor)) { 2322 return $this->ErrorImage('Invalid hex color string "'.$this->config_background_hexcolor.'" for parameter "bg"'); 2323 } 2324 2325 $rotate_angle = 0; 2326 if ($this->ra) { 2327 2328 $rotate_angle = floatval($this->ra); 2329 2330 } else { 2331 2332 if ($this->ar == 'x') { 2333 if (phpthumb_functions::version_compare_replacement(phpversion(), '4.2.0', '>=')) { 2334 if ($this->sourceFilename) { 2335 if (function_exists('exif_read_data')) { 2336 if ($exif_data = @exif_read_data($this->sourceFilename, 'IFD0')) { 2337 // http://sylvana.net/jpegcrop/exif_orientation.html 2338 switch (@$exif_data['Orientation']) { 2339 case 1: 2340 $rotate_angle = 0; 2341 break; 2342 case 3: 2343 $rotate_angle = 180; 2344 break; 2345 case 6: 2346 $rotate_angle = 270; 2347 break; 2348 case 8: 2349 $rotate_angle = 90; 2350 break; 2351 2352 default: 2353 $this->DebugMessage('EXIF auto-rotate failed because unknown $exif_data[Orientation] "'.@$exif_data['Orientation'].'"', __FILE__, __LINE__); 2354 return false; 2355 break; 2356 } 2357 $this->DebugMessage('EXIF auto-rotate set to '.$rotate_angle.' degrees ($exif_data[Orientation] = "'.@$exif_data['Orientation'].'")', __FILE__, __LINE__); 2358 } else { 2359 $this->DebugMessage('failed: exif_read_data('.$this->sourceFilename.')', __FILE__, __LINE__); 2360 return false; 2361 } 2362 } else { 2363 $this->DebugMessage('!function_exists(exif_read_data)', __FILE__, __LINE__); 2364 return false; 2365 } 2366 } else { 2367 $this->DebugMessage('Cannot auto-rotate from EXIF data because $this->sourceFilename is empty', __FILE__, __LINE__); 2368 return false; 2369 } 2370 } else { 2371 $this->DebugMessage('Cannot auto-rotate from EXIF data because PHP is less than v4.2.0 ('.phpversion().')', __FILE__, __LINE__); 2372 return false; 2373 } 2374 } elseif (($this->ar == 'l') && ($this->source_height > $this->source_width)) { 2375 $rotate_angle = 270; 2376 } elseif (($this->ar == 'L') && ($this->source_height > $this->source_width)) { 2377 $rotate_angle = 90; 2378 } elseif (($this->ar == 'p') && ($this->source_width > $this->source_height)) { 2379 $rotate_angle = 90; 2380 } elseif (($this->ar == 'P') && ($this->source_width > $this->source_height)) { 2381 $rotate_angle = 270; 2382 } 2383 2384 } 2385 if ($rotate_angle % 90) { 2386 $this->is_alpha = true; 2387 } 2388 phpthumb_filters::ImprovedImageRotate($this->gdimg_source, $rotate_angle, $this->config_background_hexcolor, $this->bg, $this); 2389 $this->source_width = imagesx($this->gdimg_source); 2390 $this->source_height = imagesy($this->gdimg_source); 2391 } 2392 return true; 2393 } 2394 2395 2396 function FixedAspectRatio() { 2397 // optional fixed-dimension images (regardless of aspect ratio) 2398 2399 if (!$this->far) { 2400 // do nothing 2401 return true; 2402 } 2403 2404 if (!$this->w || !$this->h) { 2405 return false; 2406 } 2407 $this->thumbnail_width = $this->w; 2408 $this->thumbnail_height = $this->h; 2409 $this->is_alpha = true; 2410 if ($this->thumbnail_image_width >= $this->thumbnail_width) { 2411 2412 $aspectratio = $this->thumbnail_image_height / $this->thumbnail_image_width; 2413 if ($this->w) { 2414 $this->thumbnail_image_height = round($this->thumbnail_image_width * $aspectratio); 2415 $this->thumbnail_height = ($this->h ? $this->h : $this->thumbnail_image_height); 2416 } elseif ($this->thumbnail_image_height < $this->thumbnail_height) { 2417 $this->thumbnail_image_height = $this->thumbnail_height; 2418 $this->thumbnail_image_width = round($this->thumbnail_image_height / $aspectratio); 2419 } 2420 2421 } else { 2422 2423 $aspectratio = $this->thumbnail_image_width / $this->thumbnail_image_height; 2424 if ($this->h) { 2425 $this->thumbnail_image_width = round($this->thumbnail_image_height * $aspectratio); 2426 } elseif ($this->thumbnail_image_width < $this->thumbnail_width) { 2427 $this->thumbnail_image_width = $this->thumbnail_width; 2428 $this->thumbnail_image_height = round($this->thumbnail_image_width / $aspectratio); 2429 } 2430 2431 } 2432 return true; 2433 } 2434 2435 2436 function OffsiteDomainIsAllowed($hostname, $allowed_domains) { 2437 static $domain_is_allowed = array(); 2438 $hostname = strtolower($hostname); 2439 if (!isset($domain_is_allowed[$hostname])) { 2440 $domain_is_allowed[$hostname] = false; 2441 foreach ($allowed_domains as $valid_domain) { 2442 $starpos = strpos($valid_domain, '*'); 2443 if ($starpos !== false) { 2444 $valid_domain = substr($valid_domain, $starpos + 1); 2445 if (preg_match('#'.preg_quote($valid_domain).'$#', $hostname)) { 2446 $domain_is_allowed[$hostname] = true; 2447 break; 2448 } 2449 } else { 2450 if (strtolower($valid_domain) === $hostname) { 2451 $domain_is_allowed[$hostname] = true; 2452 break; 2453 } 2454 } 2455 } 2456 } 2457 return $domain_is_allowed[$hostname]; 2458 } 2459 2460 2461 function AntiOffsiteLinking() { 2462 // Optional anti-offsite hijacking of the thumbnail script 2463 $allow = true; 2464 if ($allow && $this->config_nooffsitelink_enabled && (@$_SERVER['HTTP_REFERER'] || $this->config_nooffsitelink_require_refer)) { 2465 $this->DebugMessage('AntiOffsiteLinking() checking $_SERVER[HTTP_REFERER] "'.@$_SERVER['HTTP_REFERER'].'"', __FILE__, __LINE__); 2466 foreach ($this->config_nooffsitelink_valid_domains as $key => $valid_domain) { 2467 // $_SERVER['HTTP_HOST'] contains the port number, so strip it out here to make default configuration work 2468 list($clean_domain) = explode(':', $valid_domain); 2469 $this->config_nooffsitelink_valid_domains[$key] = $clean_domain; 2470 } 2471 $parsed_url = phpthumb_functions::ParseURLbetter(@$_SERVER['HTTP_REFERER']); 2472 if (!$this->OffsiteDomainIsAllowed(@$parsed_url['host'], $this->config_nooffsitelink_valid_domains)) { 2473 $allow = false; 2474 //$this->DebugMessage('AntiOffsiteLinking() - "'.@$parsed_url['host'].'" is NOT in $this->config_nooffsitelink_valid_domains ('.implode(';', $this->config_nooffsitelink_valid_domains).')', __FILE__, __LINE__); 2475 $this->ErrorImage('AntiOffsiteLinking() - "'.@$parsed_url['host'].'" is NOT in $this->config_nooffsitelink_valid_domains ('.implode(';', $this->config_nooffsitelink_valid_domains).')'); 2476 } else { 2477 $this->DebugMessage('AntiOffsiteLinking() - "'.@$parsed_url['host'].'" is in $this->config_nooffsitelink_valid_domains ('.implode(';', $this->config_nooffsitelink_valid_domains).')', __FILE__, __LINE__); 2478 } 2479 } 2480 2481 if ($allow && $this->config_nohotlink_enabled && preg_match('#^(f|ht)tps?\://#i', $this->src)) { 2482 $parsed_url = phpthumb_functions::ParseURLbetter($this->src); 2483 //if (!phpthumb_functions::CaseInsensitiveInArray(@$parsed_url['host'], $this->config_nohotlink_valid_domains)) { 2484 if (!$this->OffsiteDomainIsAllowed(@$parsed_url['host'], $this->config_nohotlink_valid_domains)) { 2485 // This domain is not allowed 2486 $allow = false; 2487 $this->DebugMessage('AntiOffsiteLinking() - "'.$parsed_url['host'].'" is NOT in $this->config_nohotlink_valid_domains ('.implode(';', $this->config_nohotlink_valid_domains).')', __FILE__, __LINE__); 2488 } else { 2489 $this->DebugMessage('AntiOffsiteLinking() - "'.$parsed_url['host'].'" is in $this->config_nohotlink_valid_domains ('.implode(';', $this->config_nohotlink_valid_domains).')', __FILE__, __LINE__); 2490 } 2491 } 2492 2493 if ($allow) { 2494 $this->DebugMessage('AntiOffsiteLinking() says this is allowed', __FILE__, __LINE__); 2495 return true; 2496 } 2497 2498 if (!phpthumb_functions::IsHexColor($this->config_error_bgcolor)) { 2499 return $this->ErrorImage('Invalid hex color string "'.$this->config_error_bgcolor.'" for $this->config_error_bgcolor'); 2500 } 2501 if (!phpthumb_functions::IsHexColor($this->config_error_textcolor)) { 2502 return $this->ErrorImage('Invalid hex color string "'.$this->config_error_textcolor.'" for $this->config_error_textcolor'); 2503 } 2504 if ($this->config_nooffsitelink_erase_image) { 2505 2506 return $this->ErrorImage($this->config_nooffsitelink_text_message, $this->thumbnail_width, $this->thumbnail_height); 2507 2508 } else { 2509 2510 $this->config_nooffsitelink_watermark_src = $this->ResolveFilenameToAbsolute($this->config_nooffsitelink_watermark_src); 2511 if (is_file($this->config_nooffsitelink_watermark_src)) { 2512 2513 if (!include_once(dirname(__FILE__).'/phpthumb.filters.php')) { 2514 $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.filters.php" which is required for applying watermark', __FILE__, __LINE__); 2515 return false; 2516 } 2517 $watermark_img = $this->ImageCreateFromStringReplacement(file_get_contents($this->config_nooffsitelink_watermark_src)); 2518 $phpthumbFilters = new phpthumb_filters(); 2519 $phpthumbFilters->phpThumbObject = &$this; 2520 $opacity = 50; 2521 $margin = 5; 2522 $phpthumbFilters->WatermarkOverlay($this->gdimg_output, $watermark_img, '*', $opacity, $margin); 2523 imagedestroy($watermark_img); 2524 unset($phpthumbFilters); 2525 2526 } else { 2527 2528 $nohotlink_text_array = explode("\n", wordwrap($this->config_nooffsitelink_text_message, floor($this->thumbnail_width / imagefontwidth($this->config_error_fontsize)), "\n")); 2529 $nohotlink_text_color = phpthumb_functions::ImageHexColorAllocate($this->gdimg_output, $this->config_error_textcolor); 2530 2531 $topoffset = round(($this->thumbnail_height - (count($nohotlink_text_array) * imagefontheight($this->config_error_fontsize))) / 2); 2532 2533 $rowcounter = 0; 2534 $this->DebugMessage('AntiOffsiteLinking() writing '.count($nohotlink_text_array).' lines of text "'.$this->config_nooffsitelink_text_message.'" (in #'.$this->config_error_textcolor.') on top of image', __FILE__, __LINE__); 2535 foreach ($nohotlink_text_array as $textline) { 2536 $leftoffset = max(0, round(($this->thumbnail_width - (strlen($textline) * imagefontwidth($this->config_error_fontsize))) / 2)); 2537 imagestring($this->gdimg_output, $this->config_error_fontsize, $leftoffset, $topoffset + ($rowcounter++ * imagefontheight($this->config_error_fontsize)), $textline, $nohotlink_text_color); 2538 } 2539 2540 } 2541 2542 } 2543 return true; 2544 } 2545 2546 2547 function AlphaChannelFlatten() { 2548 if (!$this->is_alpha) { 2549 // image doesn't have alpha transparency, no need to flatten 2550 $this->DebugMessage('skipping AlphaChannelFlatten() because !$this->is_alpha', __FILE__, __LINE__); 2551 return false; 2552 } 2553 switch ($this->thumbnailFormat) { 2554 case 'png': 2555 case 'ico': 2556 // image has alpha transparency, but output as PNG or ICO which can handle it 2557 $this->DebugMessage('skipping AlphaChannelFlatten() because ($this->thumbnailFormat == "'.$this->thumbnailFormat.'")', __FILE__, __LINE__); 2558 return false; 2559 break; 2560 2561 case 'gif': 2562 // image has alpha transparency, but output as GIF which can handle only single-color transparency 2563 $CurrentImageColorTransparent = imagecolortransparent($this->gdimg_output); 2564 if ($CurrentImageColorTransparent == -1) { 2565 // no transparent color defined 2566 2567 if (phpthumb_functions::gd_version() < 2.0) { 2568 $this->DebugMessage('AlphaChannelFlatten() failed because GD version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2569 return false; 2570 } 2571 2572 if ($img_alpha_mixdown_dither = @imagecreatetruecolor(imagesx($this->gdimg_output), imagesy($this->gdimg_output))) { 2573 2574 $dither_color = array(); 2575 for ($i = 0; $i <= 255; $i++) { 2576 $dither_color[$i] = imagecolorallocate($img_alpha_mixdown_dither, $i, $i, $i); 2577 } 2578 2579 // scan through current truecolor image copy alpha channel to temp image as grayscale 2580 for ($x = 0; $x < $this->thumbnail_width; $x++) { 2581 for ($y = 0; $y < $this->thumbnail_height; $y++) { 2582 $PixelColor = phpthumb_functions::GetPixelColor($this->gdimg_output, $x, $y); 2583 imagesetpixel($img_alpha_mixdown_dither, $x, $y, $dither_color[($PixelColor['alpha'] * 2)]); 2584 } 2585 } 2586 2587 // dither alpha channel grayscale version down to 2 colors 2588 imagetruecolortopalette($img_alpha_mixdown_dither, true, 2); 2589 2590 // reduce color palette to 256-1 colors (leave one palette position for transparent color) 2591 imagetruecolortopalette($this->gdimg_output, true, 255); 2592 2593 // allocate a new color for transparent color index 2594 $TransparentColor = imagecolorallocate($this->gdimg_output, 1, 254, 253); 2595 imagecolortransparent($this->gdimg_output, $TransparentColor); 2596 2597 // scan through alpha channel image and note pixels with >50% transparency 2598 for ($x = 0; $x < $this->thumbnail_width; $x++) { 2599 for ($y = 0; $y < $this->thumbnail_height; $y++) { 2600 $AlphaChannelPixel = phpthumb_functions::GetPixelColor($img_alpha_mixdown_dither, $x, $y); 2601 if ($AlphaChannelPixel['red'] > 127) { 2602 imagesetpixel($this->gdimg_output, $x, $y, $TransparentColor); 2603 } 2604 } 2605 } 2606 imagedestroy($img_alpha_mixdown_dither); 2607 2608 $this->DebugMessage('AlphaChannelFlatten() set image to 255+1 colors with transparency for GIF output', __FILE__, __LINE__); 2609 return true; 2610 2611 } else { 2612 $this->DebugMessage('AlphaChannelFlatten() failed imagecreate('.imagesx($this->gdimg_output).', '.imagesy($this->gdimg_output).')', __FILE__, __LINE__); 2613 return false; 2614 } 2615 2616 } else { 2617 // a single transparent color already defined, leave as-is 2618 $this->DebugMessage('skipping AlphaChannelFlatten() because ($this->thumbnailFormat == "'.$this->thumbnailFormat.'") and imagecolortransparent() returned "'.$CurrentImageColorTransparent.'"', __FILE__, __LINE__); 2619 return true; 2620 } 2621 break; 2622 } 2623 $this->DebugMessage('continuing AlphaChannelFlatten() for output format "'.$this->thumbnailFormat.'"', __FILE__, __LINE__); 2624 // image has alpha transparency, and is being output in a format that doesn't support it -- flatten 2625 if ($gdimg_flatten_temp = phpthumb_functions::ImageCreateFunction($this->thumbnail_width, $this->thumbnail_height)) { 2626 2627 $this->config_background_hexcolor = ($this->bg ? $this->bg : $this->config_background_hexcolor); 2628 if (!phpthumb_functions::IsHexColor($this->config_background_hexcolor)) { 2629 return $this->ErrorImage('Invalid hex color string "'.$this->config_background_hexcolor.'" for parameter "bg"'); 2630 } 2631 $background_color = phpthumb_functions::ImageHexColorAllocate($this->gdimg_output, $this->config_background_hexcolor); 2632 imagefilledrectangle($gdimg_flatten_temp, 0, 0, $this->thumbnail_width, $this->thumbnail_height, $background_color); 2633 imagecopy($gdimg_flatten_temp, $this->gdimg_output, 0, 0, 0, 0, $this->thumbnail_width, $this->thumbnail_height); 2634 2635 imagealphablending($this->gdimg_output, true); 2636 imagesavealpha($this->gdimg_output, false); 2637 imagecolortransparent($this->gdimg_output, -1); 2638 imagecopy($this->gdimg_output, $gdimg_flatten_temp, 0, 0, 0, 0, $this->thumbnail_width, $this->thumbnail_height); 2639 2640 imagedestroy($gdimg_flatten_temp); 2641 return true; 2642 2643 } else { 2644 $this->DebugMessage('ImageCreateFunction() failed', __FILE__, __LINE__); 2645 } 2646 return false; 2647 } 2648 2649 2650 function ApplyFilters() { 2651 if ($this->fltr && is_array($this->fltr)) { 2652 if (!include_once(dirname(__FILE__).'/phpthumb.filters.php')) { 2653 $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.filters.php" which is required for applying filters ('.implode(';', $this->fltr).')', __FILE__, __LINE__); 2654 return false; 2655 } 2656 $phpthumbFilters = new phpthumb_filters(); 2657 $phpthumbFilters->phpThumbObject = &$this; 2658 foreach ($this->fltr as $filtercommand) { 2659 @list($command, $parameter) = explode('|', $filtercommand, 2); 2660 $this->DebugMessage('Attempting to process filter command "'.$command.'('.$parameter.')"', __FILE__, __LINE__); 2661 switch ($command) { 2662 case 'brit': // Brightness 2663 $phpthumbFilters->Brightness($this->gdimg_output, $parameter); 2664 break; 2665 2666 case 'cont': // Contrast 2667 $phpthumbFilters->Contrast($this->gdimg_output, $parameter); 2668 break; 2669 2670 case 'ds': // Desaturation 2671 $phpthumbFilters->Desaturate($this->gdimg_output, $parameter, ''); 2672 break; 2673 2674 case 'sat': // Saturation 2675 $phpthumbFilters->Saturation($this->gdimg_output, $parameter, ''); 2676 break; 2677 2678 case 'gray': // Grayscale 2679 $phpthumbFilters->Grayscale($this->gdimg_output); 2680 break; 2681 2682 case 'clr': // Colorize 2683 if (phpthumb_functions::gd_version() < 2) { 2684 $this->DebugMessage('Skipping Colorize() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2685 break; 2686 } 2687 @list($amount, $color) = explode('|', $parameter, 2); 2688 $phpthumbFilters->Colorize($this->gdimg_output, $amount, $color); 2689 break; 2690 2691 case 'sep': // Sepia 2692 if (phpthumb_functions::gd_version() < 2) { 2693 $this->DebugMessage('Skipping Sepia() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2694 break; 2695 } 2696 @list($amount, $color) = explode('|', $parameter, 2); 2697 $phpthumbFilters->Sepia($this->gdimg_output, $amount, $color); 2698 break; 2699 2700 case 'gam': // Gamma correction 2701 $phpthumbFilters->Gamma($this->gdimg_output, $parameter); 2702 break; 2703 2704 case 'neg': // Negative colors 2705 $phpthumbFilters->Negative($this->gdimg_output); 2706 break; 2707 2708 case 'th': // Threshold 2709 $phpthumbFilters->Threshold($this->gdimg_output, $parameter); 2710 break; 2711 2712 case 'rcd': // ReduceColorDepth 2713 if (phpthumb_functions::gd_version() < 2) { 2714 $this->DebugMessage('Skipping ReduceColorDepth() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2715 break; 2716 } 2717 @list($colors, $dither) = explode('|', $parameter, 2); 2718 $colors = ($colors ? (int) $colors : 256); 2719 $dither = ((strlen($dither) > 0) ? (bool) $dither : true); 2720 $phpthumbFilters->ReduceColorDepth($this->gdimg_output, $colors, $dither); 2721 break; 2722 2723 case 'flip': // Flip 2724 $phpthumbFilters->Flip($this->gdimg_output, (strpos(strtolower($parameter), 'x') !== false), (strpos(strtolower($parameter), 'y') !== false)); 2725 break; 2726 2727 case 'edge': // EdgeDetect 2728 $phpthumbFilters->EdgeDetect($this->gdimg_output); 2729 break; 2730 2731 case 'emb': // Emboss 2732 $phpthumbFilters->Emboss($this->gdimg_output); 2733 break; 2734 2735 case 'bvl': // Bevel 2736 @list($width, $color1, $color2) = explode('|', $parameter, 3); 2737 $phpthumbFilters->Bevel($this->gdimg_output, $width, $color1, $color2); 2738 break; 2739 2740 case 'lvl': // autoLevels 2741 @list($band, $method, $threshold) = explode('|', $parameter, 3); 2742 $band = ($band ? preg_replace('#[^RGBA\\*]#', '', strtoupper($band)) : '*'); 2743 $method = ((strlen($method) > 0) ? intval($method) : 2); 2744 $threshold = ((strlen($threshold) > 0) ? floatval($threshold) : 0.1); 2745 2746 $phpthumbFilters->HistogramStretch($this->gdimg_output, $band, $method, $threshold); 2747 break; 2748 2749 case 'wb': // WhiteBalance 2750 $phpthumbFilters->WhiteBalance($this->gdimg_output, $parameter); 2751 break; 2752 2753 case 'hist': // Histogram overlay 2754 if (phpthumb_functions::gd_version() < 2) { 2755 $this->DebugMessage('Skipping HistogramOverlay() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2756 break; 2757 } 2758 @list($bands, $colors, $width, $height, $alignment, $opacity, $margin_x, $margin_y) = explode('|', $parameter, 8); 2759 $bands = ($bands ? $bands : '*'); 2760 $colors = ($colors ? $colors : ''); 2761 $width = ($width ? $width : 0.25); 2762 $height = ($height ? $height : 0.25); 2763 $alignment = ($alignment ? $alignment : 'BR'); 2764 $opacity = ($opacity ? $opacity : 50); 2765 $margin_x = ($margin_x ? $margin_x : 5); 2766 // $margin_y -- it wasn't forgotten, let the value always pass unchanged 2767 $phpthumbFilters->HistogramOverlay($this->gdimg_output, $bands, $colors, $width, $height, $alignment, $opacity, $margin_x, $margin_y); 2768 break; 2769 2770 case 'fram': // Frame 2771 @list($frame_width, $edge_width, $color_frame, $color1, $color2) = explode('|', $parameter, 5); 2772 $phpthumbFilters->Frame($this->gdimg_output, $frame_width, $edge_width, $color_frame, $color1, $color2); 2773 break; 2774 2775 case 'drop': // DropShadow 2776 if (phpthumb_functions::gd_version() < 2) { 2777 $this->DebugMessage('Skipping DropShadow() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2778 return false; 2779 } 2780 $this->is_alpha = true; 2781 @list($distance, $width, $color, $angle, $fade) = explode('|', $parameter, 5); 2782 $phpthumbFilters->DropShadow($this->gdimg_output, $distance, $width, $color, $angle, $fade); 2783 break; 2784 2785 case 'mask': // Mask cropping 2786 if (phpthumb_functions::gd_version() < 2) { 2787 $this->DebugMessage('Skipping Mask() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2788 return false; 2789 } 2790 @list($mask_filename, $invert) = explode('|', $parameter, 2); 2791 $mask_filename = $this->ResolveFilenameToAbsolute($mask_filename); 2792 if (@is_readable($mask_filename) && ($fp_mask = @fopen($mask_filename, 'rb'))) { 2793 $MaskImageData = ''; 2794 do { 2795 $buffer = fread($fp_mask, 8192); 2796 $MaskImageData .= $buffer; 2797 } while (strlen($buffer) > 0); 2798 fclose($fp_mask); 2799 if ($gdimg_mask = $this->ImageCreateFromStringReplacement($MaskImageData)) { 2800 if ($invert && phpthumb_functions::version_compare_replacement(phpversion(), '5.0.0', '>=') && phpthumb_functions::gd_is_bundled()) { 2801 imagefilter($gdimg_mask, IMG_FILTER_NEGATE); 2802 } 2803 $this->is_alpha = true; 2804 $phpthumbFilters->ApplyMask($gdimg_mask, $this->gdimg_output); 2805 imagedestroy($gdimg_mask); 2806 } else { 2807 $this->DebugMessage('ImageCreateFromStringReplacement() failed for "'.$mask_filename.'"', __FILE__, __LINE__); 2808 } 2809 } else { 2810 $this->DebugMessage('Cannot open mask file "'.$mask_filename.'"', __FILE__, __LINE__); 2811 } 2812 break; 2813 2814 case 'elip': // Ellipse cropping 2815 if (phpthumb_functions::gd_version() < 2) { 2816 $this->DebugMessage('Skipping Ellipse() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2817 return false; 2818 } 2819 $this->is_alpha = true; 2820 $phpthumbFilters->Ellipse($this->gdimg_output); 2821 break; 2822 2823 case 'ric': // RoundedImageCorners 2824 if (phpthumb_functions::gd_version() < 2) { 2825 $this->DebugMessage('Skipping RoundedImageCorners() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 2826 return false; 2827 } 2828 @list($radius_x, $radius_y) = explode('|', $parameter, 2); 2829 if (($radius_x < 1) || ($radius_y < 1)) { 2830 $this->DebugMessage('Skipping RoundedImageCorners('.$radius_x.', '.$radius_y.') because x/y radius is less than 1', __FILE__, __LINE__); 2831 break; 2832 } 2833 $this->is_alpha = true; 2834 $phpthumbFilters->RoundedImageCorners($this->gdimg_output, $radius_x, $radius_y); 2835 break; 2836 2837 case 'crop': // Crop 2838 @list($left, $right, $top, $bottom) = explode('|', $parameter, 4); 2839 $phpthumbFilters->Crop($this->gdimg_output, $left, $right, $top, $bottom); 2840 break; 2841 2842 case 'bord': // Border 2843 @list($border_width, $radius_x, $radius_y, $hexcolor_border) = explode('|', $parameter, 4); 2844 $this->is_alpha = true; 2845 $phpthumbFilters->ImageBorder($this->gdimg_output, $border_width, $radius_x, $radius_y, $hexcolor_border); 2846 break; 2847 2848 case 'over': // Overlay 2849 @list($filename, $underlay, $margin, $opacity) = explode('|', $parameter, 4); 2850 $underlay = (bool) ($underlay ? $underlay : false); 2851 $margin = ((strlen($margin) > 0) ? $margin : ($underlay ? 0.1 : 0.0)); 2852 $opacity = ((strlen($opacity) > 0) ? $opacity : 100); 2853 if (($margin > 0) && ($margin < 1)) { 2854 $margin = min(0.499, $margin); 2855 } elseif (($margin > -1) && ($margin < 0)) { 2856 $margin = max(-0.499, $margin); 2857 } 2858 2859 $filename = $this->ResolveFilenameToAbsolute($filename); 2860 if (@is_readable($filename) && ($fp_watermark = @fopen($filename, 'rb'))) { 2861 $WatermarkImageData = ''; 2862 do { 2863 $buffer = fread($fp_watermark, 8192); 2864 $WatermarkImageData .= $buffer; 2865 } while (strlen($buffer) > 0); 2866 fclose($fp_watermark); 2867 if ($img_watermark = $this->ImageCreateFromStringReplacement($WatermarkImageData)) { 2868 if (($margin > 0) && ($margin < 1)) { 2869 $resized_x = max(1, imagesx($this->gdimg_output) - round(2 * (imagesx($this->gdimg_output) * $margin))); 2870 $resized_y = max(1, imagesy($this->gdimg_output) - round(2 * (imagesy($this->gdimg_output) * $margin))); 2871 } else { 2872 $resized_x = max(1, imagesx($this->gdimg_output) - round(2 * $margin)); 2873 $resized_y = max(1, imagesy($this->gdimg_output) - round(2 * $margin)); 2874 } 2875 2876 if ($underlay) { 2877 2878 if ($img_watermark_resized = phpthumb_functions::ImageCreateFunction(imagesx($this->gdimg_output), imagesy($this->gdimg_output))) { 2879 imagealphablending($img_watermark_resized, false); 2880 imagesavealpha($img_watermark_resized, true); 2881 $this->ImageResizeFunction($img_watermark_resized, $img_watermark, 0, 0, 0, 0, imagesx($img_watermark_resized), imagesy($img_watermark_resized), imagesx($img_watermark), imagesy($img_watermark)); 2882 if ($img_source_resized = phpthumb_functions::ImageCreateFunction($resized_x, $resized_y)) { 2883 imagealphablending($img_source_resized, false); 2884 imagesavealpha($img_source_resized, true); 2885 $this->ImageResizeFunction($img_source_resized, $this->gdimg_output, 0, 0, 0, 0, imagesx($img_source_resized), imagesy($img_source_resized), imagesx($this->gdimg_output), imagesy($this->gdimg_output)); 2886 $phpthumbFilters->WatermarkOverlay($img_watermark_resized, $img_source_resized, 'C', $opacity, $margin); 2887 imagecopy($this->gdimg_output, $img_watermark_resized, 0, 0, 0, 0, imagesx($this->gdimg_output), imagesy($this->gdimg_output)); 2888 } else { 2889 $this->DebugMessage('phpthumb_functions::ImageCreateFunction('.$resized_x.', '.$resized_y.')', __FILE__, __LINE__); 2890 } 2891 imagedestroy($img_watermark_resized); 2892 } else { 2893 $this->DebugMessage('phpthumb_functions::ImageCreateFunction('.imagesx($this->gdimg_output).', '.imagesy($this->gdimg_output).')', __FILE__, __LINE__); 2894 } 2895 2896 } else { // overlay 2897 2898 if ($img_watermark_resized = phpthumb_functions::ImageCreateFunction($resized_x, $resized_y)) { 2899 imagealphablending($img_watermark_resized, false); 2900 imagesavealpha($img_watermark_resized, true); 2901 $this->ImageResizeFunction($img_watermark_resized, $img_watermark, 0, 0, 0, 0, imagesx($img_watermark_resized), imagesy($img_watermark_resized), imagesx($img_watermark), imagesy($img_watermark)); 2902 $phpthumbFilters->WatermarkOverlay($this->gdimg_output, $img_watermark_resized, 'C', $opacity, $margin); 2903 imagedestroy($img_watermark_resized); 2904 } else { 2905 $this->DebugMessage('phpthumb_functions::ImageCreateFunction('.$resized_x.', '.$resized_y.')', __FILE__, __LINE__); 2906 } 2907 2908 } 2909 imagedestroy($img_watermark); 2910 2911 } else { 2912 $this->DebugMessage('ImageCreateFromStringReplacement() failed for "'.$filename.'"', __FILE__, __LINE__); 2913 } 2914 } else { 2915 $this->DebugMessage('Cannot open overlay file "'.$filename.'"', __FILE__, __LINE__); 2916 } 2917 break; 2918 2919 case 'wmi': // WaterMarkImage 2920 @list($filename, $alignment, $opacity, $margin['x'], $margin['y'], $rotate_angle) = explode('|', $parameter, 6); 2921 // $margin can be pixel margin or percent margin if $alignment is text, or max width/height if $alignment is position like "50x75" 2922 $alignment = ($alignment ? $alignment : 'BR'); 2923 $opacity = (strlen($opacity) ? intval($opacity) : 50); 2924 $rotate_angle = (strlen($rotate_angle) ? intval($rotate_angle) : 0); 2925 if (!preg_match('#^([0-9\\.\\-]*)x([0-9\\.\\-]*)$#i', $alignment, $matches)) { 2926 $margins = array('x', 'y'); 2927 foreach ($margins as $xy) { 2928 $margin[$xy] = (strlen($margin[$xy]) ? $margin[$xy] : 5); 2929 if (($margin[$xy] > 0) && ($margin[$xy] < 1)) { 2930 $margin[$xy] = min(0.499, $margin[$xy]); 2931 } elseif (($margin[$xy] > -1) && ($margin[$xy] < 0)) { 2932 $margin[$xy] = max(-0.499, $margin[$xy]); 2933 } 2934 } 2935 } 2936 2937 $filename = $this->ResolveFilenameToAbsolute($filename); 2938 if (@is_readable($filename)) { 2939 if ($img_watermark = $this->ImageCreateFromFilename($filename)) { 2940 if ($rotate_angle !== 0) { 2941 $phpthumbFilters->ImprovedImageRotate($img_watermark, $rotate_angle, 'FFFFFF', null, $this); 2942 } 2943 if (preg_match('#^([0-9\\.\\-]*)x([0-9\\.\\-]*)$#i', $alignment, $matches)) { 2944 $watermark_max_width = intval($margin['x'] ? $margin['x'] : imagesx($img_watermark)); 2945 $watermark_max_height = intval($margin['y'] ? $margin['y'] : imagesy($img_watermark)); 2946 $scale = phpthumb_functions::ScaleToFitInBox(imagesx($img_watermark), imagesy($img_watermark), $watermark_max_width, $watermark_max_height, true, true); 2947 $this->DebugMessage('Scaling watermark by a factor of '.number_format($scale, 4), __FILE__, __LINE__); 2948 if (($scale > 1) || ($scale < 1)) { 2949 if ($img_watermark2 = phpthumb_functions::ImageCreateFunction($scale * imagesx($img_watermark), $scale * imagesy($img_watermark))) { 2950 imagealphablending($img_watermark2, false); 2951 imagesavealpha($img_watermark2, true); 2952 $this->ImageResizeFunction($img_watermark2, $img_watermark, 0, 0, 0, 0, imagesx($img_watermark2), imagesy($img_watermark2), imagesx($img_watermark), imagesy($img_watermark)); 2953 $img_watermark = $img_watermark2; 2954 } else { 2955 $this->DebugMessage('ImageCreateFunction('.($scale * imagesx($img_watermark)).', '.($scale * imagesx($img_watermark)).') failed', __FILE__, __LINE__); 2956 } 2957 } 2958 $watermark_dest_x = round($matches[1] - (imagesx($img_watermark) / 2)); 2959 $watermark_dest_y = round($matches[2] - (imagesy($img_watermark) / 2)); 2960 $alignment = $watermark_dest_x.'x'.$watermark_dest_y; 2961 } 2962 $phpthumbFilters->WatermarkOverlay($this->gdimg_output, $img_watermark, $alignment, $opacity, $margin['x'], $margin['y']); 2963 imagedestroy($img_watermark); 2964 if (isset($img_watermark2) && is_resource($img_watermark2)) { 2965 imagedestroy($img_watermark2); 2966 } 2967 } else { 2968 $this->DebugMessage('ImageCreateFromFilename() failed for "'.$filename.'"', __FILE__, __LINE__); 2969 } 2970 } else { 2971 $this->DebugMessage('!is_readable('.$filename.')', __FILE__, __LINE__); 2972 } 2973 break; 2974 2975 case 'wmt': // WaterMarkText 2976 @list($text, $size, $alignment, $hex_color, $ttffont, $opacity, $margin, $angle, $bg_color, $bg_opacity, $fillextend) = explode('|', $parameter, 11); 2977 $text = ($text ? $text : ''); 2978 $size = ($size ? $size : 3); 2979 $alignment = ($alignment ? $alignment : 'BR'); 2980 $hex_color = ($hex_color ? $hex_color : '000000'); 2981 $ttffont = ($ttffont ? $ttffont : ''); 2982 $opacity = (strlen($opacity) ? $opacity : 50); 2983 $margin = (strlen($margin) ? $margin : 5); 2984 $angle = (strlen($angle) ? $angle : 0); 2985 $bg_color = ($bg_color ? $bg_color : false); 2986 $bg_opacity = ($bg_opacity ? $bg_opacity : 0); 2987 $fillextend = ($fillextend ? $fillextend : ''); 2988 2989 if (basename($ttffont) == $ttffont) { 2990 $ttffont = $this->realPathSafe($this->config_ttf_directory.DIRECTORY_SEPARATOR.$ttffont); 2991 } else { 2992 $ttffont = $this->ResolveFilenameToAbsolute($ttffont); 2993 } 2994 $phpthumbFilters->WatermarkText($this->gdimg_output, $text, $size, $alignment, $hex_color, $ttffont, $opacity, $margin, $angle, $bg_color, $bg_opacity, $fillextend); 2995 break; 2996 2997 case 'blur': // Blur 2998 @list($radius) = explode('|', $parameter, 1); 2999 $radius = ($radius ? $radius : 1); 3000 if (phpthumb_functions::gd_version() >= 2) { 3001 $phpthumbFilters->Blur($this->gdimg_output, $radius); 3002 } else { 3003 $this->DebugMessage('Skipping Blur() because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 3004 } 3005 break; 3006 3007 case 'gblr': // Gaussian Blur 3008 $phpthumbFilters->BlurGaussian($this->gdimg_output); 3009 break; 3010 3011 case 'sblr': // Selective Blur 3012 $phpthumbFilters->BlurSelective($this->gdimg_output); 3013 break; 3014 3015 case 'mean': // MeanRemoval blur 3016 $phpthumbFilters->MeanRemoval($this->gdimg_output); 3017 break; 3018 3019 case 'smth': // Smooth blur 3020 $phpthumbFilters->Smooth($this->gdimg_output, $parameter); 3021 break; 3022 3023 case 'usm': // UnSharpMask sharpening 3024 @list($amount, $radius, $threshold) = explode('|', $parameter, 3); 3025 $amount = ($amount ? $amount : 80); 3026 $radius = ($radius ? $radius : 0.5); 3027 $threshold = (strlen($threshold) ? $threshold : 3); 3028 if (phpthumb_functions::gd_version() >= 2.0) { 3029 ob_start(); 3030 if (!@include_once(dirname(__FILE__).'/phpthumb.unsharp.php')) { 3031 $include_error = ob_get_contents(); 3032 if ($include_error) { 3033 $this->DebugMessage('include_once("'.dirname(__FILE__).'/phpthumb.unsharp.php") generated message: "'.$include_error.'"', __FILE__, __LINE__); 3034 } 3035 $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.unsharp.php" which is required for unsharp masking', __FILE__, __LINE__); 3036 ob_end_clean(); 3037 return false; 3038 } 3039 ob_end_clean(); 3040 phpUnsharpMask::applyUnsharpMask($this->gdimg_output, $amount, $radius, $threshold); 3041 } else { 3042 $this->DebugMessage('Skipping unsharp mask because gd_version is "'.phpthumb_functions::gd_version().'"', __FILE__, __LINE__); 3043 return false; 3044 } 3045 break; 3046 3047 case 'size': // Resize 3048 @list($newwidth, $newheight, $stretch) = explode('|', $parameter); 3049 $newwidth = (!$newwidth ? imagesx($this->gdimg_output) : ((($newwidth > 0) && ($newwidth < 1)) ? round($newwidth * imagesx($this->gdimg_output)) : round($newwidth))); 3050 $newheight = (!$newheight ? imagesy($this->gdimg_output) : ((($newheight > 0) && ($newheight < 1)) ? round($newheight * imagesy($this->gdimg_output)) : round($newheight))); 3051 $stretch = ($stretch ? true : false); 3052 if ($stretch) { 3053 $scale_x = phpthumb_functions::ScaleToFitInBox(imagesx($this->gdimg_output), imagesx($this->gdimg_output), $newwidth, $newwidth, true, true); 3054 $scale_y = phpthumb_functions::ScaleToFitInBox(imagesy($this->gdimg_output), imagesy($this->gdimg_output), $newheight, $newheight, true, true); 3055 } else { 3056 $scale_x = phpthumb_functions::ScaleToFitInBox(imagesx($this->gdimg_output), imagesy($this->gdimg_output), $newwidth, $newheight, true, true); 3057 $scale_y = $scale_x; 3058 } 3059 $this->DebugMessage('Scaling watermark ('.($stretch ? 'with' : 'without').' stretch) by a factor of "'.number_format($scale_x, 4).' x '.number_format($scale_y, 4).'"', __FILE__, __LINE__); 3060 if (($scale_x > 1) || ($scale_x < 1) || ($scale_y > 1) || ($scale_y < 1)) { 3061 if ($img_temp = phpthumb_functions::ImageCreateFunction(imagesx($this->gdimg_output), imagesy($this->gdimg_output))) { 3062 imagecopy($img_temp, $this->gdimg_output, 0, 0, 0, 0, imagesx($this->gdimg_output), imagesy($this->gdimg_output)); 3063 if ($this->gdimg_output = phpthumb_functions::ImageCreateFunction($scale_x * imagesx($img_temp), $scale_y * imagesy($img_temp))) { 3064 imagealphablending($this->gdimg_output, false); 3065 imagesavealpha($this->gdimg_output, true); 3066 $this->ImageResizeFunction($this->gdimg_output, $img_temp, 0, 0, 0, 0, imagesx($this->gdimg_output), imagesy($this->gdimg_output), imagesx($img_temp), imagesy($img_temp)); 3067 } else { 3068 $this->DebugMessage('ImageCreateFunction('.($scale_x * imagesx($img_temp)).', '.($scale_y * imagesy($img_temp)).') failed', __FILE__, __LINE__); 3069 } 3070 imagedestroy($img_temp); 3071 } else { 3072 $this->DebugMessage('ImageCreateFunction('.imagesx($this->gdimg_output).', '.imagesy($this->gdimg_output).') failed', __FILE__, __LINE__); 3073 } 3074 } 3075 break; 3076 3077 case 'rot': // ROTate 3078 @list($angle, $bgcolor) = explode('|', $parameter, 2); 3079 $phpthumbFilters->ImprovedImageRotate($this->gdimg_output, $angle, $bgcolor, null, $this); 3080 break; 3081 3082 case 'stc': // Source Transparent Color 3083 @list($hexcolor, $min_limit, $max_limit) = explode('|', $parameter, 3); 3084 if (!phpthumb_functions::IsHexColor($hexcolor)) { 3085 $this->DebugMessage('Skipping SourceTransparentColor hex color is invalid ('.$hexcolor.')', __FILE__, __LINE__); 3086 return false; 3087 } 3088 $min_limit = (strlen($