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 // phpthumb.functions.php - general support functions       //
9 //                                                         ///
10 //////////////////////////////////////////////////////////////
11 
12 class phpthumb_functions {
13 
14 	public static function is_windows() {
15 		return (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN');
16 	}
17 
18 	public static function user_function_exists($functionname) {
19 		if (function_exists('get_defined_functions')) {
20 			static $get_defined_functions = array();
21 			if (empty($get_defined_functions)) {
22 				$get_defined_functions = get_defined_functions();
23 			}
24 			return in_array(strtolower($functionname), $get_defined_functions['user']);
25 		}
26 		return function_exists($functionname);
27 	}
28 
29 
30 	public static function builtin_function_exists($functionname) {
31 		if (function_exists('get_defined_functions')) {
32 			static $get_defined_functions = array();
33 			if (empty($get_defined_functions)) {
34 				$get_defined_functions = get_defined_functions();
35 			}
36 			return in_array(strtolower($functionname), $get_defined_functions['internal']);
37 		}
38 		return function_exists($functionname);
39 	}
40 
41 
42 	public static function version_compare_replacement_sub($version1, $version2, $operator='') {
43 		// If you specify the third optional operator argument, you can test for a particular relationship.
44 		// The possible operators are: <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne respectively.
45 		// Using this argument, the function will return 1 if the relationship is the one specified by the operator, 0 otherwise.
46 
47 		// If a part contains special version strings these are handled in the following order:
48 		// (any string not found in this list) < (dev) < (alpha = a) < (beta = b) < (RC = rc) < (#) < (pl = p)
49 		static $versiontype_lookup = array();
50 		if (empty($versiontype_lookup)) {
51 			$versiontype_lookup['dev']   = 10001;
52 			$versiontype_lookup['a']     = 10002;
53 			$versiontype_lookup['alpha'] = 10002;
54 			$versiontype_lookup['b']     = 10003;
55 			$versiontype_lookup['beta']  = 10003;
56 			$versiontype_lookup['RC']    = 10004;
57 			$versiontype_lookup['rc']    = 10004;
58 			$versiontype_lookup['#']     = 10005;
59 			$versiontype_lookup['pl']    = 10006;
60 			$versiontype_lookup['p']     = 10006;
61 		}
62 		$version1 = (isset($versiontype_lookup[$version1]) ? $versiontype_lookup[$version1] : $version1);
63 		$version2 = (isset($versiontype_lookup[$version2]) ? $versiontype_lookup[$version2] : $version2);
64 
65 		switch ($operator) {
66 			case '<':
67 			case 'lt':
68 				return (int) ($version1 < $version2);
69 				break;
70 			case '<=':
71 			case 'le':
72 				return (int) ($version1 <= $version2);
73 				break;
74 			case '>':
75 			case 'gt':
76 				return (int) ($version1 > $version2);
77 				break;
78 			case '>=':
79 			case 'ge':
80 				return (int) ($version1 >= $version2);
81 				break;
82 			case '==':
83 			case '=':
84 			case 'eq':
85 				return (int) ($version1 == $version2);
86 				break;
87 			case '!=':
88 			case '<>':
89 			case 'ne':
90 				return (int) ($version1 != $version2);
91 				break;
92 		}
93 		if ($version1 == $version2) {
94 			return 0;
95 		} elseif ($version1 < $version2) {
96 			return -1;
97 		}
98 		return 1;
99 	}
100 
101 
102 	public static function version_compare_replacement($version1, $version2, $operator='') {
103 		if (function_exists('version_compare')) {
104 			// built into PHP v4.1.0+
105 			return version_compare($version1, $version2, $operator);
106 		}
107 
108 		// The function first replaces _, - and + with a dot . in the version strings
109 		$version1 = strtr($version1, '_-+', '...');
110 		$version2 = strtr($version2, '_-+', '...');
111 
112 		// and also inserts dots . before and after any non number so that for example '4.3.2RC1' becomes '4.3.2.RC.1'.
113 		// Then it splits the results like if you were using explode('.',$ver). Then it compares the parts starting from left to right.
114 		$version1 = preg_replace('#([\d]+)([A-Z]+)([\d]+)#i', '$1.$2.$3', $version1);
115 		$version2 = preg_replace('#([\d]+)([A-Z]+)([\d]+)#i', '$1.$2.$3', $version2);
116 
117 		$parts1 = explode('.', $version1);
118 		$parts2 = explode('.', $version1);
119 		$parts_count = max(count($parts1), count($parts2));
120 		for ($i = 0; $i < $parts_count; $i++) {
121 			$comparison = self::version_compare_replacement_sub($version1, $version2, $operator);
122 			if ($comparison != 0) {
123 				return $comparison;
124 			}
125 		}
126 		return 0;
127 	}
128 
129 	public static function escapeshellarg_replacement($arg) {
130 		if (function_exists('escapeshellarg') && !self::FunctionIsDisabled('escapeshellarg')) {
131 			return escapeshellarg($arg);
132 		}
133 		return '\''.str_replace('\'', '\\\'', $arg).'\'';
134 	}
135 
136 	public static function phpinfo_array() {
137 		static $phpinfo_array = array();
138 		if (empty($phpinfo_array)) {
139 			ob_start();
140 			phpinfo();
141 			$phpinfo = ob_get_contents();
142 			ob_end_clean();
143 			$phpinfo_array = explode("\n", $phpinfo);
144 		}
145 		return $phpinfo_array;
146 	}
147 
148 
149 	public static function exif_info() {
150 		static $exif_info = array();
151 		if (empty($exif_info)) {
152 			// based on code by johnschaefer at gmx dot de
153 			// from PHP help on gd_info()
154 			$exif_info = array(
155 				'EXIF Support'           => '',
156 				'EXIF Version'           => '',
157 				'Supported EXIF Version' => '',
158 				'Supported filetypes'    => ''
159 			);
160 			$phpinfo_array = self::phpinfo_array();
161 			foreach ($phpinfo_array as $line) {
162 				$line = trim(strip_tags($line));
163 				foreach ($exif_info as $key => $value) {
164 					if (strpos($line, $key) === 0) {
165 						$newvalue = trim(str_replace($key, '', $line));
166 						$exif_info[$key] = $newvalue;
167 					}
168 				}
169 			}
170 		}
171 		return $exif_info;
172 	}
173 
174 
175 	public static function ImageTypeToMIMEtype($imagetype) {
176 		if (function_exists('image_type_to_mime_type') && ($imagetype >= 1) && ($imagetype <= 18)) {
177 			// PHP v4.3.0+
178 			return image_type_to_mime_type($imagetype);
179 		}
180 		static $image_type_to_mime_type = array(
181 			1  => 'image/gif',                     // IMAGETYPE_GIF
182 			2  => 'image/jpeg',                    // IMAGETYPE_JPEG
183 			3  => 'image/png',                     // IMAGETYPE_PNG
184 			4  => 'application/x-shockwave-flash', // IMAGETYPE_SWF
185 			5  => 'image/psd',                     // IMAGETYPE_PSD
186 			6  => 'image/bmp',                     // IMAGETYPE_BMP
187 			7  => 'image/tiff',                    // IMAGETYPE_TIFF_II (intel byte order)
188 			8  => 'image/tiff',                    // IMAGETYPE_TIFF_MM (motorola byte order)
189 			9  => 'application/octet-stream',      // IMAGETYPE_JPC
190 			10 => 'image/jp2',                     // IMAGETYPE_JP2
191 			11 => 'application/octet-stream',      // IMAGETYPE_JPX
192 			12 => 'application/octet-stream',      // IMAGETYPE_JB2
193 			13 => 'application/x-shockwave-flash', // IMAGETYPE_SWC
194 			14 => 'image/iff',                     // IMAGETYPE_IFF
195 			15 => 'image/vnd.wap.wbmp',            // IMAGETYPE_WBMP
196 			16 => 'image/xbm',                     // IMAGETYPE_XBM
197 			17 => 'image/x-icon',                  // IMAGETYPE_ICO
198 			18 => 'image/webp',                    // IMAGETYPE_WEBP
199 
200 			'gif'  => 'image/gif',                 // IMAGETYPE_GIF
201 			'jpg'  => 'image/jpeg',                // IMAGETYPE_JPEG
202 			'jpeg' => 'image/jpeg',                // IMAGETYPE_JPEG
203 			'png'  => 'image/png',                 // IMAGETYPE_PNG
204 			'bmp'  => 'image/bmp',                 // IMAGETYPE_BMP
205 			'ico'  => 'image/x-icon',              // IMAGETYPE_ICO
206 			'webp' => 'image/webp',                // IMAGETYPE_WEBP
207 		);
208 
209 		return (isset($image_type_to_mime_type[$imagetype]) ? $image_type_to_mime_type[$imagetype] : false);
210 	}
211 
212 
213 	public static function TranslateWHbyAngle($width, $height, $angle) {
214 		if (($angle % 180) == 0) {
215 			return array($width, $height);
216 		}
217 		$newwidth  = (abs(sin(deg2rad($angle))) * $height) + (abs(cos(deg2rad($angle))) * $width);
218 		$newheight = (abs(sin(deg2rad($angle))) * $width)  + (abs(cos(deg2rad($angle))) * $height);
219 		return array($newwidth, $newheight);
220 	}
221 
222 	public static function HexCharDisplay($string) {
223 		$len = strlen($string);
224 		$output = '';
225 		for ($i = 0; $i < $len; $i++) {
226 			$output .= ' 0x'.str_pad(dechex(ord($string[$i])), 2, '0', STR_PAD_LEFT);
227 		}
228 		return $output;
229 	}
230 
231 
232 	public static function IsHexColor($HexColorString) {
233 		return preg_match('#^[0-9A-F]{6}$#i', $HexColorString);
234 	}
235 
236 
237 	public static function ImageColorAllocateAlphaSafe(&$gdimg_hexcolorallocate, $R, $G, $B, $alpha=false) {
238 		if (self::version_compare_replacement(PHP_VERSION, '4.3.2', '>=') && ($alpha !== false)) {
239 			return imagecolorallocatealpha($gdimg_hexcolorallocate, $R, $G, $B, (int) $alpha);
240 		} else {
241 			return imagecolorallocate($gdimg_hexcolorallocate, $R, $G, $B);
242 		}
243 	}
244 
245 	public static function ImageHexColorAllocate(&$gdimg_hexcolorallocate, $HexColorString, $dieOnInvalid=false, $alpha=false) {
246 		if (!is_resource($gdimg_hexcolorallocate) && !(is_object($gdimg_hexcolorallocate) && $gdimg_hexcolorallocate instanceOf \GdImage)) {
247 			die('$gdimg_hexcolorallocate is not a GD resource in ImageHexColorAllocate()');
248 		}
249 		if (self::IsHexColor($HexColorString)) {
250 			$R = hexdec(substr($HexColorString, 0, 2));
251 			$G = hexdec(substr($HexColorString, 2, 2));
252 			$B = hexdec(substr($HexColorString, 4, 2));
253 			return self::ImageColorAllocateAlphaSafe($gdimg_hexcolorallocate, $R, $G, $B, $alpha);
254 		}
255 		if ($dieOnInvalid) {
256 			die('Invalid hex color string: "'.$HexColorString.'"');
257 		}
258 		return imagecolorallocate($gdimg_hexcolorallocate, 0x00, 0x00, 0x00);
259 	}
260 
261 
262 	public static function HexColorXOR($hexcolor) {
263 		return strtoupper(str_pad(dechex(~hexdec($hexcolor) & 0xFFFFFF), 6, '0', STR_PAD_LEFT));
264 	}
265 
266 
267 	public static function GetPixelColor(&$img, $x, $y) {
268 		if (!is_resource($img) && !(is_object($img) && $img instanceOf \GdImage)) {
269 			return false;
270 		}
271 		return @imagecolorsforindex($img, @imagecolorat($img, $x, $y));
272 	}
273 
274 
275 	public static function PixelColorDifferencePercent($currentPixel, $targetPixel) {
276 		$diff = 0;
277 		foreach ($targetPixel as $channel => $currentvalue) {
278 			$diff = max($diff, (max($currentPixel[$channel], $targetPixel[$channel]) - min($currentPixel[$channel], $targetPixel[$channel])) / 255);
279 		}
280 		return $diff * 100;
281 	}
282 
283 	public static function GrayscaleValue($r, $g, $b) {
284 		return round(($r * 0.30) + ($g * 0.59) + ($b * 0.11));
285 	}
286 
287 
288 	public static function GrayscalePixel($OriginalPixel) {
289 		$gray = self::GrayscaleValue($OriginalPixel[ 'red'], $OriginalPixel[ 'green'], $OriginalPixel[ 'blue']);
290 		return array('red'=>$gray, 'green'=>$gray, 'blue'=>$gray);
291 	}
292 
293 
294 	public static function GrayscalePixelRGB($rgb) {
295 		$r = ($rgb >> 16) & 0xFF;
296 		$g = ($rgb >>  8) & 0xFF;
297 		$b =  $rgb        & 0xFF;
298 		return ($r * 0.299) + ($g * 0.587) + ($b * 0.114);
299 	}
300 
301 
302 	public static function ScaleToFitInBox($width, $height, $maxwidth=null, $maxheight=null, $allow_enlarge=true, $allow_reduce=true) {
303 		$maxwidth  = (null === $maxwidth  ? $width  : $maxwidth);
304 		$maxheight = (null === $maxheight ? $height : $maxheight);
305 		$scale_x = 1;
306 		$scale_y = 1;
307 		if (($width > $maxwidth) || ($width < $maxwidth)) {
308 			$scale_x = ($maxwidth / $width);
309 		}
310 		if (($height > $maxheight) || ($height < $maxheight)) {
311 			$scale_y = ($maxheight / $height);
312 		}
313 		$scale = min($scale_x, $scale_y);
314 		if (!$allow_enlarge) {
315 			$scale = min($scale, 1);
316 		}
317 		if (!$allow_reduce) {
318 			$scale = max($scale, 1);
319 		}
320 		return $scale;
321 	}
322 
323 	public static function ImageCopyResampleBicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) {
324 		// ron at korving dot demon dot nl
325 		// http://www.php.net/imagecopyresampled
326 
327 		$scaleX = ($src_w - 1) / $dst_w;
328 		$scaleY = ($src_h - 1) / $dst_h;
329 
330 		$scaleX2 = $scaleX / 2.0;
331 		$scaleY2 = $scaleY / 2.0;
332 
333 		$isTrueColor = imageistruecolor($src_img);
334 
335 		for ($y = $src_y; $y < $src_y + $dst_h; $y++) {
336 			$sY   = $y * $scaleY;
337 			$siY  = (int) $sY;
338 			$siY2 = (int) $sY + $scaleY2;
339 
340 			for ($x = $src_x; $x < $src_x + $dst_w; $x++) {
341 				$sX   = $x * $scaleX;
342 				$siX  = (int) $sX;
343 				$siX2 = (int) $sX + $scaleX2;
344 
345 				if ($isTrueColor) {
346 
347 					$c1 = imagecolorat($src_img, $siX, $siY2);
348 					$c2 = imagecolorat($src_img, $siX, $siY);
349 					$c3 = imagecolorat($src_img, $siX2, $siY2);
350 					$c4 = imagecolorat($src_img, $siX2, $siY);
351 
352 					$r = (( $c1             +  $c2             +  $c3             +  $c4            ) >> 2) & 0xFF0000;
353 					$g = ((($c1 & 0x00FF00) + ($c2 & 0x00FF00) + ($c3 & 0x00FF00) + ($c4 & 0x00FF00)) >> 2) & 0x00FF00;
354 					$b = ((($c1 & 0x0000FF) + ($c2 & 0x0000FF) + ($c3 & 0x0000FF) + ($c4 & 0x0000FF)) >> 2);
355 
356 				} else {
357 
358 					$c1 = imagecolorsforindex($src_img, imagecolorat($src_img, $siX, $siY2));
359 					$c2 = imagecolorsforindex($src_img, imagecolorat($src_img, $siX, $siY));
360 					$c3 = imagecolorsforindex($src_img, imagecolorat($src_img, $siX2, $siY2));
361 					$c4 = imagecolorsforindex($src_img, imagecolorat($src_img, $siX2, $siY));
362 
363 					$r = ($c1['red']   + $c2['red']   + $c3['red']   + $c4['red'] )  << 14;
364 					$g = ($c1['green'] + $c2['green'] + $c3['green'] + $c4['green']) <<  6;
365 					$b = ($c1['blue']  + $c2['blue']  + $c3['blue']  + $c4['blue'] ) >>  2;
366 
367 				}
368 				imagesetpixel($dst_img, $dst_x + $x - $src_x, $dst_y + $y - $src_y, $r+$g+$b);
369 			}
370 		}
371 		return true;
372 	}
373 
374 
375 	public static function ImageCreateFunction($x_size, $y_size) {
376 		$ImageCreateFunction = 'imagecreate';
377 		if (self::gd_version() >= 2.0) {
378 			$ImageCreateFunction = 'imagecreatetruecolor';
379 		}
380 		if (!function_exists($ImageCreateFunction)) {
381 			return phpthumb::ErrorImage($ImageCreateFunction.'() does not exist - no GD support?');
382 		}
383 		if (($x_size <= 0) || ($y_size <= 0)) {
384 			return phpthumb::ErrorImage('Invalid image dimensions: '.$ImageCreateFunction.'('.$x_size.', '.$y_size.')');
385 		}
386 		return $ImageCreateFunction(round($x_size), round($y_size));
387 	}
388 
389 
390 	public static function ImageCopyRespectAlpha(&$dst_im, &$src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $opacity_pct=100) {
391 		$opacipct = $opacity_pct / 100;
392 		for ($x = $src_x; $x < $src_w; $x++) {
393 			for ($y = $src_y; $y < $src_h; $y++) {
394 				$RealPixel    = self::GetPixelColor($dst_im, $dst_x + $x, $dst_y + $y);
395 				$OverlayPixel = self::GetPixelColor($src_im, $x, $y);
396 				$alphapct = $OverlayPixel['alpha'] / 127;
397 				$overlaypct = (1 - $alphapct) * $opacipct;
398 
399 				$newcolor = self::ImageColorAllocateAlphaSafe(
400 					$dst_im,
401 					$RealPixel['alpha'] == 127 ? $OverlayPixel['red'] : ($OverlayPixel['alpha'] == 127 ? $RealPixel['red'] : (round($RealPixel['red'] * (1 - $overlaypct)) + ($OverlayPixel['red'] * $overlaypct))),
402 					$RealPixel['alpha'] == 127 ? $OverlayPixel['green'] : ($OverlayPixel['alpha'] == 127 ? $RealPixel['green'] : (round($RealPixel['green'] * (1 - $overlaypct)) + ($OverlayPixel['green'] * $overlaypct))),
403 					$RealPixel['alpha'] == 127 ? $OverlayPixel['blue'] : ($OverlayPixel['alpha'] == 127 ? $RealPixel['blue'] : (round($RealPixel['blue'] * (1 - $overlaypct)) + ($OverlayPixel['blue'] * $overlaypct))),
404 //					0);
405 					min([$RealPixel['alpha'], floor($OverlayPixel['alpha'] * $opacipct)])
406 				);
407 
408 				imagesetpixel($dst_im, $dst_x + $x, $dst_y + $y, $newcolor);
409 			}
410 		}
411 		return true;
412 	}
413 
414 
415 	public static function ProportionalResize($old_width, $old_height, $new_width=false, $new_height=false) {
416 		$old_aspect_ratio = $old_width / $old_height;
417 		if (($new_width === false) && ($new_height === false)) {
418 			return false;
419 		} elseif ($new_width === false) {
420 			$new_width = $new_height * $old_aspect_ratio;
421 		} elseif ($new_height === false) {
422 			$new_height = $new_width / $old_aspect_ratio;
423 		}
424 		$new_aspect_ratio = $new_width / $new_height;
425 		if ($new_aspect_ratio == $old_aspect_ratio) {
426 			// great, done
427 		} elseif ($new_aspect_ratio < $old_aspect_ratio) {
428 			// limited by width
429 			$new_height = $new_width / $old_aspect_ratio;
430 		} elseif ($new_aspect_ratio > $old_aspect_ratio) {
431 			// limited by height
432 			$new_width = $new_height * $old_aspect_ratio;
433 		}
434 		return array(
435 			(int) round($new_width),
436 			(int) round($new_height)
437 		);
438 	}
439 
440 
441 	public static function FunctionIsDisabled($function) {
442 		static $DisabledFunctions = null;
443 		if (null === $DisabledFunctions) {
444 			$disable_functions_local  = explode(',',     strtolower(@ini_get('disable_functions')));
445 			$disable_functions_global = explode(',', strtolower(@get_cfg_var('disable_functions')));
446 			foreach ($disable_functions_local as $key => $value) {
447 				$DisabledFunctions[trim($value)] = 'local';
448 			}
449 			foreach ($disable_functions_global as $key => $value) {
450 				$DisabledFunctions[trim($value)] = 'global';
451 			}
452 			if (@ini_get('safe_mode')) {
453 				$DisabledFunctions['shell_exec']     = 'local';
454 				$DisabledFunctions['set_time_limit'] = 'local';
455 			}
456 		}
457 		return isset($DisabledFunctions[strtolower($function)]);
458 	}
459 
460 
461 	public static function SafeExec($command) {
462 		static $AllowedExecFunctions = array();
463 		if (empty($AllowedExecFunctions)) {
464 			$AllowedExecFunctions = array('shell_exec'=>true, 'passthru'=>true, 'system'=>true, 'exec'=>true);
465 			foreach ($AllowedExecFunctions as $key => $value) {
466 				$AllowedExecFunctions[$key] = !self::FunctionIsDisabled($key);
467 			}
468 		}
469 		$command .= ' 2>&1'; // force redirect stderr to stdout
470 		foreach ($AllowedExecFunctions as $execfunction => $is_allowed) {
471 			if (!$is_allowed) {
472 				continue;
473 			}
474 			$returnvalue = false;
475 			switch ($execfunction) {
476 				case 'passthru':
477 				case 'system':
478 					ob_start();
479 					$execfunction($command);
480 					$returnvalue = ob_get_contents();
481 					ob_end_clean();
482 					break;
483 
484 				case 'exec':
485 					$output = array();
486 					$lastline = $execfunction($command, $output);
487 					$returnvalue = implode("\n", $output);
488 					break;
489 
490 				case 'shell_exec':
491 					ob_start();
492 					$returnvalue = $execfunction($command);
493 					ob_end_clean();
494 					break;
495 			}
496 			return $returnvalue;
497 		}
498 		return false;
499 	}
500 
501 
502 	public static function ApacheLookupURIarray($filename) {
503 		// apache_lookup_uri() only works when PHP is installed as an Apache module.
504 		if (PHP_SAPI == 'apache') {
505 			//$property_exists_exists = function_exists('property_exists');
506 			$keys = array('status', 'the_request', 'status_line', 'method', 'content_type', 'handler', 'uri', 'filename', 'path_info', 'args', 'boundary', 'no_cache', 'no_local_copy', 'allowed', 'send_bodyct', 'bytes_sent', 'byterange', 'clength', 'unparsed_uri', 'mtime', 'request_time');
507 			if ($apacheLookupURIobject = @apache_lookup_uri($filename)) {
508 				$apacheLookupURIarray = array();
509 				foreach ($keys as $key) {
510 					$apacheLookupURIarray[$key] = @$apacheLookupURIobject->$key;
511 				}
512 				return $apacheLookupURIarray;
513 			}
514 		}
515 		return false;
516 	}
517 
518 
519 	public static function gd_is_bundled() {
520 		static $isbundled = null;
521 		if (null === $isbundled) {
522 			$gd_info = gd_info();
523 			$isbundled = (strpos($gd_info['GD Version'], 'bundled') !== false);
524 		}
525 		return $isbundled;
526 	}
527 
528 
529 	public static function gd_version($fullstring=false) {
530 		static $cache_gd_version = array();
531 		if (empty($cache_gd_version)) {
532 			$gd_info = gd_info();
533 			if (preg_match('#bundled \((.+)\)$#i', $gd_info['GD Version'], $matches)) {
534 				$cache_gd_version[1] = $gd_info['GD Version'];  // e.g. "bundled (2.0.15 compatible)"
535 				$cache_gd_version[0] = (float) $matches[1];     // e.g. "2.0" (not "bundled (2.0.15 compatible)")
536 			} else {
537 				$cache_gd_version[1] = $gd_info['GD Version'];                       // e.g. "1.6.2 or higher"
538 				$cache_gd_version[0] = (float) substr($gd_info['GD Version'], 0, 3); // e.g. "1.6" (not "1.6.2 or higher")
539 			}
540 		}
541 		return $cache_gd_version[ (int) $fullstring ];
542 	}
543 
544 
545 	public static function filesize_remote($remotefile, $timeout=10) {
546 		$size = false;
547 		$parsed_url = self::ParseURLbetter($remotefile);
548 		if ($fp = @fsockopen($parsed_url['host'], $parsed_url['port'], $errno, $errstr, $timeout)) {
549 			fwrite($fp, 'HEAD '.$parsed_url['path'].$parsed_url['query'].' HTTP/1.0'."\r\n".'Host: '.$parsed_url['host']."\r\n\r\n");
550 			if (self::version_compare_replacement(PHP_VERSION, '4.3.0', '>=')) {
551 				stream_set_timeout($fp, $timeout);
552 			}
553 			while (!feof($fp)) {
554 				$headerline = fgets($fp, 4096);
555 				if (preg_match('#^Content-Length: (.*)#i', $headerline, $matches)) {
556 					$size = (int) $matches[ 1];
557 					break;
558 				}
559 			}
560 			fclose ($fp);
561 		}
562 		return $size;
563 	}
564 
565 
566 	public static function filedate_remote($remotefile, $timeout=10) {
567 		$date = false;
568 		$parsed_url = self::ParseURLbetter($remotefile);
569 		if ($fp = @fsockopen($parsed_url['host'], $parsed_url['port'], $errno, $errstr, $timeout)) {
570 			fwrite($fp, 'HEAD '.$parsed_url['path'].$parsed_url['query'].' HTTP/1.0'."\r\n".'Host: '.$parsed_url['host']."\r\n\r\n");
571 			if (self::version_compare_replacement(PHP_VERSION, '4.3.0', '>=')) {
572 				stream_set_timeout($fp, $timeout);
573 			}
574 			while (!feof($fp)) {
575 				$headerline = fgets($fp, 4096);
576 				if (preg_match('#^Last-Modified: (.*)#i', $headerline, $matches)) {
577 					$date = strtotime($matches[1]) - date('Z');
578 					break;
579 				}
580 			}
581 			fclose ($fp);
582 		}
583 		return $date;
584 	}
585 
586 
587 	public static function md5_file_safe($filename) {
588 		// md5_file() doesn't exist in PHP < 4.2.0
589 		if (function_exists('md5_file')) {
590 			return md5_file($filename);
591 		}
592 		if ($fp = @fopen($filename, 'rb')) {
593 			$rawData = '';
594 			do {
595 				$buffer = fread($fp, 8192);
596 				$rawData .= $buffer;
597 			} while (strlen($buffer) > 0);
598 			fclose($fp);
599 			return md5($rawData);
600 		}
601 		return false;
602 	}
603 
604 
605 	public static function nonempty_min() {
606 		$arg_list = func_get_args();
607 		$acceptable = array();
608 		foreach ($arg_list as $arg) {
609 			if ($arg) {
610 				$acceptable[] = $arg;
611 			}
612 		}
613 		return min($acceptable);
614 	}
615 
616 
617 	public static function LittleEndian2String($number, $minbytes=1) {
618 		$intstring = '';
619 		while ($number > 0) {
620 			$intstring .= chr($number & 255);
621 			$number    >>= 8;
622 		}
623 		return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
624 	}
625 
626 	public static function OneOfThese() {
627 		// return the first useful (non-empty/non-zero/non-false) value from those passed
628 		$arg_list = func_get_args();
629 		foreach ($arg_list as $key => $value) {
630 			if ($value) {
631 				return $value;
632 			}
633 		}
634 		return false;
635 	}
636 
637 	public static function CaseInsensitiveInArray($needle, $haystack) {
638 		$needle = strtolower($needle);
639 		foreach ($haystack as $key => $value) {
640 			if (is_array($value)) {
641 				// skip?
642 			} elseif ($needle == strtolower($value)) {
643 				return true;
644 			}
645 		}
646 		return false;
647 	}
648 
649 	public static function URLreadFsock($host, $file, &$errstr, $successonly=true, $port=-1, $timeout=10) {
650 		if (!function_exists('fsockopen') || self::FunctionIsDisabled('fsockopen')) {
651 			$errstr = 'URLreadFsock says: function fsockopen() unavailable';
652 			return false;
653 		}
654 		$port = (int) ($port ? $port : -1); // passing anything as the $port parameter (even empty values like null, false, 0, "") will override the default -1. fsockopen uses -1 as the default port value.
655 		//if ($fp = @fsockopen($host, $port, $errno, $errstr, $timeout)) {
656 		if ($fp = @fsockopen((($port == 443) ? 'ssl://' : '').$host, $port, $errno, $errstr, $timeout)) { // https://github.com/JamesHeinrich/phpThumb/issues/39
657 			$out  = 'GET '.$file.' HTTP/1.0'."\r\n";
658 			$out .= 'Host: '.$host."\r\n";
659 			$out .= 'Connection: Close'."\r\n\r\n";
660 			fwrite($fp, $out);
661 
662 			$isHeader = true;
663 			$data_header = '';
664 			$data_body   = '';
665 			$header_newlocation = '';
666 			while (!feof($fp)) {
667 				$line = fgets($fp, 1024);
668 				if ($isHeader) {
669 					$data_header .= $line;
670 				} else {
671 					$data_body .= $line;
672 				}
673 				if (preg_match('#^HTTP/[\\.\d]+ ([\d]+)\s*(.+)?$#i', rtrim($line), $matches)) {
674 					list( , $errno, $errstr) = $matches;
675 					$errno = (int) $errno;
676 				} elseif (preg_match('#^Location: (.*)$#i', rtrim($line), $matches)) {
677 					$header_newlocation = $matches[1];
678 				}
679 				if ($isHeader && ($line == "\r\n")) {
680 					$isHeader = false;
681 					if ($successonly) {
682 						switch ($errno) {
683 							case 200:
684 								// great, continue
685 								break;
686 
687 							default:
688 								$errstr = $errno.' '.$errstr.($header_newlocation ? '; Location: '.$header_newlocation : '');
689 								fclose($fp);
690 								return false;
691 								break;
692 						}
693 					}
694 				}
695 			}
696 			fclose($fp);
697 			return $data_body;
698 		}
699 		return null;
700 	}
701 
702 	public static function CleanUpURLencoding($url, $queryseperator='&') {
703 		if (!0 === stripos($url, "http") ) {
704 			return $url;
705 		}
706 		$parsed_url = self::ParseURLbetter($url);
707 		$pathelements = explode('/', $parsed_url['path']);
708 		$CleanPathElements = array();
709 		$TranslationMatrix = array(' '=>'%20');
710 		foreach ($pathelements as $key => $pathelement) {
711 			$CleanPathElements[] = strtr($pathelement, $TranslationMatrix);
712 		}
713 		foreach ($CleanPathElements as $key => $value) {
714 			if ($value === '') {
715 				unset($CleanPathElements[$key]);
716 			}
717 		}
718 
719 		$queries = explode($queryseperator, $parsed_url['query']);
720 		$CleanQueries = array();
721 		foreach ($queries as $key => $query) {
722 			@list($param, $value) = explode('=', $query);
723 			$CleanQueries[] = strtr($param, $TranslationMatrix).($value ? '='.strtr($value, $TranslationMatrix) : '');
724 		}
725 		foreach ($CleanQueries as $key => $value) {
726 			if ($value === '') {
727 				unset($CleanQueries[$key]);
728 			}
729 		}
730 
731 		$cleaned_url  = $parsed_url['scheme'].'://';
732 		$cleaned_url .= ($parsed_url['user'] ? $parsed_url['user'].($parsed_url['pass'] ? ':'.$parsed_url['pass'] : '').'@' : '');
733 		$cleaned_url .= $parsed_url['host'];
734 		$cleaned_url .= (($parsed_url['port'] && ($parsed_url['port'] != self::URLschemeDefaultPort($parsed_url['scheme']))) ? ':'.$parsed_url['port'] : '');
735 		$cleaned_url .= '/'.implode('/', $CleanPathElements);
736 		$cleaned_url .= (!empty($CleanQueries) ? '?'.implode($queryseperator, $CleanQueries) : '');
737 		return $cleaned_url;
738 	}
739 
740 	public static function URLschemeDefaultPort($scheme) {
741 		static $schemePort = array(
742 			'ftp'   => 21,
743 			'http'  => 80,
744 			'https' => 443,
745 		);
746 		return ((!empty($scheme) && isset($schemePort[strtolower($scheme)])) ? $schemePort[strtolower($scheme)] : null);
747 	}
748 
749 	public static function ParseURLbetter($url) {
750 		$parsedURL = @parse_url($url);
751 		foreach (array('scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment') as $key) { // ensure all possible array keys are always returned
752 			if (!array_key_exists($key, $parsedURL)) {
753 				$parsedURL[$key] = null;
754 			}
755 		}
756 		$parsedURL['port'] = ($parsedURL['port'] ? $parsedURL['port'] : self::URLschemeDefaultPort($parsedURL['scheme']));
757 		return $parsedURL;
758 	}
759 
760 	public static function SafeURLread($url, &$error, $timeout=10, $followredirects=true) {
761 		$error   = '';
762 		$errstr  = '';
763 		$rawData = '';
764 
765 		$parsed_url = self::ParseURLbetter($url);
766 		$alreadyLookedAtURLs[trim($url)] = true;
767 
768 		while (true) {
769 			$tryagain = false;
770 			$rawData = self::URLreadFsock($parsed_url['host'], $parsed_url['path'].'?'.$parsed_url['query'], $errstr, true, $parsed_url['port'], $timeout);
771 			if ($followredirects && preg_match('#302 [a-z ]+; Location\\: (http.*)#i', $errstr, $matches)) {
772 				$matches[1] = trim(@$matches[1]);
773 				if (!@$alreadyLookedAtURLs[$matches[1]]) {
774 					// loop through and examine new URL
775 					$error .= 'URL "'.$url.'" redirected to "'.$matches[1].'"';
776 
777 					$tryagain = true;
778 					$alreadyLookedAtURLs[$matches[1]] = true;
779 					$parsed_url = self::ParseURLbetter($matches[ 1]);
780 				}
781 			}
782 			if (!$tryagain) {
783 				break;
784 			}
785 		}
786 
787 		if ($rawData === false) {
788 			$error .= 'Error opening "'.$url.'":'."\n\n".$errstr;
789 			return false;
790 		} elseif ($rawData === null) {
791 			// fall through
792 			$error .= 'Error opening "'.$url.'":'."\n\n".$errstr;
793 		} else {
794 			return $rawData;
795 		}
796 
797 		if (function_exists('curl_version') && !self::FunctionIsDisabled('curl_exec')) {
798 			$ch = curl_init();
799 			curl_setopt($ch, CURLOPT_URL, $url);
800 			curl_setopt($ch, CURLOPT_HEADER, false);
801 			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
802 			curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
803 			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
804 			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
805 			curl_setopt($ch, CURLOPT_FOLLOWLOCATION, (bool) $followredirects);
806 			curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
807 			$rawData = curl_exec($ch);
808 			curl_close($ch);
809 			if (strlen($rawData) > 0) {
810 				$error .= 'CURL succeeded ('.strlen($rawData).' bytes); ';
811 				return $rawData;
812 			}
813 			$error .= 'CURL available but returned no data; ';
814 		} else {
815 			$error .= 'CURL unavailable; ';
816 		}
817 
818 		$BrokenURLfopenPHPversions = array('4.4.2');
819 		if (in_array(PHP_VERSION, $BrokenURLfopenPHPversions)) {
820 			$error .= 'fopen(URL) broken in PHP v'. PHP_VERSION .'; ';
821 		} elseif (@ini_get('allow_url_fopen')) {
822 			$rawData = '';
823 			$error_fopen = '';
824 			ob_start();
825 			if ($fp = fopen($url, 'rb')) {
826 				do {
827 					$buffer = fread($fp, 8192);
828 					$rawData .= $buffer;
829 				} while (strlen($buffer) > 0);
830 				fclose($fp);
831 			} else {
832 				$error_fopen .= trim(strip_tags(ob_get_contents()));
833 			}
834 			ob_end_clean();
835 			$error .= $error_fopen;
836 			if (!$error_fopen) {
837 				$error .= '; "allow_url_fopen" succeeded ('.strlen($rawData).' bytes); ';
838 				return $rawData;
839 			}
840 			$error .= '; "allow_url_fopen" enabled but returned no data ('.$error_fopen.'); ';
841 		} else {
842 			$error .= '"allow_url_fopen" disabled; ';
843 		}
844 
845 		return false;
846 	}
847 
848 	public static function EnsureDirectoryExists($dirname, $mask=0755) {
849 		// https://www.php.net/manual/en/ini.core.php#ini.open-basedir says:
850 		// "Under Windows, separate the directories with a semicolon. On all other systems, separate the directories with a colon."
851 		$config_open_basedir = ini_get('open_basedir');
852 		$startoffset = 2; // 1-based counting, first element to left of first directory separator will either be drive letter (Windows) or blank (unix). May be overridden below.
853 		if (self::is_windows()) {
854 			$delimiter = ';';
855 			$case_insensitive_pathname = true;
856 			// unix OSs will always use "/", some Windows configurations you may find "/" used interchangeably with the OS-correct "\", so standardize for ease of comparison
857 			$dirname             = str_replace('/', DIRECTORY_SEPARATOR, $dirname);
858 			$config_open_basedir = str_replace('/', DIRECTORY_SEPARATOR, $config_open_basedir);
859 		} else {
860 			$delimiter = ':';
861 			$case_insensitive_pathname = false;
862 		}
863 		$open_basedirs = explode($delimiter, $config_open_basedir);
864 		foreach ($open_basedirs as $key => $open_basedir) {
865 			if (preg_match('#^'.preg_quote($open_basedir).'#'.($case_insensitive_pathname ? 'i' : ''), $dirname) && (strlen($dirname) > strlen($open_basedir))) {
866 				$startoffset = substr_count($open_basedir, DIRECTORY_SEPARATOR) + 1;
867 				break;
868 			}
869 		}
870 
871 		$directory_elements = explode(DIRECTORY_SEPARATOR, $dirname);
872 		$endoffset = count($directory_elements);
873 		for ($i = $startoffset; $i <= $endoffset; $i++) {
874 			$test_directory = implode(DIRECTORY_SEPARATOR, array_slice($directory_elements, 0, $i));
875 			if (!$test_directory) {
876 				continue;
877 			}
878 			if (!@is_dir($test_directory)) {
879 				if (@file_exists($test_directory)) {
880 					// directory name already exists as a file
881 					return false;
882 				}
883 				@mkdir($test_directory, $mask);
884 				@chmod($test_directory, $mask);
885 				if (!@is_dir($test_directory) || !@is_writable($test_directory)) {
886 					return false;
887 				}
888 			}
889 		}
890 		return true;
891 	}
892 
893 
894 	public static function GetAllFilesInSubfolders($dirname) {
895 		$AllFiles = array();
896 		$dirname = rtrim(realpath($dirname), '/\\');
897 		if ($dirhandle = @opendir($dirname)) {
898 			while (($file = readdir($dirhandle)) !== false) {
899 				$fullfilename = $dirname.DIRECTORY_SEPARATOR.$file;
900 				if (is_file($fullfilename)) {
901 					$AllFiles[] = $fullfilename;
902 				} elseif (is_dir($fullfilename)) {
903 					switch ($file) {
904 						case '.':
905 						case '..':
906 							break;
907 
908 						default:
909 							$AllFiles[] = $fullfilename;
910 							$subfiles = self::GetAllFilesInSubfolders($fullfilename);
911 							foreach ($subfiles as $filename) {
912 								$AllFiles[] = $filename;
913 							}
914 							break;
915 					}
916 				} else {
917 					// ignore?
918 				}
919 			}
920 			closedir($dirhandle);
921 		}
922 		sort($AllFiles);
923 		return array_unique($AllFiles);
924 	}
925 
926 
927 	public static function SanitizeFilename($filename) {
928 		$filename = preg_replace('/[^'.preg_quote(' !#$%^()+,-.;<>=@[]_{}').'a-zA-Z0-9]/', '_', $filename);
929 		if (self::version_compare_replacement(PHP_VERSION, '4.1.0', '>=')) {
930 			$filename = trim($filename, '.');
931 		}
932 		return $filename;
933 	}
934 
935 	public static function PasswordStrength($password) {
936 		$strength = 0;
937 		$strength += strlen(preg_replace('#[^a-z]#',       '', $password)) * 0.5; // lowercase characters are weak
938 		$strength += strlen(preg_replace('#[^A-Z]#',       '', $password)) * 0.8; // uppercase characters are somewhat better
939 		$strength += strlen(preg_replace('#[^0-9]#',       '', $password)) * 1.0; // numbers are somewhat better
940 		$strength += strlen(preg_replace('#[a-zA-Z0-9]#',  '', $password)) * 2.0; // other non-alphanumeric characters are best
941 		return $strength;
942 	}
943 
944 }
945 
946 
947 ////////////// END: class phpthumb_functions //////////////
948 
949 
950 if (!function_exists('gd_info')) {
951 	// built into PHP v4.3.0+ (with bundled GD2 library)
952 	function gd_info() {
953 		static $gd_info = array();
954 		if (empty($gd_info)) {
955 			// based on code by johnschaefer at gmx dot de
956 			// from PHP help on gd_info()
957 			$gd_info = array(
958 				'GD Version'         => '',
959 				'FreeType Support'   => false,
960 				'FreeType Linkage'   => '',
961 				'T1Lib Support'      => false,
962 				'GIF Read Support'   => false,
963 				'GIF Create Support' => false,
964 				'JPG Support'        => false,
965 				'PNG Support'        => false,
966 				'WBMP Support'       => false,
967 				'XBM Support'        => false
968 			);
969 			$phpinfo_array = phpthumb_functions::phpinfo_array();
970 			foreach ($phpinfo_array as $line) {
971 				$line = trim(strip_tags($line));
972 				foreach ($gd_info as $key => $value) {
973 					//if (strpos($line, $key) !== false) {
974 					if (strpos($line, $key) === 0) {
975 						$newvalue = trim(str_replace($key, '', $line));
976 						$gd_info[$key] = $newvalue;
977 					}
978 				}
979 			}
980 			if (empty($gd_info['GD Version'])) {
981 				// probable cause: "phpinfo() disabled for security reasons"
982 				if (function_exists('imagetypes')) {
983 					$imagetypes = imagetypes();
984 					if ($imagetypes & IMG_PNG) {
985 						$gd_info['PNG Support'] = true;
986 					}
987 					if ($imagetypes & IMG_GIF) {
988 						$gd_info['GIF Create Support'] = true;
989 					}
990 					if ($imagetypes & IMG_JPG) {
991 						$gd_info['JPG Support'] = true;
992 					}
993 					if ($imagetypes & IMG_WBMP) {
994 						$gd_info['WBMP Support'] = true;
995 					}
996 				}
997 				// to determine capability of GIF creation, try to use imagecreatefromgif on a 1px GIF
998 				if (function_exists('imagecreatefromgif')) {
999 					if ($tempfilename = phpthumb::phpThumb_tempnam()) {
1000 						if ($fp_tempfile = @fopen($tempfilename, 'wb')) {
1001 							fwrite($fp_tempfile, base64_decode('R0lGODlhAQABAIAAAH//AP///ywAAAAAAQABAAACAUQAOw==')); // very simple 1px GIF file base64-encoded as string
1002 							fclose($fp_tempfile);
1003 							$phpthumb_temp = new phpthumb();
1004 							@chmod($tempfilename, $phpthumb_temp->getParameter('config_file_create_mask'));
1005 
1006 							// if we can convert the GIF file to a GD image then GIF create support must be enabled, otherwise it's not
1007 							$gd_info['GIF Read Support'] = (bool) @imagecreatefromgif($tempfilename);
1008 						}
1009 						unlink($tempfilename);
1010 					}
1011 				}
1012 				if (function_exists('imagecreatetruecolor') && @imagecreatetruecolor(1, 1)) {
1013 					$gd_info['GD Version'] = '2.0.1 or higher (assumed)';
1014 				} elseif (function_exists('imagecreate') && @imagecreate(1, 1)) {
1015 					$gd_info['GD Version'] = '1.6.0 or higher (assumed)';
1016 				}
1017 			}
1018 		}
1019 		return $gd_info;
1020 	}
1021 }
1022 
1023 
1024 if (!function_exists('is_executable')) {
1025 	// in PHP v3+, but v5.0+ for Windows
1026 	function is_executable($filename) {
1027 		// poor substitute, but better than nothing
1028 		return file_exists($filename);
1029 	}
1030 }
1031 
1032 
1033 if (!function_exists('preg_quote')) {
1034 	// included in PHP v3.0.9+, but may be unavailable if not compiled in
1035 	function preg_quote($string, $delimiter='\\') {
1036 		static $preg_quote_array = array();
1037 		if (empty($preg_quote_array)) {
1038 			$escapeables = '.\\+*?[^]$(){}=!<>|:';
1039 			for ($i = 0, $iMax = strlen($escapeables); $i < $iMax; $i++) {
1040 				$strtr_preg_quote[$escapeables[$i]] = $delimiter.$escapeables[$i];
1041 			}
1042 		}
1043 		return strtr($string, $strtr_preg_quote);
1044 	}
1045 }
1046 
1047 if (!function_exists('file_get_contents')) {
1048 	// included in PHP v4.3.0+
1049 	function file_get_contents($filename) {
1050 		if (preg_match('#^(f|ht)tp\://#i', $filename)) {
1051 			return SafeURLread($filename, $error);
1052 		}
1053 		if ($fp = @fopen($filename, 'rb')) {
1054 			$rawData = '';
1055 			do {
1056 				$buffer = fread($fp, 8192);
1057 				$rawData .= $buffer;
1058 			} while (strlen($buffer) > 0);
1059 			fclose($fp);
1060 			return $rawData;
1061 		}
1062 		return false;
1063 	}
1064 }
1065 
1066 
1067 if (!function_exists('file_put_contents')) {
1068 	// included in PHP v5.0.0+
1069 	function file_put_contents($filename, $filedata) {
1070 		if ($fp = @fopen($filename, 'wb')) {
1071 			fwrite($fp, $filedata);
1072 			fclose($fp);
1073 			return true;
1074 		}
1075 		return false;
1076 	}
1077 }
1078 
1079 if (!function_exists('imagealphablending')) {
1080 	// built-in function requires PHP v4.0.6+ *and* GD v2.0.1+
1081 	function imagealphablending(&$img, $blendmode=true) {
1082 		// do nothing, this function is declared here just to
1083 		// prevent runtime errors if GD2 is not available
1084 		return true;
1085 	}
1086 }
1087 
1088 if (!function_exists('imagesavealpha')) {
1089 	// built-in function requires PHP v4.3.2+ *and* GD v2.0.1+
1090 	function imagesavealpha(&$img, $blendmode=true) {
1091 		// do nothing, this function is declared here just to
1092 		// prevent runtime errors if GD2 is not available
1093 		return true;
1094 	}
1095 }
1096