| Server IP : 46.105.57.169 / Your IP : 216.73.216.144 Web Server : Apache System : Linux webd003.cluster120.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64 User : maitricfuz ( 93378) PHP Version : 8.4.10 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/maitricfuz/www/saint-martin-lg/plugins/convertformstools/emailcloak/ |
Upload File : |
<?php
/**
* @package Convert Forms
* @version 5.1.6 Free
*
* @author Tassos Marinos <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2026 Tassos All Rights Reserved
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\HTML\HTMLHelper;
/**
* Email Cloak Protection Plugin for Convert Forms
*
* This plugin addresses conflicts with Joomla's Email Cloak plugin which normally
* processes and cloaks all email addresses in content. When Email Cloak runs on
* Convert Forms content, it can break form layouts and functionality.
*
* How it works:
* 1. Temporarily replaces email addresses in form elements with unique hashes
* 2. Allows Email Cloak plugin to process the content
* 3. Restores the original email addresses after processing
*
* Key features:
* - Preserves email addresses in form inputs (value, placeholder attributes)
* - Allows proper email cloaking in HTML content fields
* - Maintains form functionality while keeping email protection
* - Prevents layout breaking caused by unwanted email address cloaking
*/
class PlgConvertFormsToolsEmailCloak extends CMSPlugin
{
/**
* Prefix used when generating unique hashes for email addresses.
* This makes the hashes easily identifiable in the HTML output.
*/
private const EMAIL_HASH_PREFIX = 'cf_email_';
/**
* Special marker used to disable Joomla's Email Cloak plugin processing.
* This is appended to form content to prevent unwanted email cloaking.
*/
private const EMAIL_CLOAK_OFF = '{emailcloak=off}';
/**
* Stores email addresses and their corresponding hashes.
* Used to temporarily replace email addresses with hashes and restore them later.
*
* @var array
*/
private static $emailHashes = [];
/**
* Application object
*
* @var \Joomla\CMS\Application\CMSApplication
*/
protected $app;
/**
* Protects email addresses found in the form HTML by replacing them with hashes.
* This prevents the core emailcloak plugin from processing these emails.
*
* @param string &$html The form HTML content
*
* @return void
*/
public function onConvertFormsFormAfterRender(&$html)
{
if (!$this->shouldIrun())
{
return;
}
// Let's protect the emails in the form HTML
// This is the first pass, where we protect the emails before any content plugins are applied.
$html = $this->protectEmails($html);
// After this event, Convert Forms triggers Joomla's onContentPrepare event, which activates content plugins, including EmailCloak.
// If a content plugin runs before EmailCloak and introduces additional email addresses into the form, those emails will be cloaked.
// To prevent this, we append {emailcloak=off} to disable the EmailCloak plugin for the form content.
$html .= self::EMAIL_CLOAK_OFF;
}
/**
* Additional protection pass after content plugins have run.
* This is needed because content plugins might introduce new email addresses.
*
* @param string &$html The form HTML content
*
* @return void
*/
public function onConvertFormsFormAfterContentPrepare(&$html)
{
if (!$this->shouldIrun())
{
return;
}
$html = $this->protectEmails($html);
// Finally, give the chance to Email Cloak plugin to cloak remaining (unprotected) email addresses.
// We skip the Joomla Articles view, as the Content Prepare event is triggered by default.
$dontRun = $this->app->input->get('option') == 'com_content' && $this->app->input->get('view') == 'article';
if ($dontRun)
{
return;
}
$html = HTMLHelper::_('content.prepare', $html, null, 'convertforms-emailcloak');
}
/**
* Restores the protected email addresses back to their original form
* after all plugins have finished processing the page.
*
* @return void
*/
public function onAfterRender()
{
if (!$this->shouldIrun())
{
return;
}
$buffer = $this->app->getBody();
$buffer = $this->revert($buffer);
$this->app->setBody($buffer);
}
/**
* Checks if the plugin should run based on various conditions:
* - Must be in frontend
* - Email cloak plugin must be enabled
* - Convert Forms component must be installed
*
* @return boolean
*/
private function shouldIrun()
{
if (!$this->app->isClient('site'))
{
return;
}
if (!PluginHelper::isEnabled('content', 'emailcloak'))
{
return;
}
// Initialize Convert Forms Library
if (!@include_once(JPATH_ADMINISTRATOR . '/components/com_convertforms/autoload.php'))
{
return;
}
return true;
}
/**
* Protects email addresses by replacing them with unique hashes.
* Only processes emails found in specific form elements and their attributes.
* Uses DOM parsing for reliable HTML manipulation.
*
* @param string $html The HTML content to process
*
* @return string The processed HTML with protected email addresses
*/
private function protectEmails($html)
{
// Quick check for @ character before expensive DOM parsing
if (strpos($html, '@') === false)
{
return $html;
}
try
{
// Use DOMDocument to parse HTML
$dom = new \DOMDocument();
$html = iconv('UTF-8', 'UTF-8', $html);
$html = mb_encode_numericentity($html, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8');
// Prevent HTML5 errors
libxml_use_internal_errors(true);
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
// Define elements to process
$elements = [
'input',
'textarea',
'option',
];
foreach ($elements as $tag)
{
$nodes = $dom->getElementsByTagName($tag);
foreach ($nodes as $node)
{
// Process all attributes dynamically
if ($node->hasAttributes())
{
foreach ($node->attributes as $attribute)
{
$value = $attribute->value;
$newValue = $this->replaceEmails($value);
if ($value !== $newValue)
{
$attribute->value = $newValue;
}
}
}
// Process text content for textarea and option elements
if (in_array($tag, ['textarea', 'option']))
{
$value = $node->nodeValue;
$newValue = $this->replaceEmails($value);
if ($value !== $newValue)
{
$node->nodeValue = $newValue;
}
}
}
}
return $dom->saveHTML();
} catch (\Throwable $th)
{
// Fallback to return original content if DOM parsing fails
return $html;
}
}
/**
* Replaces email addresses with unique hashes using regex pattern matching.
* Stores the email-hash pairs for later restoration.
*
* @param string $text The text content to process
*
* @return string The processed text with emails replaced by hashes
*/
private function replaceEmails($text)
{
$pattern = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/';
return preg_replace_callback($pattern, function($match)
{
$email = $match[0];
// if email exists, return the existing hash to avoid generating multiple hashes for the same email
if (in_array($email, self::$emailHashes))
{
return array_search($email, self::$emailHashes);
}
// Generate a unique hash for the email using URL-safe base64
// Switching from md5 to base64 somehow bypasses caching issues
$b64 = rtrim(strtr(base64_encode($email), '+/', '-_'), '=');
$hash = self::EMAIL_HASH_PREFIX . 'b64_' . $b64;
// Store the hash and email pair
self::$emailHashes[$hash] = $email;
return $hash;
}, $text);
}
/**
* Restores all protected email addresses by replacing their hashes
* with the original email addresses.
*
* @param string $html The HTML content containing hashed emails
*
* @return string The HTML content with restored email addresses
*/
private function revert($html)
{
if (empty(self::$emailHashes))
{
return $html;
}
// Replace all hashes with their corresponding emails
return str_replace(array_keys(self::$emailHashes), array_values(self::$emailHashes), $html);
}
}