Пользователь
0,0
рейтинг
10 декабря 2012 в 10:11

Разработка → Добавляем новый тег в MODX Revolution tutorial

MODX*
В данном топике описан мой опыт по созданию плагина для MODX Revolution, который добавляет новый тег к данной CMS. Напомню, что разработчик может использовать теги в контенте ресурсов своего сайта или в шаблонах и чанках. Например, тег [[*pagetitle]] будет обработан парсером MODX и вернет заголовок страницы, на которой находится пользователь.

Среди обширного списка тегов мне не хватало еще одного — вывода полей любого выбранного ресурса. Для этого приходилось скачивать и устанавливать из репозитория MODX сниппет getResourceField. Кроме неудобства, что данное решение не входит в базовую поставку CMS, оно еще и обладает, на мой взгляд, слишком длинным именем, не говоря уже о том, что приходится держать открытым RTFM, чтобы не напутать с названиями параметров. Поэтому я написал плагин fastField, о котором пойдет речь дальше.

Для начала нужно было определиться на какое из событий требуется повесить плагин. Для тех, кто еще не знаком с системой MODX, замечу, что плагином здесь называется именно дополнение, которое вызывается на некое предопределенное событие. Из большого списка системных событий, предоставляемых по умолчанию оказалось, что подходит только событие OnParseDocument, поскольку вызывается в парсере MODX в методе processElementTags() класса modParser (файл core/model/modx/modparser.class.php). После ни одно событие уже не сможет получить контент с нашим тегом, поскольку все они будут вырезаны, как несуществующие.
Первоначальная версия плагина была довольно проста:

$content = $modx->documentOutput;
$pattern = '@\[\[#(\d+)\.(.+?)\]\]@si';
if (preg_match($pattern, $content, $matches) > 0) {
    $tag = $matches[0];
    $resource_id = $matches[1];
    $resource_field = explode('.', $matches[2]);
    $resource = $modx->getObject('modResource', $resource_id);

    if (count($resource_field) == 1) {
       $value = $resource->get($resource_field[0]);
    }
    else {
        if ($resource_field[0] == 'tv' && isset($resource_field[1])) {
            $value = $resource->getTVValue($resource_field[1]);
        }
        elseif (in_array($resource_field[0], array('properties', 'property', 'prop'))) {
            $value = $resource->getProperty($resource_field[2], $resource_field[1]);
        }
        else {
            $value = '';
         }
    }
    $modx->documentOutput = str_replace($tag, $value, $content);
}

Задачей стояла обработка тегов вида [[#10.pagetitle]], [[#10.tv.MyTV]]. В принципе задача была решена, но невозможно было применить к полям фильтры ввода-вывода.
Поэтому пришлось глубже разбираться в том, что именно делает парсер, когда обрабатывает теги. А делает он следующее.
Собирает все теги в контенте с помощью функции
public function collectElementTags($origContent, array &$matches, $prefix= '[[', $suffix= ']]') 

причем теги возвращаются в виде массива из 2 элементов — внешний тег и внутренний. Поскольку наш новый тег обладает всеми признаками тега, то данная функция вернет и его. Далее строится массив $tagMap, который содержит список замен для функции str_replace вида тег => обработанный тег. При обработке каждого тега вызывается функция парсера
public function processTag($tag, $processUncacheable = true)

в которой содержимое тега разбивается на части: токен (символ, обозначающий тот или иной вид тега, например, * для полей ресурса или ~ для ссылок, в нашем случае — решетка #), имя (или тело тега, например, pagetitle в теге [[*pagetitle]]), фильтры (:ucase и т.д.) и параметры (&parameter в тегах сниппетов или других). По токену определяется какой именно класс тега будет вызываться для обработки тега. Все они — потомки абстрактного класса modTag. Поэтому для создания нового тега создадим новый класс modResourceFieldTag. Во всех классах тегов переопределены методы process() и getContent(). При этом у нас новый тег очень сходен с тегом поля ресурса, и я сделал его производным от класса modFieldTag, чтобы оставить его метод process(). Вот, что получилось:

class modResourceFieldTag extends modFieldTag {

    /**
     * Overrides modTag::__construct to set the Field Tag token
     * {@inheritdoc}
     */
    function __construct(modX & $modx) {
        parent :: __construct($modx);
        $this->setToken('#');
    }

    /**
     * Get the raw source content of the field.
     *
     * {@inheritdoc}
     */
    public function getContent(array $options = array()) {
        if (!$this->isCacheable() || !is_string($this->_content) || $this->_content === '') {
            if (isset($options['content']) && !empty($options['content'])) {
                $this->_content = $options['content'];
            } else {
                $tag = explode('.', $this->get('name'));
                $tagLength = count($tag);
                // for processing tags in resource_id place ([[#[[+id]].pagetitle]])
                $tags = array();
                if ($collected= $this->modx->parser->collectElementTags($tag[0], $tags)) {
                    $tag[0] = $this->modx->parser->processTag($tags[0], $this->modx->parser->isProcessingUncacheable());
                }
                if (is_numeric($tag[0])) {
                    $resource = $this->modx->getObject('modResource', $tag[0]);
                    if ($resource)
                    {
                        if ($tagLength == 2) {
                            if ($tag[1] == 'content') {
                                $this->_content = $resource->getContent($options);
                            }
                            else {
                                $this->_content = $resource->get($tag[1]);
                            }
                        }
                        else {
                            if (($tag[1] == 'tv') && ($tagLength == 3)) {
                                $this->_content = $resource->getTVValue($tag[2]);
                            }
                            elseif (in_array($tag[1], array('properties', 'property', 'prop')) && ($tagLength == 4)) {
                                $this->_content = $resource->getProperty($tag[3], $tag[2]);
                            }
                            else {
                                $this->_content = '';
                            }
                        }
                    }
                    else {
                        $this->_content = '';
                    }

                }
            }
        }
        return $this->_content;
    }
}

Для обработки случая, когда тег вызывается с плейсхолдером для идентификатора ресурса (к примеру, [[#[[+id]].pagetitle]]) дополнительно обрабатываем эту часть тега:
$tags = array();
if ($collected= $this->modx->parser->collectElementTags($tag[0], $tags)) {
    $tag[0] = $this->modx->parser->processTag($tags[0], $this->modx->parser->isProcessingUncacheable());
}

Теги, которые могут вызываться в фильтрах, будут обработаны парсером после выполнения события.

Теперь осталось вызвать обработку данных тегов, собственно в плагине fastField:
switch ($modx->event->name) {
    case 'OnParseDocument':
        $content = $modx->documentOutput;
        $tags= array ();
        if ($collected= $modx->parser->collectElementTags($content, $tags, '[[', ']]', array('#')))
        {
            $tagMap= array ();
            foreach ($tags as $tag) {
                $token = substr($tag[1], 0, 1);
                if ($token == '#') {
                    include_once $modx->getOption('core_path') . 'components/fastfield/model/fastfield/fastfield.php';

                    $tagParts= xPDO :: escSplit('?', $tag[1], '`', 2);
                    $tagName= substr(trim($tagParts[0]), 1);
                    $tagPropString= null;
                    if (isset ($tagParts[1])) {
                        $tagPropString= trim($tagParts[1]);
                    }

                    $element= new modResourceFieldTag($modx);
                    $element->set('name', $tagName);
                    $element->setTag('');
                    $element->setCacheable(false);
                    $tagMap[$tag[0]] = $element->process($tagPropString);
                }
            }
            $modx->parser->mergeTagOutput($tagMap, $content);
            $modx->documentOutput = $content;
        }
        break;
}


Надеюсь, данная статья послужит читателям руководством для создания собственных тегов. Код может быть не идеальный, но он может служить заготовкой для дальнейшего экспериментирования с этой замечательной CMS/CMF.

Исходный код плагина доступен на GitHub.
Виталий @Argnist88
карма
12,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (3)

  • –1
    Если вы любитель расширять MODX, гляньте, пожалуйста, здесь и здесь. Что скажете?
  • +1
    >>Кроме неудобства, что данное решение не входит в базовую поставку CMS
    А ваше разве входит?

    >>оно еще и обладает, на мой взгляд, слишком длинным именем
    Можно переименовать как угодно.

    >>не говоря уже о том, что приходится держать открытым RTFM
    По-моему очень просто зпомнить все параметры. Их совсем не много.

    А если не нравится длинная строка, можно было бы написать сниппет, который вызывался бы примерно так:

    [[fastField?opt=`10.pagetitle`]]

    Городить такай плагин как у вас по-моему лишнее. Но как пример гибкости MODX сойдёт.
    • –1
      Помню эту заметку. И вот появился повод к ней вернуться.

      Городить такай плагин как у вас по-моему лишнее.
      Это не просто плагин, а ажно переопределение родного парсера. Сейчас вот обкатываю модуль мониторинга (чуть позже заметку опубликую), и неотъемлемая часть его — это переопределение MODX-парсера (чтобы перехватить моменты получения элементов дабы замерить тайминги). Тут вот как раз подвернулся сайт подопытный, на производительность проверить. Ставлю туда модуль, а он не работает. Потому как я предусмотрительно прописал проверку текущего используемого парсера (мой вешается плагином, а не жестко в систему прописывается). И вот всего из-за одного дополнительного поля система ломается. Как-то так.

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