Статья, в которой разберем, как в 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=`Ваше сообщение успешно отправлено!`
]]