Basic PHP Captcha (Image Verification) with Refresh Feature

This a simple PHP Captcha Class useful to prevent automatic submissions of your forms. It’s very simple to implement and it uses the GD library to generate the image with the security code. Here’s the full class code (I will explain you how it works below):

<?php
class Captcha {

// Number of characters
public $chars_number = 4;

// Numbers (1), Letters (2), Letters & Numbers (3)
public $string_type = 3;

// Font Size
public $font_size = 5;

// Border Color (optional)
public $border_color = '239, 239, 239';

// Path to TrueType Font
public $tt_font = 'arial.ttf';

/* Show Captcha Image */
public function show_image($width = 88, $height = 31)
{
if(isSet($this->tt_font))
{
if(!file_exists($this->tt_font)) exit('The path to the true type font is incorrect.');
}

if($this->chars_number < 3) exit('The captcha code must have at least 3 characters.');
 
 $string = $this->generate_string();

$im = ImageCreate($width, $height);

/* Set a White & Transparent Background Color */
$bg = ImageColorAllocateAlpha($im, 255, 255, 255, 127); // (PHP 4 >= 4.3.2, PHP 5)
ImageFill($im, 0, 0, $bg);

/* Border Color */

if($this->border_color)
{
list($red, $green, $blue) = explode(',', $this->border_color);

$border = ImageColorAllocate($im, $red, $green, $blue);
ImageRectangle($im, 0, 0, $width - 1, $height - 1, $border);
}

$textcolor = ImageColorAllocate($im, 191, 120, 120);

$y = 24;

for($i = 0; $i < $this->chars_number; $i++)
{
$char = $string[$i];

$factor = 15;
$x = ($factor * ($i + 1)) - 6;
$angle = rand(1, 15);

imagettftext($im, $this->font_size, $angle, $x, $y, $textcolor, $this->tt_font, $char);
}

$_SESSION['security_code'] = md5($string);

/* Output the verification image */
header("Content-type: image/png");
ImagePNG($im);


exit;
}

private function generate_string()
{
if($this->string_type == 1) // letters
{
$array = range('A','Z');
}
else if($this->string_type == 2) // numbers
{
$array = range(1,9);
}
else // letters & numbers
{
$x = ceil($this->chars_number / 2);

$array_one = array_rand(array_flip(range('A','Z')), $x);

if($x <= 2) $x = $x - 1;

$array_two = array_rand(array_flip(range(1,9)), $this->chars_number - $x);

$array = array_merge($array_one, $array_two);
}

$rand_keys = array_rand($array, $this->chars_number);
	
$string = '';

foreach($rand_keys as $key)
{
$string .= $array[$key];
}

return $string;
}

}
?>
Variables Info

public $chars_number – The number of characters for the security code.
public $string_type What kind of string will be generated? 1 = numbers, 2 = letters, 3 = letters & numbers (default).
public $font_size – The size of the TrueType Font. It is recommended to set it depending on both the number of characters and the size (width & height) of the Captcha image.
public $border_color – Sets the color of the border using RGB values. (ex: 211, 211, 211 (white grey) )
public $tt_font – Path to the true type font file

How is this class working?

The function which generates the Captcha is show_image(string $width, string $height). First it checks if the path to the true type font is correct. If it isn’t the script will close after an error message will be shown:

if(isSet($this->tt_font))
{
if(!file_exists($this->tt_font)) exit('The path to the true type font is incorrect.');
}

The security code should have at least 3 characters:

if($this->chars_number < 3) exit('The captcha code must have at least 3 characters');

Further on, the security code will be generated by calling the generate_string() function:

$string = $this->generate_string();

How does this function work?

The value of $string_type is analyzed. Based on the chosen type (letters, numbers, letters & numbers) an array will be generated. Afterwards, we will use array_rand() to create a second array with random keys selected from the first array:

$rand_keys = array_rand($array, $this->chars_number);

What's next? Loop through the recently created array to generate the security code:

$string = '';

foreach($rand_keys as $key)
{
$string .= $array[$key];
}

After the string is generated we can move on with the creation of the image. The first function used is ImageCreate() to create a new palette based image with the specified size.

resource imagecreate ( int $width, int $height )

$im = ImageCreate($width, $height);

Set a white & transparent background for the recently created image:

/* Set a White & Transparent Background Color */
$bg = ImageColorAllocateAlpha($im, 255, 255, 255, 127); // (PHP 4 >= 4.3.2, PHP 5)
ImageFill($im, 0, 0, $bg);

Add the border with the specified color (default is grey):

/* Border Color */

if($this->border_color)
{
list($red, $green, $blue) = explode(',', $this->border_color);

$border = ImageColorAllocate($im, $red, $green, $blue);
ImageRectangle($im, 0, 0, $width - 1, $height - 1, $border);
}

Allocate a color for the text:

$textcolor = ImageColorAllocate($im, 191, 120, 120);

Write the text to the image using imagettftext() by calculating the right coordinates:

$y = 24;

for($i = 0; $i < $this->chars_number; $i++)
{
$char = $string[$i];

$factor = 15;
$x = ($factor * ($i + 1)) - 6;
$angle = rand(1, 15);

imagettftext($im, $this->font_size, $angle, $x, $y, $textcolor, $this->tt_font, $char);
}

Now, we need to register the session for the generated string. For security reasons we will use md5() to register a 32 characters hash of the code.

$_SESSION['security_code'] = md5($string);

Finally, output the image and exit:

/* Output the verification image */
header("Content-type: image/png");
ImagePNG($im);

exit;

How can I implement this Captcha with the refresh feature?

This class can be called from a HTML file. Here's the code:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
 <head>
  <title>Captcha Verification Image</title>

<script type="text/javascript">

<!--
function new_captcha()
{
var c_currentTime = new Date();
var c_miliseconds = c_currentTime.getTime();

document.getElementById('captcha').src = 'image.php?x='+ c_miliseconds;
}
-->
</script>

 </head>

 <body onload="new_captcha();">
 <form action="check.php" method="post">

 <table>
 <tr>
 <td>&nbsp;</td>
 <td><img id="captcha" src="image.php" alt="">

 &nbsp;<a href="JavaScript: new_captcha();">
 <img border="0" alt="" src="refresh.png" align="bottom"></a></td>
</tr>
 <tr>
 <td>Security Code:</td>
 <td><input type="text" name="security_code" value="">

 &nbsp;<input type="submit" name="submit" value="Check"></td>
</tr>
</table>
</form>
 </body>
</html>

How is the Captcha refreshed?

As you can notice the image has the id attribute equal with 'captcha' and the SRC equal with 'image.php'. Everytime the refresh icon is clicked the JavaScript new_captcha() function is called setting an unique SRC for the image. This way the user will not access a recently cached version of the captcha and the image.php will re-load. The variable 'c_milliseconds' is unique every time the user accesses the page that calls the captcha and contains the number of milliseconds since midnight of January 1, 1970.

function new_captcha()
{
var c_currentTime = new Date();
var c_milliseconds = c_currentTime.getTime();

document.getElementById('captcha').src = 'image.php?x='+ c_milliseconds;
}

The new_captcha() is triggered in every page re-load:

<body onload="new_captcha();">

This way, a new image is generated and the previously shown image is not cached even if the user uses the browser's BACK button to return to the page with the captcha.

In image.php we will call the class that will output the captcha:

include_once 'common.php';
include_once 'class.captcha.php';

$captcha = new Captcha();

$captcha->chars_number = 5;
$captcha->font_size = 14;
$captcha->tt_font = 'verdana.ttf';

$captcha->show_image();

In common.php, the session is started and the right headers are sent:

<?php
session_start();
header('Cache-control: private'); // IE 6 FIX
// always modified 
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); 
// HTTP/1.1 
header('Cache-Control: no-store, no-cache, must-revalidate'); 
header('Cache-Control: post-check=0, pre-check=0', false); 
// HTTP/1.0 
header('Pragma: no-cache');
?>

captcha

How will the submitted code be checked?

In check.php, we will verify if the security code was submitted. If so, we will remove any whitespace from the beginning and ending of the script using the trim() function. Then, we will encrypt it using md5(). The returned hash is compared to the value of the registered session 'security_code'. If it's a match then the correct code was submitted. If it isn't, an error message will be shown:

<?php
include_once 'common.php';

if(isSet($_POST['security_code']))
{
$security_code = trim($_POST['security_code']);

$to_check = md5($security_code);

if($to_check == $_SESSION['security_code'])
{
echo "The security code is <font color='green'>correct</font>.";
}
else
{
echo "The security code is <font color='red'>incorrect</font>.";
}
}
?>


Do you want to increase / decrease the number of characters and also the size of the Captcha Image? Here are some examples of how you can do that:

1) Use an image with 4 characters and a smaller size (70 x 30)

<?php
include_once 'common.php';
include_once 'class.captcha.php';

$captcha = new Captcha();

$captcha->chars_number = 4;
$captcha->font_size = 14;
$captcha->tt_font = 'verdana.ttf';

$captcha->show_image(70, 30);
?>

captcha-4-chars


2) Use an image with 8 characters and a bigger size (120 x 30)

<?php
include_once 'common.php';
include_once 'class.captcha.php';

$captcha = new Captcha();

$captcha->chars_number = 8;
$captcha->font_size = 14;
$captcha->tt_font = 'verdana.ttf';

$captcha->show_image(132, 30);
?>

captcha-8-chars

If you have any questions regarding this class, please ask! Happy coding!

Comment via Facebook

comments

26 Comments

  1. Mike Lagan says

    You might have to add
    // Set the enviroment variable for GD
    putenv(‘GDFONTPATH=’ . realpath(‘.’));”
    to class.captcha.php to alter the GD path

  2. says

    • Gabriel C.Gabriel C. says

      It looks like the CAPTCHA font can’t be read from its location. Make sure you put the right path to the TrueType Font.

  3. Seven says

    I have a little problem!
    I’ve tried this code and it works properly on my localhost, but if i upload to a server the security image is not shown at all! I tried it on another server and it was almost the same problem, the image was created there but the code was not shown! How could i solve this, and whats the problem?? I tried to change path as well, but didnt work! :(

    Thank you very much in advance!

    • Sondre says

      I found that PHP version 5.2.13 has two different FreeType versions installed. Running local (Apache) everything works fine with FreeType version 2.39, but on my server (CGI) I find I have FreeType version 2.37 and it will NOT load the image.

      Any tip what to do next ?

  4. rami says

    thank you verry much,good job!
    but when i change the dimension and the number of character the image will not appear..
    Why?

  5. bennett says

    Thank you very much for publishing this lesson. The code as well as the tutorial seem to be very thorough. The code works right out of the box so thanks for that. I’ll definitely will visit your site often.

  6. Rodrigo says

    Hi. First forgive me, my English is bad

    I have 2 problems.
    The refresh of CAPTCHA not working (“in the file form.html”). I don’t know where this error, because when you click “refresh link” it does not update the CAPTCHA.
    And finally when you click submit, validation does not occur and the script returns the mensage exist in the recomenda_ERRO.html file.

    I usualled the original file check and now I┬┤m testing the solution sended for ABUT.

    Please help me!!!

    This Code I adapted and I┬┤m testing. But don┬┤t work.
    <?
    include_once '../capcode/common.php';

    $hoje_tmp = getdate();
    $hoje = ($hoje_tmp[hours].":".$hoje_tmp[minutes].":".$hoje_tmp[seconds]."hs");

    $remetente_nome = $_POST["remetente_nome"]; //trata a vari├ível – Nome
    $remetente_mail = $_POST["remetente_mail"]; //trata a vari├ível – E-mail
    $destinatario_nome = $_POST["destinatario_nome"]; //trata a vari├ível – Destinat├írio
    $destinatario_mail = $_POST["destinatario_mail"]; //trata a vari├ível – E-mail do destinat├írio
    $mensagem = $_POST["mensagem"]; //trata a vari├ível – Mensagem

    global $email; //transforma em variavel global a variável e-mail

    if( isset($_POST['submit']))
    {
    if( $_SESSION['security_code'] == md5($_POST['security_code']) && !empty($_SESSION['security_code'] ) )
    {
    $enviou = mail("$destinatario_mail", "$remetente_nome esta recomendando nosso site",
    "Olá, $destinatario_nome.

    $remetente_nome, visitou o endere├žo http://www.site.com e achou que voc├¬ iria gostar de conhecer tamb├ęm.

    Mensagem escrita por $remetente_nome:

    $mensagem

    ______________________________________________________________________________
    Mensagem enviada via formulário on-line as $hoje
    "From: $remetente_nome “, “Bcc: [email protected]“);

    if ($enviou)
    {
    Header(“Location: recomenda_ok.html”);
    }
    else
    {
    Header(“Location: recomenda_erro.html”);
    }
    }
    else
    {
    Header(“Location: recomenda_erro.html”);
    }
    }
    ?>

    FORM.HTML

    Formulário de envio

    Seu nome:

    Seu e-mail:

    Destinatário(a):

    Destinatário(a) e-mail:

    Mensagem:

    Digite o c├│digo de Valida├ž├úo:

    ATENÇÃO! Todos os campos são obrigatórios.

  7. says

    The solution is the check.php should be like this:

    if( isset($_POST[‘submit’]))
    {
    if( $_SESSION[‘security_code’] == md5($_POST[‘security_code’]) && !empty($_SESSION[‘security_code’] ) )
    {

  8. says

    # // Numbers (1), Letters (2), Letters & Numbers (3)
    # public $string_type = 3;

    This line works vice-versa…
    That is if i choose 1 it displays letters, and if i choose 2 it displays numbers and 3 is working fine!

    thanks mate i love this

Leave a Reply

Your email address will not be published. Required fields are marked *