source: extensions/CryptograPHP/securimage/securimage.php @ 26554

Last change on this file since 26554 was 26554, checked in by mistic100, 10 years ago

update Securimage to 3.5.1, new font, new preset, allow to use backgrounds

File size: 75.4 KB
RevLine 
[12617]1<?php
[26554]2
3// error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
4
[12617]5/**
[26554]6 * Project:     Securimage: A PHP class for creating and managing form CAPTCHA images<br />
7 * File:        securimage.php<br />
[12617]8 *
[26554]9 * Copyright (c) 2013, Drew Phillips
[12617]10 * All rights reserved.
[26554]11 *
[12617]12 * Redistribution and use in source and binary forms, with or without modification,
13 * are permitted provided that the following conditions are met:
[26554]14 *
[12617]15 *  - Redistributions of source code must retain the above copyright notice,
16 *    this list of conditions and the following disclaimer.
17 *  - Redistributions in binary form must reproduce the above copyright notice,
18 *    this list of conditions and the following disclaimer in the documentation
19 *    and/or other materials provided with the distribution.
[26554]20 *
[12617]21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 *
33 * Any modifications to the library should be indicated clearly in the source code
[26554]34 * to inform users that the changes are not a part of the original software.<br /><br />
[12617]35 *
[26554]36 * If you found this script useful, please take a quick moment to rate it.<br />
[12617]37 * http://www.hotscripts.com/rate/49400.html  Thanks.
38 *
39 * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
40 * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
41 * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
[26554]42 * @copyright 2013 Drew Phillips
[12617]43 * @author Drew Phillips <drew@drew-phillips.com>
[26554]44 * @version 3.5.1 (June 21, 2013)
[12617]45 * @package Securimage
46 *
47 */
48
49/**
50 ChangeLog
[26554]51
52 3.5.1
53 - Fix XSS vulnerability in example_form.php (discovered by Gjoko Krstic - <gjoko@zeroscience.mk>)
54
55 3.5
56 - Release new version
57 - MB string support for charlist
58 - Modify audio file path to use language directories
59 - Changed default captcha appearance
60
61 3.2RC4
62 - Add MySQL, PostgreSQL, and SQLite3 support for database storage
63 - Deprecate "use_sqlite_db" option and remove SQLite2/sqlite_* functions
64 - Add new captcha type that displays 2 dictionary words on one image
65 - Update examples
66
67 3.2RC3
68 - Fix canSendHeaders() check which was breaking if a PHP startup error was issued
69
70 3.2RC2
71 - Add error handler (https://github.com/dapphp/securimage/issues/15)
72 - Fix flash examples to use the correct value name for audio parameter
73
74 3.2RC1
75 - New audio captcha code.  Faster, fully dynamic audio, full WAV support
76   (Paul Voegler, Drew Phillips) <http://voegler.eu/pub/audio>
77 - New Flash audio streaming button.  User defined image and size supported
78 - Additional options for customizing captcha (noise_level, send_headers,
79   no_exit, no_session, display_value
80 - Add captcha ID support.  Uses sqlite and unique captcha IDs to track captchas,
81   no session used
82 - Add static methods for creating and validating captcha by ID
83 - Automatic clearing of old codes from SQLite database
84
85 3.0.3Beta
86 - Add improved mixing function to WavFile class (Paul Voegler)
87 - Improve performance and security of captcha audio (Paul Voegler, Drew Phillips)
88 - Add option to use random file as background noise in captcha audio
89 - Add new securimage options for audio files
90
91 3.0.2Beta
92 - Fix issue with session variables when upgrading from 2.0 - 3.0
93 - Improve audio captcha, switch to use WavFile class, make mathematical captcha audio work
94
95 3.0.1
96 - Bugfix: removed use of deprecated variable in addSignature method that would cause errors with display_errors on
97
[12617]98 3.0
99 - Rewrite class using PHP5 OOP
100 - Remove support for GD fonts, require FreeType
101 - Remove support for multi-color codes
102 - Add option to make codes case-sensitive
103 - Add namespaces to support multiple captchas on a single page or page specific captchas
104 - Add option to show simple math problems instead of codes
105 - Remove support for mp3 files due to vulnerability in decoding mp3 audio files
106 - Create new flash file to stream wav files instead of mp3
107 - Changed to BSD license
108
109 2.0.2
110 - Fix pathing to make integration into libraries easier (Nathan Phillip Brink ohnobinki@ohnopublishing.net)
111
112 2.0.1
113 - Add support for browsers with cookies disabled (requires php5, sqlite) maps users to md5 hashed ip addresses and md5 hashed codes for security
114 - 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)
115 - Check for previous definition of image type constants (Mike Challis)
116 - Fix mime type settings for audio output
117 - Fixed color allocation issues with multiple colors and background images, consolidate allocation to one function
118 - Ability to let codes expire after a given length of time
119 - Allow HTML color codes to be passed to Securimage_Color (suggested by Mike Challis)
120
121 2.0.0
122 - Add mathematical distortion to characters (using code from HKCaptcha)
123 - Improved session support
124 - Added Securimage_Color class for easier color definitions
125 - Add distortion to audio output to prevent binary comparison attack (proposed by Sven "SavageTiger" Hagemann [insecurity.nl])
126 - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net)
127 - Audio output is mp3 format by default
128 - Change font to AlteHaasGrotesk by yann le coroller
[26554]129 - Some code cleanup
[12617]130
131 1.0.4 (unreleased)
132 - Ability to output audible codes in mp3 format to stream from flash
133
134 1.0.3.1
135 - Error reading from wordlist in some cases caused words to be cut off 1 letter short
136
137 1.0.3
138 - Removed shadow_text from code which could cause an undefined property error due to removal from previous version
139
140 1.0.2
141 - Audible CAPTCHA Code wav files
142 - Create codes from a word list instead of random strings
143
144 1.0
145 - Added the ability to use a selected character set, rather than a-z0-9 only.
146 - Added the multi-color text option to use different colors for each letter.
147 - Switched to automatic session handling instead of using files for code storage
148 - Added GD Font support if ttf support is not available.  Can use internal GD fonts or load new ones.
149 - Added the ability to set line thickness
150 - Added option for drawing arced lines over letters
151 - Added ability to choose image type for output
152
153 */
154
155
156/**
157 * Securimage CAPTCHA Class.
158 *
[26554]159 * @version    3.5
[12617]160 * @package    Securimage
161 * @subpackage classes
162 * @author     Drew Phillips <drew@drew-phillips.com>
163 *
164 */
165class Securimage
166{
[26554]167    // All of the public variables below are securimage options
168    // They can be passed as an array to the Securimage constructor, set below,
169    // or set from securimage_show.php and securimage_play.php
170
[12617]171    /**
172     * Renders captcha as a JPEG image
173     * @var int
174     */
175    const SI_IMAGE_JPEG = 1;
176    /**
177     * Renders captcha as a PNG image (default)
178     * @var int
179     */
180    const SI_IMAGE_PNG  = 2;
181    /**
182     * Renders captcha as a GIF image
183     * @var int
184     */
185    const SI_IMAGE_GIF  = 3;
[26554]186
[12617]187    /**
188     * Create a normal alphanumeric captcha
189     * @var int
190     */
191    const SI_CAPTCHA_STRING     = 0;
192    /**
193     * Create a captcha consisting of a simple math problem
194     * @var int
195     */
196    const SI_CAPTCHA_MATHEMATIC = 1;
197    /**
[26554]198     * Create a word based captcha using 2 words
199     * @var int
200     */
201    const SI_CAPTCHA_WORDS      = 2;
202
203    /**
204     * MySQL option identifier for database storage option
205     *
206     * @var string
207     */
208    const SI_DRIVER_MYSQL   = 'mysql';
209
210    /**
211     * PostgreSQL option identifier for database storage option
212     *
213     * @var string
214     */
215    const SI_DRIVER_PGSQL   = 'pgsql';
216
217    /**
218     * SQLite option identifier for database storage option
219     *
220     * @var string
221     */
222    const SI_DRIVER_SQLITE3 = 'sqlite';
223
224    /*%*********************************************************************%*/
225    // Properties
226
227    /**
[12617]228     * The width of the captcha image
229     * @var int
230     */
231    public $image_width = 215;
232    /**
233     * The height of the captcha image
234     * @var int
235     */
236    public $image_height = 80;
237    /**
238     * The type of the image, default = png
239     * @var int
240     */
241    public $image_type   = self::SI_IMAGE_PNG;
242
243    /**
244     * The background color of the captcha
245     * @var Securimage_Color
246     */
247    public $image_bg_color = '#ffffff';
248    /**
249     * The color of the captcha text
250     * @var Securimage_Color
251     */
252    public $text_color     = '#707070';
253    /**
254     * The color of the lines over the captcha
255     * @var Securimage_Color
256     */
257    public $line_color     = '#707070';
258    /**
259     * The color of the noise that is drawn
[26554]260     * @var Securimage_Color
[12617]261     */
262    public $noise_color    = '#707070';
[26554]263
[12617]264    /**
265     * How transparent to make the text 0 = completely opaque, 100 = invisible
266     * @var int
267     */
[26554]268    public $text_transparency_percentage = 20;
[12617]269    /**
270     * Whether or not to draw the text transparently, true = use transparency, false = no transparency
271     * @var bool
272     */
[26554]273    public $use_transparent_text         = true;
274
[12617]275    /**
276     * The length of the captcha code
277     * @var int
278     */
279    public $code_length    = 6;
280    /**
281     * Whether the captcha should be case sensitive (not recommended, use only for maximum protection)
282     * @var bool
283     */
284    public $case_sensitive = false;
285    /**
286     * The character set to use for generating the captcha code
287     * @var string
288     */
289    public $charset        = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789';
290    /**
291     * How long in seconds a captcha remains valid, after this time it will not be accepted
292     * @var unknown_type
293     */
294    public $expiry_time    = 900;
[26554]295
[12617]296    /**
297     * The session name securimage should use, only set this if your application uses a custom session name
298     * It is recommended to set this value below so it is used by all securimage scripts
299     * @var string
300     */
301    public $session_name   = null;
[26554]302
[12617]303    /**
304     * true to use the wordlist file, false to generate random captcha codes
305     * @var bool
306     */
307    public $use_wordlist   = false;
308
309    /**
310     * The level of distortion, 0.75 = normal, 1.0 = very high distortion
311     * @var double
312     */
[26554]313    public $perturbation = 0.85;
[12617]314    /**
315     * How many lines to draw over the captcha code to increase security
316     * @var int
317     */
[26554]318    public $num_lines    = 5;
[12617]319    /**
320     * The level of noise (random dots) to place on the image, 0-10
321     * @var int
322     */
[26554]323    public $noise_level  = 2;
324
[12617]325    /**
326     * The signature text to draw on the bottom corner of the image
327     * @var string
328     */
329    public $image_signature = '';
330    /**
331     * The color of the signature text
332     * @var Securimage_Color
333     */
334    public $signature_color = '#707070';
335    /**
336     * The path to the ttf font file to use for the signature text, defaults to $ttf_file (AHGBold.ttf)
337     * @var string
338     */
339    public $signature_font;
[26554]340
[12617]341    /**
[26554]342     * DO NOT USE!!!
[12617]343     * Use an SQLite database to store data (for users that do not support cookies)
344     * @var bool
[26554]345     * @see Securimage::$use_sqlite_db
346     * @deprecated 3.2RC4
[12617]347     */
348    public $use_sqlite_db = false;
[26554]349
[12617]350    /**
[26554]351     * Use a database backend for code storage.
352     * Provides a fallback to users with cookies disabled.
353     * Required when using captcha IDs.
354     *
355     * @see Securimage::$database_driver
356     * @var bool
357     */
358    public $use_database = false;
359
360    /**
361     * Database driver to use for database support.
362     * Allowable values: 'mysql', 'pgsql', 'sqlite'.
363     * Default: sqlite
364     *
365     * @var string
366     */
367    public $database_driver = self::SI_DRIVER_SQLITE3;
368
369    /**
370     * Database host to connect to when using mysql or postgres
371     * On Linux use "localhost" for Unix domain socket, otherwise uses TCP/IP
372     * Does not apply to SQLite
373     *
374     * @var string
375     */
376    public $database_host   = 'localhost';
377
378    /**
379     * Database username for connection (mysql, postgres only)
380     * Default is an empty string
381     *
382     * @var string
383     */
384    public $database_user   = '';
385
386    /**
387     * Database password for connection (mysql, postgres only)
388     * Default is empty string
389     *
390     * @var string
391     */
392    public $database_pass   = '';
393
394    /**
395     * Name of the database to select (mysql, postgres only)
396     *
397     * @see Securimage::$database_file for SQLite
398     * @var string
399     */
400    public $database_name   = '';
401
402    /**
403     * Database table where captcha codes are stored
404     * Note: Securimage will attempt to create this table for you if it does
405     * not exist.  If the table cannot be created, an E_USER_WARNING is emitted.
406     *
407     * @var string
408     */
409    public $database_table  = 'captcha_codes';
410
411    /**
412     * Fully qualified path to the database file when using SQLite3.
413     * This value is only used when $database_driver == sqlite3 and does
414     * not apply when no database is used, or when using MySQL or PostgreSQL.
415     *
416     * @var string
417     */
418    public $database_file;
419
420    /**
[12617]421     * The type of captcha to create, either alphanumeric, or a math problem<br />
422     * Securimage::SI_CAPTCHA_STRING or Securimage::SI_CAPTCHA_MATHEMATIC
423     * @var int
424     */
[26554]425    public $captcha_type  = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC;
426
[12617]427    /**
428     * The captcha namespace, use this if you have multiple forms on a single page, blank if you do not use multiple forms on one page
429     * @var string
430     * <code>
431     * <?php
432     * // in securimage_show.php (create one show script for each form)
433     * $img->namespace = 'contact_form';
[26554]434     *
[12617]435     * // in form validator
436     * $img->namespace = 'contact_form';
437     * if ($img->check($code) == true) {
438     *     echo "Valid!";
439     *  }
440     * </code>
441     */
442    public $namespace;
[26554]443
[12617]444    /**
445     * The font file to use to draw the captcha code, leave blank for default font AHGBold.ttf
446     * @var string
447     */
448    public $ttf_file;
449    /**
450     * The path to the wordlist file to use, leave blank for default words/words.txt
451     * @var string
452     */
453    public $wordlist_file;
454    /**
455     * The directory to scan for background images, if set a random background will be chosen from this folder
456     * @var string
457     */
458    public $background_directory;
459    /**
460     * The path to the SQLite database file to use, if $use_sqlite_database = true, should be chmod 666
[26554]461     * @deprecated 3.2RC4
[12617]462     * @var string
463     */
464    public $sqlite_database;
465    /**
466     * The path to the securimage audio directory, can be set in securimage_play.php
467     * @var string
468     * <code>
[26554]469     * $img->audio_path = '/home/yoursite/public_html/securimage/audio/en/';
[12617]470     * </code>
471     */
472    public $audio_path;
[26554]473    /**
474     * The path to the directory containing audio files that will be selected
475     * randomly and mixed with the captcha audio.
476     *
477     * @var string
478     */
479    public $audio_noise_path;
480    /**
481     * Whether or not to mix background noise files into captcha audio (true = mix, false = no)
482     * Mixing random background audio with noise can help improve security of audio captcha.
483     * Default: securimage/audio/noise
484     *
485     * @since 3.0.3
486     * @see Securimage::$audio_noise_path
487     * @var bool
488     */
489    public $audio_use_noise;
490    /**
491     * The method and threshold (or gain factor) used to normalize the mixing with background noise.
492     * See http://www.voegler.eu/pub/audio/ for more information.
493     *
494     * Valid: <ul>
495     *     <li> >= 1 - Normalize by multiplying by the threshold (boost - positive gain). <br />
496     *            A value of 1 in effect means no normalization (and results in clipping). </li>
497     *     <li> <= -1 - Normalize by dividing by the the absolute value of threshold (attenuate - negative gain). <br />
498     *            A factor of 2 (-2) is about 6dB reduction in volume.</li>
499     *     <li> [0, 1) - (open inverval - not including 1) - The threshold
500     *            above which amplitudes are comressed logarithmically. <br />
501     *            e.g. 0.6 to leave amplitudes up to 60% "as is" and compress above. </li>
502     *     <li> (-1, 0) - (open inverval - not including -1 and 0) - The threshold
503     *            above which amplitudes are comressed linearly. <br />
504     *            e.g. -0.6 to leave amplitudes up to 60% "as is" and compress above. </li></ul>
505     *
506     * Default: 0.6
507     *
508     * @since 3.0.4
509     * @var float
510     */
511    public $audio_mix_normalization = 0.6;
512    /**
513     * Whether or not to degrade audio by introducing random noise (improves security of audio captcha)
514     * Default: true
515     *
516     * @since 3.0.3
517     * @var bool
518     */
519    public $degrade_audio;
520    /**
521     * Minimum delay to insert between captcha audio letters in milliseconds
522     *
523     * @since 3.0.3
524     * @var float
525     */
526    public $audio_gap_min = 0;
527    /**
528     * Maximum delay to insert between captcha audio letters in milliseconds
529     *
530     * @since 3.0.3
531     * @var float
532     */
533    public $audio_gap_max = 600;
[12617]534
[26554]535    /**
536     * Captcha ID if using static captcha
537     * @var string Unique captcha id
538     */
539    protected static $_captchaId = null;
540
[12617]541    protected $im;
542    protected $tmpimg;
543    protected $bgimg;
544    protected $iscale = 5;
[26554]545
546    public $securimage_path = null;
547
548    /**
549     * The captcha challenge value (either the case-sensitive/insensitive word captcha, or the solution to the math captcha)
550     *
551     * @var string Captcha challenge value
552     */
[12617]553    protected $code;
[26554]554
555    /**
556     * The display value of the captcha to draw on the image (the word captcha, or the math equation to present to the user)
557     *
558     * @var string Captcha display value to draw on the image
559     */
[12617]560    protected $code_display;
[26554]561
562    /**
563     * A value that can be passed to the constructor that can be used to generate a captcha image with a given value
564     * This value does not get stored in the session or database and is only used when calling Securimage::show().
565     * If a display_value was passed to the constructor and the captcha image is generated, the display_value will be used
566     * as the string to draw on the captcha image.  Used only if captcha codes are generated and managed by a 3rd party app/library
567     *
568     * @var string Captcha code value to display on the image
569     */
570    public $display_value;
571
572    /**
573     * Captcha code supplied by user [set from Securimage::check()]
574     *
575     * @var string
576     */
[12617]577    protected $captcha_code;
[26554]578
579    /**
580     * Flag that can be specified telling securimage not to call exit after generating a captcha image or audio file
581     *
582     * @var bool If true, script will not terminate; if false script will terminate (default)
583     */
584    protected $no_exit;
585
586    /**
587     * Flag indicating whether or not a PHP session should be started and used
588     *
589     * @var bool If true, no session will be started; if false, session will be started and used to store data (default)
590     */
591    protected $no_session;
592
593    /**
594     * Flag indicating whether or not HTTP headers will be sent when outputting captcha image/audio
595     *
596     * @var bool If true (default) headers will be sent, if false, no headers are sent
597     */
598    protected $send_headers;
599
600    /**
601     * PDO connection when a database is used
602     *
603     * @var resource
604     */
605    protected $pdo_conn;
606
607    // gd color resources that are allocated for drawing the image
[12617]608    protected $gdbgcolor;
609    protected $gdtextcolor;
610    protected $gdlinecolor;
611    protected $gdsignaturecolor;
[26554]612
[12617]613    /**
614     * Create a new securimage object, pass options to set in the constructor.<br />
615     * This can be used to display a captcha, play an audible captcha, or validate an entry
616     * @param array $options
617     * <code>
618     * $options = array(
619     *     'text_color' => new Securimage_Color('#013020'),
620     *     'code_length' => 5,
621     *     'num_lines' => 5,
622     *     'noise_level' => 3,
623     *     'font_file' => Securimage::getPath() . '/custom.ttf'
624     * );
[26554]625     *
[12617]626     * $img = new Securimage($options);
627     * </code>
628     */
629    public function __construct($options = array())
630    {
631        $this->securimage_path = dirname(__FILE__);
[26554]632
[12617]633        if (is_array($options) && sizeof($options) > 0) {
634            foreach($options as $prop => $val) {
[26554]635                if ($prop == 'captchaId') {
636                    Securimage::$_captchaId = $val;
637                    $this->use_database     = true;
638                } else if ($prop == 'use_sqlite_db') {
639                    trigger_error("The use_sqlite_db option is deprecated, use 'use_database' instead", E_USER_NOTICE);
640                } else {
641                    $this->$prop = $val;
642                }
[12617]643            }
644        }
645
646        $this->image_bg_color  = $this->initColor($this->image_bg_color,  '#ffffff');
647        $this->text_color      = $this->initColor($this->text_color,      '#616161');
648        $this->line_color      = $this->initColor($this->line_color,      '#616161');
649        $this->noise_color     = $this->initColor($this->noise_color,     '#616161');
650        $this->signature_color = $this->initColor($this->signature_color, '#616161');
651
[26554]652        if (is_null($this->ttf_file)) {
[12617]653            $this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
654        }
[26554]655
[12617]656        $this->signature_font = $this->ttf_file;
[26554]657
658        if (is_null($this->wordlist_file)) {
[12617]659            $this->wordlist_file = $this->securimage_path . '/words/words.txt';
660        }
[26554]661
662        if (is_null($this->database_file)) {
663            $this->database_file = $this->securimage_path . '/database/securimage.sq3';
[12617]664        }
[26554]665
666        if (is_null($this->audio_path)) {
667            $this->audio_path = $this->securimage_path . '/audio/en/';
[12617]668        }
[26554]669
670        if (is_null($this->audio_noise_path)) {
671            $this->audio_noise_path = $this->securimage_path . '/audio/noise/';
672        }
673
674        if (is_null($this->audio_use_noise)) {
675            $this->audio_use_noise = true;
676        }
677
678        if (is_null($this->degrade_audio)) {
679            $this->degrade_audio = true;
680        }
681
682        if (is_null($this->code_length) || (int)$this->code_length < 1) {
[12617]683            $this->code_length = 6;
684        }
[26554]685
686        if (is_null($this->perturbation) || !is_numeric($this->perturbation)) {
[12617]687            $this->perturbation = 0.75;
688        }
[26554]689
690        if (is_null($this->namespace) || !is_string($this->namespace)) {
[12617]691            $this->namespace = 'default';
692        }
693
[26554]694        if (is_null($this->no_exit)) {
695            $this->no_exit = false;
696        }
697
698        if (is_null($this->no_session)) {
699            $this->no_session = false;
700        }
701
702        if (is_null($this->send_headers)) {
703            $this->send_headers = true;
704        }
705
706        if ($this->no_session != true) {
707            // Initialize session or attach to existing
708            if ( session_id() == '' ) { // no session has been started yet, which is needed for validation
709                if (!is_null($this->session_name) && trim($this->session_name) != '') {
710                    session_name(trim($this->session_name)); // set session name if provided
711                }
712                session_start();
[12617]713            }
714        }
715    }
[26554]716
[12617]717    /**
718     * Return the absolute path to the Securimage directory
719     * @return string The path to the securimage base directory
720     */
721    public static function getPath()
722    {
723        return dirname(__FILE__);
724    }
[26554]725
[12617]726    /**
[26554]727     * Generate a new captcha ID or retrieve the current ID
728     *
729     * @param $new bool If true, generates a new challenge and returns and ID
730     * @param $options array Additional options to be passed to Securimage.
731     * Must include database options if not set directly in securimage.php
732     *
733     * @return null|string Returns null if no captcha id set and new was false, or string captcha ID
734     */
735    public static function getCaptchaId($new = true, array $options = array())
736    {
737        if (is_null($new) || (bool)$new == true) {
738            $id = sha1(uniqid($_SERVER['REMOTE_ADDR'], true));
739            $opts = array('no_session'    => true,
740                          'use_database'  => true);
741            if (sizeof($options) > 0) $opts = array_merge($options, $opts);
742            $si = new self($opts);
743            Securimage::$_captchaId = $id;
744            $si->createCode();
745
746            return $id;
747        } else {
748            return Securimage::$_captchaId;
749        }
750    }
751
752    /**
753     * Validate a captcha code input against a captcha ID
754     *
755     * @param string $id       The captcha ID to check
756     * @param string $value    The captcha value supplied by the user
757     * @param array  $options  Array of options to construct Securimage with.
758     * Options must include database options if they are not set in securimage.php
759     *
760     * @see Securimage::$database_driver
761     * @return bool true if the code was valid for the given captcha ID, false if not or if database failed to open
762     */
763    public static function checkByCaptchaId($id, $value, array $options = array())
764    {
765        $opts = array('captchaId'    => $id,
766                      'no_session'   => true,
767                      'use_database' => true);
768
769        if (sizeof($options) > 0) $opts = array_merge($options, $opts);
770
771        $si = new self($opts);
772
773        if ($si->openDatabase()) {
774            $code = $si->getCodeFromDatabase();
775
776            if (is_array($code)) {
777                $si->code         = $code['code'];
778                $si->code_display = $code['code_disp'];
779            }
780
781            if ($si->check($value)) {
782                $si->clearCodeFromDatabase();
783
784                return true;
785            } else {
786                return false;
787            }
788        } else {
789            return false;
790        }
791    }
792
793
794    /**
[12617]795     * Used to serve a captcha image to the browser
796     * @param string $background_image The path to the background image to use
[26554]797     * <code>
[12617]798     * $img = new Securimage();
799     * $img->code_length = 6;
800     * $img->num_lines   = 5;
801     * $img->noise_level = 5;
[26554]802     *
[12617]803     * $img->show(); // sends the image to browser
804     * exit;
805     * </code>
806     */
807    public function show($background_image = '')
808    {
[26554]809        set_error_handler(array(&$this, 'errorHandler'));
810
[12617]811        if($background_image != '' && is_readable($background_image)) {
812            $this->bgimg = $background_image;
813        }
814
815        $this->doImage();
816    }
[26554]817
[12617]818    /**
819     * Check a submitted code against the stored value
820     * @param string $code  The captcha code to check
821     * <code>
822     * $code = $_POST['code'];
823     * $img  = new Securimage();
824     * if ($img->check($code) == true) {
825     *     $captcha_valid = true;
826     * } else {
827     *     $captcha_valid = false;
828     * }
829     * </code>
830     */
831    public function check($code)
832    {
833        $this->code_entered = $code;
834        $this->validate();
835        return $this->correct_code;
836    }
[26554]837
[12617]838    /**
839     * Output a wav file of the captcha code to the browser
[26554]840     *
[12617]841     * <code>
842     * $img = new Securimage();
843     * $img->outputAudioFile(); // outputs a wav file to the browser
844     * exit;
845     * </code>
846     */
847    public function outputAudioFile()
848    {
[26554]849        set_error_handler(array(&$this, 'errorHandler'));
[12617]850
[26554]851        require_once dirname(__FILE__) . '/WavFile.php';
[12617]852
[26554]853        try {
854            $audio = $this->getAudibleCode();
855        } catch (Exception $ex) {
856            if (($fp = @fopen(dirname(__FILE__) . '/si.error_log', 'a+')) !== false) {
857                fwrite($fp, date('Y-m-d H:i:s') . ': Securimage audio error "' . $ex->getMessage() . '"' . "\n");
858                fclose($fp);
859            }
860
861            $audio = $this->audioError();
862        }
863
864        if ($this->canSendHeaders() || $this->send_headers == false) {
865            if ($this->send_headers) {
866                $uniq = md5(uniqid(microtime()));
867                header("Content-Disposition: attachment; filename=\"securimage_audio-{$uniq}.wav\"");
868                header('Cache-Control: no-store, no-cache, must-revalidate');
869                header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
870                header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
871                header('Content-type: audio/x-wav');
872
873                if (extension_loaded('zlib')) {
874                    ini_set('zlib.output_compression', true);  // compress output if supported by browser
875                } else {
876                    header('Content-Length: ' . strlen($audio));
877                }
878            }
879
880            echo $audio;
881        } else {
882            echo '<hr /><strong>'
883                .'Failed to generate audio file, content has already been '
884                .'output.<br />This is most likely due to misconfiguration or '
885                .'a PHP error was sent to the browser.</strong>';
886        }
887
888        restore_error_handler();
889
890        if (!$this->no_exit) exit;
[12617]891    }
[26554]892
893    /**
894     * Return the code from the session or sqlite database if used.  If none exists yet, an empty string is returned
895     *
896     * @param $array bool   True to receive an array containing the code and properties
897     * @return array|string Array if $array = true, otherwise a string containing the code
898     */
899    public function getCode($array = false, $returnExisting = false)
900    {
901        $code = '';
902        $time = 0;
903        $disp = 'error';
904
905        if ($returnExisting && strlen($this->code) > 0) {
906            if ($array) {
907                return array('code' => $this->code,
908                             'display' => $this->code_display,
909                             'code_display' => $this->code_display,
910                             'time' => 0);
911            } else {
912                return $this->code;
913            }
914        }
915
916        if ($this->no_session != true) {
917            if (isset($_SESSION['securimage_code_value'][$this->namespace]) &&
918                    trim($_SESSION['securimage_code_value'][$this->namespace]) != '') {
919                if ($this->isCodeExpired(
920                        $_SESSION['securimage_code_ctime'][$this->namespace]) == false) {
921                    $code = $_SESSION['securimage_code_value'][$this->namespace];
922                    $time = $_SESSION['securimage_code_ctime'][$this->namespace];
923                    $disp = $_SESSION['securimage_code_disp'] [$this->namespace];
924                }
925            }
926        }
927
928        if (empty($code) && $this->use_database) {
929            // no code in session - may mean user has cookies turned off
930            $this->openDatabase();
931            $code = $this->getCodeFromDatabase();
932        } else { /* no code stored in session or sqlite database, validation will fail */ }
933
934        if ($array == true) {
935            return array('code' => $code, 'ctime' => $time, 'display' => $disp);
936        } else {
937            return $code;
938        }
939    }
940
[12617]941    /**
942     * The main image drawing routing, responsible for constructing the entire image and serving it
943     */
944    protected function doImage()
945    {
946        if( ($this->use_transparent_text == true || $this->bgimg != '') && function_exists('imagecreatetruecolor')) {
947            $imagecreate = 'imagecreatetruecolor';
948        } else {
949            $imagecreate = 'imagecreate';
950        }
[26554]951
[12617]952        $this->im     = $imagecreate($this->image_width, $this->image_height);
953        $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
[26554]954
[12617]955        $this->allocateColors();
956        imagepalettecopy($this->tmpimg, $this->im);
957
958        $this->setBackground();
959
[26554]960        $code = '';
[12617]961
[26554]962        if ($this->getCaptchaId(false) !== null) {
963            // a captcha Id was supplied
964
965            // check to see if a display_value for the captcha image was set
966            if (is_string($this->display_value) && strlen($this->display_value) > 0) {
967                $this->code_display = $this->display_value;
968                $this->code         = ($this->case_sensitive) ?
969                                       $this->display_value   :
970                                       strtolower($this->display_value);
971                $code = $this->code;
972            } else if ($this->openDatabase()) {
973                // no display_value, check the database for existing captchaId
974                $code = $this->getCodeFromDatabase();
975
976                // got back a result from the database with a valid code for captchaId
977                if (is_array($code)) {
978                    $this->code         = $code['code'];
979                    $this->code_display = $code['code_disp'];
980                    $code = $code['code'];
981                }
982            }
983        }
984
985        if ($code == '') {
986            // if the code was not set using display_value or was not found in
987            // the database, create a new code
988            $this->createCode();
989        }
990
[12617]991        if ($this->noise_level > 0) {
992            $this->drawNoise();
993        }
[26554]994
995        $this->drawWord();
996
[12617]997        if ($this->perturbation > 0 && is_readable($this->ttf_file)) {
998            $this->distortedCopy();
999        }
1000
1001        if ($this->num_lines > 0) {
1002            $this->drawLines();
1003        }
1004
1005        if (trim($this->image_signature) != '') {
1006            $this->addSignature();
1007        }
1008
1009        $this->output();
1010    }
[26554]1011
[12617]1012    /**
1013     * Allocate the colors to be used for the image
1014     */
1015    protected function allocateColors()
1016    {
1017        // allocate bg color first for imagecreate
1018        $this->gdbgcolor = imagecolorallocate($this->im,
1019                                              $this->image_bg_color->r,
1020                                              $this->image_bg_color->g,
1021                                              $this->image_bg_color->b);
[26554]1022
[12617]1023        $alpha = intval($this->text_transparency_percentage / 100 * 127);
[26554]1024
[12617]1025        if ($this->use_transparent_text == true) {
1026            $this->gdtextcolor = imagecolorallocatealpha($this->im,
1027                                                         $this->text_color->r,
1028                                                         $this->text_color->g,
1029                                                         $this->text_color->b,
1030                                                         $alpha);
1031            $this->gdlinecolor = imagecolorallocatealpha($this->im,
1032                                                         $this->line_color->r,
1033                                                         $this->line_color->g,
1034                                                         $this->line_color->b,
1035                                                         $alpha);
1036            $this->gdnoisecolor = imagecolorallocatealpha($this->im,
1037                                                          $this->noise_color->r,
1038                                                          $this->noise_color->g,
1039                                                          $this->noise_color->b,
1040                                                          $alpha);
1041        } else {
1042            $this->gdtextcolor = imagecolorallocate($this->im,
1043                                                    $this->text_color->r,
1044                                                    $this->text_color->g,
1045                                                    $this->text_color->b);
1046            $this->gdlinecolor = imagecolorallocate($this->im,
1047                                                    $this->line_color->r,
1048                                                    $this->line_color->g,
1049                                                    $this->line_color->b);
1050            $this->gdnoisecolor = imagecolorallocate($this->im,
1051                                                          $this->noise_color->r,
1052                                                          $this->noise_color->g,
1053                                                          $this->noise_color->b);
1054        }
[26554]1055
[12617]1056        $this->gdsignaturecolor = imagecolorallocate($this->im,
1057                                                     $this->signature_color->r,
1058                                                     $this->signature_color->g,
1059                                                     $this->signature_color->b);
1060
1061    }
[26554]1062
[12617]1063    /**
1064     * The the background color, or background image to be used
1065     */
1066    protected function setBackground()
1067    {
1068        // set background color of image by drawing a rectangle since imagecreatetruecolor doesn't set a bg color
1069        imagefilledrectangle($this->im, 0, 0,
1070                             $this->image_width, $this->image_height,
1071                             $this->gdbgcolor);
1072        imagefilledrectangle($this->tmpimg, 0, 0,
1073                             $this->image_width * $this->iscale, $this->image_height * $this->iscale,
1074                             $this->gdbgcolor);
[26554]1075
[12617]1076        if ($this->bgimg == '') {
[26554]1077            if ($this->background_directory != null &&
[12617]1078                is_dir($this->background_directory) &&
1079                is_readable($this->background_directory))
1080            {
1081                $img = $this->getBackgroundFromDirectory();
1082                if ($img != false) {
1083                    $this->bgimg = $img;
1084                }
1085            }
1086        }
[26554]1087
[12617]1088        if ($this->bgimg == '') {
1089            return;
1090        }
1091
1092        $dat = @getimagesize($this->bgimg);
[26554]1093        if($dat == false) {
[12617]1094            return;
1095        }
1096
1097        switch($dat[2]) {
1098            case 1:  $newim = @imagecreatefromgif($this->bgimg); break;
1099            case 2:  $newim = @imagecreatefromjpeg($this->bgimg); break;
1100            case 3:  $newim = @imagecreatefrompng($this->bgimg); break;
1101            default: return;
1102        }
1103
1104        if(!$newim) return;
1105
1106        imagecopyresized($this->im, $newim, 0, 0, 0, 0,
1107                         $this->image_width, $this->image_height,
1108                         imagesx($newim), imagesy($newim));
1109    }
[26554]1110
[12617]1111    /**
1112     * Scan the directory for a background image to use
1113     */
1114    protected function getBackgroundFromDirectory()
1115    {
1116        $images = array();
1117
1118        if ( ($dh = opendir($this->background_directory)) !== false) {
1119            while (($file = readdir($dh)) !== false) {
1120                if (preg_match('/(jpg|gif|png)$/i', $file)) $images[] = $file;
1121            }
1122
1123            closedir($dh);
1124
1125            if (sizeof($images) > 0) {
[26554]1126                return rtrim($this->background_directory, '/') . '/' . $images[mt_rand(0, sizeof($images)-1)];
[12617]1127            }
1128        }
1129
1130        return false;
1131    }
[26554]1132
[12617]1133    /**
1134     * Generates the code or math problem and saves the value to the session
1135     */
[26554]1136    public function createCode()
[12617]1137    {
1138        $this->code = false;
1139
1140        switch($this->captcha_type) {
1141            case self::SI_CAPTCHA_MATHEMATIC:
1142            {
[26554]1143                do {
1144                    $signs = array('+', '-', 'x');
1145                    $left  = mt_rand(1, 10);
1146                    $right = mt_rand(1, 5);
1147                    $sign  = $signs[mt_rand(0, 2)];
1148
1149                    switch($sign) {
1150                        case 'x': $c = $left * $right; break;
1151                        case '-': $c = $left - $right; break;
1152                        default:  $c = $left + $right; break;
1153                    }
1154                } while ($c <= 0); // no negative #'s or 0
1155
[12617]1156                $this->code         = $c;
1157                $this->code_display = "$left $sign $right";
1158                break;
1159            }
[26554]1160
1161            case self::SI_CAPTCHA_WORDS:
1162                $words = $this->readCodeFromFile(2);
1163                $this->code = implode(' ', $words);
1164                $this->code_display = $this->code;
1165                break;
1166
[12617]1167            default:
1168            {
1169                if ($this->use_wordlist && is_readable($this->wordlist_file)) {
1170                    $this->code = $this->readCodeFromFile();
1171                }
1172
1173                if ($this->code == false) {
1174                    $this->code = $this->generateCode($this->code_length);
1175                }
[26554]1176
[12617]1177                $this->code_display = $this->code;
1178                $this->code         = ($this->case_sensitive) ? $this->code : strtolower($this->code);
1179            } // default
1180        }
[26554]1181
[12617]1182        $this->saveData();
1183    }
[26554]1184
[12617]1185    /**
1186     * Draws the captcha code on the image
1187     */
1188    protected function drawWord()
1189    {
1190        $width2  = $this->image_width * $this->iscale;
1191        $height2 = $this->image_height * $this->iscale;
[26554]1192
[12617]1193        if (!is_readable($this->ttf_file)) {
1194            imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
1195        } else {
1196            if ($this->perturbation > 0) {
1197                $font_size = $height2 * .4;
1198                $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
1199                $tx = $bb[4] - $bb[0];
1200                $ty = $bb[5] - $bb[1];
1201                $x  = floor($width2 / 2 - $tx / 2 - $bb[0]);
1202                $y  = round($height2 / 2 - $ty / 2 - $bb[1]);
1203
1204                imagettftext($this->tmpimg, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
1205            } else {
1206                $font_size = $this->image_height * .4;
1207                $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
1208                $tx = $bb[4] - $bb[0];
1209                $ty = $bb[5] - $bb[1];
1210                $x  = floor($this->image_width / 2 - $tx / 2 - $bb[0]);
1211                $y  = round($this->image_height / 2 - $ty / 2 - $bb[1]);
1212
1213                imagettftext($this->im, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
1214            }
1215        }
[26554]1216
[12617]1217        // DEBUG
1218        //$this->im = $this->tmpimg;
1219        //$this->output();
[26554]1220
[12617]1221    }
[26554]1222
[12617]1223    /**
1224     * Copies the captcha image to the final image with distortion applied
1225     */
1226    protected function distortedCopy()
1227    {
1228        $numpoles = 3; // distortion factor
1229        // make array of poles AKA attractor points
1230        for ($i = 0; $i < $numpoles; ++ $i) {
[26554]1231            $px[$i]  = mt_rand($this->image_width  * 0.2, $this->image_width  * 0.8);
1232            $py[$i]  = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
1233            $rad[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
[12617]1234            $tmp     = ((- $this->frand()) * 0.15) - .15;
1235            $amp[$i] = $this->perturbation * $tmp;
1236        }
[26554]1237
[12617]1238        $bgCol = imagecolorat($this->tmpimg, 0, 0);
1239        $width2 = $this->iscale * $this->image_width;
1240        $height2 = $this->iscale * $this->image_height;
1241        imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across
1242        // loop over $img pixels, take pixels from $tmpimg with distortion field
1243        for ($ix = 0; $ix < $this->image_width; ++ $ix) {
1244            for ($iy = 0; $iy < $this->image_height; ++ $iy) {
1245                $x = $ix;
1246                $y = $iy;
1247                for ($i = 0; $i < $numpoles; ++ $i) {
1248                    $dx = $ix - $px[$i];
1249                    $dy = $iy - $py[$i];
1250                    if ($dx == 0 && $dy == 0) {
1251                        continue;
1252                    }
1253                    $r = sqrt($dx * $dx + $dy * $dy);
1254                    if ($r > $rad[$i]) {
1255                        continue;
1256                    }
1257                    $rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]);
1258                    $x += $dx * $rscale;
1259                    $y += $dy * $rscale;
1260                }
1261                $c = $bgCol;
1262                $x *= $this->iscale;
1263                $y *= $this->iscale;
1264                if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2) {
1265                    $c = imagecolorat($this->tmpimg, $x, $y);
1266                }
1267                if ($c != $bgCol) { // only copy pixels of letters to preserve any background image
1268                    imagesetpixel($this->im, $ix, $iy, $c);
1269                }
1270            }
1271        }
1272    }
[26554]1273
[12617]1274    /**
1275     * Draws distorted lines on the image
1276     */
1277    protected function drawLines()
1278    {
1279        for ($line = 0; $line < $this->num_lines; ++ $line) {
1280            $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
1281            $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
[26554]1282            $y = mt_rand($this->image_height * 0.1, $this->image_height * 0.9);
1283
[12617]1284            $theta = ($this->frand() - 0.5) * M_PI * 0.7;
1285            $w = $this->image_width;
[26554]1286            $len = mt_rand($w * 0.4, $w * 0.7);
1287            $lwid = mt_rand(0, 2);
1288
[12617]1289            $k = $this->frand() * 0.6 + 0.2;
1290            $k = $k * $k * 0.5;
1291            $phi = $this->frand() * 6.28;
1292            $step = 0.5;
1293            $dx = $step * cos($theta);
1294            $dy = $step * sin($theta);
1295            $n = $len / $step;
1296            $amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
1297            $x0 = $x - 0.5 * $len * cos($theta);
1298            $y0 = $y - 0.5 * $len * sin($theta);
[26554]1299
[12617]1300            $ldx = round(- $dy * $lwid);
1301            $ldy = round($dx * $lwid);
[26554]1302
[12617]1303            for ($i = 0; $i < $n; ++ $i) {
1304                $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
1305                $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
1306                imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor);
1307            }
1308        }
1309    }
[26554]1310
[12617]1311    /**
1312     * Draws random noise on the image
1313     */
1314    protected function drawNoise()
1315    {
1316        if ($this->noise_level > 10) {
1317            $noise_level = 10;
1318        } else {
1319            $noise_level = $this->noise_level;
1320        }
1321
1322        $t0 = microtime(true);
[26554]1323
[12617]1324        $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
[26554]1325
[12617]1326        $points = $this->image_width * $this->image_height * $this->iscale;
1327        $height = $this->image_height * $this->iscale;
1328        $width  = $this->image_width * $this->iscale;
1329        for ($i = 0; $i < $noise_level; ++$i) {
[26554]1330            $x = mt_rand(10, $width);
1331            $y = mt_rand(10, $height);
1332            $size = mt_rand(7, 10);
[12617]1333            if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy
1334            imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
1335        }
[26554]1336
[12617]1337        $t1 = microtime(true);
[26554]1338
[12617]1339        $t = $t1 - $t0;
[26554]1340
[12617]1341        /*
1342        // DEBUG
1343        imagestring($this->tmpimg, 5, 25, 30, "$t", $this->gdnoisecolor);
1344        header('content-type: image/png');
1345        imagepng($this->tmpimg);
1346        exit;
1347        */
1348    }
[26554]1349
1350    /**
1351    * Print signature text on image
1352    */
[12617]1353    protected function addSignature()
1354    {
[26554]1355        $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
1356        $textlen = $bbox[2] - $bbox[0];
1357        $x = $this->image_width - $textlen - 5;
1358        $y = $this->image_height - 3;
1359
1360        imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
[12617]1361    }
[26554]1362
[12617]1363    /**
1364     * Sends the appropriate image and cache headers and outputs image to the browser
1365     */
1366    protected function output()
1367    {
[26554]1368        if ($this->canSendHeaders() || $this->send_headers == false) {
1369            if ($this->send_headers) {
1370                // only send the content-type headers if no headers have been output
1371                // this will ease debugging on misconfigured servers where warnings
1372                // may have been output which break the image and prevent easily viewing
1373                // source to see the error.
1374                header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
1375                header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
1376                header("Cache-Control: no-store, no-cache, must-revalidate");
1377                header("Cache-Control: post-check=0, pre-check=0", false);
1378                header("Pragma: no-cache");
1379            }
1380
1381            switch ($this->image_type) {
1382                case self::SI_IMAGE_JPEG:
1383                    if ($this->send_headers) header("Content-Type: image/jpeg");
1384                    imagejpeg($this->im, null, 90);
1385                    break;
1386                case self::SI_IMAGE_GIF:
1387                    if ($this->send_headers) header("Content-Type: image/gif");
1388                    imagegif($this->im);
1389                    break;
1390                default:
1391                    if ($this->send_headers) header("Content-Type: image/png");
1392                    imagepng($this->im);
1393                    break;
1394            }
1395        } else {
1396            echo '<hr /><strong>'
1397                .'Failed to generate captcha image, content has already been '
1398                .'output.<br />This is most likely due to misconfiguration or '
1399                .'a PHP error was sent to the browser.</strong>';
[12617]1400        }
[26554]1401
[12617]1402        imagedestroy($this->im);
[26554]1403        restore_error_handler();
1404
1405        if (!$this->no_exit) exit;
[12617]1406    }
[26554]1407
[12617]1408    /**
1409     * Gets the code and returns the binary audio file for the stored captcha code
[26554]1410     *
1411     * @return The audio representation of the captcha in Wav format
[12617]1412     */
[26554]1413    protected function getAudibleCode()
[12617]1414    {
1415        $letters = array();
[26554]1416        $code    = $this->getCode(true, true);
[12617]1417
[26554]1418        if ($code['code'] == '') {
1419            if (strlen($this->display_value) > 0) {
1420                $code = array('code' => $this->display_value, 'display' => $this->display_value);
1421            } else {
1422                $this->createCode();
1423                $code = $this->getCode(true);
1424            }
[12617]1425        }
1426
[26554]1427        if (preg_match('/(\d+) (\+|-|x) (\d+)/i', $code['display'], $eq)) {
1428            $math = true;
1429
1430            $left  = $eq[1];
1431            $sign  = str_replace(array('+', '-', 'x'), array('plus', 'minus', 'times'), $eq[2]);
1432            $right = $eq[3];
1433
1434            $letters = array($left, $sign, $right);
1435        } else {
1436            $math = false;
1437
1438            $length = strlen($code['display']);
1439
1440            for($i = 0; $i < $length; ++$i) {
1441                $letter    = $code['display']{$i};
1442                $letters[] = $letter;
1443            }
[12617]1444        }
[26554]1445
1446        try {
[12617]1447            return $this->generateWAV($letters);
[26554]1448        } catch(Exception $ex) {
1449            throw $ex;
[12617]1450        }
1451    }
1452
1453    /**
1454     * Gets a captcha code from a wordlist
1455     */
[26554]1456    protected function readCodeFromFile($numWords = 1)
[12617]1457    {
[26554]1458        $fp = fopen($this->wordlist_file, 'rb');
[12617]1459        if (!$fp) return false;
1460
1461        $fsize = filesize($this->wordlist_file);
1462        if ($fsize < 128) return false; // too small of a list to be effective
1463
[26554]1464        if ((int)$numWords < 1 || (int)$numWords > 5) $numWords = 1;
1465
1466        $words = array();
1467        $i = 0;
1468        do {
1469            fseek($fp, mt_rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64
1470            $data = fread($fp, 64); // read a chunk from our random position
1471            $data = preg_replace("/\r?\n/", "\n", $data);
1472
1473            $start = @strpos($data, "\n", mt_rand(0, 56)) + 1; // random start position
1474            $end   = @strpos($data, "\n", $start);          // find end of word
1475
1476            if ($start === false) {
1477                // picked start position at end of file
1478                continue;
1479            } else if ($end === false) {
1480                $end = strlen($data);
1481            }
1482
1483            $word = strtolower(substr($data, $start, $end - $start)); // return a line of the file
1484            $words[] = $word;
1485        } while (++$i < $numWords);
1486
[12617]1487        fclose($fp);
1488
[26554]1489        if ($numWords < 2) {
1490            return $words[0];
1491        } else {
1492            return $words;
[12617]1493        }
[26554]1494    }
[12617]1495
1496    /**
1497     * Generates a random captcha code from the set character set
1498     */
1499    protected function generateCode()
1500    {
1501        $code = '';
1502
[26554]1503        if (function_exists('mb_strlen')) {
1504            for($i = 1, $cslen = mb_strlen($this->charset); $i <= $this->code_length; ++$i) {
1505                $code .= mb_substr($this->charset, mt_rand(0, $cslen - 1), 1, 'UTF-8');
1506            }
1507        } else {
1508            for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) {
1509                $code .= substr($this->charset, mt_rand(0, $cslen - 1), 1);
1510            }
[12617]1511        }
[26554]1512
[12617]1513        return $code;
1514    }
[26554]1515
[12617]1516    /**
1517     * Checks the entered code against the value stored in the session or sqlite database, handles case sensitivity
1518     * Also clears the stored codes if the code was entered correctly to prevent re-use
1519     */
1520    protected function validate()
1521    {
[26554]1522        if (!is_string($this->code) || strlen($this->code) == 0) {
1523            $code = $this->getCode();
1524            // returns stored code, or an empty string if no stored code was found
1525            // checks the session and database if enabled
1526        } else {
1527            $code = $this->code;
1528        }
1529
[12617]1530        if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) {
1531            // case sensitive was set from securimage_show.php but not in class
1532            // the code saved in the session has capitals so set case sensitive to true
1533            $this->case_sensitive = true;
1534        }
[26554]1535
[12617]1536        $code_entered = trim( (($this->case_sensitive) ? $this->code_entered
1537                                                       : strtolower($this->code_entered))
1538                        );
1539        $this->correct_code = false;
[26554]1540
[12617]1541        if ($code != '') {
[26554]1542            if (strpos($code, ' ') !== false) {
1543                // for multi word captchas, remove more than once space from input
1544                $code_entered = preg_replace('/\s+/', ' ', $code_entered);
1545                $code_entered = strtolower($code_entered);
1546            }
1547
[12617]1548            if ($code == $code_entered) {
1549                $this->correct_code = true;
[26554]1550                if ($this->no_session != true) {
1551                    $_SESSION['securimage_code_value'][$this->namespace] = '';
1552                    $_SESSION['securimage_code_ctime'][$this->namespace] = '';
1553                }
[12617]1554                $this->clearCodeFromDatabase();
1555            }
1556        }
1557    }
[26554]1558
[12617]1559    /**
1560     * Save data to session namespace and database if used
1561     */
1562    protected function saveData()
1563    {
[26554]1564        if ($this->no_session != true) {
1565            if (isset($_SESSION['securimage_code_value']) && is_scalar($_SESSION['securimage_code_value'])) {
1566                // fix for migration from v2 - v3
1567                unset($_SESSION['securimage_code_value']);
1568                unset($_SESSION['securimage_code_ctime']);
1569            }
1570
1571            $_SESSION['securimage_code_disp'] [$this->namespace] = $this->code_display;
1572            $_SESSION['securimage_code_value'][$this->namespace] = $this->code;
1573            $_SESSION['securimage_code_ctime'][$this->namespace] = time();
1574        }
1575
1576        if ($this->use_database) {
1577            $this->saveCodeToDatabase();
1578        }
[12617]1579    }
[26554]1580
[12617]1581    /**
1582     * Saves the code to the sqlite database
1583     */
1584    protected function saveCodeToDatabase()
1585    {
1586        $success = false;
1587        $this->openDatabase();
[26554]1588
1589        if ($this->use_database && $this->pdo_conn) {
1590            $id = $this->getCaptchaId(false);
1591            $ip = $_SERVER['REMOTE_ADDR'];
1592
1593            if (empty($id)) {
1594                $id = $ip;
1595            }
1596
1597            $time      = time();
1598            $code      = $this->code;
1599            $code_disp = $this->code_display;
1600
1601            // This is somewhat expensive in PDO Sqlite3 (when there is something to delete)
1602            $this->clearCodeFromDatabase();
1603
1604            $query = "INSERT INTO {$this->database_table} ("
1605                    ."id, code, code_display, namespace, created) "
1606                    ."VALUES(?, ?, ?, ?, ?)";
1607
1608            $stmt    = $this->pdo_conn->prepare($query);
1609            $success = $stmt->execute(array($id, $code, $code_disp, $this->namespace, $time));
1610
1611            if (!$success) {
1612                $err = $stmt->errorInfo();
1613                trigger_error("Failed to insert code into database. {$err[1]}: {$err[2]}", E_USER_WARNING);
1614            }
[12617]1615        }
[26554]1616
[12617]1617        return $success !== false;
1618    }
[26554]1619
[12617]1620    /**
1621     * Open sqlite database
1622     */
1623    protected function openDatabase()
1624    {
[26554]1625        $this->pdo_conn = false;
1626
1627        if ($this->use_database) {
1628            $pdo_extension = 'PDO_' . strtoupper($this->database_driver);
1629
1630            if (!extension_loaded($pdo_extension)) {
1631                trigger_error("Database support is turned on in Securimage, but the chosen extension $pdo_extension is not loaded in PHP.", E_USER_WARNING);
1632                return false;
1633            }
1634        }
1635
1636        if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
1637            if (!file_exists($this->database_file)) {
1638                $fp = fopen($this->database_file, 'w+');
1639                if (!$fp) {
1640                    $err = error_get_last();
1641                    trigger_error("Securimage failed to create SQLite3 database file '{$this->database_file}'. Reason: {$err['message']}", E_USER_WARNING);
1642                    return false;
[12617]1643                }
[26554]1644                fclose($fp);
1645                chmod($this->database_file, 0666);
1646            } else if (!is_writeable($this->database_file)) {
1647                trigger_error("Securimage does not have read/write access to database file '{$this->database_file}. Make sure permissions are 0666 and writeable by user '" . get_current_user() . "'", E_USER_WARNING);
1648                return false;
[12617]1649            }
1650        }
[26554]1651
1652        $dsn = $this->getDsn();
1653
1654        try {
1655            $options        = array();
1656            $this->pdo_conn = new PDO($dsn, $this->database_user, $this->database_pass, $options);
1657        } catch (PDOException $pdoex) {
1658            trigger_error("Database connection failed: " . $pdoex->getMessage(), E_USER_WARNING);
1659            return false;
1660        }
1661
1662        try {
1663            if (!$this->checkTablesExist()) {
1664                // create tables...
1665                $this->createDatabaseTables();
1666            }
1667        } catch (Exception $ex) {
1668            trigger_error($ex->getMessage(), E_USER_WARNING);
1669            $this->pdo_conn = null;
1670            return false;
1671        }
1672
1673        if (mt_rand(0, 100) / 100.0 == 1.0) {
1674            $this->purgeOldCodesFromDatabase();
1675        }
1676
1677        return $this->pdo_conn;
[12617]1678    }
[26554]1679
1680    protected function getDsn()
1681    {
1682        $dsn = sprintf('%s:', $this->database_driver);
1683
1684        switch($this->database_driver) {
1685            case self::SI_DRIVER_SQLITE3:
1686                $dsn .= $this->database_file;
1687                break;
1688
1689            case self::SI_DRIVER_MYSQL:
1690            case self::SI_DRIVER_PGSQL:
1691                $dsn .= sprintf('host=%s;dbname=%s',
1692                                $this->database_host,
1693                                $this->database_name);
1694                break;
1695
1696        }
1697
1698        return $dsn;
1699    }
1700
1701    protected function checkTablesExist()
1702    {
1703        $table = $this->pdo_conn->quote($this->database_table);
1704
1705        switch($this->database_driver) {
1706            case self::SI_DRIVER_SQLITE3:
1707                // query row count for sqlite, PRAGMA queries seem to return no
1708                // rowCount using PDO even if there are rows returned
1709                $query = "SELECT COUNT(id) FROM $table";
1710                break;
1711
1712            case self::SI_DRIVER_MYSQL:
1713                $query = "SHOW TABLES LIKE $table";
1714                break;
1715
1716            case self::SI_DRIVER_PGSQL:
1717                $query = "SELECT * FROM information_schema.columns WHERE table_name = $table;";
1718                break;
1719        }
1720
1721        $result = $this->pdo_conn->query($query);
1722
1723        if (!$result) {
1724            $err = $this->pdo_conn->errorInfo();
1725
1726            if ($this->database_driver == self::SI_DRIVER_SQLITE3 &&
1727                $err[1] === 1 && strpos($err[2], 'no such table') !== false)
1728            {
1729                return false;
1730            }
1731
1732            throw new Exception("Failed to check tables: {$err[0]} - {$err[1]}: {$err[2]}");
1733        } else if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
1734            // successful here regardless of row count for sqlite
1735            return true;
1736        } else if ($result->rowCount() == 0) {
1737            return false;
1738        } else {
1739            return true;
1740        }
1741    }
1742
1743    protected function createDatabaseTables()
1744    {
1745        $queries = array();
1746
1747        switch($this->database_driver) {
1748            case self::SI_DRIVER_SQLITE3:
1749                $queries[] = "CREATE TABLE \"{$this->database_table}\" (
1750                                id VARCHAR(40),
1751                                namespace VARCHAR(32) NOT NULL,
1752                                code VARCHAR(32) NOT NULL,
1753                                code_display VARCHAR(32) NOT NULL,
1754                                created INTEGER NOT NULL,
1755                                PRIMARY KEY(id, namespace)
1756                              )";
1757
1758                $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created)";
1759                break;
1760
1761            case self::SI_DRIVER_MYSQL:
1762                $queries[] = "CREATE TABLE `{$this->database_table}` (
1763                                `id` VARCHAR(40) NOT NULL,
1764                                `namespace` VARCHAR(32) NOT NULL,
1765                                `code` VARCHAR(32) NOT NULL,
1766                                `code_display` VARCHAR(32) NOT NULL,
1767                                `created` INT NOT NULL,
1768                                PRIMARY KEY(id, namespace),
1769                                INDEX(created)
1770                              )";
1771                break;
1772
1773            case self::SI_DRIVER_PGSQL:
1774                $queries[] = "CREATE TABLE {$this->database_table} (
1775                                id character varying(40) NOT NULL,
1776                                namespace character varying(32) NOT NULL,
1777                                code character varying(32) NOT NULL,
1778                                code_display character varying(32) NOT NULL,
1779                                created integer NOT NULL,
1780                                CONSTRAINT pkey_id_namespace PRIMARY KEY (id, namespace)
1781                              )";
1782
1783                $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created);";
1784                break;
1785        }
1786
1787        $this->pdo_conn->beginTransaction();
1788
1789        foreach($queries as $query) {
1790            $result = $this->pdo_conn->query($query);
1791
1792            if (!$result) {
1793                $err = $this->pdo_conn->errorInfo();
1794                trigger_error("Failed to create table.  {$err[1]}: {$err[2]}", E_USER_WARNING);
1795                $this->pdo_conn->rollBack();
1796                $this->pdo_conn = false;
1797                return false;
1798            }
1799        }
1800
1801        $this->pdo_conn->commit();
1802
1803        return true;
1804    }
1805
[12617]1806    /**
[26554]1807     * Get a code from the sqlite database for ip address/captchaId.
1808     *
1809     * @return string|array Empty string if no code was found or has expired,
1810     * otherwise returns the stored captcha code.  If a captchaId is set, this
1811     * returns an array with indices "code" and "code_disp"
[12617]1812     */
1813    protected function getCodeFromDatabase()
1814    {
1815        $code = '';
1816
[26554]1817        if ($this->use_database == true && $this->pdo_conn) {
1818            if (Securimage::$_captchaId !== null) {
1819                $query  = "SELECT * FROM {$this->database_table} WHERE id = ?";
1820                $stmt   = $this->pdo_conn->prepare($query);
1821                $result = $stmt->execute(array(Securimage::$_captchaId));
1822            } else {
1823                $ip = $_SERVER['REMOTE_ADDR'];
1824                $ns = $this->namespace;
[12617]1825
[26554]1826                // ip is stored in id column when no captchaId
1827                $query  = "SELECT * FROM {$this->database_table} WHERE id = ? AND namespace = ?";
1828                $stmt   = $this->pdo_conn->prepare($query);
1829                $result = $stmt->execute(array($ip, $ns));
1830            }
[12617]1831
[26554]1832            if (!$result) {
1833                $err = $this->pdo_conn->errorInfo();
1834                trigger_error("Failed to select code from database.  {$err[0]}: {$err[1]}", E_USER_WARNING);
1835            } else {
1836                if ( ($row = $stmt->fetch()) !== false ) {
1837                    if (false == $this->isCodeExpired($row['created'])) {
1838                        if (Securimage::$_captchaId !== null) {
1839                            // return an array when using captchaId
1840                            $code = array('code'      => $row['code'],
1841                                          'code_disp' => $row['code_display']);
1842                        } else {
1843                            $code = $row['code'];
1844                        }
1845                    }
[12617]1846                }
1847            }
1848        }
[26554]1849
[12617]1850        return $code;
1851    }
[26554]1852
[12617]1853    /**
1854     * Remove an entered code from the database
1855     */
1856    protected function clearCodeFromDatabase()
1857    {
[26554]1858        if ($this->pdo_conn) {
[12617]1859            $ip = $_SERVER['REMOTE_ADDR'];
[26554]1860            $ns = $this->pdo_conn->quote($this->namespace);
1861            $id = Securimage::$_captchaId;
1862
1863            if (empty($id)) {
1864                $id = $ip; // if no captchaId set, IP address is captchaId.
1865            }
1866
1867            $id = $this->pdo_conn->quote($id);
1868
1869            $query = sprintf("DELETE FROM %s WHERE id = %s AND namespace = %s",
1870                             $this->database_table, $id, $ns);
1871
1872            $result = $this->pdo_conn->query($query);
1873            if (!$result) {
1874                trigger_error("Failed to delete code from database.", E_USER_WARNING);
1875            }
[12617]1876        }
1877    }
[26554]1878
[12617]1879    /**
1880     * Deletes old codes from sqlite database
1881     */
1882    protected function purgeOldCodesFromDatabase()
1883    {
[26554]1884        if ($this->use_database && $this->pdo_conn) {
[12617]1885            $now   = time();
1886            $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
[26554]1887
1888            $query = sprintf("DELETE FROM %s WHERE %s - created > %s",
1889                             $this->database_table,
1890                             $this->pdo_conn->quote($now, PDO::PARAM_INT),
1891                             $this->pdo_conn->quote($limit, PDO::PARAM_INT));
1892
1893            $result = $this->pdo_conn->query($query);
[12617]1894        }
1895    }
[26554]1896
[12617]1897    /**
1898     * Checks to see if the captcha code has expired and cannot be used
1899     * @param unknown_type $creation_time
1900     */
1901    protected function isCodeExpired($creation_time)
1902    {
1903        $expired = true;
[26554]1904
[12617]1905        if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
1906            $expired = false;
1907        } else if (time() - $creation_time < $this->expiry_time) {
1908            $expired = false;
1909        }
[26554]1910
[12617]1911        return $expired;
1912    }
[26554]1913
[12617]1914    /**
1915     * Generate a wav file given the $letters in the code
1916     * @todo Add ability to merge 2 sound files together to have random background sounds
1917     * @param array $letters
1918     * @return string The binary contents of the wav file
1919     */
1920    protected function generateWAV($letters)
1921    {
[26554]1922        $wavCaptcha = new WavFile();
1923        $first      = true;     // reading first wav file
[12617]1924
[26554]1925        foreach ($letters as $letter) {
1926            $letter = strtoupper($letter);
1927
1928            try {
1929                $l = new WavFile($this->audio_path . '/' . $letter . '.wav');
1930
1931                if ($first) {
1932                    // set sample rate, bits/sample, and # of channels for file based on first letter
1933                    $wavCaptcha->setSampleRate($l->getSampleRate())
1934                               ->setBitsPerSample($l->getBitsPerSample())
1935                               ->setNumChannels($l->getNumChannels());
1936                    $first = false;
1937                }
1938
1939                // append letter to the captcha audio
1940                $wavCaptcha->appendWav($l);
1941
1942                // random length of silence between $audio_gap_min and $audio_gap_max
1943                if ($this->audio_gap_max > 0 && $this->audio_gap_max > $this->audio_gap_min) {
1944                    $wavCaptcha->insertSilence( mt_rand($this->audio_gap_min, $this->audio_gap_max) / 1000.0 );
1945                }
1946            } catch (Exception $ex) {
1947                // failed to open file, or the wav file is broken or not supported
1948                // 2 wav files were not compatible, different # channels, bits/sample, or sample rate
1949                throw $ex;
[12617]1950            }
[26554]1951        }
[12617]1952
[26554]1953        /********* Set up audio filters *****************************/
1954        $filters = array();
1955
1956        if ($this->audio_use_noise == true) {
1957            // use background audio - find random file
1958            $noiseFile = $this->getRandomNoiseFile();
1959
1960            if ($noiseFile !== false && is_readable($noiseFile)) {
1961                try {
1962                    $wavNoise = new WavFile($noiseFile, false);
1963                } catch(Exception $ex) {
1964                    throw $ex;
[12617]1965                }
[26554]1966
1967                // start at a random offset from the beginning of the wavfile
1968                // in order to add more randomness
1969                $randOffset = 0;
1970                if ($wavNoise->getNumBlocks() > 2 * $wavCaptcha->getNumBlocks()) {
1971                    $randBlock = mt_rand(0, $wavNoise->getNumBlocks() - $wavCaptcha->getNumBlocks());
1972                    $wavNoise->readWavData($randBlock * $wavNoise->getBlockAlign(), $wavCaptcha->getNumBlocks() * $wavNoise->getBlockAlign());
1973                } else {
1974                    $wavNoise->readWavData();
1975                    $randOffset = mt_rand(0, $wavNoise->getNumBlocks() - 1);
1976                }
1977
1978
1979                $mixOpts = array('wav'  => $wavNoise,
1980                                 'loop' => true,
1981                                 'blockOffset' => $randOffset);
1982
1983                $filters[WavFile::FILTER_MIX]       = $mixOpts;
1984                $filters[WavFile::FILTER_NORMALIZE] = $this->audio_mix_normalization;
[12617]1985            }
1986        }
1987
[26554]1988        if ($this->degrade_audio == true) {
1989            // add random noise.
1990            // any noise level below 95% is intensely distorted and not pleasant to the ear
1991            $filters[WavFile::FILTER_DEGRADE] = mt_rand(95, 98) / 100.0;
1992        }
[12617]1993
[26554]1994        if (!empty($filters)) {
1995            $wavCaptcha->filter($filters);  // apply filters to captcha audio
1996        }
1997
1998        return $wavCaptcha->__toString();
[12617]1999    }
[26554]2000
2001    public function getRandomNoiseFile()
[12617]2002    {
[26554]2003        $return = false;
2004
2005        if ( ($dh = opendir($this->audio_noise_path)) !== false ) {
2006            $list = array();
2007
2008            while ( ($file = readdir($dh)) !== false ) {
2009                if ($file == '.' || $file == '..') continue;
2010                if (strtolower(substr($file, -4)) != '.wav') continue;
2011
2012                $list[] = $file;
[12617]2013            }
2014
[26554]2015            closedir($dh);
2016
2017            if (sizeof($list) > 0) {
2018                $file   = $list[array_rand($list, 1)];
2019                $return = $this->audio_noise_path . DIRECTORY_SEPARATOR . $file;
2020            }
[12617]2021        }
2022
[26554]2023        return $return;
[12617]2024    }
[26554]2025
[12617]2026    /**
2027     * Return a wav file saying there was an error generating file
[26554]2028     *
[12617]2029     * @return string The binary audio contents
2030     */
2031    protected function audioError()
2032    {
[26554]2033        return @file_get_contents(dirname(__FILE__) . '/audio/en/error.wav');
[12617]2034    }
[26554]2035
2036    /**
2037     * Checks to see if headers can be sent and if any error has been output to the browser
2038     *
2039     * @return bool true if headers haven't been sent and no output/errors will break audio/images, false if unsafe
2040     */
2041    protected function canSendHeaders()
2042    {
2043        if (headers_sent()) {
2044            // output has been flushed and headers have already been sent
2045            return false;
2046        } else if (strlen((string)ob_get_contents()) > 0) {
2047            // headers haven't been sent, but there is data in the buffer that will break image and audio data
2048            return false;
2049        }
2050
2051        return true;
2052    }
2053
2054    /**
2055     * Return a random float between 0 and 0.9999
2056     *
2057     * @return float Random float between 0 and 0.9999
2058     */
[12617]2059    function frand()
2060    {
[26554]2061        return 0.0001 * mt_rand(0,9999);
[12617]2062    }
[26554]2063
[12617]2064    /**
2065     * Convert an html color code to a Securimage_Color
2066     * @param string $color
2067     * @param Securimage_Color $default The defalt color to use if $color is invalid
2068     */
2069    protected function initColor($color, $default)
2070    {
2071        if ($color == null) {
2072            return new Securimage_Color($default);
2073        } else if (is_string($color)) {
2074            try {
2075                return new Securimage_Color($color);
2076            } catch(Exception $e) {
2077                return new Securimage_Color($default);
2078            }
2079        } else if (is_array($color) && sizeof($color) == 3) {
2080            return new Securimage_Color($color[0], $color[1], $color[2]);
2081        } else {
2082            return new Securimage_Color($default);
2083        }
2084    }
[26554]2085
2086    /**
2087     * Error handler used when outputting captcha image or audio.
2088     * This error handler helps determine if any errors raised would
2089     * prevent captcha image or audio from displaying.  If they have
2090     * no effect on the output buffer or headers, true is returned so
2091     * the script can continue processing.
2092     * See https://github.com/dapphp/securimage/issues/15
2093     *
2094     * @param int $errno
2095     * @param string $errstr
2096     * @param string $errfile
2097     * @param int $errline
2098     * @param array $errcontext
2099     * @return boolean true if handled, false if PHP should handle
2100     */
2101    public function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array())
2102    {
2103        // get the current error reporting level
2104        $level = error_reporting();
2105
2106        // if error was supressed or $errno not set in current error level
2107        if ($level == 0 || ($level & $errno) == 0) {
2108            return true;
2109        }
2110
2111        return false;
2112    }
[12617]2113}
2114
2115
2116/**
2117 * Color object for Securimage CAPTCHA
2118 *
2119 * @version 3.0
2120 * @since 2.0
2121 * @package Securimage
2122 * @subpackage classes
2123 *
2124 */
2125class Securimage_Color
2126{
2127    public $r;
2128    public $g;
2129    public $b;
2130
2131    /**
2132     * Create a new Securimage_Color object.<br />
2133     * Constructor expects 1 or 3 arguments.<br />
2134     * When passing a single argument, specify the color using HTML hex format,<br />
2135     * when passing 3 arguments, specify each RGB component (from 0-255) individually.<br />
2136     * $color = new Securimage_Color('#0080FF') or <br />
2137     * $color = new Securimage_Color(0, 128, 255)
[26554]2138     *
[12617]2139     * @param string $color
2140     * @throws Exception
2141     */
2142    public function __construct($color = '#ffffff')
2143    {
2144        $args = func_get_args();
[26554]2145
[12617]2146        if (sizeof($args) == 0) {
2147            $this->r = 255;
2148            $this->g = 255;
2149            $this->b = 255;
2150        } else if (sizeof($args) == 1) {
2151            // set based on html code
2152            if (substr($color, 0, 1) == '#') {
2153                $color = substr($color, 1);
2154            }
[26554]2155
[12617]2156            if (strlen($color) != 3 && strlen($color) != 6) {
2157                throw new InvalidArgumentException(
2158                  'Invalid HTML color code passed to Securimage_Color'
2159                );
2160            }
[26554]2161
[12617]2162            $this->constructHTML($color);
2163        } else if (sizeof($args) == 3) {
2164            $this->constructRGB($args[0], $args[1], $args[2]);
2165        } else {
2166            throw new InvalidArgumentException(
2167              'Securimage_Color constructor expects 0, 1 or 3 arguments; ' . sizeof($args) . ' given'
2168            );
2169        }
2170    }
[26554]2171
[12617]2172    /**
2173     * Construct from an rgb triplet
2174     * @param int $red The red component, 0-255
2175     * @param int $green The green component, 0-255
2176     * @param int $blue The blue component, 0-255
2177     */
2178    protected function constructRGB($red, $green, $blue)
2179    {
2180        if ($red < 0)     $red   = 0;
2181        if ($red > 255)   $red   = 255;
2182        if ($green < 0)   $green = 0;
2183        if ($green > 255) $green = 255;
2184        if ($blue < 0)    $blue  = 0;
2185        if ($blue > 255)  $blue  = 255;
[26554]2186
[12617]2187        $this->r = $red;
2188        $this->g = $green;
2189        $this->b = $blue;
2190    }
[26554]2191
[12617]2192    /**
2193     * Construct from an html hex color code
2194     * @param string $color
2195     */
2196    protected function constructHTML($color)
2197    {
2198        if (strlen($color) == 3) {
2199            $red   = str_repeat(substr($color, 0, 1), 2);
2200            $green = str_repeat(substr($color, 1, 1), 2);
2201            $blue  = str_repeat(substr($color, 2, 1), 2);
2202        } else {
2203            $red   = substr($color, 0, 2);
2204            $green = substr($color, 2, 2);
[26554]2205            $blue  = substr($color, 4, 2);
[12617]2206        }
[26554]2207
[12617]2208        $this->r = hexdec($red);
2209        $this->g = hexdec($green);
2210        $this->b = hexdec($blue);
2211    }
2212}
Note: See TracBrowser for help on using the repository browser.