Add a new tag in MODX Revolution

In this topic I described my experience creating a plug-in for MODX Revolution that adds a new tag to the CMS. Let me remind you that the developer can use tags in the content resources of your website or in templates and chunks. For example, the tag [[*pagetitle]] is processed by the MODX parser and return the title of the page the user is on.

Among the extensive list of tags I missed one more output fields of any selected resource. For this had to download and install from a repository of MODX snippet getResourceField. Other than the inconvenience that this decision is not included in the basic delivery of CMS, it also has, in my opinion, too long name, not to mention the fact that you have to keep an open RTFM not to confuse with the names of the parameters. So I wrote a plugin fastField, which will be discussed further.

First, you need to decide which event you want to hang the plugin. For those who are not familiar with the MODX system, you will notice that the plugin is called it is the Supplement, which is invoked on a predefined event. From a large list of events provided the default was that only fits the OnParseDocument event, as called in the MODX parser in the method processElementTags() class modParser (file core/model/modx/modparser.class.php). After any event will not be able to content with our tag, because they will all be cut as non-existent.
The initial version of the plugin was fairly simple:

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

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);
}

The task was the processing of tags of the form [[#10.pagetitle]], [[#10.tv.MyTV]]. In principle, the problem was solved, but it was impossible to apply to the fields of filters I / o.
So I had a deeper understanding of what makes the parser when it processes the tags. And he does the following.
Collects all tags in the content using the function
the
public function collectElementTags($origContent, array &$matches, $prefix= '[[', $suffix= ']]') 

moreover, the tags are returned as an array of 2 elements — the external tag and internal. As our new tag has all the attributes of a tag, this function will return it. Next, build the array $tagMap, which contains a list of substitutions str_replace view tag => processed tag. When processing each tag function is called the parser
the
public function processTag($tag, $processUncacheable = true)

in which the content of the tag is divided into parts: a token (a symbol denoting a particular kind of tag, for example: * for fields or resource ~ for reference, in our case, grid #), the name (or the body of the tag, for example, the pagetitle tag [[*pagetitle]]), filters (:ucase, etc.) and parameters (¶meter in the snippet tags or other). The token determines which class the tag will be invoked to handle the tag. They are all descendants of an abstract class modTag. Therefore, to create a new tag create a new class modResourceFieldTag. In all classes of tags override the methods process() and getContent(). We have a new tag is very similar to the tag field of the resource, and I made it a derived class of the modFieldTag to leave it the process () method. Here's what happened:
the
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 place resource_id is ([[#[[+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;
}
}

To handle the case when the tag is invoked with a placeholder for the ID of the resource (for example, [[#[[+id]].pagetitle]]) and additionally processed this part:
the
$tags = array();
if ($collected= $this- > modx->parser->collectElementTags($tag[0], $tags)) {
$tag[0] = $this- > modx- > parser- > processTag($tags[0], $this- > modx->parser->isProcessingUncacheable());
}

Tags that can be called in the filters will be processed by the parser after executing the event.

Now we have to call the data processing of the tags actually in the plugin fastField:
the
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;
}


I hope this article will serve as a readers guide to create your own tags. Code may not be perfect, but it can serve as a template for further experimentation with this great CMS/CMF.

The source code for the plugin available on GitHub.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Integration of PostgreSQL with MS SQL Server for those who want faster and deeper

Custom database queries in MODx Revolution

Parse URL in Zend Framework 2