Download the PHP package hirasso/html-obfuscator without Composer
On this page you can find all versions of the php package hirasso/html-obfuscator. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download hirasso/html-obfuscator
More information about hirasso/html-obfuscator
Files in hirasso/html-obfuscator
Package html-obfuscator
Short Description Obfuscate emails, phone numbers, and other sensitive data in PHP. Invisible to humans, hidden from crawlers and headless bots until they interact.
License MIT
Informations about the package html-obfuscator
hirasso/html-obfuscator
Obfuscate emails, phone numbers and other sensitive data using PHP and modern web features. Visible to humans, hidden from crawlers and headless bots until they interact.
→ html-obfuscator.rassohilber.com
Contrary to popular belief, this article by Spencer Mortensen shows that even moderate obfuscation dramatically reduces email harvesting by spam bots. Most bots simply scan raw HTML and don't simulate user interaction.
Installation
Minimal Example
Obfuscate emails and phone numbers in $html and automatically inject the client script that reveals <ob-fus-ca-ted> custom elements in the frontend:
Manually load the client script
By default, the client <script> is auto-injected into the document. If you want more control (e.g. want the script in the <head>), use clientScript() and echo it yourself:
Features
- Framework agnostic — works in any PHP project, independent of framework or frontend toolchain
- There is no visual difference between obfuscated and de-obfuscated content in the browser
- Works seamlessly with dynamically loaded content (AJAX/fetch, swup, htmx, Unpoly, ...)
- Works without configuration, but can be customized using a fluent API
- Fully compatible with HTML5 (thanks to PHP 8.4's new
\Dom\HTMLDocumentand friends) - Doesn't interfere with accessibility
- Extensively tested from all ends – PHP, JavaScript, e2e, basic benchmarks
How it works
On the server, PHP searches emails and phone numbers in the HTML (plain text or href attributes) using regex, obfuscates them using a randomly selected strategy, removes the original and injects a custom element in its place.
Text nodes
Matching parts get replaced with an obfuscated element and instructions how to reveal it:
Links with matching href and/or title attribute
Everything but the scheme is stripped from the href attribute. The link gets a hidden obfuscated element with [attr="href"] injected as a child. The same applies to title attributes:
[!NOTE] The scheme in obfuscated
hrefattributes is preserved to prevent a FOUC if links are styled usinga[href^="mailto:"]ora[href^="tel:"]
What can JS-disabled crawlers see?
Instead of the original values, there are now obfuscated custom elements. One for each obfuscated href attribute, one for each plaintext value.
What can JS-enabled crawlers see?
Not much more, before interaction (pointermove, pointerdown, or keydown) was detected.
Custom elements representing a text node do decode the value immediately on connectedCallback, but render it into a closed shadow root that is completely visible to humans but cannot be accessed from JavaScript.
href attributes of obfuscated links also stay empty until interaction.
Fluent API
->emails(bool)
Keep emails unobfuscated
->phoneNumbers(bool)
Keep phone numbers unobfuscated
->debug(bool)
Inject the client script unminified and with logging
->setStrategy(string)
Pin the obfuscation algorithm instead of picking one at random each time:
An \InvalidArgumentException is thrown for unknown strategy classes.
->withAriaLabel(?string)
Customize or disable the aria-label on each obfuscated element. Pass null to omit it entirely:
->withNoscriptText(?string)
Customize or disable the <noscript> fallback inside each obfuscated element. Pass null to omit it:
->withTagName(string)
Customize the tag name of the custom element
->addRegex(string)
Add custom patterns to obfuscate text that the built-in patterns can't reach. A common case is an email address split across HTML elements to allow for a line break — the built-in email regex matches a single text node, so <span>verylongemailaddress@</span>example.com would slip through. You can target this specifically:
The pattern must be a valid PCRE regex with delimiters. Each call to ->addRegex() appends one pattern; you can chain as many as you need. An \InvalidArgumentException is thrown for invalid patterns.
Advanced
[obfuscate-text]
Add an obfuscate-text attribute to any element to obfuscate all of its text content — no pattern matching needed. This is a simpler alternative to ->addRegex() when you control the markup:
Every text node inside is obfuscated wholesale, and the attribute is removed from the output:
The <ob-fus-ca-ted> elements handle deobfuscation as usual. Content inside <pre>, <code>, <script>, and other excluded elements is left untouched even when nested inside an [obfuscate-text] element.
Obfuscating a HTMLDocument
When passing a \Dom\HTMLDocument, the obfuscation is applied directly to the document:
Credits
Inspiration
Before writing this, I found a few existing solutions worth mentioning.
muddle is a PHP package with many interesting obfuscation strategies. But it relies on inline <script> tags for deobfuscation, which browsers won't execute when content is injected into the DOM dynamically (AJAX, fetch, ...). It also doesn't require interaction, so browser-based crawlers will be able to see the content immediately.
astro-obfuscate and astro-mail-obfuscation are both Astro integrations that handle obfuscation well within that ecosystem. But they're tied to the Astro build pipeline and don't translate outside of it. I adopted the idea with the fallbackText from the latter, though.
I needed something that works in any PHP project, independent of framework or frontend toolchain, with no visible flash during deobfuscation. Also, I this package gave me the excuse to play around with the tooling involved with building a robust FOSS package:
Tools used
PHPStan for static analysis. PHPUnit/Pest for feature and unit tests. Vitest for unit tests of the client script. Playwright for end-to-end tests. Changesets for changelog gneration. FTP-Deploy-Action for automatic deployment of the demo site on every release.
And last, but not least, Claude Code in combination with mattpocock/skills for grilling sessions and code quality improvements. Not sure if that actually saved or cost me development time 😅
→ Browse obfuscatorTest.php to see more usage examples.