#1546 Posted in ‘Route 66’

Latest post by Lefteris Kavadas on Thursday, 27 August 2026 18:24 EEST

laurent 'cosmic' henneaux
## Subject Route 66 2.4.1 — HTML entities are decoded in the final page when a performance option is on ## What happens With any of the four performance options of Route 66 enabled, HTML entities disappear from the rendered page. Two things I noticed on Joomla 6.1.3 / PHP 8.3.30 / Route 66 2.4.1: - a tag titled `audit & diagnostic` reaches the browser as `audit & diagnostic` — a bare ampersand in the markup, where Joomla had output `audit &amp; diagnostic` - a code sample published in an article, written as `&lt;b&gt;bold&lt;/b&gt;` so that readers see the tag, is rendered as actual bold text instead The second one is what put me on the trail: I write documentation articles that show HTML, and none of them display correctly. ## Steps to reproduce 1. Joomla 6.1.3, Route 66 2.4.1, any template. 2. In Route 66 options, set **lazy load images** to yes, leave the other three off. 3. Create a tag named `A & B`, assign it to an article, display tags on the article. 4. Put the literal text `&lt;b&gt;bold&lt;/b&gt;` in the article body. 5. Look at the response source on the front end: the tag shows a bare `&`, and the escaped tag has become bold text. 6. Set the four performance options to no: correct output returns. ## Where it comes from `administrator/components/com_route66/src/Helper/PerformanceHelper.php`, in `optimize()`: ```php $buffer = $dom->saveHTML(); $buffer = str_replace('<?xml encoding="UTF-8">', '', $buffer); $buffer = html_entity_decode($buffer, ENT_HTML5, 'UTF-8');   // <- here $application->setBody($buffer); ``` Called from `plugins/system/route66/src/Extension/Route66.php` in `onAfterRender()`. The decode looks intended to undo the numeric entities `DOMDocument::saveHTML()` produces for non-ASCII characters. Applied to the whole buffer, it also removes the entities that belonged to the content. Worth noting: `prepareProtectedBlocks()` already shields `<pre>`, `<code>` and `<textarea>` from the DOM round-trip — but the decode runs after those blocks are restored, so a code sample inside `<code>` loses its entities anyway. The protection is there, the final decode undoes it. ## Trigger `optimize()` returns early only when all four options are off, so enabling a single one is enough for the whole page to be decoded: ```php if (!$params->get('iframe_facades') && !$params->get('iframes_lazy_load')     && !$params->get('images_lazy_load') && !$params->get('optimize_css')) {     return; } ``` ## A possible fix Decode only what `saveHTML()` introduced — numeric entities above codepoint 127 — and leave named entities from the content alone: ```php $buffer = preg_replace_callback(     '/&#(?:(\d+)|[xX]([0-9a-fA-F]+));/',     static function (array $m): string {         $cp = $m[1] !== '' ? (int) $m[1] : (int) hexdec($m[2]);         return $cp > 127 ? mb_chr($cp, 'UTF-8') : $m[0];     },     $buffer ); ``` ## Workaround meanwhile Setting the four performance options to no is enough — routing and SEF are untouched, only the optimizer stops running.

Lefteris Kavadas

Hi,

Thank you for the detailed report and excellent analysis. You were correct: the final html_entity_decode() was decoding entities belonging to the page content after the protected blocks had been restored.

We have fixed this for the next Route 66 release. The optimizer no longer decodes the complete HTML response. Instead, <style> and <script> contents are protected during the DOM serialization and restored verbatim afterward. This preserves CSS icon glyphs—the reason decoding was originally introduced—without affecting entities such as &amp; and &lt; in page content or code samples.

We also corrected the insertion of combined CSS so ampersands and other special characters are preserved correctly.

The fix has been tested on PHP 8.2 through 8.5 with all four performance-option paths, including the reproduction cases from this ticket.

Until the update is available, disabling the four performance options remains a valid workaround. Routing and SEF functionality are unaffected.

Thanks again for providing such a clear reproduction and pointing directly to the problematic code.
 
Best regards,
Lefteris

Note: An active subscription is required in order to get support for our paid extensions. For our free extensions, if you don't have an account, register and then submit your support request.  In case you just want to ask a question, you can also use the contact form .

Firecoders
Are you using our extensions? Please post a review at the Joomla extensions directory!
Post a review