Find that Placeholder I

A snippet to help find the code where placeholders are being set


In this article, we'll look at a utility snippet that helps you find the code that's setting a particular placeholder.

MODX logo

The Problem

Imagine that you have inherited a site created by another developer. You want to change the content or format of a value that's displayed with a placeholder. The value of the placeholder is most likely done in a snippet or plugin, but which one? If the site has a lot of snippets and plugins, it can be a real pain to examine them all looking for the code that sets the placeholder.

Another use case is one that happens to me with some regularity. I know what the placeholder name is, but I can't remember the name of the custom snippet or plugin I used to set it. I have a lot of custom snippets and plugins and they may have been written years ago, so it would be nice if there were a tool to find the code that sets the plugin.

$c->sortby('cat_category,TemplateVarTemplate.rank,modTemplateVar.rank','ASC');

Find that Placeholder

The solution is a fairly straightforward snippet called FindPlaceholder that searches all snippets and plugins on the site and reports the names of any that contain the placeholder name in question. This snippet will report any instance of the placeholder name, even if it's in a comment or it's not a placeholder, so we may get some extra results, but this still beats having to do an exhaustive search through all the snippets and plugins, loading each one in the Manager, and looking for the code that sets the placeholder.

We'll put the placeholder name we're looking for in a property of the snippet tag on a new resource (let's call the snippet FindPlaceholder). The resource should be unpublished and/or hidden from menus. Here's an example snippet tag:

[[!FindPlaceholder? &placeholder=`SomePlaceholder`]]

Here's the code of the FindPlaceholder snippet:

/* FindPlaceholder snippet */

function checkContent($content, $ph, $type, $name, &$output) {
    $found = 0;
    if (strpos($content, $ph) !== false) {
           $found = 1;
           $output .= "\n<p> " . $type . ' ' .  $name .
                ' contains placeholder ' . $ph . '</p>';
    }

    return $found;

}
$ph = $modx->getOption('placeholder', $scriptProperties);

$output = "\n\n<br>
<h3>Searching Snippets</h3>";

if (!empty($ph)) {

    $count = 0;
    $type = 'snippet';

    $snippets = $modx->getCollection('modSnippet');

    foreach ($snippets as $snippet) {
        $name = $snippet->get('name');
        $content = $snippet->get('snippet');

        $count += checkContent($content, $ph, $type, $name, $output);

    }
    if ($count == 0) {
        $output .= '<p>Placeholder not found in snippets</p>';
    }
} else {
   return "\n<p>Error: Placeholder property is empty</p>";
}

$output = "\n\n<br>
<h3>Searching Plugins</h3>";

if (!empty($ph)) {
    $count = 0;
    $type = 'plugin';
    $plugins = $modx->getCollection('modPlugin');
    foreach ($plugins as $plugin) {
        $name = $plugin->get('name');
        $content = $plugin->get('plugincode');

        $count += checkContent($content, $ph, $type, $name, $output);
    }
    if ($count == 0) {
    $output .= "\n<p>Placeholder not found in plugins</p>";
    }
}

return $output;

First, we get the placeholder name from the snippet property and put it in the $ph variable. In the else statement, we return an error message if we didn't get the placeholder (maybe we misspelled it).

Next, we get all the snippets and pull their code with $content = $snippet->get('snippet');, or in the case of plugins, $content = $plugin->get('plugincode');.


If you are short of memory and have really a lot of snippets and plugins, you can change getCollection() to getIterator(), which will load the objects one at a time, though this is seldom necessary. That makes the snippet a little slower, so it could possibly time out.


We use str_pos() to check for the placeholder because it's the fastest method. It gives the position of the placeholder (which we don't need) if it's found. If it's not found, str_pos() returns false.

If we find the placeholder (str_pos() returned something other than false), we add a message giving the name of the snippet or plugin and the name of the placeholder to the $output variable to be returned at the end of the snippet. Notice that the $output variable is passed by reference to the checkContent() function. You can see this in the arguments at the top of that function: &$output. We do this so that we can change the output variable in the function and the changes will persist outside it so we can display them at the end of the snippet.


Improvements

If you are writing this for a client, you might want to beautify the output some by adding class names and some CSS, and/or making the output of each section into an ordered or unordered list. You could use a Tpl chunk for the format of each line.

It would be possible (though a little tricky) to calculate the line number where the placeholder appears in the snippet or plugin code. That would involve using the position to pull all the code up to the placeholder with substr() and counting the number of newlines by using explode() and count(). I'm not sure it would be reliable, though, and it's easy enough to edit the snippet or plugin and find the placeholder with your browser's search function.

It's arguable whether this would be an improvement, but I really hate having two sections of code that are near duplicates of each other. You may have noticed that we've done that with the snippet and plugin sections of the code above. If would be possible to have a single section of code that executed twice using an array like this one:

    $elements = array(
        'modSnippet' => 'snippet',
        'modPlugin' => 'plugincode',
    };

The keys are the names of the elements and the values are the field that contains the code. A foreach() loop could iterate through them, using the key and value in the arguments to our function, and in the headings. We'd probably want to remove the 'mod' for the headings and the individual lines of the results display. The problem is that doing it this way would indent and complicate the code, making it more difficult to read and to understand, especially for people not fluent in PHP. I don't think it would improve the speed, and any memory savings would be negligible. You could make a case that it would be a more "elegant" solution, but since the two code sections are very short, I opted to go with the version above.

We'll look at another improvement in the next article.


Coming Up

Some snippets or plugins might set the placeholder in a file (usually a class file) that's pulled into their code with include, include_once, require, or require_once. In those cases, our snippet won't see the placeholder because it's not in the body of the snippet or plugin code. We'll add an enhancement to handle that situation in the next article.


Looking for high-quality, MODX-friendly hosting? As of May 2016, Bob's Guides is hosted at A2 hosting. (More information in the box below.)



Comments (0)


Please login to comment.

  (Login)