Changeset 26554


Ignore:
Timestamp:
Jan 9, 2014, 1:36:32 PM (10 years ago)
Author:
mistic100
Message:

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

Location:
extensions/CryptograPHP
Files:
4 added
1 deleted
12 edited
1 moved

Legend:

Unmodified
Added
Removed
  • extensions/CryptograPHP/admin.php

    r26072 r26554  
    3434    'height'          => (int)$_POST['height'],
    3535    'perturbation'    => (float)$_POST['perturbation'],
    36     'image_bg_color'  => $_POST['image_bg_color'],
     36    'background'      => $_POST['background'],
     37    'bg_color'        => $_POST['bg_color'],
     38    'bg_image'        => $_POST['bg_image'],
    3739    'code_length'     => (int)$_POST['code_length'],
    3840    'text_color'      => $_POST['text_color'],
     
    5052
    5153$presets = array(
    52   'bluenoise' =>  array('perturbation'=>0.25, 'image_bg_color'=>'ffffff', 'text_color'=>'0000ff', 'num_lines'=>2, 'line_color'=>'0000ff', 'noise_level'=>2,   'noise_color'=>'0000ff', 'ttf_file'=>'AlteHassGroteskB'),
    53   'gray' =>       array('perturbation'=>1,    'image_bg_color'=>'ffffff', 'text_color'=>'8a8a8a', 'num_lines'=>2, 'line_color'=>'8a8a8a', 'noise_level'=>0.1, 'noise_color'=>'8a8a8a', 'ttf_file'=>'TopSecret'),
    54   'xcolor' =>     array('perturbation'=>0.5,  'image_bg_color'=>'ffffff', 'text_color'=>'random', 'num_lines'=>1, 'line_color'=>'ffffff', 'noise_level'=>2,   'noise_color'=>'ffffff', 'ttf_file'=>'Dread'),
    55   'pencil' =>     array('perturbation'=>0.8,  'image_bg_color'=>'9e9e9e', 'text_color'=>'363636', 'num_lines'=>0, 'line_color'=>'ffffff', 'noise_level'=>0,   'noise_color'=>'ffffff', 'ttf_file'=>'AllStar'),
     54  'bluenoise' =>  array('perturbation'=>0.25, 'background'=>'color', 'bg_image'=>'', 'bg_color'=>'ffffff', 'text_color'=>'0000ff', 'num_lines'=>2, 'line_color'=>'0000ff', 'noise_level'=>2, 'noise_color'=>'0000ff', 'ttf_file'=>'AlteHassGroteskB'),
     55  'gray' =>       array('perturbation'=>1, 'background'=>'color', 'bg_image'=>'', 'bg_color'=>'ffffff', 'text_color'=>'8a8a8a', 'num_lines'=>2, 'line_color'=>'8a8a8a', 'noise_level'=>0.1, 'noise_color'=>'8a8a8a', 'ttf_file'=>'TopSecret'),
     56  'xcolor' =>     array('perturbation'=>0.5, 'background'=>'color', 'bg_image'=>'', 'bg_color'=>'ffffff', 'text_color'=>'random', 'num_lines'=>1, 'line_color'=>'ffffff', 'noise_level'=>2, 'noise_color'=>'ffffff', 'ttf_file'=>'Dread'),
     57  'pencil' =>     array('perturbation'=>0.8, 'background'=>'color', 'bg_image'=>'', 'bg_color'=>'9e9e9e', 'text_color'=>'363636', 'num_lines'=>0, 'line_color'=>'ffffff', 'noise_level'=>0, 'noise_color'=>'ffffff', 'ttf_file'=>'AllStar'),
     58  'ransom' =>     array('perturbation'=>0, 'background'=>'image', 'bg_image'=>'bg1.jpg', 'bg_color'=>'ffffff', 'text_color'=>'4a003a', 'num_lines'=>0, 'line_color'=>'ffffff', 'noise_level'=>0, 'noise_color'=>'ffffff', 'ttf_file'=>'ransom'),
    5659  );
    57  
     60
     61
     62$template->assign(array(
     63  'crypto' => $conf['cryptographp'],
     64  'loaded' => $loaded,
     65  'fonts' => list_fonts(CRYPTO_PATH.'securimage/fonts'),
     66  'backgrounds' => list_backgrounds(CRYPTO_PATH.'securimage/backgrounds'),
     67  'PRESETS' => $presets,
     68  'CRYPTO_PATH' => CRYPTO_PATH,
     69  ));
     70
     71$template->set_filename('plugin_admin_content', realpath(CRYPTO_PATH . 'template/admin.tpl'));
     72$template->assign_var_from_handle('ADMIN_CONTENT', 'plugin_admin_content');
     73
     74
     75
    5876function list_fonts($dir)
    5977{
     
    6583  {
    6684    if ($file !== '.' && $file !== '..' && get_extension($file)=='ttf')
    67       $fonts[] = get_filename_wo_extension($file);
     85    {
     86      $fonts[get_filename_wo_extension($file)] = $dir . '/' . $file;
     87    }
    6888  }
    6989 
     
    7292}
    7393
    74 $template->assign(array(
    75   'crypto' => $conf['cryptographp'],
    76   'loaded' => $loaded,
    77   'fonts' => list_fonts(CRYPTO_PATH.'securimage/fonts'),
    78   'PRESETS' => $presets,
    79   'CRYPTO_PATH' => CRYPTO_PATH,
    80   ));
    81 
    82 $template->set_filename('plugin_admin_content', dirname(__FILE__).'/template/admin.tpl');
    83 $template->assign_var_from_handle('ADMIN_CONTENT', 'plugin_admin_content');
     94function list_backgrounds($dir)
     95{
     96  $dir = rtrim($dir, '/');
     97  $dh = opendir($dir);
     98  $backgrounds = array();
     99 
     100  while (($file = readdir($dh)) !== false )
     101  {
     102    if ($file !== '.' && $file !== '..')
     103    {
     104      $ext = get_extension($file);
     105      if ($ext=='jpg' || $ext=='png' || $ext=='jpeg' || $ext=='gif')
     106      {
     107        $backgrounds[$file] = $dir . '/' . $file;
     108      }
     109    }
     110  }
     111 
     112  closedir($dh);
     113  return $backgrounds;
     114}
  • extensions/CryptograPHP/language/en_UK/plugin.lang.php

    r26041 r26554  
    55$lang['Solve equation'] = 'Solve equation';
    66$lang['Button color'] = 'Button color';
    7 
    87$lang['Comments action'] = 'Comments action';
    98$lang['Moderate'] = 'Moderate';
     
    2120$lang['Contact form'] = 'Contact form';
    2221$lang['Guestbook'] = 'Guestbook';
    23 
    2422$lang['Perturbation'] = 'Perturbation';
    2523$lang['range:'] = 'range:';
     
    3331$lang['Font'] = 'Font';
    3432$lang['Preview'] = 'Preview';
     33$lang['Background'] = 'Background';
     34$lang['Background image'] = 'Background image';
     35$lang['Color'] = 'Color';
     36$lang['Image'] = 'Image';
    3537$lang['Tip: type "random" on a color field to have a random color'] = 'Tip: type "random" on a color field to have a random color';
    3638$lang['We detected that EasyCaptcha plugin is available on your gallery. Both plugins can be used at the same time, but you should not under any circumstances activate both of them on the same page.'] = 'We detected that EasyCaptcha plugin is available in your gallery. Both plugins can be used at the same time, but you should not under any circumstances activate both of them on the same page.';
  • extensions/CryptograPHP/language/fr_FR/plugin.lang.php

    r26041 r26554  
    55$lang['Solve equation'] = 'Resolvez l\'équation';
    66$lang['Button color'] = 'Couleur du bouton';
    7 
    87$lang['Comments action'] = 'Action pour les commentaires';
    98$lang['Moderate'] = 'Modérer';
     
    2120$lang['Contact form'] = 'Formulaire de contact';
    2221$lang['Guestbook'] = 'Livre d\'or';
    23 
    2422$lang['Perturbation'] = 'Perturbation';
    2523$lang['range:'] = 'plage:';
     
    3331$lang['Font'] = 'Police';
    3432$lang['Preview'] = 'Prévisualisation';
     33$lang['Background'] = 'Fond';
     34$lang['Background image'] = 'Image de fond';
     35$lang['Color'] = 'Couleur';
     36$lang['Image'] = 'Image';
    3537$lang['Tip: type "random" on a color field to have a random color'] = 'Astuce: inscrivez "random" dans un champs de couleur pour avoir une couleur aléatoire';
    3638$lang['We detected that EasyCaptcha plugin is available on your gallery. Both plugins can be used at the same time, but you should not under any circumstances activate both of them on the same page.'] = 'Nous avons détecté que EasyCaptcha est activé sur votre galerie. Les deux plugins peuvent fonctionner en même temps, mais sous aucun prétexte ils ne doivent être activés sur les mêmes pages.';
  • extensions/CryptograPHP/maintain.inc.php

    r26072 r26554  
    3232        'width'           => 180,
    3333        'height'          => 70,
    34         'perturbation'    => 1,
    35         'image_bg_color'  => 'ffffff',
     34        'perturbation'    => 1,
     35        'background'      => 'color',
     36        'bg_color'        => 'ffffff',
     37        'bg_image'        => '',
    3638        'text_color'      => '8a8a8a',
    3739        'num_lines'       => 2,
     
    6466        $old_conf['button_color'] = 'dark';
    6567      }
     68      if (!isset($old_conf['background']))
     69      {
     70        $old_conf['background'] = 'color';
     71        $old_conf['bg_color'] = $old_conf['image_bg_color'];
     72        $old_conf['bg_image'] = '';
     73        unset($old_conf['image_bg_color']);
     74      }
    6675     
    6776      $conf['cryptographp'] = serialize($old_conf);
  • extensions/CryptograPHP/securimage/README.txt

    r12617 r26554  
    33    Securimage - A PHP class for creating captcha images and audio with many options.
    44
    5 VERSION: 3.0
     5VERSION: 3.5.1
    66
    77AUTHOR:
     
    2323    GD  2.0
    2424    FreeType (Required, for TTF fonts)
     25    PDO (if using Sqlite, MySQL, or PostgreSQL)
    2526
    2627SYNOPSIS:
     
    5354
    5455COPYRIGHT:
    55     Copyright (c) 2011 Drew Phillips
     56    Copyright (c) 2013 Drew Phillips
    5657    All rights reserved.
    5758
     
    7879
    7980    -----------------------------------------------------------------------------
    80     Flash code created for Securimage by Mario Romero (animario@hotmail.com)
     81    The WavFile.php class used in Securimage by Drew Phillips and Paul Voegler is
     82    used under the BSD License.  See WavFile.php for details.
     83    Many thanks to Paul Voegler (http://www.voegler.eu/) for contributing to
     84    Securimage.
     85
     86    -----------------------------------------------------------------------------
     87    Flash code created for Securimage by Age Bosma & Mario Romero (animario@hotmail.com)
    8188    Many thanks for releasing this to the project!
    8289
     
    107114    yann@lecoroller.com
    108115
     116    -------------------------------------------------------------------------------
     117    Portions of securimage_play.swf use the PopForge flash library for playing audio
     118
     119    /**
     120     * Copyright(C) 2007 Andre Michelle and Joa Ebert
     121     *
     122     * PopForge is an ActionScript3 code sandbox developed by Andre Michelle and Joa Ebert
     123     * http://sandbox.popforge.de
     124     *
     125     * PopforgeAS3Audio is free software; you can redistribute it and/or modify
     126     * it under the terms of the GNU General Public License as published by
     127     * the Free Software Foundation; either version 3 of the License, or
     128     * (at your option) any later version.
     129     *
     130     * PopforgeAS3Audio is distributed in the hope that it will be useful,
     131     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     132     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     133     * GNU General Public License for more details.
     134     *
     135     * You should have received a copy of the GNU General Public License
     136     * along with this program. If not, see <http://www.gnu.org/licenses/>
     137     */
     138     
     139     -------------------------------------------------------------------------------
     140     Some graphics used are from the Humility Icon Pack by WorLord
     141
     142     License: GNU/GPL (http://findicons.com/pack/1723/humility)
     143     http://findicons.com/icon/192558/gnome_volume_control
     144     http://findicons.com/icon/192562/gtk_refresh
     145
     146     -------------------------------------------------------------------------------
     147     Background noise sound files are from SoundJay.com
     148     http://www.soundjay.com/tos.html
     149     
     150     All sound effects on this website are created by us and protected under
     151     the copyright laws, international treaty provisions and other applicable
     152     laws. By downloading sounds, music or any material from this site implies
     153     that you have read and accepted these terms and conditions:
     154
     155     Sound Effects
     156     You are allowed to use the sounds free of charge and royalty free in your
     157     projects (such as films, videos, games, presentations, animations, stage
     158     plays, radio plays, audio books, apps) be it for commercial or
     159     non-commercial purposes.
     160   
     161     But you are NOT allowed to
     162     - post the sounds (as sound effects or ringtones) on any website for
     163       others to download, copy or use
     164     - use them as a raw material to create sound effects or ringtones that
     165       you will sell, distribute or offer for downloading
     166     - sell, re-sell, license or re-license the sounds (as individual sound
     167       effects or as a sound effects library) to anyone else
     168     - claim the sounds as yours
     169     - link directly to individual sound files
     170     - distribute the sounds in apps or computer programs that are clearly
     171       sound related in nature (such as sound machine, sound effect
     172       generator, ringtone maker, funny sounds app, sound therapy app, etc.)
     173       or in apps or computer programs that use the sounds as the program's
     174       sound resource library for other people's use (such as animation
     175       creator, digital book creator, song maker software, etc.). If you are
     176       developing such computer programs, contact us for licensing options.
     177   
     178     If you use the sound effects, please consider giving us a credit and
     179     linking back to us but it's not required.
     180     
     181     
  • extensions/CryptograPHP/securimage/securimage.php

    r12617 r26554  
    11<?php
     2
     3// error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
     4
    25/**
    3  * Project:     Securimage: A PHP class for creating and managing form CAPTCHA images
    4  * File:        securimage.php
     6 * Project:     Securimage: A PHP class for creating and managing form CAPTCHA images<br />
     7 * File:        securimage.php<br />
    58 *
    6  * Copyright (c) 2011, Drew Phillips
     9 * Copyright (c) 2013, Drew Phillips
    710 * All rights reserved.
    8  * 
     11 *
    912 * Redistribution and use in source and binary forms, with or without modification,
    1013 * are permitted provided that the following conditions are met:
    11  * 
     14 *
    1215 *  - Redistributions of source code must retain the above copyright notice,
    1316 *    this list of conditions and the following disclaimer.
     
    1518 *    this list of conditions and the following disclaimer in the documentation
    1619 *    and/or other materials provided with the distribution.
    17  * 
     20 *
    1821 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    1922 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     
    2932 *
    3033 * Any modifications to the library should be indicated clearly in the source code
    31  * to inform users that the changes are not a part of the original software.
     34 * to inform users that the changes are not a part of the original software.<br /><br />
    3235 *
    33  * If you found this script useful, please take a quick moment to rate it.
     36 * If you found this script useful, please take a quick moment to rate it.<br />
    3437 * http://www.hotscripts.com/rate/49400.html  Thanks.
    3538 *
     
    3740 * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
    3841 * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
    39  * @copyright 2011 Drew Phillips
     42 * @copyright 2013 Drew Phillips
    4043 * @author Drew Phillips <drew@drew-phillips.com>
    41  * @version 3.0 (October 2011)
     44 * @version 3.5.1 (June 21, 2013)
    4245 * @package Securimage
    4346 *
     
    4649/**
    4750 ChangeLog
    48  
     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
    4998 3.0
    5099 - Rewrite class using PHP5 OOP
     
    78127 - Audio output is mp3 format by default
    79128 - Change font to AlteHaasGrotesk by yann le coroller
    80  - Some code cleanup 
     129 - Some code cleanup
    81130
    82131 1.0.4 (unreleased)
     
    108157 * Securimage CAPTCHA Class.
    109158 *
    110  * @version    3.0
     159 * @version    3.5
    111160 * @package    Securimage
    112161 * @subpackage classes
     
    116165class Securimage
    117166{
    118         // All of the public variables below are securimage options
    119         // They can be passed as an array to the Securimage constructor, set below,
    120         // or set from securimage_show.php and securimage_play.php
    121        
     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
    122171    /**
    123172     * Renders captcha as a JPEG image
     
    135184     */
    136185    const SI_IMAGE_GIF  = 3;
    137    
     186
    138187    /**
    139188     * Create a normal alphanumeric captcha
     
    146195     */
    147196    const SI_CAPTCHA_MATHEMATIC = 1;
    148    
     197    /**
     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
    149227    /**
    150228     * The width of the captcha image
     
    180258    /**
    181259     * The color of the noise that is drawn
    182      * @var Securimage_Color 
     260     * @var Securimage_Color
    183261     */
    184262    public $noise_color    = '#707070';
    185    
     263
    186264    /**
    187265     * How transparent to make the text 0 = completely opaque, 100 = invisible
    188266     * @var int
    189267     */
    190     public $text_transparency_percentage = 50;
     268    public $text_transparency_percentage = 20;
    191269    /**
    192270     * Whether or not to draw the text transparently, true = use transparency, false = no transparency
    193271     * @var bool
    194272     */
    195     public $use_transparent_text         = false;
    196    
     273    public $use_transparent_text         = true;
     274
    197275    /**
    198276     * The length of the captcha code
     
    215293     */
    216294    public $expiry_time    = 900;
    217    
     295
    218296    /**
    219297     * The session name securimage should use, only set this if your application uses a custom session name
     
    222300     */
    223301    public $session_name   = null;
    224    
     302
    225303    /**
    226304     * true to use the wordlist file, false to generate random captcha codes
     
    233311     * @var double
    234312     */
    235     public $perturbation = 0.75;
     313    public $perturbation = 0.85;
    236314    /**
    237315     * How many lines to draw over the captcha code to increase security
    238316     * @var int
    239317     */
    240     public $num_lines    = 8;
     318    public $num_lines    = 5;
    241319    /**
    242320     * The level of noise (random dots) to place on the image, 0-10
    243321     * @var int
    244322     */
    245     public $noise_level  = 0;
    246    
     323    public $noise_level  = 2;
     324
    247325    /**
    248326     * The signature text to draw on the bottom corner of the image
     
    260338     */
    261339    public $signature_font;
    262    
    263     /**
     340
     341    /**
     342     * DO NOT USE!!!
    264343     * Use an SQLite database to store data (for users that do not support cookies)
    265344     * @var bool
     345     * @see Securimage::$use_sqlite_db
     346     * @deprecated 3.2RC4
    266347     */
    267348    public $use_sqlite_db = false;
    268    
     349
     350    /**
     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
    269420    /**
    270421     * The type of captcha to create, either alphanumeric, or a math problem<br />
     
    272423     * @var int
    273424     */
    274     public $captcha_type  = self::SI_CAPTCHA_STRING;
    275    
     425    public $captcha_type  = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC;
     426
    276427    /**
    277428     * 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
     
    281432     * // in securimage_show.php (create one show script for each form)
    282433     * $img->namespace = 'contact_form';
    283      * 
     434     *
    284435     * // in form validator
    285436     * $img->namespace = 'contact_form';
     
    290441     */
    291442    public $namespace;
    292    
     443
    293444    /**
    294445     * The font file to use to draw the captcha code, leave blank for default font AHGBold.ttf
     
    308459    /**
    309460     * The path to the SQLite database file to use, if $use_sqlite_database = true, should be chmod 666
     461     * @deprecated 3.2RC4
    310462     * @var string
    311463     */
     
    315467     * @var string
    316468     * <code>
    317      * $img->audio_path = '/home/yoursite/public_html/securimage/audio/';
     469     * $img->audio_path = '/home/yoursite/public_html/securimage/audio/en/';
    318470     * </code>
    319471     */
    320472    public $audio_path;
    321 
    322    
    323    
     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;
     534
     535    /**
     536     * Captcha ID if using static captcha
     537     * @var string Unique captcha id
     538     */
     539    protected static $_captchaId = null;
     540
    324541    protected $im;
    325542    protected $tmpimg;
    326543    protected $bgimg;
    327544    protected $iscale = 5;
    328    
    329     protected $securimage_path = null;
    330    
     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     */
    331553    protected $code;
     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     */
    332560    protected $code_display;
    333    
     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     */
    334577    protected $captcha_code;
    335     protected $sqlite_handle;
    336    
     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
    337608    protected $gdbgcolor;
    338609    protected $gdtextcolor;
    339610    protected $gdlinecolor;
    340611    protected $gdsignaturecolor;
    341    
     612
    342613    /**
    343614     * Create a new securimage object, pass options to set in the constructor.<br />
     
    352623     *     'font_file' => Securimage::getPath() . '/custom.ttf'
    353624     * );
    354      * 
     625     *
    355626     * $img = new Securimage($options);
    356627     * </code>
     
    359630    {
    360631        $this->securimage_path = dirname(__FILE__);
    361        
     632
    362633        if (is_array($options) && sizeof($options) > 0) {
    363634            foreach($options as $prop => $val) {
    364                 $this->$prop = $val;
     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                }
    365643            }
    366644        }
     
    372650        $this->signature_color = $this->initColor($this->signature_color, '#616161');
    373651
    374         if ($this->ttf_file == null) {
     652        if (is_null($this->ttf_file)) {
    375653            $this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
    376654        }
    377        
     655
    378656        $this->signature_font = $this->ttf_file;
    379        
    380         if ($this->wordlist_file == null) {
     657
     658        if (is_null($this->wordlist_file)) {
    381659            $this->wordlist_file = $this->securimage_path . '/words/words.txt';
    382660        }
    383        
    384         if ($this->sqlite_database == null) {
    385             $this->sqlite_database = $this->securimage_path . '/database/securimage.sqlite';
    386         }
    387        
    388         if ($this->audio_path == null) {
    389             $this->audio_path = $this->securimage_path . '/audio/';
    390         }
    391        
    392         if ($this->code_length == null || $this->code_length < 1) {
     661
     662        if (is_null($this->database_file)) {
     663            $this->database_file = $this->securimage_path . '/database/securimage.sq3';
     664        }
     665
     666        if (is_null($this->audio_path)) {
     667            $this->audio_path = $this->securimage_path . '/audio/en/';
     668        }
     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) {
    393683            $this->code_length = 6;
    394684        }
    395        
    396         if ($this->perturbation == null || !is_numeric($this->perturbation)) {
     685
     686        if (is_null($this->perturbation) || !is_numeric($this->perturbation)) {
    397687            $this->perturbation = 0.75;
    398688        }
    399        
    400         if ($this->namespace == null || !is_string($this->namespace)) {
     689
     690        if (is_null($this->namespace) || !is_string($this->namespace)) {
    401691            $this->namespace = 'default';
    402692        }
    403693
    404         // Initialize session or attach to existing
    405         if ( session_id() == '' ) { // no session has been started yet, which is needed for validation
    406             if ($this->session_name != null && trim($this->session_name) != '') {
    407                 session_name(trim($this->session_name)); // set session name if provided
    408             }
    409             session_start();
    410         }
    411     }
    412    
     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();
     713            }
     714        }
     715    }
     716
    413717    /**
    414718     * Return the absolute path to the Securimage directory
     
    419723        return dirname(__FILE__);
    420724    }
    421    
     725
     726    /**
     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
    422794    /**
    423795     * Used to serve a captcha image to the browser
    424796     * @param string $background_image The path to the background image to use
    425      * <code> 
     797     * <code>
    426798     * $img = new Securimage();
    427799     * $img->code_length = 6;
    428800     * $img->num_lines   = 5;
    429801     * $img->noise_level = 5;
    430      * 
     802     *
    431803     * $img->show(); // sends the image to browser
    432804     * exit;
     
    435807    public function show($background_image = '')
    436808    {
     809        set_error_handler(array(&$this, 'errorHandler'));
     810
    437811        if($background_image != '' && is_readable($background_image)) {
    438812            $this->bgimg = $background_image;
     
    441815        $this->doImage();
    442816    }
    443    
     817
    444818    /**
    445819     * Check a submitted code against the stored value
     
    461835        return $this->correct_code;
    462836    }
    463    
     837
    464838    /**
    465839     * Output a wav file of the captcha code to the browser
    466      * 
     840     *
    467841     * <code>
    468842     * $img = new Securimage();
     
    473847    public function outputAudioFile()
    474848    {
    475         $ext = 'wav'; // force wav - mp3 is insecure
    476        
    477         header("Content-Disposition: attachment; filename=\"securimage_audio.{$ext}\"");
    478         header('Cache-Control: no-store, no-cache, must-revalidate');
    479         header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
    480         header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
    481         header('Content-type: audio/x-wav');
    482        
    483         $audio = $this->getAudibleCode($ext);
    484 
    485         header('Content-Length: ' . strlen($audio));
    486 
    487         echo $audio;
    488         exit;
    489     }
    490    
     849        set_error_handler(array(&$this, 'errorHandler'));
     850
     851        require_once dirname(__FILE__) . '/WavFile.php';
     852
     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;
     891    }
     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
    491941    /**
    492942     * The main image drawing routing, responsible for constructing the entire image and serving it
     
    499949            $imagecreate = 'imagecreate';
    500950        }
    501        
     951
    502952        $this->im     = $imagecreate($this->image_width, $this->image_height);
    503953        $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
    504        
     954
    505955        $this->allocateColors();
    506956        imagepalettecopy($this->tmpimg, $this->im);
     
    508958        $this->setBackground();
    509959
    510         $this->createCode();
    511        
    512         $this->drawWord();
     960        $code = '';
     961
     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        }
    513990
    514991        if ($this->noise_level > 0) {
    515992            $this->drawNoise();
    516993        }
    517        
     994
     995        $this->drawWord();
     996
    518997        if ($this->perturbation > 0 && is_readable($this->ttf_file)) {
    519998            $this->distortedCopy();
     
    5301009        $this->output();
    5311010    }
    532    
     1011
    5331012    /**
    5341013     * Allocate the colors to be used for the image
     
    5411020                                              $this->image_bg_color->g,
    5421021                                              $this->image_bg_color->b);
    543        
     1022
    5441023        $alpha = intval($this->text_transparency_percentage / 100 * 127);
    545        
     1024
    5461025        if ($this->use_transparent_text == true) {
    5471026            $this->gdtextcolor = imagecolorallocatealpha($this->im,
     
    5741053                                                          $this->noise_color->b);
    5751054        }
    576    
     1055
    5771056        $this->gdsignaturecolor = imagecolorallocate($this->im,
    5781057                                                     $this->signature_color->r,
     
    5811060
    5821061    }
    583    
     1062
    5841063    /**
    5851064     * The the background color, or background image to be used
     
    5941073                             $this->image_width * $this->iscale, $this->image_height * $this->iscale,
    5951074                             $this->gdbgcolor);
    596    
     1075
    5971076        if ($this->bgimg == '') {
    598             if ($this->background_directory != null && 
     1077            if ($this->background_directory != null &&
    5991078                is_dir($this->background_directory) &&
    6001079                is_readable($this->background_directory))
     
    6061085            }
    6071086        }
    608        
     1087
    6091088        if ($this->bgimg == '') {
    6101089            return;
     
    6121091
    6131092        $dat = @getimagesize($this->bgimg);
    614         if($dat == false) { 
     1093        if($dat == false) {
    6151094            return;
    6161095        }
     
    6291108                         imagesx($newim), imagesy($newim));
    6301109    }
    631    
     1110
    6321111    /**
    6331112     * Scan the directory for a background image to use
     
    6451124
    6461125            if (sizeof($images) > 0) {
    647                 return rtrim($this->background_directory, '/') . '/' . $images[rand(0, sizeof($images)-1)];
     1126                return rtrim($this->background_directory, '/') . '/' . $images[mt_rand(0, sizeof($images)-1)];
    6481127            }
    6491128        }
     
    6511130        return false;
    6521131    }
    653    
     1132
    6541133    /**
    6551134     * Generates the code or math problem and saves the value to the session
    6561135     */
    657     protected function createCode()
     1136    public function createCode()
    6581137    {
    6591138        $this->code = false;
     
    6621141            case self::SI_CAPTCHA_MATHEMATIC:
    6631142            {
    664                 $signs = array('+', '-', 'x');
    665                 $left  = rand(1, 10);
    666                 $right = rand(1, 5);
    667                 $sign  = $signs[rand(0, 2)];
    668                
    669                 switch($sign) {
    670                     case 'x': $c = $left * $right; break;
    671                     case '-': $c = $left - $right; break;
    672                     default:  $c = $left + $right; break;
    673                 }
    674                
     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
    6751156                $this->code         = $c;
    6761157                $this->code_display = "$left $sign $right";
    6771158                break;
    6781159            }
    679            
     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
    6801167            default:
    6811168            {
     
    6871174                    $this->code = $this->generateCode($this->code_length);
    6881175                }
    689                
     1176
    6901177                $this->code_display = $this->code;
    6911178                $this->code         = ($this->case_sensitive) ? $this->code : strtolower($this->code);
    6921179            } // default
    6931180        }
    694        
     1181
    6951182        $this->saveData();
    6961183    }
    697    
     1184
    6981185    /**
    6991186     * Draws the captcha code on the image
     
    7031190        $width2  = $this->image_width * $this->iscale;
    7041191        $height2 = $this->image_height * $this->iscale;
    705          
     1192
    7061193        if (!is_readable($this->ttf_file)) {
    7071194            imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
     
    7271214            }
    7281215        }
    729        
     1216
    7301217        // DEBUG
    7311218        //$this->im = $this->tmpimg;
    7321219        //$this->output();
    733        
    734     }
    735    
     1220
     1221    }
     1222
    7361223    /**
    7371224     * Copies the captcha image to the final image with distortion applied
     
    7421229        // make array of poles AKA attractor points
    7431230        for ($i = 0; $i < $numpoles; ++ $i) {
    744             $px[$i]  = rand($this->image_width  * 0.2, $this->image_width  * 0.8);
    745             $py[$i]  = rand($this->image_height * 0.2, $this->image_height * 0.8);
    746             $rad[$i] = rand($this->image_height * 0.2, $this->image_height * 0.8);
     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);
    7471234            $tmp     = ((- $this->frand()) * 0.15) - .15;
    7481235            $amp[$i] = $this->perturbation * $tmp;
    7491236        }
    750        
     1237
    7511238        $bgCol = imagecolorat($this->tmpimg, 0, 0);
    7521239        $width2 = $this->iscale * $this->image_width;
     
    7841271        }
    7851272    }
    786    
     1273
    7871274    /**
    7881275     * Draws distorted lines on the image
     
    7931280            $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
    7941281            $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
    795             $y = rand($this->image_height * 0.1, $this->image_height * 0.9);
    796            
     1282            $y = mt_rand($this->image_height * 0.1, $this->image_height * 0.9);
     1283
    7971284            $theta = ($this->frand() - 0.5) * M_PI * 0.7;
    7981285            $w = $this->image_width;
    799             $len = rand($w * 0.4, $w * 0.7);
    800             $lwid = rand(0, 2);
    801            
     1286            $len = mt_rand($w * 0.4, $w * 0.7);
     1287            $lwid = mt_rand(0, 2);
     1288
    8021289            $k = $this->frand() * 0.6 + 0.2;
    8031290            $k = $k * $k * 0.5;
     
    8101297            $x0 = $x - 0.5 * $len * cos($theta);
    8111298            $y0 = $y - 0.5 * $len * sin($theta);
    812            
     1299
    8131300            $ldx = round(- $dy * $lwid);
    8141301            $ldy = round($dx * $lwid);
    815            
     1302
    8161303            for ($i = 0; $i < $n; ++ $i) {
    8171304                $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
     
    8211308        }
    8221309    }
    823    
     1310
    8241311    /**
    8251312     * Draws random noise on the image
     
    8341321
    8351322        $t0 = microtime(true);
    836        
     1323
    8371324        $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
    838        
     1325
    8391326        $points = $this->image_width * $this->image_height * $this->iscale;
    8401327        $height = $this->image_height * $this->iscale;
    8411328        $width  = $this->image_width * $this->iscale;
    8421329        for ($i = 0; $i < $noise_level; ++$i) {
    843             $x = rand(10, $width);
    844             $y = rand(10, $height);
    845             $size = rand(7, 10);
     1330            $x = mt_rand(10, $width);
     1331            $y = mt_rand(10, $height);
     1332            $size = mt_rand(7, 10);
    8461333            if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy
    8471334            imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
    8481335        }
    849        
     1336
    8501337        $t1 = microtime(true);
    851        
     1338
    8521339        $t = $t1 - $t0;
    853        
     1340
    8541341        /*
    8551342        // DEBUG
     
    8601347        */
    8611348    }
    862    
    863         /**
    864         * Print signature text on image
    865         */
     1349
     1350    /**
     1351    * Print signature text on image
     1352    */
    8661353    protected function addSignature()
    8671354    {
    868         if ($this->use_gd_font) {
    869             imagestring($this->im, 5, $this->image_width - (strlen($this->image_signature) * 10), $this->image_height - 20, $this->image_signature, $this->gdsignaturecolor);
     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);
     1361    }
     1362
     1363    /**
     1364     * Sends the appropriate image and cache headers and outputs image to the browser
     1365     */
     1366    protected function output()
     1367    {
     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            }
    8701395        } else {
    871              
    872             $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
    873             $textlen = $bbox[2] - $bbox[0];
    874             $x = $this->image_width - $textlen - 5;
    875             $y = $this->image_height - 3;
    876              
    877             imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
    878         }
    879     }
    880    
    881     /**
    882      * Sends the appropriate image and cache headers and outputs image to the browser
    883      */
    884     protected function output()
    885     {
    886         header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
    887         header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
    888         header("Cache-Control: no-store, no-cache, must-revalidate");
    889         header("Cache-Control: post-check=0, pre-check=0", false);
    890         header("Pragma: no-cache");
    891        
    892         switch ($this->image_type) {
    893             case self::SI_IMAGE_JPEG:
    894                 header("Content-Type: image/jpeg");
    895                 imagejpeg($this->im, null, 90);
    896                 break;
    897             case self::SI_IMAGE_GIF:
    898                 header("Content-Type: image/gif");
    899                 imagegif($this->im);
    900                 break;
    901             default:
    902                 header("Content-Type: image/png");
    903                 imagepng($this->im);
    904                 break;
    905         }
    906        
     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>';
     1400        }
     1401
    9071402        imagedestroy($this->im);
    908         exit();
    909     }
    910    
     1403        restore_error_handler();
     1404
     1405        if (!$this->no_exit) exit;
     1406    }
     1407
    9111408    /**
    9121409     * Gets the code and returns the binary audio file for the stored captcha code
    913      * @param string $format WAV only
    914      */
    915     protected function getAudibleCode($format = 'wav')
    916     {
    917         // override any format other than wav for now
    918         // this is due to security issues with MP3 files
    919         $format  = 'wav';
    920        
     1410     *
     1411     * @return The audio representation of the captcha in Wav format
     1412     */
     1413    protected function getAudibleCode()
     1414    {
    9211415        $letters = array();
    922         $code    = $this->getCode();
    923 
    924         if ($code == '') {
    925             $this->createCode();
    926             $code = $this->getCode();
    927         }
    928 
    929         for($i = 0; $i < strlen($code); ++$i) {
    930             $letters[] = $code{$i};
    931         }
    932        
    933         if ($format == 'mp3') {
    934             return $this->generateMP3($letters);
     1416        $code    = $this->getCode(true, true);
     1417
     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            }
     1425        }
     1426
     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);
    9351435        } 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            }
     1444        }
     1445
     1446        try {
    9361447            return $this->generateWAV($letters);
     1448        } catch(Exception $ex) {
     1449            throw $ex;
    9371450        }
    9381451    }
     
    9411454     * Gets a captcha code from a wordlist
    9421455     */
    943     protected function readCodeFromFile()
    944     {
    945         $fp = @fopen($this->wordlist_file, 'rb');
     1456    protected function readCodeFromFile($numWords = 1)
     1457    {
     1458        $fp = fopen($this->wordlist_file, 'rb');
    9461459        if (!$fp) return false;
    9471460
     
    9491462        if ($fsize < 128) return false; // too small of a list to be effective
    9501463
    951         fseek($fp, rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64
    952         $data = fread($fp, 64); // read a chunk from our random position
     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
    9531487        fclose($fp);
    954         $data = preg_replace("/\r?\n/", "\n", $data);
    955 
    956         $start = @strpos($data, "\n", rand(0, 56)) + 1; // random start position
    957         $end   = @strpos($data, "\n", $start);          // find end of word
    958        
    959         if ($start === false) {
    960             return false;
    961         } else if ($end === false) {
    962             $end = strlen($data);
    963         }
    964 
    965         return strtolower(substr($data, $start, $end - $start)); // return a line of the file
    966     }
    967    
     1488
     1489        if ($numWords < 2) {
     1490            return $words[0];
     1491        } else {
     1492            return $words;
     1493        }
     1494    }
     1495
    9681496    /**
    9691497     * Generates a random captcha code from the set character set
     
    9731501        $code = '';
    9741502
    975         for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) {
    976             $code .= $this->charset{rand(0, $cslen - 1)};
    977         }
    978        
    979         //return 'testing';  // debug, set the code to given string
    980        
     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            }
     1511        }
     1512
    9811513        return $code;
    9821514    }
    983    
     1515
    9841516    /**
    9851517     * Checks the entered code against the value stored in the session or sqlite database, handles case sensitivity
     
    9881520    protected function validate()
    9891521    {
    990         $code = $this->getCode();
    991         // returns stored code, or an empty string if no stored code was found
    992         // checks the session and sqlite database if enabled
    993        
     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
    9941530        if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) {
    9951531            // case sensitive was set from securimage_show.php but not in class
     
    9971533            $this->case_sensitive = true;
    9981534        }
    999        
     1535
    10001536        $code_entered = trim( (($this->case_sensitive) ? $this->code_entered
    10011537                                                       : strtolower($this->code_entered))
    10021538                        );
    10031539        $this->correct_code = false;
    1004        
     1540
    10051541        if ($code != '') {
     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
    10061548            if ($code == $code_entered) {
    10071549                $this->correct_code = true;
    1008                 $_SESSION['securimage_code_value'][$this->namespace] = '';
    1009                 $_SESSION['securimage_code_ctime'][$this->namespace] = '';
     1550                if ($this->no_session != true) {
     1551                    $_SESSION['securimage_code_value'][$this->namespace] = '';
     1552                    $_SESSION['securimage_code_ctime'][$this->namespace] = '';
     1553                }
    10101554                $this->clearCodeFromDatabase();
    10111555            }
    10121556        }
    10131557    }
    1014    
    1015     /**
    1016      * Return the code from the session or sqlite database if used.  If none exists yet, an empty string is returned
    1017      */
    1018     protected function getCode()
     1558
     1559    /**
     1560     * Save data to session namespace and database if used
     1561     */
     1562    protected function saveData()
     1563    {
     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        }
     1579    }
     1580
     1581    /**
     1582     * Saves the code to the sqlite database
     1583     */
     1584    protected function saveCodeToDatabase()
     1585    {
     1586        $success = false;
     1587        $this->openDatabase();
     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            }
     1615        }
     1616
     1617        return $success !== false;
     1618    }
     1619
     1620    /**
     1621     * Open sqlite database
     1622     */
     1623    protected function openDatabase()
     1624    {
     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;
     1643                }
     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;
     1649            }
     1650        }
     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;
     1678    }
     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
     1806    /**
     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"
     1812     */
     1813    protected function getCodeFromDatabase()
    10191814    {
    10201815        $code = '';
    1021        
    1022         if (isset($_SESSION['securimage_code_value'][$this->namespace]) &&
    1023          trim($_SESSION['securimage_code_value'][$this->namespace]) != '') {
    1024             if ($this->isCodeExpired(
    1025             $_SESSION['securimage_code_ctime'][$this->namespace]) == false) {
    1026                 $code = $_SESSION['securimage_code_value'][$this->namespace];
    1027             }
    1028         } else if ($this->use_sqlite_db == true && function_exists('sqlite_open')) {
    1029             // no code in session - may mean user has cookies turned off
    1030             $this->openDatabase();
    1031             $code = $this->getCodeFromDatabase();
    1032         } else { /* no code stored in session or sqlite database, validation will fail */ }
    1033        
     1816
     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;
     1825
     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            }
     1831
     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                    }
     1846                }
     1847            }
     1848        }
     1849
    10341850        return $code;
    10351851    }
    1036    
    1037     /**
    1038      * Save data to session namespace and database if used
    1039      */
    1040     protected function saveData()
    1041     {
    1042         $_SESSION['securimage_code_value'][$this->namespace] = $this->code;
    1043         $_SESSION['securimage_code_ctime'][$this->namespace] = time();
    1044        
    1045         $this->saveCodeToDatabase();
    1046     }
    1047    
    1048     /**
    1049      * Saves the code to the sqlite database
    1050      */
    1051     protected function saveCodeToDatabase()
    1052     {
    1053         $success = false;
    1054        
    1055         $this->openDatabase();
    1056        
    1057         if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
    1058             $ip      = $_SERVER['REMOTE_ADDR'];
    1059             $time    = time();
    1060             $code    = $_SESSION['securimage_code_value'][$this->namespace]; // if cookies are disabled the session still exists at this point
    1061             $success = sqlite_query($this->sqlite_handle,
    1062                                     "INSERT OR REPLACE INTO codes(ip, code, namespace, created)
    1063                                     VALUES('$ip', '$code', '{$this->namespace}', $time)");
    1064         }
    1065        
    1066         return $success !== false;
    1067     }
    1068    
    1069     /**
    1070      * Open sqlite database
    1071      */
    1072     protected function openDatabase()
    1073     {
    1074         $this->sqlite_handle = false;
    1075        
    1076         if ($this->use_sqlite_db && function_exists('sqlite_open')) {
    1077             $this->sqlite_handle = sqlite_open($this->sqlite_database, 0666, $error);
    1078            
    1079             if ($this->sqlite_handle !== false) {
    1080                 $res = sqlite_query($this->sqlite_handle, "PRAGMA table_info(codes)");
    1081                 if (sqlite_num_rows($res) == 0) {
    1082                     sqlite_query($this->sqlite_handle, "CREATE TABLE codes (ip VARCHAR(32) PRIMARY KEY, code VARCHAR(32) NOT NULL, namespace VARCHAR(32) NOT NULL, created INTEGER)");
    1083                 }
    1084             }
    1085            
    1086             return $this->sqlite_handle != false;
    1087         }
    1088        
    1089         return $this->sqlite_handle;
    1090     }
    1091    
    1092     /**
    1093      * Get a code from the sqlite database for ip address
    1094      */
    1095     protected function getCodeFromDatabase()
    1096     {
    1097         $code = '';
    1098 
    1099         if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
     1852
     1853    /**
     1854     * Remove an entered code from the database
     1855     */
     1856    protected function clearCodeFromDatabase()
     1857    {
     1858        if ($this->pdo_conn) {
    11001859            $ip = $_SERVER['REMOTE_ADDR'];
    1101             $ns = sqlite_escape_string($this->namespace);
    1102 
    1103             $res = sqlite_query($this->sqlite_handle, "SELECT * FROM codes WHERE ip = '$ip' AND namespace = '$ns'");
    1104             if ($res && sqlite_num_rows($res) > 0) {
    1105                 $res = sqlite_fetch_array($res);
    1106 
    1107                 if ($this->isCodeExpired($res['created']) == false) {
    1108                     $code = $res['code'];
    1109                 }
    1110             }
    1111         }
    1112         return $code;
    1113     }
    1114    
    1115     /**
    1116      * Remove an entered code from the database
    1117      */
    1118     protected function clearCodeFromDatabase()
    1119     {
    1120         if (is_resource($this->sqlite_handle)) {
    1121             $ip = $_SERVER['REMOTE_ADDR'];
    1122             $ns = sqlite_escape_string($this->namespace);
    1123            
    1124             sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE ip = '$ip' AND namespace = '$ns'");
    1125         }
    1126     }
    1127    
     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            }
     1876        }
     1877    }
     1878
    11281879    /**
    11291880     * Deletes old codes from sqlite database
     
    11311882    protected function purgeOldCodesFromDatabase()
    11321883    {
    1133         if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
     1884        if ($this->use_database && $this->pdo_conn) {
    11341885            $now   = time();
    11351886            $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
    1136            
    1137             sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE $now - created > $limit");
    1138         }
    1139     }
    1140    
     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);
     1894        }
     1895    }
     1896
    11411897    /**
    11421898     * Checks to see if the captcha code has expired and cannot be used
     
    11461902    {
    11471903        $expired = true;
    1148        
     1904
    11491905        if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
    11501906            $expired = false;
     
    11521908            $expired = false;
    11531909        }
    1154        
     1910
    11551911        return $expired;
    11561912    }
    1157    
    1158     /**
    1159      *
    1160      * Generate an MP3 audio file of the captcha image
    1161      *
    1162      * @deprecated 3.0
    1163      */
    1164     protected function generateMP3()
    1165     {
    1166         return false;
    1167     }
    1168    
     1913
    11691914    /**
    11701915     * Generate a wav file given the $letters in the code
     
    11751920    protected function generateWAV($letters)
    11761921    {
    1177         $data_len       = 0;
    1178         $files          = array();
    1179         $out_data       = '';
    1180         $out_channels   = 0;
    1181         $out_samplert   = 0;
    1182         $out_bpersample = 0;
    1183         $numSamples     = 0;
    1184         $removeChunks   = array('LIST', 'DISP', 'NOTE');
    1185 
    1186         for ($i = 0; $i < sizeof($letters); ++$i) {
    1187             $letter   = $letters[$i];
    1188             $filename = $this->audio_path . strtoupper($letter) . '.wav';
    1189             $file     = array();
    1190             $data     = @file_get_contents($filename);
    1191            
    1192             if ($data === false) {
    1193                 // echo "Failed to read $filename";
    1194                 return $this->audioError();
    1195             }
    1196 
    1197             $header = substr($data, 0, 36);
    1198             $info   = unpack('NChunkID/VChunkSize/NFormat/NSubChunk1ID/'
    1199                             .'VSubChunk1Size/vAudioFormat/vNumChannels/'
    1200                             .'VSampleRate/VByteRate/vBlockAlign/vBitsPerSample',
    1201                              $header);
    1202            
    1203             $dataPos        = strpos($data, 'data');
    1204             $out_channels   = $info['NumChannels'];
    1205             $out_samplert   = $info['SampleRate'];
    1206             $out_bpersample = $info['BitsPerSample'];
    1207            
    1208             if ($dataPos === false) {
    1209                 // wav file with no data?
    1210                 // echo "Failed to find DATA segment in $filename";
    1211                 return $this->audioError();
    1212             }
    1213            
    1214             if ($info['AudioFormat'] != 1) {
    1215                 // only work with PCM audio
    1216                 // echo "$filename was not PCM audio, only PCM is supported";
    1217                 return $this->audioError();
    1218             }
    1219            
    1220             if ($info['SubChunk1Size'] != 16 && $info['SubChunk1Size'] != 18) {
    1221                 // probably unsupported extension
    1222                 // echo "Bad SubChunk1Size in $filename - Size was {$info['SubChunk1Size']}";
    1223                 return $this->audioError();
    1224             }
    1225            
    1226             if ($info['SubChunk1Size'] > 16) {
    1227                 $header .= substr($data, 36, $info['SubChunk1Size'] - 16);
    1228             }
    1229            
    1230             if ($i == 0) {
    1231                 // create the final file's header, size will be adjusted later
    1232                 $out_data = $header . 'data';
    1233             }
    1234            
    1235             $removed = 0;
    1236            
    1237             foreach($removeChunks as $chunk) {
    1238                 $chunkPos = strpos($data, $chunk);
    1239                 if ($chunkPos !== false) {
    1240                     $listSize = unpack('VSize', substr($data, $chunkPos + 4, 4));
    1241                    
    1242                     $data = substr($data, 0, $chunkPos) .
    1243                             substr($data, $chunkPos + 8 + $listSize['Size']);
    1244                            
    1245                     $removed += $listSize['Size'] + 8;
     1922        $wavCaptcha = new WavFile();
     1923        $first      = true;     // reading first wav file
     1924
     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;
    12461937                }
    1247             }
    1248            
    1249             $dataSize    = unpack('VSubchunk2Size', substr($data, $dataPos + 4, 4));
    1250             $dataSize['Subchunk2Size'] -= $removed;
    1251             $out_data   .= substr($data, $dataPos + 8, $dataSize['Subchunk2Size'] * ($out_bpersample / 8));
    1252             $numSamples += $dataSize['Subchunk2Size'];
    1253         }
    1254 
    1255         $filesize  = strlen($out_data);
    1256         $chunkSize = $filesize - 8;
    1257         $dataCSize = $numSamples;
    1258        
    1259         $out_data = substr_replace($out_data, pack('V', $chunkSize), 4, 4);
    1260         $out_data = substr_replace($out_data, pack('V', $numSamples), 40 + ($info['SubChunk1Size'] - 16), 4);
    1261 
    1262         $this->scrambleAudioData($out_data, 'wav');
    1263        
    1264         return $out_data;
    1265     }
    1266    
    1267     /**
    1268      * Randomizes the audio data to add noise and prevent binary recognition
    1269      * @param string $data  The binary audio file data
    1270      * @param string $format The format of the sound file (wav only)
    1271      */
    1272     protected function scrambleAudioData(&$data, $format)
    1273     {
    1274         $start = strpos($data, 'data') + 4; // look for "data" indicator
    1275         if ($start === false) $start = 44;  // if not found assume 44 byte header
    1276          
    1277         $start  += rand(1, 4); // randomize starting offset
    1278         $datalen = strlen($data) - $start;
    1279         $step    = 1;
    1280        
    1281         for ($i = $start; $i < $datalen; $i += $step) {
    1282             $ch = ord($data{$i});
    1283             if ($ch == 0 || $ch == 255) continue;
    1284            
    1285             if ($ch < 16 || $ch > 239) {
    1286                 $ch += rand(-6, 6);
    1287             } else {
    1288                 $ch += rand(-12, 12);
    1289             }
    1290            
    1291             if ($ch < 0) $ch = 0; else if ($ch > 255) $ch = 255;
    1292 
    1293             $data{$i} = chr($ch);
    1294            
    1295             $step = rand(1,4);
    1296         }
    1297 
    1298         return $data;
    1299     }
    1300    
     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;
     1950            }
     1951        }
     1952
     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;
     1965                }
     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;
     1985            }
     1986        }
     1987
     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        }
     1993
     1994        if (!empty($filters)) {
     1995            $wavCaptcha->filter($filters);  // apply filters to captcha audio
     1996        }
     1997
     1998        return $wavCaptcha->__toString();
     1999    }
     2000
     2001    public function getRandomNoiseFile()
     2002    {
     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;
     2013            }
     2014
     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            }
     2021        }
     2022
     2023        return $return;
     2024    }
     2025
    13012026    /**
    13022027     * Return a wav file saying there was an error generating file
    1303      * 
     2028     *
    13042029     * @return string The binary audio contents
    13052030     */
    13062031    protected function audioError()
    13072032    {
    1308         return @file_get_contents(dirname(__FILE__) . '/audio/error.wav');
    1309     }
    1310    
     2033        return @file_get_contents(dirname(__FILE__) . '/audio/en/error.wav');
     2034    }
     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     */
    13112059    function frand()
    13122060    {
    1313         return 0.0001 * rand(0,9999);
    1314     }
    1315    
     2061        return 0.0001 * mt_rand(0,9999);
     2062    }
     2063
    13162064    /**
    13172065     * Convert an html color code to a Securimage_Color
     
    13342082            return new Securimage_Color($default);
    13352083        }
     2084    }
     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;
    13362112    }
    13372113}
     
    13602136     * $color = new Securimage_Color('#0080FF') or <br />
    13612137     * $color = new Securimage_Color(0, 128, 255)
    1362      * 
     2138     *
    13632139     * @param string $color
    13642140     * @throws Exception
     
    13672143    {
    13682144        $args = func_get_args();
    1369        
     2145
    13702146        if (sizeof($args) == 0) {
    13712147            $this->r = 255;
     
    13772153                $color = substr($color, 1);
    13782154            }
    1379            
     2155
    13802156            if (strlen($color) != 3 && strlen($color) != 6) {
    13812157                throw new InvalidArgumentException(
     
    13832159                );
    13842160            }
    1385            
     2161
    13862162            $this->constructHTML($color);
    13872163        } else if (sizeof($args) == 3) {
     
    13932169        }
    13942170    }
    1395    
     2171
    13962172    /**
    13972173     * Construct from an rgb triplet
     
    14082184        if ($blue < 0)    $blue  = 0;
    14092185        if ($blue > 255)  $blue  = 255;
    1410        
     2186
    14112187        $this->r = $red;
    14122188        $this->g = $green;
    14132189        $this->b = $blue;
    14142190    }
    1415    
     2191
    14162192    /**
    14172193     * Construct from an html hex color code
     
    14272203            $red   = substr($color, 0, 2);
    14282204            $green = substr($color, 2, 2);
    1429             $blue  = substr($color, 4, 2); 
    1430         }
    1431        
     2205            $blue  = substr($color, 4, 2);
     2206        }
     2207
    14322208        $this->r = hexdec($red);
    14332209        $this->g = hexdec($green);
     
    14352211    }
    14362212}
    1437 ?>
  • extensions/CryptograPHP/securimage/securimage_preview.php

    r26041 r26554  
    1515  'height'          => (int)$_GET['height'],
    1616  'perturbation'    => (float)$_GET['perturbation'],
    17   'image_bg_color'  => $_GET['image_bg_color'],
     17  'background'      => $_GET['background'],
     18  'bg_color'        => $_GET['bg_color'],
     19  'bg_image'        => $_GET['bg_image'],
    1820  'code_length'     => (int)$_GET['code_length'],
    1921  'text_color'      => $_GET['text_color'],
     
    3840}
    3941
    40 foreach (array('image_bg_color','text_color','line_color','noise_color') as $color)
     42foreach (array('bg_color','text_color','line_color','noise_color') as $color)
    4143{
    4244  if ($temp_conf[$color] == 'random') $temp_conf[$color] = randomColor();
     
    5355$img->image_height    = $temp_conf['height'];
    5456$img->perturbation    = $temp_conf['perturbation'];
    55 $img->image_bg_color  = new Securimage_Color('#'.$temp_conf['image_bg_color']);
    5657$img->text_color      = new Securimage_Color('#'.$temp_conf['text_color']);
    5758$img->num_lines       = $temp_conf['num_lines'];
     
    6162$img->code_length     = $temp_conf['code_length'];
    6263
    63 $img->show();
    64 
    65 ?>
     64if ($temp_conf['background'] == 'image')
     65{
     66  if ($temp_conf['bg_image'] == 'random')
     67  {
     68    $img->background_directory = realpath(CRYPTO_PATH . 'securimage/backgrounds/');
     69    $img->show();
     70  }
     71  else
     72  {
     73    $img->show(realpath(CRYPTO_PATH . 'securimage/backgrounds/' . $temp_conf['bg_image']));
     74  }
     75}
     76else
     77{
     78  $img->image_bg_color  = new Securimage_Color('#'.$temp_conf['bg_color']);
     79  $img->show();
     80}
  • extensions/CryptograPHP/securimage/securimage_show.php

    r19428 r26554  
    6060}
    6161
    62 foreach (array('image_bg_color','text_color','line_color','noise_color') as $color)
     62foreach (array('bg_color','text_color','line_color','noise_color') as $color)
    6363{
    6464  if ($conf['cryptographp'][$color] == 'random') $conf['cryptographp'][$color] = randomColor();
     
    7575$img->image_height    = $conf['cryptographp']['height'];
    7676$img->perturbation    = $conf['cryptographp']['perturbation'];
    77 $img->image_bg_color  = new Securimage_Color('#'.$conf['cryptographp']['image_bg_color']);
    7877$img->text_color      = new Securimage_Color('#'.$conf['cryptographp']['text_color']);
    7978$img->num_lines       = $conf['cryptographp']['num_lines'];
     
    8382$img->code_length     = $conf['cryptographp']['code_length'];
    8483
    85 $img->show();
    86 
    87 ?>
     84if ($conf['cryptographp']['background'] == 'image')
     85{
     86  if ($conf['cryptographp']['bg_image'] == 'random')
     87  {
     88    $img->background_directory = realpath(CRYPTO_PATH . 'securimage/backgrounds/');
     89    $img->show();
     90  }
     91  else
     92  {
     93    $img->show(realpath(CRYPTO_PATH . 'securimage/backgrounds/' . $conf['cryptographp']['bg_image']));
     94  }
     95}
     96else
     97{
     98  $img->image_bg_color  = new Securimage_Color('#'.$conf['cryptographp']['bg_color']);
     99  $img->show();
     100}
  • extensions/CryptograPHP/template/admin.tpl

    r26041 r26554  
    99
    1010{footer_script}
     11var time = 0;
     12
    1113// colorpicker
    1214$('.colorpicker-input')
     
    1719    },
    1820    onChange: function(hsb, hex, rgb, el) {
    19       $(el).val(hex);
     21      $(el).val(hex).trigger('change');
    2022      changeColor(el, hex);
    21       changePreview();
    22       setThemeCutom();
    2323    },
    2424    onBeforeShow: function () {
     
    3333    changeColor(this, $(this).val());
    3434  });
     35 
     36 
     37$('.button').click(function() {
     38  $(this).siblings('.button').removeClass('selected');
     39  $(this).addClass('selected');
     40  $('input[name='+ $(this).data('input') +']').val($(this).data('val')).trigger('change');
     41});
    3542
    3643// change button
    37 $('.button').click(function() {
    38   $('.button').removeClass('selected');
    39   $(this).addClass('selected');
    40   $('input[name=button_color]').val($(this).attr('title'));
    41   $('#reload').attr('src', '{$CRYPTO_PATH}template/refresh_'+ $(this).attr('title') +'.png');
     44$('input[name=button_color]').change(function() {
     45  $('#reload').attr('src', '{$CRYPTO_PATH}template/refresh_'+ $(this).val() +'.png');
    4246});
    4347
    4448// apply a preset
    45 $('.preset').click(function() {
    46   $('.preset').removeClass('selected');
    47   $(this).addClass('selected');
    48  
    49   var id = $(this).attr("title");
     49$('input[name=theme]').change(function() {
     50  var id = $(this).val();
    5051 
    5152  for (key in presets[id]) {
    52     $('input[name="'+ key +'"]').val([presets[id][key]]);
     53    if ($('input[name="'+ key +'"]').attr('type') == 'radio') {
     54      $('input[name="'+ key +'"][value="'+ presets[id][key] +'"]').prop('checked', true).trigger('change', false);
     55    }
     56    else {
     57      $('input[name="'+ key +'"]').val(presets[id][key]).trigger('change', false);
     58    }
    5359  }
    5460 
     
    5662    changeColor(this, $(this).val());
    5763  });
    58   $('input[name="theme"]').val($(this).attr('title'));
     64
    5965  changePreview();
     66});
     67
     68// toggle background type
     69$('input[name=background]').change(function() {
     70  $('li[id^=background]').hide().filter('#background-'+$(this).val()).show();
    6071});
    6172
     
    6677
    6778// change theme to 'custom' if a parameter is changed
    68 $('input.istheme').change(function() {
    69   setThemeCutom();
     79$('input.istheme').change(function(e, p) {
     80  if (p!==false) setThemeCustom();
    7081});
    7182
    7283// update the preview
    73 $('input.istheme, input.preview').change(function() {
    74   changePreview();
     84$('input.preview').change(function(e, p) {
     85  if (p!==false) changePreview();
    7586});
    7687$('#reload').click(function() {
     
    8091// links for random color
    8192$('a.random').click(function() {
    82   $(this).prev('label').children('input').val('random');
     93  $(this).prev('label').children('input').val('random').trigger('change');
    8394  changeColor($(this).prev('label').children('input'), 'random');
    84   changePreview();
    85   setThemeCutom();
    8695});
    8796
     
    93102});
    94103
    95 function setThemeCutom() {
    96   $('.preset').removeClass('selected');
     104function setThemeCustom() {
     105  $('.button[data-input=theme]').removeClass('selected');
    97106  $('input[name=theme]').val('custom');
    98107}
    99108
    100109function changePreview() {
     110  var now = (new Date()).getTime();
     111
     112  if (now-time < 1000) {
     113    return;
     114  }
     115  time = now;
     116 
    101117  options = new Array();
    102118  str = '';
    103119 
    104   $('input[type="text"], input[type="radio"]:checked').each(function() {
     120  $('input.preview:not([type=radio]), input[type=radio].preview:checked').each(function() {
    105121    options[$(this).attr('name')] = $(this).val();
    106122  });
     
    113129
    114130function changeColor(target, color) {
    115   if (color == 'random') color = '808080';
     131  if (color == 'random') {
     132    color = '808080';
     133  }
    116134  if (parseInt(color, 16) > 16777215/2) {
    117135    $(target).css('color', '#222');
     
    136154
    137155{html_style}
    138 {foreach from=$fonts item=font}
     156{foreach from=$fonts item=path key=font}
    139157@font-face { 
    140158  font-family: '{$font}'; 
    141   src: url({$CRYPTO_PATH}securimage/fonts/{$font}.ttf) format("truetype"); 
     159  src: url({$path}) format("truetype"); 
    142160}
    143161{/foreach}
     
    193211    <li>
    194212      <b>{'Button color'|translate}</b>
    195       <a class="button {if $crypto.button_color == 'dark'}selected{/if}" title="dark"><img src="{$CRYPTO_PATH}template/refresh_dark.png" alt="dark"></a>
    196       <a class="button {if $crypto.button_color == 'light'}selected{/if}" title="light"><img src="{$CRYPTO_PATH}template/refresh_light.png" alt="light"></a>
     213      <a class="button {if $crypto.button_color == 'dark'}selected{/if}" data-val="dark" data-input="button_color"><img src="{$CRYPTO_PATH}template/refresh_dark.png" alt="dark"></a>
     214      <a class="button {if $crypto.button_color == 'light'}selected{/if}" data-val="light" data-input="button_color"><img src="{$CRYPTO_PATH}template/refresh_light.png" alt="light"></a>
    197215      <input type="hidden" name="button_color" value="{$crypto.button_color}">
    198216    </li>
    199217    <li>
    200218      <b>{'Captcha theme'|translate}</b>
    201       {foreach from=$PRESETS key=preset item=params}
    202       <a class="preset {if $crypto.theme == $preset}selected{/if}" title="{$preset}"><img src="{$CRYPTO_PATH}template/presets/{$preset}.png" alt="{$preset}"></a>
    203       {/foreach}
     219    {foreach from=$PRESETS key=preset item=params}
     220      <a class="button {if $crypto.theme == $preset}selected{/if}" data-val="{$preset}" data-input="theme"><img src="{$CRYPTO_PATH}template/presets/{$preset}.png" alt="{$preset}"></a>
     221    {/foreach}
    204222      <input type="hidden" name="theme" value="{$crypto.theme}">
    205223      <a class="customize">{'Customize'|translate}</a>
     
    213231      <li>
    214232        <b>{'Perturbation'|translate}</b>
    215         <label><input type="text" name="perturbation" value="{$crypto.perturbation}" class="istheme" size="6" maxlength="4"> {'range:'|translate} 0 - 1</label>
    216       </li>
    217       <li>
     233        <label><input type="text" name="perturbation" value="{$crypto.perturbation}" class="istheme preview" size="6" maxlength="4"> {'range:'|translate} 0 - 1</label>
     234      </li>
     235      <li>
     236        <b>{'Background'|translate}</b>
     237        <label><input type="radio" name="background" class="istheme preview" value="color" {if $crypto.background == 'color'}checked="checked"{/if}> {'Color'|translate}</label>
     238        <label><input type="radio" name="background" class="istheme preview" value="image" {if $crypto.background == 'image'}checked="checked"{/if}> {'Image'|translate}</label>
     239      </li>
     240      <li id="background-color" {if $crypto.background != 'color'}style="display:none;"{/if}>
    218241        <b>{'Background color'|translate}</b>
    219         <label><input type="text" name="image_bg_color" value="{$crypto.image_bg_color}" class="colorpicker-input istheme" size="6" maxlength="6"></label>
     242        <label><input type="text" name="bg_color" value="{$crypto.bg_color}" class="colorpicker-input istheme preview" size="6" maxlength="6"></label>
    220243        <a class="random" title="{'random'|translate}"><img src="{$CRYPTO_PATH}/template/arrow_switch.png"></a>
    221244      </li>
     245      <li id="background-image" {if $crypto.background != 'image'}style="display:none;"{/if}>
     246        <b>{'Background image'|translate}</b>
     247      {foreach from=$backgrounds item=path key=background}
     248        <a class="button {if $crypto.bg_image == $background}selected{/if}" data-val="{$background}" data-input="bg_image"><img src="{$path}" alt="{$background}" style="width:120px;height:40px;"></a>
     249      {/foreach}
     250        <!-- <a class="button {if $crypto.bg_image == 'random'}selected{/if}" title="{'random'|translate}" data-val="random" data-input="bg_image"><img src="{$CRYPTO_PATH}/template/arrow_switch.png"></a> -->
     251        <input type="hidden" name="bg_image" value="{$crypto.bg_image}" class="istheme preview">
     252      </li>
    222253      <li>
    223254        <b>{'Text color'|translate}</b>
    224         <label><input type="text" name="text_color" value="{$crypto.text_color}" class="colorpicker-input istheme" size="6" maxlength="6"></label>
     255        <label><input type="text" name="text_color" value="{$crypto.text_color}" class="colorpicker-input istheme preview" size="6" maxlength="6"></label>
    225256        <a class="random" title="{'random'|translate}"><img src="{$CRYPTO_PATH}/template/arrow_switch.png"></a>
    226257      </li>
    227258      <li>
    228259        <b>{'Lines density'|translate}</b>
    229         <label><input type="text" name="num_lines" value="{$crypto.num_lines}" class="istheme" size="6" maxlength="4"> {'range:'|translate} 0 - 10</label>
     260        <label><input type="text" name="num_lines" value="{$crypto.num_lines}" class="istheme preview" size="6" maxlength="4"> {'range:'|translate} 0 - 10</label>
    230261      </li>
    231262      <li>
    232263        <b>{'Lines color'|translate}</b>
    233         <label><input type="text" name="line_color" value="{$crypto.line_color}" class="colorpicker-input istheme" size="6" maxlength="6"></label>
     264        <label><input type="text" name="line_color" value="{$crypto.line_color}" class="colorpicker-input istheme preview" size="6" maxlength="6"></label>
    234265        <a class="random" title="{'random'|translate}"><img src="{$CRYPTO_PATH}/template/arrow_switch.png"></a>
    235266      </li>
    236267      <li>
    237268        <b>{'Noise level'|translate}</b>
    238         <label><input type="text" name="noise_level" value="{$crypto.noise_level}" class="istheme" size="6" maxlength="4"> {'range:'|translate} 0 - 10</label>
     269        <label><input type="text" name="noise_level" value="{$crypto.noise_level}" class="istheme preview" size="6" maxlength="4"> {'range:'|translate} 0 - 10</label>
    239270      </li>
    240271      <li>
    241272        <b>{'Noise color'|translate}</b>
    242         <label><input type="text" name="noise_color" value="{$crypto.noise_color}" class="colorpicker-input istheme" size="6" maxlength="6"></label>
     273        <label><input type="text" name="noise_color" value="{$crypto.noise_color}" class="colorpicker-input istheme preview" size="6" maxlength="6"></label>
    243274        <a class="random" title="{'random'|translate}"><img src="{$CRYPTO_PATH}/template/arrow_switch.png"></a>
    244275      </li>
    245276      <li>
    246277        <b>{'Font'|translate}</b>
    247         {foreach from=$fonts item=font}
    248         <label style="font-family:{$font};" title="{$font}"><input type="radio" name="ttf_file" value="{$font}" {if $crypto.ttf_file == $font}checked="checked"{/if} class="istheme"> {$font}</label>
    249         {/foreach}
     278      {foreach from=$fonts item=path key=font}
     279        <label style="font-family:{$font};" title="{$font}"><input type="radio" name="ttf_file" value="{$font}" {if $crypto.ttf_file == $font}checked="checked"{/if} class="istheme preview"> {$font}</label>
     280      {/foreach}
    250281      </li>
    251282    </ul>
  • extensions/CryptograPHP/template/style.css

    r23209 r26554  
    1 .preset img, .button img {
     1.button img {
    22  margin:1px;
    33  padding:3px;
    44  border:1px solid #999;
    55}
    6 .preset.selected img, .button.selected img {
     6.button.selected img {
    77  border-color:#f70;
    88}
Note: See TracChangeset for help on using the changeset viewer.