Jump to content

Email Obfuscation (EMO)


Recommended Posts

ProcessWire Email Obfuscation (EMO)

Download | GitHub

Email Obfuscation module for email addresses with 64 base crypting.

This module finds all plaintext emails and email links from the document and replaces them with span elements including configurable replace text. All the addresses are encoded to 64 base strings and stored in spans data attributes. Then on client side we decode these strings back to their original state.


  1. Create new 'EmailObfuscation' folder into /site/modules/ and place the content of this repository into the directory.
  2. Login to processwire and go to Modules page and click 'Check for new modules'. Find 'EmailObfuscation' and click install.
  3. You can make optional configuration changes in the module admin page.


This is a ProcessWire module fork from MODX Evolution plugin emo E-Mail Obfuscation.

2013-03-03 - Added description.
2020-04-16 - Fixed GitHub links and updated description.

Hello all. Just found PW few days ago and it's already looking awesome! Here comes first contribute and some questions for developing it further.

There was one existing email obfuscator on reposity that didn't use any crypting for addresses so I decided to do a little test run and port the one that we currenly use with MODX Evo to ProcessWire module.

I'd like to make PageAutocomplete like gonfigure option to admin so that one could select and set templates to exclude module action. It looks like autocomplete is tied to pages and since templates are not set to system as pages this is option is no go, am I right?

  • Like 10
Link to comment
Share on other sites

OK, ASMSelect did the job perfectly. Thanks!

Another question:

In Hello world example module admin pages are restricted by: if($page->template == 'admin') return;

$template array also holds another admin pages like role and user so is there some easy way skip these in loop without naming all of them?

foreach ($this->templates as $t) {
    if ($t != 'admin') { // !in_array($t, array(admin,role,user)); is this proposed way?
        $f->addOption($t->id, $t->name);

I just need same template names that are visible on Setup > Templates.

Link to comment
Share on other sites

What I've been doing in another module is removing all templates flagged as system. That's probably what you want to do here too.

foreach ($this->templates as $t) {
    // skip system templates
    if (!($t->flags & Template::flagSystem)) {
       $f->addOption($t->id, $t->name);
  • Like 2
Link to comment
Share on other sites

Is there a reason why $field->collapsed = Inputfield::collapsedBlank; works as expected with InputfieldAsmSelect but does nothing with InputfieldCheckbox in module config?

Link to comment
Share on other sites

Is there a reason why $field->collapsed = Inputfield::collapsedBlank; works as expected with InputfieldAsmSelect but does nothing with InputfieldCheckbox in module config?

I think we had a bug related to this awhile back. It should be fixed in 2.3. But for now this might work:

$field->collapsed = $field->attr('checked') ? Inputfield::collapsedYes : Inputfield::collapsedNo; 

Thanks for your work with this module. When you get a chance, please add to the modules.processwire.com directory. 

Link to comment
Share on other sites

Thanks Ryan! This got work done perfectly.

There's still one thing I can't get figured out. With InputfieldPageAutocomplete in module config I can set parent_id and template_id to limit results. How could I configure it so that all admin pages plus the ones limited in $this->exclude_tpls config would be left out from results?

I get what I want by using InputfieldAsmSelect but I think InputfieldPageAutocomplete would fit much better here:

$f = $this->modules->get('InputfieldAsmSelect');
$f->label = 'Exclude in pages';
$f->attr('name', 'exclude_pages');
$f->attr('value', $this->exclude_pages);
$home = $this->pages->get("/");
$pages = $home->children;
foreach ($pages as $p) {
    if (!in_array($p->template->id, $this->exclude_tpls)) {
        $f->addOption($p->id, $p->name);
Link to comment
Share on other sites

The parent_id and template_id are one way to go, but you could instead use 'findPagesSelector'. You can set this to a Field object, or you can set it to an InputfieldPageAutocomplete instance, like this:

$f = $this->modules->get('InputfieldPageAutocomplete');
$f->label = 'Exclude in pages';
$f->attr('name', 'exclude_pages');
$f->attr('value', $this->exclude_pages);
$selector = "parent_id=1, template!=admin";
foreach($this->exclude_tpls as $name) {
  $selector .= "|$name";
// example: parent_id=1, template!=admin|this|that|whatever
$f->findPagesSelector = $selector; 

  • Like 1
Link to comment
Share on other sites

  • 10 months later...
Almost one year later I'm happy to say that I finally got this autocomplete feature implemented to exclude pages option. Also forced mailto links can now be disabled from module config. Version got bumped to 1.0.2.

This also means that our love and relationship with PW is soon having it's first birthday. What a year! Still having butterflies in stomach.

  • Like 8
Link to comment
Share on other sites

  • 1 month later...

I think we had a bug related to this awhile back. It should be fixed in 2.3. But for now this might work:

$field->collapsed = $field->attr('checked') ? Inputfield::collapsedYes : Inputfield::collapsedNo; 

Thanks for your work with this module. When you get a chance, please add to the modules.processwire.com directory. 

Hey Ryan,

I just came across this issue on the latest dev version - presumably with stable as well. I ended up going with your fix for now, although for anyone stumbling across this, the Yes and No need to be reversed in the ternary operator.

Link to comment
Share on other sites

  • 7 months later...

It seems to be adding the classes to the email and adding mailto, but in the source, the actual email address is as clear as day... :)

Hi Joss!

If classes are added and it turns email strings to mailto links then it sounds like it's working like it should be. My first guess is that you are looking at generated source code, e.g. using inspect element tool from the browser? Decryption is done on client side so in this case, all is fine.

Document source code from server should have predefined text string wrapped to span elements (with emo class and unique id) in place of email addresses.

Link to comment
Share on other sites

  • 2 months later...

@Roope -

great module! Used on a recent site; And now trying to use it on a new site, but having 2 issues:

1.) There was a point where there was accidentally an email address in the meta description of the site, and that triggered the emo, which broke the page; any way to disable it in the head section?

2.) the emo is triggering on a retina image (@2x in the image name...)


Link to comment
Share on other sites

@roope - i downloaded the latest version, and replaced it, but it still picks up the retina images, which are marked up like this:

<a href="/" class="retina-logo" data-dark-logo="/site/templates/images/lk-logo-dark@2x.png">

i guess there is no way to prevent this sort of thing, because i see in the code that the module will exclude head, images and forms.., but this is a link with a data attribute...

but maybe there could be an option to allow the module to only work on parts of the page in a setting, for example, "container classes to apply the obfuscation to", then it would be possible for designers to limit to only specific places in the page, which would reduce the chance of it picking up images with the @ symbol..


Link to comment
Share on other sites

OK, this one is bit tricky..

Currently emo uses regex to leave head, form and img tags untouched. Link element can't be left untouched since we're parsing emails.
Parsing unregular language like html with regular expressions is always on risky side. For that reason adding support for emo container class like you suggested would need proper DOM parser and IMO that introduces too much heavy lifting for the small need.
So, at least for now, you have the following options:
1. Use @2x names on a img tag
2. Disable current page from module settings
3. Disable current template from module settings
From these I would personally just go with number one:
<a href="/" class="retina-logo">
   <img src="../lk-logo-dark.png" data-dark-logo="../lk-logo-dark@2x.png" />

  • Like 1
Link to comment
Share on other sites

This worked great on my local server but when I installed it on the real server pages with email addresses fail. I get ERR_EMPTY_RESPONSE. I wasn't able to find any more clues but if I ever do I'll let you know. Both setups are running 2.5.10 dev.

Link to comment
Share on other sites

  • 2 months later...
  • 2 months later...

While testing your module I found out that it doesn't work properly in some cases:

//no need to obfuscate, since there is no E-Mail Adress
<a href="mailto:?subject=Lorem%20Ipsum&body=Lorem%20Ipsum!">Foo</a>
<a href="mailto:?subject=Lorem Ipsum!">Foo</a>

//doesn't work with the following examples
<a href="mailto:mail@example.org?subject=Lorem Ipsum!">Foo</a>
<a href="mailto:mail@example.org?subject=Lorem%20Ipsum&body=Lorem%20Ipsum!">Foo</a>

For the 1st Example the module should leave the code untouched. Replacement only necessary if '@' is found.

For 2nd Example I can offer a solution


// urlencode a possible subject
$matches[1] = preg_replace_callback("!(.*\?(subject|body)=)([^\?]*)!i",
function ($m) { return rawurlencode($m[3]); }, $matches[1]);

// the function removes the questionmark, the words 'subject' and 'body' and the equalsign from the querystring 


// urlencode a possible subject and body, no matter if already urlencoded
$_matches = preg_replace_callback("!(^\?*)?((\??subject|\??body)=([^&]+)){1,2}!i",
function ($m) { return $m[1].$m[3].'='.rawurlencode(rawurldecode($m[4])); }, $matches[1]);

I tested a little bit and it worked well with my solution.

  • Like 3
Link to comment
Share on other sites

  • 1 month later...
Thanks for your input kixe!

Sorry I'm little late to the party but I finally got some time to look this through on summer vacation. There was also some other issues with the module that I wanted to fix while I'm there so here it goes:

I updated the mailto link regex to match only cases where actual email address is also present so your 1st example doesn't get obfuscated no more.

While your fix for the second example worked really well, I decided to leave whole url encoding part off since it didn't seem to be vital for this module to work.

I also changed the way emo avoids @2x named images to be triggered as false positives so now markup like what @Macrura had in the example here works. Before it was only possible with image tags which were skipped from obfuscation. Old method also failed when there was image inside mailto link.

And last but not least; matched mailto link markup doesn't get replaced anymore by new element. We'll use the original captured html and just add required emo_email class to the link - like what @joey102030 just suggested here one post earlier.

I've pushed a new commit to Github that contains all these modifications. Please test it out and report any issues you may find. Thanks!

Link to comment
Share on other sites

  • 1 month later...

I've doubled up on javascripts and apparently it's causing an issue.  I recently started working on an interactive map on my site, the problem is that since I'm rendering the HTML on an event (click of the state loads .html(<div>...john@email.com...) ), it's loading the replacement text instead of my email addresses.  Are there any steps I can take to make it work properly without blocking the template altogether?

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By kixe
      Field that stores one or more references to ProcessWire pages with additional data in field context.
      Values are editable via page edit modal of the referenced page provided from the field if module AdminPageFieldEditLinks is installed and "Enable link to create new pages?" is checked in field settings.
      Requirements: AdminPageFieldEditLinks >= 3.1.4
      Use case example:
      The planning of the Tonmeistertagung in the CCD (Congress Center Düsseldorf) from November 3rd, 2021 to November 6th, 2021 is in the finalization phase.
      The conference consists of a conference part and an exhibition. The planning is done via a separate frontendless PW instance. There, all companies (pages) that are active at various events are kept in a pool. Changes (address, logo) can always be done there. For the exhibition of the current conference in November, the exhibitor-companies (pages) are selected via a page reference field. A stand number must now be assigned to each selected company (page). We had originally solved this using the Profield FieldtypeTable. However, this had the disadvantage that each entry again made all companies available for selection and did not recognize which were already selected in a previous table row. The new field type now allows the value (company's stand number) to be assigned to a Company (page) in context to a specific Pagefield living in a specific page.
    • By Robin S
      This module lets you add some custom menu items to the main admin menu, and you can set the dropdown links dynamically in a hook if needed.
      Sidenote: the module config uses some repeatable/sortable rows for the child link settings, similar to the ProFields Table interface. The data gets saved as JSON in a hidden textarea field. Might be interesting to other module developers?
      Custom Admin Menus
      Adds up to three custom menu items with optional dropdowns to the main admin menu.
      The menu items can link to admin pages, front-end pages, or pages on external websites.
      The links can be set to open in a new browser tab, and child links in the dropdown can be given an icon.
      Requires ProcessWire v3.0.178 or newer.
      Example of menu items

      Module config for the menus

      Link list shown when parent menu item is not given a URL

      Setting child menu items dynamically
      If needed you can set the child menu items dynamically using a hook.
      $wire->addHookAfter('CustomAdminMenus::getMenuChildren', function(HookEvent $event) { // The menu number is the first argument $menu_number = $event->arguments(0); if($menu_number === 1) { $colours = $event->wire()->pages->findRaw('template=colour', ['title', 'url', 'page_icon']); $children = []; foreach($colours as $colour) { // Each child item should be an array with the following keys $children[] = [ 'icon' => $colour['page_icon'], 'label' => $colour['title'], 'url' => $colour['url'], 'newtab' => false, ]; } $event->return = $children; } }); Create multiple levels of flyout menus
      It's also possible to create multiple levels of flyout submenus using a hook.

      For each level a submenu can be defined in a "children" item. Example:
      $wire->addHookAfter('CustomAdminMenus::getMenuChildren', function(HookEvent $event) { // The menu number is the first argument $menu_number = $event->arguments(0); if($menu_number === 1) { $children = [ [ 'icon' => 'adjust', 'label' => 'One', 'url' => '/one/', 'newtab' => false, ], [ 'icon' => 'anchor', 'label' => 'Two', 'url' => '/two/', 'newtab' => false, 'children' => [ [ 'icon' => 'child', 'label' => 'Red', 'url' => '/red/', 'newtab' => false, ], [ 'icon' => 'bullhorn', 'label' => 'Green', 'url' => '/green/', 'newtab' => false, 'children' => [ [ 'icon' => 'wifi', 'label' => 'Small', 'url' => '/small/', 'newtab' => true, ], [ 'icon' => 'codepen', 'label' => 'Medium', 'url' => '/medium/', 'newtab' => false, ], [ 'icon' => 'cogs', 'label' => 'Large', 'url' => '/large/', 'newtab' => false, ], ] ], [ 'icon' => 'futbol-o', 'label' => 'Blue', 'url' => '/blue/', 'newtab' => true, ], ] ], [ 'icon' => 'hand-o-left', 'label' => 'Three', 'url' => '/three/', 'newtab' => false, ], ]; $event->return = $children; } }); Showing/hiding menus according to user role
      You can determine which menu items can be seen by a role by checking the user's role in the hook.
      For example, if a user has or lacks a role you could include different child menu items in the hook return value. Or if you want to conditionally hide a custom menu altogether you can set the return value to false. Example:
      $wire->addHookAfter('CustomAdminMenus::getMenuChildren', function(HookEvent $event) { // The menu number is the first argument $menu_number = $event->arguments(0); $user = $event->wire()->user; // For custom menu number 1... if($menu_number === 1) { // ...if user does not have some particular role... if(!$user->hasRole('foo')) { // ...do not show the menu $event->return = false; } } });  
    • By tcnet
      This module for ProcessWire sends a notification email for each failed login attempt. Similar modules exists already in the module directory of ProcessWire. However, this module is designed to notify, even if specified user doesn't exist.
      The settings for this module are located in the menu Modules=>Configure=>LoginFailNotifier.
      Notification email
      Specifies the email address to which the notification emails should be sent.
        Email subject
      Specifies the subject line for the notification email.
        Post variables
      Specifies the $_POST variables to be included in the notification email. Each variable must be separated by a comma. For example: login_name,login_pass
        Server variables
      Specifies the $_SERVER variables to be included in the notification email. Each variable must be separated by a comma. For example: REMOTE_ADDR,HTTP_USER_AGENT
      Link to ProcessWire module directory:
      Link to github.com:
    • By Fokke
      ProcessWire 3.x markup module for rendering meta tags in HTML document head section. Note that this module is not a full-blown SEO solution, but rather a simple tool for rendering meta tags based on module configuration. Adding custom meta tags is also supported.
      Built-in meta tags
      The following meta tags are supported out-of-the-box:
      Document title consisting of page title and site name Character set Canonical Viewport Description Keywords Hreflang tags Open Graph og:title og:site_name og:type og:url og:description og:image og:image:width og:image:height Twitter meta tags twitter:card twitter:site twitter:creator twitter:title twitter:description twitter:image Facebook meta tags fb:app_id The full documentation with configurable options can be found here: https://github.com/Fokke-/MarkupMetadata
      ProcessWire>=3.0.0 PHP >=7.1 Installation using Composer
      composer require fokke/markup-metadata Manual installation
      Download latest version from https://github.com/Fokke-/MarkupMetadata/archive/master.zip Extract module files to site/modules/MarkupMetadata directory.
    • By m.sieber
      ITRK-Service for ProcessWire
      Module for the automated transfer of imprint, data protection declaration and terms and conditions from IT-Recht Kanzlei to your ProcessWire installation
      What is ITRK Service for ProcessWire?
      ITRK-Service for ProcessWire is a free module for ProcessWire CMS. It provides an interface to the update service of IT-Recht Kanzlei, via which the legal texts of your online presence are automatically updated. In this way, the texts remain legally secure and warning-proof in the long term. Imprint, data protection declaration, revocation and general terms and conditions are currently supported.
      You can find our documentation (in german language) here: https://www.pupit.de/itrk-service-for-processwire/dokumentation/

      Download: https://www.pupit.de/itrk-service-for-processwire/
      Github: https://github.com/pupit-de/pwItrkServiceConnector
  • Create New...