resizeImage()   F
last analyzed

Complexity

Conditions 72
Paths 11666

Size

Total Lines 166
Code Lines 108

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 72
eloc 108
c 0
b 0
f 0
nc 11666
nop 8
dl 0
loc 166
rs 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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)
0 ignored issues
show
introduced by
$fp is of type resource, thus it always evaluated to false.
Loading history...
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)
0 ignored issues
show
introduced by
$fp is of type resource, thus it always evaluated to false.
Loading history...
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
introduced by
$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)
0 ignored issues
show
introduced by
$fp_destination is of type resource, thus it always evaluated to false.
Loading history...
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);
0 ignored issues
show
introduced by
The condition $max_width === null is always false.
Loading history...
introduced by
The condition $max_height === null is always false.
Loading history...
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();
0 ignored issues
show
Bug introduced by
The function newMagickWand 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

531
			$magick_wand = /** @scrutinizer ignore-call */ newMagickWand();
Loading history...
532
			MagickReadImage($magick_wand, $destName);
0 ignored issues
show
Bug introduced by
The function MagickReadImage 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

532
			/** @scrutinizer ignore-call */ 
533
   MagickReadImage($magick_wand, $destName);
Loading history...
533
			$src_width = empty($src_width) ? MagickGetImageWidth($magick_wand) : $src_width;
0 ignored issues
show
Bug introduced by
The function MagickGetImageWidth 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

533
			$src_width = empty($src_width) ? /** @scrutinizer ignore-call */ MagickGetImageWidth($magick_wand) : $src_width;
Loading history...
534
			$src_height = empty($src_height) ? MagickGetImageSize($magick_wand) : $src_height;
0 ignored issues
show
Bug introduced by
The function MagickGetImageSize 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

534
			$src_height = empty($src_height) ? /** @scrutinizer ignore-call */ MagickGetImageSize($magick_wand) : $src_height;
Loading history...
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
Bug introduced by
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]);
0 ignored issues
show
Bug introduced by
The function MagickSetImageFormat 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

541
			/** @scrutinizer ignore-call */ 
542
   MagickSetImageFormat($magick_wand, $default_formats[$preferred_format]);
Loading history...
542
			MagickResizeImage($magick_wand, $dest_width, $dest_height, MW_LanczosFilter, 1, true);
0 ignored issues
show
Bug introduced by
The function MagickResizeImage 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

542
			/** @scrutinizer ignore-call */ 
543
   MagickResizeImage($magick_wand, $dest_width, $dest_height, MW_LanczosFilter, 1, true);
Loading history...
Bug introduced by
The constant MW_LanczosFilter was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
543
544
			if ($orientation > 1)
545
			{
546
				if (in_array($orientation, [3, 4]))
547
					MagickResizeImage($magick_wand, NewPixelWand('white'), 180);
0 ignored issues
show
Bug introduced by
The function NewPixelWand 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

547
					MagickResizeImage($magick_wand, /** @scrutinizer ignore-call */ NewPixelWand('white'), 180);
Loading history...
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);
0 ignored issues
show
Bug introduced by
The function MagickFlopImage 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

554
					/** @scrutinizer ignore-call */ 
555
     MagickFlopImage($magick_wand);
Loading history...
555
			}
556
			$success = MagickWriteImage($magick_wand, $destName);
0 ignored issues
show
Bug introduced by
The function MagickWriteImage 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

556
			$success = /** @scrutinizer ignore-call */ MagickWriteImage($magick_wand, $destName);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $dst_img can also be of type GdImage; however, parameter $dst_img of imagecopyresamplebicubic() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

602
					imagecopyresamplebicubic(/** @scrutinizer ignore-type */ $dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
Loading history...
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]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $dotbgcolor does not seem to be defined for all execution paths leading up to this point.
Loading history...
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);
0 ignored issues
show
Bug introduced by
$x2 of type double is incompatible with the type integer expected by parameter $x2 of imagefilledrectangle(). ( Ignorable by Annotation )

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

1074
			imagefilledrectangle($code_image, $x1, $y1, /** @scrutinizer ignore-type */ $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
Loading history...
Bug introduced by
$y2 of type double is incompatible with the type integer expected by parameter $y2 of imagefilledrectangle(). ( Ignorable by Annotation )

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

1074
			imagefilledrectangle($code_image, $x1, $y1, $x2, /** @scrutinizer ignore-type */ $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
Loading history...
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']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fontface does not seem to be defined for all execution paths leading up to this point.
Loading history...
1139
				if (empty($fontcord))
1140
					$can_do_ttf = false;
1141
				elseif ($is_reverse)
0 ignored issues
show
Bug Best Practice introduced by
The expression $is_reverse of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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]));
0 ignored issues
show
Bug introduced by
floor($max_height - $character['height'] / 2) of type double is incompatible with the type integer expected by parameter $y of imagechar(). ( Ignorable by Annotation )

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

1169
					imagechar($code_image, $loaded_fonts[$character['font']], $cur_x, /** @scrutinizer ignore-type */ floor(($max_height - $character['height']) / 2), $character['id'], imagecolorallocate($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2]));
Loading history...
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);
0 ignored issues
show
Bug introduced by
$y1 of type double is incompatible with the type integer expected by parameter $cy of imageellipse(). ( Ignorable by Annotation )

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

1228
				imageellipse($code_image, $x1, /** @scrutinizer ignore-type */ $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
Loading history...
Bug introduced by
$x2 of type double is incompatible with the type integer expected by parameter $width of imageellipse(). ( Ignorable by Annotation )

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

1228
				imageellipse($code_image, $x1, $y1, /** @scrutinizer ignore-type */ $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
Loading history...
Bug introduced by
$y2 of type double is incompatible with the type integer expected by parameter $height of imageellipse(). ( Ignorable by Annotation )

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

1228
				imageellipse($code_image, $x1, $y1, $x2, /** @scrutinizer ignore-type */ $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
Loading history...
Bug introduced by
$x1 of type double is incompatible with the type integer expected by parameter $cx of imageellipse(). ( Ignorable by Annotation )

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

1228
				imageellipse($code_image, /** @scrutinizer ignore-type */ $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
Loading history...
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();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
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();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
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))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $width seems to never exist and therefore isset should always be false.
Loading history...
Comprehensibility Best Practice introduced by
The variable $height does not exist. Did you maybe mean $vb_height?
Loading history...
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));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $height does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $width does not seem to be defined for all execution paths leading up to this point.
Loading history...
1413
}
1414
1415
?>