Создаем капчу для формы обратной связи

CAPTCHA своими руками капчаНа тему создания капчи (captcha) было написано очень много статей, а также почти в каждой такой заметке в комментариях начинался холивар на тему нужна ли вобще нам каптча, и если да то в каком виде она должна быть. Вариантов как вы наверное знаете привеликое множество, это цифры, или буквы, или их сочетание, деформированные или ровные, или подбор котиков, или даже решение формул или каких то других математических или тематических задачек.

Тем не менее, свой первый сайт когда я залил на хостинг, я делал без защиты комментариев, т.е. никакой капчи у меня не было, была просто форма для комментариев, и когда я зашел через какое то время, то был сильно удивлен тому, что вся базы была заспамлена автоботами. Там были тысячи почти одинаковых текстов с ссылками. После этого, я уже никогда не размещал формы без captcha. В данной статье мы все-же посмотрим как создается и прикручивается анти-бот фильтр например для формы обратной связи. Ничего сложного в этом нет. Алгоритм наших действий будет таков: нам нужен скрипт который будет генерировать случайным образом набор символов. Далее нам нужен скрипт, который будет принимать эти символы и рисовать из них рисунок, а также закидывать это значение в сессию. Этот рисунок у нас будет отображаться в нашей форме с просьбой к юзеру, чтобы он ввел символы которые видит. После отправки формы, нам останется сравнивать значение симвовов введеных пользователем со значением сохраненным в сессии.

Начнем с простенькой формы. Здесь добавим кнопку “обновить капчу” на тот случай, если изображение сгенерировалось совсем не читаемое. В этом случае у нас заново сработает скрипты случайного значения и рисунка, а также перепишется сессия.

<form action="" method="post">   
    Ваше имя<br />   
    <input name="online_name" type="text"/><br />   
    Ваш е-mail:<br />   
    <input name="online_mail" type="email" required /><br />   
    Сообщение:<br />   
    <textarea name="online_message"></textarea><br /><br />  
            <img src='captcha.php' id='capcha-image'>
            <a href="javascript:void(0);" onclick="document.getElementById('capcha-image').src='./captcha.php'">Обновить капчу</a><br/>
            <!-- Запрашиваем у captcha.php случайное изображение.  -->
            <span>Введите капчу:</span><br/>
            <input type="text" name="code"><br/>
    <input name="go" type="submit" value="Отправить" /><br /><br /> 
</form>

Вот эта функция будет выдавать нам произвольный рендомный кусок символов

function generate_code() 
{    
      $chars = 'abdefhknrstyz23456789'; // Задаем символы, которые будут в капче
      $length = rand(3, 4); // Длина капчи
      $numChars = strlen($chars); // Вычисляем, сколько всего символов
      $str = '';
      for ($i = 0; $i < $length; $i++) {
        $str .= substr($chars, rand(1, $numChars) - 1, 1);
      } // Выдергиваем случайные символы
      
    // Перемешиваем
        $array_mix = preg_split('//', $str, -1, PREG_SPLIT_NO_EMPTY);
        srand ((float)microtime()*1000000);
        shuffle ($array_mix);
    // Возвращаем полученный код
    return implode("", $array_mix);
}

Вот собственно скрипт, который будет рисовать нашу картинку-капчу:

// Устанавливаем переменную img_dir, путm к шрифтам и фонам
define("img_dir", $_SERVER['DOCUMENT_ROOT']."/img/");

// Подключаем генератор текста
include("random.php");
$captcha = generate_code();

// Записываем значение в сессию
session_start();
$_SESSION['captcha']=md5($captcha);

// Функция генерации изображения
function img_code($code) // $code - сюда передадим сгенерированный код
{
    // Отправляем браузеру Header'ы
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");                   
        header("Last-Modified: " . gmdate("D, d M Y H:i:s", 10000) . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");         
        header("Cache-Control: post-check=0, pre-check=0", false);           
        header("Pragma: no-cache");                                           
        header("Content-Type:image/png");
    // Количество линий. Будут за текстом и сверху текста
        $linenum = rand(3, 15); 
    // Задаем фоны для капчи. Можете загрузить свой (у меня в папке /img). Рекомендуемый размер - 150х70
        $img_arr = array(
                         "1.png", "2.png"
        );
    // Шрифты
        $font_arr = array();
            $font_arr[0]["fname"] = "Andika-R.ttf"; // Имя шрифта. Например Droid Sans
            $font_arr[0]["size"] = rand(20, 35);        // Размер в pt
    // Генерируем подложку со случайным фоном
        $n = rand(0,sizeof($font_arr)-1);
        $img_fn = $img_arr[rand(0, sizeof($img_arr)-1)];
        
        $im = imagecreatefrompng (img_dir . $img_fn);
    // Рисуем линии на подложке
        for ($i=0; $i<$linenum; $i++)
        {
            $color = imagecolorallocate($im, rand(0, 150), rand(0, 100), rand(0, 150)); // Случайный цвет c изображения
            imageline($im, rand(0, 20), rand(1, 50), rand(150, 180), rand(1, 50), $color);
        }
        $color = imagecolorallocate($im, rand(0, 200), 0, rand(0, 200)); // Случайный цвет для текста.

    // Накладываем текст капчи        
        $x = rand(0, 35);
        for($i = 0; $i < strlen($code); $i++) {
            $x+=15;
            $letter=substr($code, $i, 1);
            imagettftext ($im, $font_arr[$n]["size"], rand(2, 4), $x, rand(50, 55), $color, img_dir.$font_arr[$n]["fname"], $letter);
        }

    // Линии поверх текста
        for ($i=0; $i<$linenum; $i++)
        {
            $color = imagecolorallocate($im, rand(0, 255), rand(0, 200), rand(0, 255));
            imageline($im, rand(0, 20), rand(1, 50), rand(150, 180), rand(1, 50), $color);
        }
    // Возвращаем получившееся изображение
        ImagePNG ($im);
        ImageDestroy ($im);
}
img_code($captcha) // Выводим изображение

И наконец обработка формы:

//Если в куках есть какая инфо-запись то сохраним ее в переменной $info а чуть ниже отобразим ее.
if (isset($_COOKIE['info'])) {
    $info = $_COOKIE['info'];
    setcookie("info","",time()+3600);
}

<?php if (isset($info)) {
    echo '<h2>'.$info.'</h2>';
} ?>

//Функция проверки капчи
function check_code($code,$cap) 
{
    $code = trim($code);
    $code = md5($code);
    if ($code == $cap){return TRUE;}else{return FALSE;}
}

if (isset($_POST['go'])) { //Если была нажата кнопка...

    session_start();
    $cap = $_SESSION['captcha']; //берем из сессии значение MD5 нашего кода, занесенного туда captcha.php
    $_SESSION['captcha'] = ''; //Чистим сессию

    $online_theme   = '=?UTF-8?B?'.base64_encode("Письмо с сайта: Мой сайт").'?='; //Тема письма
    $online_name    = strip_tags(stripslashes(substr($_POST['online_name'],0,50))); //Имя
    $online_mail    = strip_tags(stripslashes(substr($_POST['online_mail'],0,30))); //Email товарища
    $online_message = strip_tags(stripslashes(substr($_POST['online_message'],0,3000))); //Сообщение для передачи

                        
    if (!empty($_POST['online_name']))      {
    if (!empty($_POST['online_mail']))      {       
    if (!empty($_POST['online_message']))   {   
    if(checkmail($online_mail) !== -1)      {

        //Отправка письма если все проверено!
        $message = "Письмо с сайта мой сайт\nОт: {$online_name}\nЕго email: {$online_mail}\nСообщение: {$online_message}\n";
        $headers = 'MIME-Version: 1.0' . "\r\n";
        $headers="Content-type: text/plain; charset=\"UTF-8\"";
        $headers.="From: <{$online_mail}>";
        $headers.="Subject: {$online_theme}";
        $headers.="Content-type: text/plain; charset=\"UTF-8\"";

            if (check_code($_POST['code'], $cap)) //Тут как раз и проверяем совпадение капчи
            {
            if(mail("ваш@ящик.ru, второй@ящик.com",$online_theme,$message,$headers) !== FALSE) //Отправляем письмо если все ок 
                {
                    //Через куки передаем информацию, что все ок
                    setcookie("info","Сообщение успешно отправлено",time()+3600);
                    header('Location: /');
                    exit();
                } 
            else 
                {echo "<p>Возникла ошибка при отправке, повторите позднее<br/><a href='/'>Назад</a></p>"; }    
            }
            else 
            { echo "<p>Неправильный код капчи <a href='/'>Назад</a></p>";}
        
    } else {    echo "<p>Введите корректный электронный адрес <a href='/'>Назад</a></p>";     }
    } else {    echo "<p>Введите сообщение      <a href='/'>Назад</a></p>";   }
    } else {    echo "<p>Вы не ввели e-mail     <a href='/'>Назад</a></p>";   }           
    } else {    echo "<p>Вы не указали имя      <a href='/'>Назад</a></p>";   }
}

Дополнительная функция проверки корректности введенного емэйла:

function checkmail($email) {
$email=trim($email);
if (strlen($email)==0) return -1;
if (!preg_match("/^[a-z0-9_-]{1,20}+(\.){0,2}+([a-z0-9_-]){0,5}@(([a-z0-9-]+\.)+(com|net|org|mil|edu|gov|arpa|info|biz|inc|name|[a-z]{2})|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/is",$email))
return -1;
return $email;
}

Ну и финальная сборка. Для теста разместим все в корне сайта. Для начала создадим директорию “img” и закинем туда пару картинок с шумом сгенерированных в фотошопе, назвав их 1.png и 2.png. В эту же директорию закинем шрифт, например у меня Andika-R.ttf.

Файлов будет всего два. Первый captcha.php будет нашей картинкой. Второй собственно скрипт-форма-обработчик.
captcha.php

define("img_dir", $_SERVER['DOCUMENT_ROOT']."/img/");

function generate_code() 
{    
      $chars = 'abdefhknrstyz23456789';
      $length = rand(3, 4); 
      $numChars = strlen($chars); 
      $str = '';
      for ($i = 0; $i < $length; $i++) {
        $str .= substr($chars, rand(1, $numChars) - 1, 1);
      } 

        $array_mix = preg_split('//', $str, -1, PREG_SPLIT_NO_EMPTY);
        srand ((float)microtime()*1000000);
        shuffle ($array_mix);
    return implode("", $array_mix);
}

$captcha = generate_code();

session_start();
$_SESSION['captcha']=md5($captcha);


function img_code($code) 
{
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");                   
        header("Last-Modified: " . gmdate("D, d M Y H:i:s", 10000) . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");         
        header("Cache-Control: post-check=0, pre-check=0", false);           
        header("Pragma: no-cache");                                           
        header("Content-Type:image/png");

        $linenum = rand(2, 4); 
        $img_arr = array("1.png", "2.png");
        $font_arr = array();
            $font_arr[0]["fname"] = "Andika-R.ttf";
            $font_arr[0]["size"] = rand(20, 35);
        $n = rand(0,sizeof($font_arr)-1);
        $img_fn = $img_arr[rand(0, sizeof($img_arr)-1)];
        $im = imagecreatefrompng (img_dir . $img_fn);

        for ($i=0; $i<$linenum; $i++)
        {
            $color = imagecolorallocate($im, rand(0, 150), rand(0, 100), rand(0, 150)); 
            imageline($im, rand(0, 20), rand(1, 50), rand(150, 180), rand(1, 50), $color);
        }
        $color = imagecolorallocate($im, rand(0, 200), 0, rand(0, 200)); 

        $x = rand(0, 35);
        for($i = 0; $i < strlen($code); $i++) {
            $x+=15;
            $letter=substr($code, $i, 1);
            imagettftext ($im, $font_arr[$n]["size"], rand(2, 4), $x, rand(50, 55), $color, img_dir.$font_arr[$n]["fname"], $letter);
        }

        for ($i=0; $i<$linenum; $i++)
        {
            $color = imagecolorallocate($im, rand(0, 255), rand(0, 200), rand(0, 255));
            imageline($im, rand(0, 20), rand(1, 50), rand(150, 180), rand(1, 50), $color);
        }

        ImagePNG ($im);
        ImageDestroy ($im);
}
img_code($captcha);

Второй файл форма-обработчик: index.php

function checkmail($email) {
$email=trim($email);
if (strlen($email)==0) return -1;
if (!preg_match("/^[a-z0-9_-]{1,20}+(\.){0,2}+([a-z0-9_-]){0,5}@(([a-z0-9-]+\.)+(com|net|org|mil|edu|gov|arpa|info|biz|inc|name|[a-z]{2})|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/is",$email))
return -1;
return $email;
}

function check_code($code,$cap) 
{
    $code = trim($code);
    $code = md5($code);
    if ($code == $cap){return TRUE;}else{return FALSE;}
}

if (isset($_POST['go'])) { 

    session_start();
    $cap = $_SESSION['captcha']; 
    $_SESSION['captcha'] = ''; 

    $online_theme   = '=?UTF-8?B?'.base64_encode("Письмо с сайта: Мой сайт").'?='; 
    $online_name    = strip_tags(stripslashes(substr($_POST['online_name'],0,50))); 
    $online_mail    = strip_tags(stripslashes(substr($_POST['online_mail'],0,30))); 
    $online_message = strip_tags(stripslashes(substr($_POST['online_message'],0,3000))); 

                        
    if (!empty($_POST['online_name']))      {
    if (!empty($_POST['online_mail']))      {       
    if (!empty($_POST['online_message']))   {   
    if(checkmail($online_mail) !== -1)      {

        
        $message = "Письмо с сайта мой сайт\nОт: {$online_name}\nЕго email: {$online_mail}\nСообщение: {$online_message}\n";
        $headers = 'MIME-Version: 1.0' . "\r\n";
        $headers="Content-type: text/plain; charset=\"UTF-8\"";
        $headers.="From: <{$online_mail}>";
        $headers.="Subject: {$online_theme}";
        $headers.="Content-type: text/plain; charset=\"UTF-8\"";

            if (check_code($_POST['code'], $cap)) 
            {
            if(mail("mail@mail.ru, mail@mail.com",$online_theme,$message,$headers) !== FALSE) 
                {
                    setcookie("info","Сообщение успешно отправлено",time()+3600);
                    header('Location: /');
                    exit();
                } 
            else 
                {echo "<p>Возникла ошибка при отправке, повторите позднее<br/><a href='/online'>Назад</a></p>"; }    
            }
            else 
            { echo "<p>Неправильный код капчи <a href='/'>Назад</a></p>";}

    } else {    echo "<p>Введите корректный электронный адрес <a href='/online'>Назад</a></p>";     }
    } else {    echo "<p>Введите сообщение      <a href='/'>Назад</a></p>";   }
    } else {    echo "<p>Вы не ввели e-mail     <a href='/'>Назад</a></p>";   }           
    } else {    echo "<p>Вы не указали имя      <a href='/'>Назад</a></p>";   }
                
} 

if (isset($_COOKIE['info'])) {
    $info = $_COOKIE['info'];
    setcookie("info","",time()+3600);
}

if (isset($info)) {
    echo '<h2>'.$info.'</h2>';
}
?>

<form action="" method="post">   
    Ваше имя<br />   
    <input name="online_name" type="text"/><br />   
    Ваш е-mail:<br />   
    <input name="online_mail" type="email" required /><br />   
    Сообщение:<br />   
    <textarea name="online_message"></textarea><br /><br />  
            <img src='captcha.php' id='capcha-image'>
            <a href="javascript:void(0);" onclick="document.getElementById('capcha-image').src='./captcha.php'">Обновить капчу</a><br/>
            <span>Введите капчу:</span><br/>
            <input type="text" name="code"><br/>
    <input name="go" type="submit" value="Отправить" /><br /><br /> 
</form> 

В реальном проекте конечно функции лучше вынести в отдельный файл с функциями. Буду признателен если кто-то в комментариях поделится более правильным алгоритмом или покажет свой проверенный пример. Генерация картинки капчи по материалам этой заметки.

Есть 3 коммент.

  1. Вадим пишет:

    Добрый день!
    У меня к Вам такой вопрос (форма обратной связи и капча не Ваша).
    Капча срабатывает не с первого раза, т.е. при первом заходе на страницу обратной связи, правильного заполнения всех полей и капчи появляется сообщение, что не правильно введены символы капчи и кнопка возврат к форме и только теперь, если вернутся и все правильно заполнить и капчу тоже (при возврате уже новые символы), все отправляется. Не помогает, если зайти первый раз в форму обратной связи и предварительно обновить капчу, все тоже самое – срабатывает когда происходит возврат на форму. И так всегда. Можно ли это как-то исправить?
    И до кучи еще один вопрос. При не правильном заполнении формы или ошибке при вводе капчи возврат происходит в пустую форму, т.е. все ранее заполненные поля очищаются. Часто такое же встречаю в интернете на других сайтах. Для посетителей очень не удобно заполнять поля формы еще раз и многих это отпугивает. Как сделать возврат к форме чтобы ранее заполненные поля сохранились? Ну как у Вас здесь.

  2. admin пишет:

    Добрый день! По поводу не срабатывания капчи, тут надо смотреть код, раз идет не совпадение то смотрите где и что выводится. При возврате на страничку, чтобы поля заново не заполнять их значения нужно прописывать так – например input type=text name=”email” value=”< ?php echo $_POST['email']; ?>“

  3. Алексей пишет:

    А попроще като нельзя организовать обратку на сайте??

Написать комментарий

XHTML: Вы можете использовать эти теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

*

*