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

Александр Мальцев
Александр Мальцев
50K
81
Содержание:
  1. Что собой представляет invisible reCAPTCHA от Google
  2. Регистрация домена и получение ключей
  3. Примеры форм обратной связи с invisible reCAPTCHA
  4. Комментарии

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

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

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

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

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

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

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

  1. Открываем страницу www.google.com/recaptcha/admin (при необходимости выполняем регистрацию и авторизацию на сайте).
  2. Заполняем форму "Регистрация сайта". Указываем название, отмечаем Invisible reCAPTCHA (тип reCAPTCHA), вводим домены (по одному встроке).
    reCaptcha - заполнение регистрационной карточки
    reCaptcha - заполнение регистрационной карточки
  3. Получаем ключи: публичный (для клиента) и секретный (для сервера).

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

Рассмотрим различные варианты установки капчи invisible reCAPTCHA на сайт (в формы обратной связи). Ознакомиться с примерами можно на GitHub.

Примеры feedback форм с invisible reCAPTCHA на Github

Форму по умолчанию необходимо помещать в корневую директорию сайта (т.е. /feedback/файлы_формы...). Настройка форм осуществляется посредством ввода публичного и секретного ключа, в также настройки почты (phpMailer).

Приведём основные шаги по внедрению невидимой капчи reCAPTCHA от Google в формы обратной связи.

Интеграция invisible reCAPTCHA в форму обратной связи (без AJAX)

Самый простой способ использования виджета невидимой reCAPTCHA на странице - это подключить необходимый JavaScript ресурс и добавить несколько атрибутов к вашей html кнопке.

Рассмотрим этот процесс пошагово:

  1. Добавить на страницу скрипт reCAPTCHA:
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
  2. Добавить к кнопке, которая будет использоваться для отправки формы или выполнения другого действия на сайте атрибуты (класс "g-recaptcha", в data-sitekey - публичный ключ сайта, в data-callback - имя JavaScript функции обратного вызова для выполнения действий после завершения проверки капчи):
    <form id="messageForm" method="POST" action="process.php">
      <input type="email" name="email" required="required">
      <textarea name="message"    required="required"></textarea>
      <!-- Кнопка, с подключённым к ней виджетом invisible reCAPTCHA -->
      <button class="g-recaptcha" data-sitekey="your_site_key" data-callback="onSubmit">Отправить</button>
    </form>
    
  3. Добавить на страницу сценарий JavaScript функцию onSubmit:
    <script>
    function onSubmit(token) {
      // отправить форму на сервер
      document.getElementById("messageForm").submit();
    }
    </script>
    

Посмотреть полный код формы с invisible reCAPTCHA от Google можно на GitHub или по ниже приведённой ссылке:

Простая контактная форма с invisible reCAPTCHA (Яндекс.Диск)

Пример простой формы с invisible reCAPTCHA
Пример простой формы с invisible reCAPTCHA

Добавление invisible 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
Пример 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

Дополнительные (необязательные) параметры капчи 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 и т.д.


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

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

  1. Дмитрий
    21 сентября 2019, 06:25
    Александр, здравствуйте. Скажите, пожалуйста, насколько эффективно использовать invisible recapcha для многостраничного сайта (более 50 тыс. страниц) для защиты от парсинга. У нас сайт-энциклопедия и часто новые материалы парсят конкуренты. Поможет ли невидимая капча сделать их деятельность по скачиванию нашего сайта более трудоемкой? Спасибо. С уважением, Д.Ж.
    1. Александр Мальцев
      21 сентября 2019, 13:39
      Здравствуйте! В любом случае это создаст для них трудности. А найдут ли они решения или нет зависит от того насколько у них для этого имеется возможностей.
      1. Дмитрий
        05 октября 2019, 07:07
        Спасибо за ответ. Хотел еще поинтересоваться, а будет ли invisible recapcha по умолчанию «отстреливать» белые боты (краулеры Яндекса или Гугл)?
        1. Александр Мальцев
          05 октября 2019, 07:37
          Не проверял.
          В этом случае можно в php добавить проверку на нужных ботов. Если они пришли, то отдавать страницу без invisible recapcha.
    2. Dokar
      05 марта 2019, 13:09
      Благодарю за вашу работу. Все работает. Все отлично.
      Единственный момент, когда создавал ключи рекапчи, оказалось, что сегодня в природе 4 типа рекапчи.
      Вопрос:
      Использую форму с капчей: yadi.sk/d/Wk5GUzlI3GFCHp
      Не подскажете, какой код добавить, чтобы после отправки письма уведомление «Внимание! Форма была успешно отправлена.» не висело бесконечно. А спустя какое то время форма возвращалась к исходному состоянию?
      Спасибо.
      1. Александр Мальцев
        07 марта 2019, 06:48
        Спасибо!
        Лучше наверно в сообщение добавить ссылку, при нажатию на которую форма будет возвращаться в исходное состояние.
        Для этого необходимо в сообщение добавить ссылку:
        <!-- Сообщение, отображаемое в случае успешной отправки данных -->
        <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');
          })
        };
        
        Архив этой формы расположен здесь.
      2. Александр
        09 августа 2018, 09:38
        Александр. День добрый.
        Вопрос по отправке сообщения после всех проверок.
        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 по указанному пути?
        И если да, как с этим справиться?

        1. Александр Мальцев
          10 августа 2018, 17:51
          Добрый! Александр, вам нужно установить и настроить SMTP Server. А также внести настройки в конфигурационный файл php.ini.
          1. Александр
            10 августа 2018, 18:00
            Да, Александр, благодарю.
            Похоже, что IIS 8 больше не содержит SMTP сервера и просто так от него не добиться SMTP-relay, по-этому и грабли.
        2. Александр
          02 августа 2018, 12:34
          Александр, приветствую.
          У меня вопрос немного не по использованию Рекапча а по 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?
          1. Александр
            02 августа 2018, 12:47
            Вопрос снят, вопрос решился добавлением session_start() в process.php (невнимательность моя).
          2. Иван
            30 июля 2018, 18:07
            Здравствуйте! Прекрасная форма! Но у меня почему то не работает…
            Сайт на Prestashop 1.7.3, кладу папку в корень сайта, меняю ключ капчи, открываю страницу, заполняю поля, хочу отправить, а мне пишет «Произошла ошибка при отправке данных.». Как отследить где проблема?
            Исходящая почта работает, проверял php кодом:
            <?
            mail(«ivan@mail.ru», «Тема», «Сообщение»);
            ?>
            1. Иван
              31 июля 2018, 10:45
              Вот это в консоле. В чем проблема?
              1. Александр Мальцев
                31 июля 2018, 13:43
                Скорее всего не выставлены права на изменение и на запись. Неоходимо проверить этот момент.
                Общие рекомендации:
                1. В php файле (в данном случае process.php) необходимо добавить строки:
                <?php
                ini_set('display_errors',1);
                error_reporting(E_ALL);
                Это включит отображение ошибок.
                2. Ошибки можно смотреть в «инструментах разработчика» браузера на вкладке Network. На этой вкладке необходимо выбрать файл process.php и посмотреть response (ответ).
                1. Иван
                  01 августа 2018, 13:09
                  Ключ гугла надо прописать только в 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 />
                  1. Александр Мальцев
                    01 августа 2018, 14:11
                    Секретный ключ необходимо прописать в файл process.php.
                    В этом файле есть строчка:
                    $secret = ''; // ваш секретный ключ
                    
                    1. Макс
                      23 декабря 2018, 00:57
                      Александр, подскажите, пожалуйста.
                      Нашел файл process.php, строки $secret в нем не было. Вставил строку с кодом, но ошибка в строке 61 осталась. Капча почему то не видит кода…
                      Uncaught Error: Missing required parameters: sitekey
                      Что можно сделать или проверить?
                      1. Александр Мальцев
                        24 декабря 2018, 14:26
                        Эта 6 строчка в файле process.php.
                        1. Макс
                          24 декабря 2018, 16:24
                          Но у меня в файле process.php шестая строка это $filenames = array();
                          Если вставляю $secret под ней, ошибка остается… ://
                      2. Иван
                        01 августа 2018, 16:56
                        Какой же я слепой!!! Много раз просмотрел файл и слона та не приметил в самом начале!!! Огромное вам спасибо за помощь, все заработало!
              2. bigvax
                06 февраля 2018, 13:17
                День добрый!
                Я объединил две ваших статьи — о 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 и передаются в форму, где могут быть использованы

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

                1. Александр Мальцев
                  06 февраля 2018, 16:11
                  Добрый день! Круто получилось.
                  1. bigvax
                    07 февраля 2018, 06:20
                    Спасибо за высокую оценку!

                    Вот только, мне несколько не нравится мой блок парзинга/обработки передаваемых параметров, в файле 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 — это не мой «конек» ;)
                    Может-быть ваш опыт подскажет, что-ли более оптимальное… ;)
                    1. Александр Мальцев
                      08 февраля 2018, 14:52
                      Мне понравился принцип. Код ещё не смотрел. Напишу, как его можно улучшить.
                2. Александр
                  11 сентября 2017, 08:11
                  Здравствуйте!
                  У меня в 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 происходит. На Битриксе, например, все работает.
                  1. Александр Мальцев
                    11 сентября 2017, 14:40
                    Здравствуйте! Вам необходимо обеспечить доступ к файлу, с помощью которого вы осуществляете обработку формы (process.php) с клиента. Для проверки доступа попробуйте его (process.php) открыть из браузера. Тут, скорее всего дело в настройках htaccess.
                    1. Александр
                      12 сентября 2017, 09:11
                      Из браузера не открывается, тоже самое пишется.
                      в .htaccess я положил
                      <Files process.php>
                      order allow,deny
                      allow from all
                      </Files>
                      
                      не помогает ((
                  2. Евгений
                    26 мая 2017, 14:22
                    Все супер, все работает.
                    Алексадр, как сделать на эту форму всплывающее окно, через кнопку. Спасибо!
                    И куда поблагодарить $?
                    1. Александр Мальцев
                      27 мая 2017, 04:58
                      Добавил в статью пример формы, открывающейся во всплывающем окне.
                      Поблагодарить можно с помощью кнопки «Поддержать сайт», расположенной в футере.
                    2. Александр
                      24 мая 2017, 09:38
                      Александр, здравствуйте.
                      Я к вам снова с ошибками.

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


                      Вот код в этом месте: <a href="">
                      Из-за чего такое возможно?
                      1. Александр Мальцев
                        24 мая 2017, 12:27
                        Ошибка связана с тем, что php-скрипт возвращает ответ не в формате JSON. Надо смотреть, почему он отрабатывает не правильно и где возникает ошибка. Для этого можно воспользоваться вкладкой Network в браузере.
                        1. Александр
                          24 мая 2017, 13:18
                          Вкладка Network говорит так:
                          1. Александр Мальцев
                            24 мая 2017, 15:26
                            Во вкладке Network необходимо смотреть ответ сервера (Response).
                            1. Александр
                              25 мая 2017, 07:25
                              Этот запрос не имеет данных для ответа

                              This request has no response data available.

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

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

                              Отправка письма закомментирована, пока хочу просто добиться загрузки файла на сервер.
                              1. Александр Мальцев
                                26 мая 2017, 17:19
                                Поправил код на Github и в архиве. Обнови php-файлы и посмотри, какой сейчас придёт ответ.
                                1. Александр
                                  27 мая 2017, 12:12
                                  Я так понимаю, что это «Произошла ошибка при отправке формы» — этот ответ формируется, если не получилось сохранить/создать/записать файл message.txt

                                  Ну как- бы на этот файл можно закрыть глаза. До этого на сервере должен, по идее, сохраниться файл с формы. А он не сохраняется.
                                  1. Александр
                                    27 мая 2017, 07:53
                                    {«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."}
                                    1. Александр Мальцев
                                      27 мая 2017, 15:40
                                      Значит, у вас ошибка возникает в строчке:
                                      if (file_put_contents(dirname(__FILE__).'/message.txt', $output, FILE_APPEND | LOCK_EX)) {
                                      
                                      А это значит, что php функция file_put_contents скорее всего не может создать файл и записать в него данные. Проверьте права доступа.

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

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

                                        И по поводу загрузки файла/картинки на сервер. Не срабатывает функция создания текстового файла на сервере, но до нее, же идут функции именно записи на сервер файла, который на форме прикрепили, и они, я так понимаю, отрабатывают, то есть файл, который на форме прикрепили, теоретически куда-то должен был на сервере записаться, а его нет.
                                        1. Александр Мальцев
                                          28 мая 2017, 07:30
                                          На apache, nginx — работает. На IIS не подскажу, т.к. его не использую. Попробуйте создать простой php файл из одной только этой функции и проверьте (посмотрите логи IIS):
                                          <?php
                                          $output = 'Некоторые данные...';
                                          file_put_contents(dirname(__FILE__).'/message.txt', $output, FILE_APPEND | LOCK_EX));
                                          
                                          Может, кто вам другой из пользователей сайта подскажет или как вариант, почитать соответствующие темы на форумах компании Microsoft.
                                          1. Александр
                                            28 мая 2017, 08:16
                                            Благодарю. Результат очень странный:
                                            $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
                                            1. Александр
                                              28 мая 2017, 08:20
                                              Перепроверил. по первому варианту тоже работает отдельный скрипт.

                                              Вдруг кому пригодится: помимо прав на ЗАПИСЬ в каталог должны быть установлены права на ИЗМЕНЕНИЕ (если IIS)
                                              1. Александр
                                                29 мая 2017, 09:07
                                                Александр, если я правильно понимаю, то вот эта часть кода:
                                                // обработка переданных файлов (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 создается, данные в него записываются, но файл приложенный в форме — никуда не сохраняется.
                                                1. Александр
                                                  29 мая 2017, 09:36
                                                  Александр, проблему нашел.
                                                  Для добавления картинки в форму использую Этот плагин. Ну и Соответственно, если не выбрать «с кнопки» файл, а перетащить на предпросмотр, то input type=«file», остается пустым.
                                                  1. Александр Мальцев
                                                    29 мая 2017, 11:39
                                                    Отлично!
                                                    1. Александр
                                                      06 июня 2017, 15:05
                                                      Александр, приветствую.
                                                      Пытаюсь дальше обрабатывать загруженный файл. Не нашел соответствующего раздела.

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

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

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

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

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

                            В первом списке надо 10 наименований продукта, а во втором 2. Ну и далее по списку.
                            [ВЫБЕРИТЕ НАИМЕНОВАНИЕ ПРОДУКТА] <-это список из 10 наименований
                            [ФАСОВКА] <-здесь всего два способа
                            ИМЯ,
                            ТЕЛЕФОН,
                            ЭЛ.ПОЧТА
                            ТОЧНЫЙ АДРЕС ДОСТАВКИ
                            1. Николай
                              17 апреля 2017, 09:51
                              Кажется разобрался)
                              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>
                              
                              1. Александр Мальцев
                                17 апреля 2017, 15:02
                                Молодец!
                                1. Николай
                                  18 апреля 2017, 10:55
                                  Александр здравствуйте!)
                                  А как сделать что бы на указанный email пришло письмо с уведомлением о том, что «Ваша заявка принята в обработку»?
                                  1. Александр Мальцев
                                    18 апреля 2017, 14:24
                                    Здравствуйте!
                                    Для этого необходимо подготовить и отправить ещё одно письмо. Это необходимо сделать после того, как будет отправлено предыдущее письмо.
                                    Т.е. как-то так:
                                    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';
                                      }    
                                    }
                                    
                                    1. Николай
                                      18 апреля 2017, 15:53
                                      Спасибо большое! Всё работает!
                                      Здесь оказывается можно перечислить всё то, что заполняли в форму
                                        // отправляем ещё одно письмо
                                        $output = "Ваша заявка принята в обработку";
                                        $output .= '<p><b>Организация:</b> ' . $organiz . '</p>';
                                        $output .= '<p><b>Место выгрузки:</b> ' . $dostavka . '</p>';
                                        $output .= '<p><b>Химическая продукция:</b> ' . $second . '</p>';
                                      
                                      Не постесняюсь спросить ещё, как вставить ссылку на png картинку/логотип в output, что бы заполняющему форму, пришло более менее оформленное письмо?
                                      1. Александр Мальцев
                                        18 апреля 2017, 16:47
                                        Для того чтобы вставить картинку необходимо использовать HTML элемент img и полный адрес к картинке с учётом протокола.
                                        $output .= '<img src="http://mysite.ru/images/logo.svg" alt="Описание картинки">';
                                        
                                        1. Николай
                                          18 апреля 2017, 19:49
                                          Вот спасибо) Может быть есть какой нибудь материал, о том как оформить/украсить письмо, которое получает заполняющий форму?
                                          Например мне нужно изменить background письма, цвет текста, применить стили/колонки и всё такое прочее
                                          1. Александр Мальцев
                                            19 апреля 2017, 15:11
                                            Формируете разметку и вставляете в нужные места значения переменных:
                                            //формируем тело письма
                                            $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>';
                                            
                                            1. Николай
                                              20 апреля 2017, 12:38
                                              Александр, в общем на примере с php получилось всё реализовать, zayavka.kubovik.ru а на примере AJAX как только подключаю туда выбор по списку — письма не отправляются
                                              можете мне написать на эл.почту? kimisun@ya.ru, не хочется флудить в комментариях
                                            2. Александр Мальцев
                                              19 апреля 2017, 11:52
                                              Материалу по этой теме пока ещё нет.
                                              Но сделать простой HTML шаблон письма не очень сложно. Для этого используйте табличную вёрстку, т.е. вёрстку с помощью элементов table, td и tr. Для создания фона используйте CSS-свойство background-color.
                                              <body style="margin: 0; padding: 0; background-color: #f8f8f8;">
                                                <table>
                                                  <!-- Табличная вёрстка --> 
                                                </table>
                                              </body>
                                              
                                              1. Николай
                                                19 апреля 2017, 12:02
                                                Не понял как вот это
                                                <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>';
                                                
                                                1. Александр
                                                  11 сентября 2017, 08:21
                                                  Так и применить.
                                                  Просто php переменные вставлять не в параграфы а в ячейки таблицы
                              2. Алексей
                                14 апреля 2017, 16:14
                                Спасибо за статью, воспользовался вашим примером.

                                Но у меня проблема в IE (MS Edge) с кириллическим доменом, капча не работает, предлагает перейти на поддерживаемый браузер. Проверял на домене .ru — все ок.
                                1. Александр Мальцев
                                  15 апреля 2017, 11:00
                                  В домены, на которых должна работать гугловская рекапча, необходимо добавить кириллический домен в формате punycode.
                                  1. Алексей
                                    15 апреля 2017, 11:29
                                    Вы имеете ввиду в настройках recaptcha на сайте гугла? Там добавлен в обоих форматах (в firefox работает).
                                    1. Александр Мальцев
                                      15 апреля 2017, 12:10
                                      Значит какой-то баг в IE (MS Edge). Посмотрите, как он шифрует кириллическое доменное имя. Скорее всего, что-то в этом месте не так. Не знаю чем помочь, только если отправить bug report в Microsoft.
                                      Наиболее простой выход — это использовать редирект с кириллического домена на обычный. Это вариант и для поискового продвижения лучше, т.к. контент в этом случае не будет доступен по нескольким URL.
                                2. Олег
                                  02 апреля 2017, 14:31
                                  Встает закономерный вопрос, если привязывать рекапчу к ссылке или напрямую к url, то нужные боты — яндекса, гугла и т.д. тоже будут на ней спотыкаться?
                                  1. Александр Мальцев
                                    02 апреля 2017, 16:34
                                    Вообще не пониманию, зачем защищать ссылку, которая содержит «прямой» URL. Не знаю, какие поисковые системы используют алгоритмы, но в этом случае её можно прочитать. Надо защищать то, что действительно может заспамить сайт. Например, её можно поставить на скачивание файла, чтобы защитить его от нереальных загрузок. Т.е. ставите ссылке некоторый идентификатор, который будет определять ссылку, которую вы отдадите пользователю на скачивание этого файла, после того как он пройдёт проверку reCAPTCHA. В этом случае только реальные пользователи её смогут скачать.
                                    1. Олег
                                      02 апреля 2017, 19:50
                                      Дело в том, что у меня доска объявлений. Ну за страницы с формами добавления и входа на сайт я не переживаю, ботам яндекса и гугла там делать нечего, но вот есть некоторые страницы которые подвергаются прямо бомбардировке и явно, это дело рук ботов — время на странице 0 секунд. Удалять их не хочется они все в топе, ip ботов меняются. И все это создаёт нагрузку на БД.
                                      1. Александр Мальцев
                                        03 апреля 2017, 15:58
                                        Ну тогда попробуйте, на сервере определять, какой пользовательский агент посещает страницу ($_SERVER['HTTP_USER_AGENT']). Если это роботы Яндекса или Google, то отдавать одно содержимое страницы. А если нет, то другое.
                                  2. Олег
                                    31 марта 2017, 16:09
                                    Александр здравствуйте! Такой вопрос, а можно ли привязать рекапчу к ссылке выполненной в с изображением, чтоб при нажатии на нее срабатывала рекапча?
                                    Пример моей ссылки:
                                    <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>
                                    1. Александр Мальцев
                                      01 апреля 2017, 05:18
                                      Здравствуйте, можно привязать к любому действию на сайте.
                                      1. Олег
                                        01 апреля 2017, 12:31
                                        Александр, если не трудно, помогите с реализацией 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>
                                        и всем остальным?
                                        1. Александр Мальцев
                                          01 апреля 2017, 15:41
                                          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();
                                          ?>
                                          
                                          1. Олег
                                            02 апреля 2017, 00:49
                                            Александр поясните пожалуйста этот кусочек
                                            // отдаём адрес URL
                                                echo 'https://www.yandex.ru/';
                                                exit();
                                            Вроде я все сделал, но непонятно работает или нет, как можно проверить, ведь капча не отображается?
                                            1. Александр Мальцев
                                              02 апреля 2017, 01:55
                                              Вам же зачем-то необходимо защитить переход по ссылке. Т.е. изначально у вас имеется ссылка, которая не на что не ссылается. При нажатии на ссылку reCAPTCHA проверяет, не является ли данное действие подозрительным (т.е. не совершает ли его робот). Если да, то предлагает пользователю решить капчу. Если пользователь решает данную капчу или его действие не является подозрительным выполняется функция onSubmitReCaptcha. На сервере вы проверяете результат проверки капчи и выполняете какие-то действия, например, возвращаете какой-то адрес URL. В примере просто был отдан адрес Яндекса, и после этого завершена работа серверного скрипта. После этого клиент получает ответ, в данном случае адрес Яндекса и осуществляет переход на этот URL.
                                              Проверяйте в режиме инкогнито, после нескольких переходов, он запросит у вас решить капчу.
                                    2. Александр
                                      24 марта 2017, 17:43
                                      Александр, приветствую, как всегда появились вопросы.
                                      После нажатия на кнопку отправки формы
                                      адресная строка браузера выглядит так _http://m..w.ru/index.php/cells/fabric-cells?g-recaptcha-response="
                                      Форма не оправилась.
                                      Из js файла и php файла удалил части связанные с отправкой файлов.

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

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