Drupal 7: Making a global regular expression replacement

This post was written by eli on August 17, 2013
Posted Under: Drupal,Server admin

The goal: Create spam-harvesting safe mailto links anywhere in the pages, generated on the fly with slightly obfuscated JavaScript, so that harvesting bots don’t get the email addresses. To make it slightly more complicateded, I want a replacement pattern with a parameter, telling the replacement script which email address to inject.

In other words, if I wrote %mail{1}% somewhere in the page’s source, that is replaced by the second email address in a known list of addresses.

Of course there the Token module. But I wanted the replacement to depend on the token string. Globally.

I use arthemia, so this appears everywhere in the paths to files.

Bad attempt #1

I thought that dvessel’s suggestion on a pretty old page would do the job (and it looks like it was obvious to everyone which file to edit, sites/all/themes/arthemia/template.php). But that suggestion was good for Drupal 5.

The theme I used, arthemia, already has an arthemia_preprocess_page() function, but not an arthemia_render_template(). I followed the suggestion, and nothing happened, of course. Drupal 7 doesn’t look in that direction.

Bad attempt #2

There is this golden rule in Drupal, that you don’t edit the core functions. Well, well, it was time to break it again. This time with the core of the core: include/common.inc, modifying drupal_render. Yup, I couldn’t have broken that rule harder. But after a few hours of the regular Drupal ceremony of reading a lot of useless tips on the web, I thought it was best to just get the job done.

The main problem was that all hooks are related to a certain type of elements. There is no hook for “just before the HTML is handed over to the browser”. Or that’s the impression I got.

First, I added the script I wanted included in every page. It would prefer it to be in the <head> section (because that’s usually where global scripts are) and I would prefer it to be a piece of content. But again, hours of looking for the “Drupal” way to do it, I just added the following to the top of sites/all/themes/arthemia/page.tpl.php:

<script language="JavaScript">
<!--
function myfunction(i) {
 [ ... ]
}
-->
</script>

It’s an ugly solution, but it gets the job done. I could, in theory, plant it as the content of an unused part of the page, and then mangle the page’s structure as it was being processed. Too much work for the cute shortcut of altering the script from Drupal’s interface.

And now to finding the markups, and make them run the function above. Using the preg_replace_callback() function, render_template was edited to this in the end of the function (just before “return $output”):

// Mangle
 $output = preg_replace_callback("|%mail\{(\d+)\}%|",
   function ($matches) {
     return "<script language=\"JavaScript\">\n<!--\nmyfunction($matches[1]);\n-->\n</script>";
   },
   $output)

That worked well. Actually, too well. The replacement took place also in editing boxes, so the original markup text couldn’t be edited.

The correct way

OK, so I took one step back. Let’s use the regular hooking mechanism, after all, and define the hooks for the content and the footer. Good enough. And as a side effect, the core is left intact, as I only edit the theme’s page template file.

So instead of the changes above I added this to the top of sites/all/themes/arthemia/page.tpl.php:

<script language="JavaScript">
<!--
function myfunction(i) {

 [ ... ]
}
-->
</script>

<?php
function mail_post_render($content, $element) {
  $content = preg_replace_callback("|%mail\{(\d+)\}%|",
    function ($matches) {
      return "<script language=\"JavaScript\">\n<!--\nmyfunction($matches[1]);\n-->\n</script>";
    },
    $content);

 return $content;
}

if ($page['content']) {
 $page['content']['#post_render'] = array('mail_post_render');
}
if ($page['footer']) {
 $page['footer']['#post_render'] = array('mail_post_render');
}

?>

And that finally worked in a sane manner: The footer and content were mangled, and I was still able to edit the original text. Hurray!

As usual, a simple task took a full day to complete. Welcome to the wonderful world of Drupal.

Add a Comment

required, use real name
required, will not be published
optional, your blog address