Savant2 Mailto Plugin

DanF 0 Tallied Votes 145 Views Share

Hi, this is my first post here, just wanted a place to post some PHP code for a Savant2 plugin I created. I hope that's ok.

If you've never heard of Savant/Savant2, you should definitely visit its website. Savant is a very elegant template solution for PHP that stands out for its simplicity.

Anyway, Savant supports plugins, so I decided to make one for mailto links. It offers many options to obfuscate e-mail addresses in order to protect them from evil e-mail harvesting software/bots.

<?php

/**
* Definitions.
*/

define('SAVANT2_PLUGIN_MAILTO_NOSCRIPT', 0);


/**
* Plugin text.
*/

if (! isset($GLOBALS['_SAVANT2_PLUGIN_MAILTO'])) {
	$GLOBALS['_SAVANT2_PLUGIN_MAILTO'] = array(
		SAVANT2_PLUGIN_MAILTO_NOSCRIPT => '(address hidden)',
	);
}


/**
* Base plugin class.
*/

require_once 'Savant2/Plugin.php';


/**
* 
* Output an HTML <a href="mailto:">...</a> tag, and optionally 
* obfuscates e-mail addresses to protect them from spam harvesting.
* 
* 
* @author Dan Ferreira (savant at vemconcursos dot com)
* 
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* 
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
* 
*/

class Savant2_Plugin_mailto extends Savant2_Plugin {
	
	/**
	* @var array $_javaScriptStart HTML prepended to JavaScript code
	*/
	
	var $javaScriptPrepend;
	
	
	/**
	* @var array $_javaScriptEnd HTML appended to JavaScript code
	*/
	
	var $javaScriptAppend;
	
	
	/**
	* 
	* Constructor
	* 
	* 
	* @access public
	* 
	*/
	
	function Savant2_Plugin_mailto()
	{
		$this->javaScriptPrepend = '<script type="text/javascript" ' .
			"language=\"JavaScript\">\n<!--\n";

		$this->javaScriptAppend = "\n//-->\n</script><noscript>" .
			$GLOBALS['_SAVANT2_PLUGIN_MAILTO'][SAVANT2_PLUGIN_MAILTO_NOSCRIPT]
			. "</noscript>";
	}
	
	
	/**
	* 
	* Output an HTML <a href="mailto:">...</a> tag, and optionally
	* obfuscates e-mail addresses to protect them from spam harvesting.
	* 
	* Examples:
	* 
	* $this->plugin('mailto', 'email@domain.com');
	* $this->plugin('mailto', 'email@domain.com', 'Email me!');
	* $this->plugin('mailto', 'email@domain.com', null, 'munge');
	* $this->plugin('mailto', 'personal@domain.com', null, 'jsmix');
	* $this->plugin(
	* 	'mailto',
	* 	array(
	* 		'to' => 'email@domain.com',
	* 		'subject' => 'Hi there',
	* 		'cc' => 'friend@domain.com'
	* 	),
	* 	'Contact'
	* );
	* 
	* $this->plugin(
	* 	'mailto',
	* 	'email@domain.com',
	* 	null,
	* 	null,
	* 	'class="email" onClick = "javaFunction()"'
	* );
	* 
	* $this->plugin(
	* 	'mailto',
	* 	'email@domain.com',
	* 	'my Email',
	* 	'jsxor',
	* 	array(
	* 		'id' => 'mailLink',
	* 		'onHover' => 'showDescription()'
	* 	)
	* );
	* 
	* @access public
	* 
	* @param string|array $mailto The e-mail address, can also be
	* an array with any combination of these keys:
	* array(
	* 	'to' => 'e-mail address',
	* 	'cc' => 'carbon copy',
	* 	'bcc' => 'blind carbon copy',
	* 	'body' => 'message body',
	* 	'subject' => 'message subject',
	* 	'newsgroups' => 'newsgroup(s) to post to',
	* 	'followupto' => 'address(es) to follow up to'
	* );
	* 
	* @param string|null $text Optional displayed text of the link. If
	* null, the e-mail address will be displayed.
	* 
	* @param string|null $obfuscate Optional obfuscation. Can be one of
	* the following:
	* 
	* - 'none'     (no changes applied)
	* - 'munge'    (no link, just munged address: email at domain dot com)
	* - 'hex'      (simple hex encoding)
	* - 'jschar'   (JavaScript encoding with String.fromCharCode())
	* - 'jshex'    (JavaScript hex encoding)
	* - 'jsmix'    (JavaScript encoding with assignment instructions)
	* - 'jsxor'    (JavaScript encoding with simple xor encryption)
	* 
	* ... or the name of your own method if you extend this class.
	* 
	* @param string|array|null $attr Any optional extra attributes for
	* the <a> tag.
	* 
	* @return string The link or JavaScript that generates the link.
	* 
	*/
	
	function plugin($mailto, $text = null, $obfuscate = null, $attr = null)
	{
		$query = '';
		
		// $mailto is an array? retrieve $address and $query
		if (is_array($mailto)) {
			
			if (isset($mailto['to']) && is_string($mailto['to']))
				$address = $mailto['to'];
			else
				$address = '';
			
			$separator = strpos($address, '?') === false ? '?' : '&';
			
			foreach($mailto as $key => $val) {
				
				switch ($key) {
					case 'cc':
					case 'bcc':
					case 'followupto':
						if (! empty($val)) {
							$query .= $separator . rawurlencode($key) . '=' .
								str_replace('%40', '@', rawurlencode($val));
							$separator = '&';
						}
						break;
					
					case 'body':
					case 'subject':
					case 'newsgroups':
						if (! empty($val)) {
							$query .= $separator . rawurlencode($key) . '=' .
								rawurlencode($val);
							$separator = '&';
						}
						break;
				}
			}
		}
		// $mailto is a string
		else {			
			$address = $mailto;
		}
		
		// if not defined, the displayed text is the e-mail address
		if (is_null($text)) {
			$text = $address;
		}
		
		$extra = '';
		
		// add attributes
		if (is_array($attr)) {
			// from array
			foreach ($attr as $key => $val) {
				$key = htmlspecialchars($key);
				$val = htmlspecialchars($val);
				$extra .= " $key=\"$val\"";
			}
		} elseif (! is_null($attr)) {
			// from scalar
			$extra .= " $attr";
		}
		
		// full link HTML
		$html = "<a href=\"mailto:{$address}{$query}\"{$extra}>$text</a>";
		
		// apply obfuscation
		switch($obfuscate) {
			
			// displays e-mail address like so: email at domain dot com
			// every other information is lost, does not return a link
			case 'munge':
				return str_replace(array('.','@'), array(' dot ', ' at '), 
					$address);
				break;
			
			
			// standard hex encoding
			case 'hex':
				return "<a href=\"". $this->encode("mailto:$address$query") .
					"\"$extra>" . $this->encode($text) ."</a>";
				break;
			
			
			// uses String.fromCharCode() to decode the string
			case 'jschar':
				$encoded = $this->encode($html, 'char');
				
				return $this->javaScriptPrepend .
					"document.write(String.fromCharCode($encoded));" . 
					$this->javaScriptAppend;
				break;
			
			
			// encodes with regular hex chars
			case 'jshex':
				$encoded = $this->encode("document.write('$html');", 'js');
				
				return $this->javaScriptPrepend .
					"eval(unescape('$encoded'));" .
					$this->javaScriptAppend;
				break;
			
			
			// outputs a script with random regular assignments that
			// assemble the link
			case 'jsmix':
				$html = $this->encode($html, 'jsarray');
				
				$i = count($html) - 1;
				
				$l = 1;
				
				$j = $k = round($i / 2);
				
				$encoded = 'var e=\'' . $html[$j] . '\';';
				
				while ($j < $i || $k > 0) {
					if ($k ? ($j < $i ? rand(0,1) : 0) : 1)
						$encoded .= 'e+=\'' . $html[++$j] . '\';';
					else
						$encoded .= 'e=\'' . $html[--$k] . '\'+e;';
					
					if ($l++ > 8) {
						$encoded .= "\n";
						$l = 0;
					}
				}
				
				return $this->javaScriptPrepend .
					"$encoded\ndocument.write(unescape(e))" .
					$this->javaScriptAppend;
				break;
			
			
			// applies straightforward xor encryption
			case 'jsxor':
				$key = chr(rand(0, 255));
				$encoded = '';
				for ($i = 0; $i < strlen($html); $i++) {
					$encoded .= '%' . bin2hex($key ^ $html[$i]);
				}
				
				$key = ord($key);
				
				return $this->javaScriptPrepend . <<<SCR
var link = '';
var e = unescape('$encoded');
for (var i = 0; i < e.length; i++)
	link += String.fromCharCode($key ^ e.charCodeAt(i));
document.write(link);
SCR
				. $this->javaScriptAppend;
				break;
			
			
			// tries to call a method with this name, useful so you can
			// extend this class and define your own obfuscation.
			default:
				if (is_callable(array(&$this, $obfuscate))) {
					return call_user_func_array(array(&$this, $obfuscate),
						array(
							'address' => &$address,
							'text' => &$text,
							'html' => &$html,
							'query' => &$query,
							'extra' => &$extra,
							'mailto' => &$mailto,
							'attr' => &$attr
						)
					);
				}
			
			
			// just displays the link
			case null:
			case 'none':
				return $html;
				break;
		}
	}
	
	
	/**
	* You can also extend this class and define your own obfuscation
	* method, which can be invoked using plugin()'s third argument.
	* 
	* Your method will receive arguments you can retrieve with
	* func_get_args():
	* 
	* array(
	* 	0 => 'address: string with e-mail address',
	* 	1 => 'text: the text to be displayed on the link',
	* 	2 => 'html: string with HTML mailto link',
	* 	3 => 'query: string with query ("?cc=email@...", etc.)',
	* 	4 => 'extra: string with the extra tags, retrieved from $attr',
	* 	5 => 'mailto: string with e-mail address or an array',
	* 	6 => 'attr: string or array with extra attributes for the link'
	* )
	* 
	* You can also directly assign them in your function declaration:
	* 
	* function myMethod ($address)
	* function myMethod ($address, $text)
	* function myMethod ($address, $text, $html, $query)
	* function myMethod ($address, $text, $html, $query, $extra, $mailto,
	* 	$attr)
	*/
	
	
	/**
	* 
	* Encodes string in a few different formats, mostly for internal use
	* 
	* 
	* @access private
	* 
	* @param string $str The string to be encoded.
	* 
	* @param string $type Type of encoding.
	* 
	* @return string|array The encoded string or array.
	* 
	*/
	
	function encode($str, $type = 'hexents') {
		$return = '';
		switch ($type) {
			case 'char':
				$comma = '';
				for ($i = 0; $i < strlen($str); $i++) {
					$return .= $comma . ord($str[$i]);
					$comma = ',';
				}
				break;
			
			case 'js':
				for ($i = 0; $i < strlen($str); $i++) {
					$return .= '%' . bin2hex($str[$i]);
				}
				break;
			
			case 'jsarray':
				$return = array();
				for ($i = 0; $i < strlen($str); $i++) {
					$return[$i] = '%' . bin2hex($str[$i]);
				}
				break;
			
			default:
			case 'hexents':
				for ($i = 0; $i < strlen($str); $i++) {
					$return .= '&#x' . bin2hex($str[$i]) . ';';
				}
				break;
		}
		return $return;
	}
}

?>
Dani 4,084 The Queen of DaniWeb Administrator Featured Poster Premium Member

Thanks for giving us DaniWeb's most popular code snippet :) I'm sorry to see you haven't stuck around very long at all ... come visit us, Dan! :)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.