PageRenderTime 60ms CodeModel.GetById 6ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 2ms

/assets/snippets/phpthumb/phpthumb.class.php

https://github.com/modxcms/evolution
PHP | 4292 lines | 3548 code | 464 blank | 280 comment | 834 complexity | ee0e8bb583a334318312de3fe86c9161 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   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("'.realpath(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_query                          = null;
 134	var $config_mysql_hostname                       = null;
 135	var $config_mysql_username                       = null;
 136	var $config_mysql_password                       = null;
 137	var $config_mysql_database                       = null;
 138
 139	// * Security
 140	var $config_high_security_enabled                = true;
 141	var $config_high_security_password               = null;
 142	var $config_high_security_url_separator          = '&';
 143	var $config_disable_debug                        = true;
 144	var $config_allow_src_above_docroot              = false;
 145	var $config_allow_src_above_phpthumb             = true;
 146	var $config_auto_allow_symlinks                  = true;    // allow symlink target directories without explicitly whitelisting them
 147	var $config_additional_allowed_dirs              = array(); // additional directories to allow source images to be read from
 148
 149	// * HTTP fopen
 150	var $config_http_fopen_timeout                   = 10;
 151	var $config_http_follow_redirect                 = true;
 152
 153	// * Compatability
 154	var $config_disable_pathinfo_parsing             = false;
 155	var $config_disable_imagecopyresampled           = false;
 156	var $config_disable_onlycreateable_passthru      = false;
 157
 158	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';
 159
 160	// END CONFIGURATION OPTIONS
 161
 162
 163	// public: error messages (read-only; persistant)
 164	var $debugmessages = array();
 165	var $debugtiming   = array();
 166	var $fatalerror    = null;
 167
 168
 169	// private: (should not be modified directly)
 170	var $thumbnailQuality = 75;
 171	var $thumbnailFormat  = null;
 172
 173	var $sourceFilename   = null;
 174	var $rawImageData     = null;
 175	var $IMresizedData    = null;
 176	var $outputImageData  = null;
 177
 178	var $useRawIMoutput   = false;
 179
 180	var $gdimg_output     = null;
 181	var $gdimg_source     = null;
 182
 183	var $getimagesizeinfo = null;
 184
 185	var $source_width  = null;
 186	var $source_height = null;
 187
 188	var $thumbnailCropX = null;
 189	var $thumbnailCropY = null;
 190	var $thumbnailCropW = null;
 191	var $thumbnailCropH = null;
 192
 193	var $exif_thumbnail_width  = null;
 194	var $exif_thumbnail_height = null;
 195	var $exif_thumbnail_type   = null;
 196	var $exif_thumbnail_data   = null;
 197	var $exif_raw_data         = null;
 198
 199	var $thumbnail_width        = null;
 200	var $thumbnail_height       = null;
 201	var $thumbnail_image_width  = null;
 202	var $thumbnail_image_height = null;
 203
 204	var $tempFilesToDelete = array();
 205	var $cache_filename    = null;
 206
 207	var $AlphaCapableFormats = array('png', 'ico', 'gif');
 208	var $is_alpha = false;
 209
 210	var $iswindows  = null;
 211	var $issafemode = null;
 212
 213	var $phpthumb_version = '1.7.13-201406261000';
 214
 215	//////////////////////////////////////////////////////////////////////
 216
 217	// public: constructor
 218	function phpThumb() {
 219		$this->DebugTimingMessage('phpThumb() constructor', __FILE__, __LINE__);
 220		$this->DebugMessage('phpThumb() v'.$this->phpthumb_version, __FILE__, __LINE__);
 221		$this->config_max_source_pixels = round(max(intval(ini_get('memory_limit')), intval(get_cfg_var('memory_limit'))) * 1048576 * 0.20); // 20% of memory_limit
 222		$this->iswindows  = (bool) (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN');
 223		$this->issafemode = (bool) preg_match('#(1|ON)#i', ini_get('safe_mode'));
 224		$this->config_document_root = (!empty($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT']   : $this->config_document_root);
 225		$this->config_cache_prefix  = ( isset($_SERVER['SERVER_NAME'])   ? $_SERVER['SERVER_NAME'].'_' : '');
 226
 227		$this->purgeTempFiles(); // purge existing temp files if re-initializing object
 228
 229		$php_sapi_name = strtolower(function_exists('php_sapi_name') ? php_sapi_name() : '');
 230		if ($php_sapi_name == 'cli') {
 231			$this->config_allow_src_above_docroot = true;
 232		}
 233
 234		if (!$this->config_disable_debug) {
 235			// if debug mode is enabled, force phpThumbDebug output, do not allow normal thumbnails to be generated
 236			$this->phpThumbDebug = (is_null($this->phpThumbDebug) ? 9 : max(1, intval($this->phpThumbDebug)));
 237		}
 238	}
 239
 240	function __destruct() {
 241		$this->purgeTempFiles();
 242	}
 243
 244	// public:
 245	function purgeTempFiles() {
 246		foreach ($this->tempFilesToDelete as $tempFileToDelete) {
 247			if (file_exists($tempFileToDelete)) {
 248				$this->DebugMessage('Deleting temp file "'.$tempFileToDelete.'"', __FILE__, __LINE__);
 249				@unlink($tempFileToDelete);
 250			}
 251		}
 252		$this->tempFilesToDelete = array();
 253		return true;
 254	}
 255
 256	// public:
 257	function setSourceFilename($sourceFilename) {
 258		//$this->resetObject();
 259		//$this->rawImageData   = null;
 260		$this->sourceFilename = $sourceFilename;
 261		$this->src            = $sourceFilename;
 262		if (is_null($this->config_output_format)) {
 263			$sourceFileExtension = strtolower(substr(strrchr($sourceFilename, '.'), 1));
 264			if (preg_match('#^[a-z]{3,4}$#', $sourceFileExtension)) {
 265				$this->config_output_format = $sourceFileExtension;
 266				$this->DebugMessage('setSourceFilename('.$sourceFilename.') set $this->config_output_format to "'.$sourceFileExtension.'"', __FILE__, __LINE__);
 267			} else {
 268				$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__);
 269			}
 270		}
 271		$this->DebugMessage('setSourceFilename('.$sourceFilename.') set $this->sourceFilename to "'.$this->sourceFilename.'"', __FILE__, __LINE__);
 272		return true;
 273	}
 274
 275	// public:
 276	function setSourceData($rawImageData, $sourceFilename='') {
 277		//$this->resetObject();
 278		//$this->sourceFilename = null;
 279		$this->rawImageData   = $rawImageData;
 280		$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__);
 281		if ($this->config_cache_source_enabled) {
 282			$sourceFilename = ($sourceFilename ? $sourceFilename : md5($rawImageData));
 283			if (!is_dir($this->config_cache_source_directory)) {
 284				$this->ErrorImage('$this->config_cache_source_directory ('.$this->config_cache_source_directory.') is not a directory');
 285			} elseif (!@is_writable($this->config_cache_source_directory)) {
 286				$this->ErrorImage('$this->config_cache_source_directory ('.$this->config_cache_source_directory.') is not writable');
 287			}
 288			$this->DebugMessage('setSourceData() attempting to save source image to "'.$this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename).'"', __FILE__, __LINE__);
 289			if ($fp = @fopen($this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename), 'wb')) {
 290				fwrite($fp, $rawImageData);
 291				fclose($fp);
 292			} elseif (!$this->phpThumbDebug) {
 293				$this->ErrorImage('setSourceData() failed to write to source cache ('.$this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename).')');
 294			}
 295		}
 296		return true;
 297	}
 298
 299	// public:
 300	function setSourceImageResource($gdimg) {
 301		//$this->resetObject();
 302		$this->gdimg_source = $gdimg;
 303		return true;
 304	}
 305
 306	// public:
 307	function setParameter($param, $value) {
 308		if ($param == 'src') {
 309			$this->setSourceFilename($this->ResolveFilenameToAbsolute($value));
 310		} elseif (@is_array($this->$param)) {
 311			if (is_array($value)) {
 312				foreach ($value as $arraykey => $arrayvalue) {
 313					array_push($this->$param, $arrayvalue);
 314				}
 315			} else {
 316				array_push($this->$param, $value);
 317			}
 318		} else {
 319			$this->$param = $value;
 320		}
 321		return true;
 322	}
 323
 324	// public:
 325	function getParameter($param) {
 326		//if (property_exists('phpThumb', $param)) {
 327			return $this->$param;
 328		//}
 329		//$this->DebugMessage('setParameter() attempting to get non-existant parameter "'.$param.'"', __FILE__, __LINE__);
 330		//return false;
 331	}
 332
 333
 334	// public:
 335	function GenerateThumbnail() {
 336
 337		$this->setOutputFormat();
 338			$this->phpThumbDebug('8a');
 339		$this->ResolveSource();
 340			$this->phpThumbDebug('8b');
 341		$this->SetCacheFilename();
 342			$this->phpThumbDebug('8c');
 343		$this->ExtractEXIFgetImageSize();
 344			$this->phpThumbDebug('8d');
 345		if ($this->useRawIMoutput) {
 346			$this->DebugMessage('Skipping rest of GenerateThumbnail() because ($this->useRawIMoutput == true)', __FILE__, __LINE__);
 347			return true;
 348		}
 349			$this->phpThumbDebug('8e');
 350		if (!$this->SourceImageToGD()) {
 351			$this->DebugMessage('SourceImageToGD() failed', __FILE__, __LINE__);
 352			return false;
 353		}
 354			$this->phpThumbDebug('8f');
 355		$this->Rotate();
 356			$this->phpThumbDebug('8g');
 357		$this->CreateGDoutput();
 358			$this->phpThumbDebug('8h');
 359
 360		// default values, also applicable for far="C"
 361		$destination_offset_x = round(($this->thumbnail_width  - $this->thumbnail_image_width)  / 2);
 362		$destination_offset_y = round(($this->thumbnail_height - $this->thumbnail_image_height) / 2);
 363		if (($this->far == 'L') || ($this->far == 'TL') || ($this->far == 'BL')) {
 364			$destination_offset_x = 0;
 365		}
 366		if (($this->far == 'R') || ($this->far == 'TR') || ($this->far == 'BR')) {
 367			$destination_offset_x =  round($this->thumbnail_width  - $this->thumbnail_image_width);
 368		}
 369		if (($this->far == 'T') || ($this->far == 'TL') || ($this->far == 'TR')) {
 370			$destination_offset_y = 0;
 371		}
 372		if (($this->far == 'B') || ($this->far == 'BL') || ($this->far == 'BR')) {
 373			$destination_offset_y =  round($this->thumbnail_height - $this->thumbnail_image_height);
 374		}
 375
 376//		// copy/resize image to appropriate dimensions
 377//		$borderThickness = 0;
 378//		if (!empty($this->fltr)) {
 379//			foreach ($this->fltr as $key => $value) {
 380//				if (preg_match('#^bord\|([0-9]+)#', $value, $matches)) {
 381//					$borderThickness = $matches[1];
 382//					break;
 383//				}
 384//			}
 385//		}
 386//		if ($borderThickness > 0) {
 387//			//$this->DebugMessage('Skipping ImageResizeFunction() because BorderThickness="'.$borderThickness.'"', __FILE__, __LINE__);
 388//			$this->thumbnail_image_height /= 2;
 389//		}
 390		$this->ImageResizeFunction(
 391			$this->gdimg_output,
 392			$this->gdimg_source,
 393			$destination_offset_x,
 394			$destination_offset_y,
 395			$this->thumbnailCropX,
 396			$this->thumbnailCropY,
 397			$this->thumbnail_image_width,
 398			$this->thumbnail_image_height,
 399			$this->thumbnailCropW,
 400			$this->thumbnailCropH
 401		);
 402
 403		$this->DebugMessage('memory_get_usage() after copy-resize = '.(function_exists('memory_get_usage') ? @memory_get_usage() : 'n/a'), __FILE__, __LINE__);
 404		ImageDestroy($this->gdimg_source);
 405		$this->DebugMessage('memory_get_usage() after ImageDestroy = '.(function_exists('memory_get_usage') ? @memory_get_usage() : 'n/a'), __FILE__, __LINE__);
 406
 407			$this->phpThumbDebug('8i');
 408		$this->AntiOffsiteLinking();
 409			$this->phpThumbDebug('8j');
 410		$this->ApplyFilters();
 411			$this->phpThumbDebug('8k');
 412		$this->AlphaChannelFlatten();
 413			$this->phpThumbDebug('8l');
 414		$this->MaxFileSize();
 415			$this->phpThumbDebug('8m');
 416
 417		$this->DebugMessage('GenerateThumbnail() completed successfully', __FILE__, __LINE__);
 418		return true;
 419	}
 420
 421
 422	// public:
 423	function RenderOutput() {
 424		if (!$this->useRawIMoutput && !is_resource($this->gdimg_output)) {
 425			$this->DebugMessage('RenderOutput() failed because !is_resource($this->gdimg_output)', __FILE__, __LINE__);
 426			return false;
 427		}
 428		if (!$this->thumbnailFormat) {
 429			$this->DebugMessage('RenderOutput() failed because $this->thumbnailFormat is empty', __FILE__, __LINE__);
 430			return false;
 431		}
 432		if ($this->useRawIMoutput) {
 433			$this->DebugMessage('RenderOutput copying $this->IMresizedData ('.strlen($this->IMresizedData).' bytes) to $this->outputImage', __FILE__, __LINE__);
 434			$this->outputImageData = $this->IMresizedData;
 435			return true;
 436		}
 437
 438		$builtin_formats = array();
 439		if (function_exists('ImageTypes')) {
 440			$imagetypes = ImageTypes();
 441			$builtin_formats['wbmp'] = (bool) ($imagetypes & IMG_WBMP);
 442			$builtin_formats['jpg']  = (bool) ($imagetypes & IMG_JPG);
 443			$builtin_formats['gif']  = (bool) ($imagetypes & IMG_GIF);
 444			$builtin_formats['png']  = (bool) ($imagetypes & IMG_PNG);
 445		}
 446		$this->DebugMessage('RenderOutput() attempting Image'.strtoupper(@$this->thumbnailFormat).'($this->gdimg_output)', __FILE__, __LINE__);
 447		ob_start();
 448		switch ($this->thumbnailFormat) {
 449			case 'wbmp':
 450				if (!@$builtin_formats['wbmp']) {
 451					$this->DebugMessage('GD does not have required built-in support for WBMP output', __FILE__, __LINE__);
 452					ob_end_clean();
 453					return false;
 454				}
 455				ImageJPEG($this->gdimg_output, null, $this->thumbnailQuality);
 456				$this->outputImageData = ob_get_contents();
 457				break;
 458
 459			case 'jpeg':
 460			case 'jpg':  // should be "jpeg" not "jpg" but just in case...
 461				if (!@$builtin_formats['jpg']) {
 462					$this->DebugMessage('GD does not have required built-in support for JPEG output', __FILE__, __LINE__);
 463					ob_end_clean();
 464					return false;
 465				}
 466				ImageJPEG($this->gdimg_output, null, $this->thumbnailQuality);
 467				$this->outputImageData = ob_get_contents();
 468				break;
 469
 470			case 'png':
 471				if (!@$builtin_formats['png']) {
 472					$this->DebugMessage('GD does not have required built-in support for PNG output', __FILE__, __LINE__);
 473					ob_end_clean();
 474					return false;
 475				}
 476				ImagePNG($this->gdimg_output);
 477				$this->outputImageData = ob_get_contents();
 478				break;
 479
 480			case 'gif':
 481				if (!@$builtin_formats['gif']) {
 482					$this->DebugMessage('GD does not have required built-in support for GIF output', __FILE__, __LINE__);
 483					ob_end_clean();
 484					return false;
 485				}
 486				ImageGIF($this->gdimg_output);
 487				$this->outputImageData = ob_get_contents();
 488				break;
 489
 490			case 'bmp':
 491				$ImageOutFunction = '"builtin BMP output"';
 492				if (!@include_once(dirname(__FILE__).'/phpthumb.bmp.php')) {
 493					$this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.bmp.php" which is required for BMP format output', __FILE__, __LINE__);
 494					ob_end_clean();
 495					return false;
 496				}
 497				$phpthumb_bmp = new phpthumb_bmp();
 498				$this->outputImageData = $phpthumb_bmp->GD2BMPstring($this->gdimg_output);
 499				unset($phpthumb_bmp);
 500				break;
 501
 502			case 'ico':
 503				$ImageOutFunction = '"builtin ICO output"';
 504				if (!@include_once(dirname(__FILE__) . '/phpthumb.ico.php')) {
 505					$this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.ico.php" which is required for ICO format output', __FILE__, __LINE__);
 506					ob_end_clean();
 507					return false;
 508				}
 509				$phpthumb_ico = new phpthumb_ico();
 510				$arrayOfOutputImages = array($this->gdimg_output);
 511				$this->outputImageData = $phpthumb_ico->GD2ICOstring($arrayOfOutputImages);
 512				unset($phpthumb_ico);
 513				break;
 514
 515			default:
 516				$this->DebugMessage('RenderOutput failed because $this->thumbnailFormat "'.$this->thumbnailFormat.'" is not valid', __FILE__, __LINE__);
 517				ob_end_clean();
 518				return false;
 519		}
 520		ob_end_clean();
 521		if (!$this->outputImageData) {
 522			$this->DebugMessage('RenderOutput() for "'.$this->thumbnailFormat.'" failed', __FILE__, __LINE__);
 523			ob_end_clean();
 524			return false;
 525		}
 526		$this->DebugMessage('RenderOutput() completing with $this->outputImageData = '.strlen($this->outputImageData).' bytes', __FILE__, __LINE__);
 527		return true;
 528	}
 529
 530
 531	// public:
 532	function RenderToFile($filename) {
 533		if (preg_match('#^[a-z0-9]+://#i', $filename)) {
 534			$this->DebugMessage('RenderToFile() failed because $filename ('.$filename.') is a URL', __FILE__, __LINE__);
 535			return false;
 536		}
 537		// render thumbnail to this file only, do not cache, do not output to browser
 538		//$renderfilename = $this->ResolveFilenameToAbsolute(dirname($filename)).DIRECTORY_SEPARATOR.basename($filename);
 539		$renderfilename = $filename;
 540		if (($filename{0} != '/') && ($filename{0} != '\\') && ($filename{1} != ':')) {
 541			$renderfilename = $this->ResolveFilenameToAbsolute($renderfilename);
 542		}
 543		if (!@is_writable(dirname($renderfilename))) {
 544			$this->DebugMessage('RenderToFile() failed because "'.dirname($renderfilename).'/" is not writable', __FILE__, __LINE__);
 545			return false;
 546		}
 547		if (@is_file($renderfilename) && !@is_writable($renderfilename)) {
 548			$this->DebugMessage('RenderToFile() failed because "'.$renderfilename.'" is not writable', __FILE__, __LINE__);
 549			return false;
 550		}
 551
 552		if ($this->RenderOutput()) {
 553			if (file_put_contents($renderfilename, $this->outputImageData)) {
 554				$this->DebugMessage('RenderToFile('.$renderfilename.') succeeded', __FILE__, __LINE__);
 555				return true;
 556			}
 557			if (!@file_exists($renderfilename)) {
 558				$this->DebugMessage('RenderOutput ['.$this->thumbnailFormat.'('.$renderfilename.')] did not appear to fail, but the output image does not exist either...', __FILE__, __LINE__);
 559			}
 560		} else {
 561			$this->DebugMessage('RenderOutput ['.$this->thumbnailFormat.'('.$renderfilename.')] failed', __FILE__, __LINE__);
 562		}
 563		return false;
 564	}
 565
 566
 567	// public:
 568	function OutputThumbnail() {
 569		$this->purgeTempFiles();
 570
 571		if (!$this->useRawIMoutput && !is_resource($this->gdimg_output)) {
 572			$this->DebugMessage('OutputThumbnail() failed because !is_resource($this->gdimg_output)', __FILE__, __LINE__);
 573			return false;
 574		}
 575		if (headers_sent()) {
 576			return $this->ErrorImage('OutputThumbnail() failed - headers already sent');
 577			exit;
 578		}
 579
 580		$downloadfilename = phpthumb_functions::SanitizeFilename(is_string($this->sia) ? $this->sia : ($this->down ? $this->down : 'phpThumb_generated_thumbnail'.'.'.$this->thumbnailFormat));
 581		$this->DebugMessage('Content-Disposition header filename set to "'.$downloadfilename.'"', __FILE__, __LINE__);
 582		if ($downloadfilename) {
 583			header('Content-Disposition: '.($this->down ? 'attachment' : 'inline').'; filename="'.$downloadfilename.'"');
 584		} else {
 585			$this->DebugMessage('failed to send Content-Disposition header because $downloadfilename is empty', __FILE__, __LINE__);
 586		}
 587
 588		if ($this->useRawIMoutput) {
 589
 590			header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
 591			echo $this->IMresizedData;
 592
 593		} else {
 594
 595			$this->DebugMessage('ImageInterlace($this->gdimg_output, '.intval($this->config_output_interlace).')', __FILE__, __LINE__);
 596			ImageInterlace($this->gdimg_output, intval($this->config_output_interlace));
 597			switch ($this->thumbnailFormat) {
 598				case 'jpeg':
 599					header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
 600					$ImageOutFunction = 'image'.$this->thumbnailFormat;
 601					@$ImageOutFunction($this->gdimg_output, null, $this->thumbnailQuality);
 602					break;
 603
 604				case 'png':
 605				case 'gif':
 606					header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
 607					$ImageOutFunction = 'image'.$this->thumbnailFormat;
 608					@$ImageOutFunction($this->gdimg_output);
 609					break;
 610
 611				case 'bmp':
 612					if (!@include_once(dirname(__FILE__).'/phpthumb.bmp.php')) {
 613						$this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.bmp.php" which is required for BMP format output', __FILE__, __LINE__);
 614						return false;
 615					}
 616					$phpthumb_bmp = new phpthumb_bmp();
 617					if (is_object($phpthumb_bmp)) {
 618						$bmp_data = $phpthumb_bmp->GD2BMPstring($this->gdimg_output);
 619						unset($phpthumb_bmp);
 620						if (!$bmp_data) {
 621							$this->DebugMessage('$phpthumb_bmp->GD2BMPstring() failed', __FILE__, __LINE__);
 622							return false;
 623						}
 624						header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
 625						echo $bmp_data;
 626					} else {
 627						$this->DebugMessage('new phpthumb_bmp() failed', __FILE__, __LINE__);
 628						return false;
 629					}
 630					break;
 631
 632				case 'ico':
 633					if (!@include_once(dirname(__FILE__) . '/phpthumb.ico.php')) {
 634						$this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.ico.php" which is required for ICO format output', __FILE__, __LINE__);
 635						return false;
 636					}
 637					$phpthumb_ico = new phpthumb_ico();
 638					if (is_object($phpthumb_ico)) {
 639						$arrayOfOutputImages = array($this->gdimg_output);
 640						$ico_data = $phpthumb_ico->GD2ICOstring($arrayOfOutputImages);
 641						unset($phpthumb_ico);
 642						if (!$ico_data) {
 643							$this->DebugMessage('$phpthumb_ico->GD2ICOstring() failed', __FILE__, __LINE__);
 644							return false;
 645						}
 646						header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
 647						echo $ico_data;
 648					} else {
 649						$this->DebugMessage('new phpthumb_ico() failed', __FILE__, __LINE__);
 650						return false;
 651					}
 652					break;
 653
 654				default:
 655					$this->DebugMessage('OutputThumbnail failed because $this->thumbnailFormat "'.$this->thumbnailFormat.'" is not valid', __FILE__, __LINE__);
 656					return false;
 657					break;
 658			}
 659
 660		}
 661		return true;
 662	}
 663
 664
 665	// public:
 666	function CleanUpCacheDirectory() {
 667		$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__);
 668
 669		if (!is_writable($this->config_cache_directory)) {
 670			$this->DebugMessage('CleanUpCacheDirectory() skipped because "'.$this->config_cache_directory.'" is not writable', __FILE__, __LINE__);
 671			return true;
 672		}
 673
 674		// cache status of cache directory for 1 hour to avoid hammering the filesystem functions
 675		$phpThumbCacheStats_filename = $this->config_cache_directory.DIRECTORY_SEPARATOR.'phpThumbCacheStats.txt';
 676		if (file_exists($phpThumbCacheStats_filename) && is_readable($phpThumbCacheStats_filename) && (filemtime($phpThumbCacheStats_filename) >= (time() - 3600))) {
 677			$this->DebugMessage('CleanUpCacheDirectory() skipped because "'.$phpThumbCacheStats_filename.'" is recently modified', __FILE__, __LINE__);
 678			return true;
 679		}
 680		if (!@touch($phpThumbCacheStats_filename)) {
 681			$this->DebugMessage('touch('.$phpThumbCacheStats_filename.') failed', __FILE__, __LINE__);
 682		}
 683
 684		$DeletedKeys = array();
 685		$AllFilesInCacheDirectory = array();
 686		if (($this->config_cache_maxage > 0) || ($this->config_cache_maxsize > 0) || ($this->config_cache_maxfiles > 0)) {
 687			$CacheDirOldFilesAge  = array();
 688			$CacheDirOldFilesSize = array();
 689			$AllFilesInCacheDirectory = phpthumb_functions::GetAllFilesInSubfolders($this->config_cache_directory);
 690			foreach ($AllFilesInCacheDirectory as $fullfilename) {
 691				if (preg_match('#'.preg_quote($this->config_cache_prefix).'#i', $fullfilename) && file_exists($fullfilename)) {
 692					$CacheDirOldFilesAge[$fullfilename] = @fileatime($fullfilename);
 693					if ($CacheDirOldFilesAge[$fullfilename] == 0) {
 694						$CacheDirOldFilesAge[$fullfilename] = @filemtime($fullfilename);
 695					}
 696					$CacheDirOldFilesSize[$fullfilename] = @filesize($fullfilename);
 697				}
 698			}
 699			if (empty($CacheDirOldFilesSize)) {
 700				$this->DebugMessage('CleanUpCacheDirectory() skipped because $CacheDirOldFilesSize is empty (phpthumb_functions::GetAllFilesInSubfolders('.$this->config_cache_directory.') found no files)', __FILE__, __LINE__);
 701				return true;
 702			}
 703			$DeletedKeys['zerobyte'] = array();
 704			foreach ($CacheDirOldFilesSize as $fullfilename => $filesize) {
 705				// purge all zero-size files more than an hour old (to prevent trying to delete just-created and/or in-use files)
 706				$cutofftime = time() - 3600;
 707				if (($filesize == 0) && ($CacheDirOldFilesAge[$fullfilename] < $cutofftime)) {
 708					$this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__);
 709					if (@unlink($fullfilename)) {
 710						$DeletedKeys['zerobyte'][] = $fullfilename;
 711						unset($CacheDirOldFilesSize[$fullfilename]);
 712						unset($CacheDirOldFilesAge[$fullfilename]);
 713					}
 714				}
 715			}
 716			$this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['zerobyte']).' zero-byte files', __FILE__, __LINE__);
 717			asort($CacheDirOldFilesAge);
 718
 719			if ($this->config_cache_maxfiles > 0) {
 720				$TotalCachedFiles = count($CacheDirOldFilesAge);
 721				$DeletedKeys['maxfiles'] = array();
 722				foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
 723					if ($TotalCachedFiles > $this->config_cache_maxfiles) {
 724						$this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__);
 725						if (@unlink($fullfilename)) {
 726							$TotalCachedFiles--;
 727							$DeletedKeys['maxfiles'][] = $fullfilename;
 728						}
 729					} else {
 730						// there are few enough files to keep the rest
 731						break;
 732					}
 733				}
 734				$this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxfiles']).' files based on (config_cache_maxfiles='.$this->config_cache_maxfiles.')', __FILE__, __LINE__);
 735				foreach ($DeletedKeys['maxfiles'] as $fullfilename) {
 736					unset($CacheDirOldFilesAge[$fullfilename]);
 737					unset($CacheDirOldFilesSize[$fullfilename]);
 738				}
 739			}
 740
 741			if ($this->config_cache_maxage > 0) {
 742				$mindate = time() - $this->config_cache_maxage;
 743				$DeletedKeys['maxage'] = array();
 744				foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
 745					if ($filedate > 0) {
 746						if ($filedate < $mindate) {
 747							$this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__);
 748							if (@unlink($fullfilename)) {
 749								$DeletedKeys['maxage'][] = $fullfilename;
 750							}
 751						} else {
 752							// the rest of the files are new enough to keep
 753							break;
 754						}
 755					}
 756				}
 757				$this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxage']).' files based on (config_cache_maxage='.$this->config_cache_maxage.')', __FILE__, __LINE__);
 758				foreach ($DeletedKeys['maxage'] as $fullfilename) {
 759					unset($CacheDirOldFilesAge[$fullfilename]);
 760					unset($CacheDirOldFilesSize[$fullfilename]);
 761				}
 762			}
 763
 764			if ($this->config_cache_maxsize > 0) {
 765				$TotalCachedFileSize = array_sum($CacheDirOldFilesSize);
 766				$DeletedKeys['maxsize'] = array();
 767				foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
 768					if ($TotalCachedFileSize > $this->config_cache_maxsize) {
 769						$this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__);
 770						if (@unlink($fullfilename)) {
 771							$TotalCachedFileSize -= $CacheDirOldFilesSize[$fullfilename];
 772							$DeletedKeys['maxsize'][] = $fullfilename;
 773						}
 774					} else {
 775						// the total filesizes are small enough to keep the rest of the files
 776						break;
 777					}
 778				}
 779				$this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxsize']).' files based on (config_cache_maxsize='.$this->config_cache_maxsize.')', __FILE__, __LINE__);
 780				foreach ($DeletedKeys['maxsize'] as $fullfilename) {
 781					unset($CacheDirOldFilesAge[$fullfilename]);
 782					unset($CacheDirOldFilesSize[$fullfilename]);
 783				}
 784			}
 785
 786		} else {
 787			$this->DebugMessage('skipping CleanUpCacheDirectory() because config set to not use it', __FILE__, __LINE__);
 788		}
 789		$totalpurged = 0;
 790		foreach ($DeletedKeys as $key => $value) {
 791			$totalpurged += count($value);
 792		}
 793		$this->DebugMessage('CleanUpCacheDirectory() purged '.$totalpurged.' files (from '.count($AllFilesInCacheDirectory).') based on config settings', __FILE__, __LINE__);
 794		if ($totalpurged > 0) {
 795			$empty_dirs = array();
 796			foreach ($AllFilesInCacheDirectory as $fullfilename) {
 797				if (is_dir($fullfilename)) {
 798					$empty_dirs[realpath($fullfilename)] = 1;
 799				} else {
 800					unset($empty_dirs[realpath(dirname($fullfilename))]);
 801				}
 802			}
 803			krsort($empty_dirs);
 804			$totalpurgeddirs = 0;
 805			foreach ($empty_dirs as $empty_dir => $dummy) {
 806				if ($empty_dir == $this->config_cache_directory) {
 807					// shouldn't happen, but just in case, don't let it delete actual cache directory
 808					continue;
 809				} elseif (@rmdir($empty_dir)) {
 810					$totalpurgeddirs++;
 811				} else {
 812					$this->DebugMessage('failed to rmdir('.$empty_dir.')', __FILE__, __LINE__);
 813				}
 814			}
 815			$this->DebugMessage('purged '.$totalpurgeddirs.' empty directories', __FILE__, __LINE__);
 816		}
 817		return true;
 818	}
 819
 820	//////////////////////////////////////////////////////////////////////
 821
 822	// private: re-initializator (call between rendering multiple images with one object)
 823	function resetObject() {
 824		$class_vars = get_class_vars(get_class($this));
 825		foreach ($class_vars as $key => $value) {
 826			// do not clobber debug or config info
 827			if (!preg_match('#^(config_|debug|fatalerror)#i', $key)) {
 828				$this->$key = $value;
 829			}
 830		}
 831		$this->phpThumb(); // re-initialize some class variables
 832		return true;
 833	}
 834
 835	//////////////////////////////////////////////////////////////////////
 836
 837	function ResolveSource() {
 838		if (is_resource($this->gdimg_source)) {
 839			$this->DebugMessage('ResolveSource() exiting because is_resource($this->gdimg_source)', __FILE__, __LINE__);
 840			return true;
 841		}
 842		if ($this->rawImageData) {
 843			$this->sourceFilename = null;
 844			$this->DebugMessage('ResolveSource() exiting because $this->rawImageData is set ('.number_format(strlen($this->rawImageData)).' bytes)', __FILE__, __LINE__);
 845			return true;
 846		}
 847		if ($this->sourceFilename) {
 848			$this->sourceFilename = $this->ResolveFilenameToAbsolute($this->sourceFilename);
 849			$this->DebugMessage('$this->sourceFilename set to "'.$this->sourceFilename.'"', __FILE__, __LINE__);
 850		} elseif ($this->src) {
 851			$this->sourceFilename = $this->ResolveFilenameToAbsolute($this->src);
 852			$this->DebugMessage('$this->sourceFilename set to "'.$this->sourceFilename.'" from $this->src ('.$this->src.')', __FILE__, __LINE__);
 853		} else {
 854			return $this->ErrorImage('$this->sourceFilename and $this->src are both empty');
 855		}
 856		if ($this->iswindows && ((substr($this->sourceFilename, 0, 2) == '//') || (substr($this->sourceFilename, 0, 2) == '\\\\'))) {
 857			// Windows \\share\filename.ext
 858		} elseif (preg_match('#^[a-z0-9]+://#i', $this->sourceFilename, $protocol_matches)) {
 859			if (preg_match('#^(f|ht)tps?\://#i', $this->sourceFilename)) {
 860				// URL
 861				if ($this->config_http_user_agent) {
 862					ini_set('user_agent', $this->config_http_user_agent);
 863				}
 864			} else {
 865				return $this->ErrorImage('only FTP and HTTP/HTTPS protocols are allowed, "'.$protocol_matches[1].'" is not');
 866		}
 867		} elseif (!@file_exists($this->sourceFilename)) {
 868			return $this->ErrorImage('"'.$this->sourceFilename.'" does not exist');
 869		} elseif (!@is_file($this->sourceFilename)) {
 870			return $this->ErrorImage('"'.$this->sourceFilename.'" is not a file');
 871		}
 872		return true;
 873	}
 874
 875
 876	function setOutputFormat() {
 877		static $alreadyCalled = false;
 878		if ($this->thumbnailFormat && $alreadyCalled) {
 879			return true;
 880		}
 881		$alreadyCalled = true;
 882
 883		$AvailableImageOutputFormats = array();
 884		$AvailableImageOutputFormats[] = 'text';
 885		if (@is_readable(dirname(__FILE__).'/phpthumb.ico.php')) {
 886			$AvailableImageOutputFormats[] = 'ico';
 887		}
 888		if (@is_readable(dirname(__FILE__).'/phpthumb.bmp.php')) {
 889			$AvailableImageOutputFormats[] = 'bmp';
 890		}
 891
 892		$this->thumbnailFormat = 'ico';
 893
 894		// Set default output format based on what image types are available
 895		if (function_exists('ImageTypes')) {
 896			$imagetypes = ImageTypes();
 897			if ($imagetypes & IMG_WBMP) {
 898				$this->thumbnailFormat         = 'wbmp';
 899				$AvailableImageOutputFormats[] = 'wbmp';
 900			}
 901			if ($imagetypes & IMG_GIF) {
 902				$this->thumbnailFormat         = 'gif';
 903				$AvailableImageOutputFormats[] = 'gif';
 904			}
 905			if ($imagetypes & IMG_PNG) {
 906				$this->thumbnailFormat         = 'png';
 907				$AvailableImageOutputFormats[] = 'png';
 908			}
 909			if ($imagetypes & IMG_JPG) {
 910				$this->thumbnailFormat         = 'jpeg';
 911				$AvailableImageOutputFormats[] = 'jpeg';
 912			}
 913		} else {
 914			//return $this->ErrorImage('ImageTypes() does not exist - GD support might not be enabled?');
 915			$this->DebugMessage('ImageTypes() does not exist - GD support might not be enabled?',  __FILE__, __LINE__);
 916		}
 917		if ($this->ImageMagickVersion()) {
 918			$IMformats = array('jpeg', 'png', 'gif', 'bmp', 'ico', 'wbmp');
 919			$this->DebugMessage('Addding ImageMagick formats to $AvailableImageOutputFormats ('.implode(';', $AvailableImageOutputFormats).')', __FILE__, __LINE__);
 920			foreach ($IMformats as $key => $format) {
 921				$AvailableImageOutputFormats[] = $format;
 922			}
 923		}
 924		$AvailableImageOutputFormats = array_unique($AvailableImageOutputFormats);
 925		$this->DebugMessage('$AvailableImageOutputFormats = array('.implode(';', $AvailableImageOutputFormats).')', __FILE__, __LINE__);
 926
 927		$this->f = preg_replace('#[^a-z]#', '', strtolower($this->f));
 928		if (strtolower($this->config_output_format) == 'jpg') {
 929			$this->config_output_format = 'jpeg';
 930		}
 931		if (strtolower($this->f) == 'jpg') {
 932			$this->f = 'jpeg';
 933		}
 934		if (phpthumb_functions::CaseInsensitiveInArray($this->config_output_format, $AvailableImageOutputFormats)) {
 935			// set output format to config default if that format is available
 936			$this->DebugMessage('$this->thumbnailFormat set to $this->config_output_format "'.strtolower($this->config_output_format).'"', __FILE__, __LINE__);
 937			$this->thumbnailFormat = strtolower($this->config_output_format);
 938		} elseif ($this->config_output_format) {
 939			$this->DebugMessage('$this->thumbnailFormat staying as "'.$this->thumbnailFormat.'" because $this->config_output_format ('.strtolower($this->config_output_format).') is not in $AvailableImageOutputFormats', __FILE__, __LINE__);
 940		}
 941		if ($this->f && (phpthumb_functions::CaseInsensitiveInArray($this->f, $AvailableImageOutputFormats))) {
 942			// override output format if $this->f is set and that format is available
 943			$this->DebugMessage('$this->thumbnailFormat set to $this->f "'.strtolower($this->f).'"', __FILE__, __LINE__);
 944			$this->thumbnailFormat = strtolower($this->f);
 945		} elseif ($this->f) {
 946			$this->DebugMessage('$this->thumbnailFormat staying as "'.$this->thumbnailFormat.'" because $this->f ('.strtolower($this->f).') is not in $AvailableImageOutputFormats', __FILE__, __LINE__);
 947		}
 948
 949		// for JPEG images, quality 1 (worst) to 99 (best)
 950		// quality < 25 is nasty, with not much size savings - not recommended
 951		// problems with 100 - invalid JPEG?
 952		$this->thumbnailQuality = max(1, min(99, ($this->q ? intval($this->q) : 75)));
 953		$this->DebugMessage('$this->thumbnailQuality set to "'.$this->thumbnailQuality.'"', __FILE__, __LINE__);
 954
 955		return true;
 956	}
 957
 958
 959	function setCacheDirectory() {
 960		// resolve cache directory to absolute pathname
 961		$this->DebugMessage('setCacheDirectory() starting with config_cache_directory = "'.$this->config_cache_directory.'"', __FILE__, __LINE__);
 962		if (substr($this->config_cache_directory, 0, 1) == '.') {
 963			if (preg_match('#^(f|ht)tps?\://#i', $this->src)) {
 964				if (!$this->config_cache_disable_warning) {
 965					$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');
 966				}
 967			} elseif ($this->src) {
 968				// resolve relative cache directory to source image
 969				$this->config_cache_directory = dirname($this->ResolveFilenameToAbsolute($this->src)).DIRECTORY_SEPARATOR.$this->config_cache_directory;
 970			} else {
 971				// $this->new is probably set
 972			}
 973		}
 974		if (substr($this->config_cache_directory, -1) == '/') {
 975			$this->config_cache_directory = substr($this->config_cache_directory, 0, -1);
 976		}
 977		if ($this->iswindows) {
 978			$this->config_cache_directory = str_replace('/', DIRECTORY_SEPARATOR, $this->config_cache_directory);
 979		}
 980		if ($this->config_cache_directory) {
 981			$real_cache_path = realpath($this->config_cache_directory);
 982			if (!$real_cache_path) {
 983				$this->DebugMessage('realpath($this->config_cache_directory) failed for "'.$this->config_cache_directory.'"', __FILE__, __LINE__);
 984				if (!is_dir($this->config_cache_directory)) {
 985					$this->DebugMessage('!is_dir('.$this->config_cache_directory.')', __FILE__, __LINE__);
 986				}
 987			}
 988			if ($real_cache_path) {
 989				$this->DebugMessage('setting config_cache_directory to realpath('.$this->config_cache_directory.') = "'.$real_cache_path.'"', __FILE__, __LINE__);
 990				$this->config_cache_directory = $real_cache_path;
 991			}
 992		}
 993		if (!is_dir($this->config_cache_directory)) {
 994			if (!$this->config_cache_disable_warning) {
 995				$this->ErrorImage('$this->config_cache_directory ('.$this->config_cache_directory.') does not exist. Adjust "cache_directory" or "cache_disable_warning" in phpThumb.config.php');
 996			}
 997			$this->DebugMessage('$this->config_cache_directory ('.$this->config_cache_directory.') is not a directory', __FILE__, __LINE__);
 998			$this->config_cache_directory = null;
 999		} elseif (!@is_writable($this->config_cache_directory)) {
1000			$this->DebugMessage('$this->config_cache_directory is not writable ('.$this->config_cache_directory.')', __FILE__, __LINE__);
1001		}
1002
1003		$this->InitializeTempDirSetting();
1004		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)) {
1005			$this->DebugMessage('setting $this->config_temp_directory = $this->config_cache_directory ('.$this->config_cache_directory.')', __FILE__, __LINE__);
1006			$this->config_temp_directory = $this->config_cache_directory;
1007		}
1008		return true;
1009	}
1010
1011	/* Takes the array of path segments up to now, and the next segment (maybe a modifier: empty, . or ..)
1012	   Applies it, adding or removing from $segments as a result. Returns nothing. */
1013	// http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1014	function applyPathSegment(&$segments, $segment) {
1015		if ($segment == '.') {
1016			return; // always remove
1017		}
1018		if ($segment == '') {
1019			$test = array_pop($segments);
1020			if (is_null($test)) {
1021				$segments[] = $segment; // keep the first empty block
1022			} elseif ($test == '') {
1023				$test = array_pop($segments);
1024				if (is_null($test)) {
1025					$segments[] = $test;
1026					$segments[] = $segment; // keep the second one too
1027				} else { // put both back and ignore segment
1028					$segments[] = $test;
1029					$segments[] = $test;
1030				}
1031			} else {
1032				$segments[] = $test; // ignore empty blocks
1033			}
1034		} else {
1035			if ($segment == '..') {
1036				$test = array_pop($segments);
1037				if (is_null($test)) {
1038					$segments[] = $segment;
1039				} elseif ($test == '..') {
1040					$segments[] = $test;
1041					$segments[] = $segment;
1042				} else {
1043					if ($test == '') {
1044						$segments[] = $test;
1045					} // else nothing, remove both
1046				}
1047			} else {
1048				$segments[] = $segment;
1049			}
1050		}
1051	}
1052
1053	/* Takes array of path components, normalizes it: removes empty slots and '.', collapses '..' and folder names.  Returns array. */
1054	// http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1055	function normalizePath($segments) {
1056		$parts = array();
1057		foreach ($segments as $segment) {
1058			$this->applyPathSegment($parts, $segment);
1059		}
1060		return $parts;
1061	}
1062
1063	/* True if the provided path points (without resolving symbolic links) into one of the allowed directories. */
1064	// http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1065	function matchPath($path, $allowed_dirs) {
1066		if (!empty($allowed_dirs)) {
1067			foreach ($allowed_dirs as $one_dir) {
1068				if (preg_match('#^'.preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', realpath($one_dir))).'#', $path)) {
1069					return true;
1070				}
1071			}
1072		}
1073		return false;
1074	}
1075
1076	/* True if the provided path points inside one of open_basedirs (or if open_basedirs are disabled) */
1077	// http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1078	function isInOpenBasedir($path) {
1079		static $open_basedirs = null;
1080		if (is_null($open_basedirs)) {
1081			$ini_text = ini_get('open_basedir');
1082			$this->DebugMessage('open_basedir: "'.$ini_text.'"', __FILE__, __LINE__);
1083			$open_basedirs = array();
1084			if (strlen($ini_text) > 0) {
1085				foreach (preg_split('#[;:]#', $ini_text) as $key => $value) {
1086					$open_basedirs[$key] = realpath($value);
1087				}
1088			}
1089		}
1090		return (empty($open_basedirs) || $this->matchPath($path, $open_basedirs));
1091	}
1092
1093	/* 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. */
1094	// http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
1095	function resolvePath($path, $allowed_dirs) {
1096		$this->DebugMessage('resolvePath: '.$path.' (allowed_dirs: '.print_r($allowed_dirs, true).')', __FILE__, __LINE__);
1097
1098		// add base path to the top of the list
1099		if (!$this->config_allow_src_above_docroot) {
1100			array_unshift($allowed_dirs, realpath($this->config_document_root));
1101		} else {
1102			if (!$this->config_allow_src_above_phpthumb) {
1103				array_unshift($allowed_dirs, realpath(dirname(__FILE__)));
1104			} else {
1105				// no checks are needed, offload the work to realpath and forget about it
1106				$this->DebugMessage('resolvePath: checks disabled, returning '.realpath($path), __FILE__, __LINE__);
1107				return realpath($path);
1108			}
1109		}
1110		if ($path == '') {
1111			return null; // save us trouble
1112		}
1113
1114		do {
1115			$this->DebugMessage('resolvePath: iteration, path='.$path.', base path = '.$allowed_dirs[0], __FILE__, __LINE__);
1116
1117			$parts = array();
1118			// do not use "cleaner" foreach version of this loop as later code relies on both $segments and $i
1119			// http://support.silisoftware.com/phpBB3/viewtopic.php?t=964
1120			$segments = explode(DIRECTORY_SEPARATOR, $path);
1121			for ($i = 0; $i < count($segments); $i++) {
1122	            $this->applyPathSegment($parts, $segments[$i]);
1123				$thispart = implode(DIRECTORY_SEPARATOR, $parts);
1124				if ($this->isInOpenBasedir($thispart)) {
1125					if (is_link($thispart)) {
1126						break;
1127					}
1128				}
1129			}
1130
1131			$this->DebugMessage('resolvePath: stop at component '.$i, __FILE__, __LINE__);
1132			// test the part up to here
1133			$path = implode(DIRECTORY_SEPARATOR, $parts);
1134			$this->DebugMessage('resolvePath: stop at path='.$path, __FILE__, __LINE__);
1135			if (!$this->matchPath($path, $allowed_dirs)) {
1136				$this->DebugMessage('resolvePath: no match, returning null', __FILE__, __LINE__);
1137				return null;
1138			}
1139			if ($i >= count($segments)) { // reached end
1140				$this->DebugMessage('resolvePath: path parsed, over', __FILE__, __LINE__);
1141				break;
1142			}
1143			// else it's symlink, rewrite path
1144			$path = readlink($path);
1145			$this->DebugMessage('resolvePath: symlink matched, target='.$path, __FILE__, __LINE__);
1146
1147			/*
1148			Replace base path with symlink target.
1149			Assuming:
1150			  /www/img/external -> /external
1151			This is allowed:
1152			  GET /www/img/external/../external/test/pic.jpg
1153			This isn't:
1154			  GET /www/img/external/../www/img/pic.jpg
1155			So there's only one base path which is the last symlink target, but any number of stable whitelisted paths.
1156			*/
1157			if ($this->config_auto_allow_symlinks) {
1158				$allowed_dirs[0] = $path;
1159			}
1160			$path = $path.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, array_slice($segments,$i + 1));
1161		} while (true);
1162		return $path;
1163	}
1164
1165	function ResolveFilenameToAbsolute($filename) {
1166		if (empty($filename)) {
1167			return false;
1168		}
1169
1170		if (preg_match('#^[a-z0-9]+\:/{1,2}#i', $filename)) {
1171			// eg: http://host/path/file.jpg (HTTP URL)
1172			// eg: ftp://host/path/file.jpg  (FTP URL)
1173			// eg: data1:/path/file.jpg      (Netware path)
1174
1175			//$AbsoluteFilename = $filename;
1176			return $filename;
1177
1178		} elseif ($this->iswindows && isset($filename{1}) && ($filename{1} == ':')) {
1179
1180			// absolute pathname (Windows)
1181			$AbsoluteFilename = $filename;
1182
1183		} elseif ($this->iswindows && ((substr($filename, 0, 2) == '//') || (substr($filename, 0, 2) == '\\\\'))) {
1184
1185			// absolute pathname (Windows)
1186			$AbsoluteFilename = $filename;
1187
1188		} elseif ($filename{0} == '/') {
1189
1190			if (@is_readable($filename) && !@is_readable($this->config_document_root.$filename)) {
1191
1192				// absolute filename (*nix)
1193				$AbsoluteFilename = $filename;
1194
1195			} elseif (isset($filename{1}) && ($filename{1} == '~')) {
1196
1197				// /~user/path
1198				if ($ApacheLookupURIarray = phpthumb_functions::ApacheLookupURIarray($filename)) {
1199					$AbsoluteFilename = $ApacheLookupURIarray['filename'];
1200				} else {
1201					$AbsoluteFilename = realpath($filename);
1202					if (@is_readable($AbsoluteFilename)) {
1203						$this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "'.$filename.'", but the correct filename ('.$AbsoluteFilename.') seems to have been resolved with realpath($filename)', __FILE__, __LINE__);
1204					} elseif (is_dir(dirname($AbsoluteFilename))) {
1205						$this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "'.dirname($filename).'", but the correct directory ('.dirname($AbsoluteFilename).') seems to have been resolved with realpath(.)', __FILE__, __LINE__);
1206					} else {
1207						return $this->ErrorImage('phpthumb_functions::Apach…

Large files files are truncated, but you can click here to view the full file