Кастомизация каптчи в Zend Framework 2

в 14:11, , рубрики: captcha, zend framework 2, Песочница, метки: ,

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

Элемент Captcha имеет более одного поля, которые рендерятся друг за другом. Встроеный генератор изображений каптч (ZendCaptchaImage.php) использует свой собственный хелпер (ZendFormViewHelperCaptchaImage.php) для создания изображения. Так-же, в ZendCaptchaImage.php находится метод 'getHelperName'. Этот метод передает имя хелпера для рендеринга изображения каптчи. По умолчанию 'getHelperName' передает 'captcha/image' — экземпляр класса ZendFormViewHelperCaptchaImage.php. Если углубиться с помощью дебаггера, можно увидеть, что в свойстве экземпляра phpRenderer::__helpers, в invokablesClasses располагается хелпер 'captchaimage'. Это и есть ZendFormViewHelperCaptchaImage.php, упомянутый ранее. Грубо говоря — вы просто создаете изображение, а всё остальное делает за вас рендерер используя хелпер, хотя такое положение вещей устраивает не всех.

Когда же вы создаете элемент формы Captcha (ZendFormElementCaptcha.php), вы передаете этому элементу изображение каптчи(ZendCaptchaImage.php). Элемент формы Captcha, в свою очередь, имеет тоже свой хелпер (ZendFormViewHelperFormCaptcha.php). В методе render этого хелпера вы увидите, что изображение каптчи (ZendCaptchaImage.php) загружается с помощью ElementInterface:

//ZendFormViewHelperFormCaptcha.php
public function render(ElementInterface $element)
    {
        $captcha = $element->getCaptcha();

после чего вызывается хелпер через метод 'getHelperName':

$helper  = $captcha->getHelperName();

В итоге мы получаем экземпляр класса хелпера посредством экземпляра PhpRenderer и возвращаем представление:

$helper = $renderer->plugin($helper);
        return $helper($element);

Нам нужно четко представлять различие между 'элементом формы Captcha и его хелпером' и 'изображением Captcha также со своим хелпером'. Мы передадим 'изображение каптчи' 'элементу формы Captcha', который уже располагает привязанным хелпером. Метод render 'элемента формы Captcha' встроит экземпляр 'изображения Captcha', который мы передали, используя хелпер для генерации изображения и вернет представление. Все, что нужно сделать, это передать 'изображению Captcha' новый хелпер, который отобразит представление так, как нас устроит и переписать ZendCaptchaImage.php, чтоб он получил наш новый хелпер, а не свой по умолчанию.

Перед тем как начать, давайте обратим внимание на несколько деталей:
хелпер ZendFormViewHelperCaptchaImage.php для отображения каптчи определяет паттерн как %s%s%s:

$pattern = '%s%s%s';

Таким образом, первое, что необходимо — наш кастомный хелпер с собственным паттерном. Давайте создадим его в модуле Application:

//moduleApplicationsrcApplicationViewHelperFormCaptchaViewHelperCaptcha.php
<?php

namespace ApplicationViewHelperFormCaptcha;

use ZendFormViewHelperCaptchaAbstractWord;
use ApplicationViewHelperFormCaptchaCustomCaptcha as CaptchaAdapter;
use ZendFormElementInterface;
use ZendFormException;

class ViewHelperCaptcha extends AbstractWord
{
/**
* Override
*
* Render the captcha
*
* @param ElementInterface $element
* @throws ExceptionDomainException
* @return string
*/
    public function render(ElementInterface $element)
    {
//Мы можем установить здесь разделитель между изображением и полем ввода.
        $this->setSeparator('')
        $captcha = $element->getCaptcha();
        if ($captcha === null || !$captcha instanceof CaptchaAdapter) {
            throw new ExceptionDomainException(sprintf(
                '%s requires that the element has a "captcha" attribute of type ZendCaptchaImage; none found',
                __METHOD__
            ));
        }
//Как долго будет храниться изображение (по умолчанию 600).
        $captcha->setExpiration(10);

//Как часто выполнять очистку файлов(по умолчанию 10). При данной конфигурации старый файл затирается вновь созданным.
        $captcha->setGcFreq(1);

        $captcha->generate();
        $imgAttributes = array(
        'width' => $captcha->getWidth(),
        'height' => $captcha->getHeight(),
        'alt' => $captcha->getImgAlt(),
        'src' => $captcha->getImgUrl() . $captcha->getId() . $captcha->getSuffix(),
        );
        $closingBracket = $this->getInlineClosingBracket();
        $img = sprintf(
            '<img %s%s',
            $this->createAttributesString($imgAttributes),
            $closingBracket
            );
        $position = $this->getCaptchaPosition();
        $separator = $this->getSeparator();
        $captchaInput = $this->renderCaptchaInputs($element);

//Наш измененный паттерн
        $pattern = '<div class="captcha_image">
            %s</div>
            %s<div class="captcha_input">
            %s</div>'

        if ($position == self::CAPTCHA_PREPEND) {
                return sprintf($pattern, $captchaInput, $separator, $img);
            }
            return sprintf($pattern, $img, $separator, $captchaInput);
    }
}

Класс нашего хелпера дублирует класс ZendFormViewHelperCaptchaImage.php с небольшими изменениями. Наш хелпер не использует ZendCaptchaImage.php для генерации изображения, в отличие от оригинального. Помните, что ZendCaptchaImage.php предоставляет метод 'getHelperName' который возвращает жёстко закодированное имя хелпера 'captcha/image', таким образом, когда форма будет генерировать изображение, она получит для этого не свой хелпер по умолчанию, а только что созданный нами. Что еще нужно, так это передать в phpRenderer наш кастомный хелпер и сгенерировать новое изображение каптчи, которое будет расширять оригинальный класс ZendCaptchaImage.php и перепишет метод 'getHelperName' установив имя хелпера, который мы создали.

Итак, давайте добавим наш класс в конфигурацию хелперов phpRenderer invokables. Реализуем это в module.config.php:

//moduleApplicationconfigmodule.config.php
...
'view_helpers' => array(
    'invokables' => array(
        'viewhelpercaptcha' => 'ApplicationViewHelperFormCaptchaViewHelperCaptcha', 
        ),
    ),

Следующим шагом будет создание изображения каптчи, которое возвратит наш хелпер добавленный в конфигурацию модуля как phpRenderer invokables класс. Нет нужды переопределять весь класс ZendCaptchaImage.php, достаточно указать методу 'getHelperName' наш кастомный класс хелпера в качестве параметра. Для этого создадим класс, и назовем его, например, CustomCaptcha.php в папке moduleApplicationsrcApplicationViewHelperFormCaptcha. Мы собираемся расширить оригинальный класс ZendCaptchaImage.php и переопределить метод 'getHelperName' чтобы он вернул наш хелпер 'viewhelpercaptcha'. Не помешает также переопределить сообщения об ошибках в свойстве $messageTemplates. На ваше усмотрение.

//moduleApplicationsrcApplicationViewHelperFormCaptchaCustomCaptcha.php
<?php

namespace  ApplicationViewHelperFormCaptcha;
 
//Оригинальный класс, который мы расширим.
use ZendCaptchaImage as CaptchaImage;

//Новая версия класса, в которой мы изменим только необходимое.
class CustomCaptcha extends CaptchaImage
{ 
    protected $messageTemplates = array(
        self::MISSING_VALUE => 'Отсутствует значение',
        self::MISSING_ID    => 'Поле ID отсутствует',
        self::BAD_CAPTCHA   => 'Неверно введено значение',

    public function getHelperName()
    {
        return 'viewhelpercaptcha';
    } 
}

Последнее, что нам нужно сделать — использовать наше CustomCaptcha изображение в форме. Для этого создадим две папки: одну для шрифта (zf2folder/data/fonts), который понадобится чтобы генерировать слова каптчи, и другую, для хранения файлов изображений каптч (zf2folder/public/img/captcha). Естественно, в папку fonts скопируем шрифт *.ttf, например arial.ttf.

Для примера можно взять форму из официального туториала:

<?php

namespace AlbumForm;

use ZendFormForm;

class AlbumForm extends Form
{
    public function __construct($name = null)
    {
//Имя формы можно игнорировать
        parent::__construct('album');
        $this->setAttribute('method', 'post');

//Здесь расположены стандартные элементы
        ...

//Здесь будем создавать элемент Captcha

        $dirdata = './data';

//Создаем новый CustomCaptcha класс
        $captchaImage = new CustomCaptcha(array(
            'font'           => $dirdata . '/fonts/arial.ttf',
            'width'          => 120,
            'height'         => 60,
            'fsize'          => 20,
            'wordLen'        => 5,
            'dotNoiseLevel'  => 25,
            'lineNoiseLevel' => 2
        ));

//Назначаем директорию для хранения файлов
        $captchaImage->setImgDir('public/img/captcha/');

//Назначаем путь для загрузки файлов каптчи
        $captchaImage->setImgUrl('/img/captcha/');
        $captchaImage->setImgAlt('Вы человек или робот?');

//Создаем элемент формы Captcha куда добавим нашу CustomCaptcha, созданную выше

        $this->add(array(
            'type'       => 'ZendFormElementCaptcha',
            'name'       => 'captcha',
            'options'    => array(
                'captcha' => $captchaImage,
            ),
            'attributes' => array(
                'class' => 'some_class',
            )
        ));
        
        $this->add(array(
            'name' => 'submit',
            'attributes' => array(
                'type'  => 'submit',
                'value' => 'Go',
                'id' => 'submitbutton',
            ),
        ));
    }
}

Изучив структуру html документа, получившегося на выходе, вы увидите, что теперь изображение и поле ввода каптчи обернуты в div-ы. Мы получили более упорядоченный дизайн.

Использованные источники:
framework.zend.com/manual/2.2/en/modules/zend.captcha.intro.html
framework.zend.com/manual/2.2/en/modules/zend.captcha.operation.html
framework.zend.com/manual/2.2/en/modules/zend.captcha.adapters.html
framework.zend.com/manual/2.2/en/user-guide/forms-and-actions.html
zendtemple.blogspot.com/2012/12/zend-framework-2-zf2-creating-view.html
samsonasik.wordpress.com/2012/09/12/zend-framework-2-using-captcha-image-in-zend-form/

Автор: usrmrz

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js