📜 ⬆️ ⬇️

Add a new tag to MODX Revolution

This topic describes my experience in creating a plugin for MODX Revolution, which adds a new tag to this CMS. Let me remind you that a developer can use tags in the content of the resources of his site or in templates and chunks. For example, the [[* pagetitle]] tag will be processed by the MODX parser and will return the title of the page on which the user is located.

Among the extensive list of tags, I lacked one more - the output of the fields of any selected resource. To do this, you had to download and install the snipet getResourceField from the MODX repository. In addition to the inconvenience that this solution is not included in the basic delivery of CMS, it also has, in my opinion, too long a name, not to mention the fact that you have to keep the RTFM open so as not to confuse with the names of the parameters. Therefore, I wrote a fastField plugin, which will be discussed further.

To begin with, it was necessary to decide on which of the events a plug-in should be hung. For those who are not yet familiar with the MODX system, I note that the plugin here is just the add-on, which is called on a certain predefined event. From the large list of system events provided by default, it turned out that only the OnParseDocument event is appropriate, since it is called in the MODX parser in the processElementTags () method of the modParser class (core / model / modx / modparser.class.php). After any event will not be able to get content with our tag, since all of them will be cut out as non-existent.
The initial version of the plugin was pretty simple:

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

The task was to handle tags like [[# 10.pagetitle]], [[# 10.tv.MyTV]]. In principle, the problem was solved, but it was impossible to apply I / O filters to the fields.
Therefore, I had to more deeply understand what the parser does when processing tags. And he does the following.
Collects all tags in content using the function
 public function collectElementTags($origContent, array &$matches, $prefix= '[[', $suffix= ']]') 

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

in which the contents of the tag are broken into parts: a token (a symbol denoting a particular type of tag, for example, * for resource fields or ~ for links, in our case, #), a name (or a body of a tag, for example, pagetitle in the tag [ [* pagetitle]]), filters (: ucase, etc.) and parameters (& parameter in snippet tags or others). By the token it is determined which class of the tag will be called to process the tag. All of them are descendants of the abstract modTag class. Therefore, to create a new tag, create a new modResourceFieldTag class. In all classes of tags, the process () and getContent () methods are overridden. In this case, we have a new tag that is very similar to the resource field tag, and I made it a derivative of the modFieldTag class to leave its process () method. Here is what happened:
')
 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; } } 

To handle the case when the tag is called with a placeholder for a resource identifier (for example, [[# [[+ id]]. Pagetitle]]), we further process this part of the tag:
 $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 filters will be processed by the parser after the event has been executed.

It now remains to invoke the processing of tag data, in the fastField plugin itself:
 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 guide for readers to create their own tags. The code may not be perfect, but it can serve as a blank for further experimentation with this wonderful CMS / CMF.

The source code of the plugin is available on GitHub .

Source: https://habr.com/ru/post/161843/


All Articles