Nickolay.info. PHP. Статьи. Делаем простую капчу с цифрами

Обычно в наше время для капчи (captcha, "антиспамовый код", вводимый на многих страницах Интернета) программисты генерируют картинки средствами библиотеки GDLib, получая код вроде приведённого ниже (код взят из одного закрытого проекта):

<?php
$letters = array();
function generate_key()
{
	global $letters;
	$chars = array
	(
		'A', 'B', 'C', 'D',
		'E', 'F', 'G', 'H',
		'I', 'J', 'K', 'L',
		'M', 'N', 'O', 'P',
		'Q', 'R', 'S', 'T',
		'U', 'V', 'W', 'X',
		'Y', 'Z', '1', '2',
		'3', '4', '5', '6',
		'7', '8', '9', '0',
	);
	$count = count($chars) - 1;
	$key = array();
	for($i = 0; $i < 2; $i++)
	{
		$key[ $i ] = '';
		for($j = 0; $j < 3; $j++)
		{
			list($usec, $sec) = explode(' ', microtime());
			mt_srand((float) $sec + ((float) $usec * 1000000));
			$letter = $chars[ mt_rand(0, $count) ];
			if ( isset($letters[ $letter ]) )
			{
				--$j;
			}
			else
			{
				$letters[ $letter ] = $letter;
				
				$key[ $i ] .= $letter;
			}
		}
	}
	
	return implode('', $key);
	
}
$img_x = 200;
$img_y = 50;
$font = '/font.ttf';
$content = generate_key();
session_start();
session_register("antibot");
$_SESSION['antibot'] = $content;
$img = imagecreate($img_x, $img_y);
$colours = array();
$colours['white'] = imagecolorallocate($img, 255, 255, 255);
$colours['grey'] = imagecolorallocate($img, 230, 230, 230);
$colours['black'] = imagecolorallocate($img, 0, 0, 0);
$number_of_x_lines = ($img_x - 1) / 6;
$number_of_y_lines = ($img_y - 1) / 6;
for($i = 0; $i < $number_of_x_lines; $i++){
 imageline($img, $i * $number_of_x_lines, 0, $i * $number_of_x_lines, $img_y, $colours['grey']);}
for($i = 0; $i < $number_of_y_lines; $i++){
 imageline($img, 0, $i * $number_of_y_lines, $img_x, $i * $number_of_y_lines, $colours['grey']);}
$txt_bbox = imagettfbbox(20, 0, $font, $content);
$sx = ($img_x - ($txt_bbox[2] - $txt_bbox[0])) / 2;
$sy = ($img_y - ($txt_bbox[1] - $txt_bbox[7])) / 2;
$sy -= $txt_bbox[7];
header('Content-type: image/png');
 
imagettftext($img, mt_rand(14, 18), mt_rand(-10, 10), $sx, $sy, $colours['black'], $font, $content);
imagepng($img);
imagedestroy($img);
?>

Часто так сделать невозможно (отключена или недоступна GDLib, просто не хочется стрелять из пушки по воробьям и т.п.) Тогда можно сделать гораздо проще, написав скрипт, самостоятельно кодирующий GIF'ки капчи прямо в теле скрипта с помощью base64.

Для простоты ограничимся капчами, состоящими только из цифр и напишем следующий демо-скрипт:

<?php
//Совсем простая капча
 
$captha_length=4; // Количество символов в коде
$captcha_random_seed="543129"; // Случайное число для защиты; поставьте своё
 
if (isset($_REQUEST['image'])) {
 function write_image_number ($num) {
  $number="R0lGODlhCgAMAIABAFNTU////yH5BAEAAAEALAAAAAAKAAwAAAI"; // Заголовок GIFки в base64
  if ($num=="0") { $len="63"; $number.="WjIFgi6e+QpMP0jin1bfv2nFaBlJaAQA7";}
  if ($num=="1") { $len="61"; $number.="UjA1wG8noXlJsUnlrXhE/+DXb0RUAOw==";}
  if ($num=="2") { $len="64"; $number.="XjIFgi6e+QpMPRlbjvFtnfFnchyVJUAAAOw==";}
  if ($num=="3") { $len="64"; $number.="XjIFgi6e+Qovs0RkTzXbj+3yTJnUlVgAAOw==";}
  if ($num=="4") { $len="64"; $number.="XjA9wG8mWFIty0amczbVJDVHg9oSlZxQAOw==";}
  if ($num=="5") { $len="63"; $number.="WTIAJdsuPHovSKGoprhs67mzaJypMAQA7";}
  if ($num=="6") { $len="63"; $number.="WjIFoB6vxmFw0pfpihI3jOW1at3FRAQA7";}
  if ($num=="7") { $len="61"; $number.="UDI4Xy6vtAIzTyPpg1ndu9oEdNxUAOw==";}
  if ($num=="8") { $len="63"; $number.="WjIFgi6e+QpMP2slSpJbn7mFeWDlYAQA7";}
  if ($num=="9") { $len="64"; $number.="XjIFgi6e+QpMP0jinvbT2FGGPxmlkohUAOw==";}
  header("Content-type: image/gif"); 
  header("Content-length: $len");
  echo base64_decode($number); 
 }
 
 // Вывод закодированных изображений на экран
 if (array_key_exists('image', $_REQUEST)) { 
  $num=$_REQUEST['image'];
  for ($i=0; $i<10; $i++) { 
   if (md5($i+$captcha_random_seed)==$num) { 
    write_image_number($i); exit; //Вывод одной цифры и выход
   } 
  } 
 }
 exit;
}
 
//Печатаем заголовок документа
echo '
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
 <meta content="text/html; charset=Windows-1251" http-equiv="content-type">
 <title>Простая капча на PHP</title>
</head>
<body>';
 
$captcha_key=''; 
//mt_srand(time()+(double)microtime()*1000000); //Не нужно с PHP 4.2.0
print"<form action=\"".$_SERVER["PHP_SELF"]."\" method=\"post\">Защитный код: ";
for ($i=0; $i<$captha_length; $i++) {
 $snum=mt_rand(0,9); 
 $psnum=md5($snum+$captcha_random_seed);
 echo '<img src="'.$_SERVER["PHP_SELF"]."?image=$psnum\" border=\"0\" alt=\"\">\n";
 $captcha_key.=$snum;
}
$captcha_key=md5("$captcha_key+$captcha_random_seed");
print" <input name=\"captcha_key\" type=\"hidden\" value=\"$captcha_key\">
<input name=\"user_captcha_number\" type=\"text\" maxlength=\"$captha_length\" size=\"$captha_length\"> 
(введите число, указанное на картинке) 
<input type=\"submit\" name=\"action\" value=\"OK\"></form>";
 
//проверка:
if (isset($_REQUEST['action'])) { //была нажата кнопка
 $errormsg="<font color=red>Введённый код неверен!</font>";
 if (!isset($_POST['user_captcha_number']) or !isset($_POST['captcha_key'])) {
  print "$errormsg"; exit;
 }
 $user_captcha_number=$_POST['user_captcha_number'];
 $captcha_key=$_POST['captcha_key'];
 if (md5("$user_captcha_number+$captcha_random_seed")!=$captcha_key) { print "$errormsg"; exit; }
 print "Всё верно</body></html>";
}
?>

Так как скрипт вызывает сам себя для получения очередной картинки-цифры, нельзя перед выводом картинки ставить какой-нибудь HTML-код. А вот перед ветвью для вывода формы - можно, как и сделано в примере. Не уверен также, что уже нет ботов, читающих эти довольно стандартные цифровые последовательности. Попозже, может, сделаю из этого простенький модуль. Конечно, для его работы в форме всё равно нужно будет указывать текстовое поле user_captcha_number (число, введённое пользователем), скрытое поле captcha_key (зашифрованный "правильный ответ") и кнопку action (отправка формы). Разумеется, названия полей формы HTML можно изменить, тогда изменятся и названия соответствующих переменных в теле скрипта.

 Этот пример в работе

У приведённого выше кода можно найти, как минимум, один недостаток: в IE 7-8 сообщения "Код неправилен" или "Всё верно" не выводятся, если отправить форму клавишей Enter, а не кнопкой ОК.

Печать из скрипта массива $_REQUEST строкой

print_r ($_REQUEST);

показала в Internet Explorer 8 следующее:

Array ( [captcha_key] => 0d38b62fe1d3fb3cebe67a35c627c59a [user_captcha_number] => 4089 [__utma] => 
96992031.150883131.1284642699.1285238584.1285244288.8 [phpbb2mysql_data] => 
a:2:{s:11:\"autologinid\";s:0:\"\";s:6:\"userid\";i:-1;} )

Видно, что в массиве нет записи [action] => OK, раз не нажималась кнопка ОК. Во всех остальных браузерах (Opera, Firefox, Google Chrome) такая запись есть независимо от способа отправки формы (клавишей Enter или кнопкой ОК). Легче всего решить эту проблему, просто определяя факт отправки данных по другому полю, например, поставив строку

if (isset($_REQUEST['captcha_key'])) { //была нажата кнопка

вместо

if (isset($_REQUEST['action'])) { //была нажата кнопка

В онлайн-демке так и сделано, всё работает, в том числе, в Internet Explorer.

P.S. Версия самого первого скрипта, работающая в PHP8, может быть в простейшем случае такой:

<?php
function generate_key() {
 $letters = array();
 $chars = [ 'A', 'B', 'C', 'D',	'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
            'M', 'N', 'O', 'P',	'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
            'Y', 'Z', '1', '2',	'3', '4', '5', '6', '7', '8', '9', '0' 
          ];
 $count = count($chars) - 1;
 $key = [];
 for ($i = 0; $i < 2; $i++) {
  $key[ $i ] = '';
  for ($j = 0; $j < 3; $j++) {
   list ($usec, $sec) = explode(' ', microtime());
   mt_srand((float) $sec + ((float) $usec * 1000000));
   $letter = $chars[ mt_rand(0, $count) ];
   if ( isset($letters[ $letter ]) ) --$j;
   else {
    $letters[ $letter ] = $letter;
    $key[ $i ] .= $letter;
   }
  }
 }
 return implode('', $key);
}

$img_x = 200; $img_y = 50;

$font = dirname(__FILE__) . '/arial.ttf'; 
$content = generate_key();
$_SESSION['antibot'] = $content;
header('Content-type: image/png');

$img = imagecreate($img_x, $img_y);
$colours = array();
$colours['white'] = imagecolorallocate($img, 255, 255, 255);
$colours['grey'] = imagecolorallocate($img, 230, 230, 230);
$colours['black'] = imagecolorallocate($img, 0, 0, 0);
$number_of_x_lines = ($img_x - 1) / 6;
$number_of_y_lines = ($img_y - 1) / 6;
for($i = 0; $i < $number_of_x_lines; $i++) 
 imageline($img, $i * $number_of_x_lines, 0, $i * $number_of_x_lines, $img_y, $colours['grey']);
for($i = 0; $i < $number_of_y_lines; $i++)
 imageline($img, 0, $i * $number_of_y_lines, $img_x, $i * $number_of_y_lines, $colours['grey']);
$txt_bbox = imagettfbbox(20, 0, $font, $content);
$sx = ($img_x - ($txt_bbox[2] - $txt_bbox[0])) / 2;
$sy = ($img_y - ($txt_bbox[1] - $txt_bbox[7])) / 2;
$sy -= $txt_bbox[7];

imagettftext($img, mt_rand(14, 18), mt_rand(-10, 10), $sx, $sy, $colours['black'], $font, $content);
imagepng($img);
imagedestroy($img);
?>

Предполагается, что шрифт с именем arial.ttf подключен из текущей папки скрипта.

Рейтинг@Mail.ru

вверх гостевая; E-mail