PR-5.4-dev Pending

User tests: Successful: Unsuccessful:

avatar hans2103
hans2103
8 Sep 2026

Pull Request resolves #38047.

  • I read the Generative AI policy and my contribution is either not created with the help of AI or is compatible with the policy and GNU/GPL 2 or later.

Summary of Changes

HtmlDocument::addCustomTag() is documented as adding markup "to the head block", but its output is actually rendered by ScriptsRenderer. Templates that place <jdoc:include type="scripts" /> at the end of the body — a common performance optimisation — therefore end up with head-only markup incorrectly duplicated into the body.

In this issue that markup is the x-default hreflang <link> tag added by plg_system_languagefilter, which then fails HTML validation: a <link> element without itemprop or an allowed rel value must not appear inside <body>.

addHeadLink() could not be used instead, because HtmlDocument::$_links is keyed by href, and the x-default link normally shares its href with an already-added language-specific alternate link — so it would silently overwrite that entry.

The fix:

  • Adds a new, purely additive HtmlDocument::addHeadTag() method and $_customHead property, wired into getHeadData() / setHeadData() / mergeHeadData() / resetHeadData() for module output-caching parity with the existing _custom handling.
  • MetasRenderer renders $_customHead. MetasRenderer is always part of the <head> output, unlike scripts, which templates may relocate.
  • plg_system_languagefilter now calls addHeadTag() instead of addCustomTag() for the x-default link.

This is intentionally not a change to addCustomTag() / ScriptsRenderer / $_custom. Third-party extensions may rely on the existing (documented-as-head-but-actually-scripts-position) behaviour, so it is left completely untouched. This PR only adds a new opt-in API and switches core's own languagefilter plugin to use it — no behavioural change for any existing addCustomTag() caller.

Testing Instructions

  1. Build a multilingual site with at least 2 published content languages.
  2. In plg_system_languagefilter, enable "Remove URL Language Code" (or set xdefault).
  3. Edit your site template's index.php to move <jdoc:include type="scripts" /> to just above </body>, keeping <jdoc:include type="metas" /> and <jdoc:include type="styles" /> in <head>.
  4. Load a page on the frontend and view the page source.
  5. Locate the <link rel="alternate" hreflang="x-default"> tag.
  6. Also confirm the regular per-language <link rel="alternate" hreflang="..."> tags (added via addHeadLink()) are still present and correct.
  7. Confirm existing addCustomTag() output — e.g. an analytics <script> snippet added by another extension — still renders at the scripts jdoc position, unchanged.
  8. Validate the page at https://validator.w3.org/nu/.

Actual result BEFORE applying this Pull Request

The x-default link is rendered by ScriptsRenderer, so it follows the relocated scripts position and lands inside the body:

   <body>
       ...
       <link rel="alternate" hreflang="x-default" href="https://example.org/">
       <script src="/media/system/js/core.min.js"></script>
   </body>

The W3C validator reports:

   Error: Element link is missing required attribute property.
   Error: The element link must not appear as a descendant of the body element.

Expected result AFTER applying this Pull Request

The x-default link is rendered by MetasRenderer and stays in the head, regardless of where the template places the scripts position:

   <head>
       ...
       <link rel="alternate" hreflang="en-GB" href="https://example.org/en/">
       <link rel="alternate" hreflang="nl-NL" href="https://example.org/nl/">
       <link rel="alternate" hreflang="x-default" href="https://example.org/">
   </head>

The per-language alternates are unaffected, existing addCustomTag() output still renders at the scripts position, and the validator reports no link-in-body error.

Link to documentations

Please select:

  • Documentation link for guide.joomla.org:

  • No documentation changes for guide.joomla.org needed

  • Pull Request link for manual.joomla.org:

  • No documentation changes for manual.joomla.org needed

avatar hans2103 hans2103 - open - 8 Sep 2026
avatar hans2103 hans2103 - change - 8 Sep 2026
Status New Pending
avatar joomla-cms-bot joomla-cms-bot - change - 8 Sep 2026
Category Libraries Front End Plugins
avatar richard67
richard67 - comment - 8 Sep 2026

@hans2103 Please keep the structure of the description of this PR like in our pull request template, and acknowledge the Generative AI policy . Thanks in advance.

avatar hans2103 hans2103 - change - 8 Sep 2026
Title
Prevent invalid <link> element in <body> from languagefilter x-default tag
Prevent invalid element in from languagefilter x-default tag
Labels Added: PR-5.4-dev
avatar hans2103 hans2103 - change - 8 Sep 2026
The description was changed
avatar hans2103 hans2103 - edited - 8 Sep 2026
avatar hans2103
hans2103 - comment - 8 Sep 2026

@richard67 body text adjusted to the template

avatar hans2103 hans2103 - change - 8 Sep 2026
The description was changed
avatar hans2103 hans2103 - edited - 8 Sep 2026

Add a Comment

Login with GitHub to post a comment