Простейшая капча на PHP и GD
В этой статье, мы займемся внедрением капчи (случайно генерируемых изображений для проверки на то, кем является посетитель: человеком или роботом). Капча это необходимое зло, и в данной статье я научу вас как её можно сделать.
Замечу, что есть много сторонних, более мощных и автоматизированных решений, как например ReCaptcha, но данный урок нацелен исключительно на то, чтобы объяснить и продемонстрировать, как на самом деле работает данная технология.
Мы не будем объяснять, что такое капча, так как предполагается, что это основы и вы с ними уже знакомы из других источников.

Рисование капчи
Для начала, вам нужно убедиться, что у вас установлена библиотека GD (Graphics Draw). Она позволяет отрисовывать графику и изображения с помощью встроенных PHP-функций.
Чтобы установить GD, выполните команду sudo apt-get install php5-gd, а если вы используете не-Ubuntu-подобную операционную систему, то следуйте этим инструкциям.
Капча обычно состоит из трех основных компонентов – фигура, искажение и текст.
Мы будем следовать следующим шагам для достижения цели статьи:
- Отобразим пустое изображение в браузере;
- Создадим фигуру;
- Сгенерируем случайные линии;
- Сгенерируем случайные точки;
- Сгенерируем случайный текст.
В приведенном коде используется процедурный стиль, чтобы сохранить простоту и читаемость примера. В реальной же реализации стоит использовать ООП-стиль.
Отображение пустого изображения
Изображение будет сформировано с помощью HTML-тега «img», ссылающегося на внешний файл.
Для этого будет использовано две функции – одна для создания, а вторая для отображения изображения:
<?php session_start(); ?>
<title>demo.php</title>
<body style="background-color:#ddd; ">
<?php
create_image();
display();
/***** определение функций *****/
function display()
{
?>
<div style="text-align:center;">
<h3>ВВЕДИТЕ ТЕКСТ, ИЗОБРАЖЕННЫЙ НА КАРТИНКЕ</h3>
<b>Это проверка на то, робот вы или живой человек </b>
<div style="display:block;margin-bottom:20px;margin-top:20px;">
<img src="image.png">
</div> //div1 конец
</div> //div2 конец
<?php
}
function create_image()
{
$image = imagecreatetruecolor(200, 50);
imagepng($image, "image.png");
}
?>
</body>
<?php ?>
Первая строка запускает пользовательскую сессию на данной странице.
Функция display() не отличается от обычного HTML-кода и выводит изображение в окно браузера. Также, тут производится небольшая стилизация блока div, чтобы выходное изображение выглядело презентабельно.
Внутри функции create_image(), переменная используется, чтобы сохранить ссылку на изображение, возвращаемую функцией imagecreatetruecolor(), которая принимает ширину и высоту изображения в качестве аргументов.
Функция imagepng() создает png-изображение с указанным именем и путем (в той же папке).
После выполнения первого шага, наш код будет генерировать только черный прямоугольник:

imagepng() завершает код нашей функции. Непосредственно её вызов производится выше, строкой create_image(), иначе сама по себе эта функция не будет иметь никакого эффекта.
Создание фигуры
Для капчи может быть выбрана любая фигура. Мы выберем прямоугольник, который вызывается функцией imagefilledrectangle().
Она принимает пять аргументов: ссылка на изображение, начальную позицию по оси Х, начальную позицию по оси Y, конечную позицию по оси Х, конечную позицию по оси Y и фоновый цвет. Вы можете использовать соответствующую функцию эллипса для генерации эллиптической капчи.
Функция imagecolorallocate() назначает цвет принимаемой переменной в формате RGB, передаваемый как аргумент. Следующий код добавляется к функции create():
$background_color = imagecolorallocate($image, 255, 255, 255);
imagefilledrectangle($image,0,0,200,50,$background_color);
Предыдущая картинка после данного шага станет белой:

Генерация случайных линий
Теперь, приступим к искажению капчи. В PHP линии создаются от начальной точки (x1, y1) до конечной (x2, y2). Мы хотим, чтобы линия протянулась от одного края картинки до другого, укажем координаты со значениями , то есть сделаем их равными ширине изображения.
Координаты будут генерироваться автоматически. Таким образом, будет создана одна случайная линия.
Мы будем генерировать несколько линий путем помещения функции отрисовки линии в цикл:
$line_color = imagecolorallocate($image, 64,64,64);
for($i=0;$i<10;$i++) {
imageline($image,0,rand()%50,200,rand()%50,$line_color);
}
Функция imageline() получает координаты x1,x2,y1,y2 и цвет линии в качестве аргументов, а также ссылку на изображение. Цвет линии выставлен равным цвету линии, заданному на предыдущем шаге.
Y-координата задана, как rand()*%50, потому что высота нашего изображения 50 пикселей и, соответственно, будет возвращаться случайное значение не больше 50. Можно также использовать функцию rand(0,50).
Получится тот же самый результат:

Генерация случайных точек
Случайные точки будут генерироваться аналогично линиям на предыдущем шаге. Для этого используется функция imagesetpixel(), принимающая значения координат, где необходимо отрисовать точки:
$pixel_color = imagecolorallocate($image, 0,0,255);
for($i=0;$i<1000;$i++) {
imagesetpixel($image,rand()%200,rand()%50,$pixel_color);
}
X-координата генерируется случайным образом с помощью команды rand()*%200, так как длина нашего изображения составляет 200 пикселей. Также можно использовать функцию rand(0,200) и получить тот же самый результат.
Y-координата генерируется на стадии отрисовки линий:

Генерация случайного текста
Мы будем случайно указывать на позицию в строке, содержащей в себе весь латинский алфавит в верхнем и нижнем регистрах, и присваивать её переменной $letter:
$letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$len = strlen($letters);
$letter = $letters[rand(0, $len-1)];
$text_color = imagecolorallocate($image, 0,0,0);
Цикл выглядит следующим образом:
for ($i = 0; $i< 6; $i++) {
$letter = $letters[rand(0, $len-1)];
imagestring($image, 5, 5+($i*30), 20, $letter, $text_color);
$word.=$letter;
}
$_SESSION['captcha_string'] = $word;
Мы разъясним значение строк:
$word.=$letter;
$_SESSION['captcha_string'] = $word;
Функция imagestring() выводит текст на наше изображение. Она принимает 6 аргументов:
- Ссылка на изображение;
- Размер шрифта текста (от 5 и более);
- Координата X (изменяется пропорционально для каждого алфавита);
- Координата Y (аналогично предыдущему, но можно изменять и случайным образом);
- Строка, в которую ведется запись;
- Цвет шрифта текста.
Вы также можете использовать функцию imagettftext(), если вам нужен больший шрифт или другой его стиль. Данная функция принимает два дополнительных параметра – угол и стиль шрифта текста.
X-координата вычисляется путем проверки. Грубо говоря, буква занимает около 35 пикселей (5+($i*30)), где $i=0,1,2,3,4,5,6. Это так, потому что нужно соблюдать расстояние между буквами, чтобы они не сливались при выводе.
Если же данное значение будет больше 40 пикселей, то буквы не поместятся внутри изображения.
В итоге, мы получили 6-буквенную капчу. Всегда можно усилить алгоритм, добавив новые случайные величины, которые сделают защиту более надежной.
Теперь, наша капча выглядит вот так:

Текст внутри капчи будет меняться каждый раз при обновлении страницы.
Валидация
На данном этапе принимается ответ пользователя и после его обработки, посылается ответ. Для начала, нужно сделать простую форму с полем ввода и кнопкой отправки.
Есть множество способов обработки капчи в зависимости от требований того или иного веб-приложения. Для целей нашей статьи, мы произведем обработку на этой же странице.
Вот тут пришло время пояснить две строки, оставленные без комментариев выше:
- $word.=$letter; – оператор конкатенации строк . используется, чтобы присоединять сгенерированные буквы, формируя строку из 6 случайных символов;
- $_SESSION['captcha_string'] = $word; – наша строка с капчей хранится в глобальном массиве $_SESSION и используется для валидации.
Изменим код функции display(), добавив простую форму.
Мы используем две кнопки отправки: одна отправляет строку, а другая – обновляет страницу.
Следующие строки нужно добавить между двумя закрывающими тегами div (смотрите комментарии к коду функции display() выше):
function display()
{
?>
<div style="text-align:center;">
<h3>ВВЕДИТЕ ТЕКСТ, ИЗОБРАЖЕННЫЙ НА КАРТИНКЕ</h3>
<b>Это проверка на то, робот вы или живой человек </b>
<div style="display:block;margin-bottom:20px;margin-top:20px;">
<img src="image<?php echo $_SESSION['count'] ?>.png">
</div> //div1 конец
<form action=" <?php echo $_SERVER['PHP_SELF']; ?>" method="POST" / >
<input type="text" name="input"/>
<input type="hidden" name="flag" value="1"/>
<input type="submit" value="Отправить" name="submit"/>
</form>
<form action=" <?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
<input type="submit" value="Обновить страницу">
</form>
</div> //div2 конец
<?php
}
Перед тем, как двигаться дальше, мы должны знать, когда нужно, а когда не нужно отображать поле ввода. Оно будет отображаться, только когда:
- Страница была только что загружена;
- Ответ пользователя оказался некорректным.
Первое условие проверяется с помощью переменной $flag, которая устанавливается в значение «1» каждый раз, когда нажимается кнопка «Отправить». Первоначально, значение должно быть любым другим, например «0».
Второе условие можно выполнить, проверив равенство значения, сохраненного в массиве $_SESSION и значения, введенного в поле ввода пользователем (смотрите код ниже).
Для этого, заменим следующие строки из первого шага:
create_image();
display();
на:
$flag = 5;
if (isset($_POST["flag"])) // проверяем, существует ли переменная $flag в массиве POST
{
$input = $_POST["input"];
$flag = $_POST["flag"];
}
if ($flag == 1) // кнопка «Отправить» нажата
{
if (isset($_SESSION['captcha_string']) && $input == $_SESSION['captcha_string'])
// пользовательское значение и реальное значение капчи совпали
{ ?>
<div style="text-align:center;">
<h1>Ответ введен верно!</h1>
<form action=" <?php echo $_SERVER['PHP_SELF']; ?>" method="POST"> // кнопка обновления страницы
<input type="submit" value="Обновить страницу">
</form>
</div>
<?php
} else // если ответ неверный, капча показывается снова
{ ?>
<div style="text-align:center;">
<h1>Ваш ответ неправильный!<br>Пожалуйста, попробуйте снова.</h1>
</div>
<?php
create_image();
display();
}
} else // страница загружается снова {
create_image();
display();
}
Функции create_image() и display() вызываются, только если выполнены два условия, описанные выше.
Нам понадобится переменная сессии с предыдущей страницы, так что тут сессия еще не закрывается. Уничтожается она автоматически при закрытии окна браузера.
Капча теперь выглядит так:

Если значение, введенное в поле неверно, то пользователь будет уведомлен об этом и капча появится еще раз:

Если все верно, то пользователь получит соответствующее сообщение:

Тут есть один нюанс – когда пользователь наживает кнопку «Назад» в браузере, изображение возьмется из кэша браузера, а страница перезагрузится.
Если страница запрашивается методом POST, то при нажатии кнопки «Назад», отобразится сообщение о том, что страница устарела, в то время как при запросе GET, изображение не обновится.
Решение тут очень простое – нужно создавать уникальные имена изображений каждый раз, чтобы браузер не смог найти их в кэше.
Мы будем прикреплять к имени изображения уникальную строку, генерируемую встроенной функцией time(), при создании и отображении в браузере.
Добавьте следующую строку ниже начала сессии:
$_SESSION['count']=time(); // прибавление уникального значения к имени
Заменяем атрибут src тега img в функции display():
<img src="image<?php echo $_SESSION['count']?>.png">
Также изменяем место, где создается png-изображение в функции create_image():
imagepng($image,"image".$_SESSION['count'].".png");
Теперь, изображения будут называться именами наподобие image39342015.png.
Эта процедура будет создавать изображения каждый раз при перезагрузке страницы, что может привести к неоправданному расходу дискового пространства, поэтому мы должны убедиться, что перед созданием очередного png-изображения, все прочие были удалены.
Добавьте следующие строки прямо перед вызовом функции imagepng():
$images = glob("*.png");
foreach($images as $image_to_delete) {
unlink($image_to_delete);
}
В реальном приложении, нужно убедиться, чтобы изображения капчи хранились в отдельной папке, иначе можно случайно удалить другие важные изображения.
Полный исходный код примера находится здесь.
Заключение
Создавать различные капчи на PHP очень просто. В этой статье были рассмотрены три базовых компонента стандартной капчи – фигура, искажение и текст.
Эта статья продемонстрировала лишь принцип создания капчи, и её не следует использовать в таком виде в реальном приложении – особенно, когда есть такие мощные альтернативы, как ReCaptcha, помимо всего прочего поддерживающие голосовое произношение для людей с ограниченными возможностями.
Мы надеемся, что статья была для вас полезной и интересной!