Алексей Костюк

Решено. Современная защищенная форма обратной связи на PHP 8, Fetch+Google Recaptcha 2. 4 варианта формы на все случаи жизни

Как-то раз я озадачился поиском решения универсальной формы обратной связи для внедрения на сайты клиентов. Просмотрев статьи в интернете мне стало грустно, потому как ничего толкового я не нашел.

Требования, которые я предъявляю к форме обратной связи

  1. Универсальность. Можно внедрить в любую CMS без плагинов. Wordpress одобряет.
  2. Адаптивность. Форма подстраивается под разные экраны.
  3. PHP не ниже 7.4.
  4. Современные скрипты Jquery, Fancybox последних версий и т.д.
  5. Возможность вывода во всплывающем окне по нажатию на кнопку.
  6. Безопасность. Валидация полей форм и CSFR токен.
  7. Защита от спама. Когда я вижу скрипты форм в интернете хочется плакать, когда предлагают код без защиты от спама. 2025 год на дворе всё-таки.
  8. Соблюдение требований законодательства РФ в области персональных данных.
  9. Маска телефона, чтобы пользователь не смог ошибиться с его вводом.

В этой статье я дам подробную инструкцию и готовые файлы для внедрения на ваш сайт.

Виды форм, которые я подробно опишу

  1. Статичная форма с именем и номером телефона.
  2. Статичная форма с именем, номером телефона и комментарием.
  3. Статичная форма с именем, комментарием, выбором вида связи (телефон, Email, whatsapp. telegram).
  4. Форма во всплывающем окне + окно благодарности после отправки формы.

Все формы будут с Google Recaptha 2, которая хорошо себя зарекомендовала в борьбе со спамом, но не защищает от него на 100%, увы. Статья не рассчитана для новичков только постигающих азы программирования, нужно уметь внедрять код на сайт и понимать его, также будет единый скрипт JS для всех форм и демо страница  показа как работают формы и возможность скачать полностью весь код в конце статьи.

Структура проекта

Подключение стилей и скриптов для работы форм

Между тегов <head> </head> я подключаю нужные скрипты и стили, в том числе Google Recaptcha и скрипт Fancybox для всплывающих окон.

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Статическая форма обратной связи с телефоном</title>
    <meta name="description" content="форма обратной связи">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Подключение скриптов и стилей форм начало -->
    <link rel="stylesheet" href="/forms/assets/css/fancybox.css"/>
    <link rel="stylesheet" href="/forms/assets/css/style.css"/>
    <script src="/forms/assets/js/jquery-3.7.1.min.js"></script>
    <script src="https://www.google.com/recaptcha/api.js?onload=initializeReCaptcha&render=explicit"></script>
    <script src="/forms/assets/js/fancybox.umd.js"></script>
    <script src="/forms/assets/js/scripts.js"></script>
    <!-- Подключение скриптов и стилей форм конец -->
</head>

Важный момент. Если у вас на сайте уже подключён скрипт jquery, то подключать его не нужно. Также соблюдайте порядок подключения скриптов и стилей. Предвижу проблему со стилями формы style.css. Они могут изменять другие стили на сайте. Проверяйте отображения и корректируйте свойства в файле, чтобы было ок.

Посмотрите как подключается скрипт капчи, с параметрами в конце ссылки, чтобы на одной странице корректно работали более 1 капчи.

<script src="https://www.google.com/recaptcha/api.js?onload=initializeReCaptcha&render=explicit"></script>

Статичная форма с именем и номером телефона

Самая распространенная форма, которая встречается на любом сайте.

HTML-код формы

<section class="static-form section">
	<div class="container">
		<h2 class="heading__title">Статичная форма обратной связи</h2>
		<div class="formArea"> <span class="quest-form__title">Оставить заявку на консультацию</span>
			<form id="contactFrm1" class="form" action="/forms/php/static_form_1.php" autocomplete="off" method="post">
				<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']['contactFrm1']; ?>">
				<fieldset class="ui-field">
					<input name="name-1" type="text" id="username-1" class="ui-input" placeholder="Ваше имя*" tabindex="0" />
					<div class="error-message" id="name-error-1"></div>
				</fieldset>
				<fieldset class="ui-field">
					<input type="text" name="telephon-1" class="mask ui-input" placeholder="Номер телефона*" data-error-id="phone-error-1">
					<div class="error-message" id="phone-error-1"></div>
				</fieldset>
				<div id="contactFrm1-recaptcha" class="g-recaptcha"></div>
				<div class="error-message" id="captcha-error-1"></div>
				<div class="consform-block">
					<label class="custom-checkbox-label">
						<input type="checkbox" class="simple-consent-checkbox" name="pravila" value="agree" title> 
 					    <span> Нажимая на кнопку, вы даете согласие на обработку своих персональных данных и соглашаетесь с
					    <a href="#" data-fancybox data-src="#pravila"> Пользовательским соглашением</a></span> 
 				    </label>
				</div>
				<div class="error-message" id="pravila-error-1"></div>
				<div class="form-footer">
					<button type="submit" class="formBtn">Получить консультацию</button>
					<div class="response"></div>
				</div>
			</form>
		</div>
	</div>
</section>

Подготовка страницы сайта для работы формы

У меня создан для всех форм файл index.php, где в верхней части я генерирую CSRF токен для каждой формы и запускаю сессию.

<?php
session_start();
if (!isset($_SESSION['csrf_token']) || !is_array($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = [];
}
$formNames = ['contactFrm1', 'contactFrm2', 'contactFrm3', 'contactFrm4'];
foreach ($formNames as $formName) {
    if (empty($_SESSION['csrf_token'][$formName])) {
        $_SESSION['csrf_token'][$formName] = bin2hex(random_bytes(32));
    }
}
?>

Если у вас в CMS или фреймворке уже запускается сессия на страницах, то добавлять в верхнюю часть session_start(); не нужно!

В переменную $formNames я создал массив из 4 форм, которые соответствуют их id:

<form id="contactFrm1" class="form" action="/forms/php/static_form_1.php" autocomplete="off" method="post">
<form id="contactFrm2" class="form" action="/forms/php/static_form_2.php" autocomplete="off" method="post">
<form id="contactFrm3" class="form" action="/forms/php/static_form_3.php" autocomplete="off" method="post">
<form id="contactFrm4" class="form" action="/forms/php/modalform.php" autocomplete="off" method="post">

В атрибуте action указан путь до файлов, если у вас другой путь, то нужно скорректировать!

Вы можете изменить на свои id, которые прописали в теге form.

Далее я запускаю цикл и генерирую для каждой формы CSRF токен, который нужен для защиты форм:

foreach ($formNames as $formName) {
    if (empty($_SESSION['csrf_token'][$formName])) {
        $_SESSION['csrf_token'][$formName] = bin2hex(random_bytes(32));
    }
}
Если у вас в CMS или фреймворке уже есть формирование CSRF токена, то добавлять его генерацию не нужно и вам нужно смотреть в документации как его получить.

Хочу обратить внимание, что у меня на 1 странице несколько форм и Google Recaptcha должна работать одновременно во всех формах. Поэтому я решил не давать вам код только для одной формы, а именно для всех и на одной странице. Довольно часто на сайтах присутствует более 2х форм, например всплывающая и статическая и чтобы они корректно работали с Google капчей нужна доработка скрипта, которая не такая простая. Мое решение поможет реализовать несколько форм на 1 странице, также как и одну форму вы без проблем интегрируете на ваш сайт.

— Алексей Костюк

На что в коде нужно обратить внимание?

Атрибуты id, name, value, data-error-id должны быть уникальные и если у вас на одной странице более 1 формы, то одинаковые атрибуты приведут к тому, что формы будут работать некорректно.
Атрибуты у нескольких форм должны быть с уникальными значениями. name="name-1", name="name-2", id="form-1", id="form-2" и т.д.

PHP обработчик формы

Файл static_form_1.php отвечает за обработку данных которые приходят с формы отправляет их вам на Email.

<?php
session_start();
$secretKey = 'ваш секретный код google recaptcha 2';
$toEmail = 'your-email@example.com';

$status = 0;
$statusMsg = 'Oops! Something went wrong! Please try again later.';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $phone = filter_var($_POST['telephon-1'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $name = filter_var($_POST['name-1'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $recaptchaResponse = $_POST['g-recaptcha-response'] ?? '';

    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']['contactFrm1']) {
        $statusMsg = 'Invalid CSRF token.';
    } elseif (empty($recaptchaResponse)) {
        $statusMsg = 'Please confirm you are not a robot.';
    } else {

        $url = 'https://www.google.com/recaptcha/api/siteverify';
        $data = [
            'secret' => $secretKey,
            'response' => $recaptchaResponse,
        ];

        $options = [
            'http' => [
                'method' => 'POST',
                'header' => 'Content-Type: application/x-www-form-urlencoded',
                'content' => http_build_query($data),
            ]
        ];

        $context = stream_context_create($options);
        $verify = file_get_contents($url, false, $context);
        $captcha_success = json_decode($verify, true);

        if (!($captcha_success['success'] ?? false)) {
            $statusMsg = 'Please confirm you are not a robot.';
        } else {
            // Validate form data
            if (!empty($phone) && !empty($name)) {
                $emailSubject = 'Заявка на консультацию';
                $htmlContent = "
                    <html>
                        <body>
                            <h2>Заявка на консультацию</h2>
                            <p><strong>Имя:</strong> {$name}</p>
                            <p><strong>Телефон:</strong> {$phone}</p>
                        </body>
                    </html>";

                $headers = "MIME-Version: 1.0\r\n";
                $headers .= "Content-type:text/html;charset=UTF-8\r\n";
                $headers .= "From: no-reply@example.com\r\n";

                if (mail($toEmail, $emailSubject, $htmlContent, $headers)) {
                    $status = 1;
                    $statusMsg = 'Thank you for your request! Our manager will contact you as soon as possible.';
                } else {
                    $statusMsg = 'Error sending email. Please try again later.';
                }
            } else {
                $statusMsg = 'Please fill in all form fields.';
            }
        }
    }
}

echo json_encode([
    'status' => $status,
    'message' => $statusMsg
]);
?>

В переменной $secretKey нужно указать ваш секретный ключ, полученный в Google Recaptcha 2. Надеюсь не нужно объяснять как получать ключи в админпанели Google Recaptcha?

Куда будут приходить письма?

В переменной $toEmail указываем почту куда должны будут приходить письма. 

$toEmail = 'your-email@example.com';
Когда я только начинал в разработке сайтов, то самая частая проблема была, что письма с форм не приходили. Почему так может происходить? На вашем сервере или хостинге нужно создать почтовый домен и  почтовый ящик вида info@вашдомен.ру. Если этого не сделать, то почта может не приходить с форм.

Вторая проблема. Письма приходят в спам, особенно на почтовые ящики mail.ru. На вашем почтовом домене должны быть активированы подписи DKIM, DMARC. Углубляться в эту тему не буду, обращайтесь в тех. поддержку хостинга, чтобы они вам корректно всё настроили, если возникнет такая проблема. Обязательно смотрите логи отправки почты - mail.log.

В PHP обработчике вам нужно также прописать почту от которой будет приходит письма. У меня указана почта вида:

$headers .= "From: no-reply@example.com\r\n";

Вы можете указать свою почту, но не почтовых сервисов yandex, mail и так далее, а именно на домене вашего сайта. Создавать no-reply@example.com не нужно на хостинге. Это просто технический адрес почты. 

Единый скрипт JS для всех форм

document.addEventListener("DOMContentLoaded", function () {
    const formsConfig = [
        {  // форма с именем и телефоном
            id: "contactFrm1",
            action: "/forms/php/static_form_1.php",
            recaptchaId: "contactFrm1-recaptcha",
            captchaErrorId: "captcha-error-1",
            fields: [
                { name: "name-1", error: "Введите ваше имя.", errorId: "name-error-1" },
                { name: "telephon-1", type: "phone", error: "Введите номер телефона.", errorId: "phone-error-1" },
                { name: "pravila", type: "checkbox", error: "Необходимо согласие.", errorId: "pravila-error-1" }
            ]
        },
        { // форма с комментарием
            id: "contactFrm2",
            action: "/forms/php/static_form_2.php",
            recaptchaId: "contactFrm2-recaptcha",
            captchaErrorId: "captcha-error-2",
            fields: [
                { name: "name-2", error: "Введите ваше имя.", errorId: "name-error-2" },
                { name: "telephon-2", type: "phone", error: "Введите номер телефона.", errorId: "phone-error-2" },
                { name: "comment", error: "Напишите комментарий.", errorId: "comment-error" },
                { name: "pravila-2", type: "checkbox", error: "Необходимо согласие.", errorId: "pravila-error-2" }
            ]
        },
        {  // форма с выбором вида связи
            id: "contactFrm3",
            action: "/forms/php/static_form_3.php",
            recaptchaId: "contactFrm3-recaptcha",
            captchaErrorId: "captcha-error-3",
            fields: [
                { name: "name-3", error: "Введите ваше имя.", errorId: "name-error-3" },
                { name: "contact-method", type: "radio", error: "Выберите способ связи.", errorId: "contact-method-error" },
                { name: "telephon-3", type: "conditional", condition: "phone", error: "Введите номер телефона.", errorId: "phone-error-3" },
                { name: "email-3", type: "conditional", condition: "email", error: "Введите ваш email.", errorId: "email-error-3" },
                { name: "whatsapp-3", type: "conditional", condition: "whatsapp", error: "Введите номер WhatsApp.", errorId: "whatsapp-error-3" },
                { name: "telegram-3", type: "conditional", condition: "telegram", error: "Введите номер Telegram.", errorId: "telegram-error-3" },
                { name: "pravila-3", type: "checkbox", error: "Необходимо согласие.", errorId: "pravila-error-3" }
            ]
        },
        {  // всплывающая форма
            id: "contactFrm4",
            action: "/forms/php/modalform.php",
            recaptchaId: "contactFrm4-recaptcha",
            captchaErrorId: "captcha-error-4",
            fields: [
                { name: "name-4", error: "Введите ваше имя.", errorId: "name-error-4" },
                { name: "telephon-4", type: "phone", error: "Введите номер телефона.", errorId: "phone-error-4" },
                { name: "pravila-4", type: "checkbox", error: "Необходимо согласие.", errorId: "pravila-error-4" }
            ]
        }
    ];

    const widgetIds = {}; // Для хранения ID виджетов reCAPTCHA

    // Валидация
    function validateFields(fields, form) {
        let isValid = true;
        fields.forEach((field) => {
            const input = form.querySelector(`[name="${field.name}"]`);
            const errorElement = document.getElementById(field.errorId);
            if (errorElement) errorElement.textContent = ""; // Сброс ошибок

            if (field.type === "checkbox" && input && !input.checked) {
                isValid = false;
                if (errorElement) errorElement.textContent = field.error;
            } else if (field.type === "conditional") {
                const selectedMethod = form.querySelector('[name="contact-method"]:checked');
                if (selectedMethod && selectedMethod.value === field.condition) {
                    if (!input || !input.value.trim()) {
                        isValid = false;
                        if (errorElement) errorElement.textContent = field.error;
                    }
                }
            } else if (input && !input.value.trim()) {
                isValid = false;
                if (errorElement) errorElement.textContent = field.error;
            }
        });
        return isValid;
    }

    // Обработчик формы
    function handleFormSubmit(formId, action, fields, recaptchaId, captchaErrorId) {
        const form = document.getElementById(formId);
        form.addEventListener("submit", function (e) {
            e.preventDefault();

            // Валидация полей
            if (!validateFields(fields, form)) return;

            // Получение ответа reCAPTCHA
            const recaptchaResponse = grecaptcha.getResponse(widgetIds[recaptchaId]);
            const captchaErrorElement = document.getElementById(captchaErrorId);

            if (captchaErrorElement) captchaErrorElement.textContent = ""; // Сброс ошибки капчи

            if (!recaptchaResponse) {
                if (captchaErrorElement) captchaErrorElement.textContent = "Подтвердите, что вы не робот.";
                return;
            }

            // Формируем данные для отправки
            const formData = new FormData(form);

            // Добавляем g-recaptcha-response только один раз
            if (!formData.has("g-recaptcha-response")) {
                formData.append("g-recaptcha-response", recaptchaResponse);
            }

            // AJAX-запрос
            fetch(action, { method: "POST", body: formData })
                .then((response) => response.json())
                .then((data) => {
                    if (data.status === 1) {
                        form.reset();
                        grecaptcha.reset(widgetIds[recaptchaId]);
                        if (formId === "contactFrm4") {
                            Fancybox.close(); // Закрыть всплывающее окно
                        }
                        Fancybox.show([{ src: "#success", type: "inline" }]);
                    } else {
                        alert(data.message || "Произошла ошибка на сервере.");
                    }
                })
                .catch(() => {
                    alert("Произошла ошибка при отправке формы.");
                });
        });
    }


    // Инициализация reCAPTCHA
    window.initializeReCaptcha = function () {
        formsConfig.forEach(({ recaptchaId }) => {
            const recaptchaElement = document.getElementById(recaptchaId);
            if (recaptchaElement) {
                try {
                    const widgetId = grecaptcha.render(recaptchaElement, {
                        sitekey: "Укажите ключ сайта из Google Recaptcha"
                    });
                    widgetIds[recaptchaId] = widgetId;
                } catch (error) {
                    console.error(`Failed to initialize reCAPTCHA for ${recaptchaId}:`, error);
                }
            } else {
                console.warn(`reCAPTCHA element not found: ${recaptchaId}`);
            }
        });
    };

    // Инициализация всех форм
    formsConfig.forEach(({ id, action, fields, recaptchaId, captchaErrorId }) => {
        handleFormSubmit(id, action, fields, recaptchaId, captchaErrorId);
    });

});
//маска телефона
window.addEventListener("DOMContentLoaded", function () {
    [].forEach.call(document.querySelectorAll(".mask"), function (input) {
        var keyCode;

        function mask(event) {
            event.keyCode && (keyCode = event.keyCode);
            var pos = this.selectionStart;
            if (pos < 3) event.preventDefault();
            var matrix = "+7 (___) ___ ____",
                i = 0,
                def = matrix.replace(/\D/g, ""),
                val = this.value.replace(/\D/g, ""),
                new_value = matrix.replace(/[_\d]/g, function (a) {
                    return i < val.length ? val.charAt(i++) || def.charAt(i) : a;
                });
            i = new_value.indexOf("_");
            if (i != -1) {
                i < 5 && (i = 3);
                new_value = new_value.slice(0, i);
            }
            var reg = matrix
                .substr(0, this.value.length)
                .replace(/_+/g, function (a) {
                    return "\\d{1," + a.length + "}";
                })
                .replace(/[+()]/g, "\\$&");
            reg = new RegExp("^" + reg + "$");
            if (!reg.test(this.value) || this.value.length < 17 || keyCode > 47 && keyCode < 58) {
                this.value = new_value;
            }
            // Проверка на длину введенного номера телефона
            const errorId = this.getAttribute("data-error-id");
            if (errorId) {
                const errorElement = document.getElementById(errorId);
                if (this.value.length < 17) {
                    errorElement.textContent = "Заполните поле телефон";
                } else {
                    // Очистка текста ошибки при успешном вводе
                    errorElement.textContent = "";
                }
            }
        }

        input.addEventListener("input", mask, false);
        input.addEventListener("focus", mask, false);
        input.addEventListener("blur", mask, false);
        input.addEventListener("keydown", mask, false);
    });
});


// Подключение Fancybox
document.addEventListener("DOMContentLoaded", function () {
    Fancybox.bind('[data-fancybox]', {
        autoFocus: false,
        trapFocus: true,
    });
});
// Показ и скрытие полей форм при клике на кнопку выбора связи
document.addEventListener("DOMContentLoaded", function () {
$('input[name="contact-method"]').change(function () {
    let selectedMethod = $(this).val();
    $('.contact-field').hide(); // Скрыть все поля
    $('#' + selectedMethod + '-field').show(); // Показать выбранное поле
});
});
// Закрытие всплывающей формы при клике на кнопку в виде крестика
$(document).on("click", ".close-btn", function () {
    Fancybox.close();
});

Важные моменты по скрипту:

1. Указать ключ site капчи, который вы получили в админпанели Google. Ранее в PHP скрипте мы указывали secret key, не путайте.

const widgetId = grecaptcha.render(recaptchaElement, {
  sitekey: "ваш ключ site"
});

2. Маска телефона срабатывает по классу mask. Можете указать другой класс, если нужно. 

[].forEach.call(document.querySelectorAll(".mask"), function (input) {

3. После успешной отправки должно всплывать модальное окно Fancybox, которое сигнализирует пользователю, что форма отправилась.

Код подключения fancybox в script.js:

document.addEventListener("DOMContentLoaded", function () {
    Fancybox.bind('[data-fancybox]', {
        autoFocus: false,
        trapFocus: true,
    });
});

Чтобы изменить текст всплывающего уведомления нужно найти в файле index.php внизу код:

<div id="success" style="display:none;max-width:500px;">
  <svg class="success-icon" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" x="0" y="0" viewBox="0 0 2.54 2.54" style="enable-background:new 0 0 512 512" xml:space="preserve" fill-rule="evenodd"><g><circle cx="1.27" cy="1.27" r="1.27" fill="#48b02c" opacity="1" data-original="#48b02c" class=""></circle><g fill="#fff"><path d="m.962 1.626.895-.895a.068.068 0 0 1 .096 0l.087.087a.068.068 0 0 1 0 .096l-.896.895a.068.068 0 0 1-.095 0l-.087-.087a.068.068 0 0 1 0-.096z" fill="#ffffff" opacity="1" data-original="#ffffff"></path><path d="m.683 1.08.545.546a.068.068 0 0 1 0 .096l-.086.086a.068.068 0 0 1-.096 0L.5 1.263a.068.068 0 0 1 0-.096l.087-.086a.068.068 0 0 1 .096 0z" fill="#ffffff" opacity="1" data-original="#ffffff"></path></g></g></svg>
  <h2>Форма успешно отправлена</h2>
  <p>Благодарим за ваше обращение! Мы перезвоним вам как можно скорее</p>
  <button class="close-btn">Закрыть</button>
</div>

Данный код вы можете разместить у себя, если это к примеру Wordpress, то в файле footer.php. В общем задача разместить его туда, где он будет подключаться на всех страницах сайта где есть формы.

Статичная форма с полем ввода комментария

По сути это такая же форма, что и предыдущая, но есть дополнительное поле ввода комментария.

HMTL формы

<section class="static-form section">
	<div class="container">
		<h2 class="heading__title">Статичная форма обратной связи с комментарием</h2>
		<div class="formArea"> <span class="quest-form__title">Оставить заявку на консультацию</span>
			<form id="contactFrm2" class="form" action="/forms/php/static_form_2.php" autocomplete="off" method="post">
				<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']['contactFrm2']; ?>">
				<fieldset class="ui-field">
					<input name="name-2" type="text" id="username" class="ui-input" placeholder="Ваше имя*" tabindex="0" />
					<div class="error-message" id="name-error-2"></div>
				</fieldset>
				<fieldset class="ui-field">
					<input type="text" name="telephon-2" class="mask ui-input" placeholder="Номер телефона*" data-error-id="phone-error-2">
					<div class="error-message" id="phone-error-2"></div>
				</fieldset>
				<fieldset class="ui-field">
					<textarea name="comment" class="comment" placeholder="Написать комментарий"></textarea>
					<div class="error-message" id="comment-error"></div>
				</fieldset>
				<div id="contactFrm2-recaptcha" class="g-recaptcha"></div>
				<div class="error-message" id="captcha-error-2"></div>
				<div class="consform-block">
					<label class="custom-checkbox-label">
						<input type="checkbox" class="simple-consent-checkbox" name="pravila-2" value="agree" title> <span> Нажимая на кнопку, вы даете
									согласие на обработку своих персональных данных и соглашаетесь с
									<a href="#" data-fancybox data-src="#pravila"> Пользовательским соглашением</a></span> </label>
				</div>
				<div class="error-message" id="pravila-error-2"></div>
				<div class="form-footer">
					<button type="submit" class="formBtn-2">Получить консультацию</button>
					<div class="response"></div>
				</div>
			</form>
		</div>
	</div>
</section>

PHP обработчик

Получение данных и проверка их происходит в файле static_form_2.php

<?php
session_start(); // Старт сессии

error_log(print_r($_POST, true));
$secretKey = 'Ваш секретный ключ Google Recaptcha 2';
$toEmail = 'Ваша почта';

$status = 0;
$statusMsg = 'Возникла ошибка на сервере!';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $phone = filter_var($_POST['telephon-2'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $name = filter_var($_POST['name-2'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $comment = filter_var($_POST['comment'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $recaptchaResponse = $_POST['g-recaptcha-response'] ?? '';

    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']['contactFrm2']) {
        $statusMsg = 'Неверный CSRF токен.';
    } elseif (empty($recaptchaResponse)) {
        $statusMsg = 'Пройдите проверка на робота.';
    } else {

        $url = 'https://www.google.com/recaptcha/api/siteverify';
        $data = [
            'secret' => $secretKey,
            'response' => $recaptchaResponse,
        ];

        $options = [
            'http' => [
                'method' => 'POST',
                'header' => 'Content-Type: application/x-www-form-urlencoded',
                'content' => http_build_query($data),
            ]
        ];

        $context = stream_context_create($options);
        $verify = file_get_contents($url, false, $context);
        $captcha_success = json_decode($verify, true);

        if (!($captcha_success['success'] ?? false)) {
            $statusMsg = 'Пройдите проверка на робота.';
        } else {

            if (!empty($phone) && !empty($name) && !empty($comment)) {
                $emailSubject = 'Запрос консультации';
                $htmlContent = "
                    <html>
                        <body>
                            <h2>Запрос на консультацию</h2>
                            <p><strong>Имя:</strong> {$name}</p>
                            <p><strong>Телефон:</strong> {$phone}</p>
                            <p><strong>Комментарий:</strong> {$comment}</p>
                        </body>
                    </html>";

                $headers = "MIME-Version: 1.0\r\n";
                $headers .= "Content-type:text/html;charset=UTF-8\r\n";
                $headers .= "From: no-reply@example.com\r\n";

                if (mail($toEmail, $emailSubject, $htmlContent, $headers)) {
                    $status = 1;
                    $statusMsg = 'Благодарим за обращение. Мы свяжемся с вами как можно скорее';
                } else {
                    $statusMsg = 'Возникла ошибка. Попробуйте отправить форму еще раз';
                }
            } else {
                $statusMsg = 'Пожалуйста заполните все поля формы.';
            }
        }
    }
}
echo json_encode([
    'status' => $status,
    'message' => $statusMsg
]);
?>

По сути здесь всё тоже самое, что и для первой формы.

  1. Указываем секретный ключ
  2. Меняем почту  и техническую почту на свою

Скрипт JS

Смотрите в первой форме - Единый скрипт JS для всех форм.

Статичная форма с выбором вида связи

Эта форма более интересная, так как даёт пользователю выбрать как связаться с ним и я предусмотрел 4 варианта - Телефон, Email, WhatsApp, Telegram.

HTML формы

<section class="static-form section">
	<div class="container">
		<h2 class="heading__title">Статичная форма обратной связи с выбором связи</h2>
		<div class="formArea"> <span class="quest-form__title">Оставить заявку на консультацию</span>
			<form id="contactFrm3" class="form" action="/forms/php/static_form_3.php" autocomplete="off" method="post">
				<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']['contactFrm3']; ?>">
				<fieldset class="ui-field">
					<input name="name-3" type="text" id="username-3" class="ui-input" placeholder="Ваше имя*" tabindex="0" />
					<div class="error-message" id="name-error-3"></div>
				</fieldset>
				<fieldset class="ui-field"> <span class="input-title">Удобный вид связи</span>
					<div class="contact-method">
						<label>
							<input type="radio" name="contact-method" value="phone" checked> <span class="custom-radio">
									<svg width="16" height="22" viewBox="0 0 16 22" fill="none" xmlns="http://www.w3.org/2000/svg">
										<path d="M11 1V2.4C11 2.96005 11 3.24008 10.891 3.45399C10.7951 3.64215 10.6422 3.79513 10.454 3.89101C10.2401 4 9.96005 4 9.4 4H6.6C6.03995 4 5.75992 4 5.54601 3.89101C5.35785 3.79513 5.20487 3.64215 5.10899 3.45399C5 3.24008 5 2.96005 5 2.4V1M4.2 21H11.8C12.9201 21 13.4802 21 13.908 20.782C14.2843 20.5903 14.5903 20.2843 14.782 19.908C15 19.4802 15 18.9201 15 17.8V4.2C15 3.07989 15 2.51984 14.782 2.09202C14.5903 1.71569 14.2843 1.40973 13.908 1.21799C13.4802 1 12.9201 1 11.8 1H4.2C3.0799 1 2.51984 1 2.09202 1.21799C1.71569 1.40973 1.40973 1.71569 1.21799 2.09202C1 2.51984 1 3.0799 1 4.2V17.8C1 18.9201 1 19.4802 1.21799 19.908C1.40973 20.2843 1.71569 20.5903 2.09202 20.782C2.51984 21 3.07989 21 4.2 21Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
									</svg>
									Телефон
								</span> </label>
						<label>
							<input type="radio" name="contact-method" value="email"> <span class="custom-radio">
										<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
                                        <path d="M1 4L9.16492 9.71544C9.82609 10.1783 10.1567 10.4097 10.5163 10.4993C10.8339 10.5785 11.1661 10.5785 11.4837 10.4993C11.8433 10.4097 12.1739 10.1783 12.8351 9.71544L21 4M5.8 17H16.2C17.8802 17 18.7202 17 19.362 16.673C19.9265 16.3854 20.3854 15.9265 20.673 15.362C21 14.7202 21 13.8802 21 12.2V5.8C21 4.11984 21 3.27976 20.673 2.63803C20.3854 2.07354 19.9265 1.6146 19.362 1.32698C18.7202 1 17.8802 1 16.2 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V12.2C1 13.8802 1 14.7202 1.32698 15.362C1.6146 15.9265 2.07354 16.3854 2.63803 16.673C3.27976 17 4.11984 17 5.8 17Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                                        </svg>

										Email
									</span> </label>
						<label>
							<input type="radio" name="contact-method" value="whatsapp"> <span class="custom-radio">
										<svg width="16" height="22" viewBox="0 0 16 22" fill="none" xmlns="http://www.w3.org/2000/svg">
											<path d="M11 1V2.4C11 2.96005 11 3.24008 10.891 3.45399C10.7951 3.64215 10.6422 3.79513 10.454 3.89101C10.2401 4 9.96005 4 9.4 4H6.6C6.03995 4 5.75992 4 5.54601 3.89101C5.35785 3.79513 5.20487 3.64215 5.10899 3.45399C5 3.24008 5 2.96005 5 2.4V1M4.2 21H11.8C12.9201 21 13.4802 21 13.908 20.782C14.2843 20.5903 14.5903 20.2843 14.782 19.908C15 19.4802 15 18.9201 15 17.8V4.2C15 3.07989 15 2.51984 14.782 2.09202C14.5903 1.71569 14.2843 1.40973 13.908 1.21799C13.4802 1 12.9201 1 11.8 1H4.2C3.0799 1 2.51984 1 2.09202 1.21799C1.71569 1.40973 1.40973 1.71569 1.21799 2.09202C1 2.51984 1 3.0799 1 4.2V17.8C1 18.9201 1 19.4802 1.21799 19.908C1.40973 20.2843 1.71569 20.5903 2.09202 20.782C2.51984 21 3.07989 21 4.2 21Z"  stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
										</svg>
										WhatsApp
									</span> </label>
						<label>
							<input type="radio" name="contact-method" value="telegram"> <span class="custom-radio">
										<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
                                        <path d="M9.49952 12.5002L19.9995 2.00017M9.6271 12.8282L12.2552 19.5862C12.4867 20.1816 12.6025 20.4793 12.7693 20.5662C12.9139 20.6415 13.0862 20.6416 13.2308 20.5664C13.3977 20.4797 13.5139 20.1822 13.7461 19.5871L20.3364 2.69937C20.5461 2.16219 20.6509 1.8936 20.5935 1.72197C20.5437 1.57292 20.4268 1.45596 20.2777 1.40616C20.1061 1.34883 19.8375 1.45364 19.3003 1.66327L2.41258 8.25361C1.8175 8.48584 1.51997 8.60195 1.43326 8.76886C1.35809 8.91354 1.35819 9.08579 1.43353 9.23039C1.52043 9.39719 1.81811 9.51296 2.41345 9.74448L9.17146 12.3726C9.29231 12.4196 9.35273 12.4431 9.40361 12.4794C9.44871 12.5115 9.48815 12.551 9.52031 12.5961C9.55661 12.647 9.58011 12.7074 9.6271 12.8282Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                                        </svg>

										Telegram
									</span> </label>
					</div>
				</fieldset>
				<div id="phone-field" class="contact-field">
					<input type="text" name="telephon-3" class="mask ui-input" placeholder="Номер телефона*" data-error-id="phone-error-3">
					<div class="error-message" id="phone-error-3"></div>
				</div>
				<div id="email-field" class="contact-field" style="display: none;">
					<input type="email" name="email-3" class="ui-input" placeholder="Ваш Email*">
					<div class="error-message" id="email-error-3"></div>
				</div>
				<div id="whatsapp-field" class=" contact-field" style="display: none;">
					<input type="text" name="whatsapp-3" class="mask ui-input" placeholder="Ваш номер WhatsApp*" data-error-id="whatsapp-error-3">
					<div class="error-message" id="whatsapp-error-3"></div>
				</div>
				<div id="telegram-field" class=" contact-field" style="display: none;">
					<input type="text" name="telegram-3" class="mask ui-input" placeholder="Ваш номер в Телеграм*" data-error-id="telegram-error-3">
					<div class="error-message" id="telegram-error-3"></div>
				</div>
				<div id="contactFrm3-recaptcha" class="g-recaptcha"></div>
				<div class="error-message" id="captcha-error-3"></div>
				<div class="consform-block">
					<label class="custom-checkbox-label">
						<input type="checkbox" class="simple-consent-checkbox" name="pravila-3" value="agree"> <span> Нажимая на кнопку, вы даете
									согласие на обработку своих персональных данных и соглашаетесь с
									<a href="#" data-fancybox data-src="#pravila"> Пользовательским соглашением</a></span> </label>
				</div>
				<div class="error-message" id="pravila-error-3"></div>
				<div class="form-footer">
					<button type="submit" class="formBtn-3">Получить консультацию</button>
					<div class="response"></div>
				</div>
			</form>
		</div>
	</div>
</section>

В этом коде как вы видите есть поля типа radio которые будут переключаться.

PHP обработчик

<?php
session_start();
$secretKey  = '6Lf4i5wqAAAAAPJAS-gzxXCnJbp11jXELtlbtrZz';
$toEmail = 'your-email@example.com';

$status = 0;
$statusMsg = 'Возникла ошибка на сервере!';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = filter_var($_POST['name-3'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $contactMethod = filter_var($_POST['contact-method'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $contactValue = '';
    $recaptchaResponse = $_POST['g-recaptcha-response'] ?? '';
    switch ($contactMethod) {
        case 'phone':
            $contactValue = filter_var($_POST['telephon-3'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
            break;
        case 'email':
            $contactValue = filter_var($_POST['email-3'] ?? '', FILTER_VALIDATE_EMAIL);
            break;
        case 'whatsapp':
            $contactValue = filter_var($_POST['whatsapp-3'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
            break;
        case 'telegram':
            $contactValue = filter_var($_POST['telegram-3'] ?? '', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
            break;
        default:
            $contactValue = '';
    }

    if (empty($name)) {
        $statusMsg = 'Пожалуйста, укажите ваше имя.';
    } elseif (empty($contactValue)) {
        $statusMsg = 'Пожалуйста, заполните поле.';
    } elseif ($contactMethod === 'email' && !$contactValue) {
        $statusMsg = 'Пожалуйста, введите корректный адрес электронной почты.';
    } elseif (empty($recaptchaResponse)) {
        $statusMsg = 'Пожалуйста, подтвердите, что вы не робот.';
    } elseif (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']['contactFrm3']) {
        $statusMsg = 'Invalid CSRF token.';
    } else {
        $url = 'https://www.google.com/recaptcha/api/siteverify';
        $data = [
            'secret' => $secretKey,
            'response' => $recaptchaResponse,
        ];
        $options = [
            'http' => [
                'method' => 'POST',
                'header' => 'Content-Type: application/x-www-form-urlencoded',
                'content' => http_build_query($data),
            ],
        ];
        $context = stream_context_create($options);
        $verify = file_get_contents($url, false, $context);
        $captcha_success = json_decode($verify, true);

        if (!($captcha_success['success'] ?? false)) {
            $statusMsg = 'Пожалуйста, подтвердите, что вы не робот.';
        } else {
            $emailSubject = 'Заявка на консультацию';
            $htmlContent = "
                <html>
                    <head>
                        <meta charset='utf-8'>
                        <title>Заявка на консультацию</title>
                    </head>
                    <body>
                        <h2>Новая заявка на консультацию</h2>
                        <p><strong>Имя:</strong> " . htmlspecialchars($name) . "</p>
                        <p><strong>Контакт:</strong> " . htmlspecialchars($contactValue) . "</p>
                        <p><strong>Метод связи:</strong> " . htmlspecialchars(ucfirst($contactMethod)) . "</p>
                    </body>
                </html>";
            $headers = "MIME-Version: 1.0\r\n";
            $headers .= "Content-type:text/html;charset=UTF-8\r\n";
            $headers .= "From: no-reply@example.com\r\n";
            if (mail($toEmail, $emailSubject, $htmlContent, $headers)) {
                $status = 1;
                $statusMsg = 'Благодарим за обращение! Наш менеджер свяжется с вами как можно скорее.';
            } else {
                $statusMsg = 'Ошибка отправки письма. Попробуйте позже.';
            }
        }
    }
}
echo json_encode([
    'status' => $status,
    'message' => $statusMsg,
]);
?>

Он более сложнее, чем в двух последних формах, так как нужно обрабатывать поля типа radio и проверять их заполнение. По изменению данного обработчика вам также нужно добавить секретный ключ Google Recaptcha и изменить почту в двух местах на вашу. Инструкция как это сделать находиться в первой форме.

Всплывающая форма обратной связи

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

HTML код формы

<section class="static-form section">
	<div class="container">
		<h2 class="heading__title">Всплывающая форма обратной связи</h2>
		<button class="modalFormBtn" data-fancybox data-src="#dialog-content">Показать форму</button>
		<div id="dialog-content" style="display:none;max-width:500px;">
			<div class="formArea"> <span class="quest-form__title">Оставить заявку на консультацию</span>
				<form id="contactFrm4" class="form" action="/forms/php/modalform.php" autocomplete="off" method="post">
					<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']['contactFrm4']; ?>">
					<fieldset class="ui-field">
						<input name="name-4" type="text" id="username-4" class="ui-input" placeholder="Ваше имя*" tabindex="0" />
						<div class="error-message" id="name-error-4"></div>
					</fieldset>
					<fieldset class="ui-field">
						<input type="text" name="telephon-4" class="mask ui-input" placeholder="Номер телефона*" data-error-id="phone-error-4">
						<div class="error-message" id="phone-error-4"></div>
					</fieldset>
					<div id="contactFrm4-recaptcha" class="g-recaptcha"></div>
					<div class="error-message" id="captcha-error-4"></div>
					<div class="consform-block">
						<label class="custom-checkbox-label">
							<input type="checkbox" class="simple-consent-checkbox-4" name="pravila-4" value="agree" title> <span> Нажимая на кнопку, вы даете
										согласие на обработку своих персональных данных и соглашаетесь с
										<a href="#" data-fancybox data-src="#pravila"> Пользовательским соглашением</a></span> </label>
					</div>
					<div class="error-message" id="pravila-error-4"></div>
					<div class="form-footer">
						<button type="submit" class="formBtn-4">Получить консультацию</button>
						<div class="response"></div>
					</div>
				</form>
			</div>
		</div>
	</div>
</section>

 Кнопка активации всплывающего окна

<button class="modalFormBtn" data-fancybox data-src="#dialog-content">Показать форму</button>

Атрибут data-fancybox data-src="#dialog-content" активирует всплывающее окно скриптом Fancybox.

Форма

В блоке с id="dialog-content" ниже кнопки вставлена форма, которая будет показываться при нажатии кнопки. 

Благодарственное всплывающее окно

<div id="success" style="display:none;max-width:500px;">
  <svg class="success-icon" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" x="0" y="0" viewBox="0 0 2.54 2.54" style="enable-background:new 0 0 512 512" xml:space="preserve" fill-rule="evenodd"><g><circle cx="1.27" cy="1.27" r="1.27" fill="#48b02c" opacity="1" data-original="#48b02c" class=""></circle><g fill="#fff"><path d="m.962 1.626.895-.895a.068.068 0 0 1 .096 0l.087.087a.068.068 0 0 1 0 .096l-.896.895a.068.068 0 0 1-.095 0l-.087-.087a.068.068 0 0 1 0-.096z" fill="#ffffff" opacity="1" data-original="#ffffff"></path><path d="m.683 1.08.545.546a.068.068 0 0 1 0 .096l-.086.086a.068.068 0 0 1-.096 0L.5 1.263a.068.068 0 0 1 0-.096l.087-.086a.068.068 0 0 1 .096 0z" fill="#ffffff" opacity="1" data-original="#ffffff"></path></g></g></svg>
  <h2>Форма успешно отправлена</h2>
  <p>Благодарим за ваше обращение! Мы перезвоним вам как можно скорее</p>
  <button class="close-btn">Закрыть</button>
</div>

Должно быть расположено перед закрывающимся тегом </body>. Текст можете изменить по вашему вкусу.

Правила обработки данных

Показ правил настроен на всплывающее окно при нажатии на ссылку в текст рядом с чекбоксом в формах. 

Код ссылки в каждой форме:

<a href="#" data-fancybox data-src="#pravila"> Пользовательским соглашением</a>

HTML код вплывающего окна правил, который нужно разместить перед закрывающимся тегом </body>.

<div id="pravila" style="display:none;max-width:1000px;">
   <h2>Условия обработки данных на сайте</h2>
</div>

После заголовка вы должны прописать реальный текст правил, который будет соответствовать условиям обработки данных на сайте.

Не советую пренебрегать правилами обработки данных. В законе предусмотрены штрафы за отсутствие согласия на обработку и пока никого не штрафуют, но этот день может наступить довольно резко!

Проблемы, которые могут возникнуть у новичков

Прочитав множество статей про формы я заметил некоторую тенденцию в возникающих проблемах.

Неверное подключение скриптов и стилей

Проблема. После подключения формы она не работает.

Решение. Открыть панель разработчика в браузере и переключиться на вкладку Консоль. Проверить там ошибки. Частые ошибки, которые можно наблюдать:

  1. Jquery is not defined. Означает что скрипт не подключён.
  2. 404 ошибка по скриптам и стилям говорит о том, что вы указали неверный путь. Лучше всего размещать папку forms в корне сайта.

Проблема. Форма отображается криво.

Решение. Открыть панель разработчика на вкладку Элементы и посмотреть какие стили влияют на формы. При клике на элемент кода слева, справа вы увидите CSS свойства и если они будут перечеркнуты, то стиль переписывается другим файлом стилей подключенный к странице. Если стилей нет, то возможно вы неправильно подключили стили в head теги. Проверьте пути до файла style.css.

Элементы кода страницы Элементы кода страницы
CSS свойства CSS свойства

Проблема. Вы поменяли id у формы и полях

Скачать архив со всеми формами

Скачайте архив с формой. Вирусов нет, не стоит беспокоиться. Если вы хотите протестировать форму у себя компьютере, то просто открыв файлы у вас не получиться запустить формы. Нужно развернуть локальный сервер, например ospanel, которой я часто пользуюсь, либо закинуть на хостинг и там уже тестировать. 

На какие комментарии я точно дам дельный ответ?

  1. Вы установили формы на хостинг и есть доступ по ссылке к сайту. Гаданием не занимаюсь. С вашей стороны готовность дать доступы к сайту по FTP.
  2. Рассказали как вы устанавливали форму и что меняли в коде форм.
  3. Какая версия PHP у вас на сервере.
0
1
0
0
0
Алексей Костюк
Привет Username! Я Алексей Костюк
16 лет занимаюсь тем, что помогаю людям создавать сайты и дизайн, настраивать рекламу, продвигать проекты в Интернете. Я создал этот блог, чтобы делиться своим опытом и мыслями, а если вы хотите, то можете меня поддержать, это поможет мне создавать больше полезных статей для вас.