Как в MODX Revo избавиться от спама с FormIt, AjaxForm, FetchIt (без капч)

Избавляемся от спама с FormIt и AjaxForm (без капч) MODX Revo

Сегодня разберем пару методов борьбы со спамом в MODX Revo с форм на FormIt, AjaxForm, FetchIt без капч.

Один из самых действенных методов борьбы со спамом — поставить Google reCAPTCHA. Данный метод тоже не без изъянов, останавливаться на нем не буду. Опишу альтернативные способы борьбы со спамом без использования надоедливых капч.

Способ 1: использование хуков FormIt (spam и math)

hooks spam

В форму добавляем скрытый импут: <input type="hidden" name="nospam" value="">

В вызове сниппета в hooks добавляем spam (обязательно перед хуком email), и в validate добавляем nospam:blank, пример:

...
&hooks=`spam,email`
&validate=`nospam:blank, name:required`
...

(hooks math)

В вызове сниппета в hooks добавляем math (обязательно первым — иначе не работает толком), и в validate добавляем math:required, пример:

... 
&hooks=`math,spam,email` 
&validate=`math:required, nospam:blank, name:required` 
...

Далее в форму добавляем следующие поля:

<label>[[!+fi.op1]] [[!+fi.operator]] [[!+fi.op2]]?</label>
[[!+fi.error.math]]
<input type="text" name="math" value="[[!+fi.math]]">
<input type="hidden" name="op1" value="[[!+fi.op1]]">
<input type="hidden" name="op2" value="[[!+fi.op2]]">
<input type="hidden" name="operator" value="[[!+fi.operator]]">

Либо label вообще лучше вывести вот так:

<label>[[!+fi.op1]] [[!+fi.operator:is=`-`:then=`минус`:else=`плюс`]] [[!+fi.op2]]?</label>

Способ 2: использование своих скрытых полей

Так как боты стали очень умными, нет смысла добавлять input type="hidden" , тип импута должен быть нормальным: text или email.

Вариация 1.

Добавляем в форму строку <input type="text" class="s-message" name="s-message" value="">, скрываем ее через css .s-message{display:none}

и добавляем в вызов, в параметр validate: &validate=`s-message:blank`

Вариация 2 (под AjaxForm).

Добавляем в форму строку <input type="text" class="n-message" name="n-message" value="">, скрываем ее через более сложный css, например:

input[name="n-message"] {
    display: block;
    width: 5px;
    height: 3px;
    margin-bottom: -3px;
    opacity: 0.01;
}

Далее создаем сниппет checkSpam:

<?php
if ($_POST['n-message']) { // проверяем наше поле на пустоту
    echo $AjaxForm->success('Ваше сообщение отправлено');
    die();
} else {
    return true;
}

и добавляем его в качестве хука перед email:

'hooks' => 'checkSpam,email',

Теперь если письмо не отправлено, спамеру всё равно будет показано сообщение об успехе.

Вариация 3 (под AjaxForm).

За частую спам-боты заполняют все поля и отключат js, по этому добавляем с помощью js в форму поле, и если это поле будет заполнено или отсутствовать, форма не отправиться

Создаем сниппет validate со следующим содержимым:

<?php
function text_error(){
    return false;
    die();
}
if(isset($_POST['org'])){
    if($hook->getValue('org')!=''){
            $modx->log(xPDO::LOG_LEVEL_ERROR, 'Ошибка заполнения формы: не пустое поле антиспама');
            $modx->log(xPDO::LOG_LEVEL_ERROR, print_r($_POST, 1));
            text_error();
    }else{
        return true;
    }
}else{
    $modx->log(xPDO::LOG_LEVEL_ERROR, 'Ошибка заполнения формы: нет поля антиспама');
    $modx->log(xPDO::LOG_LEVEL_ERROR, print_r($_POST, 1));
    text_error();
}

В вызов формы хук validate — название выше созданного сниппета

&hooks=`validate, email, FormItSaveForm`

Ну и пишем небольшой скриптик на JavaScript (с jQuery)

$(function(){
    // Антиспам
    $('.ajax_form').append('<input type="text" name="org" value="" class="_org" style="visibility:hidden; height: 0; width: 0; padding: 0; border:none;"/>')
    // Антиспам х  
})

где .ajax_form — класс формы

Способ 3 — в основном от ручного спама ну и от части роботов

Суть в том что обычно спамеры пытаются отправить ссылку на какой-нибудь сайт, а реальные пользователи обычно просто задают вопрос, типа как сколько стоит, как купить,  и т.д. Поэтому первое — запрещаем ввод ссылок. Так же в сниппете предусмотрена проверка поля ввода телефона.

Создаем сниппет antispam со следующим содержимым

<?php
/*Сниппет, проверяющий содержание формы на признаки спама*/
//Определяем значения служебных переменных по умолчанию
$success=true;
$haserror=false;
//Массив запрещенных фрагментов строк
//Если в каком либо поле формы встретится один из ниже перечисленных фрагментов, то далее обрабатывать сообщение не будем
$forb=array(
'@',
'http',
'https',
'://',
'www'
);

//Пробегаем по массиву запрещенных фрагментов
foreach ($forb as $f){
    if($haserror==false){ //Если еще не встретился запрещенный фрагмент
        $haserror=strpos($value,$f); //Проверяем его в значении текущего проверяемого поля ввода
    }
}

/*Проверка телефона (если не нужна, просто закомментируйте строки 23-26)*/
if(substr_count($key,'phone')>0){ //Если проверке подвергается поле ввода телефона
    if($value!='' and strlen($value)<16){$haserror=1;} //для поля ввода телефона проверяем длину введенного значения (при использовании скрипта форматирования это будет строка из 16 символов всегда!)
}

//Если была обнаружена ошибка
if ($haserror!=false) {
  $validator->addError($key,'Недопустимое значение!');
  $modx->log(xPDO::LOG_LEVEL_ERROR,'Антиспам: IP='.$_SERVER['REMOTE_ADDR'].' СОДЕРЖАНИЕ '.$key.': '.$value);
  $success=false;
}
//Возвращаем результат работы валидатора
return $success;

Далее подключаем сниппет в formIt в виде кастомного валидатора

&customValidators=`antispam`

и прописывать валидацию к нужным полям. Как правило, это поле message.

&validate=`message:antispam`

Данный сниппет пишет содержание в лог на всякий случай.

Способ 4 — генерация ключа (для AjaxForm)

Создаем hidden-поле и записываем в него сгенерированный на сервере ключ, который кладем в сессию. Когда аякс с формой уйдет на сервер, происходит сверка значения из hidden-поля с тем, что лежит в сессии. В случае, если значения не равны — это спам, следовательно останавливаем отправку формы на почту.

<input type="hidden" name="message-key" value="[[!getMessageKey]]" />

где сниппет со следующим содержимым

if (!isset($_SESSION['spamProtectionMessageKey']) || empty($_SESSION['spamProtectionMessageKey'])){
    $_SESSION['spamProtectionMessageKey'] = time();
}
return $_SESSION['spamProtectionMessageKey'];
И создаем еще один снимппет - хук: chekSpamProtectionMessageKey, со следующим содержимым:
$messageKey = $hook->getValue('message-key');
if (!isset($_SESSION['spamProtectionMessageKey']) || empty($_SESSION['spamProtectionMessageKey'])
    || empty($messageKey) || (int)$messageKey !== (int)$_SESSION['spamProtectionMessageKey']){
    return false;
}
return true;
и добавляем вы вызов данный хук
&hooks=`spam,chekSpamProtectionMessageKey,email`

Способ 5 — ограничение на количество вводимых символов

В последнее время начали спамить практически в любые текстовые поля, например в поле имя (name) и для таких полей проще всего поставить ограничение на количество вводимых символов, например имя явно не может быть длиннее 15 символов, поэтому можно ограничить его так:

в параметр validate указать значение maxLength=^15^ — проверка, чтобы поле содержало не более 15 символов.

&validate=`name:maxLength=^15^`

Способ 6 -генерацией случайной строки

Вариация 1

  • Создаём сниппет randString
    <?php 
        $permitted_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $output = 'antispam-'.substr(str_shuffle($permitted_chars), 0, 16);
        return $output;
  • Добавляем в вызов формы, в параметр &validate
    nobot:contains=^[[randString]]^
  • Добавляем класс ajax_form форме.
  • Добавляем скрипт внизу сайта (обычно у меня это чанк [[$scripts]])
    <script>
        $(function(){
            $('.ajax_form').append('<input hidden name="nobot" value="[[randString]]">')
        });
    </script>

Вариация 2 (на fenom)

Все тоже самое, но еще и полю nobot (эта рандомная строка) присвоится только(!) при наведении на форму (а не как в вариации 1).

{set $secret = md5(rand(0,999999999))}
{'!AjaxForm' | snippet :[
    ....
    'secret' => $secret ,
    'validate' => 'vscval:contains=^'~$secret~'^'
    ....
]}
//В форме
<input type="hidden" name="vscval" style="display:none" data-vscval="{$_pls['secret']}">
//Js
function watchForms(){
    if(document.querySelector('form')){
        document.querySelectorAll('form').forEach((ff) => {
            ff.addEventListener('mousemove', (e) => {
                if(ff.querySelector('input[name="vscval"]')){
                    let inpSecret = ff.querySelector('input[name="vscval"]'),
                        inpSecretData = inpSecret.getAttribute('data-vscval');
                    if(inpSecret.value == ''){
                        inpSecret.value = inpSecretData;
                    }
                }
            });
        });
    }
}
watchForms()

Прочие способы

Немного полазив по гуглу, нашёл еще пару способов: 1, 2, 2 — собирать ip и блочить спамеров, установить пакеты: Akismet, SpamProtect (платный), CSRFHelper — по большому счету тоже самое что генерация случайной строчки, только в виде отдельного пакета, который далее разберем.

Что помогло пишите в сообщениях) А если еще способы, делитесь.

Поделиться с друзьями
Алексей

Веб-дизайнер и SEO оптимизатор. Занимаюсь созданием сайтов с 2010 года и их продвижение с 2012 года!

Оцените автора
( 5 оценок, среднее 5 из 5 )
Web-Revenue.ru
Добавить комментарий

  1. Владислав

    Не сталкивался с тем, что сниппет validate приводит к 500 ошибке после отправки сообщения? PHP 7.3 Если отключаю проверку validate в hook то все работает

    Ответить
    1. Алексей автор

      Давно им не пользуюсь (на modx 2 работал норм, когда писал), пользуюсь в основном методами 2, 5 и защита от CRF https://web-revenue.ru/modx-revo/csrfhelper

      Ответить
  2. ssan

    А как сделать в 3 способе, чтобы спаммер думал, что форма успешно отправлена, но по факту чтобы антиспам срабатывал ?

    Ответить
    1. Алексей автор

      Попробуйте заменить $success=false; на echo $AjaxForm->success(‘Ваше сообщение отправлено’);
      die();

      Ответить
  3. Александр

    Спасибо за статью, а как сделать так, чтобы если пользователь ввёл определённое значение (например номер телефона определённый) то форма бы не отправилась, а ему пришло уведомление об успешной отправке ?

    Ответить
  4. Аноним

    В конце 3-го способа — лишний текст из конца 2-го дублируется

    Ответить
    1. Голягин Алексей

      Поправил)

      Ответить
  5. Slavny

    Третий способ полностью блокирует все отправления. Видимо что-то с хуком, т.к. getMsKey сниппет отрабатывает и генерит число.

    Ответить
    1. Алексей автор

      Поправил 3 способ в статье, теперь должно работать

      Ответить