For the complete documentation index, see llms.txt. This page is also available as Markdown.

Header & footer partials with working menu areas

This page documents a theme pattern used by Aragorn that is not an official e107 standard: page header and footer live in plain HTML partial files (headers/*.html, footers/*.html) loaded through theme shortcodes, and — the important part — {MENU} / {MENUAREA} shortcodes inside those partials are fully functional: they render on the front-end and their areas appear in the Menu Manager, including the Add dropdown.

That last sentence resolves a problem first reported in 2019 (e107inc/e107#3987, later discussion #5226): menu areas emitted from theme shortcodes were invisible to the Menu Manager and unparsed on the front-end. Both issues were closed in 2026 after this pattern proved they no longer occur with the HTML layout system.

What core provides vs. what the theme adds

Core (v2.2.2+ HTML layout system): theme.html may contain the markers {---HEADER---} and {---FOOTER---}. During e_theme::loadLayout() core replaces each marker with the parsed output of a theme shortcode of the same name:

// e107_handlers/theme_handler.php (loadLayout)
$LAYOUT['_header_'] = str_replace('{---HEADER---}', $tp->parseTemplate('{HEADER}'), $LAYOUT['_header_']);
$LAYOUT['_footer_'] = str_replace('{---FOOTER---}', $tp->parseTemplate('{FOOTER}'), $LAYOUT['_footer_']);

Core does not define what {HEADER} / {FOOTER} return — that is the theme's job. If the shortcodes don't exist, the markers simply produce nothing.

The theme adds the convention: the shortcodes load partial files from the theme directory, with a prepared switch for per-layout variants.

Why this makes menu areas work

The replacement above happens inside loadLayout() — i.e. before the layout string is parsed and before the Menu Manager scans it. The partial content therefore becomes an ordinary part of the layout, and every consumer sees it:

  1. Front-endrenderLayout() parses the merged string; {MENUAREA=101} inside the partial renders through the core single shortcode like anywhere else.

  2. Menu Manager preview — the preview iframe scans the merged $HEADER.$FOOTER string; the area shows up as a drop zone.

  3. Menu Manager "Add" dropdownmenumanager_class::getLayouts() builds per-layout templates as _header_ + layout + _footer_ and counts areas with a regex accepting {MENU|MENUAREA=NNN} (1–3 digit id); the area appears as an Area NNN button.

Under the legacy system the footer emitted by a shortcode existed only at render time: it was never part of the scanned strings (problems 2 and 3) and its output was never re-parsed (problem 1). That was the whole 2019 bug.

Implementation

1. theme.html

2. Theme shortcodes

sc_header() is identical with headers/header_<variant>.html. The switch is the prepared hook for per-layout variants; until they exist, every layout loads the default partial.

3. Partial files

Plain HTML with shortcodes — {MENUAREA=101}, {NAVIGATION=footer}, {XURL_ICONS}, {SITELOGO: h=70}, ... No PHP, no escaping, directly diffable.

The rules (each one paid for in debugging time)

Path resolution — always __DIR__

The partial path must be built with __DIR__, never with the THEME constant or the sitetheme preference:

  • THEME points to the admin theme inside the admin area — and the Menu Manager builds its Add list in admin context. With THEME the partial is not found there, {FOOTER} returns an empty string, and every menu area inside it silently vanishes from the Add dropdown (the exact symptom of the historic bug, resurrected).

  • e107::getPref('sitetheme') ignores a user-selected theme (USERTHEME).

  • USERTHEME itself is forced off while the Menu Manager is active.

__DIR__ resolves to the directory of theme_shortcodes.php itself — whichever theme's shortcode batch is executing, its own partials sit next to it. Correct in every context, immune to future core changes.

THEME_LAYOUT — read at call time

Read the constant with defset('THEME_LAYOUT') inside the shortcode method. The shortcode batch is a singleton created on the first getScBatch('theme') call anywhere; caching the constant in the constructor works only sometimes. Details in Theme layouts.

One shortcode per line

The layout renderer parses line by line. A shortcode wrapped across lines is not parsed. Keep {MENUAREA=101} and friends each on a single line in the partials.

No HTML comments in partials

The Menu Manager preview prints <!-- ... --> content as visible text. Keep partials comment-free; document the file in the loading shortcode's docblock instead.

The Menu Manager regex accepts 1–3 digit ids ({MENUAREA=1}{MENUAREA=999}). Pick a dedicated range for partial areas (Aragorn uses 101+) so they don't collide with layout-body areas.

Verification checklist

After adding a menu area to a partial:

  1. Front-end: the area's assigned menus render in the header/footer.

  2. Admin → Menu Manager: the area shows as a drop zone in the preview.

  3. Menu Manager → select an inactive menu → Add: the Area NNN button is listed for every layout.

If (2) works but (3) doesn't, the {HEADER}/{FOOTER} shortcode returned nothing in admin context — in practice always a path built from the wrong constant. See "Path resolution" above.

Last updated