Issues (1065)

Sources/Subs-Graphics.php (2 issues)

1
<?php
2
3
/**
4
 * This file deals with low-level graphics operations performed on images,
5
 * specially as needed for avatars (uploaded avatars), attachments, or
6
 * visual verification images.
7
 * It uses, for gifs at least, Gif Util. For more information on that,
8
 * please see its website.
9
 * TrueType fonts supplied by www.LarabieFonts.com
10
 *
11
 * Simple Machines Forum (SMF)
12
 *
13
 * @package SMF
14
 * @author Simple Machines https://www.simplemachines.org
15
 * @copyright 2025 Simple Machines and individual contributors
16
 * @license https://www.simplemachines.org/about/smf/license.php BSD
17
 *
18
 * @version 2.1.5
19
 */
20
21
if (!defined('SMF'))
22
	die('No direct access...');
23
24
/**
25
 * downloads a file from a url and stores it locally for avatar use by id_member.
26
 * - supports GIF, JPG, PNG, BMP and WBMP formats.
27
 * - detects if GD2 is available.
28
 * - uses resizeImageFile() to resize to max_width by max_height, and saves the result to a file.
29
 * - updates the database info for the member's avatar.
30
 * - returns whether the download and resize was successful.
31
 *
32
 * @param string $url The full path to the temporary file
33
 * @param int $memID The member ID
34
 * @param int $max_width The maximum allowed width for the avatar
35
 * @param int $max_height The maximum allowed height for the avatar
36
 * @return boolean Whether the download and resize was successful.
37
 *
38
 */
39
function downloadAvatar($url, $memID, $max_width, $max_height)
40
{
41
	global $modSettings, $sourcedir, $smcFunc;
42
43
	$ext = !empty($modSettings['avatar_download_png']) ? 'png' : 'jpeg';
44
	$destName = 'avatar_' . $memID . '_' . time() . '.' . $ext;
45
46
	// Just making sure there is a non-zero member.
47
	if (empty($memID))
48
		return false;
49
50
	require_once($sourcedir . '/ManageAttachments.php');
51
	removeAttachments(array('id_member' => $memID));
52
53
	$id_folder = 1;
54
	$avatar_hash = '';
55
	$attachID = $smcFunc['db_insert']('',
56
		'{db_prefix}attachments',
57
		array(
58
			'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-255', 'fileext' => 'string-8', 'size' => 'int',
59
			'id_folder' => 'int',
60
		),
61
		array(
62
			$memID, 1, $destName, $avatar_hash, $ext, 1,
63
			$id_folder,
64
		),
65
		array('id_attach'),
66
		1
67
	);
68
69
	// Retain this globally in case the script wants it.
70
	$modSettings['new_avatar_data'] = array(
71
		'id' => $attachID,
72
		'filename' => $destName,
73
		'type' => 1,
74
	);
75
76
	$destName = $modSettings['custom_avatar_dir'] . '/' . $destName . '.tmp';
77
78
	// Resize it.
79
	if (!empty($modSettings['avatar_download_png']))
80
		$success = resizeImageFile($url, $destName, $max_width, $max_height, 3);
81
	else
82
		$success = resizeImageFile($url, $destName, $max_width, $max_height);
83
84
	// Remove the .tmp extension.
85
	$destName = substr($destName, 0, -4);
86
87
	if ($success)
88
	{
89
		// Remove the .tmp extension from the attachment.
90
		if (rename($destName . '.tmp', $destName))
91
		{
92
			list ($width, $height) = getimagesize($destName);
93
			$mime_type = 'image/' . $ext;
94
95
			// Write filesize in the database.
96
			$smcFunc['db_query']('', '
97
				UPDATE {db_prefix}attachments
98
				SET size = {int:filesize}, width = {int:width}, height = {int:height},
99
					mime_type = {string:mime_type}
100
				WHERE id_attach = {int:current_attachment}',
101
				array(
102
					'filesize' => filesize($destName),
103
					'width' => (int) $width,
104
					'height' => (int) $height,
105
					'current_attachment' => $attachID,
106
					'mime_type' => $mime_type,
107
				)
108
			);
109
			return true;
110
		}
111
		else
112
			return false;
113
	}
114
	else
115
	{
116
		$smcFunc['db_query']('', '
117
			DELETE FROM {db_prefix}attachments
118
			WHERE id_attach = {int:current_attachment}',
119
			array(
120
				'current_attachment' => $attachID,
121
			)
122
		);
123
124
		@unlink($destName . '.tmp');
125
		return false;
126
	}
127
}
128
129
/**
130
 * Create a thumbnail of the given source.
131
 *
132
 * @uses resizeImageFile() function to achieve the resize.
133
 *
134
 * @param string $source The name of the source image
135
 * @param int $max_width The maximum allowed width
136
 * @param int $max_height The maximum allowed height
137
 * @return boolean Whether the thumbnail creation was successful.
138
 */
139
function createThumbnail($source, $max_width, $max_height)
140
{
141
	global $modSettings;
142
143
	$destName = $source . '_thumb.tmp';
144
145
	// Do the actual resize.
146
	if (!empty($modSettings['attachment_thumb_png']))
147
		$success = resizeImageFile($source, $destName, $max_width, $max_height, 3);
148
	else
149
		$success = resizeImageFile($source, $destName, $max_width, $max_height);
150
151
	// Okay, we're done with the temporary stuff.
152
	$destName = substr($destName, 0, -4);
153
154
	if ($success && @rename($destName . '.tmp', $destName))
155
		return true;
156
	else
157
	{
158
		@unlink($destName . '.tmp');
159
		@touch($destName);
160
		return false;
161
	}
162
}
163
164
/**
165
 * Used to re-econodes an image to a specified image format
166
 * - creates a copy of the file at the same location as fileName.
167
 * - the file would have the format preferred_format if possible, otherwise the default format is jpeg.
168
 * - the function makes sure that all non-essential image contents are disposed.
169
 *
170
 * @param string $fileName The path to the file
171
 * @param int $preferred_format The preferred format - 0 to automatically determine, 1 for gif, 2 for jpg, 3 for png, 6 for bmp and 15 for wbmp
172
 * @return boolean Whether the reencoding was successful
173
 */
174
function reencodeImage($fileName, $preferred_format = 0)
175
{
176
	if (!resizeImageFile($fileName, $fileName . '.tmp', null, null, $preferred_format))
177
	{
178
		if (file_exists($fileName . '.tmp'))
179
			unlink($fileName . '.tmp');
180
181
		return false;
182
	}
183
184
	if (!unlink($fileName))
185
		return false;
186
187
	if (!rename($fileName . '.tmp', $fileName))
188
		return false;
189
190
	return true;
191
}
192
193
/**
194
 * Searches through the file to see if there's potentially harmful non-binary content.
195
 * - if extensiveCheck is true, searches for asp/php short tags as well.
196
 *
197
 * @param string $fileName The path to the file
198
 * @param bool $extensiveCheck Whether to perform extensive checks
199
 * @return bool Whether the image appears to be safe
200
 */
201
function checkImageContents($fileName, $extensiveCheck = false)
202
{
203
	$fp = fopen($fileName, 'rb');
204
	if (!$fp)
205
		fatal_lang_error('attach_timeout');
206
207
	$prev_chunk = '';
208
	while (!feof($fp))
209
	{
210
		$cur_chunk = fread($fp, 8192);
211
212
		// Though not exhaustive lists, better safe than sorry.
213
		if (!empty($extensiveCheck))
214
		{
215
			// Paranoid check.  Use this if you have reason to distrust your host's security config.
216
			// Will result in MANY false positives, and is not suitable for photography sites.
217
			if (preg_match('~(iframe|\\<\\?|\\<%|html|eval|body|script\W|(?-i)[CFZ]WS[\x01-\x0E])~i', $prev_chunk . $cur_chunk) === 1)
218
			{
219
				fclose($fp);
220
				return false;
221
			}
222
		}
223
		else
224
		{
225
			// Check for potential infection - focus on clues for inline php & flash.
226
			// Will result in significantly fewer false positives than the paranoid check.
227
			if (preg_match('~(\\<\\?php\s|(?-i)[CFZ]WS[\x01-\x0E])~i', $prev_chunk . $cur_chunk) === 1)
228
			{
229
				fclose($fp);
230
				return false;
231
			}
232
		}
233
		$prev_chunk = $cur_chunk;
234
	}
235
	fclose($fp);
236
237
	return true;
238
}
239
240
/**
241
 * Searches through an SVG file to see if there's potentially harmful content.
242
 *
243
 * @param string $fileName The path to the file.
244
 * @return bool Whether the image appears to be safe.
245
 */
246
function checkSvgContents($fileName)
247
{
248
	$fp = fopen($fileName, 'rb');
249
	if (!$fp)
250
		fatal_lang_error('attach_timeout');
251
252
	$patterns = array(
253
		// No external or embedded scripts allowed.
254
		'/<(\S*:)?script\b/i',
255
		'/\b(\S:)?href\s*=\s*["\']\s*javascript:/i',
256
257
		// No SVG event attributes allowed, since they execute scripts.
258
		'/\bon\w+\s*=\s*["\']/',
259
		'/<(\S*:)?set\b[^>]*\battributeName\s*=\s*(["\'])\s*on\w+\\1/i',
260
261
		// No XML Events allowed, since they execute scripts.
262
		'~\bhttp://www\.w3\.org/2001/xml-events\b~i',
263
264
		// No data URIs allowed, since they contain arbitrary data.
265
		'/\b(\S*:)?href\s*=\s*["\']\s*data:/i',
266
267
		// No foreignObjects allowed, since they allow embedded HTML.
268
		'/<(\S*:)?foreignObject\b/i',
269
270
		// No custom entities allowed, since they can be used for entity
271
		// recursion attacks.
272
		'/<!ENTITY\b/',
273
274
		// Embedded external images can't have custom cross-origin rules.
275
		'/<\b(\S*:)?image\b[^>]*\bcrossorigin\s*=/',
276
277
		// No embedded PHP tags allowed.
278
		// Harmless if the SVG is just the src of an img element, but very bad
279
		// if the SVG is embedded inline into the HTML document.
280
		'/<[?](php|=|\s)/i',
281
	);
282
283
	$prev_chunk = '';
284
	while (!feof($fp))
285
	{
286
		$cur_chunk = fread($fp, 8192);
287
288
		foreach ($patterns as $pattern)
289
		{
290
			if (preg_match($pattern, $prev_chunk . $cur_chunk))
291
			{
292
				fclose($fp);
293
				return false;
294
			}
295
		}
296
297
		$prev_chunk = $cur_chunk;
298
	}
299
	fclose($fp);
300
301
	return true;
302
}
303
304
/**
305
 * Sets a global $gd2 variable needed by some functions to determine
306
 * whether the GD2 library is present.
307
 *
308
 * @return bool Whether or not GD1 is available.
309
 */
310
function checkGD()
311
{
312
	global $gd2;
313
314
	// Check to see if GD is installed and what version.
315
	if (($extensionFunctions = get_extension_funcs('gd')) === false)
316
		return false;
317
318
	// Also determine if GD2 is installed and store it in a global.
319
	$gd2 = in_array('imagecreatetruecolor', $extensionFunctions) && function_exists('imagecreatetruecolor');
320
321
	return true;
322
}
323
324
/**
325
 * Checks whether the Imagick class is present.
326
 *
327
 * @return bool Whether or not the Imagick extension is available.
328
 */
329
function checkImagick()
330
{
331
	return class_exists('Imagick', false);
332
}
333
334
/**
335
 * Checks whether the MagickWand extension is present.
336
 *
337
 * @return bool Whether or not the MagickWand extension is available.
338
 */
339
function checkMagickWand()
340
{
341
	return function_exists('newMagickWand');
342
}
343
344
/**
345
 * See if we have enough memory to thumbnail an image
346
 *
347
 * @param array $sizes image size
348
 * @return bool Whether we do
349
 */
350
function imageMemoryCheck($sizes)
351
{
352
	global $modSettings;
353
354
	// doing the old 'set it and hope' way?
355
	if (empty($modSettings['attachment_thumb_memory']))
356
	{
357
		setMemoryLimit('128M');
358
		return true;
359
	}
360
361
	// Determine the memory requirements for this image, note: if you want to use an image formula W x H x bits/8 x channels x Overhead factor
362
	// you will need to account for single bit images as GD expands them to an 8 bit and will greatly overun the calculated value.  The 5 is
363
	// simply a shortcut of 8bpp, 3 channels, 1.66 overhead
364
	$needed_memory = ($sizes[0] * $sizes[1] * 5);
365
366
	// if we need more, lets try to get it
367
	return setMemoryLimit($needed_memory, true);
368
}
369
370
/**
371
 * Resizes an image from a remote location or a local file.
372
 * Puts the resized image at the destination location.
373
 * The file would have the format preferred_format if possible,
374
 * otherwise the default format is jpeg.
375
 *
376
 * @param string $source The path to the source image
377
 * @param string $destination The path to the destination image
378
 * @param int $max_width The maximum allowed width
379
 * @param int $max_height The maximum allowed height
380
 * @param int $preferred_format - The preferred format (0 to use jpeg, 1 for gif, 2 to force jpeg, 3 for png, 6 for bmp and 15 for wbmp)
381
 * @return bool Whether it succeeded.
382
 */
383
function resizeImageFile($source, $destination, $max_width, $max_height, $preferred_format = 0)
384
{
385
	global $sourcedir;
386
387
	// Nothing to do without GD or IM/MW
388
	if (!checkGD() && !checkImagick() && !checkMagickWand())
389
		return false;
390
391
	static $default_formats = array(
392
		'1' => 'gif',
393
		'2' => 'jpeg',
394
		'3' => 'png',
395
		'6' => 'bmp',
396
		'15' => 'wbmp',
397
		'18' => 'webp'
398
	);
399
400
	// Get the image file, we have to work with something after all
401
	$fp_destination = fopen($destination, 'wb');
402
	if ($fp_destination && (substr($source, 0, 7) == 'http://' || substr($source, 0, 8) == 'https://'))
0 ignored issues
show
$fp_destination is of type resource, thus it always evaluated to false.
Loading history...
403
	{
404
		$fileContents = fetch_web_data($source);
405
406
		$mime_valid = check_mime_type($fileContents, implode('|', array_map('image_type_to_mime_type', array_keys($default_formats))));
407
		if (empty($mime_valid))
408
			return false;
409
410
		fwrite($fp_destination, $fileContents);
411
		fclose($fp_destination);
412
413
		$sizes = @getimagesize($destination);
414
	}
415
	elseif ($fp_destination)
416
	{
417
		$mime_valid = check_mime_type($source, implode('|', array_map('image_type_to_mime_type', array_keys($default_formats))), true);
418
		if (empty($mime_valid))
419
			return false;
420
421
		$sizes = @getimagesize($source);
422
423
		$fp_source = fopen($source, 'rb');
424
		if ($fp_source !== false)
425
		{
426
			while (!feof($fp_source))
427
				fwrite($fp_destination, fread($fp_source, 8192));
428
			fclose($fp_source);
429
		}
430
		else
431
			$sizes = array(-1, -1, -1);
432
		fclose($fp_destination);
433
	}
434
435
	// We can't get to the file. or a previous getimagesize failed.
436
	if (empty($sizes))
437
		$sizes = array(-1, -1, -1);
438
439
	// See if we have -or- can get the needed memory for this operation
440
	// ImageMagick isn't subject to PHP's memory limits :)
441
	if (!(checkIMagick() || checkMagickWand()) && checkGD() && !imageMemoryCheck($sizes))
442
		return false;
443
444
	// A known and supported format?
445
	// @todo test PSD and gif.
446
	if ((checkImagick() || checkMagickWand()) && isset($default_formats[$sizes[2]]))
447
	{
448
		return resizeImage(null, $destination, null, null, $max_width, $max_height, true, $preferred_format);
449
	}
450
	elseif (checkGD() && isset($default_formats[$sizes[2]]) && function_exists('imagecreatefrom' . $default_formats[$sizes[2]]))
451
	{
452
		$imagecreatefrom = 'imagecreatefrom' . $default_formats[$sizes[2]];
453
		if ($src_img = @$imagecreatefrom($destination))
454
		{
455
			return resizeImage($src_img, $destination, imagesx($src_img), imagesy($src_img), $max_width === null ? imagesx($src_img) : $max_width, $max_height === null ? imagesy($src_img) : $max_height, true, $preferred_format);
456
		}
457
	}
458
459
	return false;
460
}
461
462
/**
463
 * Resizes src_img proportionally to fit within max_width and max_height limits
464
 * if it is too large.
465
 * If GD2 is present, it'll use it to achieve better quality.
466
 * It saves the new image to destination_filename, as preferred_format
467
 * if possible, default is jpeg.
468
 *
469
 * Uses Imagemagick (IMagick or MagickWand extension) or GD
470
 *
471
 * @param resource $src_img The source image
472
 * @param string $destName The path to the destination image
473
 * @param int $src_width The width of the source image
474
 * @param int $src_height The height of the source image
475
 * @param int $max_width The maximum allowed width
476
 * @param int $max_height The maximum allowed height
477
 * @param bool $force_resize = false Whether to forcibly resize it
478
 * @param int $preferred_format - 1 for gif, 2 for jpeg, 3 for png, 6 for bmp or 15 for wbmp
479
 * @return bool Whether the resize was successful
480
 */
481
function resizeImage($src_img, $destName, $src_width, $src_height, $max_width, $max_height, $force_resize = false, $preferred_format = 0)
482
{
483
	global $gd2, $modSettings;
484
485
	$orientation = 0;
486
	if (function_exists('exif_read_data') && ($exif_data = @exif_read_data($destName)) !== false && !empty($exif_data['Orientation']))
487
		$orientation = $exif_data['Orientation'];
488
489
	if (checkImagick() || checkMagickWand())
490
	{
491
		static $default_formats = array(
492
			'1' => 'gif',
493
			'2' => 'jpeg',
494
			'3' => 'png',
495
			'6' => 'bmp',
496
			'15' => 'wbmp',
497
			'18' => 'webp'
498
		);
499
		$preferred_format = empty($preferred_format) || !isset($default_formats[$preferred_format]) ? 2 : $preferred_format;
500
501
		if (checkImagick())
502
		{
503
			$imagick = New Imagick($destName);
504
			$src_width = empty($src_width) ? $imagick->getImageWidth() : $src_width;
505
			$src_height = empty($src_height) ? $imagick->getImageHeight() : $src_height;
506
			$dest_width = empty($max_width) ? $src_width : $max_width;
507
			$dest_height = empty($max_height) ? $src_height : $max_height;
508
509
			if ($default_formats[$preferred_format] == 'jpeg')
510
				$imagick->setCompressionQuality(!empty($modSettings['avatar_jpeg_quality']) ? $modSettings['avatar_jpeg_quality'] : 82);
511
512
			$imagick->setImageFormat($default_formats[$preferred_format]);
513
			$imagick->resizeImage($dest_width, $dest_height, Imagick::FILTER_LANCZOS, 1, true);
514
515
			if ($orientation > 1 && $preferred_format == 3)
516
			{
517
				if (in_array($orientation, [3, 4]))
518
					$imagick->rotateImage('#00000000', 180);
519
				elseif (in_array($orientation, [5, 6]))
520
					$imagick->rotateImage('#00000000', 90);
521
				elseif (in_array($orientation, [7, 8]))
522
					$imagick->rotateImage('#00000000', 270);
523
524
				if (in_array($orientation, [2, 4, 5, 7]))
525
					$imagick->flopImage();
526
			}
527
			$success = $imagick->writeImage($destName);
528
		}
529
		else
530
		{
531
			$magick_wand = newMagickWand();
532
			MagickReadImage($magick_wand, $destName);
533
			$src_width = empty($src_width) ? MagickGetImageWidth($magick_wand) : $src_width;
534
			$src_height = empty($src_height) ? MagickGetImageSize($magick_wand) : $src_height;
535
			$dest_width = empty($max_width) ? $src_width : $max_width;
536
			$dest_height = empty($max_height) ? $src_height : $max_height;
537
538
			if ($default_formats[$preferred_format] == 'jpeg')
539
				MagickSetCompressionQuality($magick_wand, !empty($modSettings['avatar_jpeg_quality']) ? $modSettings['avatar_jpeg_quality'] : 82);
0 ignored issues
show
The function MagickSetCompressionQuality was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

539
				/** @scrutinizer ignore-call */ 
540
    MagickSetCompressionQuality($magick_wand, !empty($modSettings['avatar_jpeg_quality']) ? $modSettings['avatar_jpeg_quality'] : 82);
Loading history...
540
541
			MagickSetImageFormat($magick_wand, $default_formats[$preferred_format]);
542
			MagickResizeImage($magick_wand, $dest_width, $dest_height, MW_LanczosFilter, 1, true);
543
544
			if ($orientation > 1)
545
			{
546
				if (in_array($orientation, [3, 4]))
547
					MagickResizeImage($magick_wand, NewPixelWand('white'), 180);
548
				elseif (in_array($orientation, [5, 6]))
549
					MagickResizeImage($magick_wand, NewPixelWand('white'), 90);
550
				elseif (in_array($orientation, [7, 8]))
551
					MagickResizeImage($magick_wand, NewPixelWand('white'), 270);
552
553
				if (in_array($orientation, [2, 4, 5, 7]))
554
					MagickFlopImage($magick_wand);
555
			}
556
			$success = MagickWriteImage($magick_wand, $destName);
557
		}
558
559
		return !empty($success);
560
	}
561
	elseif (checkGD())
562
	{
563
		$success = false;
564
565
		// Determine whether to resize to max width or to max height (depending on the limits.)
566
		if (!empty($max_width) || !empty($max_height))
567
		{
568
			if (!empty($max_width) && (empty($max_height) || round($src_height * $max_width / $src_width) <= $max_height))
569
			{
570
				$dst_width = $max_width;
571
				$dst_height = round($src_height * $max_width / $src_width);
572
			}
573
			elseif (!empty($max_height))
574
			{
575
				$dst_width = round($src_width * $max_height / $src_height);
576
				$dst_height = $max_height;
577
			}
578
579
			// Don't bother resizing if it's already smaller...
580
			if (!empty($dst_width) && !empty($dst_height) && ($dst_width < $src_width || $dst_height < $src_height || $force_resize))
581
			{
582
				// (make a true color image, because it just looks better for resizing.)
583
				if ($gd2)
584
				{
585
					$dst_img = imagecreatetruecolor($dst_width, $dst_height);
586
587
					// Deal nicely with a PNG - because we can.
588
					if ((!empty($preferred_format)) && ($preferred_format == 3))
589
					{
590
						imagealphablending($dst_img, false);
591
						if (function_exists('imagesavealpha'))
592
							imagesavealpha($dst_img, true);
593
					}
594
				}
595
				else
596
					$dst_img = imagecreate($dst_width, $dst_height);
597
598
				// Resize it!
599
				if ($gd2)
600
					imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
601
				else
602
					imagecopyresamplebicubic($dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
603
			}
604
			else
605
				$dst_img = $src_img;
606
		}
607
		else
608
			$dst_img = $src_img;
609
610
		if ($orientation > 1)
611
		{
612
			if (in_array($orientation, [3, 4]))
613
				$dst_img = imagerotate($dst_img, 180, 0);
614
			elseif (in_array($orientation, [5, 6]))
615
				$dst_img = imagerotate($dst_img, 270, 0);
616
			elseif (in_array($orientation, [7, 8]))
617
				$dst_img = imagerotate($dst_img, 90, 0);
618
619
			if (in_array($orientation, [2, 4, 5, 7]))
620
				imageflip($dst_img, IMG_FLIP_HORIZONTAL);
621
		}
622
623
		// Save the image as ...
624
		if (!empty($preferred_format) && ($preferred_format == 3) && function_exists('imagepng'))
625
			$success = imagepng($dst_img, $destName);
626
		elseif (!empty($preferred_format) && ($preferred_format == 1) && function_exists('imagegif'))
627
			$success = imagegif($dst_img, $destName);
628
		elseif (!empty($preferred_format) && ($preferred_format == 6) && function_exists('imagebmp'))
629
			$success = imagebmp($dst_img, $destName);
630
		elseif (!empty($preferred_format) && ($preferred_format == 15) && function_exists('imagewbmp'))
631
			$success = imagewbmp($dst_img, $destName);
632
		elseif (!empty($preferred_format) && ($preferred_format == 18) && function_exists('imagewebp'))
633
			$success = imagewebp($dst_img, $destName);
634
		elseif (function_exists('imagejpeg'))
635
			$success = imagejpeg($dst_img, $destName, !empty($modSettings['avatar_jpeg_quality']) ? $modSettings['avatar_jpeg_quality'] : 82);
636
637
		// Free the memory.
638
		imagedestroy($src_img);
639
		if ($dst_img != $src_img)
640
			imagedestroy($dst_img);
641
642
		return $success;
643
	}
644
	else
645
		// Without GD, no image resizing at all.
646
		return false;
647
}
648
649
/**
650
 * Copy image.
651
 * Used when imagecopyresample() is not available.
652
 *
653
 * @param resource $dst_img The destination image - a GD image resource
654
 * @param resource $src_img The source image - a GD image resource
655
 * @param int $dst_x The "x" coordinate of the destination image
656
 * @param int $dst_y The "y" coordinate of the destination image
657
 * @param int $src_x The "x" coordinate of the source image
658
 * @param int $src_y The "y" coordinate of the source image
659
 * @param int $dst_w The width of the destination image
660
 * @param int $dst_h The height of the destination image
661
 * @param int $src_w The width of the destination image
662
 * @param int $src_h The height of the destination image
663
 */
664
function imagecopyresamplebicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h)
665
{
666
	$palsize = imagecolorstotal($src_img);
667
	for ($i = 0; $i < $palsize; $i++)
668
	{
669
		$colors = imagecolorsforindex($src_img, $i);
670
		imagecolorallocate($dst_img, $colors['red'], $colors['green'], $colors['blue']);
671
	}
672
673
	$scaleX = ($src_w - 1) / $dst_w;
674
	$scaleY = ($src_h - 1) / $dst_h;
675
676
	$scaleX2 = (int) $scaleX / 2;
677
	$scaleY2 = (int) $scaleY / 2;
678
679
	for ($j = $src_y; $j < $dst_h; $j++)
680
	{
681
		$sY = (int) $j * $scaleY;
682
		$y13 = $sY + $scaleY2;
683
684
		for ($i = $src_x; $i < $dst_w; $i++)
685
		{
686
			$sX = (int) $i * $scaleX;
687
			$x34 = $sX + $scaleX2;
688
689
			$color1 = imagecolorsforindex($src_img, imagecolorat($src_img, $sX, $y13));
690
			$color2 = imagecolorsforindex($src_img, imagecolorat($src_img, $sX, $sY));
691
			$color3 = imagecolorsforindex($src_img, imagecolorat($src_img, $x34, $y13));
692
			$color4 = imagecolorsforindex($src_img, imagecolorat($src_img, $x34, $sY));
693
694
			$red = ($color1['red'] + $color2['red'] + $color3['red'] + $color4['red']) / 4;
695
			$green = ($color1['green'] + $color2['green'] + $color3['green'] + $color4['green']) / 4;
696
			$blue = ($color1['blue'] + $color2['blue'] + $color3['blue'] + $color4['blue']) / 4;
697
698
			$color = imagecolorresolve($dst_img, $red, $green, $blue);
699
			if ($color == -1)
700
			{
701
				if ($palsize++ < 256)
702
					imagecolorallocate($dst_img, $red, $green, $blue);
703
				$color = imagecolorclosest($dst_img, $red, $green, $blue);
704
			}
705
706
			imagesetpixel($dst_img, $i + $dst_x - $src_x, $j + $dst_y - $src_y, $color);
707
		}
708
	}
709
}
710
711
if (!function_exists('imagecreatefrombmp'))
712
{
713
	/**
714
	 * It is set only if it doesn't already exist (for forwards compatiblity.)
715
	 * It only supports uncompressed bitmaps.
716
	 *
717
	 * @param string $filename The name of the file
718
	 * @return resource An image identifier representing the bitmap image
719
	 * obtained from the given filename.
720
	 */
721
	function imagecreatefrombmp($filename)
722
	{
723
		global $gd2;
724
725
		$fp = fopen($filename, 'rb');
726
727
		$errors = error_reporting(0);
728
729
		$header = unpack('vtype/Vsize/Vreserved/Voffset', fread($fp, 14));
730
		$info = unpack('Vsize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vncolor/Vcolorimportant', fread($fp, 40));
731
732
		if ($header['type'] != 0x4D42)
733
			return false;
734
735
		if ($gd2)
736
			$dst_img = imagecreatetruecolor($info['width'], $info['height']);
737
		else
738
			$dst_img = imagecreate($info['width'], $info['height']);
739
740
		$palette_size = $header['offset'] - 54;
741
		$info['ncolor'] = $palette_size / 4;
742
743
		$palette = array();
744
745
		$palettedata = fread($fp, $palette_size);
746
		$n = 0;
747
		for ($j = 0; $j < $palette_size; $j++)
748
		{
749
			$b = ord($palettedata[$j++]);
750
			$g = ord($palettedata[$j++]);
751
			$r = ord($palettedata[$j++]);
752
753
			$palette[$n++] = imagecolorallocate($dst_img, $r, $g, $b);
754
		}
755
756
		$scan_line_size = ($info['bits'] * $info['width'] + 7) >> 3;
757
		$scan_line_align = $scan_line_size & 3 ? 4 - ($scan_line_size & 3) : 0;
758
759
		for ($y = 0, $l = $info['height'] - 1; $y < $info['height']; $y++, $l--)
760
		{
761
			fseek($fp, $header['offset'] + ($scan_line_size + $scan_line_align) * $l);
762
			$scan_line = fread($fp, $scan_line_size);
763
764
			if (strlen($scan_line) < $scan_line_size)
765
				continue;
766
767
			if ($info['bits'] == 32)
768
			{
769
				$x = 0;
770
				for ($j = 0; $j < $scan_line_size; $x++)
771
				{
772
					$b = ord($scan_line[$j++]);
773
					$g = ord($scan_line[$j++]);
774
					$r = ord($scan_line[$j++]);
775
					$j++;
776
777
					$color = imagecolorexact($dst_img, $r, $g, $b);
778
					if ($color == -1)
779
					{
780
						$color = imagecolorallocate($dst_img, $r, $g, $b);
781
782
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
783
						if ($color == -1)
784
							$color = imagecolorclosest($dst_img, $r, $g, $b);
785
					}
786
787
					imagesetpixel($dst_img, $x, $y, $color);
788
				}
789
			}
790
			elseif ($info['bits'] == 24)
791
			{
792
				$x = 0;
793
				for ($j = 0; $j < $scan_line_size; $x++)
794
				{
795
					$b = ord($scan_line[$j++]);
796
					$g = ord($scan_line[$j++]);
797
					$r = ord($scan_line[$j++]);
798
799
					$color = imagecolorexact($dst_img, $r, $g, $b);
800
					if ($color == -1)
801
					{
802
						$color = imagecolorallocate($dst_img, $r, $g, $b);
803
804
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
805
						if ($color == -1)
806
							$color = imagecolorclosest($dst_img, $r, $g, $b);
807
					}
808
809
					imagesetpixel($dst_img, $x, $y, $color);
810
				}
811
			}
812
			elseif ($info['bits'] == 16)
813
			{
814
				$x = 0;
815
				for ($j = 0; $j < $scan_line_size; $x++)
816
				{
817
					$b1 = ord($scan_line[$j++]);
818
					$b2 = ord($scan_line[$j++]);
819
820
					$word = $b2 * 256 + $b1;
821
822
					$b = (($word & 31) * 255) / 31;
823
					$g = ((($word >> 5) & 31) * 255) / 31;
824
					$r = ((($word >> 10) & 31) * 255) / 31;
825
826
					// Scale the image colors up properly.
827
					$color = imagecolorexact($dst_img, $r, $g, $b);
828
					if ($color == -1)
829
					{
830
						$color = imagecolorallocate($dst_img, $r, $g, $b);
831
832
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
833
						if ($color == -1)
834
							$color = imagecolorclosest($dst_img, $r, $g, $b);
835
					}
836
837
					imagesetpixel($dst_img, $x, $y, $color);
838
				}
839
			}
840
			elseif ($info['bits'] == 8)
841
			{
842
				$x = 0;
843
				for ($j = 0; $j < $scan_line_size; $x++)
844
					imagesetpixel($dst_img, $x, $y, $palette[ord($scan_line[$j++])]);
845
			}
846
			elseif ($info['bits'] == 4)
847
			{
848
				$x = 0;
849
				for ($j = 0; $j < $scan_line_size; $x++)
850
				{
851
					$byte = ord($scan_line[$j++]);
852
853
					imagesetpixel($dst_img, $x, $y, $palette[(int) ($byte / 16)]);
854
855
					if (++$x < $info['width'])
856
						imagesetpixel($dst_img, $x, $y, $palette[$byte & 15]);
857
				}
858
			}
859
			elseif ($info['bits'] == 1)
860
			{
861
				$x = 0;
862
				for ($j = 0; $j < $scan_line_size; $x++)
863
				{
864
					$byte = ord($scan_line[$j++]);
865
866
					imagesetpixel($dst_img, $x, $y, $palette[(($byte) & 128) != 0]);
867
868
					for ($shift = 1; $shift < 8; $shift++)
869
					{
870
						if (++$x < $info['width'])
871
							imagesetpixel($dst_img, $x, $y, $palette[(($byte << $shift) & 128) != 0]);
872
					}
873
				}
874
			}
875
		}
876
877
		fclose($fp);
878
879
		error_reporting($errors);
880
881
		return $dst_img;
882
	}
883
}
884
885
/**
886
 * Writes a gif file to disk as a png file.
887
 *
888
 * @param gif_file $gif A gif image resource
889
 * @param string $lpszFileName The name of the file
890
 * @param int $background_color The background color
891
 * @return bool Whether the operation was successful
892
 */
893
function gif_outputAsPng($gif, $lpszFileName, $background_color = -1)
894
{
895
	if (!is_a($gif, 'gif_file') || $lpszFileName == '')
896
		return false;
897
898
	if (($fd = $gif->get_png_data($background_color)) === false)
899
		return false;
900
901
	if (($fh = @fopen($lpszFileName, 'wb')) === false)
902
		return false;
903
904
	@fwrite($fh, $fd, strlen($fd));
905
	@fflush($fh);
906
	@fclose($fh);
907
908
	return true;
909
}
910
911
/**
912
 * Show an image containing the visual verification code for registration.
913
 * Requires the GD extension.
914
 * Uses a random font for each letter from default_theme_dir/fonts.
915
 * Outputs a gif or a png (depending on whether gif ix supported).
916
 *
917
 * @param string $code The code to display
918
 * @return void|false False if something goes wrong.
919
 */
920
function showCodeImage($code)
921
{
922
	global $gd2, $settings, $user_info, $modSettings;
923
924
	// Note: The higher the value of visual_verification_type the harder the verification is - from 0 as disabled through to 4 as "Very hard".
925
926
	// What type are we going to be doing?
927
	$imageType = $modSettings['visual_verification_type'];
928
	// Special case to allow the admin center to show samples.
929
	if ($user_info['is_admin'] && isset($_GET['type']))
930
		$imageType = (int) $_GET['type'];
931
932
	// Some quick references for what we do.
933
	// Do we show no, low or high noise?
934
	$noiseType = $imageType == 3 ? 'low' : ($imageType == 4 ? 'high' : ($imageType == 5 ? 'extreme' : 'none'));
935
	// Can we have more than one font in use?
936
	$varyFonts = $imageType > 3 ? true : false;
937
	// Just a plain white background?
938
	$simpleBGColor = $imageType < 3 ? true : false;
939
	// Plain black foreground?
940
	$simpleFGColor = $imageType == 0 ? true : false;
941
	// High much to rotate each character.
942
	$rotationType = $imageType == 1 ? 'none' : ($imageType > 3 ? 'low' : 'high');
943
	// Do we show some characters inversed?
944
	$showReverseChars = $imageType > 3 ? true : false;
945
	// Special case for not showing any characters.
946
	$disableChars = $imageType == 0 ? true : false;
947
	// What do we do with the font colors. Are they one color, close to one color or random?
948
	$fontColorType = $imageType == 1 ? 'plain' : ($imageType > 3 ? 'random' : 'cyclic');
949
	// Are the fonts random sizes?
950
	$fontSizeRandom = $imageType > 3 ? true : false;
951
	// How much space between characters?
952
	$fontHorSpace = $imageType > 3 ? 'high' : ($imageType == 1 ? 'medium' : 'minus');
953
	// Where do characters sit on the image? (Fixed position or random/very random)
954
	$fontVerPos = $imageType == 1 ? 'fixed' : ($imageType > 3 ? 'vrandom' : 'random');
955
	// Make font semi-transparent?
956
	$fontTrans = $imageType == 2 || $imageType == 3 ? true : false;
957
	// Give the image a border?
958
	$hasBorder = $simpleBGColor;
959
960
	// The amount of pixels inbetween characters.
961
	$character_spacing = 1;
962
963
	// What color is the background - generally white unless we're on "hard".
964
	if ($simpleBGColor)
965
		$background_color = array(255, 255, 255);
966
	else
967
		$background_color = isset($settings['verification_background']) ? $settings['verification_background'] : array(236, 237, 243);
968
969
	// The color of the characters shown (red, green, blue).
970
	if ($simpleFGColor)
971
		$foreground_color = array(0, 0, 0);
972
	else
973
	{
974
		$foreground_color = array(64, 101, 136);
975
976
		// Has the theme author requested a custom color?
977
		if (isset($settings['verification_foreground']))
978
			$foreground_color = $settings['verification_foreground'];
979
	}
980
981
	if (!is_dir($settings['default_theme_dir'] . '/fonts'))
982
		return false;
983
984
	// Get a list of the available fonts.
985
	$font_dir = dir($settings['default_theme_dir'] . '/fonts');
986
	$font_list = array();
987
	$ttfont_list = array();
988
	$endian = unpack('v', pack('S', 0x00FF)) === 0x00FF;
989
	while ($entry = $font_dir->read())
990
	{
991
		if (preg_match('~^(.+)\.gdf$~', $entry, $matches) === 1)
992
		{
993
			if ($endian ^ (strpos($entry, '_end.gdf') === false))
994
				$font_list[] = $entry;
995
		}
996
		elseif (preg_match('~^(.+)\.ttf$~', $entry, $matches) === 1)
997
			$ttfont_list[] = $entry;
998
	}
999
1000
	if (empty($font_list))
1001
		return false;
1002
1003
	// For non-hard things don't even change fonts.
1004
	if (!$varyFonts)
1005
	{
1006
		$font_list = array($font_list[0]);
1007
		// Try use Screenge if we can - it looks good!
1008
		if (in_array('AnonymousPro.ttf', $ttfont_list))
1009
			$ttfont_list = array('AnonymousPro.ttf');
1010
		else
1011
			$ttfont_list = empty($ttfont_list) ? array() : array($ttfont_list[0]);
1012
	}
1013
1014
	// Create a list of characters to be shown.
1015
	$characters = array();
1016
	$loaded_fonts = array();
1017
	for ($i = 0; $i < strlen($code); $i++)
1018
	{
1019
		$characters[$i] = array(
1020
			'id' => $code[$i],
1021
			'font' => array_rand($font_list),
1022
		);
1023
1024
		$loaded_fonts[$characters[$i]['font']] = null;
1025
	}
1026
1027
	// Load all fonts and determine the maximum font height.
1028
	foreach ($loaded_fonts as $font_index => $dummy)
1029
		$loaded_fonts[$font_index] = imageloadfont($settings['default_theme_dir'] . '/fonts/' . $font_list[$font_index]);
1030
1031
	// Determine the dimensions of each character.
1032
	if ($imageType == 4 || $imageType == 5)
1033
		$extra = 80;
1034
	else
1035
		$extra = 45;
1036
1037
	$total_width = $character_spacing * strlen($code) + $extra;
1038
	$max_height = 0;
1039
	foreach ($characters as $char_index => $character)
1040
	{
1041
		$characters[$char_index]['width'] = imagefontwidth($loaded_fonts[$character['font']]);
1042
		$characters[$char_index]['height'] = imagefontheight($loaded_fonts[$character['font']]);
1043
1044
		$max_height = max($characters[$char_index]['height'] + 5, $max_height);
1045
		$total_width += $characters[$char_index]['width'];
1046
	}
1047
1048
	// Create an image.
1049
	$code_image = $gd2 ? imagecreatetruecolor($total_width, $max_height) : imagecreate($total_width, $max_height);
1050
1051
	// Draw the background.
1052
	$bg_color = imagecolorallocate($code_image, $background_color[0], $background_color[1], $background_color[2]);
1053
	imagefilledrectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $bg_color);
1054
1055
	// Randomize the foreground color a little.
1056
	for ($i = 0; $i < 3; $i++)
1057
		$foreground_color[$i] = mt_rand(max($foreground_color[$i] - 3, 0), min($foreground_color[$i] + 3, 255));
1058
	$fg_color = imagecolorallocate($code_image, $foreground_color[0], $foreground_color[1], $foreground_color[2]);
1059
1060
	// Color for the dots.
1061
	for ($i = 0; $i < 3; $i++)
1062
		$dotbgcolor[$i] = $background_color[$i] < $foreground_color[$i] ? mt_rand(0, max($foreground_color[$i] - 20, 0)) : mt_rand(min($foreground_color[$i] + 20, 255), 255);
1063
	$randomness_color = imagecolorallocate($code_image, $dotbgcolor[0], $dotbgcolor[1], $dotbgcolor[2]);
1064
1065
	// Some squares/rectanges for new extreme level
1066
	if ($noiseType == 'extreme')
1067
	{
1068
		for ($i = 0; $i < mt_rand(1, 5); $i++)
1069
		{
1070
			$x1 = mt_rand(0, $total_width / 4);
1071
			$x2 = $x1 + round(rand($total_width / 4, $total_width));
1072
			$y1 = mt_rand(0, $max_height);
1073
			$y2 = $y1 + round(rand(0, $max_height / 3));
1074
			imagefilledrectangle($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1075
		}
1076
	}
1077
1078
	// Fill in the characters.
1079
	if (!$disableChars)
1080
	{
1081
		$cur_x = 0;
1082
		foreach ($characters as $char_index => $character)
1083
		{
1084
			// Can we use true type fonts?
1085
			$can_do_ttf = function_exists('imagettftext');
1086
1087
			// How much rotation will we give?
1088
			if ($rotationType == 'none')
1089
				$angle = 0;
1090
			else
1091
				$angle = mt_rand(-100, 100) / ($rotationType == 'high' ? 6 : 10);
1092
1093
			// What color shall we do it?
1094
			if ($fontColorType == 'cyclic')
1095
			{
1096
				// Here we'll pick from a set of acceptance types.
1097
				$colors = array(
1098
					array(10, 120, 95),
1099
					array(46, 81, 29),
1100
					array(4, 22, 154),
1101
					array(131, 9, 130),
1102
					array(0, 0, 0),
1103
					array(143, 39, 31),
1104
				);
1105
				if (!isset($last_index))
1106
					$last_index = -1;
1107
				$new_index = $last_index;
1108
				while ($last_index == $new_index)
1109
					$new_index = mt_rand(0, count($colors) - 1);
1110
				$char_fg_color = $colors[$new_index];
1111
				$last_index = $new_index;
1112
			}
1113
			elseif ($fontColorType == 'random')
1114
				$char_fg_color = array(mt_rand(max($foreground_color[0] - 2, 0), $foreground_color[0]), mt_rand(max($foreground_color[1] - 2, 0), $foreground_color[1]), mt_rand(max($foreground_color[2] - 2, 0), $foreground_color[2]));
1115
			else
1116
				$char_fg_color = array($foreground_color[0], $foreground_color[1], $foreground_color[2]);
1117
1118
			if (!empty($can_do_ttf))
1119
			{
1120
				// GD2 handles font size differently.
1121
				if ($fontSizeRandom)
1122
					$font_size = $gd2 ? mt_rand(17, 19) : mt_rand(18, 25);
1123
				else
1124
					$font_size = $gd2 ? 18 : 24;
1125
1126
				// Work out the sizes - also fix the character width cause TTF not quite so wide!
1127
				$font_x = $fontHorSpace == 'minus' && $cur_x > 0 ? $cur_x - 3 : $cur_x + 5;
1128
				$font_y = $max_height - ($fontVerPos == 'vrandom' ? mt_rand(2, 8) : ($fontVerPos == 'random' ? mt_rand(3, 5) : 5));
1129
1130
				// What font face?
1131
				if (!empty($ttfont_list))
1132
					$fontface = $settings['default_theme_dir'] . '/fonts/' . $ttfont_list[mt_rand(0, count($ttfont_list) - 1)];
1133
1134
				// What color are we to do it in?
1135
				$is_reverse = $showReverseChars ? mt_rand(0, 1) : false;
1136
				$char_color = function_exists('imagecolorallocatealpha') && $fontTrans ? imagecolorallocatealpha($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2], 50) : imagecolorallocate($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2]);
1137
1138
				$fontcord = @imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $char_color, $fontface, $character['id']);
1139
				if (empty($fontcord))
1140
					$can_do_ttf = false;
1141
				elseif ($is_reverse)
1142
				{
1143
					imagefilledpolygon($code_image, $fontcord, 4, $fg_color);
1144
					// Put the character back!
1145
					imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $randomness_color, $fontface, $character['id']);
1146
				}
1147
1148
				if ($can_do_ttf)
1149
					$cur_x = max($fontcord[2], $fontcord[4]) + ($angle == 0 ? 0 : 3);
1150
			}
1151
1152
			if (!$can_do_ttf)
1153
			{
1154
				// Rotating the characters a little...
1155
				if (function_exists('imagerotate'))
1156
				{
1157
					$char_image = $gd2 ? imagecreatetruecolor($character['width'], $character['height']) : imagecreate($character['width'], $character['height']);
1158
					$char_bgcolor = imagecolorallocate($char_image, $background_color[0], $background_color[1], $background_color[2]);
1159
					imagefilledrectangle($char_image, 0, 0, $character['width'] - 1, $character['height'] - 1, $char_bgcolor);
1160
					imagechar($char_image, $loaded_fonts[$character['font']], 0, 0, $character['id'], imagecolorallocate($char_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2]));
1161
					$rotated_char = imagerotate($char_image, mt_rand(-100, 100) / 10, $char_bgcolor);
1162
					imagecopy($code_image, $rotated_char, $cur_x, 0, 0, 0, $character['width'], $character['height']);
1163
					imagedestroy($rotated_char);
1164
					imagedestroy($char_image);
1165
				}
1166
1167
				// Sorry, no rotation available.
1168
				else
1169
					imagechar($code_image, $loaded_fonts[$character['font']], $cur_x, floor(($max_height - $character['height']) / 2), $character['id'], imagecolorallocate($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2]));
1170
				$cur_x += $character['width'] + $character_spacing;
1171
			}
1172
		}
1173
	}
1174
	// If disabled just show a cross.
1175
	else
1176
	{
1177
		imageline($code_image, 0, 0, $total_width, $max_height, $fg_color);
1178
		imageline($code_image, 0, $max_height, $total_width, 0, $fg_color);
1179
	}
1180
1181
	// Make the background color transparent on the hard image.
1182
	if (!$simpleBGColor)
1183
		imagecolortransparent($code_image, $bg_color);
1184
	if ($hasBorder)
1185
		imagerectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $fg_color);
1186
1187
	// Add some noise to the background?
1188
	if ($noiseType != 'none')
1189
	{
1190
		for ($i = mt_rand(0, 2); $i < $max_height; $i += mt_rand(1, 2))
1191
			for ($j = mt_rand(0, 10); $j < $total_width; $j += mt_rand(1, 10))
1192
				imagesetpixel($code_image, $j, $i, mt_rand(0, 1) ? $fg_color : $randomness_color);
1193
1194
		// Put in some lines too?
1195
		if ($noiseType != 'extreme')
1196
		{
1197
			$num_lines = $noiseType == 'high' ? mt_rand(3, 7) : mt_rand(2, 5);
1198
			for ($i = 0; $i < $num_lines; $i++)
1199
			{
1200
				if (mt_rand(0, 1))
1201
				{
1202
					$x1 = mt_rand(0, $total_width);
1203
					$x2 = mt_rand(0, $total_width);
1204
					$y1 = 0;
1205
					$y2 = $max_height;
1206
				}
1207
				else
1208
				{
1209
					$y1 = mt_rand(0, $max_height);
1210
					$y2 = mt_rand(0, $max_height);
1211
					$x1 = 0;
1212
					$x2 = $total_width;
1213
				}
1214
				imagesetthickness($code_image, mt_rand(1, 2));
1215
				imageline($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1216
			}
1217
		}
1218
		else
1219
		{
1220
			// Put in some ellipse
1221
			$num_ellipse = $noiseType == 'extreme' ? mt_rand(6, 12) : mt_rand(2, 6);
1222
			for ($i = 0; $i < $num_ellipse; $i++)
1223
			{
1224
				$x1 = round(rand(($total_width / 4) * -1, $total_width + ($total_width / 4)));
1225
				$x2 = round(rand($total_width / 2, 2 * $total_width));
1226
				$y1 = round(rand(($max_height / 4) * -1, $max_height + ($max_height / 4)));
1227
				$y2 = round(rand($max_height / 2, 2 * $max_height));
1228
				imageellipse($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1229
			}
1230
		}
1231
	}
1232
1233
	// Show the image.
1234
	if (function_exists('imagegif'))
1235
	{
1236
		header('content-type: image/gif');
1237
		imagegif($code_image);
1238
	}
1239
	else
1240
	{
1241
		header('content-type: image/png');
1242
		imagepng($code_image);
1243
	}
1244
1245
	// Bail out.
1246
	imagedestroy($code_image);
1247
	die();
1248
}
1249
1250
/**
1251
 * Show a letter for the visual verification code.
1252
 * Alternative function for showCodeImage() in case GD is missing.
1253
 * Includes an image from a random sub directory of default_theme_dir/fonts.
1254
 *
1255
 * @param string $letter A letter to show as an image
1256
 * @return void|false False if something went wrong
1257
 */
1258
function showLetterImage($letter)
1259
{
1260
	global $settings;
1261
1262
	if (!is_dir($settings['default_theme_dir'] . '/fonts'))
1263
		return false;
1264
1265
	// Get a list of the available font directories.
1266
	$font_dir = dir($settings['default_theme_dir'] . '/fonts');
1267
	$font_list = array();
1268
	while ($entry = $font_dir->read())
1269
		if ($entry[0] !== '.' && is_dir($settings['default_theme_dir'] . '/fonts/' . $entry) && file_exists($settings['default_theme_dir'] . '/fonts/' . $entry . '.gdf'))
1270
			$font_list[] = $entry;
1271
1272
	if (empty($font_list))
1273
		return false;
1274
1275
	// Pick a random font.
1276
	$random_font = $font_list[array_rand($font_list)];
1277
1278
	// Check if the given letter exists.
1279
	if (!file_exists($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . strtoupper($letter) . '.png'))
1280
		return false;
1281
1282
	// Include it!
1283
	header('content-type: image/png');
1284
	include($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . strtoupper($letter) . '.png');
1285
1286
	// Nothing more to come.
1287
	die();
1288
}
1289
1290
/**
1291
 * Gets the dimensions of an SVG image (specifically, of its viewport).
1292
 *
1293
 * See https://www.w3.org/TR/SVG11/coords.html#IntrinsicSizing
1294
 *
1295
 * @param string $filepath The path to the SVG file.
1296
 * @return array The width and height of the SVG image in pixels.
1297
 */
1298
function getSvgSize($filepath)
1299
{
1300
	if (!preg_match('/<svg\b[^>]*>/', file_get_contents($filepath, false, null, 0, 480), $matches))
1301
	{
1302
		return array('width' => 0, 'height' => 0);
1303
	}
1304
1305
	$svg = $matches[0];
1306
1307
	// If the SVG has width and height attributes, use those.
1308
	// If attribute is missing, SVG spec says the default is '100%'.
1309
	// If no unit is supplied, spec says unit defaults to px.
1310
	foreach (array('width', 'height') as $dimension)
1311
	{
1312
		if (preg_match("/\b$dimension\s*=\s*([\"'])\s*([\d.]+)([\D\S]*)\s*\\1/", $svg, $matches))
1313
		{
1314
			$$dimension = $matches[2];
1315
			$unit = !empty($matches[3]) ? $matches[3] : 'px';
1316
		}
1317
		else
1318
		{
1319
			$$dimension = 100;
1320
			$unit = '%';
1321
		}
1322
1323
		// Resolve unit.
1324
		switch ($unit)
1325
		{
1326
			// Already pixels, so do nothing.
1327
			case 'px':
1328
				break;
1329
1330
			// Points.
1331
			case 'pt':
1332
				$$dimension *= 0.75;
1333
				break;
1334
1335
			// Picas.
1336
			case 'pc':
1337
				$$dimension *= 16;
1338
				break;
1339
1340
			// Inches.
1341
			case 'in':
1342
				$$dimension *= 96;
1343
				break;
1344
1345
			// Centimetres.
1346
			case 'cm':
1347
				$$dimension *= 37.8;
1348
				break;
1349
1350
			// Millimetres.
1351
			case 'mm':
1352
				$$dimension *= 3.78;
1353
				break;
1354
1355
			// Font height.
1356
			// Assume browser default of 1em = 1pc.
1357
			case 'em':
1358
				$$dimension *= 16;
1359
				break;
1360
1361
			// Font x-height.
1362
			// Assume half of font height.
1363
			case 'ex':
1364
				$$dimension *= 8;
1365
				break;
1366
1367
			// Font '0' character width.
1368
			// Assume a typical monospace font at 1em = 1pc.
1369
			case 'ch':
1370
				$$dimension *= 9.6;
1371
				break;
1372
1373
			// Percentage.
1374
			// SVG spec says to use viewBox dimensions in this case.
1375
			default:
1376
				unset($$dimension);
1377
				break;
1378
		}
1379
	}
1380
1381
	// Width and/or height is missing or a percentage, so try the viewBox attribute.
1382
	if ((!isset($width) || !isset($height)) && preg_match('/\bviewBox\s*=\s*(["\'])\s*[\d.]+[,\s]+[\d.]+[,\s]+([\d.]+)[,\s]+([\d.]+)\s*\\1/', $svg, $matches))
1383
	{
1384
		$vb_width = $matches[2];
1385
		$vb_height = $matches[3];
1386
1387
		// No dimensions given, so use viewBox dimensions.
1388
		if (!isset($width) && !isset($height))
1389
		{
1390
			$width = $vb_width;
1391
			$height = $vb_height;
1392
		}
1393
		// Width but no height, so calculate height.
1394
		elseif (isset($width))
1395
		{
1396
			$height = $width * $vb_height / $vb_width;
1397
		}
1398
		// Height but no width, so calculate width.
1399
		elseif (isset($height))
1400
		{
1401
			$width = $height * $vb_width / $vb_height;
1402
		}
1403
	}
1404
1405
	// Viewport undefined, so call it infinite.
1406
	if (!isset($width) && !isset($height))
1407
	{
1408
		$width = INF;
1409
		$height = INF;
1410
	}
1411
1412
	return array('width' => round($width), 'height' => round($height));
1413
}
1414
1415
?>