- PVSM.RU - https://www.pvsm.ru -
В данном топике описан мой опыт по созданию плагина для MODX Revolution, который добавляет новый тег к данной CMS. Напомню, что разработчик может использовать теги в контенте ресурсов своего сайта или в шаблонах и чанках. Например, тег [[*pagetitle]] будет обработан парсером MODX и вернет заголовок страницы, на которой находится пользователь.
Среди обширного списка тегов мне не хватало еще одного — вывода полей любого выбранного ресурса. Для этого приходилось скачивать и устанавливать из репозитория MODX сниппет getResourceField. Кроме неудобства, что данное решение не входит в базовую поставку CMS, оно еще и обладает, на мой взгляд, слишком длинным именем, не говоря уже о том, что приходится держать открытым RTFM [1], чтобы не напутать с названиями параметров. Поэтому я написал плагин fastField [2], о котором пойдет речь дальше.
Для начала нужно было определиться на какое из событий требуется повесить плагин. Для тех, кто еще не знаком с системой MODX, замечу, что плагином здесь называется именно дополнение, которое вызывается на некое предопределенное событие. Из большого списка системных событий [3], предоставляемых по умолчанию оказалось, что подходит только событие 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]]. В принципе задача была решена, но невозможно было применить к полям фильтры ввода-вывода [4].
Поэтому пришлось глубже разбираться в том, что именно делает парсер, когда обрабатывает теги. А делает он следующее.
Собирает все теги в контенте с помощью функции
public function collectElementTags($origContent, array &$matches, $prefix= '[[', $suffix= ']]')
причем теги возвращаются в виде массива из 2 элементов — внешний тег и внутренний. Поскольку наш новый тег обладает всеми признаками тега, то данная функция вернет и его. Далее строится массив $tagMap, который содержит список замен для функции str_replace вида тег => обработанный тег. При обработке каждого тега вызывается функция парсера
public function processTag($tag, $processUncacheable = true)
в которой содержимое тега разбивается на части: токен (символ, обозначающий тот или иной вид тега, например, * для полей ресурса или ~ для ссылок, в нашем случае — решетка #), имя (или тело тега, например, pagetitle в теге [[*pagetitle]]), фильтры (:ucase и т.д.) и параметры (¶meter в тегах сниппетов или других). По токену определяется какой именно класс тега будет вызываться для обработки тега. Все они — потомки абстрактного класса 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 [5].
Автор: Argnist88
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/modx/22245
Ссылки в тексте:
[1] RTFM: http://rtfm.modx.com/display/ADDON/Home
[2] fastField: http://modx.com/extras/package/fastfield
[3] системных событий: http://rtfm.modx.com/display/revolution20/System+Events
[4] фильтры ввода-вывода: http://wiki.modx.im/revolution:%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2_%D1%81_modx:%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%BC%D0%BE%D0%B3%D0%BE:%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D1%8B_%D0%B2%D0%B2%D0%BE%D0%B4%D0%B0-%D0%B2%D1%8B%D0%B2%D0%BE%D0%B4%D0%B0_%D0%BC%D0%BE%D0%B4%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80%D1%8B_%D0%B2%D1%8B%D0%B2%D0%BE%D0%B4%D0%B0
[5] GitHub: https://github.com/argnist/fastField
[6] Источник: http://habrahabr.ru/post/161843/
Нажмите здесь для печати.