MODX – Защита AJAX FormIt с помощью графической капчи

Александр Мальцев
Александр Мальцев
7,1K
15
Содержание:
  1. Создание капчи для её использования в MODX
  2. Применение капчи для защиты FormIt формы
  3. Пример использования нескольких капч на одной странице
  4. Комментарии

Статья, в которой разберем, как в MODX Revolution с помощью графической капчи можно защитить форму обратной связи. Для обработки HTML формы будем использовать дополнение FormIt вместе с AjaxForm.

Создание капчи для её использования в MODX

Разработку капчи для MODX осуществим во внешнем php файле (например, captcha.php). Для удобного хранения файлов в файловой структуре MODX будем использовать каталог captcha, который создадим в assets. Кроме captcha.php в данный каталог поместим ещё файл с фоном (background.png) и шрифт (oswald.ttf). Скачать готовый архив каталога captcha можно с сервиса Яндекс.Диск.

assets/
├── captcha/
│   ├── background.png
│   ├── captcha.php
│   └── oswald.ttf
└── ...

Содержимое файла captcha.php:

<?php
require_once "../../core/model/modx/modx.class.php";
$modx = new modX();
$modx->initialize('web');
$id = 'captcha';
if (isset($_GET['id'])) {
  $id = filter_var($_GET['id'], FILTER_SANITIZE_STRING);
}
$string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz';
$captcha = substr(str_shuffle($string), 0, 6);
$_SESSION[$id] = $captcha;
$image = imagecreatefrompng(dirname(__FILE__).'/background.png');
$colour = imagecolorallocate($image, 200, 240, 240);
$font = dirname(__FILE__).'/oswald.ttf';
$rotate = rand(-10, 10);
imagettftext($image, 18, $rotate, 28, 32, $colour, $font, $captcha);
header('Content-type: image/png');
imagepng($image);

Данный файл выполняет следующие действия:

  • инициализацию системы MODX (это необходимо сделать для того, чтобы сгенерированный код капчи можно было использовать в элементах этой системы, например, в сниппетах);
  • генерирование защитного кода и его регистрация в сессионной переменной;
  • представление сгенерированного кода в виде изображения (в формате PNG).

Данный скрипт позволяет использовать несколько капч на одной странице. Это осуществляется посредством вызова данного файла с параметром id. В качестве значения ключа id необходимо указать имя CAPTCHA (например, captcha1).

Применение капчи для защиты FormIt формы

Пример графической капчи, с помощью которой осуществляется защита HTML формы от спама
Пример графической капчи, с помощью которой осуществляется защита HTML формы от спама

Для обработки формы будем использовать дополнение (сниппет) FormIt в сочетании с AjaxForm. Сниппет AjaxForm необходим для того, чтобы обеспечить работу с FormIt через AJAX. Более подробно изучить работу сниппетов FormIt и AjaxForm можно в статье Как в MODX Revolution создать AJAX форму обратной связи.

Для внедрения капчи в форму необходимо выполнить следующие действия:

  • создать кастомный валидатор для FormIt;
  • назначить проверку кода капчи этому валидатору;
  • добавить в HTML форму элементы (img - изображение капчи, input - поле для ввода разгаданного кода);
  • вставить на страницу JavaScript сценарий, который будет обновлять код капчи.

Создание пользовательского валидатора для FormIt

Для проверки того правильно или нет пользователь решил капчу напишем кастомный валидатор (например, имеющий имя isCaptchaValid):

<?php
$success = true;
if (empty($value)) {
  $validator->addError($key,'Вы не ввели капчу!');
  $success = false;
} else {
  if (isset($_SESSION[$key])) {
    if (!($_SESSION[$key] == $value)) {
      $validator->addError($key,'Вы ввели неправильный код капчи!');
      $success = false;
    }
  } else {
    $validator->addError($key,'Произошла ошибка при проверки кода капчи!');
    $success = false;
  }
}
return $success;

Добавление капчи в HTML-форму

Вставить в HTML-форму, указанную в параметре form сниппета AjaxForm, следующий код капчи:

<form action="" method="post" class="ajax_form af_example">
  ...
  <!-- Капча -->
  <div class="captcha">
    <img class="img-captcha" src="assets/captcha/captcha.php?rnd=[[!RandomNumber]]" data-src="assets/captcha/captcha.php" height="46" width="132" style="display:inline-block;">
    <div class="btn btn-default refresh-captcha"><i class="glyphicon glyphicon-refresh"></i> Обновить</div>
    <div class="form-group">
      <label for="captcha" class="control-label">Пожалуйста, введите проверочный код:</label>
      <div class="controls">
        <input id="captcha" name="captcha" type="text" class="form-control" required="required" value="" minlength="6" maxlength="6" autocomplete="off">
        <span class="error_captcha"></span>
      </div>
    </div>
  </div>
  ...
</form>

Сниппет randomNumber предназначен для добавления случайного GET параметра (текущей метки времени с микросекундами) к URL. Это действие необходимо сделать для того, чтобы браузер не кэшировал ответ файла captcha.php.

Пример сниппета RandomNumber:

<?php
return round(microtime(true)*1000);

Вызов сниппета AjaxForm с проверкой капчи

Добавить в вызов сниппета AjaxForm (FormIt):

  • параметр customValidators со значением isCaptchaValid;
  • проверку поля CAPTCHA с помощью валидатора isCaptchaValid (параметр validate).
[[!AjaxForm?
  &snippet=`FormIt` 
  ..
  &customValidators=`isCaptchaValid`
  &validate=`...,captcha:isCaptchaValid`
  ...
]]

Пример вызова сниппета AjaxForm с проверкой капчи:

[[!AjaxForm? 
  &snippet=`FormIt` 
  &form=`tpl.AjaxForm ` 
  &hooks=`email`
  &emailSubject=`Тема письма`
  &emailTo=`to@email.ru`
  &emailFrom=`no-reply@mysite.ru`
  &emailFromName=`Мой сайт`
  &emailTpl=`tpl.email `
  &customValidators=`isCaptchaValid`
  &validate=`name:required,email:required,message:required,captcha:isCaptchaValid`
  &validationErrorMessage=`Пожалуйста, исправьте ошибки!`
  &successMessage=`Ваше сообщение успешно отправлено!`
]]

JavaScript сценарий для обновления кода капчи

Вставить на страницу сценарий JavaScript, который будет обновлять код капчи в форме:

  • после получения ответа от сервера;
  • при нажатию на кнопку "Обновить".
$(function() {
  var refreshSrcCapctha = function(img) {
    var src = img.attr('data-src');
    if (src.indexOf('?id') !== -1) {
      src += '&rnd='+(new Date()).getTime();
    } else {
      src += '?rnd='+(new Date()).getTime();        
    }
    img.attr('src',src);
  }
  $(document).on('af_complete', function(event, response) {
    var form = response.form;
    if (form.find('.captcha').length == 1) {
      var img = form.find('.captcha .img-captcha');
      refreshSrcCapctha(img);
      form.find('.captcha input').val('');
    }
  });
  $('.refresh-captcha').click(function(){
    var img = $(this).closest('.captcha').find('.img-captcha');
    refreshSrcCapctha(img);
  });
});

Пример использования нескольких капч на одной странице

Для того чтобы на одной странице можно было использовать несколько капч необходимо в каждую HTML форму внести следующие изменения:

  • добавить в атрибуты src и data-src изображения id капчи;
  • установить в качестве значения атрибута name элемента input (в который пользователь вводит проверочный код) id капчи;
  • изменить класс элемента span, который применяется для отображения ошибки так, чтобы он соответствовал значению атрибуту name элемента input.

Пример капчи для первой HTML формы (tpl.AjaxForm1):

<form action="" method="post" class="ajax_form af_example">
  ...
  <!-- Капча -->
  <div class="captcha">
    <img class="img-captcha" src="assets/captcha/captcha.php?id=captcha1&rnd=[[!RandomNumber]]" data-src="assets/captcha/captcha.php?id=captcha1" height="46" width="132" style="display:inline-block;">
    <div class="btn btn-default refresh-captcha"><i class="glyphicon glyphicon-refresh"></i> Обновить</div>
    <div class="form-group">
      <label for="captcha1" class="control-label">Пожалуйста, введите проверочный код:</label>
      <div class="controls">
        <input id="captcha1" name="captcha1" type="text" class="form-control" required="required" value="" minlength="6" maxlength="6" autocomplete="off">
        <span class="error_captcha1"></span>
      </div>
    </div>
  </div>
  ...
</form>

Пример капчи для второй HTML формы (tpl.AjaxForm2):

<form action="" method="post" class="ajax_form af_example">
  ...
  <!-- Капча -->
  <div class="captcha">
    <img class="img-captcha" src="assets/captcha/captcha.php?id=captcha2&rnd=[[!RandomNumber]]" data-src="assets/captcha/captcha.php?id=captcha2" height="46" width="132" style="display:inline-block;">
    <div class="btn btn-default refresh-captcha"><i class="glyphicon glyphicon-refresh"></i> Обновить</div>
    <div class="form-group">
      <label for="captcha2" class="control-label">Пожалуйста, введите проверочный код:</label>
      <div class="controls">
        <input id="captcha2" name="captcha2" type="text" class="form-control" required="required" value="" minlength="6" maxlength="6" autocomplete="off">
        <span class="error_captcha2"></span>
      </div>
    </div>
  </div>
  ...
</form>

Пример вызова сниппета AjaxForm для первой HTML формы:

[[!AjaxForm? 
  &snippet=`FormIt` 
  &form=`tpl.AjaxForm1` 
  &hooks=`email`
  &emailSubject=`Тема письма`
  &emailTo=`to@email.ru`
  &emailFrom=`no-reply@mysite.ru`
  &emailFromName=`Мой сайт`
  &emailTpl=`tpl.email `
  &customValidators=`isCaptchaValid`
  &validate=`name:required,email:required,message:required,captcha1:isCaptchaValid`
  &validationErrorMessage=`Пожалуйста, исправьте ошибки!`
  &successMessage=`Ваше сообщение успешно отправлено!`
]]

Пример вызовы сниппета AjaxForm для второй HTML формы:

[[!AjaxForm? 
  &snippet=`FormIt` 
  &form=`tpl.AjaxForm2` 
  &hooks=`email`
  &emailSubject=`Тема письма`
  &emailTo=`to@email.ru`
  &emailFrom=`no-reply@mysite.ru`
  &emailFromName=`Мой сайт`
  &emailTpl=`tpl.email `
  &customValidators=`isCaptchaValid`
  &validate=`name:required,email:required,message:required,captcha2:isCaptchaValid`
  &validationErrorMessage=`Пожалуйста, исправьте ошибки!`
  &successMessage=`Ваше сообщение успешно отправлено!`
]]

Комментарии:

  1. Сергей
    01 февраля 2020, 13:41
    Добрый день.
    Почему то при использовании двух капчей не появляются сообщения об отправке в правом верхнем углу.
    Ошибок в консоли нет.
    Установка .jGrowl еще дополнительных пары 99 не помогла. При этом сообщения отправляются.
    И, если сообщение уходит со страницы где одна форма, то сообщение есть, если со страницы где две формы, то нет.
    1. Александр Мальцев
      01 февраля 2020, 15:39
      Здравствуйте! Проверил решение, приведённое в этой статье на MODX 2.7.2. Всё отлично.
      Что-то дополнительное править не нужно. Проверьте ответ, который вам присылает сервер (action.php). Данный ответ можно посмотреть на вкладке Network в браузере.
    2. Евгений
      08 июля 2017, 14:06
      Здравствуйте. Подскажите, куда размещать файл java script для обновления капчи? Спасибо.
      1. Евгений
        08 июля 2017, 13:35
        Здравствуйте, Александр. Спасибо за Ваш огромный труд, он многим помогает. Подскажите, пожалуйста, как добавить скрипт для обновления капчи? Получилось все, вот только не обновляется. Если можно, то подробнее укажите место, в котором разместить скрипт. Спасибо.
        1. Александр Мальцев
          08 июля 2017, 17:00
          Здравствуйте, спасибо. Лучше данный код добавить в свой файл скриптов JavaScript, который необходимо подключить после библиотеки jQuery.
          1. Евгений
            08 июля 2017, 19:20
            Спасибо.
        2. Evgenij
          23 мая 2017, 15:45
          Да, спасибо, разобрался. Координаты надо было корректировать и размер шрифта. И у меня там еще по своему классы стояли, видимо нельзя было менять класс общего div-а.
          1. Evgenij
            23 мая 2017, 15:02
            Вот что выдает консоль, это после нажатия на кнопку обновить:


            1. Александр Мальцев
              23 мая 2017, 15:59
              Может у вас ещё какие-то изображения в этом блоке есть. Немного изменил скрипт (выбор капчи происходит по классу img-captcha).
              1. Evgenij
                23 мая 2017, 16:08
                кроме этого изображения других изображений не было. чуть позже посмотрю как со второй формой будет. они в модальных окнах идут, одним потоком. после тех ошибок вставил html «один в один» как в примере и js-ка подхватила все что нужно. Так вроде работает. Посмотрел 2-ую модалку, работает! Обновляется.
            2. Evgenij
              23 мая 2017, 14:49
              Александр, здравствуйте!
              Вроде все по вашему сделал, но что то все же пошло не так. Получилось вот что:

              То есть буквы не вмещаются, и по кнопке не происходит обновления. Пока еще посмотрю, но пока вот так. В чем может быть дело?
              1. Александр Мальцев
                23 мая 2017, 15:42
                Добрый день. Параметры капчи подкорректировал, а то они были выставлены для более высокого разрешения картинки.
                1. Evgenij
                  23 мая 2017, 15:48
                  К слову. Почему то ссылка на Яндекс.Диск не работает. Перенаправляет на вашу главную страницу и все… (
                  1. Александр Мальцев
                    23 мая 2017, 16:05
                    Спасибо, исправил.
              2. Evgenij
                18 мая 2017, 07:51
                Александр! СПАСИБО!!! Увидел. Наконец то. Все по полочкам разложено. И все таки я был прав, что валидация по своему делается)
                Войдите, пожалуйста, в аккаунт, чтобы оставить комментарий.