summary refs log tree commit diff stats
path: root/includes/securimage/securimage.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/securimage/securimage.php')
-rw-r--r--includes/securimage/securimage.php1584
1 files changed, 1584 insertions, 0 deletions
diff --git a/includes/securimage/securimage.php b/includes/securimage/securimage.php new file mode 100644 index 0000000..ebabab0 --- /dev/null +++ b/includes/securimage/securimage.php
@@ -0,0 +1,1584 @@
1<?php
2
3/**
4 * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br />
5 * File: securimage.php<br />
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or any later version.<br /><br />
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.<br /><br />
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA<br /><br />
20 *
21 * Any modifications to the library should be indicated clearly in the source code
22 * to inform users that the changes are not a part of the original software.<br /><br />
23 *
24 * If you found this script useful, please take a quick moment to rate it.<br />
25 * http://www.hotscripts.com/rate/49400.html Thanks.
26 *
27 * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
28 * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
29 * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
30 * @copyright 2009 Drew Phillips
31 * @author Drew Phillips <drew@drew-phillips.com>
32 * @version 2.0.1 BETA (December 6th, 2009)
33 * @package Securimage
34 *
35 */
36
37/**
38 ChangeLog
39
40 2.0.1
41 - Add support for browsers with cookies disabled (requires php5, sqlite) maps users to md5 hashed ip addresses and md5 hashed codes for security
42 - Add fallback to gd fonts if ttf support is not enabled or font file not found (Mike Challis http://www.642weather.com/weather/scripts.php)
43 - Check for previous definition of image type constants (Mike Challis)
44 - Fix mime type settings for audio output
45 - Fixed color allocation issues with multiple colors and background images, consolidate allocation to one function
46 - Ability to let codes expire after a given length of time
47 - Allow HTML color codes to be passed to Securimage_Color (suggested by Mike Challis)
48
49 2.0.0
50 - Add mathematical distortion to characters (using code from HKCaptcha)
51 - Improved session support
52 - Added Securimage_Color class for easier color definitions
53 - Add distortion to audio output to prevent binary comparison attack (proposed by Sven "SavageTiger" Hagemann [insecurity.nl])
54 - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net)
55 - Audio output is mp3 format by default
56 - Change font to AlteHaasGrotesk by yann le coroller
57 - Some code cleanup
58
59 1.0.4 (unreleased)
60 - Ability to output audible codes in mp3 format to stream from flash
61
62 1.0.3.1
63 - Error reading from wordlist in some cases caused words to be cut off 1 letter short
64
65 1.0.3
66 - Removed shadow_text from code which could cause an undefined property error due to removal from previous version
67
68 1.0.2
69 - Audible CAPTCHA Code wav files
70 - Create codes from a word list instead of random strings
71
72 1.0
73 - Added the ability to use a selected character set, rather than a-z0-9 only.
74 - Added the multi-color text option to use different colors for each letter.
75 - Switched to automatic session handling instead of using files for code storage
76 - Added GD Font support if ttf support is not available. Can use internal GD fonts or load new ones.
77 - Added the ability to set line thickness
78 - Added option for drawing arced lines over letters
79 - Added ability to choose image type for output
80
81 */
82
83/**
84 * Output images in JPEG format
85 */
86if (!defined('SI_IMAGE_JPEG'))
87 define('SI_IMAGE_JPEG', 1);
88/**
89 * Output images in PNG format
90 */
91if (!defined('SI_IMAGE_PNG'))
92 define('SI_IMAGE_PNG', 2);
93/**
94 * Output images in GIF format (not recommended)
95 * Must have GD >= 2.0.28!
96 */
97if (!defined('SI_IMAGE_GIF'))
98 define('SI_IMAGE_GIF', 3);
99
100/**
101 * Securimage CAPTCHA Class.
102 *
103 * @package Securimage
104 * @subpackage classes
105 *
106 */
107class Securimage {
108
109 /**
110 * The desired width of the CAPTCHA image.
111 *
112 * @var int
113 */
114 var $image_width;
115
116 /**
117 * The desired width of the CAPTCHA image.
118 *
119 * @var int
120 */
121 var $image_height;
122
123 /**
124 * The image format for output.<br />
125 * Valid options: SI_IMAGE_PNG, SI_IMAGE_JPG, SI_IMAGE_GIF
126 *
127 * @var int
128 */
129 var $image_type;
130
131 /**
132 * The length of the code to generate.
133 *
134 * @var int
135 */
136 var $code_length;
137
138 /**
139 * The character set for individual characters in the image.<br />
140 * Letters are converted to uppercase.<br />
141 * The font must support the letters or there may be problematic substitutions.
142 *
143 * @var string
144 */
145 var $charset;
146
147 /**
148 * Create codes using this word list
149 *
150 * @var string The path to the word list to use for creating CAPTCHA codes
151 */
152 var $wordlist_file;
153
154 /**
155 * Use wordlist of not
156 *
157 * @var bool true to use wordlist file, false to use random code
158 */
159 var $use_wordlist = false;
160
161 /**
162 * Note: Use of GD fonts is not recommended as many distortion features are not available<br />
163 * The GD font to use.<br />
164 * Internal gd fonts can be loaded by their number.<br />
165 * Alternatively, a file path can be given and the font will be loaded from file.
166 *
167 * @var mixed
168 */
169 var $gd_font_file;
170
171 /**
172 * The approximate size of the font in pixels.<br />
173 * This does not control the size of the font because that is determined by the GD font itself.<br />
174 * This is used to aid the calculations of positioning used by this class.<br />
175 *
176 * @var int
177 */
178 var $gd_font_size;
179
180 /**
181 * Use a gd font instead of TTF
182 *
183 * @var bool true for gd font, false for TTF
184 */
185 var $use_gd_font;
186
187 // Note: These font options below do not apply if you set $use_gd_font to true with the exception of $text_color
188
189 /**
190 * The path to the TTF font file to load.
191 *
192 * @var string
193 */
194 var $ttf_file;
195
196 /**
197 * How much to distort image, higher = more distortion.<br />
198 * Distortion is only available when using TTF fonts.<br />
199 *
200 * @var float
201 */
202 var $perturbation;
203
204 /**
205 * The minimum angle in degrees, with 0 degrees being left-to-right reading text.<br />
206 * Higher values represent a counter-clockwise rotation.<br />
207 * For example, a value of 90 would result in bottom-to-top reading text.<br />
208 * This value along with maximum angle distance do not need to be very high with perturbation
209 *
210 * @var int
211 */
212 var $text_angle_minimum;
213
214 /**
215 * The minimum angle in degrees, with 0 degrees being left-to-right reading text.<br />
216 * Higher values represent a counter-clockwise rotation.<br />
217 * For example, a value of 90 would result in bottom-to-top reading text.
218 *
219 * @var int
220 */
221 var $text_angle_maximum;
222
223 /**
224 * The X-Position on the image where letter drawing will begin.<br />
225 * This value is in pixels from the left side of the image.
226 *
227 * @var int
228 * @deprecated 2.0
229 */
230 var $text_x_start;
231
232 /**
233 * The background color for the image as a Securimage_Color.<br />
234 *
235 * @var Securimage_Color
236 */
237 var $image_bg_color;
238
239 /**
240 * Scan this directory for gif, jpg, and png files to use as background images.<br />
241 * A random image file will be picked each time.<br />
242 * Change from null to the full path to your directory.<br />
243 * i.e. var $background_directory = $_SERVER['DOCUMENT_ROOT'] . '/securimage/backgrounds';
244 * Make sure not to pass a background image to the show function, otherwise this directive is ignored.
245 *
246 * @var string
247 */
248 var $background_directory = null; //'./backgrounds';
249
250 /**
251 * The text color to use for drawing characters as a Securimage_Color.<br />
252 * This value is ignored if $use_multi_text is set to true.<br />
253 * Make sure this contrasts well with the background color or image.<br />
254 *
255 * @see Securimage::$use_multi_text
256 * @var Securimage_Color
257 */
258 var $text_color;
259
260 /**
261 * Set to true to use multiple colors for each character.
262 *
263 * @see Securimage::$multi_text_color
264 * @var boolean
265 */
266 var $use_multi_text;
267
268 /**
269 * Array of Securimage_Colors which will be randomly selected for each letter.<br />
270 *
271 * @var array
272 */
273 var $multi_text_color;
274
275 /**
276 * Set to true to make the characters appear transparent.
277 *
278 * @see Securimage::$text_transparency_percentage
279 * @var boolean
280 */
281 var $use_transparent_text;
282
283 /**
284 * The percentage of transparency, 0 to 100.<br />
285 * A value of 0 is completely opaque, 100 is completely transparent (invisble)
286 *
287 * @see Securimage::$use_transparent_text
288 * @var int
289 */
290 var $text_transparency_percentage;
291
292
293 // Line options
294 /**
295 * Draw vertical and horizontal lines on the image.
296 *
297 * @see Securimage::$line_color
298 * @see Securimage::$draw_lines_over_text
299 * @var boolean
300 */
301 var $num_lines;
302
303 /**
304 * Color of lines drawn over text
305 *
306 * @var string
307 */
308 var $line_color;
309
310 /**
311 * Draw the lines over the text.<br />
312 * If fales lines will be drawn before putting the text on the image.
313 *
314 * @var boolean
315 */
316 var $draw_lines_over_text;
317
318 /**
319 * Text to write at the bottom corner of captcha image
320 *
321 * @since 2.0
322 * @var string Signature text
323 */
324 var $image_signature;
325
326 /**
327 * Color to use for writing signature text
328 *
329 * @since 2.0
330 * @var Securimage_Color
331 */
332 var $signature_color;
333
334 /**
335 * Full path to the WAV files to use to make the audio files, include trailing /.<br />
336 * Name Files [A-Z0-9].wav
337 *
338 * @since 1.0.1
339 * @var string
340 */
341 var $audio_path;
342
343 /**
344 * Type of audio file to generate (mp3 or wav)
345 *
346 * @var string
347 */
348 var $audio_format;
349
350 /**
351 * The session name to use if not the default. Blank for none
352 *
353 * @see http://php.net/session_name
354 * @since 2.0
355 * @var string
356 */
357 var $session_name = '';
358
359 /**
360 * The amount of time in seconds that a code remains valid.<br />
361 * Any code older than this number will be considered invalid even if entered correctly.<br />
362 * Any non-numeric or value less than 1 disables this functionality.
363 *
364 * @var int
365 */
366 var $expiry_time;
367
368 /**
369 * Path to the file to use for storing codes for users.<br />
370 * THIS FILE MUST ABSOLUTELY NOT BE ACCESSIBLE FROM A WEB BROWSER!!<br />
371 * Put this file in a directory below the web root or one that is restricted (i.e. an apache .htaccess file with deny from all)<br />
372 * If you cannot meet those requirements your forms may not be completely protected.<br />
373 * You could obscure the database file name but this is also not recommended.
374 *
375 * @var string
376 */
377 var $sqlite_database;
378
379 /**
380 * Use an SQLite database for storing codes as a backup to sessions.<br />
381 * Note: Sessions will still be used
382 */
383 var $use_sqlite_db;
384
385
386 //END USER CONFIGURATION
387 //There should be no need to edit below unless you really know what you are doing.
388
389 /**
390 * The gd image resource.
391 *
392 * @access private
393 * @var resource
394 */
395 var $im;
396
397 /**
398 * Temporary image for rendering
399 *
400 * @access private
401 * @var resource
402 */
403 var $tmpimg;
404
405 /**
406 * Internal scale factor for anti-alias @hkcaptcha
407 *
408 * @access private
409 * @since 2.0
410 * @var int
411 */
412 var $iscale; // internal scale factor for anti-alias @hkcaptcha
413
414 /**
415 * The background image resource
416 *
417 * @access private
418 * @var resource
419 */
420 var $bgimg;
421
422 /**
423 * The code generated by the script
424 *
425 * @access private
426 * @var string
427 */
428 var $code;
429
430 /**
431 * The code that was entered by the user
432 *
433 * @access private
434 * @var string
435 */
436 var $code_entered;
437
438 /**
439 * Whether or not the correct code was entered
440 *
441 * @access private
442 * @var boolean
443 */
444 var $correct_code;
445
446 /**
447 * Handle to SQLite database
448 *
449 * @access private
450 * @var resource
451 */
452 var $sqlite_handle;
453
454 /**
455 * Color resource for image line color
456 *
457 * @access private
458 * @var int
459 */
460 var $gdlinecolor;
461
462 /**
463 * Array of colors for multi colored codes
464 *
465 * @access private
466 * @var array
467 */
468 var $gdmulticolor;
469
470 /**
471 * Color resource for image font color
472 *
473 * @access private
474 * @var int
475 */
476 var $gdtextcolor;
477
478 /**
479 * Color resource for image signature color
480 *
481 * @access private
482 * @var int
483 */
484 var $gdsignaturecolor;
485
486 /**
487 * Color resource for image background color
488 *
489 * @access private
490 * @var int
491 */
492 var $gdbgcolor;
493
494
495 /**
496 * Class constructor.<br />
497 * Because the class uses sessions, this will attempt to start a session if there is no previous one.<br />
498 * If you do not start a session before calling the class, the constructor must be called before any
499 * output is sent to the browser.
500 *
501 * <code>
502 * $securimage = new Securimage();
503 * </code>
504 *
505 */
506 function Securimage()
507 {
508 // Initialize session or attach to existing
509 if ( session_id() == '' ) { // no session has been started yet, which is needed for validation
510 if (trim($this->session_name) != '') {
511 session_name($this->session_name); // set session name if provided
512 }
513 session_start();
514 }
515
516 // Set Default Values
517 $this->image_width = 230;
518 $this->image_height = 80;
519 $this->image_type = SI_IMAGE_PNG;
520
521 $this->code_length = 6;
522 $this->charset = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789';
523 $this->wordlist_file = './words/words.txt';
524 $this->use_wordlist = false;
525
526 $this->gd_font_file = 'gdfonts/automatic.gdf';
527 $this->use_gd_font = false;
528 $this->gd_font_size = 24;
529 $this->text_x_start = 15;
530
531 $this->ttf_file = './AHGBold.ttf';
532
533 $this->perturbation = 0.75;
534 $this->iscale = 5;
535 $this->text_angle_minimum = 0;
536 $this->text_angle_maximum = 0;
537
538 $this->image_bg_color = new Securimage_Color(0xff, 0xff, 0xff);
539 $this->text_color = new Securimage_Color(0x3d, 0x3d, 0x3d);
540 $this->multi_text_color = array(new Securimage_Color(0x0, 0x20, 0xCC),
541 new Securimage_Color(0x0, 0x30, 0xEE),
542 new Securimage_color(0x0, 0x40, 0xCC),
543 new Securimage_Color(0x0, 0x50, 0xEE),
544 new Securimage_Color(0x0, 0x60, 0xCC));
545 $this->use_multi_text = false;
546
547 $this->use_transparent_text = false;
548 $this->text_transparency_percentage = 30;
549
550 $this->num_lines = 10;
551 $this->line_color = new Securimage_Color(0x3d, 0x3d, 0x3d);
552 $this->draw_lines_over_text = true;
553
554 $this->image_signature = '';
555 $this->signature_color = new Securimage_Color(0x20, 0x50, 0xCC);
556 $this->signature_font = './AHGBold.ttf';
557
558 $this->audio_path = './audio/';
559 $this->audio_format = 'mp3';
560 $this->session_name = '';
561 $this->expiry_time = 900;
562
563 $this->sqlite_database = 'database/securimage.sqlite';
564 $this->use_sqlite_db = false;
565
566 $this->sqlite_handle = false;
567 }
568
569 /**
570 * Generate a code and output the image to the browser.
571 *
572 * <code>
573 * <?php
574 * include 'securimage.php';
575 * $securimage = new Securimage();
576 * $securimage->show('bg.jpg');
577 * ?>
578 * </code>
579 *
580 * @param string $background_image The path to an image to use as the background for the CAPTCHA
581 */
582 function show($background_image = "")
583 {
584 if($background_image != "" && is_readable($background_image)) {
585 $this->bgimg = $background_image;
586 }
587
588 $this->doImage();
589 }
590
591 /**
592 * Validate the code entered by the user.
593 *
594 * <code>
595 * $code = $_POST['code'];
596 * if ($securimage->check($code) == false) {
597 * die("Sorry, the code entered did not match.");
598 * } else {
599 * $valid = true;
600 * }
601 * </code>
602 * @param string $code The code the user entered
603 * @return boolean true if the code was correct, false if not
604 */
605 function check($code)
606 {
607 $this->code_entered = $code;
608 $this->validate();
609 return $this->correct_code;
610 }
611
612 /**
613 * Output audio file with HTTP headers to browser
614 *
615 * <code>
616 * $sound = new Securimage();
617 * $sound->audio_format = 'mp3';
618 * $sound->outputAudioFile();
619 * </code>
620 *
621 * @since 2.0
622 */
623 function outputAudioFile()
624 {
625 if (strtolower($this->audio_format) == 'wav') {
626 header('Content-type: audio/x-wav');
627 $ext = 'wav';
628 } else {
629 header('Content-type: audio/mpeg'); // default to mp3
630 $ext = 'mp3';
631 }
632
633 header("Content-Disposition: attachment; filename=\"securimage_audio.{$ext}\"");
634 header('Cache-Control: no-store, no-cache, must-revalidate');
635 header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
636 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
637
638 $audio = $this->getAudibleCode($ext);
639
640 header('Content-Length: ' . strlen($audio));
641
642 echo $audio;
643 exit;
644 }
645
646 /**
647 * Generate and output the image
648 *
649 * @access private
650 *
651 */
652 function doImage()
653 {
654 if ($this->use_gd_font == true) {
655 $this->iscale = 1;
656 }
657 if($this->use_transparent_text == true || $this->bgimg != "") {
658 $this->im = imagecreatetruecolor($this->image_width, $this->image_height);
659 $this->tmpimg = imagecreatetruecolor($this->image_width * $this->iscale, $this->image_height * $this->iscale);
660
661 } else { //no transparency
662 $this->im = imagecreate($this->image_width, $this->image_height);
663 $this->tmpimg = imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
664 }
665
666 $this->allocateColors();
667 imagepalettecopy($this->tmpimg, $this->im);
668
669 $this->setBackground();
670
671 $this->createCode();
672
673 if (!$this->draw_lines_over_text && $this->num_lines > 0) $this->drawLines();
674
675 $this->drawWord();
676 if ($this->use_gd_font == false && is_readable($this->ttf_file)) $this->distortedCopy();
677
678 if ($this->draw_lines_over_text && $this->num_lines > 0) $this->drawLines();
679
680 if (trim($this->image_signature) != '') $this->addSignature();
681
682 $this->output();
683
684 }
685
686 /**
687 * Allocate all colors that will be used in the CAPTCHA image
688 *
689 * @since 2.0.1
690 * @access private
691 */
692 function allocateColors()
693 {
694 // allocate bg color first for imagecreate
695 $this->gdbgcolor = imagecolorallocate($this->im, $this->image_bg_color->r, $this->image_bg_color->g, $this->image_bg_color->b);
696
697 $alpha = intval($this->text_transparency_percentage / 100 * 127);
698
699 if ($this->use_transparent_text == true) {
700 $this->gdtextcolor = imagecolorallocatealpha($this->im, $this->text_color->r, $this->text_color->g, $this->text_color->b, $alpha);
701 $this->gdlinecolor = imagecolorallocatealpha($this->im, $this->line_color->r, $this->line_color->g, $this->line_color->b, $alpha);
702 } else {
703 $this->gdtextcolor = imagecolorallocate($this->im, $this->text_color->r, $this->text_color->g, $this->text_color->b);
704 $this->gdlinecolor = imagecolorallocate($this->im, $this->line_color->r, $this->line_color->g, $this->line_color->b);
705 }
706
707 $this->gdsignaturecolor = imagecolorallocate($this->im, $this->signature_color->r, $this->signature_color->g, $this->signature_color->b);
708
709 if ($this->use_multi_text == true) {
710 $this->gdmulticolor = array();
711
712 foreach($this->multi_text_color as $color) {
713 if ($this->use_transparent_text == true) {
714 $this->gdmulticolor[] = imagecolorallocatealpha($this->im, $color->r, $color->g, $color->b, $alpha);
715 } else {
716 $this->gdmulticolor[] = imagecolorallocate($this->im, $color->r, $color->g, $color->b);
717 }
718 }
719 }
720 }
721
722 /**
723 * Set the background of the CAPTCHA image
724 *
725 * @access private
726 *
727 */
728 function setBackground()
729 {
730 imagefilledrectangle($this->im, 0, 0, $this->image_width * $this->iscale, $this->image_height * $this->iscale, $this->gdbgcolor);
731 imagefilledrectangle($this->tmpimg, 0, 0, $this->image_width * $this->iscale, $this->image_height * $this->iscale, $this->gdbgcolor);
732
733 if ($this->bgimg == '') {
734 if ($this->background_directory != null && is_dir($this->background_directory) && is_readable($this->background_directory)) {
735 $img = $this->getBackgroundFromDirectory();
736 if ($img != false) {
737 $this->bgimg = $img;
738 }
739 }
740 }
741
742 $dat = @getimagesize($this->bgimg);
743 if($dat == false) {
744 return;
745 }
746
747 switch($dat[2]) {
748 case 1: $newim = @imagecreatefromgif($this->bgimg); break;
749 case 2: $newim = @imagecreatefromjpeg($this->bgimg); break;
750 case 3: $newim = @imagecreatefrompng($this->bgimg); break;
751 case 15: $newim = @imagecreatefromwbmp($this->bgimg); break;
752 case 16: $newim = @imagecreatefromxbm($this->bgimg); break;
753 default: return;
754 }
755
756 if(!$newim) return;
757
758 imagecopyresized($this->im, $newim, 0, 0, 0, 0, $this->image_width, $this->image_height, imagesx($newim), imagesy($newim));
759 }
760
761 /**
762 * Return the full path to a random gif, jpg, or png from the background directory.
763 *
764 * @access private
765 * @see Securimage::$background_directory
766 * @return mixed false if none found, string $path if found
767 */
768 function getBackgroundFromDirectory()
769 {
770 $images = array();
771
772 if ($dh = opendir($this->background_directory)) {
773 while (($file = readdir($dh)) !== false) {
774 if (preg_match('/(jpg|gif|png)$/i', $file)) $images[] = $file;
775 }
776
777 closedir($dh);
778
779 if (sizeof($images) > 0) {
780 return rtrim($this->background_directory, '/') . '/' . $images[rand(0, sizeof($images)-1)];
781 }
782 }
783
784 return false;
785 }
786
787 /**
788 * Draw random curvy lines over the image<br />
789 * Modified code from HKCaptcha
790 *
791 * @since 2.0
792 * @access private
793 *
794 */
795 function drawLines()
796 {
797 for ($line = 0; $line < $this->num_lines; ++$line) {
798 $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
799 $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
800 $y = rand($this->image_height * 0.1, $this->image_height * 0.9);
801
802 $theta = ($this->frand()-0.5) * M_PI * 0.7;
803 $w = $this->image_width;
804 $len = rand($w * 0.4, $w * 0.7);
805 $lwid = rand(0, 2);
806
807 $k = $this->frand() * 0.6 + 0.2;
808 $k = $k * $k * 0.5;
809 $phi = $this->frand() * 6.28;
810 $step = 0.5;
811 $dx = $step * cos($theta);
812 $dy = $step * sin($theta);
813 $n = $len / $step;
814 $amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
815 $x0 = $x - 0.5 * $len * cos($theta);
816 $y0 = $y - 0.5 * $len * sin($theta);
817
818 $ldx = round(-$dy * $lwid);
819 $ldy = round($dx * $lwid);
820
821 for ($i = 0; $i < $n; ++$i) {
822 $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
823 $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
824 imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor);
825 }
826 }
827 }
828
829 /**
830 * Draw the CAPTCHA code over the image
831 *
832 * @access private
833 *
834 */
835 function drawWord()
836 {
837 $width2 = $this->image_width * $this->iscale;
838 $height2 = $this->image_height * $this->iscale;
839
840 if ($this->use_gd_font == true || !is_readable($this->ttf_file)) {
841 if (!is_int($this->gd_font_file)) { //is a file name
842 $font = @imageloadfont($this->gd_font_file);
843 if ($font == false) {
844 trigger_error("Failed to load GD Font file {$this->gd_font_file} ", E_USER_WARNING);
845 return;
846 }
847 } else { //gd font identifier
848 $font = $this->gd_font_file;
849 }
850
851 imagestring($this->im, $font, $this->text_x_start, ($this->image_height / 2) - ($this->gd_font_size / 2), $this->code, $this->gdtextcolor);
852 } else { //ttf font
853 $font_size = $height2 * .35;
854 $bb = imagettfbbox($font_size, 0, $this->ttf_file, $this->code);
855 $tx = $bb[4] - $bb[0];
856 $ty = $bb[5] - $bb[1];
857 $x = floor($width2 / 2 - $tx / 2 - $bb[0]);
858 $y = round($height2 / 2 - $ty / 2 - $bb[1]);
859
860 $strlen = strlen($this->code);
861 if (!is_array($this->multi_text_color)) $this->use_multi_text = false;
862
863
864 if ($this->use_multi_text == false && $this->text_angle_minimum == 0 && $this->text_angle_maximum == 0) { // no angled or multi-color characters
865 imagettftext($this->tmpimg, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code);
866 } else {
867 for($i = 0; $i < $strlen; ++$i) {
868 $angle = rand($this->text_angle_minimum, $this->text_angle_maximum);
869 $y = rand($y - 5, $y + 5);
870 if ($this->use_multi_text == true) {
871 $font_color = $this->gdmulticolor[rand(0, sizeof($this->gdmulticolor) - 1)];
872 } else {
873 $font_color = $this->gdtextcolor;
874 }
875
876 $ch = $this->code{$i};
877
878 imagettftext($this->tmpimg, $font_size, $angle, $x, $y, $font_color, $this->ttf_file, $ch);
879
880 // estimate character widths to increment $x without creating spaces that are too large or too small
881 // these are best estimates to align text but may vary between fonts
882 // for optimal character widths, do not use multiple text colors or character angles and the complete string will be written by imagettftext
883 if (strpos('abcdeghknopqsuvxyz', $ch) !== false) {
884 $min_x = $font_size - ($this->iscale * 6);
885 $max_x = $font_size - ($this->iscale * 6);
886 } else if (strpos('ilI1', $ch) !== false) {
887 $min_x = $font_size / 5;
888 $max_x = $font_size / 3;
889 } else if (strpos('fjrt', $ch) !== false) {
890 $min_x = $font_size - ($this->iscale * 12);
891 $max_x = $font_size - ($this->iscale * 12);
892 } else if ($ch == 'wm') {
893 $min_x = $font_size;
894 $max_x = $font_size + ($this->iscale * 3);
895 } else { // numbers, capitals or unicode
896 $min_x = $font_size + ($this->iscale * 2);
897 $max_x = $font_size + ($this->iscale * 5);
898 }
899
900 $x += rand($min_x, $max_x);
901 } //for loop
902 } // angled or multi-color
903 } //else ttf font
904 //$this->im = $this->tmpimg;
905 //$this->output();
906 } //function
907
908 /**
909 * Warp text from temporary image onto final image.<br />
910 * Modified for securimage
911 *
912 * @access private
913 * @since 2.0
914 * @author Han-Kwang Nienhuys modified
915 * @copyright Han-Kwang Neinhuys
916 *
917 */
918 function distortedCopy()
919 {
920 $numpoles = 3; // distortion factor
921
922 // make array of poles AKA attractor points
923 for ($i = 0; $i < $numpoles; ++$i) {
924 $px[$i] = rand($this->image_width * 0.3, $this->image_width * 0.7);
925 $py[$i] = rand($this->image_height * 0.3, $this->image_height * 0.7);
926 $rad[$i] = rand($this->image_width * 0.4, $this->image_width * 0.7);
927 $tmp = -$this->frand() * 0.15 - 0.15;
928 $amp[$i] = $this->perturbation * $tmp;
929 }
930
931 $bgCol = imagecolorat($this->tmpimg, 0, 0);
932 $width2 = $this->iscale * $this->image_width;
933 $height2 = $this->iscale * $this->image_height;
934
935 imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across
936
937 // loop over $img pixels, take pixels from $tmpimg with distortion field
938 for ($ix = 0; $ix < $this->image_width; ++$ix) {
939 for ($iy = 0; $iy < $this->image_height; ++$iy) {
940 $x = $ix;
941 $y = $iy;
942
943 for ($i = 0; $i < $numpoles; ++$i) {
944 $dx = $ix - $px[$i];
945 $dy = $iy - $py[$i];
946 if ($dx == 0 && $dy == 0) continue;
947
948 $r = sqrt($dx * $dx + $dy * $dy);
949 if ($r > $rad[$i]) continue;
950
951 $rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]);
952 $x += $dx * $rscale;
953 $y += $dy * $rscale;
954 }
955
956 $c = $bgCol;
957 $x *= $this->iscale;
958 $y *= $this->iscale;
959
960 if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2) {
961 $c = imagecolorat($this->tmpimg, $x, $y);
962 }
963
964 if ($c != $bgCol) { // only copy pixels of letters to preserve any background image
965 imagesetpixel($this->im, $ix, $iy, $c);
966 }
967 }
968 }
969 }
970
971 /**
972 * Create a code and save to the session
973 *
974 * @access private
975 * @since 1.0.1
976 *
977 */
978 function createCode()
979 {
980 $this->code = false;
981
982 if ($this->use_wordlist && is_readable($this->wordlist_file)) {
983 $this->code = $this->readCodeFromFile();
984 }
985
986 if ($this->code == false) {
987 $this->code = $this->generateCode($this->code_length);
988 }
989
990 $this->saveData();
991 }
992
993 /**
994 * Generate a code
995 *
996 * @access private
997 * @param int $len The code length
998 * @return string
999 */
1000 function generateCode($len)
1001 {
1002 $code = '';
1003
1004 for($i = 1, $cslen = strlen($this->charset); $i <= $len; ++$i) {
1005 $code .= $this->charset{rand(0, $cslen - 1)};
1006 }
1007 return $code;
1008 }
1009
1010 /**
1011 * Reads a word list file to get a code
1012 *
1013 * @access private
1014 * @since 1.0.2
1015 * @return mixed false on failure, a word on success
1016 */
1017 function readCodeFromFile()
1018 {
1019 $fp = @fopen($this->wordlist_file, 'rb');
1020 if (!$fp) return false;
1021
1022 $fsize = filesize($this->wordlist_file);
1023 if ($fsize < 32) return false; // too small of a list to be effective
1024
1025 if ($fsize < 128) {
1026 $max = $fsize; // still pretty small but changes the range of seeking
1027 } else {
1028 $max = 128;
1029 }
1030
1031 fseek($fp, rand(0, $fsize - $max), SEEK_SET);
1032 $data = fread($fp, 128); // read a random 128 bytes from file
1033 fclose($fp);
1034 $data = preg_replace("/\r?\n/", "\n", $data);
1035
1036 $start = strpos($data, "\n", rand(0, 100)) + 1; // random start position
1037 $end = strpos($data, "\n", $start); // find end of word
1038
1039 return strtolower(substr($data, $start, $end - $start)); // return substring in 128 bytes
1040 }
1041
1042 /**
1043 * Output image to the browser
1044 *
1045 * @access private
1046 *
1047 */
1048 function output()
1049 {
1050 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
1051 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
1052 header("Cache-Control: no-store, no-cache, must-revalidate");
1053 header("Cache-Control: post-check=0, pre-check=0", false);
1054 header("Pragma: no-cache");
1055
1056 switch($this->image_type)
1057 {
1058 case SI_IMAGE_JPEG:
1059 header("Content-Type: image/jpeg");
1060 imagejpeg($this->im, null, 90);
1061 break;
1062
1063 case SI_IMAGE_GIF:
1064 header("Content-Type: image/gif");
1065 imagegif($this->im);
1066 break;
1067
1068 default:
1069 header("Content-Type: image/png");
1070 imagepng($this->im);
1071 break;
1072 }
1073
1074 imagedestroy($this->im);
1075 exit;
1076 }
1077
1078 /**
1079 * Get WAV or MP3 file data of the spoken code.<br />
1080 * This is appropriate for output to the browser as audio/x-wav or audio/mpeg
1081 *
1082 * @since 1.0.1
1083 * @return string WAV or MP3 data
1084 *
1085 */
1086 function getAudibleCode($format = 'wav')
1087 {
1088 $letters = array();
1089 $code = $this->getCode();
1090
1091 if ($code == '') {
1092 $this->createCode();
1093 $code = $this->getCode();
1094 }
1095
1096 for($i = 0; $i < strlen($code); ++$i) {
1097 $letters[] = $code{$i};
1098 }
1099
1100 if ($format == 'mp3') {
1101 return $this->generateMP3($letters);
1102 } else {
1103 return $this->generateWAV($letters);
1104 }
1105 }
1106
1107 /**
1108 * Set the path to the audio directory.<br />
1109 *
1110 * @since 1.0.4
1111 * @return bool true if the directory exists and is readble, false if not
1112 */
1113 function setAudioPath($audio_directory)
1114 {
1115 if (is_dir($audio_directory) && is_readable($audio_directory)) {
1116 $this->audio_path = $audio_directory;
1117 return true;
1118 } else {
1119 return false;
1120 }
1121 }
1122
1123 /**
1124 * Save the code in the session
1125 *
1126 * @access private
1127 *
1128 */
1129 function saveData()
1130 {
1131 $_SESSION['securimage_code_value'] = strtolower($this->code);
1132 $_SESSION['securimage_code_ctime'] = time();
1133
1134 $this->saveCodeToDatabase();
1135 }
1136
1137 /**
1138 * Validate the code to the user code
1139 *
1140 * @access private
1141 *
1142 */
1143 function validate()
1144 {
1145 // retrieve code from session, if no code exists check sqlite database if supported.
1146
1147 if (isset($_SESSION['securimage_code_value']) && trim($_SESSION['securimage_code_value']) != '') {
1148 if ($this->isCodeExpired($_SESSION['securimage_code_ctime']) == false) {
1149 $code = $_SESSION['securimage_code_value'];
1150 }
1151 } else if ($this->use_sqlite_db == true && function_exists('sqlite_open')) { // no code in session - may mean user has cookies turned off
1152 $this->openDatabase();
1153 $code = $this->getCodeFromDatabase();
1154 } else {
1155 // session code invalid or non-existant and code not found in sqlite db or sqlite is not available
1156 $code = '';
1157 }
1158
1159 $code = trim(strtolower($code));
1160 $code_entered = trim(strtolower($this->code_entered));
1161 $this->correct_code = false;
1162
1163 if ($code != '') {
1164 if ($code == $code_entered) {
1165 $this->correct_code = true;
1166 $_SESSION['securimage_code_value'] = '';
1167 $_SESSION['securimage_code_ctime'] = '';
1168 $this->clearCodeFromDatabase();
1169 }
1170 }
1171 }
1172
1173 /**
1174 * Get the captcha code
1175 *
1176 * @since 1.0.1
1177 * @return string
1178 */
1179 function getCode()
1180 {
1181 if (isset($_SESSION['securimage_code_value']) && !empty($_SESSION['securimage_code_value'])) {
1182 return strtolower($_SESSION['securimage_code_value']);
1183 } else {
1184 if ($this->sqlite_handle == false) $this->openDatabase();
1185
1186 return $this->getCodeFromDatabase(); // attempt to get from database, returns empty string if sqlite is not available or disabled
1187 }
1188 }
1189
1190 /**
1191 * Check if the user entered code was correct
1192 *
1193 * @access private
1194 * @return boolean
1195 */
1196 function checkCode()
1197 {
1198 return $this->correct_code;
1199 }
1200
1201 /**
1202 * Generate a wav file by concatenating individual files
1203 *
1204 * @since 1.0.1
1205 * @access private
1206 * @param array $letters Array of letters to build a file from
1207 * @return string WAV file data
1208 */
1209 function generateWAV($letters)
1210 {
1211 $data_len = 0;
1212 $files = array();
1213 $out_data = '';
1214
1215 foreach ($letters as $letter) {
1216 $filename = $this->audio_path . strtoupper($letter) . '.wav';
1217
1218 $fp = fopen($filename, 'rb');
1219
1220 $file = array();
1221
1222 $data = fread($fp, filesize($filename)); // read file in
1223
1224 $header = substr($data, 0, 36);
1225 $body = substr($data, 44);
1226
1227
1228 $data = unpack('NChunkID/VChunkSize/NFormat/NSubChunk1ID/VSubChunk1Size/vAudioFormat/vNumChannels/VSampleRate/VByteRate/vBlockAlign/vBitsPerSample', $header);
1229
1230 $file['sub_chunk1_id'] = $data['SubChunk1ID'];
1231 $file['bits_per_sample'] = $data['BitsPerSample'];
1232 $file['channels'] = $data['NumChannels'];
1233 $file['format'] = $data['AudioFormat'];
1234 $file['sample_rate'] = $data['SampleRate'];
1235 $file['size'] = $data['ChunkSize'] + 8;
1236 $file['data'] = $body;
1237
1238 if ( ($p = strpos($file['data'], 'LIST')) !== false) {
1239 // If the LIST data is not at the end of the file, this will probably break your sound file
1240 $info = substr($file['data'], $p + 4, 8);
1241 $data = unpack('Vlength/Vjunk', $info);
1242 $file['data'] = substr($file['data'], 0, $p);
1243 $file['size'] = $file['size'] - (strlen($file['data']) - $p);
1244 }
1245
1246 $files[] = $file;
1247 $data = null;
1248 $header = null;
1249 $body = null;
1250
1251 $data_len += strlen($file['data']);
1252
1253 fclose($fp);
1254 }
1255
1256 $out_data = '';
1257 for($i = 0; $i < sizeof($files); ++$i) {
1258 if ($i == 0) { // output header
1259 $out_data .= pack('C4VC8', ord('R'), ord('I'), ord('F'), ord('F'), $data_len + 36, ord('W'), ord('A'), ord('V'), ord('E'), ord('f'), ord('m'), ord('t'), ord(' '));
1260
1261 $out_data .= pack('VvvVVvv',
1262 16,
1263 $files[$i]['format'],
1264 $files[$i]['channels'],
1265 $files[$i]['sample_rate'],
1266 $files[$i]['sample_rate'] * (($files[$i]['bits_per_sample'] * $files[$i]['channels']) / 8),
1267 ($files[$i]['bits_per_sample'] * $files[$i]['channels']) / 8,
1268 $files[$i]['bits_per_sample'] );
1269
1270 $out_data .= pack('C4', ord('d'), ord('a'), ord('t'), ord('a'));
1271
1272 $out_data .= pack('V', $data_len);
1273 }
1274
1275 $out_data .= $files[$i]['data'];
1276 }
1277
1278 $this->scrambleAudioData($out_data, 'wav');
1279 return $out_data;
1280 }
1281
1282 /**
1283 * Randomly modify the audio data to scramble sound and prevent binary recognition.<br />
1284 * Take care not to "break" the audio file by leaving the header data intact.
1285 *
1286 * @since 2.0
1287 * @access private
1288 * @param $data Sound data in mp3 of wav format
1289 */
1290 function scrambleAudioData(&$data, $format)
1291 {
1292 if ($format == 'wav') {
1293 $start = strpos($data, 'data') + 4; // look for "data" indicator
1294 if ($start === false) $start = 44; // if not found assume 44 byte header
1295 } else { // mp3
1296 $start = 4; // 4 byte (32 bit) frame header
1297 }
1298
1299 $start += rand(1, 64); // randomize starting offset
1300 $datalen = strlen($data) - $start - 256; // leave last 256 bytes unchanged
1301
1302 for ($i = $start; $i < $datalen; $i += 64) {
1303 $ch = ord($data{$i});
1304 if ($ch < 9 || $ch > 119) continue;
1305
1306 $data{$i} = chr($ch + rand(-8, 8));
1307 }
1308 }
1309
1310 /**
1311 * Generate an mp3 file by concatenating individual files
1312 * @since 1.0.4
1313 * @access private
1314 * @param array $letters Array of letters to build a file from
1315 * @return string MP3 file data
1316 */
1317 function generateMP3($letters)
1318 {
1319 $data_len = 0;
1320 $files = array();
1321 $out_data = '';
1322
1323 foreach ($letters as $letter) {
1324 $filename = $this->audio_path . strtoupper($letter) . '.mp3';
1325
1326 $fp = fopen($filename, 'rb');
1327 $data = fread($fp, filesize($filename)); // read file in
1328
1329 $this->scrambleAudioData($data, 'mp3');
1330 $out_data .= $data;
1331
1332 fclose($fp);
1333 }
1334
1335
1336 return $out_data;
1337 }
1338
1339 /**
1340 * Generate random number less than 1
1341 * @since 2.0
1342 * @access private
1343 * @return float
1344 */
1345 function frand()
1346 {
1347 return 0.0001*rand(0,9999);
1348 }
1349
1350 /**
1351 * Print signature text on image
1352 *
1353 * @since 2.0
1354 * @access private
1355 *
1356 */
1357 function addSignature()
1358 {
1359 if ($this->use_gd_font) {
1360 imagestring($this->im, 5, $this->image_width - (strlen($this->image_signature) * 10), $this->image_height - 20, $this->image_signature, $this->gdsignaturecolor);
1361 } else {
1362
1363 $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
1364 $textlen = $bbox[2] - $bbox[0];
1365 $x = $this->image_width - $textlen - 5;
1366 $y = $this->image_height - 3;
1367
1368 imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
1369 }
1370 }
1371
1372 /**
1373 * Get hashed IP address of remote user
1374 *
1375 * @access private
1376 * @since 2.0.1
1377 * @return string
1378 */
1379 function getIPHash()
1380 {
1381 return strtolower(md5($_SERVER['REMOTE_ADDR']));
1382 }
1383
1384 /**
1385 * Open SQLite database
1386 *
1387 * @access private
1388 * @since 2.0.1
1389 * @return bool true if database was opened successfully
1390 */
1391 function openDatabase()
1392 {
1393 $this->sqlite_handle = false;
1394
1395 if ($this->use_sqlite_db && function_exists('sqlite_open')) {
1396 $this->sqlite_handle = sqlite_open($this->sqlite_database, 0666, $error);
1397
1398 if ($this->sqlite_handle !== false) {
1399 $res = sqlite_query($this->sqlite_handle, "PRAGMA table_info(codes)");
1400 if (sqlite_num_rows($res) == 0) {
1401 sqlite_query($this->sqlite_handle, "CREATE TABLE codes (iphash VARCHAR(32) PRIMARY KEY, code VARCHAR(32) NOT NULL, created INTEGER)");
1402 }
1403 }
1404
1405 return $this->sqlite_handle != false;
1406 }
1407
1408 return $this->sqlite_handle;
1409 }
1410
1411 /**
1412 * Save captcha code to sqlite database
1413 *
1414 * @access private
1415 * @since 2.0.1
1416 * @return bool true if code was saved, false if not
1417 */
1418 function saveCodeToDatabase()
1419 {
1420 $success = false;
1421
1422 $this->openDatabase();
1423
1424 if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
1425 $ip = $this->getIPHash();
1426 $time = time();
1427 $code = $_SESSION['securimage_code_value']; // hash code for security - if cookies are disabled the session still exists at this point
1428 $success = sqlite_query($this->sqlite_handle, "INSERT OR REPLACE INTO codes(iphash, code, created) VALUES('$ip', '$code', $time)");
1429 }
1430
1431 return $success !== false;
1432 }
1433
1434 /**
1435 * Get stored captcha code from sqlite database based on ip address hash
1436 *
1437 * @access private
1438 * @since 2.0.1
1439 * @return string captcha code
1440 */
1441 function getCodeFromDatabase()
1442 {
1443 $code = '';
1444
1445 if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
1446 $ip = $this->getIPHash();
1447
1448 $res = sqlite_query($this->sqlite_handle, "SELECT * FROM codes WHERE iphash = '$ip'");
1449 if ($res && sqlite_num_rows($res) > 0) {
1450 $res = sqlite_fetch_array($res);
1451
1452 if ($this->isCodeExpired($res['created']) == false) {
1453 $code = $res['code'];
1454 }
1455 }
1456 }
1457
1458 return $code;
1459 }
1460
1461 /**
1462 * Delete a code from the database by ip address hash
1463 *
1464 * @access private
1465 * @since 2.0.1
1466 */
1467 function clearCodeFromDatabase()
1468 {
1469 if ($this->sqlite_handle !== false) {
1470 $ip = $this->getIPHash();
1471
1472 sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE iphash = '$ip'");
1473 }
1474 }
1475
1476 /**
1477 * Purge codes over a day old from database
1478 *
1479 * @access private
1480 * @since 2.0.1
1481 */
1482 function purgeOldCodesFromDatabase()
1483 {
1484 if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
1485 $now = time();
1486 $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
1487
1488 sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE $now - created > $limit");
1489 }
1490 }
1491
1492 /**
1493 * Check a code to see if it is expired based on creation time
1494 *
1495 * @access private
1496 * @since 2.0.1
1497 * @param $creation_time unix timestamp of code creation time
1498 * @return bool true if code has expired, false if not
1499 */
1500 function isCodeExpired($creation_time)
1501 {
1502 $expired = true;
1503
1504 if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
1505 $expired = false;
1506 } else if (time() - $creation_time < $this->expiry_time) {
1507 $expired = false;
1508 }
1509
1510 return $expired;
1511 }
1512
1513} /* class Securimage */
1514
1515
1516/**
1517 * Color object for Securimage CAPTCHA
1518 *
1519 * @since 2.0
1520 * @package Securimage
1521 * @subpackage classes
1522 *
1523 */
1524class Securimage_Color {
1525 /**
1526 * Red component: 0-255
1527 *
1528 * @var int
1529 */
1530 var $r;
1531 /**
1532 * Green component: 0-255
1533 *
1534 * @var int
1535 */
1536 var $g;
1537 /**
1538 * Blue component: 0-255
1539 *
1540 * @var int
1541 */
1542 var $b;
1543
1544 /**
1545 * Create a new Securimage_Color object.<br />
1546 * Specify the red, green, and blue components using their HTML hex code equivalent.<br />
1547 * Example: The code for the HTML color #4A203C is:<br />
1548 * $color = new Securimage_Color(0x4A, 0x20, 0x3C);
1549 *
1550 * @param $red Red component 0-255
1551 * @param $green Green component 0-255
1552 * @param $blue Blue component 0-255
1553 */
1554 function Securimage_Color($red, $green = null, $blue = null)
1555 {
1556 if ($green == null && $blue == null && preg_match('/^#[a-f0-9]{3,6}$/i', $red)) {
1557 $col = substr($red, 1);
1558 if (strlen($col) == 3) {
1559 $red = str_repeat(substr($col, 0, 1), 2);
1560 $green = str_repeat(substr($col, 1, 1), 2);
1561 $blue = str_repeat(substr($col, 2, 1), 2);
1562 } else {
1563 $red = substr($col, 0, 2);
1564 $green = substr($col, 2, 2);
1565 $blue = substr($col, 4, 2);
1566 }
1567
1568 $red = hexdec($red);
1569 $green = hexdec($green);
1570 $blue = hexdec($blue);
1571 } else {
1572 if ($red < 0) $red = 0;
1573 if ($red > 255) $red = 255;
1574 if ($green < 0) $green = 0;
1575 if ($green > 255) $green = 255;
1576 if ($blue < 0) $blue = 0;
1577 if ($blue > 255) $blue = 255;
1578 }
1579
1580 $this->r = $red;
1581 $this->g = $green;
1582 $this->b = $blue;
1583 }
1584}