How to prevent MODx tags from parsing in MODx Revo

So, you have a shiny new blog rolling in MODx Revolution and you want to post up some code samples about MODx Revo - but, hold on, if MODx tags are posted in the sample code, they get parsed. Oh oh!

Just how do you work around that little annoyance? Well, with a plugin of course! Read on for the solution.

The solution is relatively simple and requires a plugin to fire on three events, these are:

  • OnLoadWebDocument: This event fires before any tags are parsed, we are going to use this to pre-format our sample tags into a format the MODx parser won't recognize.
  • OnWebPagePrerender: This fires after all tags have been parsed (actually, right before the page is delivered to the browser). We're going to use this event to remove all that pre-formatting to make our MODX sample code copy-friendly.
  • OnBeforeSaveWebPageCache: This event fires right before the cache is written to. We'll use this event to set the resource content back to the original content that before all the parsing was done.

The first step is to create a new plugin, select those three events above in the 'System Events' tab and then paste in the following code:

$eventName = $modx->event->name;
switch ($eventName) {
	case 'OnLoadWebDocument' :
		$output = $GLOBALS['code_content'] = $modx->resource->get('content');
		preg_match_all('/<code.*?>(.*?)<\/code>/ims', $output, $matches);

		$i = 0;
		foreach ($matches[1] as $key => $match) {
			$output = str_replace($match, '~*code_' . $i . '*~', $output);
			$GLOBALS['code_matches']['~*code_' . $i . '*~'] = $match;
			$i++;
		}
		$modx->resource->set('content', $output);
		break;
	case 'OnWebPagePrerender' :
		$html = & $modx->resource->_output;
		$output = $modx->resource->get('content');
		preg_match_all('/<code.*?>(.*?)<\/code>/ims', $html, $matches);
		$i = 0;

		$replacements = array (
			'<' => '&lt;',
			'>' => '&gt;'
		);

		foreach ($GLOBALS['code_matches'] as $key => $match) {
			$match = preg_replace('/&([^\s]*;)/i', '&amp;$1', $match);
			foreach ($replacements as $key => $value) {
				$match = str_replace($key, $value, $match);
			}

			$html = str_replace('~*code_' . $i . '*~', $match, $html);
			$output = str_replace('~*code_' . $i . '*~', $match, $output);
			$i++;
		}
		$modx->resource->set('content', $output);
		break;
	case 'OnBeforeSaveWebPageCache' :
		$modx->resource->set('content', $GLOBALS['code_content']);
		break;
	default :
		break;
}

Save the plugin, and then go give it a whirl! Just paste in some MODx tags between some <code> tags and the MODx tags should get rendered out to the page unparsed.

Maximizing the content caching

You may have noticed that the plugin makes a few uses of $modx->resource in both getting the content and setting it. This may seem a bit puzzling for a start, but the answer lies in bypassing the content caching.

By getting and setting the content dynamically, I can still call my page cached but still allow for direct manipulation of the content variable ([[*content]]). The only other alternative would be to call the page uncached which would have a serious disadvantage in terms of performance.

Caveats

This plugin isn't 100% perfect, there will still be some instances where you have to manually escape tags (and HTML entities) - one example being when I posted the plugin code above. But, on the whole, it will greatly reduce the time to post up MODx code samples on a MODx Revo driven site.

See a working example of the plugin here: Using getResources to generate an RSS feed

Bookmark and Share

Comments (4)

Mar 15, 2010 at 03:53 PM : #
Shawn Wilkerson:

I tackled this last year, in a much simpler manner I believe: http://www.shawnwilkerson.com/code/modx_revolution/modx_snippets/showSnippet_snippet.html I also created a version for chunks.

Mar 18, 2010 at 04:39 AM : #
Garry Nutting:

I like your snippet but the limitation with it is that the content must already exist within the database.

With my solution, I can copy and paste any MODx code (plugins, snippets, chunks, or even one-line tags) straight into my blog posts when I'm creating them without worrying about the tags being parsed. As most of my blog posts will be code-based, that is my preferred solution.

Although, saying that, I'm sure your snippet could be customized to do that as well :)

Jul 22, 2010 at 12:19 AM : #
Andreas Wettainen (mrhaw):

Awesome Blog! Keep it up! :)

Aug 31, 2010 at 11:02 AM : #
Andrew Smith:

Very nice plugin, I tweeted @slittingred about how he done this 2 days back and then found it on your blog while looking for something else. I found 1 bug so far which can be easily fixed with a if statement. On line 32 you are looping through $GLOBALS['code_matches'] but in some instance it doesn't exist and this throws a error warning. I added a if(!empty($GLOBALS['code_matches'])) which catches the error and skip the foreach loop at that point.






Allowed tags: <b><i><br>Add a new comment: