+7 495 008 8452 пн.-пт. 10:00 – 17:00
Если у вас возникли какие либо вопросы которые вы не смогли решить по нашим публикациям самостоятельно,
то ждем ваше обращение в нашей службе тех поддержки.


Усиливаем CAPTCHA - Armored

Всем доброго времени суток!

Решил несколько разнообразить нашу замечательную CAPTCHA. Существует множество различных вариантов CAPTCHA, стандартно используется обычный ввод с картинки. В данной статье мы попытаемся добавить еще 3 варианта
1) Математическое выражение
2) Последовательность
3) Цвет

Прежде всего начнем с расширения стандартного класса CAPTCHA, для этого в /bitrix/php_interface/init.php подключим его
//include Captcha and extend
include_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/classes/general/captcha.php");


и объявим расширяющий класс
if (!class_exists("CCaptchaExt"))
{
    Class CCaptchaExt extends CCaptcha
    {
...


Основные переопределяемые функции будут
        public function SetCaptchaCode($sid = false)
        {
            if(COption::GetOptionString("main", "capctha_shuffle_mode", "N") == "Y")
                $this->Shuffle();

            switch($this->codeType)
            {
                case 'MATH': return $this->SetCaptchaMathCode($sid);
                case 'SEQUENCE': return $this->SetCaptchaSequenceCode($sid);
                case 'COLOR': return $this->SetCaptchaColorCode($sid);
                default: return parent::SetCaptchaCode($sid);
            }
        }

Т.е. функция установки текста CAPTCHA, как вы можете видеть здесь используется 4 варианта MATH, SEQUENCE, COLOR и default(STANDART).

Второй но не менее важной перегруженной функцией будет функция проверки корректности ввода, она по описанию очень похожа на SetCaptchaCode()
        public function CheckCaptchaCode($userCode, $sid, $bUpperCode = true)
        {
            switch($this->codeType)
            {
                case 'MATH': return $this->CheckCaptchaMathCode($userCode, $sid, $bUpperCode);
                case 'SEQUENCE': return $this->CheckCaptchaSequenceCode($userCode, $sid, $bUpperCode);
                case 'COLOR': return $this->CheckCaptchaColorCode($userCode, $sid, $bUpperCode);
                default: return parent::CheckCaptchaCode($userCode, $sid, $bUpperCode);
            }
        }


После переопределения мы пишем свои функции установки и обработки CAPTCHA, рассмотрим их принципы.

1. Математическая
Код устанавливается с помощью следующего выражения
$this->code = rand(intval($this->mathMin),intval($this->mathMax)).$this->mathActions[rand(0,count($this->mathActions)-1)].rand(intval($this->mathMin),intval($this->mathMax));

где
        public $mathActions = array("-", "+");
        public $mathMin = 1;
        public $mathMax = 99;

Конечно же Вы можете дополнить и переопределить данные значения, но иногда стоит подумать а будет ли пользователю высчитывать ваши интегралы?

2. Последовательность
            $this->code = ''; $max = count($this->arChars);
            $arSequence = array();
            for($i=0;$i<$this->sequenceLength;$i++)
            {
                $character = $this->arChars[rand(1, $max) - 1];
                if(rand(0,1)==1)
                    $arSequence[$i] = $character;

                $this->code .= $character;
            }

Здесь мы из стандартного набора символов формируем строку и случайным образом выбираем номера символов для последующего ввода.
Набор символов и их последовательность хранится в сессии
$_SESSION["CUSTOM_CAPCTHA"]["SEQUENCE"][$this->sid] = $arSequence;

где
public $sequenceLength = 8;


3. Цветовая
Над текстом мы не стали особо работать и оставили его обычной строкой
$this->code = "COLOR";

Но обработка чутка хитрая
Мы выбираем 2 набора цветов и ответов
            $arShemes = array_rand(self::$arColorVariants, 2);

Выбираем что будет за вопрос. Цвет фона или текста
            $answerType = rand(0,1);

и сохраняем полученные данные в сессии для последующей отрисовки и проверки CAPTCHA
            $_SESSION["CUSTOM_CAPCTHA"]["COLOR"][$this->sid] = array(
                "TYPE" => $answerType==0?'BACKGROUND':'TEXT',
                "ANSWER" => self::$arColorVariants[$arShemes[$answerType]]["ANSWER"],
                "COLORS" => $arShemes,
            );


Вот принципе и все по формированию CAPTCHA После описание методов установки и проверки - мы допиливаем немного напильником и у нас получается новый улучшенный класс
<?
//COption::SetOptionString("main", "captcha_registration", "Y");
//COption::SetOptionString("main", "capctha_type", "COLOR");
//COption::SetOptionString("main", "capctha_shuffle_mode", "Y");

//include Captcha and extend
include_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/classes/general/captcha.php");
if (!class_exists("CCaptchaExt"))
{
    Class CCaptchaExt extends CCaptcha
    {
        public $codeType = NULL;
        public $mathActions = array("-", "+");
        public $mathMin = 1;
        public $mathMax = 99;
        public $sequenceLength = 8;
        static $currentCodeType = NULL;
        static $arColorVariants = array(
            array(
                "ANSWER" => "БЕЛЫЙ",
                "COLOR" => array(0,0,0),
            ),
            array(
                "ANSWER" => "ЧЕРНЫЙ",
                "COLOR" => array(255,255,255),
            ),
            array(
                "ANSWER" => "СИНИЙ",
                "COLOR" => array(0,0,156),
            ),
            array(
                "ANSWER" => "КРАСНЫЙ",
                "COLOR" => array(255,36,0),
            ),
            array(
                "ANSWER" => "ЖЕЛТЫЙ",
                "COLOR" => array(255,255,0),
            ),
            array(
                "ANSWER" => "КРАСНЫЙ",
                "COLOR" => array(35,142,35),
            ),
        );

        protected function Shuffle()
        {
            $arrTypes = array("STANDART", "SEQUENCE", "MATH", "COLOR");
            
            $this->codeType = self::$currentCodeType = $arrTypes[array_rand($arrTypes)];
            COption::SetOptionString("main", "capctha_type", $this->codeType);
        }

        public function __construct()
        {
            $this->codeType = self::$currentCodeType = COption::GetOptionString("main", "capctha_type", "STANDART");
            parent::__construct();
        }

        public function InitCode($sid)
        {
            global $DB;

            $res = $DB->Query("SELECT CODE FROM b_captcha WHERE ID = '".$DB->ForSQL($sid,32)."' ");
            if (!$ar = $res->Fetch())
            {
                    $this->SetCaptchaCode($sid);
                    $res = $DB->Query("SELECT CODE FROM b_captcha WHERE ID = '".$DB->ForSQL($sid,32)."' ");
                    if (!$ar = $res->Fetch())
                            return false;
            }
            $this->code = $ar["CODE"];
            $this->sid = $sid;
            $this->codeLength = strlen($this->code);
            
            //some additional inits
            switch($this->codeType)
            {
                case 'MATH': break;
                case 'SEQUENCE': break;
                case 'COLOR':
                    $colorsID = $_SESSION["CUSTOM_CAPCTHA"]["COLOR"][$this->sid]["COLORS"];
                    $this->SetBGColor(self::$arColorVariants[$colorsID[0]]["COLOR"]);
                    $this->SetTextColor(self::$arColorVariants[$colorsID[1]]["COLOR"]);
                break;
            }

            return true;
        }

        public function SetCaptchaCode($sid = false)
        {
            if(COption::GetOptionString("main", "capctha_shuffle_mode", "N") == "Y")
                $this->Shuffle();

            switch($this->codeType)
            {
                case 'MATH': return $this->SetCaptchaMathCode($sid);
                case 'SEQUENCE': return $this->SetCaptchaSequenceCode($sid);
                case 'COLOR': return $this->SetCaptchaColorCode($sid);
                default: return parent::SetCaptchaCode($sid);
            }
        }

        protected function SetCaptchaMathCode($sid)
        {
            $this->code = rand(intval($this->mathMin),intval($this->mathMax)).$this->mathActions[rand(0,count($this->mathActions)-1)].rand(intval($this->mathMin),intval($this->mathMax));
            $this->sid = $sid===false? md5( uniqid(microtime())): $sid;

            CCaptcha::Add(
                    Array(
                            "CODE" => $this->code,
                            "ID" => $this->sid
                    )
            );
        }

        protected function SetCaptchaColorCode($sid)
        {
            $this->code = "COLOR";
            $this->sid = $sid===false? md5( uniqid(microtime())): $sid;

            CCaptcha::Add(
                    Array(
                            "CODE" => $this->code,
                            "ID" => $this->sid
                    )
            );

            $arShemes = array_rand(self::$arColorVariants, 2);
            $answerType = rand(0,1);
            $_SESSION["CUSTOM_CAPCTHA"]["COLOR"][$this->sid] = array(
                "TYPE" => $answerType==0?'BACKGROUND':'TEXT',
                "ANSWER" => self::$arColorVariants[$arShemes[$answerType]]["ANSWER"],
                "COLORS" => $arShemes,
            );
        }

        protected function SetCaptchaSequenceCode($sid)
        {
            $this->code = ''; $max = count($this->arChars);
            $arSequence = array();
            for($i=0;$i<$this->sequenceLength;$i++)
            {
                $character = $this->arChars[rand(1, $max) - 1];
                if(rand(0,1)==1)
                    $arSequence[$i] = $character;

                $this->code .= $character;
            }
            $this->sid = $sid===false? md5( uniqid(microtime())): $sid;

            CCaptcha::Add(
                    Array(
                            "CODE" => $this->code,
                            "ID" => $this->sid
                    )
            );

            $_SESSION["CUSTOM_CAPCTHA"]["SEQUENCE"][$this->sid] = $arSequence;
        }

        public function CheckCaptchaCode($userCode, $sid, $bUpperCode = true)
        {
            switch($this->codeType)
            {
                case 'MATH': return $this->CheckCaptchaMathCode($userCode, $sid, $bUpperCode);
                case 'SEQUENCE': return $this->CheckCaptchaSequenceCode($userCode, $sid, $bUpperCode);
                case 'COLOR': return $this->CheckCaptchaColorCode($userCode, $sid, $bUpperCode);
                default: return parent::CheckCaptchaCode($userCode, $sid, $bUpperCode);
            }
        }

        protected function CheckCaptchaMathCode($userCode, $sid, $bUpperCode = true)
        {
                global $DB;

                if (strlen($userCode)<=0 || strlen($sid)<=0)
                        return false;

                if ($bUpperCode)
                        $userCode = toUpper($userCode);

                $res = $DB->Query("SELECT CODE FROM b_captcha WHERE ID = '".$DB->ForSQL($sid,32)."' ");
                if (!$ar = $res->Fetch())
                        return false;

                eval('$checkCODE='.$ar["CODE"].';');
                if ($checkCODE != $userCode)
                        return false;

                CCaptcha::Delete($sid);

                return true;
        }
        
        protected function CheckCaptchaSequenceCode($userCode, $sid, $bUpperCode = true)
        {
                global $DB;

                if (strlen($userCode)<=0 || strlen($sid)<=0)
                        return false;

                if ($bUpperCode)
                        $userCode = toUpper($userCode);

                $res = $DB->Query("SELECT CODE FROM b_captcha WHERE ID = '".$DB->ForSQL($sid,32)."' ");
                if (!$ar = $res->Fetch())
                        return false;

                if (join('',$_SESSION["CUSTOM_CAPCTHA"]["SEQUENCE"][$sid]) != $userCode)
                        return false;

                CCaptcha::Delete($sid);
                unset($_SESSION["CUSTOM_CAPCTHA"]["SEQUENCE"][$sid]);
                
                return true;
        }

        protected function CheckCaptchaColorCode($userCode, $sid, $bUpperCode = true)
        {
                global $DB;

                if (strlen($userCode)<=0 || strlen($sid)<=0)
                        return false;

                if ($bUpperCode)
                        $userCode = str_replace('Ё','Е',toUpper($userCode));

                $res = $DB->Query("SELECT CODE FROM b_captcha WHERE ID = '".$DB->ForSQL($sid,32)."' ");
                if (!$ar = $res->Fetch())
                        return false;

                if ($_SESSION["CUSTOM_CAPCTHA"]["COLOR"][$sid]["ANSWER"] != $userCode)
                        return false;

                CCaptcha::Delete($sid);
                unset($_SESSION["CUSTOM_CAPCTHA"]["COLOR"][$sid]);

                return true;
        }

        public function SetCode()
        {
            return $this->SetCaptchaCode();
        }

        public function CheckCode($userCode, $sid, $bUpperCode = True)
        {
            return $this->CheckCaptchaCode($userCode, $sid, $bUpperCode);
        }

        static function APPCaptchaGetCode()
        {
            $cpt = new CCaptchaExt();
            $cpt->SetCode();

            return $cpt->GetSID();
        }

        static function APPCaptchaCheckCode($captcha_word, $captcha_sid)
        {
            $cpt = new CCaptchaExt();
            
            return $cpt->CheckCode($captcha_word, $captcha_sid);
        }

        static function APPBeforeCaptchaCheckCode()
        {
            global $APPLICATION;
            if(COption::GetOptionString("main", "captcha_registration", "N") == "Y")
            {
                define("APPCaptchaCheckCode", true);
                COption::SetOptionString("main", "captcha_registration", "N");
                if(!self::APPCaptchaCheckCode($_REQUEST["captcha_word"], $_REQUEST["captcha_sid"]))
                    define("APPCaptchaCheckCodeWRONG", true);
            }
        }

        static function APPOnCaptchaCheckCode($arParams)
        {
            if(defined("APPCaptchaCheckCode"))
                COption::SetOptionString("main", "captcha_registration", "Y");
            
            if(defined("APPCaptchaCheckCodeWRONG"))
            {
                global $APPLICATION;
                $APPLICATION->ThrowException(GetMessage("MAIN_FUNCTION_REGISTER_CAPTCHA")."!<br>");
                
                return false;
            }

            return true;
        }

        static function APPAfterCaptchaCheckCode()
        {
            if(defined("APPCaptchaCheckCode"))
                COption::SetOptionString("main", "captcha_registration", "Y");
        }
    }
}

AddEventHandler("main", "OnPageStart", Array("CCaptchaExt", "APPBeforeCaptchaCheckCode"));
AddEventHandler("main", "OnBeforeUserRegister", Array("CCaptchaExt", "APPOnCaptchaCheckCode"));
AddEventHandler("main", "OnBeforeProlog", Array("CCaptchaExt", "APPAfterCaptchaCheckCode"));
AddEventHandler("main", "OnAfterUserRegister", Array("CCaptchaExt", "APPAfterCaptchaCheckCode"));
?>


Далее чтобы наш класс воспринимался и корректно работал придется немного поработать, а именно изменить создание объекта в файле:
/bitrix/tools/captcha.php
с
$cpt = new CCaptcha();

на
$cpt = new CCaptchaExt();

Отлично! наш класс уже вроде должен как работать но, увы это не все изменения. Далее если мы будем работать с формой регистрации, нам надо будет "подкрутить" стандартный компонент "system.auth.registration"
в component.php нам нужно установить свой вызов
if ($arResult["USE_CAPTCHA"])
{
   $arResult["CAPTCHA_CODE"] = htmlspecialchars(CCaptchaExt::APPCaptchaGetCode());
}

Однако нам ничто не мешает сделать это в result_modifer.php

Далее в самом же шаблоне в блоке ввода CAPTCHA добавить вывод своих текстовых сообщений, Я сделал это более банально обычным текстом, но Вам ничего не мешает использовать языковые файлы.
         <td><span class="starrequired">*</span>
                        <?
                        switch(CCaptchaExt::$currentCodeType)
                        {
                            case 'MATH': echo 'Введите результат математического действия с картинки: '; break;
                            case 'SEQUENCE': 
                                $sequence = array_keys($_SESSION["CUSTOM_CAPCTHA"]["SEQUENCE"][$arResult["CAPTCHA_CODE"]]);
                                for($i=0, $n = count($sequence); $i<$n; $i++)
                                    $sequence[$i]++;
                            echo 'Введите '.join(', ', $sequence).' символ с картинки'; break;
                            case 'COLOR':
                                echo 'Введите цвет '.($_SESSION["CUSTOM_CAPCTHA"]["COLOR"][$arResult["CAPTCHA_CODE"]]["TYPE"]=="TEXT"?'текса':'фона').' с картинки (напр. бежевый): ';
                            break;
                            default: echo 'Введите код с картинки:'; break;
                        }
                        ?>
                        </td>
         <td><input type="text" name="captcha_word" maxlength="50" value="" /></td>

Фу-у-ух, ну вроде все сделали, осталось только включить. Для включения вначале кода нашего класса нам необходимо установить какой вид CAPTCHA мы будем использовать, сделать это можно так
COption::SetOptionString("main", "capctha_type", "MATH"); 


Ну а теперь немного о грустном, из-за ряда подводных камней, пришлось подключать 4 обработчика, которые выполняют обработку и выкидывают EXCEPTION при необходимости, однако это стоит временным отключением проверки CAPTCHA при регистрации стандартным механизмом, а вместо него использовать наш от CAPTCHA. Поэтому если у Вас CAPCTHA всегда используется, то я рекомендую расскоментировать строку:
COption::SetOptionString("main", "captcha_registration", "Y");

PS. Для ценителей изысков, есть режим NO PASARUN!, т.е. постоянная смета типа CAPTCHA. Для этого единожды выполните код
COption::SetOptionString("main", "capctha_shuffle_mode", "Y");

Даже самому порою было проблематично ввести.
Проиллюстрируем что у нас получилось:

Математическая


Последовательность


Цветовая


Стандартная


Назад в раздел

Подписаться на новые материалы раздела:












CAPTCHA