+ антиспам, ajax, Bootstrap
Обычная форма обратной связи, которую я использую везде.
В каждом-каждом проекте обязательно есть форма обратной связи. И вот этот кусочек функционала, который можно копипастить. Пара слов об особенностях именно этой реализации. Во-первых, это FormLister
. Во-вторых, здесь есть набор правил для защиты от спама, а также контролируется время на заполнение формы. Идеи не новые: запрещаем имени содержать латинские буквы и цифры; запрещаем в поле для сообщения вставлять все, что похоже на ссылку; не отправляем данные, если форму заполнили быстрее, чем за две секунды. В-третьих, предполагается использование Bootstrap, поэтому все классы заточены под него. В-четвертых, используется ajax
для отправки формы из всплывающего окошка.
Вот чанк (или часть шаблона, если вы - редиска), где описано всплывающее окошко, обычное для Bootstrap, и вызов чанка формы.
<!-- Modal -->
<div class="modal fade" id="feedbackModal" tabindex="-1" aria-labelledby="feedbackModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title" id="feedbackModalLabel">Записаться на консультацию</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div id="feedbackDiv">
{{feedbackTpl}}
</div>
</div>
</div>
</div>
</div>
А вот кусочек скрипта, который "слушает" submit
нашей формы, отправляет данные и получает ответ от FormLister
, а также проставляет время инициализации формы в скрытом поле для блокировки спама. Обратите внимание на то, что в этом скрипте можно отправить данные о достижении цели для Яндекс.Метрики.
var ajax = {
post: function(form_wrapper_id,form_id,query_key,success_callback){
//обёртка формы, айди формы, ключ запроса, код на успех
$.ajax({
type: 'post',
url: '/ajax.php?q=' + query_key,
data: $(form_id).serialize(),
success:success_callback
});
}
};
$(document).ready(function(){
var now = (Date.now ? Date.now() : new Date().getTime()) / 1000;
$('input[name="now"]').each(function(){
$(this).val(now);
});
$(document).on('submit', '#feedbackForm',function(e){
ajax.post(
'#feedbackDiv',
'#feedbackForm',
'feedback',
function(data){
//console.log(data);
$('#feedbackDiv').html(data);
//ym(XXXXXXXX, 'reachGoal', 'goal');
}
);
e.preventDefault();
});
});
Отправленные данные из формы обрабатываются сниппетом FormLister
, который вызывается в файле ajax.php
, расположенном в корне сайта. Вот пример этого файла:
<?php
define('MODX_API_MODE', true);
include_once("index.php");
$modx->db->connect();
if (empty ($modx->config)){
$modx->getSettings();
}
$modx->invokeEvent("OnWebPageInit");
if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')){
$modx->sendRedirect($modx->config['site_url']);
}
switch($_REQUEST['q']){
case 'feedback':
$result = $modx->runSnippet('FormLister', array(
'formid' => 'feedbackForm',
'formTpl' => 'feedbackTpl',
'prepare' => 'checkSpamTimeFL',
'formControls' => 'greeCheck,sendCheck',
'emptyFormControls' => '{"sendCheck":"0","agreeCheck":""}',
'successTpl' => 'feedbackSuccess',
'errorTpl' => '@CODE: <div class="invalid-feedback">[+message+]</div>',
'requiredClass' => 'is-invalid',
'errorClass' => 'is-invalid',
'to' => $modx->getConfig('email_mngr'),
'subject' => 'Запись на консультацию',
'reportTpl' => 'feedbackReport',
'ccSender' => '1',
'parseMailerParams' => '1',
'replyTo' => '[+email.value+]',
'autoSubject' => 'Запись на консультацию',
'ccSenderTpl' => 'feedbackCcReport',
'rules' => '{
"name":{
"required":"Пожалуйста, представьтесь.",
"matches":{
"params":"/^[^a-zA-Z0-9]+$/u",
"message":"Пожалуйста, представьтесь на русском языке."
}
},
"phone":{
"required":"Пожалуйста, укажите свой номер телефона для связи с вами.",
"phone":"Проверьте, пожалуйста, указанный номер телефона."
},
"!email":{
"email":"Проверьте, пожалуйста, указанный адрес электронной почты."
},
"!comment":{
"matches":{
"params":"/^(?:(?!\\w+\\.\\w+).)*$/",
"message":"Здесь нельзя отправлять ссылки. Извините за неудобства."
}
},
"agreeCheck":{
"required":"Требуется ваше согласие."
}
}'
));
echo $modx->parseDocumentSource($result);
exit();
break;
default:
$modx->sendForward($modx->config['error_page']);
break;
}
?>
Теперь о параметрах FormLister
.
&formid=`feedbackForm`
- в самой форме нужно скрытое поле formid
с этим же значением, + я ставлю такой же id
для самой формы.
&formTpl=`feedbackTpl`
- чанк с формой.
&prepare=`checkSpamTimeFL`
- вызов сниппета, в котором описана проверка времени для блокировки спама. Подробнее об этом будет ниже.
&formControls=`agreeCheck,sendCheck`
- список имен полей-чекбоксов и радиобаттонов, которые должны быть установлены.
&emptyFormControls=`{"sendCheck":"0","agreeCheck":""}`
- список имен полей-чекбоксов и радиобаттонов, которые есть в форме, могут быть установлены пользователем, а могут быть проигнорированы. Здесь мы задаем им значения по умолчанию.
&successTpl=`feedbackSuccess`
- чанк с сообщением пользователю об успешной отправке данных.
&errorTpl=`@CODE: <div class="invalid-feedback">[+message+]</div>`
- код вывода общего сообщения об ошибках.
&requiredClass=`is-invalid`
- класс ошибки для полей, которые обязательны к заполнению, но не заполнены.
&errorClass=`is-invalid`
- класс ошибки для полей, в которые введены некорректные данные.
&to=`[(email_mngr)]`
- здесь адрес администратора сайта. Я разделяю адрес отправителя по умолчанию и адрес администратора сайта. Почту админа храню в конфиге сайта, записываю ее туда с помощью плагина customSettings
.
&subject=`Запись на консультацию`
- тема письма для админа сайта.
&reportTpl=`feedbackReport`
- чанк с текстом письма админу.
&ccSender=`1`
- нужно отправлять письмо автоответчика пользователю.
&parseMailerParams=`1`
- разрешаем парсить отправленную форму на предмет почты пользователя - туда отправит своё письмо автоответчик.
&replyTo=`[+email.value+]`
- нужно отправлять письмо пользователю на его электронку, она живет в плейсхолдере [+email.value+]
.
&autoSubject=`Запись на консультацию`
- тема письма автоответчика. Выделю этот параметр отдельно, поскольку обычно темы отличаются формулировкой.
&ccSenderTpl=`feedbackCcReport`
- чанк с текстом письма автоответчика.
&rules
- правила проверки данных в полях формы, далее подробнее:
"name":{
"required":"Пожалуйста, представьтесь.",
"matches":{
"params":"/^[^a-zA-Z0-9]+$/u",
"message":"Пожалуйста, представьтесь на русском языке."
}
},
Поле для ввода имени обязательно к заполнению, и туда нельзя вписать латинские буквы или цифры.
"phone":{
"required":"Пожалуйста, укажите свой номер телефона для связи с вами.",
"phone":"Проверьте, пожалуйста, указанный номер телефона."
},
Поле для ввода номера телефона обязательно к заполнению, и данные обязаны быть номером телефона.
"!email":{
"email":"Проверьте, пожалуйста, указанный адрес электронной почты."
},
Адрес электронки для примера не обязателен, проверка данных в поле проходит, только если это поле заполнено. И данные в этом поле обязаны быть адресом электронной почты.
"!comment":{
"matches":{
"params":"/^(?:(?!\\w+\\.\\w+).)*$/",
"message":"Здесь нельзя отправлять ссылки. Извините за неудобства."
}
}
Поле для ввода сообщения не обязательно к заполнению, проверяется, если заполнено. Формально регулярное выражение проверяет отсутствие ссылок в этом поле. Фактически - запрещены конструкции с точкой. Например, фраза "Яндекс.Директ" тоже не пройдет эту проверку.
"agreeCheck":{
"required":"Требуется ваше согласие."
}
В свете последних тенденций в законодательстве стоит подстраховаться и требовать согласие с Политикой конфиденциальности как минимум. Здесь мы запрещаем отправку сообщений без установленной галки согласия.
Продолжая тему о защите от спама, расскажу про сниппет checkSpamTimeFL
. Я не являюсь автором идеи, оригинал сниппета нашла здесь. Я считаю, что для большинства моих проектов этот сниппет следует изменить. Поскольку намного чаще отправка форм происходит через ajax
, на страницу выводится обычно только чанк формы, а сам FormLister
вызывается при попытке обработать запрос с данными формы. То есть, значение времени инициализации формы нужно задавать отдельно от FormLister
. Я использую для этих целей скрытое поле now
в форме и JS
. Итак, код сниппета:
<?php
if ($FormLister->isSubmitted()){
$flag = false;
$now = microtime(true);
$da=floatval($FormLister->getField('now'));
if (($now - $da) > 2){
$flag = true;
}
$FormLister->setValid($flag);
}
Теперь посмотрим на чанк с формой.
<form id="feedbackForm" method="post">
<input type="hidden" name="formid" value="feedbackForm"/>
<input type="hidden" name="now" value="0"/>
<div class="form-group">
<label for="inputName">Имя *</label>
<input type="text" class="form-control [+name.classname+]" id="inputName" name="name" required="required" value="[+name+]">
[+name.error+]
</div>
<div class="form-row mb-3">
<div class="form-group col-md-6 mb-0">
<label for="inputPhone">Телефон *</label>
<input type="tel" class="form-control [+phone.classname+]" id="inputPhone" name="phone" required="required" aria-describedby="phoneHelpBlock" value="[+phone+]">
[+phone.error+]
</div>
<div class="form-group col-md-6 mb-0">
<label for="inputEmail">Email</label>
<input type="email" class="form-control [+email.classname+]" id="inputEmail" name="email" value="[+email+]">
[+email.error+]
</div>
<small id="phoneHelpBlock" class="form-text text-muted">Мы никому не передаем ваши контактные данные. Также мы не отправим вам рассылку, если вы не согласны ее получать. Ваш телефон и электронная почта используются только для связи с вами.</small>
</div>
<div class="form-group">
<label for="inputMessage">Пояснение</label>
<textarea class="form-control [+comment.classname+]" id="inputMessage" name="comment" aria-describedby="messageHelpBlock">[+comment+]</textarea>
[+comment.error+]
<small id="messageHelpBlock" class="form-text text-muted">Укажите здесь ваш вопрос и другие детали, которые вы считаете важными.</small>
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input [+agreeCheck.classname+]" type="checkbox" id="agreeCheck" required="required" value="1" name="agreeCheck">
<label class="form-check-label" for="agreeCheck">
Я принимаю <a href="..." target="_blank">Пользовательское соглашение</a> и согласен с <a href="..." target="_blank">Политикой конфиденциальности</a>.
</label>
[+agreeCheck.error+]
</div>
</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input [+sendCheck.classname+]" type="checkbox" id="sendCheck" value="1" name="sendCheck">
<label class="form-check-label" for="sendCheck">
Я согласен получать информационную рассылку для клиентов.
</label>
[+sendCheck.error+]
</div>
</div>
<button type="submit" class="btn btn-primary">Отправить</button>
</form>
Код формы достаточно нагляден, поэтому покажу, как выглядит кнопка, вызывающая модальное окно с формой.
<a class="btn btn-primary" href="#" role="button" data-toggle="modal" data-target="#feedbackModal">Обратная связь</a>
Ну и закончу на этом.