Защита PHP формы от спама с помощью invisible reCAPTCHA

Содержание:
  1. Что собой представляет invisible reCAPTCHA от Google
  2. Регистрация сайта и получение ключей
  3. Установка клиентских PHP библиотек
  4. Проверка отправки почты по SMTP
  5. Подключение reCAPCTHA к форме, работающей без AJAX
  6. Защита от спама AJAX формы
  7. Несколько invisible reCAPTCHA на одной странице
  8. Настройка виджета invisible reCAPTCHA
  9. Комментарии

Статья, в которой рассмотрим, как защитить форму обратной связи от спама с помощью invisible reCaptcha от Google.

Что собой представляет invisible reCAPTCHA от Google

Значок невидимой reCAPTCHA

invisible reCAPTCHA - это капча, которая позволяет осуществлять фоновую валидацию действий пользователей на сайте. В отличие от reCAPTCHA v2 (пример формы с reCAPTCHA 2) она не требует от пользователей того, чтобы они нажимали на флажок (checkbox) "Я не робот". Она вызывается напрямую, т.е. тогда когда пользователь нажимает на существующую кнопку на сайте или программно через вызов JavaScript API.

invisible reCAPTCHA позволяет совершать пользователям на сайте какие-либо действия сразу (без ввода капчи). По умолчанию только самые подозрительные действия пользователей не позволят им пройти дальше. В этом случае invisible reCAPTCHA предложит им решить капчу (т.е. определить действительно ли данную операцию совершает человек, а не робот).

Каптча reCaptcha, которую предстоит решить пользователю

Регистрация сайта и получение ключей

1. Открываем страницу www.google.com/recaptcha/admin (при необходимости выполняем регистрацию и авторизацию на сайте).

2. Заполняем страницу «Регистрация сайта»:

  • вводим название ярлыка;
  • в разделе «Тип reCAPTCHA» выбираем пункт «reCAPTCHA v2» и в открывшемся списке отмечаем «Невидимый значок reCAPTCHA»;
  • добавляем домены, в данном случае «site.ru»;
  • устанавливаем флажок «Примите Условия использования reCAPTCHA.»;
  • нажимаем кнопку «Отправить».
Регистрация сайта для получение невидимой reCAPTCHA v2

3. После этого получаем публичный (для клиента) и секретный (для сервера) ключи.

Получение публичного и секретного ключей невидимой reCAPTCHA v2

Установка клиентских PHP библиотек

Так как серверная часть у нас будет на PHP, то установим клиентскую PHP библиотеку reCAPTCHA с использованием Composer. Для этого перейдем в корень сайта и введём следующую команду:

composer require google/recaptcha "^1.2"

Кроме этого ещё установим PHPMailer:

composer require phpmailer/phpmailer

Эту библиотеку будем использовать для отправки почты на электронный адрес через SMTP.

После этого у нас в корне сайта появится файл composer.json, содержащий все пакеты от которых зависит работа нашего сайта:

{
  "require": {
      "google/recaptcha": "^1.2",
      "phpmailer/phpmailer": "^6.6"
  }
}

Если вы разрабатываете сайт на локальном компьютере, то на сервер не нужно переносить папку vendor. Необходимо лишь подключиться к серверу по протоколу SSH и в терминале набрать команду:

composer install

Эта команда установит все пакеты, перечисленные в composer.json.

Кроме этого в папке vendor у нас ещё генерируется файл autoload.php. Для того чтобы подключить к проекту эти библиотеки, достаточно подключить только этот файл:

require_once $_SERVER['DOCUMENT_ROOT'] . '/vendor/autoload.php';

Проверка отправки почты по SMTP

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

Для этого, например, в корень сайта достаточно положить php-файл с именем test_mail.php со следующим содержимым:

<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);

try {
  // Настройки сервера
  $mail->SMTPDebug = SMTP::DEBUG_SERVER;
  $mail->isSMTP();
  $mail->Host = 'ssl://smtp.yandex.ru';                      // SMTP сервер
  $mail->SMTPAuth = true;
  $mail->Username = 'xxxxxxxx@yandex.ru';                    // SMTP имя пользователя
  $mail->Password = 'xxxxxxxx';                              // SMTP пароль
  $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
  $mail->Port = 465;                                         // TCP порт

  // От кого отправить
  $mail->setFrom('xxxxxxxx@yandex.ru', 'Имя отправителя');

  // Кому отправить
  $mail->addAddress('xxxxxxxx@gmail.com');

  // Контент
  $mail->CharSet = 'utf-8';
  $mail->Encoding = 'base64';
  $mail->isHTML(true);
  $mail->Subject = 'Сообщение с формы обратной связи';
  $mail->Body = 'Это текст HTML-сообщения, у которого <b>эта часть выделена жирным начертанием!</b>';

  $mail->send();
  echo 'Сообщение было отправлено!';
} catch (Exception $e) {
  echo "Сообщение не удалось отправить. Ошибка: {$mail->ErrorInfo}";
}

Здесь в нужных местах необходимо указать свои данные для отправки почты через используемый вами SMTP-сервер. В этом файле приведены настройки Yandex.

После этого этот файл нужно запустить, т.е. в адресной строке набрать соответствующий URL:

Проверяем правильность настройки PHPMailer для отправки почты через протокол SMTP

После того как вы полностью настроили PHPMailer для отправки писем и увидели, что сообщение было отправлено, можно переходить к следующему шагу.

Подключение reCAPCTHA к форме, работающей без AJAX

Самый простой способ подключить невидимую reCAPTCHA к форме - это привязывать её вызов к кнопке.

Итак, для этого нам необходимо на клиентской стороне:

  • подключить скрипт Google reCAPTCHA API;
  • добавить к кнопке для отправки формы класс g-recaptcha, и атрибуты data-sitekey и data-callback; в качестве значения атрибута data-sitekey устанавливаем публичный ключ, а в data-callback – имя функции обратного вызова для обработки завершения проверки капчи;
  • добавить функцию с именем, которое мы указали в атрибуте data-callback; это функция будет запускаться при нажатии на эту кнопку.
<!-- Подключаем скрипт Google reCAPTCHA API -->
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script>
  // Напишем функцию onSubmit
  function onSubmit(token) {
    document.querySelector('#form-1').submit();
  }
</script>

<form id="form-1" action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" novalidate>
  ...
  <!-- Добавляем к кнопке класс g-recaptcha и атрибуты data-sitekey и data-callback -->
  <button class="btn btn-primary g-recaptcha" data-sitekey="6LeSClchAAAAAKTjOyWnKVgYVHTKHEScxp8gOSBJ" data-callback="onSubmit">Отправить</button>
</form>

На серверной стороне:

// секретный ключ
define('SECRET_KEY', '6LeSClchAAAAAAGDbQczz20se0A4e-eDkulFzeKC');
// подключим автозагрузчик Composer
require_once $_SERVER['DOCUMENT_ROOT'] . '/vendor/autoload.php';
// проверяем ответ от службы reCAPTCHA
// создаем экземпляр класса ReCaptcha с указанием секретного ключа
$recaptcha = new \ReCaptcha\ReCaptcha(SECRET_KEY);
// указываем, чтобы имя хоста совпадало с site.ru, и вызываем verify()
$resp = $recaptcha->setExpectedHostname('site.ru')
  ->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
// если проверка не прошла
if (!$resp->isSuccess()) {
  $errors = $resp->getErrorCodes();
  error_log('reCAPTCHA Error: ' . print_r($errors, true));
  // другие действия ...
}

Полный код формы с invisible reCAPTCHA от Google расположен на GitHub: without-ajax.

Вид формы:

Форма обратной связи с невидимой reCAPTCHA

Для простоты форма состоит из 2 полей: name и email. Оформление формы обратной связи выполнено посредством Bootstrap 5.

Сообщение об успешной отправке формы:

Сообщение об успешной отправке формы с невидимой reCAPTCHA

Сообщение, которое приходит на почту:

Сообщение, которое приходит на почту

Защита от спама AJAX формы

Процесс встраивания invisible reCAPTCHA в AJAX форму обратной связи:

  1. Добавить в HTML форму элемент div с атрибутами class="g-recaptcha", data-sitekey="значение публичного ключа", data-callback="onSubmitReCaptcha" и data-size="invisible". onSubmitReCaptcha - это имя функции, которая будет вызвана после окончания успешной проверки пользователя. В данный элемент будет осуществляться рендеринг капчи invisible reCAPTCHA.
    <!-- Форма обратной связи -->
    <form id="messageForm" enctype="multipart/form-data" method="post" novalidate>
      <!-- ... содержимое HTML формы -->
      <!-- invisible reCaptcha -->
      <div id="recaptcha" class="g-recaptcha" data-sitekey="6LdXhhgUAAAAAFUToe9JV6qa19DrI2TEM3GH-l7g" data-callback="onSubmitReCaptcha" data-size="invisible"></div>
      <!-- Кнопка, отправляющая форму по технологии AJAX -->
      <button id="submit" type="submit" class="btn btn-primary pull-right" name="send-message">Отправить сообщение</button>
    </form><!-- Конец формы -->
    
  2. Подключить скрипт reCaptcha.
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
    
  3. Вызвать проверку reCaptcha (grecaptcha.execute()) после успешной валидации HTML формы.
    // при отправке формы messageForm на сервер (id="messageForm")
    $('#messageForm').submit(function (event) {
      // отменим стандартную отправку формы на сервер
      event.preventDefault();
      // если форма прошла валидацию, то вызовем invisible reCaptcha
      if (validateForm(this)) {
        grecaptcha.execute();
      }
    });
    
  4. Добавить функцию обратного вызова onSubmitReCaptcha, которая будет запущена, если пользователь успешно прошёл капча тест. Код данной функции, содержит вызов другой функции, которая отправляет форму обратной связи на AJAX.
    function onSubmitReCaptcha(token) {
      var idForm='messageForm';
      sendForm(document.getElementById(idForm),'/feedback/process.php');
    }
    

Посмотреть пример полного кода контактной формы можно на GitHub или посредством следующей ссылки (архив):

Форма обратной связи с защитой invisible reCAPTCHA (Яндекс.Диск)

Пример AJAX формы обратной связи с invisible reCAPTCHA

Пример HTML-формы с invisible reCAPTCHA, отображаемой во всплывающем окне:

HTML-форма в выпадающем окне (Яндекс.Диск)

Несколько invisible reCAPTCHA на одной странице

Для того чтобы разместить несколько invisible reCAPTCHA на странице, их необходимо генерировать вручную.

Инструкция по размещению 2 invisible reCAPTCHA на одной странице:

  1. Подключить скрипт reCAPTCHA с указанием функции (в данном случае onloadReCaptchaInvisible), которая будет вызвана после завершения загрузки api.js. Функцию onloadReCaptchaInvisible будем использовать для программной генерации виджетов и получения их id.
    <script src="https://www.google.com/recaptcha/api.js?onload=onloadReCaptchaInvisible&render=explicit" async defer></script>
    
  2. Добавим в каждую из форм обратной связи контейнер, который будет представлять собой виджет reCaptcha.
    <!-- HTML форма 1 -->
    <form id="messageForm1" method="post">
      <!-- ...  -->
      <div id="recaptcha1"></div>
      <!-- ...  -->
    </form>
    <!-- HTML форма 2 -->
    <form id="messageForm2"  method="post">
      <!-- ...  -->
      <div id="recaptcha2"></div>
      <!-- ...  -->
    </form>
    
  3. Представим каждый контейнер как виджет reCAPTCHA. Получим id созданных виджетов.
    var idCaptcha1, idCaptcha2;
    var onloadReCaptchaInvisible = function() {
      idCaptcha1 = grecaptcha.render('recaptcha1', {
        "sitekey":"значение публичного ключа",
        "callback": "onSubmitReCaptcha",
        "size":"invisible"
      });
      idCaptcha2 = grecaptcha.render('recaptcha2', {
        "sitekey":"значение публичного ключа",
        "callback": "onSubmitReCaptcha",
        "size":"invisible"
      });
    };
    
  4. Использовать ID виджета при проверке reCAPTCHA:
    // выполнить проверку reCAPTCHA, имеющей в качестве ID виджета значение переменной idCaptcha1
    grecaptcha.execute(idCaptcha1);
    // выполнить проверку reCAPTCHA, имеющей в качестве ID виджета значение переменной idCaptcha2
    grecaptcha.execute(idCaptcha2);
    
  5. Применять ID виджета invisible reCAPTCHA при получении ответа:
    // передать в FormData ответ виджета invisible reCAPTCHA, у которого ID равно значению переменной idCaptcha1 (для первой формы)
    formData.append('g-recaptcha-response', grecaptcha.getResponse(idCaptcha1));
    // передать в FormData ответ виджета invisible reCAPTCHA, у которого ID равно значению переменной idCaptcha2 (для второй формы)
    formData.append('g-recaptcha-response', grecaptcha.getResponse(idCaptcha2));
    

Полный код примера, в котором invisible reCAPCTHA используется для защиты нескольких форм:

Две формы invisible reCAPTCHA на странице (архив на Яндекс.Диск)

Пример размещения двух invisible reCAPTCHA на одной странице

Настройка виджета invisible reCAPTCHA

Дополнительные (необязательные) параметры капчи invisible reCAPTCHA:

  • data-badge (badge) - местоположение значка reCAPTCHA (по умолчанию: bottomright). В качестве значения можно указывать ещё: bottomleft (внизу слева) и inline (позволяет управлять положением с помощью CSS).
  • data-type (type) - тип капчи (по умолчанию: image). Изменить тип капчи при необходимости можно на audio.
  • data-tabindex (tabindex) - для страниц, в которых для навигации по элементам дополнительно используется клавиша tab. Данный параметр позволяет установить порядок элемента, его tabindex (по умолчанию: 0).

Внимание: Использовать атрибуты с префиксом data- необходимо только в HTML-коде. В скриптах задавать параметры необходимо без префикса, т.е. вместо data-badge указывать badge и т.д.


Похожие темы, которые вас могут заинтересовать:

Комментарии: 86

achilless
achilless
А как разделить код? если например нужно вызывать в разных местах?

<script>
var onloadReCaptchaInvisible = function() {
 
  idCaptcha2 = grecaptcha.render('recaptcha2', {
    "sitekey":"значение публичного ключа",
    "callback": "onSubmitReCaptcha",
    "size":"invisible"
  });
};
</script>

<script>
var onloadReCaptchaInvisible = function() {
  idCaptcha1 = grecaptcha.render('recaptcha1', {
   "sitekey":"значение публичного ключа",
    "callback": "onSubmitReCaptcha",
    "size":"invisible"
  });
 
};
</script>
Владимир
Владимир
Добрый день! Подскажите пожалуйста, пытаюсь разобраться с несколькими invisible reCaptcha на одной странице, вроде всё сделал как написано в примере, но при отправке формы ничего не происходит. В отладчике показывает на ошибку в JSON.parse(data) что это может значить?
Александр Мальцев
Александр Мальцев
Здравствуйте! Посмотрите какой ответ приходит от сервера. Скорее всего не JSON, поэтому и возникает ошибка на этой строчке.
Евгений
Евгений
Добрый день!

Уточните пожалуйста сколько будет стоить услуга по установке на сай формы обратной связи с invisible reCAPTCHA. Скачал выложенный вами пример AJAX формы обратной связи с invisible reCAPTCHA, но самостоятельная установка не удалась из за Could not load content for www.controldrive.ru/feedback/css/bootstrap.min.css.map: HTTP error: status code 404. При отправке форма форма не выдает ни каких сообщений.
Надеюсь на вашу помощь!
Заранее благодарен за ответ.
Александр Мальцев
Александр Мальцев
Здравствуйте! Напишите на почту, если не срочно.
Дмитрий
Дмитрий
Александр, здравствуйте. Скажите, пожалуйста, насколько эффективно использовать invisible recapcha для многостраничного сайта (более 50 тыс. страниц) для защиты от парсинга. У нас сайт-энциклопедия и часто новые материалы парсят конкуренты. Поможет ли невидимая капча сделать их деятельность по скачиванию нашего сайта более трудоемкой? Спасибо. С уважением, Д.Ж.
Александр Мальцев
Александр Мальцев
Здравствуйте! В любом случае это создаст для них трудности. А найдут ли они решения или нет зависит от того насколько у них для этого имеется возможностей.
Дмитрий
Дмитрий
Спасибо за ответ. Хотел еще поинтересоваться, а будет ли invisible recapcha по умолчанию «отстреливать» белые боты (краулеры Яндекса или Гугл)?
Александр Мальцев
Александр Мальцев
Не проверял.
В этом случае можно в php добавить проверку на нужных ботов. Если они пришли, то отдавать страницу без invisible recapcha.
Dokar
Dokar
Благодарю за вашу работу. Все работает. Все отлично.
Единственный момент, когда создавал ключи рекапчи, оказалось, что сегодня в природе 4 типа рекапчи.
Вопрос:
Использую форму с капчей: yadi.sk/d/Wk5GUzlI3GFCHp
Не подскажете, какой код добавить, чтобы после отправки письма уведомление «Внимание! Форма была успешно отправлена.» не висело бесконечно. А спустя какое то время форма возвращалась к исходному состоянию?
Спасибо.
Александр Мальцев
Александр Мальцев
Спасибо!
Лучше наверно в сообщение добавить ссылку, при нажатию на которую форма будет возвращаться в исходное состояние.
Для этого необходимо в сообщение добавить ссылку:
<!-- Сообщение, отображаемое в случае успешной отправки данных -->
<div class="alert alert-success success-send hidden" role="alert">
  <strong>Внимание!</strong> Форма успешно отправлена. Нажмите на <a class="alert-link" href="#" data-reloadform="#messageForm">ссылку</a>, чтобы отправить ещё одно сообщение.
</div>
После этого добавить в JavaScript следующий код:
$('.alert-link').click(function () {
  var
    selector = $(this).attr('data-reloadform'),
    form = $(selector);

  $(this).closest('.success-send').addClass('hidden');
  $(form).css('display', 'block');
  // действия для очистки формы
  $(form).find('input, textarea').each(function (item) {
    $(this).val('').closest('.form-group').removeClass('has-success').find('.form-control-feedback').removeClass('glyphicon-ok');
  })
});
Если нужно через определённое количество времени, то так:
if (data.result == "success") {
  ...
  window.setTimeout(function(){
    resetForm($(feedbackForm))}, 5000);
  } 
Функция resetForm:
var resetForm = function (form) {
  $(form).parent().find('.success-send').addClass('hidden');
  $(form).css('display', 'block');
  $(form).find('input, textarea').each(function (item) {
    $(this).val('').closest('.form-group').removeClass('has-success').find('.form-control-feedback').removeClass('glyphicon-ok');
  })
};
Архив этой формы расположен здесь.
Александр
Александр
Александр. День добрый.
Вопрос по отправке сообщения после всех проверок.
if ($mail->Send()) {
	$result = true;
} else {
	$result = false;
}
Возвращает false? сообщение не отправляется.
когда смотрю содержимое
var_dump($mail)
вижу в нем в том числе:
["ErrorInfo"]=>string(36) "Could not instantiate mail function."
и
["Sendmail"]=>string(18) "/usr/sbin/sendmail"
То-есть я понимаю, что класс PHPМailer подключен $mail формируется.
Сайт пока в локале под IIS.
Правильно -ли я понимаю, что PHPМailer не смог выполнить/инициировать отправку сообщения потому, что не нашел Sendmail по указанному пути?
И если да, как с этим справиться?

Александр Мальцев
Александр Мальцев
Добрый! Александр, вам нужно установить и настроить SMTP Server. А также внести настройки в конфигурационный файл php.ini.
Александр
Александр
Да, Александр, благодарю.
Похоже, что IIS 8 больше не содержит SMTP сервера и просто так от него не добиться SMTP-relay, по-этому и грабли.
Александр
Александр
Александр, приветствую.
У меня вопрос немного не по использованию Рекапча а по PHP.
В файле process.php есть проверка на ajax/не axaj запрос, хочется ее расширить и проверять еще с какого хоста пришел запрос и сравнивать с хостом на котором лежит сайт.
У меня ситуация следующая:
глобально для сайта
в $_SESSION['siteHome'] хранится, допустим: localhost:8181/
в $_SESSION['recapchaSecret'] хранится секретный ключ.

в файле process.php я получаю $_SERVER['HTTP_REFERER'] — из заголовка localhost:8181/ и пытаюсь его сравнить с $_SESSION['siteHome'], но $_SESSION['siteHome'] как бы не существует для этого файла, я так понимаю, что сервер под ajax запрос создал новую сессию. Существует-ли какой-то способ добраться до $_SESSION['siteHome'] и $_SESSION['recapchaSecret'] из файла process.php?
Александр
Александр
Вопрос снят, вопрос решился добавлением session_start() в process.php (невнимательность моя).
Иван
Иван
Здравствуйте! Прекрасная форма! Но у меня почему то не работает…
Сайт на Prestashop 1.7.3, кладу папку в корень сайта, меняю ключ капчи, открываю страницу, заполняю поля, хочу отправить, а мне пишет «Произошла ошибка при отправке данных.». Как отследить где проблема?
Исходящая почта работает, проверял php кодом:
<?
mail(«ivan@mail.ru», «Тема», «Сообщение»);
?>
Иван
Иван
Вот это в консоле. В чем проблема?
Александр Мальцев
Александр Мальцев
Скорее всего не выставлены права на изменение и на запись. Неоходимо проверить этот момент.
Общие рекомендации:
1. В php файле (в данном случае process.php) необходимо добавить строки:
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
Это включит отображение ошибок.
2. Ошибки можно смотреть в «инструментах разработчика» браузера на вкладке Network. На этой вкладке необходимо выбрать файл process.php и посмотреть response (ответ).
Иван
Иван
Ключ гугла надо прописать только в index.html или еще где то?
Вот что пишет response:

<br />
<b>Fatal error</b>:  Uncaught RuntimeException: No secret provided in /var/www/мойсайт/data/www/мойсайт/feedback/recaptcha/ReCaptcha/ReCaptcha.php:61
Stack trace:
#0 /var/www/мойсайт/data/www/мойсайт/feedback/process.php(73): ReCaptcha\ReCaptcha->__construct('')
#1 {main}
  thrown in <b>/var/www/мойсайт/data/www/мойсайт/feedback/recaptcha/ReCaptcha/ReCaptcha.php</b> on line <b>61</b><br />
Александр Мальцев
Александр Мальцев
Секретный ключ необходимо прописать в файл process.php.
В этом файле есть строчка:
$secret = ''; // ваш секретный ключ
Иван
Иван
Какой же я слепой!!! Много раз просмотрел файл и слона та не приметил в самом начале!!! Огромное вам спасибо за помощь, все заработало!
Макс
Макс
Александр, подскажите, пожалуйста.
Нашел файл process.php, строки $secret в нем не было. Вставил строку с кодом, но ошибка в строке 61 осталась. Капча почему то не видит кода…
Uncaught Error: Missing required parameters: sitekey
Что можно сделать или проверить?
Александр Мальцев
Александр Мальцев
Эта 6 строчка в файле process.php.
Макс
Макс
Но у меня в файле process.php шестая строка это $filenames = array();
Если вставляю $secret под ней, ошибка остается… ://
bigvax
bigvax
День добрый!
Я объединил две ваших статьи — о reCaptha и о динамических модальных окнах и сделал заготовку (на основе вашей), с произвольным количеством динамических модальных окон на странице, и соответственно с произвольным количеством форм с рекапчей.
Точнее, форма и рекапча вроде-как одна, но т.к. она в динамическаом модале, и строится только в момент вызова, то эта форма может быть вызвана из разных мест страницы, причем в разных вызовах может быть специфицирована параметрами, и соответственно, в зависимости от «вызывальщика» может иметь различное оформление, разные поля в форме, разные поля в режиме «обязательное» и т.п. (правда, функционал изменения самой формы от параметров вызова в моем примере не показан, но он предполагается, если само тело формы разместить не в статическом .html, а например в .php — и специфицировать его, в зависимости от параметров.)

Архив с примером: yadi.sk/d/cVb-_f0E3S8nAV

Cледует учесть:
— в связи с моей привычной структурой, $DOCUMENT_ROOT — расположен в www/
— соответственно, служебные библиотеки recaptcha и phpmailer, вынесены за тело сайта в /lib
— файл логирования messages.txt, также вынесен за тело сайта в /logs
— обработчик динамических модал-окон control-modal.js — слегка расширен — добавлены функции изменения класса заголовка модал-окна
— формат произвольных параметров для передачу в форму из «вызвальщика»:
data-params='«parametr1»:true,«parametr2»:«blah-blah-blah»'. Соответственно, они парзяться в site.js и передаются в форму, где могут быть использованы

Вот как-то так…

Александр Мальцев
Александр Мальцев
Добрый день! Круто получилось.
bigvax
bigvax
Спасибо за высокую оценку!

Вот только, мне несколько не нравится мой блок парзинга/обработки передаваемых параметров, в файле site.js — стоки 9-14.
var parameters = null;
if ($(this).attr('data-params')) {
  var params = $(this).attr('data-params');
  if (params.indexOf('{') != 0) params = '{' + params + '}';
  parameters = JSON.parse(params);
}
Как-то очень «в лоб», но ничего более красивого не придумалость :(
К сложалению, JS — это не мой «конек» ;)
Может-быть ваш опыт подскажет, что-ли более оптимальное… ;)
Александр Мальцев
Александр Мальцев
Мне понравился принцип. Код ещё не смотрел. Напишу, как его можно улучшить.
Александр
Александр
Здравствуйте!
У меня в script.js отрабатывает функция error: function (request) и выдает в поток:
Access forbidden!
You don't have permission to access the requested object. It is either read-protected or not readable by the server.
Ну, то есть, это Апач выдает. Все перерыл не знаю где что настроить для доступа((
Да! Это на Друпал 8 происходит. На Битриксе, например, все работает.
Александр Мальцев
Александр Мальцев
Здравствуйте! Вам необходимо обеспечить доступ к файлу, с помощью которого вы осуществляете обработку формы (process.php) с клиента. Для проверки доступа попробуйте его (process.php) открыть из браузера. Тут, скорее всего дело в настройках htaccess.
Александр
Александр
Из браузера не открывается, тоже самое пишется.
в .htaccess я положил
<Files process.php>
order allow,deny
allow from all
</Files>
не помогает ((
Евгений
Евгений
Все супер, все работает.
Алексадр, как сделать на эту форму всплывающее окно, через кнопку. Спасибо!
И куда поблагодарить $?
Александр Мальцев
Александр Мальцев
Добавил в статью пример формы, открывающейся во всплывающем окне.
Поблагодарить можно с помощью кнопки «Поддержать сайт», расположенной в футере.
Александр
Александр
Александр, здравствуйте.
Я к вам снова с ошибками.

при отправке формы, после того как поотмечали картинки рекапчи ничего не происходит ошибки на форме не выдаются, в консоли появляется ошибка:


Вот код в этом месте:

Из-за чего такое возможно?
Александр Мальцев
Александр Мальцев
Ошибка связана с тем, что php-скрипт возвращает ответ не в формате JSON. Надо смотреть, почему он отрабатывает не правильно и где возникает ошибка. Для этого можно воспользоваться вкладкой Network в браузере.
Александр
Александр
Вкладка Network говорит так:
Александр Мальцев
Александр Мальцев
Во вкладке Network необходимо смотреть ответ сервера (Response).
Александр
Александр
Этот запрос не имеет данных для ответа

This request has no response data available.

Вот так выглядит upload.php

Вот так выглядит upload.js

Отправка письма закомментирована, пока хочу просто добиться загрузки файла на сервер.
Александр Мальцев
Александр Мальцев
Поправил код на Github и в архиве. Обнови php-файлы и посмотри, какой сейчас придёт ответ.
Александр
Александр
{«result»:«error»,«files»:"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0444\u043e\u0440\u043c\u044b."}
Александр
Александр
Я так понимаю, что это «Произошла ошибка при отправке формы» — этот ответ формируется, если не получилось сохранить/создать/записать файл message.txt

Ну как- бы на этот файл можно закрыть глаза. До этого на сервере должен, по идее, сохраниться файл с формы. А он не сохраняется.
Александр Мальцев
Александр Мальцев
Значит, у вас ошибка возникает в строчке:
if (file_put_contents(dirname(__FILE__).'/message.txt', $output, FILE_APPEND | LOCK_EX)) {
А это значит, что php функция file_put_contents скорее всего не может создать файл и записать в него данные. Проверьте права доступа.

Если вы в качестве сервера используете IIS, то проверьте, имеет ли php-cgi.exe соответствующие разрешения. А также поставьте пользователю полный доступ на запись в этот файл.
Александр
Александр
разрешение на запись на весь каталог phptest у пользователя IIS_IUSRS есть.

Александр, правильно-ли я понимаю, что в свойствах обработчика PHP в оснастке IIS должен быть выбран пункт Запись, а не Сценарий? (просто в этом случае почему-то выдается ошибка о том, что необходимы разрешения на запись, хотя они и есть)

И по поводу загрузки файла/картинки на сервер. Не срабатывает функция создания текстового файла на сервере, но до нее, же идут функции именно записи на сервер файла, который на форме прикрепили, и они, я так понимаю, отрабатывают, то есть файл, который на форме прикрепили, теоретически куда-то должен был на сервере записаться, а его нет.
Александр Мальцев
Александр Мальцев
На apache, nginx — работает. На IIS не подскажу, т.к. его не использую. Попробуйте создать простой php файл из одной только этой функции и проверьте (посмотрите логи IIS):
<?php
$output = 'Некоторые данные...';
file_put_contents(dirname(__FILE__).'/message.txt', $output, FILE_APPEND | LOCK_EX));
Может, кто вам другой из пользователей сайта подскажет или как вариант, почитать соответствующие темы на форумах компании Microsoft.
Александр
Александр
Благодарю. Результат очень странный:
$output = 'Некоторые данные...';
file_put_contents(dirname(__FILE__).'/message.txt', $output, FILE_APPEND | LOCK_EX));
Выдает ошибку 500. Файл не создается
	$output = 'Некоторые данные...';
	if (file_put_contents(dirname(__FILE__).'/message.txt', $output, FILE_APPEND | LOCK_EX)) {
		$data = ('Файл '.dirname(__FILE__).'/message.txt'.' сохранен.');
		echo $data;
		exit();
	  } else {
		$data = ('Произошла ошибка при записи файла '.dirname(__FILE__).'/message.txt'.'.');
		echo $data;
		exit();        
	  }
Файл создается.

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

Вдруг кому пригодится: помимо прав на ЗАПИСЬ в каталог должны быть установлены права на ИЗМЕНЕНИЕ (если IIS)
Александр
Александр
Александр, если я правильно понимаю, то вот эта часть кода:
// обработка переданных файлов (name="files[]")
  if(isset($_FILES["files"])) {
    $files = array();
    // цикл по файлам
    $i = 1;
    foreach ($_FILES["files"]["error"] as $key => $error) {
      if ($error == UPLOAD_ERR_OK) {
        // получаем характеристики файла
        $nameFile = $_FILES['files']['name'][$key];
        $extFile = mb_strtolower(pathinfo($nameFile, PATHINFO_EXTENSION));
        $sizefile = $_FILES['files']['size'][$key];
        $filetype = $_FILES['files']['type'][$key];
        // проверка расширения файла и его размер
        if (!in_array($extFile, $allowedExtension)) {
          $data['files-'+$i]='Ошибка при загрузке файла '. $nameFile .' (неверное расширение).';
          $data['result']='error';
          echo json_encode($data);
          exit();           
        }
        if ($sizefile > $maxSizeFile) {
          $data['files-'+$i]='Ошибка при загрузке файлов '. $nameFile .' (размер превышает '. $maxSizeFile/1024 .' Кбайт).';
          $data['result']='error';
          echo json_encode($data);
          exit();    
        } 
        $tmpFile = $_FILES['files']['tmp_name'][$key];
        // уникальное имя файла
        $newFileName = uniqid('img_', true).'.'.$extFile;
        // полное имя файла
        $newFullFileName = $pathToFile.$newFileName;
        // перемещаем файл в директорию
        if (!move_uploaded_file($tmpFile, $newFullFileName)) {
          $data['files'] = 'Произошла ошибка при загрузке файлов.';
          $data['result']='error';
          echo json_encode($data);
          exit();              
        }
        $files[] = $newFullFileName;
      } else {
        $data['files'] = 'Произошла ошибка при загрузке файлов.';
        $data['result']='error';
        echo json_encode($data);
        exit();        
      }
    }
  }
Должна сохранить отправленные формой файлы на сервере в папке files, чтобы отправить их потом вложением на email.

Отправку непосредственно email, я отключил пока, то-есть файлы должны сохраниться на сервере в папке files с уникальными именами или выдать ошибку, однако, форма якобы отправляется (выдается сообщение об успешной ее обработке), файл message.txt создается, данные в него записываются, но файл приложенный в форме — никуда не сохраняется.
Александр
Александр
Александр, проблему нашел.
Для добавления картинки в форму использую Этот плагин. Ну и Соответственно, если не выбрать «с кнопки» файл, а перетащить на предпросмотр, то input type=«file», остается пустым.
Александр Мальцев
Александр Мальцев
Отлично!
Александр
Александр
Александр, приветствую.
Пытаюсь дальше обрабатывать загруженный файл. Не нашел соответствующего раздела.

есть вот такой jquery скрипт для изменения размеров и обрезки изображений.

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

Суть в чем, по плану можно только 4 размера изображений:300*300, 300*150, 150*300, 150*150

в архиве, канвас образмеривается, обрезается по канвасу, в этом плане все хорошо проблемы начинаются когда картинка сдвигается относительно канваса, то-есть картинку за правый-нижний угол картинки, например, можно сдвинуть в левый верхний угол канваса (красного прямоугольника), соответственно вопрос:
— можно как-то ограничить движение картинки, чтобы при движении картинки вправо, правую границу картинки невозможно было сдвинуть левее правой границы канваса (красного прямоугольника), ну и соответственно с остальными границами картинки аналогично (левая картинки не правее левой красного прямоугольника, верхняя картинки не ниже верхней красного прямоугольника, нижняя картинки не выше нижней красного прямоугольника)?

и еще один вопрос, по нажатию на кнопку обрезки открывается окно с обрезанной картинкой, а как не открывать окно, а отдать этот результат обрезки аяксом php скрипту на запись в файл/в бд?
Сильно ли будет отличаться содержимое php скрипта записи в файл этой картинки от содержимого записи в файл приложенного на форме изображения и чем?
Евгений
Евгений
Александр, добрый день.
Подключил по умолчанию вашу форму, настроил капчу, забил поля нажимаю отправку и появляется надпись: Произошла ошибка при отправке данных.
Скрин
itchief.ru/assets/uploadify/4/7/f/47f7658e33122046bd7ec3febc1db202.jpg
Евгений
Евгений
Александр Мальцев
Александр Мальцев
Добрый! Посмотрите, на какой строчке, и какая у вас возникает ошибка (вкладка Network).
Николай
Николай
Здравствуйте! Интересует как в первом простом примере, где есть index.php, прикрутить формы с выпадающими списками? (как сделать обычные дополнительные формы разобрался, всё работает, а вот с выпадающим списком не разобрался. И как сделать его отправку… Не понимаю, как там всё устроено в этом php) заранее СПАСИБО!!!

В первом списке надо 10 наименований продукта, а во втором 2. Ну и далее по списку.
[ВЫБЕРИТЕ НАИМЕНОВАНИЕ ПРОДУКТА] <-это список из 10 наименований
[ФАСОВКА] <-здесь всего два способа
ИМЯ,
ТЕЛЕФОН,
ЭЛ.ПОЧТА
ТОЧНЫЙ АДРЕС ДОСТАВКИ
Николай
Николай
Кажется разобрался)
php:
// получаем выбор продукции
if (isset($_POST['second'])) {  
  $second = isset($_POST['second']) ? $_POST['second'] : '';
}

// завершающие действия
// запись информации в файл
$output .= "Химическая продукция: " . $second . "\n";

// отправка формы на email
$output .= '<p><b>Химическая продукция:</b> ' . $second . '</p>';
далее в HTML:
<!-- выбор продукции -->
<div class="form-group has-feedback <?php if (isset($_POST['second'])) {echo "has-error";} ?>">
  <label for="second" class="control-label">Выберите продукцию:</label>
  <select name="second">
    <option value="Сода">Сода Кальцинированная</option>
    <option value="Карбид">Карбид в бочках</option>
    <option value="Песок">Песок любой</option>
  </select>
  <span class="glyphicon form-control-feedback <?php if (isset($data['second'])) {echo "glyphicon-remove";} ?>"></span>
</div>
Александр Мальцев
Александр Мальцев
Молодец!
Николай
Николай
Александр здравствуйте!)
А как сделать что бы на указанный email пришло письмо с уведомлением о том, что «Ваша заявка принята в обработку»?
Александр Мальцев
Александр Мальцев
Здравствуйте!
Для этого необходимо подготовить и отправить ещё одно письмо. Это необходимо сделать после того, как будет отправлено предыдущее письмо.
Т.е. как-то так:
if ($data['result']=='success') {
  // отправляем ещё одно письмо
  $output = "Ваша заявка принята в обработку";
  // создаём экземпляр класса PHPMailer
  $mail = new PHPMailer;
  $mail->CharSet = 'UTF-8';
  $mail->From      = 'email@mysite.ru';
  $mail->FromName  = 'Имя сайта';
  $mail->isHTML(true);
  $mail->Subject   = 'Уведомление о принятии заявки';
  $mail->Body      = $output;
  $mail->AddAddress($email);
  // отправляем письмо
  if ($mail->Send()) {
    $data['result']='success';
  } else {
    $data['result']='error';
  }    
}
Николай
Николай
Спасибо большое! Всё работает!
Здесь оказывается можно перечислить всё то, что заполняли в форму
  // отправляем ещё одно письмо
  $output = "Ваша заявка принята в обработку";
  $output .= '<p><b>Организация:</b> ' . $organiz . '</p>';
  $output .= '<p><b>Место выгрузки:</b> ' . $dostavka . '</p>';
  $output .= '<p><b>Химическая продукция:</b> ' . $second . '</p>';
Не постесняюсь спросить ещё, как вставить ссылку на png картинку/логотип в output, что бы заполняющему форму, пришло более менее оформленное письмо?
Александр Мальцев
Александр Мальцев
Для того чтобы вставить картинку необходимо использовать HTML элемент img и полный адрес к картинке с учётом протокола.
$output .= '<img src="http://mysite.ru/images/logo.svg" alt="Описание картинки">';
Николай
Николай
Вот спасибо) Может быть есть какой нибудь материал, о том как оформить/украсить письмо, которое получает заполняющий форму?
Например мне нужно изменить background письма, цвет текста, применить стили/колонки и всё такое прочее
Александр Мальцев
Александр Мальцев
Материалу по этой теме пока ещё нет.
Но сделать простой HTML шаблон письма не очень сложно. Для этого используйте табличную вёрстку, т.е. вёрстку с помощью элементов table, td и tr. Для создания фона используйте CSS-свойство background-color.
<body style="margin: 0; padding: 0; background-color: #f8f8f8;">
  <table>
    <!-- Табличная вёрстка --> 
  </table>
</body>
Николай
Николай
Не понял как вот это
<body style="margin: 0; padding: 0; background-color: #f8f8f8;">
  <table>
    <!-- Табличная вёрстка --> 
  </table>
</body>
применить вот к этому?
  // отправляем ещё одно письмо
 $output = "Ваша заявка принята в обработку";
 $output .= '<img src="http://mysite.ru/images/logo.svg" alt="Описание картинки">';
 $output .= '<p><b>Организация:</b> ' . $organiz . '</p>';
 $output .= '<p><b>Место выгрузки:</b> ' . $dostavka . '</p>';
 $output .= '<p><b>Химическая продукция:</b> ' . $second . '</p>';
Александр Мальцев
Александр Мальцев
Формируете разметку и вставляете в нужные места значения переменных:
//формируем тело письма
$output = '
  <body style="margin: 0; padding: 0; background-color: #f8f8f8;">
    <table cellpadding="0" cellspacing="0" width="100%">
      <tr>
        <td>
          <h3>Ваша заявка принята в обработку</h3>
          <p><b>Организация:</b> ' . $organiz . '</p>
          <p><b>Место выгрузки:</b> ' . $dostavka . '</p>
        </td>
      </tr>           
    </table>
  </body>';
Николай
Николай
Александр, в общем на примере с php получилось всё реализовать, zayavka.kubovik.ru а на примере AJAX как только подключаю туда выбор по списку — письма не отправляются
можете мне написать на эл.почту? kimisun@ya.ru, не хочется флудить в комментариях
Александр
Александр
Так и применить.
Просто php переменные вставлять не в параграфы а в ячейки таблицы
Алексей
Алексей
Спасибо за статью, воспользовался вашим примером.

Но у меня проблема в IE (MS Edge) с кириллическим доменом, капча не работает, предлагает перейти на поддерживаемый браузер. Проверял на домене .ru — все ок.
Александр Мальцев
Александр Мальцев
В домены, на которых должна работать гугловская рекапча, необходимо добавить кириллический домен в формате punycode.
Алексей
Алексей
Вы имеете ввиду в настройках recaptcha на сайте гугла? Там добавлен в обоих форматах (в firefox работает).
Александр Мальцев
Александр Мальцев
Значит какой-то баг в IE (MS Edge). Посмотрите, как он шифрует кириллическое доменное имя. Скорее всего, что-то в этом месте не так. Не знаю чем помочь, только если отправить bug report в Microsoft.
Наиболее простой выход — это использовать редирект с кириллического домена на обычный. Это вариант и для поискового продвижения лучше, т.к. контент в этом случае не будет доступен по нескольким URL.
Олег
Олег
Встает закономерный вопрос, если привязывать рекапчу к ссылке или напрямую к url, то нужные боты — яндекса, гугла и т.д. тоже будут на ней спотыкаться?
Александр Мальцев
Александр Мальцев
Вообще не пониманию, зачем защищать ссылку, которая содержит «прямой» URL. Не знаю, какие поисковые системы используют алгоритмы, но в этом случае её можно прочитать. Надо защищать то, что действительно может заспамить сайт. Например, её можно поставить на скачивание файла, чтобы защитить его от нереальных загрузок. Т.е. ставите ссылке некоторый идентификатор, который будет определять ссылку, которую вы отдадите пользователю на скачивание этого файла, после того как он пройдёт проверку reCAPTCHA. В этом случае только реальные пользователи её смогут скачать.
Олег
Олег
Дело в том, что у меня доска объявлений. Ну за страницы с формами добавления и входа на сайт я не переживаю, ботам яндекса и гугла там делать нечего, но вот есть некоторые страницы которые подвергаются прямо бомбардировке и явно, это дело рук ботов — время на странице 0 секунд. Удалять их не хочется они все в топе, ip ботов меняются. И все это создаёт нагрузку на БД.
Александр Мальцев
Александр Мальцев
Ну тогда попробуйте, на сервере определять, какой пользовательский агент посещает страницу ($_SERVER['HTTP_USER_AGENT']). Если это роботы Яндекса или Google, то отдавать одно содержимое страницы. А если нет, то другое.
Олег
Олег
Александр здравствуйте! Такой вопрос, а можно ли привязать рекапчу к ссылке выполненной в с изображением, чтоб при нажатии на нее срабатывала рекапча?
Пример моей ссылки:
<a href="/" title="">
<img id="bu1" src="img/1.png" border="0"
 onmouseover="document.getElementById('bu1_ro').style.display='';
 document.getElementById('bu1').style.display='none';">
 <img id="bu1_ro" src="img/2.png"
 border="0" onmouseout="document.getElementById('bu1').style.display='';
 document.getElementById('bu1_ro').style.display='none';" style="display: none;">
 </a>
Александр Мальцев
Александр Мальцев
Здравствуйте, можно привязать к любому действию на сайте.
Олег
Олег
Александр, если не трудно, помогите с реализацией invisible reCAPTCHA для ссылки.
Я так понимаю нужно подключить —
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
и добавить к ссылке
<a href="/" class="g-recaptcha" data-sitekey="your_site_key" data-callback="onSubmit"></a>
А вот как быть с этим
<script>
function onSubmit(token) {
  // отправить форму на сервер
  document.getElementById("messageForm").submit();
}
</script>
и всем остальным?
Александр Мальцев
Александр Мальцев
HTML:
<div class="g-recaptcha" data-sitekey="публичный_ключ" data-callback="onSubmitReCaptcha" data-size="invisible"></div>
<a id="recaptcha" href="#">Ссылка</a>
JavaScript:
// действие, которое будем совершать при нажатии на кнопку
function onSubmitReCaptcha(token) {
  // получаем ответ reCAPTCHA и добавляем его в набор данных для отправки на сервер 
  var formData = new FormData();
  formData.append('g-recaptcha-response', grecaptcha.getResponse());
  // выполняем AJAX запрос
  $.ajax({
    type: "POST",
    url: 'processurl.php',
    data: formData,
    contentType: false,
    processData: false,
    cache: false,
    success: function(data) {
      if (data) {
        // осуществляем переход URL, который вернул сервер
        window.location.href = data;
      }
    }
  });
}

// при нажатию на ссылку
$('#recaptcha').click(function(event) {
  // отменим стандартное поведение
  event.preventDefault();
  // проверяем действие пользователя с помощью invisible reCAPTCHA
  grecaptcha.execute();
});
Файл processurl.php (на сервере):
<?php
// если запрос не AJAX, то возвращаем ошибку и завершаем работу скрипта
if (empty($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {
  echo '';
  exit();
}
// блок проверки invisible reCAPTCHA
require_once (dirname(__FILE__).'/recaptcha/autoload.php');
// если в массиве $_POST существует ключ g-recaptcha-response, то...
if (isset($_POST['g-recaptcha-response'])) {
  // создать экземпляр службы recaptcha, используя секретный ключ
  $recaptcha = new \ReCaptcha\ReCaptcha('секретный_ключ');
  // получить результат проверки кода recaptcha
  $resp = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
  // результат проверки
  if ($resp->isSuccess()){
    // отдаём адрес URL
    echo 'https://www.yandex.ru/';
    exit();
  } 
}
echo '';
exit();
?>
Олег
Олег
Александр поясните пожалуйста этот кусочек
// отдаём адрес URL
    echo 'https://www.yandex.ru/';
    exit();
Вроде я все сделал, но непонятно работает или нет, как можно проверить, ведь капча не отображается?
Александр Мальцев
Александр Мальцев
Вам же зачем-то необходимо защитить переход по ссылке. Т.е. изначально у вас имеется ссылка, которая не на что не ссылается. При нажатии на ссылку reCAPTCHA проверяет, не является ли данное действие подозрительным (т.е. не совершает ли его робот). Если да, то предлагает пользователю решить капчу. Если пользователь решает данную капчу или его действие не является подозрительным выполняется функция onSubmitReCaptcha. На сервере вы проверяете результат проверки капчи и выполняете какие-то действия, например, возвращаете какой-то адрес URL. В примере просто был отдан адрес Яндекса, и после этого завершена работа серверного скрипта. После этого клиент получает ответ, в данном случае адрес Яндекса и осуществляет переход на этот URL.
Проверяйте в режиме инкогнито, после нескольких переходов, он запросит у вас решить капчу.
Александр
Александр
Александр, приветствую, как всегда появились вопросы.
После нажатия на кнопку отправки формы
адресная строка браузера выглядит так _http://m..w.ru/index.php/cells/fabric-cells?g-recaptcha-response="
Форма не оправилась.
Из js файла и php файла удалил части связанные с отправкой файлов.

в консоли ошибка Uncaught reference: sendForm is not defined

Где может быть проблема?
Александр
Александр
От ошибки избавился но ничего не отправилось.
еще было изменение
function onSubmitReCaptcha(token) {
  var idForm = 'messageForm';
  var sender = location.origin + '/measures.php';
  sendForm(document.getElementById(idForm), sender);
}
Александр Мальцев
Александр Мальцев
Проверьте метод отправки формы. У вас почему-то параметр g-recaptcha-response появляется в URL.
А также части которые вы удалили из файлов, т.к. функция sendForm, используется для отправки формы обратной связи на сервер.
Александр Мальцев
Александр Мальцев
Если у вас файл measures.php расположен в корне сайта, то можно и так.
Александр
Александр
Отлично.
Исчерпывающе.
Александр, благодарю Вас.