Add noopener to Link Tags III

A snippet to fix the links, then save them to the database


In the previous article, we looked at a way to automatically add rel="noopener noreferrer" to link code in MODX. The main problem with the previous method was that it slowed down page loads. In this one, we'll see a snippet that processes multiple MODX resources and elements, corrects the code, and saves the result to the database.

MODX logo

The Snippet

This snippet (let's call it FixLinks) loops through all resources, plugins, and chunks, correcting the code, and saving the results as it goes. It also reports on what it did.

/* FixLinks snippet */
$objectTypes = array(
    'modResource' =>  'pagetitle',
    'modChunk' => 'name',
    'modTemplate' => 'name',
);

/* Are we running from the command line? */
$cliMode = php_sapi_name() == 'cli';

$count = 0;
/* Define the newline based on cli mode */
$nl = $cliMode? "\n" : "<br>";
$output = '';

$typeCounts = array();

foreach ($objectTypes as $type => $nameAlias) {
    $typeCounts[$type] = 0;
    $objs = $modx->getCollection($type);

    foreach ($objs as $obj) {

        if ($obj->get('class_key') === 'Article') {
            $content = $obj->get('content');
        } else {
            $content = $obj->getContent();
        }

        if (empty($content)) {
            // $modx->log(modX::LOG_LEVEL_ERROR, 'Could not get content for ' . $type . ' ' . $obj->get('id'));
            continue;
        }

        if ((strpos($content, '_blank') !== false) && (strpos($content, 'noopener') === false)) {
            $pattern = '/target\s*=\s*[\'"]_blank[\'"]/i';
            $newContent = preg_replace($pattern, 'target="_blank" rel="noopener noreferrer" ', $content);
            $obj->setContent($newContent);
            if ($obj->save()) {
                if ($content !== $newContent) {
                    $output .= $nl . 'Modified ' . $type . '  ' . $obj->get($nameAlias);
                } else {
                    $output .=  $nl . 'Failed to modify ' . $type . '  ' . $obj->get($nameAlias);
                }
            }

            $typeCounts[$type]++;
            $count++;
        }

    }
}

$output .=  $nl . "Finished -- found " . $count . " total objects with _blank and not noopener" . $nl;
$output .=  "Changed: " . $nl . print_r($typeCounts, true);

if ($cliMode) {
    echo $output;
} else {
    return $output;
}


The code above first sets up the array of object types (resource, chunk, and template). The key and value pairs in the array contain the class key of the object as the key, and the field that contains the name of the object as the value. We'll use that name field in our report later in the code.

Next, the code checks to see if the script is running from the command line or in an editor. Then, it defines a newline character ($nl) based on the result of that test. There are two reasons for doing this.

One is that if the site is very large, the script might time out unless it's run from the command line. The second is that I test code like this by running it in my code editor (PhpStorm), which is the equivalent of running it from the command line. If the code is running in CLI mode (Command Language Interpreter), br tags will be rendered literally and won't produce a newline. If it's running as a snippet in MODX, the newline character ("\n") will be rendered literally and won't produce a newline. So we need to use the correct newline indicator.

We also initialize the $count variable which counts the total number of objects where the fix is implemented, and the $output variable which holds the output to be displayed when the code is finished. We also initialize the $typeCounts variable which holds the count of fixed objects of each type.

The outer foreach() loop cycles through the object types (modResource, modChunk, and modTemplate). The inner foreach() loop iterates over each object of the type currently being processed.

At the beginning of the outer loop, we set the count of fixed objects of that type to 0. Near the bottom of the inner loop, we increment that count every time we fix an object.

In the inner loop, we first get the content of the object. We need different code for this if the object is a resource and has Article as its class_key because calling getContent() on an Article throws a PHP error in the current version of MODX.

Next, we skip objects where the content is empty, then test to see if the content field contains _blank and doesn't contain noopener. We only modify objects when both those conditions are met. If the object passes the test, we use preg_replace to replace all variants of target="_blank" with target="_blank" rel="noopener noreferrer" and save the object. We report on whether the save was successful, then update both the $count variable and the appropriate member of the $typeCounts variable.

When both the inner and outer loops have finished, we report the results.


The Pattern

The pattern we use for the preg_replace() call will match any possible variant of target="_blank". Here's the pattern and an explanation of what it does:

$pattern = '/target\s*=\s*[\'"]_blank[\'"]/i';

The two slashes at either end are the delimiters of the pattern. They tell the regex engine where the pattern begins and ends. The i at the end says that we want a case-insensitive search (in case the word target is in upper case or mixed case). The pattern looks for the word "target" followed by 0 or more spaces, followed by an equals sign, followed by a 0 or more spaces, followed by a double or single quote, followed by the word "_blank" followed by a single or double quote.


Drawbacks

There are still a few problems with this code. First, it assumes that all the links on the page are either fixed, or not fixed. If the user adds a new, unfixed, link, and then re-runs the snippet, the new link won't be fixed because the page content already contains noopener.

The code also won't fix links that are generated in code. Wayfinder, getResources, and some other extras create links based on their Tpl chunks (which our snippet will correct if they contain target="_blank"), but there may be other snippets or plugins that create links in their PHP code. Those won't be fixed, nor will links that are in Template Variables.


Coming Up

In the following article, we'll see a new version of the snippet that performs a more intelligent search and replace operation, and we'll move our code to fix the links into a function.




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)