Completed
Pull Request — development (#2955)
by Stephen
17:46
created

Graphics.subs.php ➔ generateTextImageWithGD()   C

Complexity

Conditions 8
Paths 32

Size

Total Lines 51
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 29
nc 32
nop 4
dl 0
loc 51
ccs 0
cts 0
cp 0
crap 72
rs 6.5978
c 0
b 0
f 0

How to fix   Long Method   

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:

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
 *
8
 * TrueType fonts supplied by www.LarabieFonts.com
9
 *
10
 * @name      ElkArte Forum
11
 * @copyright ElkArte Forum contributors
12
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
13
 *
14
 * This file contains code covered by:
15
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
16
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
17
 *
18
 * @version 1.1 Release Candidate 1
19
 *
20
 */
21
22
/**
23
 * Create a thumbnail of the given source.
24
 *
25
 * @uses resizeImageFile() function to achieve the resize.
26
 *
27
 * @package Graphics
28
 * @param string $source The name of the source image
29
 * @param int $max_width The maximum allowed width
30
 * @param int $max_height The maximum allowed height
31
 * @return boolean whether the thumbnail creation was successful.
32
 */
33
function createThumbnail($source, $max_width, $max_height)
34
{
35
	global $modSettings;
36
37
	$destName = $source . '_thumb.tmp';
38
39
	// Do the actual resize.
40 View Code Duplication
	if (!empty($modSettings['attachment_thumb_png']))
41
		$success = resizeImageFile($source, $destName, $max_width, $max_height, 3);
42
	else
43
		$success = resizeImageFile($source, $destName, $max_width, $max_height);
44
45
	// Okay, we're done with the temporary stuff.
46
	$destName = substr($destName, 0, -4);
47
48
	if ($success && @rename($destName . '.tmp', $destName))
49
		return true;
50
	else
51
	{
52
		@unlink($destName . '.tmp');
53
		@touch($destName);
54
		return false;
55
	}
56
}
57
58
/**
59
 * Used to re-encodes an image to a specified image format
60
 *
61
 * What it does:
62
 *
63
 * - creates a copy of the file at the same location as fileName.
64
 * - the file would have the format preferred_format if possible, otherwise the default format is jpeg.
65
 * - the function makes sure that all non-essential image contents are disposed.
66
 *
67
 * @package Graphics
68
 * @param string $fileName The path to the file
69
 * @param int $preferred_format The preferred format, 0 to automatically determine, 1 for gif, 2 for jpg,
70
 * 3 for png, 6 for bmp and 15 for wbmp
71
 *
72
 * @return boolean true on success, false on failure.
73
 */
74
function reencodeImage($fileName, $preferred_format = 0)
75
{
76
	if (!resizeImageFile($fileName, $fileName . '.tmp', null, null, $preferred_format))
77
	{
78
		if (file_exists($fileName . '.tmp'))
79
			unlink($fileName . '.tmp');
80
81
		return false;
82
	}
83
84
	if (!unlink($fileName))
85
		return false;
86
87
	return rename($fileName . '.tmp', $fileName);
88
}
89
90
/**
91
 * Searches through the file to see if there's potentially harmful non-binary content.
92
 *
93
 * What it does:
94
 *
95
 * - if extensiveCheck is true, searches for asp/php short tags as well.
96
 *
97
 * @package Graphics
98
 *
99
 * @param string $fileName The path to the file
100
 * @param bool   $extensiveCheck = false if it should perform extensive checks
101
 *
102
 * @return bool Whether the image appears to be safe
103
 * @throws Elk_Exception attach_timeout
104
 */
105
function checkImageContents($fileName, $extensiveCheck = false)
106
{
107
	$fp = fopen($fileName, 'rb');
108
	if (!$fp)
109
	{
110
		loadLanguage('Post');
111
		throw new Elk_Exception('attach_timeout');
112
	}
113
114
	$prev_chunk = '';
115
	while (!feof($fp))
116
	{
117
		$cur_chunk = fread($fp, 8192);
118
		$test_chunk = $prev_chunk . $cur_chunk;
119
120
		// Though not exhaustive lists, better safe than sorry.
121
		if (!empty($extensiveCheck))
122
		{
123
			// Paranoid check. Some like it that way.
124
			if (preg_match('~<\\?php|<script\W|(?-i)[CFZ]WS[\x01-\x0E]~i', $test_chunk) === 1)
125
			{
126
				fclose($fp);
127
				return false;
128
			}
129
		}
130
		else
131
		{
132
			// Check for potential php injection
133
			if (preg_match('~<\\?php|<script\s+language\s*=\s*(?:php|"php"|\'php\')\s*>~i', $test_chunk))
134
			{
135
				fclose($fp);
136
				return false;
137
			}
138
		}
139
140
		$prev_chunk = $cur_chunk;
141
	}
142
143
	fclose($fp);
144
145
	return true;
146
}
147
148
/**
149
 * Sets a global $gd2 variable needed by some functions to determine
150
 * whether the GD2 library is present.
151
 *
152
 * @package Graphics
153
 *
154
 * @return bool Whether or not GD is available.
155
 */
156
function checkGD()
157
{
158
	global $gd2;
159
160
	// Check to see if GD is installed and what version.
161
	if (($extensionFunctions = get_extension_funcs('gd')) === false)
162
		return false;
163
164
	// Also determine if GD2 is installed and store it in a global.
165
	$gd2 = in_array('imagecreatetruecolor', $extensionFunctions) && function_exists('imagecreatetruecolor');
166
167
	return true;
168
}
169
170
/**
171
 * Checks whether the Imagick class is present.
172
 *
173
 * @package Graphics
174
 *
175
 * @return bool Whether or not the Imagick extension is available.
176
 */
177
function checkImagick()
178
{
179
	return class_exists('Imagick', false);
180
}
181
182
/**
183
 * See if we have enough memory to thumbnail an image
184
 *
185
 * @package Graphics
186
 * @param int[] $sizes image size
187
 *
188
 * @return bool Whether or not the memory is available.
189
 */
190
function imageMemoryCheck($sizes)
191
{
192
	global $modSettings;
193
194
	// Just to be sure
195
	if (!is_array($sizes) || $sizes[0] === -1)
196
	{
197
		return true;
198
	}
199
200
	// Doing the old 'set it and hope' way?
201
	if (empty($modSettings['attachment_thumb_memory']))
202
	{
203
		detectServer()->setMemoryLimit('128M');
204
		return true;
205
	}
206
207
	// Determine the memory requirements for this image, note: if you want to use an image formula
208
	// W x H x bits/8 x channels x Overhead factor
209
	// You will need to account for single bit images as GD expands them to an 8 bit and will greatly
210
	// overun the calculated value.
211
	// The 5 below is simply a shortcut of 8bpp, 3 channels, 1.66 overhead
212
	$needed_memory = ($sizes[0] * $sizes[1] * 5);
213
214
	// If we need more, lets try to get it
215
	return detectServer()->setMemoryLimit($needed_memory, true);
216
}
217
218
/**
219
 * Resize an image from a remote location or a local file.
220
 *
221
 * What it does:
222
 *
223
 * - Puts the resized image at the destination location.
224
 * - The file would have the format preferred_format if possible,
225
 * otherwise the default format is jpeg.
226
 *
227
 * @package Graphics
228
 *
229
 * @param string $source The name of the source image
230
 * @param string $destination The name of the destination image
231
 * @param int $max_width The maximum allowed width
232
 * @param int $max_height The maximum allowed height
233
 * @param int $preferred_format Used by Imagick/resizeImage
234
 *
235
 * @return boolean Whether the thumbnail creation was successful.
236
 */
237
function resizeImageFile($source, $destination, $max_width, $max_height, $preferred_format = 0)
238
{
239
	// Nothing to do without GD or IM
240
	if (!checkGD() && !checkImagick())
241
		return false;
242
243
	if (!file_exists($source))
244
	{
245
		return false;
246
	}
247
248
	static $default_formats = array(
249
		'1' => 'gif',
250
		'2' => 'jpeg',
251
		'3' => 'png',
252
		'6' => 'bmp',
253
		'15' => 'wbmp'
254
	);
255
256
	require_once(SUBSDIR . '/Package.subs.php');
257
	require_once(SUBSDIR . '/Attachments.subs.php');
258
259
	// Get the image file, we have to work with something after all
260
	$fp_destination = fopen($destination, 'wb');
261
	if ($fp_destination && (substr($source, 0, 7) === 'http://' || substr($source, 0, 8) === 'https://'))
262
	{
263
		$fileContents = fetch_web_data($source);
264
265
		fwrite($fp_destination, $fileContents);
266
		fclose($fp_destination);
267
268
		$sizes = elk_getimagesize($destination);
269
	}
270
	elseif ($fp_destination)
271
	{
272
		$sizes = elk_getimagesize($source);
273
274
		$fp_source = fopen($source, 'rb');
275
		if ($fp_source !== false)
276
		{
277
			while (!feof($fp_source))
278
			{
279
				fwrite($fp_destination, fread($fp_source, 8192));
280
			}
281
			fclose($fp_source);
282
		}
283
		else
284
			$sizes = array(-1, -1, -1);
285
		fclose($fp_destination);
286
	}
287
	// We can't get to the file.
288
	else
289
		$sizes = array(-1, -1, -1);
290
291
	// See if we have -or- can get the needed memory for this operation
292
	if (checkGD() && !imageMemoryCheck($sizes))
0 ignored issues
show
Documentation introduced by
$sizes is of type array|boolean, but the function expects a array<integer,integer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
293
		return false;
294
295
	// A known and supported format?
296
	if (checkImagick() && isset($default_formats[$sizes[2]]))
297
	{
298
		return resizeImage(null, $destination, null, null, $max_width, $max_height, true, $preferred_format);
299
	}
300
	elseif (checkGD() && isset($default_formats[$sizes[2]]) && function_exists('imagecreatefrom' . $default_formats[$sizes[2]]))
301
	{
302
		$imagecreatefrom = 'imagecreatefrom' . $default_formats[$sizes[2]];
303
		if ($src_img = @$imagecreatefrom($destination))
304
		{
305
			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);
306
		}
307
	}
308
309
	return false;
310
}
311
312
/**
313
 * Resize an image proportionally to fit within the defined max_width and max_height limits
314
 *
315
 * What it does:
316
 *
317
 * - Will do nothing to the image if the file fits within the size limits
318
 * - If Image Magick is present it will use those function over any GD solutions
319
 * - If GD2 is present, it'll use it to achieve better quality (imagecopyresampled)
320
 * - Saves the new image to destination_filename, in the preferred_format
321
 * if possible, default is jpeg.
322
 *
323
 * @uses GD
324
 * @uses Imagick
325
 *
326
 * @package Graphics
327
 * @param resource|null $src_img null for Imagick images, resource form imagecreatefrom for GD
328
 * @param string $destName
329
 * @param int $src_width The width of the source image
330
 * @param int $src_height The height of the source image
331
 * @param int $max_width The maximum allowed width
332
 * @param int $max_height The maximum allowed height
333
 * @param bool $force_resize = false Whether to override defaults and resize it
334
 * @param int $preferred_format - The preferred format
335
 *         - 0 to use jpeg, 1 for gif, 2 to force jpeg, 3 for png, 6 for bmp and 15 for wbmp
336
 *
337
 * @return bool Whether resize was successful.
338
 */
339
function resizeImage($src_img, $destName, $src_width, $src_height, $max_width, $max_height, $force_resize = false, $preferred_format = 0)
340
{
341
	global $gd2;
342
343
	if (checkImagick())
344
	{
345
		// These are the file formats we know about
346
		static $default_formats = array(
347
			'1' => 'gif',
348
			'2' => 'jpeg',
349
			'3' => 'png',
350
			'6' => 'bmp',
351
			'15' => 'wbmp'
352
		);
353
		$preferred_format = empty($preferred_format) || !isset($default_formats[$preferred_format]) ? 2 : $preferred_format;
354
355
		// Since Imagick can throw exceptions, lets catch them
356
		try
357
		{
358
			// Get a new instance of Imagick for use
359
			$imagick = new Imagick($destName);
360
361
			// Set the input and output image size
362
			$src_width = empty($src_width) ? $imagick->getImageWidth() : $src_width;
363
			$src_height = empty($src_height) ? $imagick->getImageHeight() : $src_height;
364
365
			// The behavior of bestfit changed in Imagick 3.0.0 and it will now scale up, we prevent that
366
			$dest_width = empty($max_width) ? $src_width : min($max_width, $src_width);
367
			$dest_height = empty($max_height) ? $src_height : min($max_height, $src_height);
368
369
			// Set jpeg image quality to 80
370
			if ($default_formats[$preferred_format] === 'jpeg')
371
			{
372
				$imagick->borderImage('white', 0, 0);
373
				$imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
374
				$imagick->setImageCompressionQuality(80);
375
			}
376
377
			// Create a new image in our preferred format and resize it if needed
378
			$imagick->setImageFormat($default_formats[$preferred_format]);
379
			$imagick->resizeImage($dest_width, $dest_height, Imagick::FILTER_LANCZOS, 1, true);
380
381
			// Save the new image in the destination location
382
			$success = $imagick->writeImage($destName);
383
384
			// Free resources associated with the Imagick object
385
			$imagick->clear();
386
		}
387
		catch (Exception $e)
388
		{
389
			$success = false;
390
		}
391
392
		return !empty($success);
393
	}
394
	elseif (checkGD())
395
	{
396
		$success = false;
397
398
		// Determine whether to resize to max width or to max height (depending on the limits.)
399
		if (!empty($max_width) || !empty($max_height))
400
		{
401
			if (!empty($max_width) && (empty($max_height) || $src_height * $max_width / $src_width <= $max_height))
402
			{
403
				$dst_width = $max_width;
404
				$dst_height = floor($src_height * $max_width / $src_width);
405
			}
406
			elseif (!empty($max_height))
407
			{
408
				$dst_width = floor($src_width * $max_height / $src_height);
409
				$dst_height = $max_height;
410
			}
411
412
			// Don't bother resizing if it's already smaller...
413
			if (!empty($dst_width) && !empty($dst_height) && ($dst_width < $src_width || $dst_height < $src_height || $force_resize))
414
			{
415
				// (make a true color image, because it just looks better for resizing.)
416
				if ($gd2)
417
				{
418
					$dst_img = imagecreatetruecolor($dst_width, $dst_height);
419
420
					// Deal nicely with a PNG - because we can.
421
					if ((!empty($preferred_format)) && ($preferred_format == 3))
422
					{
423
						imagealphablending($dst_img, false);
424
						if (function_exists('imagesavealpha'))
425
							imagesavealpha($dst_img, true);
426
					}
427
				}
428
				else
429
					$dst_img = imagecreate($dst_width, $dst_height);
430
431
				// Resize it!
432
				if ($gd2)
433
					imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
434
				else
435
					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 $src_img defined by parameter $src_img on line 339 can also be of type null; however, imagecopyresamplebicubic() does only seem to accept resource, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
436
			}
437
			else
438
				$dst_img = $src_img;
439
		}
440
		else
441
			$dst_img = $src_img;
442
443
		// Save the image as ...
444
		if (!empty($preferred_format) && ($preferred_format == 3) && function_exists('imagepng'))
445
			$success = imagepng($dst_img, $destName);
446
		elseif (!empty($preferred_format) && ($preferred_format == 1) && function_exists('imagegif'))
447
			$success = imagegif($dst_img, $destName);
448
		elseif (function_exists('imagejpeg'))
449
			$success = imagejpeg($dst_img, $destName, 80);
450
451
		// Free the memory.
452
		imagedestroy($src_img);
453
		if ($dst_img != $src_img)
454
			imagedestroy($dst_img);
455
456
		return $success;
457
	}
458
	// Without Imagick or GD, no image resizing at all.
459
	else
460
		return false;
461
}
462
463
/**
464
 * Calls GD or ImageMagick functions to correct an images orientation
465
 * based on the EXIF orientation flag
466
 *
467
 * @param string $image_name
468
 */
469
function autoRotateImage($image_name)
470
{
471
	if (checkImagick())
472
	{
473
		autoRotateImageWithIM($image_name);
474
	}
475
	elseif (checkGD())
476
	{
477
		autoRotateImageWithGD($image_name);
478
	}
479
}
480
481
/**
482
 * Autorotate an image based on its EXIF Orientation tag.
483
 *
484
 * What it does:
485
 *
486
 * - GD only
487
 * - Checks exif data for orientation flag and rotates image so its proper
488
 * - Does not update orientation flag as GD removes EXIF data
489
 * - Only works with jpeg images, could add TIFF as well
490
 * - Writes the update image back to $image_name
491
 *
492
 * @package Graphics
493
 * @uses GD
494
 * @param string $image_name full location of the file
495
 */
496
function autoRotateImageWithGD($image_name)
497
{
498
	// Read the EXIF data
499
	$exif = function_exists('exif_read_data') ? @exif_read_data($image_name) : array();
500
501
	// We're only interested in the exif orientation
502
	$orientation = isset($exif['Orientation']) ? $exif['Orientation'] : 0;
503
504
	// For now we only process jpeg images, so check that we have one
505
	$sizes = elk_getimagesize($image_name);
506
507
	// Not a jpeg or not rotated, done!
508
	if ($sizes[2] !== 2 || $orientation === 0 || !imageMemoryCheck($sizes))
0 ignored issues
show
Documentation introduced by
$sizes is of type array|boolean, but the function expects a array<integer,integer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
509
	{
510
		return false;
511
	}
512
513
	// Load the image object so we can begin the transformation(s)
514
	$source = imagecreatefromjpeg($image_name);
515
516
	// Time to spin and mirror as needed
517
	switch ($orientation)
518
	{
519
		// 0 & 1 Not set or Normal
520
		case 0:
521
		case 1:
522
			break;
523
		// 2 Mirror image, Normal orientation
524
		case 2:
525
			$source = flopImageGD($source, $sizes);
0 ignored issues
show
Bug introduced by
It seems like $sizes defined by elk_getimagesize($image_name) on line 505 can also be of type boolean; however, flopImageGD() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
526
			break;
527
		// 3 Normal image, rotated 180
528
		case 3:
529
			$source = rotateImageGD($source, 180);
530
			break;
531
		// 4 Mirror image, rotated 180
532
		case 4:
533
			$source = flipImageGD($source, $sizes);
0 ignored issues
show
Bug introduced by
It seems like $sizes defined by elk_getimagesize($image_name) on line 505 can also be of type boolean; however, flipImageGD() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
534
			break;
535
		// 5 Mirror image, rotated 90 CCW
536
		case 5:
537
			$source = flopImageGD($source, $sizes);
0 ignored issues
show
Bug introduced by
It seems like $sizes defined by elk_getimagesize($image_name) on line 505 can also be of type boolean; however, flopImageGD() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
538
			$source = rotateImageGD($source, 90);
539
			break;
540
		// 6 Normal image, rotated 90 CCW
541
		case 6:
542
			$source = rotateImageGD($source, -90);
543
			break;
544
		// 7 Mirror image, rotated 90 CW
545
		case 7:
546
			$source = flopImageGD($source, $sizes);
0 ignored issues
show
Bug introduced by
It seems like $sizes defined by elk_getimagesize($image_name) on line 505 can also be of type boolean; however, flopImageGD() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
547
			$source = rotateImageGD($source, -90);
548
			break;
549
		// 8 Normal image, rotated 90 CW
550
		case 8:
551
			$source = rotateImageGD($source, 90);
552
			break;
553
	}
554
555
	// Save the updated image, free resources
556
	imagejpeg($source, $image_name);
557
	imagedestroy($source);
558
559
	return true;
560
}
561
562
/**
563
 * Autorotate an image based on its EXIF Orientation tag.
564
 *
565
 * What it does:
566
 *
567
 * - ImageMagick only
568
 * - Checks exif data for orientation flag and rotates image so its proper
569
 * - Updates orientation flag if rotation was required
570
 * - Writes the update image back to $image_name
571
 *
572
 * @uses Imagick
573
 * @param string $image_name
574
 */
575
function autoRotateImageWithIM($image_name)
576
{
577
	try
578
	{
579
		// Get a new instance of Imagick for use
580
		$image = new Imagick($image_name);
581
582
		// This method should exist if Imagick has been compiled against ImageMagick version
583
		// 6.3.0 or higher which is forever ago, but we check anyway ;)
584
		if (!method_exists($image, 'getImageOrientation'))
585
		{
586
			return false;
587
		}
588
589
		$orientation = $image->getImageOrientation();
590
		switch ($orientation)
591
		{
592
			// 0 & 1 Not set or Normal
593
			case Imagick::ORIENTATION_UNDEFINED:
594
			case Imagick::ORIENTATION_TOPLEFT:
595
				break;
596
			// 2 Mirror image, Normal orientation
597
			case Imagick::ORIENTATION_TOPRIGHT:
598
				$image->flopImage();
599
				break;
600
			// 3 Normal image, rotated 180
601
			case Imagick::ORIENTATION_BOTTOMRIGHT:
602
				$image->rotateImage('#000', 180);
603
				break;
604
			// 4 Mirror image, rotated 180
605
			case Imagick::ORIENTATION_BOTTOMLEFT:
606
				$image->flipImage();
607
				break;
608
			// 5 Mirror image, rotated 90 CCW
609
			case Imagick::ORIENTATION_LEFTTOP:
610
				$image->rotateImage('#000', 90);
611
				$image->flopImage();
612
				break;
613
			// 6 Normal image, rotated 90 CCW
614
			case Imagick::ORIENTATION_RIGHTTOP:
615
				$image->rotateImage('#000', 90);
616
				break;
617
			// 7 Mirror image, rotated 90 CW
618
			case Imagick::ORIENTATION_RIGHTBOTTOM:
619
				$image->rotateImage('#000', -90);
620
				$image->flopImage();
621
				break;
622
			// 8 Normal image, rotated 90 CW
623
			case Imagick::ORIENTATION_LEFTBOTTOM:
624
				$image->rotateImage('#000', -90);
625
				break;
626
		}
627
628
		// Now that it's auto-rotated, make sure the EXIF data is correctly updated
629
		if ($orientation >= 2)
630
		{
631
			$image->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
632
		}
633
634
		// Save the new image in the destination location
635
		$success = $image->writeImage($image_name);
636
637
		// Free resources associated with the Imagick object
638
		$image->clear();
639
	}
640
	catch (Exception $e)
641
	{
642
		$success = false;
643
	}
644
645
	return $success;
646
}
647
648
/**
649
 * Rotate an image by X degrees, GD function
650
 *
651
 * @param resource $image
652
 * @param int $degrees
653
 *
654
 * @package Graphics
655
 * @uses GD
656
 * @return resource
657
 */
658
function rotateImageGD($image, $degrees)
659
{
660
	// Kind of need this to do anything
661
	if (function_exists('imagerotate'))
662
	{
663
		// Use positive degrees so GD does not get confused
664
		$degrees -= floor($degrees / 360) * 360;
665
666
		// Rotate away
667
		$background = imagecolorallocatealpha($image, 255, 255, 255, 127);
668
		$image = imagerotate($image, $degrees, $background);
669
	}
670
671
	return $image;
672
}
673
674
/**
675
 * Flop an image using GD functions by copying top to bottom / flop
676
 *
677
 * @param resource $image
678
 * @param array $sizes populated with getimagesize results
679
 *
680
 * @package Graphics
681
 * @uses GD
682
 * @return resource
683
 */
684
function flopImageGD($image, $sizes)
685
{
686
	return flipImageGD($image, $sizes, 'horizontal');
687
}
688
689
/**
690
 * Flip an image using GD function by copying top to bottom / flip vertical
691
 *
692
 * @param resource $image
693
 * @param array $sizes populated with getimagesize results
694
 * @param string $axis vertical for flip about vertical otherwise horizontal flip
695
 *
696
 * @package Graphics
697
 * @uses GD
698
 * @return resource
699
 */
700
function flipImageGD($image, $sizes, $axis = 'vertical')
701
{
702
	// If the built in function (php 5.5) is available, use it
703
	if (function_exists('imageflip'))
704
	{
705
		imageflip($image, $axis === 'vertical' ? IMG_FLIP_VERTICAL : IMG_FLIP_HORIZONTAL);
706
	}
707
	// Pixel mapping then
708
	else
709
	{
710
		$new = imagecreatetruecolor($sizes[0], $sizes[1]);
711
		imagealphablending($new, false);
712
		imagesavealpha($new, true);
713
714
		if ($axis === 'vertical')
715
		{
716 View Code Duplication
			for ($y = 0; $y < $sizes[1]; $y++)
717
			{
718
				imagecopy($new, $image, 0, $y, 0, $sizes[1] - $y - 1, $sizes[0], 1);
719
			}
720
		}
721
		else
722
		{
723 View Code Duplication
			for ($x = 0; $x < $sizes[0]; $x++)
724
			{
725
				imagecopy($new, $image, $x, 0, $sizes[0] - $x - 1, 0, 1, $sizes[1]);
726
			}
727
		}
728
729
		$image = $new;
730
		unset($new);
731
	}
732
733
	return $image;
734
}
735
736
/**
737
 * Copy / resize an image using GD bicubic methods
738
 *
739
 * What it does:
740
 *
741
 * - Used when imagecopyresample() is not available
742
 * - Uses bicubic resizing methods which are lower quality then imagecopyresample
743
 *
744
 * @package Graphics
745
 * @param resource $dst_img The destination image - a GD image resource
746
 * @param resource $src_img The source image - a GD image resource
747
 * @param int $dst_x The "x" coordinate of the destination image
748
 * @param int $dst_y The "y" coordinate of the destination image
749
 * @param int $src_x The "x" coordinate of the source image
750
 * @param int $src_y The "y" coordinate of the source image
751
 * @param int $dst_w The width of the destination image
752
 * @param int $dst_h The height of the destination image
753
 * @param int $src_w The width of the destination image
754
 * @param int $src_h The height of the destination image
755
 */
756
function imagecopyresamplebicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h)
757
{
758
	$palsize = imagecolorstotal($src_img);
759
	for ($i = 0; $i < $palsize; $i++)
760
	{
761
		$colors = imagecolorsforindex($src_img, $i);
762
		imagecolorallocate($dst_img, $colors['red'], $colors['green'], $colors['blue']);
763
	}
764
765
	$scaleX = ($src_w - 1) / $dst_w;
766
	$scaleY = ($src_h - 1) / $dst_h;
767
768
	$scaleX2 = (int) $scaleX / 2;
769
	$scaleY2 = (int) $scaleY / 2;
770
771
	for ($j = $src_y; $j < $dst_h; $j++)
772
	{
773
		$sY = (int) $j * $scaleY;
774
		$y13 = $sY + $scaleY2;
775
776
		for ($i = $src_x; $i < $dst_w; $i++)
777
		{
778
			$sX = (int) $i * $scaleX;
779
			$x34 = $sX + $scaleX2;
780
781
			$color1 = imagecolorsforindex($src_img, imagecolorat($src_img, $sX, $y13));
782
			$color2 = imagecolorsforindex($src_img, imagecolorat($src_img, $sX, $sY));
783
			$color3 = imagecolorsforindex($src_img, imagecolorat($src_img, $x34, $y13));
784
			$color4 = imagecolorsforindex($src_img, imagecolorat($src_img, $x34, $sY));
785
786
			$red = ($color1['red'] + $color2['red'] + $color3['red'] + $color4['red']) / 4;
787
			$green = ($color1['green'] + $color2['green'] + $color3['green'] + $color4['green']) / 4;
788
			$blue = ($color1['blue'] + $color2['blue'] + $color3['blue'] + $color4['blue']) / 4;
789
790
			$color = imagecolorresolve($dst_img, $red, $green, $blue);
791
			if ($color == -1)
792
			{
793
				if ($palsize++ < 256)
794
					imagecolorallocate($dst_img, $red, $green, $blue);
795
				$color = imagecolorclosest($dst_img, $red, $green, $blue);
796
			}
797
798
			imagesetpixel($dst_img, $i + $dst_x - $src_x, $j + $dst_y - $src_y, $color);
799
		}
800
	}
801
}
802
803
if (!function_exists('imagecreatefrombmp'))
804
{
805
	/**
806
	 * It is set only if it doesn't already exist (for forwards compatibility.)
807
	 *
808
	 * What it does:
809
	 *
810
	 * - It only supports uncompressed bitmaps.
811
	 * - It only supports standard windows bitmaps (no os/2 variants)
812
	 * - Returns an image identifier representing the bitmap image
813
	 * obtained from the given filename.
814
	 *
815
	 * @package Graphics
816
	 * @param string $filename The name of the file
817
	 * @return resource An image identifier representing the bitmap image
818
	 */
819
	function imagecreatefrombmp($filename)
820
	{
821
		global $gd2;
822
823
		$fp = fopen($filename, 'rb');
824
825
		$errors = error_reporting(0);
826
827
		// Unpack the general information about the Bitmap Image File, first 14 Bytes
828
		$header = unpack('vtype/Vsize/Vreserved/Voffset', fread($fp, 14));
829
830
		// Unpack the DIB header, it stores detailed information about the bitmap image the pixel format, 40 Bytes long
831
		$info = unpack('Vsize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vncolor/Vcolorimportant', fread($fp, 40));
832
833
		// Not a standard bitmap, bail out
834
		if ($header['type'] != 0x4D42)
835
			return false;
836
837
		// Create our image canvas with the given WxH
838
		if ($gd2)
839
			$dst_img = imagecreatetruecolor($info['width'], $info['height']);
840
		else
841
			$dst_img = imagecreate($info['width'], $info['height']);
842
843
		// Color bitCounts 1,4,8 have palette information we use
844
		$palette = array();
845
		if ($info['bits'] == 1 || $info['bits'] == 4 || $info['bits'] == 8)
846
		{
847
			$palette_size = $header['offset'] - 54;
848
849
			// Read the palette data
850
			$palettedata = fread($fp, $palette_size);
851
852
			// Create the rgb color array
853
			$n = 0;
854
			for ($j = 0; $j < $palette_size; $j++)
855
			{
856
				$b = ord($palettedata[$j++]);
857
				$g = ord($palettedata[$j++]);
858
				$r = ord($palettedata[$j++]);
859
860
				$palette[$n++] = imagecolorallocate($dst_img, $r, $g, $b);
861
			}
862
		}
863
864
		$scan_line_size = ($info['bits'] * $info['width'] + 7) >> 3;
865
		$scan_line_align = $scan_line_size & 3 ? 4 - ($scan_line_size & 3) : 0;
866
867
		for ($y = 0, $l = $info['height'] - 1; $y < $info['height']; $y++, $l--)
868
		{
869
			fseek($fp, $header['offset'] + ($scan_line_size + $scan_line_align) * $l);
870
			$scan_line = fread($fp, $scan_line_size);
871
872
			if (strlen($scan_line) < $scan_line_size)
873
				continue;
874
875
			// 32 bits per pixel
876
			if ($info['bits'] == 32)
877
			{
878
				$x = 0;
879 View Code Duplication
				for ($j = 0; $j < $scan_line_size; $x++)
880
				{
881
					$b = ord($scan_line[$j++]);
882
					$g = ord($scan_line[$j++]);
883
					$r = ord($scan_line[$j++]);
884
					$j++;
885
886
					$color = imagecolorexact($dst_img, $r, $g, $b);
887
					if ($color == -1)
888
					{
889
						$color = imagecolorallocate($dst_img, $r, $g, $b);
890
891
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
892
						if ($color == -1)
893
							$color = imagecolorclosest($dst_img, $r, $g, $b);
894
					}
895
896
					imagesetpixel($dst_img, $x, $y, $color);
897
				}
898
			}
899
			// 24 bits per pixel
900
			elseif ($info['bits'] == 24)
901
			{
902
				$x = 0;
903 View Code Duplication
				for ($j = 0; $j < $scan_line_size; $x++)
904
				{
905
					$b = ord($scan_line[$j++]);
906
					$g = ord($scan_line[$j++]);
907
					$r = ord($scan_line[$j++]);
908
909
					$color = imagecolorexact($dst_img, $r, $g, $b);
910
					if ($color == -1)
911
					{
912
						$color = imagecolorallocate($dst_img, $r, $g, $b);
913
914
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
915
						if ($color == -1)
916
							$color = imagecolorclosest($dst_img, $r, $g, $b);
917
					}
918
919
					imagesetpixel($dst_img, $x, $y, $color);
920
				}
921
			}
922
			// 16 bits per pixel
923
			elseif ($info['bits'] == 16)
924
			{
925
				$x = 0;
926
				for ($j = 0; $j < $scan_line_size; $x++)
927
				{
928
					$b1 = ord($scan_line[$j++]);
929
					$b2 = ord($scan_line[$j++]);
930
931
					$word = $b2 * 256 + $b1;
932
933
					$b = (($word & 31) * 255) / 31;
934
					$g = ((($word >> 5) & 31) * 255) / 31;
935
					$r = ((($word >> 10) & 31) * 255) / 31;
936
937
					// Scale the image colors up properly.
938
					$color = imagecolorexact($dst_img, $r, $g, $b);
939
					if ($color == -1)
940
					{
941
						$color = imagecolorallocate($dst_img, $r, $g, $b);
942
943
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
944
						if ($color == -1)
945
							$color = imagecolorclosest($dst_img, $r, $g, $b);
946
					}
947
948
					imagesetpixel($dst_img, $x, $y, $color);
949
				}
950
			}
951
			// 8 bits per pixel
952
			elseif ($info['bits'] == 8)
953
			{
954
				$x = 0;
955
				for ($j = 0; $j < $scan_line_size; $x++)
956
					imagesetpixel($dst_img, $x, $y, $palette[ord($scan_line[$j++])]);
957
			}
958
			// 4 bits per pixel
959
			elseif ($info['bits'] == 4)
960
			{
961
				$x = 0;
962
				for ($j = 0; $j < $scan_line_size; $x++)
963
				{
964
					$byte = ord($scan_line[$j++]);
965
966
					imagesetpixel($dst_img, $x, $y, $palette[(int) ($byte / 16)]);
967 View Code Duplication
					if (++$x < $info['width'])
968
						imagesetpixel($dst_img, $x, $y, $palette[$byte & 15]);
969
				}
970
			}
971
			// 1 bit
972
			elseif ($info['bits'] == 1)
973
			{
974
				$x = 0;
975
				for ($j = 0; $j < $scan_line_size; $x++)
976
				{
977
					$byte = ord($scan_line[$j++]);
978
979
					imagesetpixel($dst_img, $x, $y, $palette[(($byte) & 128) != 0]);
980
					for ($shift = 1; $shift < 8; $shift++)
981
					{
982 View Code Duplication
						if (++$x < $info['width'])
983
							imagesetpixel($dst_img, $x, $y, $palette[(($byte << $shift) & 128) != 0]);
984
					}
985
				}
986
			}
987
		}
988
989
		fclose($fp);
990
991
		error_reporting($errors);
992
993
		return $dst_img;
994
	}
995
}
996
997
/**
998
 * Show an image containing the visual verification code for registration.
999
 *
1000
 * What it does:
1001
 *
1002
 * - Requires the GD extension.
1003
 * - Uses a random font for each letter from default_theme_dir/fonts.
1004
 * - Outputs a png if possible, otherwise a gif.
1005
 *
1006
 * @package Graphics
1007
 * @param string $code The code to display
1008
 *
1009
 * @return false|null false if something goes wrong.
1010
 */
1011
function showCodeImage($code)
1012
{
1013
	global $gd2, $settings, $user_info, $modSettings;
1014
1015
	// What type are we going to be doing?
1016
	// Note: The higher the value of visual_verification_type the harder the verification is
1017
	// from 0 as disabled through to 4 as "Very hard".
1018
	$imageType = $modSettings['visual_verification_type'];
1019
1020
	// Special case to allow the admin center to show samples.
1021
	if ($user_info['is_admin'] && isset($_GET['type']))
1022
		$imageType = (int) $_GET['type'];
1023
1024
	// Some quick references for what we do.
1025
	// Do we show no, low or high noise?
1026
	$noiseType = $imageType == 3 ? 'low' : ($imageType == 4 ? 'high' : ($imageType == 5 ? 'extreme' : 'none'));
1027
	// Can we have more than one font in use?
1028
	$varyFonts = $imageType > 1 ? true : false;
1029
	// Just a plain white background?
1030
	$simpleBGColor = $imageType < 3 ? true : false;
1031
	// Plain black foreground?
1032
	$simpleFGColor = $imageType == 0 ? true : false;
1033
	// High much to rotate each character.
1034
	$rotationType = $imageType == 1 ? 'none' : ($imageType > 3 ? 'low' : 'high');
1035
	// Do we show some characters inverse?
1036
	$showReverseChars = $imageType > 3 ? true : false;
1037
	// Special case for not showing any characters.
1038
	$disableChars = $imageType == 0 ? true : false;
1039
	// What do we do with the font colors. Are they one color, close to one color or random?
1040
	$fontColorType = $imageType == 1 ? 'plain' : ($imageType > 3 ? 'random' : 'cyclic');
1041
	// Are the fonts random sizes?
1042
	$fontSizeRandom = $imageType > 3 ? true : false;
1043
	// How much space between characters?
1044
	$fontHorSpace = $imageType > 3 ? 'high' : ($imageType == 1 ? 'medium' : 'minus');
1045
	// Where do characters sit on the image? (Fixed position or random/very random)
1046
	$fontVerPos = $imageType == 1 ? 'fixed' : ($imageType > 3 ? 'vrandom' : 'random');
1047
	// Make font semi-transparent?
1048
	$fontTrans = $imageType == 2 || $imageType == 3 ? true : false;
1049
	// Give the image a border?
1050
	$hasBorder = $simpleBGColor;
1051
1052
	// The amount of pixels in between characters.
1053
	$character_spacing = 1;
1054
1055
	// What color is the background - generally white unless we're on "hard".
1056
	if ($simpleBGColor)
1057
		$background_color = array(255, 255, 255);
1058
	else
1059
		$background_color = isset($settings['verification_background']) ? $settings['verification_background'] : array(236, 237, 243);
1060
1061
	// The color of the characters shown (red, green, blue).
1062
	if ($simpleFGColor)
1063
		$foreground_color = array(0, 0, 0);
1064
	else
1065
	{
1066
		$foreground_color = array(64, 101, 136);
1067
1068
		// Has the theme author requested a custom color?
1069
		if (isset($settings['verification_foreground']))
1070
			$foreground_color = $settings['verification_foreground'];
1071
	}
1072
1073
	if (!is_dir($settings['default_theme_dir'] . '/fonts'))
1074
		return false;
1075
1076
	// Can we use true type fonts?
1077
	$can_do_ttf = function_exists('imagettftext');
1078
1079
	// Get a list of the available fonts.
1080
	$font_dir = dir($settings['default_theme_dir'] . '/fonts');
1081
	$font_list = array();
1082
	$ttfont_list = array();
1083
	while ($entry = $font_dir->read())
1084
	{
1085
		if (preg_match('~^(.+)\.gdf$~', $entry, $matches) === 1)
1086
			$font_list[] = $entry;
1087
		elseif (preg_match('~^(.+)\.ttf$~', $entry, $matches) === 1)
1088
			$ttfont_list[] = $entry;
1089
	}
1090
1091
	if (empty($font_list) && ($can_do_ttf && empty($ttfont_list)))
1092
		return false;
1093
1094
	// For non-hard things don't even change fonts.
1095
	if (!$varyFonts)
1096
	{
1097
		$font_list = !empty($font_list) ? array($font_list[0]) : $font_list;
1098
1099
		// Try use Screenge if we can - it looks good!
1100
		if (in_array('VDS_New.ttf', $ttfont_list))
1101
			$ttfont_list = array('VDS_New.ttf');
1102
		else
1103
			$ttfont_list = empty($ttfont_list) ? array() : array($ttfont_list[0]);
1104
	}
1105
1106
	// Create a list of characters to be shown.
1107
	$characters = array();
1108
	$loaded_fonts = array();
1109
	$str_len = strlen($code);
1110
	for ($i = 0; $i < $str_len; $i++)
1111
	{
1112
		$characters[$i] = array(
1113
			'id' => $code[$i],
1114
			'font' => array_rand($can_do_ttf ? $ttfont_list : $font_list),
1115
		);
1116
1117
		$loaded_fonts[$characters[$i]['font']] = null;
1118
	}
1119
1120
	// Load all fonts and determine the maximum font height.
1121
	if (!$can_do_ttf)
1122
		foreach ($loaded_fonts as $font_index => $dummy)
1123
			$loaded_fonts[$font_index] = imageloadfont($settings['default_theme_dir'] . '/fonts/' . $font_list[$font_index]);
1124
1125
	// Determine the dimensions of each character.
1126
	$total_width = $character_spacing * strlen($code) + 50;
1127
	$max_height = 0;
1128
	foreach ($characters as $char_index => $character)
1129
	{
1130
		if ($can_do_ttf)
1131
		{
1132
			// GD2 handles font size differently.
1133 View Code Duplication
			if ($fontSizeRandom)
1134
				$font_size = $gd2 ? mt_rand(17, 19) : mt_rand(25, 27);
1135
			else
1136
				$font_size = $gd2 ? 17 : 27;
1137
1138
			$img_box = imagettfbbox($font_size, 0, $settings['default_theme_dir'] . '/fonts/' . $ttfont_list[$character['font']], $character['id']);
1139
1140
			$characters[$char_index]['width'] = abs($img_box[2] - $img_box[0]);
1141
			$characters[$char_index]['height'] = abs($img_box[7] - $img_box[1]);
1142
		}
1143
		else
1144
		{
1145
			$characters[$char_index]['width'] = imagefontwidth($loaded_fonts[$character['font']]);
1146
			$characters[$char_index]['height'] = imagefontheight($loaded_fonts[$character['font']]);
1147
		}
1148
1149
		$max_height = max($characters[$char_index]['height'] + 5, $max_height);
1150
		$total_width += $characters[$char_index]['width'] + 2;
1151
	}
1152
1153
	// Create an image.
1154
	$code_image = $gd2 ? imagecreatetruecolor($total_width, $max_height) : imagecreate($total_width, $max_height);
1155
1156
	// Draw the background.
1157
	$bg_color = imagecolorallocate($code_image, $background_color[0], $background_color[1], $background_color[2]);
1158
	imagefilledrectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $bg_color);
1159
1160
	// Randomize the foreground color a little.
1161
	for ($i = 0; $i < 3; $i++)
1162
		$foreground_color[$i] = mt_rand(max($foreground_color[$i] - 3, 0), min($foreground_color[$i] + 3, 255));
1163
	$fg_color = imagecolorallocate($code_image, $foreground_color[0], $foreground_color[1], $foreground_color[2]);
1164
1165
	// Color for the noise dots.
1166
	$dotbgcolor = array();
1167
	for ($i = 0; $i < 3; $i++)
1168
		$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);
1169
	$randomness_color = imagecolorallocate($code_image, $dotbgcolor[0], $dotbgcolor[1], $dotbgcolor[2]);
1170
1171
	// Some squares/rectangles for new extreme level
1172
	if ($noiseType == 'extreme')
1173
	{
1174
		for ($i = 0; $i < rand(1, 5); $i++)
1175
		{
1176
			$x1 = rand(0, $total_width / 4);
1177
			$x2 = $x1 + round(rand($total_width / 4, $total_width));
1178
			$y1 = rand(0, $max_height);
1179
			$y2 = $y1 + round(rand(0, $max_height / 3));
1180
			imagefilledrectangle($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1181
		}
1182
	}
1183
1184
	// Fill in the characters.
1185
	if (!$disableChars)
1186
	{
1187
		$cur_x = 0;
1188
		$last_index = -1;
1189
		foreach ($characters as $char_index => $character)
1190
		{
1191
			// How much rotation will we give?
1192
			if ($rotationType == 'none')
1193
				$angle = 0;
1194
			else
1195
				$angle = mt_rand(-100, 100) / ($rotationType == 'high' ? 6 : 10);
1196
1197
			// What color shall we do it?
1198
			if ($fontColorType == 'cyclic')
1199
			{
1200
				// Here we'll pick from a set of acceptance types.
1201
				$colors = array(
1202
					array(10, 120, 95),
1203
					array(46, 81, 29),
1204
					array(4, 22, 154),
1205
					array(131, 9, 130),
1206
					array(0, 0, 0),
1207
					array(143, 39, 31),
1208
				);
1209
1210
				// Pick a color, but not the same one twice in a row
1211
				$new_index = $last_index;
1212
				while ($last_index == $new_index)
1213
					$new_index = mt_rand(0, count($colors) - 1);
1214
				$char_fg_color = $colors[$new_index];
1215
				$last_index = $new_index;
1216
			}
1217
			elseif ($fontColorType == 'random')
1218
				$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]));
1219
			else
1220
				$char_fg_color = array($foreground_color[0], $foreground_color[1], $foreground_color[2]);
1221
1222
			if (!empty($can_do_ttf))
1223
			{
1224
				// GD2 handles font size differently.
1225 View Code Duplication
				if ($fontSizeRandom)
1226
					$font_size = $gd2 ? mt_rand(17, 19) : mt_rand(18, 25);
1227
				else
1228
					$font_size = $gd2 ? 18 : 24;
1229
1230
				// Work out the sizes - also fix the character width cause TTF not quite so wide!
1231
				$font_x = $fontHorSpace === 'minus' && $cur_x > 0 ? $cur_x - 3 : $cur_x + 5;
1232
				$font_y = $max_height - ($fontVerPos === 'vrandom' ? mt_rand(2, 8) : ($fontVerPos === 'random' ? mt_rand(3, 5) : 5));
1233
1234
				// What font face?
1235
				if (!empty($ttfont_list))
1236
					$fontface = $settings['default_theme_dir'] . '/fonts/' . $ttfont_list[mt_rand(0, count($ttfont_list) - 1)];
1237
1238
				// What color are we to do it in?
1239
				$is_reverse = $showReverseChars ? mt_rand(0, 1) : false;
1240
				$char_color = $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]);
1241
1242
				$fontcord = @imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $char_color, $fontface, $character['id']);
0 ignored issues
show
Bug introduced by
The variable $fontface does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1243
				if (empty($fontcord))
1244
					$can_do_ttf = false;
1245
				elseif ($is_reverse !== false)
1246
				{
1247
					imagefilledpolygon($code_image, $fontcord, 4, $fg_color);
1248
1249
					// Put the character back!
1250
					imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $randomness_color, $fontface, $character['id']);
1251
				}
1252
1253
				if ($can_do_ttf)
1254
					$cur_x = max($fontcord[2], $fontcord[4]) + ($angle == 0 ? 0 : 3);
1255
			}
1256
1257
			if (!$can_do_ttf)
1258
			{
1259
				$char_image = $gd2 ? imagecreatetruecolor($character['width'], $character['height']) : imagecreate($character['width'], $character['height']);
1260
				$char_bgcolor = imagecolorallocate($char_image, $background_color[0], $background_color[1], $background_color[2]);
1261
				imagefilledrectangle($char_image, 0, 0, $character['width'] - 1, $character['height'] - 1, $char_bgcolor);
1262
				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]));
1263
				$rotated_char = imagerotate($char_image, mt_rand(-100, 100) / 10, $char_bgcolor);
1264
				imagecopy($code_image, $rotated_char, $cur_x, 0, 0, 0, $character['width'], $character['height']);
1265
				imagedestroy($rotated_char);
1266
				imagedestroy($char_image);
1267
1268
				$cur_x += $character['width'] + $character_spacing;
1269
			}
1270
		}
1271
	}
1272
	// If disabled just show a cross.
1273
	else
1274
	{
1275
		imageline($code_image, 0, 0, $total_width, $max_height, $fg_color);
1276
		imageline($code_image, 0, $max_height, $total_width, 0, $fg_color);
1277
	}
1278
1279
	// Make the background color transparent on the hard image.
1280
	if (!$simpleBGColor)
1281
		imagecolortransparent($code_image, $bg_color);
1282
1283
	if ($hasBorder)
1284
		imagerectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $fg_color);
1285
1286
	// Add some noise to the background?
1287
	if ($noiseType != 'none')
1288
	{
1289
		for ($i = mt_rand(0, 2); $i < $max_height; $i += mt_rand(1, 2))
1290
			for ($j = mt_rand(0, 10); $j < $total_width; $j += mt_rand(1, 10))
1291
				imagesetpixel($code_image, $j, $i, mt_rand(0, 1) ? $fg_color : $randomness_color);
1292
1293
		// Put in some lines too?
1294
		if ($noiseType != 'extreme')
1295
		{
1296
			$num_lines = $noiseType == 'high' ? mt_rand(3, 7) : mt_rand(2, 5);
1297
			for ($i = 0; $i < $num_lines; $i++)
1298
			{
1299
				if (mt_rand(0, 1))
1300
				{
1301
					$x1 = mt_rand(0, $total_width);
1302
					$x2 = mt_rand(0, $total_width);
1303
					$y1 = 0;
1304
					$y2 = $max_height;
1305
				}
1306
				else
1307
				{
1308
					$y1 = mt_rand(0, $max_height);
1309
					$y2 = mt_rand(0, $max_height);
1310
					$x1 = 0;
1311
					$x2 = $total_width;
1312
				}
1313
				imagesetthickness($code_image, mt_rand(1, 2));
1314
				imageline($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1315
			}
1316
		}
1317
		else
1318
		{
1319
			// Put in some ellipse
1320
			$num_ellipse = $noiseType == 'extreme' ? mt_rand(6, 12) : mt_rand(2, 6);
1321
			for ($i = 0; $i < $num_ellipse; $i++)
1322
			{
1323
				$x1 = round(rand(($total_width / 4) * -1, $total_width + ($total_width / 4)));
1324
				$x2 = round(rand($total_width / 2, 2 * $total_width));
1325
				$y1 = round(rand(($max_height / 4) * -1, $max_height + ($max_height / 4)));
1326
				$y2 = round(rand($max_height / 2, 2 * $max_height));
1327
				imageellipse($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1328
			}
1329
		}
1330
	}
1331
1332
	// Show the image.
1333
	if (function_exists('imagepng'))
1334
	{
1335
		header('Content-type: image/png');
1336
		imagepng($code_image);
1337
	}
1338
	else
1339
	{
1340
		header('Content-type: image/gif');
1341
		imagegif($code_image);
1342
	}
1343
1344
	// Bail out.
1345
	imagedestroy($code_image);
1346
	die();
0 ignored issues
show
Coding Style Compatibility introduced by
The function showCodeImage() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1347
}
1348
1349
/**
1350
 * Show a letter for the visual verification code.
1351
 *
1352
 * - Alternative function for showCodeImage() in case GD is missing.
1353
 * - Includes an image from a random sub directory of default_theme_dir/fonts.
1354
 *
1355
 * @package Graphics
1356
 * @param string $letter A letter to show as an image
1357
 *
1358
 * @return false|null false if something goes wrong.
1359
 */
1360
function showLetterImage($letter)
1361
{
1362
	global $settings;
1363
1364
	if (!is_dir($settings['default_theme_dir'] . '/fonts'))
1365
		return false;
1366
1367
	// Get a list of the available font directories.
1368
	$font_dir = dir($settings['default_theme_dir'] . '/fonts');
1369
	$font_list = array();
1370
	while ($entry = $font_dir->read())
1371
		if ($entry[0] !== '.' && is_dir($settings['default_theme_dir'] . '/fonts/' . $entry) && file_exists($settings['default_theme_dir'] . '/fonts/' . $entry . '.gdf'))
1372
			$font_list[] = $entry;
1373
1374
	if (empty($font_list))
1375
		return false;
1376
1377
	// Pick a random font.
1378
	$random_font = $font_list[array_rand($font_list)];
1379
1380
	// Check if the given letter exists.
1381
	if (!file_exists($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . $letter . '.gif'))
1382
		return false;
1383
1384
	// Include it!
1385
	header('Content-type: image/gif');
1386
	include($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . $letter . '.gif');
1387
1388
	// Nothing more to come.
1389
	die();
0 ignored issues
show
Coding Style Compatibility introduced by
The function showLetterImage() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1390
}
1391
1392
/**
1393
 * Simple function to generate an image containing some text.
1394
 * It uses preferentially Imagick if present, otherwise GD.
1395
 * Font and size are fixed.
1396
 *
1397
 * @package Graphics
1398
 *
1399
 * @param string $text The text the image should contain
1400
 * @param int $width Width of the final image
1401
 * @param int $height Height of the image
1402
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1403
 *
1404
 * @return boolean|resource The image or false if neither Imagick nor GD are found
1405
 */
1406
function generateTextImage($text, $width = 100, $height = 100, $format = 'png')
1407
{
1408
	$valid_formats = array('jpeg', 'png', 'gif');
1409
	if (!in_array($format, $valid_formats))
1410
	{
1411
		$format = 'png';
1412
	}
1413
1414
	if (checkImagick() === true)
1415
	{
1416
		return generateTextImageWithIM($text, $width, $height, $format);
1417
	}
1418
	elseif (checkGD() === true)
1419
	{
1420
		return generateTextImageWithGD($text, $width, $height, $format);
1421
	}
1422
	else
1423
	{
1424
		return false;
1425
	}
1426
}
1427
1428
/**
1429
 * Simple function to generate an image containing some text.
1430
 * It uses preferentially Imagick if present, otherwise GD.
1431
 * Font and size are fixed.
1432
 *
1433
 * @uses GD
1434
 *
1435
 * @package Graphics
1436
 *
1437
 * @param string $text The text the image should contain
1438
 * @param int $width Width of the final image
1439
 * @param int $height Height of the image
1440
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1441
 *
1442
 * @return resource|boolean The image
1443
 */
1444
function generateTextImageWithGD($text, $width = 100, $height = 100, $format = 'png')
1445
{
1446
	global $settings;
1447
1448
	$create_function = 'image' . $format;
1449
1450
	// Create a white filled box
1451
	$image = imagecreate($width, $height);
1452
	imagecolorallocate($image, 255, 255, 255);
1453
1454
	$text_color = imagecolorallocate($image, 0, 0, 0);
1455
	$font = $settings['default_theme_dir'] . '/fonts/VDS_New.ttf';
1456
1457
	// The loop is to try to fit the text into the image.
1458
	$true_type = function_exists('imagettftext');
1459
	$font_size = $true_type ? 28 : 5;
1460
	do
1461
	{
1462
		if ($true_type)
1463
		{
1464
			$metric = imagettfbbox($font_size, 0, $font, $text);
1465
			$text_width = abs($metric[4] - $metric[0]);
1466
			$text_height = abs($metric[5] - $metric[1]);
1467
		}
1468
		else
1469
		{
1470
			$text_width = imagefontwidth($font_size) * strlen($text);
1471
			$text_height = imagefontheight($font_size);
1472
		}
1473
	} while ($text_width > $width && $font_size-- > 1);
1474
1475
	$w_offset = ($width - $text_width) / 2;
1476
	$h_offset = $true_type ? ($height / 2) + ($text_height / 2) : ($height - $text_height) / 2;
1477
1478
	if ($true_type)
1479
	{
1480
		imagettftext($image, $font_size, 0, $w_offset, $h_offset, $text_color, $font, $text);
1481
	}
1482
	else
1483
	{
1484
		imagestring($image, $font_size, $w_offset, $h_offset, $text, $text_color);
1485
	}
1486
1487
	// Capture the image string
1488
	ob_start();
1489
	$result = $create_function($image);
1490
	$image = ob_get_contents();
1491
	ob_end_clean();
1492
1493
	return $result ? $image : false;
1494
}
1495
1496
/**
1497
 * Function to generate an image containing some text.
1498
 * It uses Imagick, Font and size are fixed to fit within width
1499
 *
1500
 * @uses Imagick
1501
 *
1502
 * @package Graphics
1503
 *
1504
 * @param string $text The text the image should contain
1505
 * @param int $width Width of the final image
1506
 * @param int $height Height of the image
1507
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1508
 *
1509
 * @return boolean|resource The image or false on error
1510
 */
1511
function generateTextImageWithIM($text, $width = 100, $height = 100, $format = 'png')
1512
{
1513
	global $settings;
1514
1515
	try
1516
	{
1517
		$image = new Imagick();
1518
		$image->newImage($width, $height, new ImagickPixel('white'));
1519
		$image->setImageFormat($format);
1520
1521
		// 28pt is ~2em given default font stack
1522
		$font_size = 28;
1523
		$draw = new ImagickDraw();
1524
		$draw->setFontSize($font_size);
1525
		$draw->setStrokeColor(new ImagickPixel('#000000'));
1526
		$draw->setFillColor(new ImagickPixel('#000000'));
1527
		$draw->setStrokeWidth(1);
1528
		$draw->setTextAlignment(Imagick::ALIGN_CENTER);
1529
		$draw->setFont($settings['default_theme_dir'] . '/fonts/VDS_New.ttf');
1530
1531
		// Make sure the text will fit the the allowed space
1532
		$metric = $image->queryFontMetrics($draw, $text);
1533
		if (isset($metric['textWidth']) && $metric['textWidth'] > $width)
1534
		{
1535
			$image->clear();
1536
			$width = (int) $metric['textWidth'];
1537
			$image->newImage($width, $height, new ImagickPixel('white'));
1538
			$image->setImageFormat($format);
1539
		}
1540
1541
		// Place text in center of block
1542
		$image->annotateImage($draw, $width / 2, $height / 2 + $font_size / 4, 0, $text);
1543
		$image = $image->getImageBlob();
1544
1545
		return $image;
1546
	}
1547
	catch (Exception $e)
1548
	{
1549
		return false;
1550
	}
1551
}
1552