<?php  // ۞//  text { encoding:utf-8  ;  bom:no  ;  linebreaks:unix  ; tabs:4sp ; }

/*
    corz.org gd verify    v0.7

    This script handles your entire human-verification process.

    It uses php+gd to generate a CAPTHCA-type image that users need to submit to
    verify their human-ness. In the event it detects non-image capable clients,
    or if the user just prefers it that way, a unique text-based "sounds-like"
    verification system is also provided.

    Examples are available, here:

        http://corz.org/source/php/imaging/

    ;o) Cor

    (c) 2004->tomorrow! ~ cor + corz.org ;o)

    Please view the license for this free software, here:

        http://corz.org/free-scripts-licence.nfo


    Note:

    If you use this script to protect, say, a comment facility, and a user
    successfully passes the verfication process, they would then be allowed
    direct access to any other resources you are similarly protecting, for
    example an email form. In other words, they only have to prove they are
    human ONE TIME for your entire site.

*/


// a two-in-one page..
if (!empty($_GET['do_img'])) {
    
$verify = new verificationImage();
    
$verify->outputImage();
    die;
}



class 
verificationImage {


     
// / / /  /   /    /     /      /         /
    // public properties..

    // width of image..
    
var $_width 150;

    
// that other dimension.
    
var $_height 90;


    
/*
        Truecolor Image

        Probably requires GD2 to use truecolor, but looks better.
        having said that, the jaggedy lines of an 8-bit image may
        actually be preferable, here. Your call..
    */
    
var $_truecolor true;


    
/*
        Image Format

        Chose from 'png', 'jpeg', or 'gif'. clearly png is superior,
        though, as above, this may not be what you want.
    */
    
var $_img_format 'png';


    
/*
        colors for the semi-transparent foiling strings..
        use regular HTML hex values. the "#" is optional.
    */

    
var $_color_bg '#ffffff'// better to use use something that aids obfuscation

    
var $_front_col '#414141'// a dark dark grey
    
var $_color_1 '#d500d5'// magenta  '#FF80FF'
    
var $_color_2 '#f5a703'// yellow '#FFFF00'
    
var $_color_3 '#00cece'// cyan '#80FFFF'


    /*
        translucency of foiling strings..
        0-127 (127 is completely transparent)
        I like 42, for obvious reasons..
    */
    
var $_translucency 20;


    
/*
        truetype font

        this must live in the same folder as this script
        or else be in some way accessible to php.
        (see ampsig.com] default scheme for more details)

        default is 'profont.ttf', a neat, mono, free truetype font
        based on Monaco, designed especially for coding. I use it all over.
        A more fancy font may be preferable (harder to machine-read)..

    */
    //var $_font_face = 'profont.ttf'; // sometimes this fails :/
    
var $_font_face 'profont.ttf'// if so, do this. assuming profont.ttf is in the same directory!


    // point size of tt font. 20-30 is good.
    
var $_font_size 27;


    
// skew angle of text. 20 works well.
    
var $_angle 20;


    
// how long a string?
    // (default: 5)..
    
var $_text_length 5;



    
/*
        Flavour of string generation

        Choose from 'md5',  'masher', or 'mish-mash'..

        'masher' creates a random string of lower-case letters.

        'md5' plucks a short segment from a random MD5 hash, a mix of lower-case
        letters a-f, plus numbers, e.g. 'ac5e3b'.

        'mish-mash' is my favourite, creating a nice mix of alpha-numerics, e.g.
        'mG2tU'..
    */
    
var $_verify_flavour 'mish-mash';


    
/*
        Verification Method

        Choose from 'image', 'text', or 'auto'.

        Non image-capable browsers will certainly prefer text-based
        authentication, though you may wish to use that all the time.

        "auto" is best; gd-verify will query the browser capabilities and decide
        for you.

        Remember, in your calling script, you will need to either have an <img>
        or <div> (or whatever text block) tag. Of course, in auto mode, you'll
        need to know in advance which one to use..

        Do $no_images = $verify->imageCapable(); $no_images will be true if they can
        handle images, or false, if not. Now you know which kind of tag to use; <img>,
        or <div>.

        Note: you can also override this default by setting a $_GET or $_POST
        variable, "text-verify" to true.

    */
    
var $_verify_method 'auto';




     
// / /  /   /    /     /      /       /
    // private..

    
var $_version '0.3.2';
    var 
$_text '';
    var 
$_img null;
    var 
$_is_verified null;
    var 
$_imageCapable true;


    
// da constructor..
    
function verificationImage() {

        
// just in case..
        
@session_start();

        if (isset(
$_SESSION['gdv']['gd_verified'])) {
            
$this->_is_verified $_SESSION['gdv']['gd_verified'];
        }
    }


    function 
getVerification() {

        
// already verified?
        
if ($this->_is_verified == true) {
            return 
true;
        }

        if (isset(
$_POST['gd_string'])) {
            if (
$this->verify_string($_POST['gd_string']) == true) {
                
$_SESSION['gdv']['gd_verified'] = $this->_is_verified true;
                return 
true;
            }
        }

        
$_SESSION['gdv']['gd_verified'] = null;
        return 
false;
    }


    
// do they match?..
    
function verify_string($img_str) {
        if (isset(
$_SESSION['gdv']['img_str']) and ($_SESSION['gdv']['img_str'] === $img_str)) {
            return 
true;
        } else {
            return 
false;
        }
    }

    function 
make_string() {

        
$string '';

        switch(
$this->_verify_flavour) {

            case 
'md5':
                
$string substr(md5(mt_rand()), 12$this->_text_length);
                break;

            case 
'masher':
                for(
$i 0$i $this->_text_length $i++) {
                    
$string .= chr(mt_rand(97,122)); // lower-case
                
}
                break;

            default: 
// 'mish-mash':
                
$strings = array();
                for(
$i 0$i $this->_text_length $i++) {
                    
$strings[] = chr(mt_rand(97,122));    // lower case
                    
$strings[] = chr(mt_rand(65,90));    // UPPER CASE
                    
$strings[] = chr(mt_rand(48,57));    // 0123456789
                
}
                
$pluck array_rand($strings$this->_text_length);
                foreach (
$pluck as $key => $value) {
                    
$string .= $strings[$key];
                }
        }

        
$_SESSION['gdv']['img_str'] = $string;
        return 
$string;
    }


    function 
doInput() {
        return 
'<input type="text" id="gd_string" name="gd_string" title="Enter Code Here" autofocus="autofocus" />';
    }

    function 
outputImage() {

        
// some text..
        
$this->_text $this->make_string();
        
$do_text_auth false;

        switch (
$this->_verify_method) {

            case 
'image':
                break;

            case 
'text':
                
$do_text_auth true;
                break;

            case 
'auto':
                if (!
$this->imageCapable()) { $do_text_auth true; }
        }

        
// $_GET override..
        //
        
if (isset($_REQUEST['text-verify']) and $_REQUEST['text-verify'] == 1) { $do_text_auth true; }

        
// assign colors from _GET variables..
        
if (isset($_GET['bg']) and $_GET['bg'] != '')        { $this->_color_bg $_GET['bg']; }
        if (isset(
$_GET['txtcol']) and $_GET['txtcol'] != '')    { $this->_front_col $_GET['txtcol']; }
        if (isset(
$_GET['col1']) and $_GET['col1'] != '')    { $this->_color_1 $_GET['col1']; }
        if (isset(
$_GET['col2']) and $_GET['col2'] != '')    { $this->_color_2 $_GET['col2']; }
        if (isset(
$_GET['col3']) and $_GET['col3'] != '')    { $this->_color_3 $_GET['col3']; }

        if (isset(
$_GET['width']) and $_GET['width'] != '')    { $this->_width $_GET['width']; }
        if (isset(
$_GET['height']) and $_GET['height'] != '')    { $this->_height $_GET['height']; }
        if (isset(
$_GET['font_size']) and $_GET['font_size'] != '')    { $this->_font_size $_GET['font_size']; }

        if (
$this->anywhere_in_array('jpg'$_GET)) { $this->_img_format 'jpeg'; }
        if (
$this->anywhere_in_array('png'$_GET)) { $this->_img_format 'png'; } // could roll into one efficient format override function
        // spit out the image and die..



        // we'll send them a text-based verification..
        //
        
if ($do_text_auth) {

            
$translate_array = array(

                
// we could also make this a multi-dimensional array, and use one of a
                // *selection* of, say, "won, one, w0n, number one, wun, numero uno, etc."
                // custom captchas are the way to go.

                
"0" => "zeehrow",
                
"1" => "wun",
                
"2" => "too!",
                
"3" => "thuree",
                
"4" => "fore",
                
"5" => "f-hive",
                
"6" => "sicks",
                
"7" => "s-heaven",
                
"8" => "ate",
                
"9" => "nein",

                
"a" => "lower-case ay",
                
"b" => "lower-case bee",
                
"c" => "lower-case see",
                
"d" => "lower-case dee",
                
"e" => "lower-case ee",
                
"f" => "lower-case eff",
                
"g" => "lower-case gee",
                
"h" => "lower-case aitch",
                
"i" => "lower-case aye",
                
"j" => "lower-case jay",
                
"k" => "lower-case kay",
                
"l" => "lower-case elle",
                
"m" => "lower-case em",
                
"n" => "lower-case en",
                
"o" => "lower-case oh",
                
"p" => "lower-case pee",
                
"q" => "lower-case queue",
                
"r" => "lower-case arrgh",
                
"s" => "lower-case ess",
                
"t" => "lower-case tee",
                
"u" => "lower-case you",
                
"v" => "lower-case vee",
                
"w" => "lower-case dbl-u",
                
"x" => "lower-case ex",
                
"y" => "lower-case why",
                
"z" => "lower-case zed",

                
"A" => "Upper-Case Ay",
                
"B" => "Upper-Case Bee",
                
"C" => "Upper-Case See",
                
"D" => "Upper-Case Dee",
                
"E" => "Upper-Case Ee",
                
"F" => "Upper-Case Eff",
                
"G" => "Upper-Case Gee",
                
"H" => "Upper-Case Aitch",
                
"I" => "Upper-Case Aye",
                
"J" => "Upper-Case Jay",
                
"K" => "Upper-Case Kay",
                
"L" => "Upper-Case Elle",
                
"M" => "Upper-Case Em",
                
"N" => "Upper-Case En",
                
"O" => "Upper-Case Oh",
                
"P" => "Upper-Case Pee",
                
"Q" => "Upper-Case Queue",
                
"R" => "Upper-Case Arrgh",
                
"S" => "Upper-Case Ess",
                
"T" => "Upper-Case Tee",
                
"U" => "Upper-Case You",
                
"V" => "Upper-Case Vee",
                
"W" => "Upper-Case Dbl-U",
                
"X" => "Upper-Case Ex",
                
"Y" => "Upper-Case Why",
                
"Z" => "Upper-Case Zed"
            
);

            
$newtext_array str_split($this->_text);
            
$tmp_str '';
            
$ret_string '<div class="verify-title">Enter the '.strlen($_SESSION['gdv']['img_str']).'-digit code this text <em>sounds like</em> :</div><br />
            <div class="verify-text">'
;
            foreach (
$newtext_array as $convert) {
                
$tmp_str .= $translate_array[$convert].", ";
            }
            return 
$ret_string.substr($tmp_str0strlen($tmp_str)-2).'</div>';

        } else {

            
// image-based verification..
            //

            // start with a blank image..
            
if ($this->_truecolor) {
                
$this->_img imagecreatetruecolor($this->_width$this->_height);
            } else {
                
$this->_img imagecreate($this->_width$this->_height);
            }

            
// colours we shall use..
            
$white imagecolorallocate($this->_img255255255);


            
$get_col $this->hex2dec($this->_color_bg);
            
$this->_background_color imagecolorallocate($this->_img$get_col[0], $get_col[1], $get_col[2]);

            
$get_col $this->hex2dec($this->_front_col);
            
$front_col imagecolorallocatealpha($this->_img$get_col[0], $get_col[1], $get_col[2], $this->_translucency);

            
$get_col $this->hex2dec($this->_color_1);
            
$color_1 imagecolorallocatealpha($this->_img$get_col[0], $get_col[1], $get_col[2], $this->_translucency);

            
$get_col $this->hex2dec($this->_color_2);
            
$color_2 imagecolorallocatealpha($this->_img$get_col[0], $get_col[1], $get_col[2], $this->_translucency);

            
$get_col $this->hex2dec($this->_color_3);
            
$color_3 imagecolorallocatealpha($this->_img$get_col[0], $get_col[1], $get_col[2], $this->_translucency);

            
// background..
            // probably we want to put shapes and shit in here..
            
imagefilledrectangle($this->_img00$this->_width$this->_height$this->_background_color);

            
// rotate the text around its centre..
            // yes, we could simply calculate the centre point and lay it there, but this is not only a
            // wee bit more clever, but introduces nice unpredictable displacement based on letter size.

            
$x $this->_width 2;
            
$y $this->_height 2// ttf's calculate from baseline, this sorta compensates. a tweaker!

            // get the boundingbox..
            
$bbox imagettfbbox($this->_font_size$this->_angle$this->_font_face$this->_text);

            
// calculate deviation..
            
$dx = (($bbox[2]-$bbox[0]) / 2) - (($bbox[2]-$bbox[4]) / 2);
            
$dy = (($bbox[3]-$bbox[1]) / 2) + (($bbox[7]-$bbox[1]) / 2);

            
// our new pivot points..
            
$px $x $dx;
            
$py $y $dy;

            
// we only need a small variation here
            
$var_x rand(47);
            
$var_y rand(47);

            
// put the text string onto the image in a few different colours..
            
imagettftext($this->_img$this->_font_size 2$this->_angle$px-$var_x$py - ($this->_height 18) - $var_y$color_2$this->_font_face$this->_text);
            
imagettftext($this->_img$this->_font_size 3$this->_angle$px - ($this->_width 12.5) - $var_x$py$color_1$this->_font_face$this->_text);
            
imagettftext($this->_img$this->_font_size 4$this->_angle$px + ($this->_width 50) + $var_x$py $var_y$color_3$this->_font_face$this->_text);

            
// now the *real* text..
            
imagettftext($this->_img$this->_font_size$this->_angle$px$py$front_col$this->_font_face$this->_text);

            
// border..
            
imagefilledrectangle($this->_img00$this->_width0$front_col);
            
imagefilledrectangle($this->_img$this->_width 10$this->_width 1$this->_height 1$front_col);
            
imagefilledrectangle($this->_img000$this->_height 1$front_col);
            
imagefilledrectangle($this->_img0$this->_height 1$this->_width$this->_height 1$front_col);

            
// finally, spit it out..
            
header('Content-type: image/'.$this->_img_format);

            switch (
$this->_img_format) {
                case 
'png':
                    
imagepng($this->_img);
                    break;
                case 
'jpeg':
                    
imagejpeg($this->_img);
                    break;
                case 
'jpg':
                    
imagejpeg($this->_img);
                    break;
                case 
'gif':
                    
imagegif ($this->_img);
            }
            
imagedestroy($this->_img);
        }
    }


    
// determine whether the browser can handle images, or not..
    //
    
function imageCapable() {

        
// regardless of capabilities, we're sending text..
        
if ($this->_verify_method == 'text') { return false; }

        
// okay, let's check what browser we're dealing with..
        
$user_agent strtolower(@$_SERVER['HTTP_USER_AGENT']);

        switch (
true) {
            case (
stristr($user_agent'gecko')):
                break;
            case (
stristr($user_agent'opera')):
                break;
            case (
stristr($user_agent'MSIE')):
                break;
            default:
                
// Last Chance!
                
if (!stristr(@$_SERVER['HTTP_ACCEPT'], 'image/')) {
                    
$this->_imageCapable false;
                }
        }
        return 
$this->_imageCapable;
    }


    
/*
    convert an HTML #hex colour to decimal colour levels..    */
    
function hex2dec($rgb) {
        if (
substr($rgb01) == '#') {
            
$rgb substr($rgb1);
        }
        if (
strlen($rgb) == 6) {
            
$r hexdec(substr($rgb02));
            
$g hexdec(substr($rgb22));
            
$b hexdec(substr($rgb42));
        } elseif (
strlen($rgb) == 3) {
            
$r hexdec(str_repeat(substr($rgb01), 2));
            
$g hexdec(str_repeat(substr($rgb11), 2));
            
$b hexdec(str_repeat(substr($rgb21), 2));
        } else { return; }
        return array(
$r$g$b);
    }


    
// dammit, php needs this function!
    
function anywhere_in_array($my_string$array) {
        foreach (
$array as $k=>$v) {
            if (
stristr($k$my_string)) { return true; }
            if (
stristr($v$my_string)) { return true; }
        }
    }

}

?>
back to the source menu
test

Welcome to corz.org!

If something isn't working, I'm probably improving it, try again in a minute. If it's still not working, please mail me!