Добрый день разработчик Impera CMS. Могли бы вы написать инструкцию по замене стандартной каптчи в формах обратной связи, корзине, обратном звонке и так далее на гугловскую?
Добрый день разработчик Impera CMS. Могли бы вы написать инструкцию по замене стандартной каптчи в формах обратной связи, корзине, обратном звонке и так далее на гугловскую?
Прежде всего скажу несколько предложений всем владельцам интернет-магазинов, кто ещё не в курсе, что это такое реКАПЧА. Это бесплатный сервис reCAPTCHA от компании Google, который служит для защиты вашего сайта от спама и прочих непредусмотренных манипуляций формами ввода на сайте. К настоящему моменту сервисом реализовано 4 версии капчи.
reCAPTCHA v1
Эта версия морально устарела и к тому же имела сложную схему подключения. Верификация пользователя происходила с помощью задач типа: распознать номер дома на фотографии, распознать плохо сосканированное слово из книги.
reCAPTCHA v2
В этой версии сервис использует механизм анализа рисков, основанный на статистическом наблюдении за активностью, совершённой с пользовательского хоста (ip-адреса) за последнее время. Чем больше в этой активности обнаруживается действий, вызывающих подозрение в автоматизме, тем вероятнее произойдёт усложнение процедуры верификации пользователя на предмет одушевлённости.
Для процедуры верификации сервис использует адаптивную капчу в виде всплывающего окна с несложным для живого человека ребусом (или серией ребусов), предлагаемого пользователю решить прямо сейчас. Примеры задач: среди предложенного набора картинок нажать изображения с какой-то деталью на снимке, нажать все квадраты картинки на которых есть некая деталь или её часть.
reCAPTCHA Invisible
Эта версия похожа на reCAPTCHA v2, но без механизма верификации. То есть невидимая reCAPTCHA просто запрещает принятие данных из формы ввода, если действия пользователя напоминают автоматические.
Подойдёт для случая, когда мы не хотели бы дразнить своего посетителя капчей, однако желаем сразу отсечь злостных спамеров. Притом мы соглашаемся и понимаем, что преграду скорее всего пройдут спамеры, постящие формы ввода в ручном режиме и не слишком часто, чтобы их засекли как роботов.
reCAPTCHA v3
В предшествующих версиях ответом сервиса было всего 2 варианта: "разрешить" или "запретить", а предметом изучения было единственное действие "разгадывание ребуса". Это означало вашу зависимость от мнения сервиса - некоторый добропорядочный посетитель мог быть отклонён просто потому, что не понравился своей прежней активностью сервису.
В третьей версии reCAPTCHA вводит градацию ответов - это дробное число от 0 (запретить) до 1 (разрешить) - и тем самым оставляет решение вам, как же поступить с этим пользователем. Своим ответом сервис как бы предупреждает, например "с вероятностью 0.7634 этот хост не является роботом".
Ещё одно новшество - добавлено понятие action (действие) и возможность определять для своего сайта какой-то набор действий, которые мы хотели бы анализировать в результатах разгадывания ребуса. Причём каждое заявленное действие будет представлено своим значением из градации ответов от 0 до 1.
Ну грубый пример:
Другое новшество - среди ребусных заданий появится разгадывание произносимых диктором слов.
Чтобы установить гугловскую капчу на сайт, нужно зарегистрировать домен вашего сайта в админпанели сервиса reCAPTCHA и получить там идентифицирующий КЛЮЧ-ВАШЕГО-САЙТА и приватный ВАШ-СЕКРЕТНЫЙ-КЛЮЧ. Затем в желаемых формах ввода вашего шаблона сайта нужно поставить тег реКАПЧИ, а в программную логику модулей, связанных с этими формами ввода, нужно добавить контроллер реКАПЧИ.
Так как версия 3 всё ещё находится в стадии бета-тестирования и поэтому пока не рекомендуется к применению, инструкции ниже будут касаться только reCAPTCHA v2.
Объясню на примере формы ввода для обратной связи. Откройте макет её страницы - это файл feedback.htm - и найдите тег формы ввода, а в нём тег с полем или картинкой стандартной капчи.
Например, картинку можно найти в разметочном коде макета по фразе captcha.jpg или найти поле ввода капчи по фразе name="captcha".
Удалите вообще или скройте инлайновым стилем style="display: none" весь блок разметки, касающийся найденной вами стандартной капчи.
Здесь следует приготовиться, что внешний вид формы ввода может "поплыть", если был рассчитан на точное количество полей. В таком случае скрывайте блок инлайновым стилем style="opacity: 0; z-index: -1; pointer-events: none". Правда, в форме возникнет некрасивое пустое место - дырка. И вторая правда, при TAB-перемещении с клавиатуры по полям ввода, пользователь не будет понимать, куда это исчез текстовый курсор при одном из нажатий клавиши TAB (на самом деле курсор в этот момент окажется на невидимом поле ввода).
Вместо удаленного или скрытого блока добавьте вот такой тег гугловской капчи:
<div class="g-recaptcha" data-sitekey="КЛЮЧ-ВАШЕГО-САЙТА"></div>
<script src="https://www.google.com/recaptcha/api.js"></script>
Вообще говоря, скрипт желательно вынести в конец общего макета - это файл index.tpl, - потому что на странице может оказаться несколько форм ввода, и тогда возникнет множественное подключение скрипта.
Когда пользователь заходит на страницу вашего сайта, где присутствует форма ввода с установленным тегом реКАПЧИ, первым делом подгружается её скрипт.
А может, грузить в асинхронном режиме? В теории, сайт Google доступен постоянно и отдаст свой скрипт мгновенно. Но практика показывает всякое: узкие мобильные каналы, временно лёгшие соединения и прочее тормоза. В связи с разной загруженностью линий, ресурсы даже с крутых сервисов могут грузиться далеко не за секунду. Тогда в принципе возможно такое, что пользователь успеет набрать и отправить форму быстрее, чем подгрузится скрипт. Ведь в асинхронном режиме подкачка ресурса может быть отложена сколько угодно далеко на потом. Такой пост будет потерян для вас.
Затем подгрузившийся скрипт находит в разметке текущей страницы свои DIV-теги с ключём вашего сайта, отправляет идентифицирующий запрос в сервис и, если оттуда пришёл одобряющий вердикт по вашему сайту, рисует на месте каждого обнаруженного DIV-а начальное окошко капчи.
Когда пользователь заполнил форму ввода и затем закончил разгадывать всплывший ребус, из сервиса приходит некий идентификатор как метка результата, хранимого сейчас на сервере reCAPTCHA. Скрипт добавляет эту метку в форму ввода и постит форму на ваш сайт.
Теперь скрипт на серверной стороне сайта ловит эту форму, и прежде всего отдаёт контроллеру реКАПЧИ, и ждёт от него ответ: разрешить или запретить.
Контроллер извлекает из формы ту самую внедрённую метку и делает запрос на сервер reCAPTCHA, который по этой метке находит у себя в базе, какой там хранился результат разгадывания ребуса. Таким образом результат становится известен контроллеру, а он отдаёт его серверному скрипту сайта.
В зависимости от полученного ответа, скрипт на серверной стороне сайта либо продолжает обработку принятой формы ввода по своей стандартной схеме, либо отправляет пользователю сообщение "Вы не прошли проверку!".
По сути это исходный код некоторого контрольного метода checkRecaptcha, который вы вставите в нужный класс на серверной стороне сайта и вызовете этот метод, когда потребуется проверить результат разгадывания капчи.
protected function checkRecaptcha () { $field = 'g-recaptcha-response'; if (! $this->cms->request->existsPost($field)) { $this->pushError('Отсутствует капча!'); return FALSE; } $id = $this->cms->request->getPost($field); if (empty($id)) { $this->pushError('Отсутствует идентификатор капчи!'); return FALSE; } $url = 'https://www.google.com/recaptcha/api/siteverify' . '?secret=ВАШ-СЕКРЕТНЫЙ-КЛЮЧ' . '&response=' . $id; $handle = curl_init(); curl_setopt($handle, CURLOPT_URL, $url); curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1); curl_setopt($handle, CURLOPT_TIMEOUT, 10); $result = curl_exec($handle); curl_close($handle); $result = json_decode($result, TRUE); if (empty($result['success'])) { $this->pushError('Капча разгадана неправильно!'); return FALSE; } return TRUE; }
Метод возвращает TRUE в случае успешного прохождения капчи и возвращает FALSE в случае подозрения на спам. Также предполагается, что в том классе, куда позже вставим этот метод, уже будет существовать некий метод pushError, заталкивающий сообщение ошибки на экран пользователя. Ещё предполагается, что в том же классе будет существовать некое свойство cms, с помощью которого можем обратиться к объекту сайтового движка.
Здесь возможны 3 места для вставки:
В хелпер шаблона - таковым является файл helper.php, находящийся в папке страничных макетов текущей темы вашего сайта.
Достоинством подобной вставки является независимость от обновления движка, то есть CMS-ки сайта. А недостаток - что на сайте с несколькими шаблонами (например, один для десктопа, другой для смартфонов) серверная часть реКАПЧИ будет действовать только для текущего шаблона, и для надёжной защиты от спама надо повторять ту же самую вставку во всех одновременно используемых темах сайта.
Вот пример, как выглядит простейший хелпер с интегрированной реКАПЧЕЙ. Принцип работы следующий: если в момент старта шаблона обнаружен какой-то постинг формы, тут же вызываем контрольный метод реКАПЧИ, и если он ответил "запретить", тогда уничтожаем запостченные данные формы.
<?php class TemplateEmulator { protected $cms = null; здесь метод checkRecaptcha (код смотрите выше) protected function pushError ( $msg ) { $this->cms->push_error($msg); } protected function seePost () { return !empty($_POST); } protected function killPost () { unset($_POST); unset($_REQUEST); } public function __construct ( & $cms ) { $this->cms = & $cms; if ($this->seePost()) { if ($this->checkRecaptcha()) return; $this->killPost(); } } } ?>
Здесь мы обеспечили наличие оговоренного выше движкового свойства cms и метода вывода ошибки, а также добавили ещё один метод seePost для обнаружения поста и один метод killPost для уничтожения его данных.
В модуль обратной связи - таковым является файл feedback/Feedback.php, находящийся в папке объектов движка.
Достоинством подобной вставки является одновременное действие на любое количество тем, которые установлены сейчас на сайте. А недостаток - что это "костыльный" способ и при обновлении движка наша вставка затрётся, так как мы фактически полезли изменять стандартный модуль системы.
Вот пример, как выглядит стандартный модуль с интегрированной реКАПЧЕЙ. Суть изменения в том, что стандартный модуль обратной связи имеет изначально пустой метод checkExternalConflicts для проверки конфликта принятой формы с некими внешними данными (метод должен возвратить FALSE, если конфликта нет и разрешено обрабатывать форму далее), и мы просто пишем в этот метод свою логику по проверке результата разгадывания ребуса.
<?php ... class ClientFeedback extends FeedbackREFModel { ... здесь метод checkRecaptcha (код смотрите выше) protected function disableCaptcha () { $prefix = $this->getSettingsPrefix(); $this->cms->any->settings->set($prefix . 'captcha_disabled', 1); } protected function checkExternalConflicts ( & $item, & $bad_fields, & $data = null ) { $result = $this->checkRecaptcha(); if ($result) $this->disableCaptcha(); return ! $result; } ... } ?>
Здесь мы добавили ещё один метод disableCaptcha для локального выключения функции проверки стандартной капчи. Ведь то что мы удалили из разметки или скрыли инлайновым стилем поле ввода капчи на клиентской стороне, ещё не означает, что серверная сторона перестала ожидать это поле. Иначе вслед за успешно пройденной reCAPTCHA будет возникать сообщение об ошибке "Неправильно ввели капчу", касающееся той неотключенной функции.
В порождённый модуль обратной связи - таковым станет воображаемый файл, например назовём этот модуль как refeedback/Refeedback.php. Мы породим его от стандартного модуля обратной связи и тоже разместим в папке объектов движка.
Достоинством подобной вставки является независимость от обновления движка и хорошая (некостыльная) практика. А недостаток - что в админпанели один раз придётся сделать несколько нудных действий по регистрации модуля, затем отсоединению страниц типа "Контакты" от стандартного модуля (и выключить его, чтобы через него не сыпался спам) и присоединению этих страниц к нашему новому модулю.
Вот пример, как выглядит порождённый модуль с интегрированной реКАПЧЕЙ. Здесь мы подключили класс стандартного модуля, объявили свой класс ClientRefeedback, унаследовав его от стандартного. Затем прописали пару параметров, если хотим, чтобы новый модуль имел собственный набор настроек и мог настраиваться независимо от старого модуля. И в конце дописали проверочную логику.
<?php require_once(dirname(__FILE__) . '/../feedback/Feedback.php'); class ClientRefeedback extends ClientFeedback { public $settings_category = 'Обратная связь с Recaptcha'; protected $settings_model = 'refeedback'; здесь метод checkRecaptcha (код смотрите выше) protected function disableCaptcha () { $param = 'captcha_disabled'; $prefix = $this->getSettingsPrefix(); $this->cms->any->settings->set($prefix . $param, 1); $prefix = $this->getDBModel() . '_'; $this->cms->any->settings->set($prefix . $param, 1); } protected function checkExternalConflicts ( & $item, & $bad_fields, & $data = null ) { $result = $this->checkRecaptcha(); if ($result) $this->disableCaptcha(); return ! $result; } } ?>
На мой взгляд, вариант 2 самый лёгкий в реализации, но идеальным решением был бы третий вариант.
Спасибо. С формой обратной связи ясно, работает. Но когда проделываешь это с регистрацией по 2 методу, страница выдает ошибку 500. Проблема вероятно в этом коде. В чем проблема?
protected function checkExternalConflicts ( & $item, & $bad_fields, & $data = null ) {
$result = $this->checkRecaptcha();
if ($result) $this->disableCaptcha();
return ! $result;
}
Изложу стандартные действия вебмастера, который вставил какой-то кусок кода на сайт и страница вдруг слетела. Сразу извиняюсь, что начинаю учить вебмастера азам поискам проблемы, но видимо этот урок здесь уместен.
Итак добавляю указанный вами фрагмент кода:
Шаг 1 Сразу пытаюсь выяснить, проблема локальная, то есть связанная только с моими действиями по месту, или проблема кроется где-то за пределами двух изменяемых файлов, то есть когда мои действия провоцируют незамеченный баг в родительских уровнях.
Значит смотрю верхнюю часть кода изменённых файлов с целью понять, от кого были порождены оба модуля. Смотрю первый файл - модуль обратной связи:
<?php
require_once(dirname(__FILE__) . '/../.ref-models/Feedback.php');
class ClientFeedback extends FeedbackREFModel {
...
}
И второй файл - модуль регистрации:
<?php
require_once(dirname(__FILE__) . '/../.ref-models/Feedback.php');
class ClientRegistration extends FeedbackREFModel {
...
}
Оба порождаются идентично, отличается только имя дочернего класса. Значит при вставке вашего фрагмента кода я что-то нарушаю во втором модуле, то есть проблема локальная.
Шаг 2 Тогда я сравниваю внутренности файлов, предполагая что во втором файле есть некое свойство или метод, конфликтующий с выполненной вставкой.
Прокручивая исходники модуля регистрации, я замечаю, что там уже существует метод checkExternalConflicts, и в нём запрограммирована какая-то проверка валидности купонного кода, предоставленного клиентом во время регистрации.
protected function checkExternalConflicts ( & $item, & $bad_fields, & $data = null ) { // если купон не запрещен для регистрации if (!$this->settings->getAsBoolean('coupons_registration_disabled')) { // пробуем найти такой купон $value = $this->getProperty($item, 'coupon'); if ($value != '') { $filter = new stdClass; $filter->code = $value; $filter->enabled = 1; $filter->deleted = 0; $this->cms->db->coupons->one($data, $filter); // если купона нет или срок действия истек $prefix = $this->getPostPrefix(); if (empty($data)) { $msg = 'Купон с таким кодом не существует.'; $bad_fields[$prefix . 'coupon'] = $msg; return $this->pushError($msg); } elseif ($data->count < 1) { $msg = 'Купон с таким кодом уже был использован кем-то ранее.'; $bad_fields[$prefix . 'coupon'] = $msg; return $this->pushError($msg); } elseif (empty($data->valid)) { $msg = 'Срок действия купона с таким кодом истек ' . $data->expired_date . ' в ' . $data->expired_time . '.'; $bad_fields[$prefix . 'coupon'] = $msg; return $this->pushError($msg); } } // иначе купоны запрещены для регистрации } else { $item->coupon = ''; } return FALSE; }
Следовательно, вставка фрагмента кода приводила к повторной декларации метода, вот тут и возникала ошибка 500.
Шаг 3 Именно для такой реализации модуля, когда в нём уже есть какая-то проверка внешних конфликтов, мне нужно было вставить фрагмент кода так, чтобы сохранилась и эта проверка, и вслед за ней выполнилась проверка конфликтов Recaptcha.
То есть заменить последнюю строчку метода вот такими строками, что помечены этим цветом:
protected function checkExternalConflicts ( & $item, & $bad_fields, & $data = null ) { // если купон не запрещен для регистрации if (!$this->settings->getAsBoolean('coupons_registration_disabled')) { // пробуем найти такой купон $value = $this->getProperty($item, 'coupon'); if ($value != '') { $filter = new stdClass; $filter->code = $value; $filter->enabled = 1; $filter->deleted = 0; $this->cms->db->coupons->one($data, $filter); // если купона нет или срок действия истек $prefix = $this->getPostPrefix(); if (empty($data)) { $msg = 'Купон с таким кодом не существует.'; $bad_fields[$prefix . 'coupon'] = $msg; return $this->pushError($msg); } elseif ($data->count < 1) { $msg = 'Купон с таким кодом уже был использован кем-то ранее.'; $bad_fields[$prefix . 'coupon'] = $msg; return $this->pushError($msg); } elseif (empty($data->valid)) { $msg = 'Срок действия купона с таким кодом истек ' . $data->expired_date . ' в ' . $data->expired_time . '.'; $bad_fields[$prefix . 'coupon'] = $msg; return $this->pushError($msg); } } // иначе купоны запрещены для регистрации } else { $item->coupon = ''; } // проверка Recaptcha $result = $this->checkRecaptcha(); if ($result) $this->disableCaptcha(); return ! $result; }
Подобным образом можно провести анализ кода при слетании любой страницы от вставки нескольких строк кода в режиме нефильтруемого копипаста.