Примеры реализации форм
Для реализации форм связи на сайтах под управлением Modx Revo мы всегда используем комбинацию двух пакетов - docs.modx.com/3.x/ru/extras/formit и docs.modx.pro/komponentyi/ajaxform для того что бы интегрировать данные в CMS
Пример вызова сниппета
[[!AjaxForm? &snippet=`Formit` &form=`callback.tpl` &hooks=`spam,csrfhelper_formit,email,FormItSaveForm` &csrfKey=`callback` &emailTpl=`callback.email` &emailTo=`[[++emailsender]]` &emailFrom=`noreply@[[#SERVER.SERVER_NAME]]` &emailFromName=`[[++site_name]]` &emailSubject=`[ЗАЯВКА] Обратный звонок` &formFields=`name,phone,message,pagetitle,link,referrer` &fieldNames=`name==Имя,phone==Телефон,message==Сообщение,pagetitle==Страница,link==Ссылка,referrer==Реферрер` &formName=`[ЗАЯВКА] Обратный звонок` &validationErrorMessage=`В форме содержатся ошибки!` &successMessage=`Сообщение успешно отправлено` &validate=`mail:blank,jsready:contains=^jsready^` ]]
Пример чанка формы
<form action="" method="post" autocomplete="disable" enctype="multipart/form-data" class="formWatcher"> <div class="fields flex flex-column gap10"> <div class="line"> <div class="field"> <input autocomplete="disable" type="text" placeholder="Ваше имя *" name="name" pattern="^[A-Za-zА-Яа-яЁё-]+$" title="Введите имя (только буквы)" required> </div> <span class="error">[[+fi.error.name]]</span> </div> <div class="line"> <div class="field"> <input autocomplete="disable" type="text" name="email" placeholder="Email *" required> </div> <span class="error">[[+fi.error.email]]</span> </div> <div class="line"> <div class="field"> <input autocomplete="disable" type="tel" maxlength="16" name="phone" placeholder="+7 (000) 000 00 00" inputmode="tel" pattern="^\(?\+?[\d\(\-\s\)]+$" title="Введите телефон в формате +7 (000) 000 00 00" required> </div> <span class="error">[[+fi.error.phone]]</span> </div> <div class="line"> <div class="field"> <textarea name="message" rows="3" placeholder="Сообщение"></textarea> </div> <span class="error">[[+fi.error.message]]</span> </div> <div class="input-file"> <label class="input-file__label"> <input hidden type="file" name="uploads[]" data-id="input-file"> <p class="input-file__title">Прикрепить файлы</p> <p class="input-file__file-extencion">В формате: .jpg, .png, .jpeg, .webp, .doc, .docx, .pdf, .excel, .txt</p> <p class="input-file__desc">Выберите или перетащите файлы</p> </label> <div class="input-file__info"> <div class="input-file__success"> <p>Загружено файлов: <span data-id="files-count">0</span> </p> </div> <div class="input-file__files"></div> </div> <span class="error error_uploads">[[+fi.error.uploads]]</span> </div> </div> <div class="private-policy">Согласие на обработку <a href="[[~6]]" target="_blank">персональных данных</a>.</div> <div class="submit"> <button type="submit" class="btn flex flex-items-center gap5">Отправить</button> </div> <input type="text" name="mail" class="d-none"> <input type="text" name="jsready" class="d-none jsready"> <input type="hidden" name="pagetitle" value="[[*pagetitle]] (id: [[*id]])"> <input type="hidden" name="link" value="[[~[[*id]]]]"> <input type="hidden" name="ip" value="[[#SERVER.REMOTE_ADDR]]"> <input type="hidden" name="referrer" value="[[#SERVER.HTTP_REFERER]]"> <input type="hidden" name="agent" value="[[#SERVER.HTTP_USER_AGENT]]"> <input type="hidden" name="csrf_token" value="[[!csrfhelper? &key=`callback`]]"> </form>
Шаблон письма
<b>Форма:</b> [[+formName]]<br/> <b>Имя:</b> [[+name]]<br/> <b>Email:</b> [[+email]]<br/> <b>Телефон:</b> [[+phone]]<br/> <b>Сообщение:</b> [[+message]]<br/> <hr> <b>Страница:</b> [[+pagetitle]]<br/> <b>Ссылка:</b> [[++site_url]][[+link]]<br/> <b>Реферрер:</b> [[+referrer]]<br/> <b>IP:</b> [[+ip]]<br/> <b>Браузер:</b> [[+agent]]<br/>
Загрузка файлов через форму
Для отправки формы с вложениями необходимо указать для неё метод кодирования данных enctype="multipart/form-data"
Добавляем поле в чанк формы
<div class="form-group"> <label for="files">Прикрепить файлы</label> <input type="file" name="files[]" multiple="multiple"> <p class="error_files">[[+fi.error.files]]</p> </div>
Валидация файлов
Создадим сниппет formit2checkfiles
<?php // инициализируем переменную output, отвечающую за результат работы хука, со значением true $output = true; // разрешённые расширения файлов $allowedExt = array('jpg','png','jpeg','webp','doc','docx','pdf','excel','txt'); // максимальный размер файла 10мб $maxFileSize = 10485760; // если ассоциативный массив $_FILES[$keys] существует, то if(isset($_FILES[$key]["error"])) { // переберём все файлы (изображения) foreach ($_FILES[$key]["error"] as $fkey => $error) { // если ошибок не возникло, т.е. файл был успешно загружен на сервер, то... if ($error == UPLOAD_ERR_OK) { // имя файла $fileName = basename($_FILES[$key]['name'][$fkey]); // расширение файла $fileExt = mb_strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); // размер файла $fileSize = filesize($_FILES[$key]['tmp_name'][$fkey]); // проверка расширения файла if(!in_array($fileExt, $allowedExt)) { // файл имеет недопустимый тип $errorMsg = 'Файл ' . $fileName . ' имеет не разрешённый тип.'; $validator->addError($key, $errorMsg); $output = false; // возвращаем false break; } if($fileSize > $maxFileSize) { // файл имеет размер больше максимального $errorMsg = 'Файл '. $fileName .' имеет не разрешённый размер.'; $validator->addError($key,$errorMsg); $output = false; // возвращаем false break; } } else { // произошла ошибка при загрузке файла на сервер $errorMsg = 'Произошла ошибка при загрузке файла ' . $fileName .' на сервер.'; $validator->addError($key,$errorMsg); $output = false; // возвращаем false break; } } } return $output; ?>
В вызов формы добавляем валидацию
&customValidators=`formit2checkfiles` &validate=`files:formit2checkfiles`
JS callback
Существуют JS callback'и которые мы можем использовать, например наиболее востребованный:
/*After send forms*/ $(document).on("af_complete", function(event, response) { let form = response.form; if (form.attr('id') === 'discount-form-form') { form.hide(); $('.discount-form-success').show(); } if (form.attr('id') === 'callback-form') { form.hide(); $('.callback-form-success').show(); setTimeout(function(){ $('#callback .is-close').trigger('click'); }, 3000); } }); /***--- END ---***/
Исправление дыры в AjaxForm
При обновлении пакета или его переустановке фикс нужно вносить заново!
Начиная с мая 2024 года начали поступать заявки о спаме, стандартная защита от которого не помогает, оказалось что эксплуатировалась проблема в сессиях AjaxForm. Прежде чем создавать формы мы должна закрыть эту дыру:
- Скорректировать код в компоненте AjaxForms в файле "core/components/ajaxform/model/ajaxform/ajaxform.class.php":
В объявлении метода process (~ 102 строка) добавить в вызов метода handleFormIt переменную $action (~ 131 строка), что бы получилось:
$response = $this->handleFormIt($scriptProperties, $action);
Далее добавить в объявлении метода handleFormIt (~ 149 строка) добавить входной параметр $action:
public function handleFormIt(array $scriptProperties = array(), string $action = '')
В этом же методе в условии обработки успешного срабатывания(~ 175 строка) вписать удаление сессии для текущего значение $action(~ 179 строка):
} else { $message = isset($this->modx->placeholders[$plPrefix . 'successMessage']) ? $this->modx->placeholders[$plPrefix . 'successMessage'] : 'af_success_submit'; //начало вставки if (!empty($_POST)) { unset($_SESSION['AjaxForm'][$action]); } //конец вставки $status = 'success'; }
Или скачать исправленный файл если ваша версия AjaxForm 4.2.7-pl (последняя на момент написания статьи)
ajaxform.class.php - Создаем в корне сайта файл например clear.php
<?php define('MODX_API_MODE', true); header('Content-type: text/html; charset=utf-8'); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Cache-Control: post-check=0, pre-check=0', false); require $_SERVER['DOCUMENT_ROOT'] . '/index.php'; $modx->getService('error', 'error.modError'); $modx->setLogLevel(modX::LOG_LEVEL_INFO); $modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML'); unset($_SESSION['AjaxForm']) ?>
- Обращаемся к файлу что бы очистить сессии, после этого файл можно удалять.
Защита от спама без капчи
Мы будем комбинировать несколько разных методов, которые в сумме дают надежную защиту.
- Устанавливаем плагин extras.modx.com/package/csrfhelper
Во всех формах Formit дополняем сниппет вызова форм и чанк форм.
Для каждой формы используем уникальный key при вызове<!--Например в вызове--> &hooks=`spam,csrfhelper_formit,email,FormItSaveForm` &csrfKey=`callback` <!--Внутри формы --> <input type="hidden" name="csrf_token" value="[[!csrfhelper? &key=`callback`]]">
- Добавляем в параметр вызова формы &validate следующее:
&validate=`mail:blank,jsready:contains=^jsready^`
- В чанк формы добавляем input именно с таким типом и без линейных стилей
<input type="text" name="mail" class="d-none"> <input type="text" name="jsready" class="d-none jsready">
Класс d-none в данном случае отвечает за стили display: none; - В ваши скрипты добавляем следующий скрипт:
$('form').each(function() { let nameField = $(this).find('input[name="jsready"]'); if (nameField.length > 0) { $(nameField).val('jsready') } })
Почему не reCaptcha 2, reCaptcha 3, hCaptcha и т.д
Основные причины это - оптимизация, сложность и время интеграции.
В современном вебе формы должны быть “юзер френдли”, если пользователь встретит на пути к конверсии препятствие которое его раздражает, он может бросить форму
Это касается всех видимых защит, какого вида бы они не были, интеллект пользователей очень разный.
Если говорить о скрытых капчах, таких как например reCaptcha v3, то их алгоритм работы не идеален и проблема не в том, что они иногда пропускают спам, проблема гораздо страшнее - они иногда блокируют реальных пользователей!
Это было выяснено путём подключения собственных логов к формам и сравнения реальных отправленных данных и тех что дошли в итоге до отправки, есть серьезные расхождения, а это значит мы теряем клиентов как и в случае видимых защит!
Так же не стоит забывать про техническую сторону вопроса, подключение таких защит связано с получением ключей и другими весьма времязатратными и сложными операциями (особенно когда нужно грузить несколько защит на одной странице), а самое главное - они очень больно бьют по скорости и производительности. Как правило на сайте используется не одна и не две формы, иногда их количество может приближаться к двадцати. И для каждой формы на странице будет подгружаться капча, будет происходить множество реквестов к внешним серверам, каждый из которых занимает милисекунды, но в сумме они и размер скаченных файлов будут отнимать по пол секунды загрузки страницы, что в условиях современного веба крайне много!
Альтернативы
Т.к официальная поддержка AjaxForm прекращена есть смысл присмотреться к альтернативным пакетам которые имеют поддержку: