The layout system in SIA Publishing connects three layers: a hard-coded sitemap inventory (the expected page structure), a layout repository (named template definitions), and WordPress posts (the live content instances). These layers are assembled at runtime and cached in transients.
The sitemap inventory is a nested PHP array defined in SIA_Publishing_Sitemap::inventory(). It lists every page slug that should exist on a given site, organised hierarchically by navigation tier. The inventory is keyed at the top level by placement type (license_id or blog_id) and then by placement value — an organisation status constant or a specific blog ID.
The inventory is flattened at runtime into a single-level $flat array via flatten(), which annotates each node with its tier depth (0 = home, increasing with depth). Tier 3 nodes exist in the inventory for block-lookup purposes but are not included in navigation.
SIA_Publishing_Layouts_Repository::get_layouts() is a ~3,700-line PHP array containing every layout definition in the system. Each layout is keyed by its slug and carries:
title — page titlepermalink — URL segmentplacementType / placement — which org status or blog this layout belongs toversion — incremented when the layout structure changesblocks — ordered array of block slug → config pairsparent (optional) — parent layout slug, used for breadcrumb hierarchynavigationTier (optional) — menu depth; defaults to the tier derived from the sitemapexcerpt (optional) — post excerptcoreBlocks (optional) — WordPress core block types permitted in the editorbypassAllowedBlocks (optional) — disables all block restrictions (e.g. Impact Blueprint)postPassword (optional) — password-protects the pageSummit and Retreat microsite layouts are defined separately in summit_microsite_layouts() and merged in.
Each layout maps to at most one WordPress page post per site. The post is identified by the _template_layout post meta field (constant SIA_Publishing::ACF_LAYOUT_FIELD_NAME), which stores the layout slug. Two companion fields are also stored: _template_layout_set (Unix timestamp of last application) and _template_layout_version (the layout version at time of application).
Stencil (STENCIL_BLOG_ID) is the canonical source blog. For most placements, page content is cloned from Stencil when a layout is applied: the target post receives the title, URL slug, block markup, and excerpt from the corresponding Stencil post. Blog-specific layouts (those with placementType = 'blog_id') and layouts applied directly on Stencil itself instead assemble their markup from scratch by iterating the layout’s block definitions.
SIA_Publishing_Sitemap is instantiated with a $placementType and $placement. On construction:
inventory() loads the hard-coded structure for that placement.$flat and $blockInventory are restored from cache.flatten() — collapses the nested tree into $this->flat, annotating each node with its tier.associate_layouts() — instantiates a SIA_Publishing_Layout for each slug and stores it on the node.associate_posts() — calls $layout->getPostIds() for each layout, stores the post object on the node if exactly one post is found, parses post blocks into $this->blockInventory, and applies the two sitemap filters.Two WordPress filters modify the inventory during associate_posts():
sitemap/associate_posts — controls whether a node is visible at all. The default implementation in SIA_Publishing_Public::maybe_showPageInSitemap() hides certain pages (e.g. articles, jury listings, registration pages) based on the current programme phase.sitemap/highlight_posts — marks a node as highlighted in navigation. The default implementation highlights about-application-host and community-voting-landing-page-host during their active phases.Both filters receive the layout slug and the associated post, and must return a boolean or WP_Error.
SIA_Publishing_Sitemap::__bust_cache($blogId) deletes all transient variants for __construct and get_breadcrumbs. It is called automatically on wp_after_insert_post and can be triggered manually:
wp publishing bust-sitemap-cache [--site=<ID>] [--network]applyTo()SIA_Publishing_Layout::applyTo($postId, $updateField, $force, $preserveContent) is the core operation.
$force = TRUE.$preserveContent = TRUE, stops here — used to register an existing page into the repository without overwriting its content.buildPostContent() on Stencil or blog-specific layouts, clonePostContent() on all others.wp_posts row directly via $wpdb->update: sets post_content, post_excerpt, and (on first assignment) post_name and post_title.Content is generated in one of two ways:
buildPostContent() — iterates $this->blocks, wraps core blocks in Gutenberg comment syntax, calls $block->getGutenbergMarkup() for custom blocks.clonePostContent() — switches to STENCIL_BLOG_ID, retrieves the Stencil post via SIA_Publishing_Sitemap, copies post_content, post_title, and post_excerpt.SIA_Publishing_Admin::allowed_block_types() limits the block inserter for layout-managed pages:
coreBlocks: those specific WordPress core block types are also permitted.bypassAllowedBlocks = TRUE: no restrictions apply.REVIEW_PERMISSION and DEBUG_LAYOUTS set in the environment bypass all restrictions.An interactive D3.js sitemap overview is maintained at public/sitemap-overview.html. It renders all five site types (Candidate, Host, International, Retreat, Summit) as zoomable horizontal trees and exposes per-page detail in a click-to-open modal.
In addition to the core fields listed under the layout repository above, the following optional fields are read by the overview:
audience — primary intended audience for the page (e.g. alumni, partner)pageUsage — functional role of the page independent of its position in the hierarchy (e.g. info)Both fields are optional and absent from most layouts.
public/sitemap-data.json supplies live page titles and excerpts from WordPress to the overview. It is generated by:
wp publishing sitemap-prototype-data [--output=<path>]The command switches to STENCIL_BLOG_ID to collect Candidate and Host content, then to INTERNATIONAL_BLOG_ID for International. For each site type it instantiates SIA_Publishing_Sitemap, iterates $sitemap->flat, and — for every node that has an associated post — writes a keyed entry containing title, excerpt, audience, pageUsage, and version. The last three come from SIA_Publishing_Layouts_Repository::get_layouts() keyed by slug. Retreat and Summit content is not included; the overview falls back to its hardcoded values for those site types.
The JSON is loaded at runtime via fetch('sitemap-data.json') relative to the HTML file. If the file is absent or the fetch fails the overview renders from its inventory silently.
When the inventory or layout repository changes (new page added, slug renamed, hierarchy changed): update the inventory in public/sitemap-overview.html to match. The inventory only defines the tree shape — slugs, inNav flags, and parent–child relationships. Field values (title, excerpt, audience, pageUsage, version) are overwritten at runtime from the JSON and do not need to be kept in sync manually.
When Stencil or International page content changes (title or excerpt edited): run the command on production using --output to write into the gitignored _data/ folder at the repo root, keeping the production working tree clean:
wp publishing sitemap-prototype-data --output=_dataThen copy _data/sitemap-data.json to public/sitemap-data.json locally and commit from there.
To publish changes to the design system: run .bin/mirror-docs.sh from the main repo root. The script copies both sitemap-overview.html and sitemap-data.json to the submodule, rebuilds the library, and commits and pushes the submodule. Run this after any change to either file.
sitemap-overview.html and sitemap-data.json are mirrored into the design system submodule by .bin/mirror-docs.sh using mirror_asset, which copies both files verbatim to assets/src/. The build pipeline copies assets/src/ to assets/dist/, making them accessible as static assets served from the library root.
A documentation page at docs/publishing-and-play/publishing/05-sitemap-overview.md embeds the overview in an <iframe src="../../../sitemap-overview.html">. The three-level relative path resolves to the static asset root in both the Fractal dev server and the compiled library/ output.
wp publishing set-layout <slug> --page-id=<id> [--site=<id>] [--force] [--preserve-content]
wp publishing unset-layout --page-id=<id> [--site=<id>]
wp publishing create-post-with-layout <slug> [--site=<id>]
wp publishing list-layouts [--site=<id> | --license=<license>]
wp publishing validate-sitemap
wp publishing sitemap [--license_id=<id> | --blog_id=<id>]
wp publishing navigation --site=<id>
wp publishing bust-sitemap-cache [--site=<id>] [--network]
wp publishing sitemap-prototype-data [--output=<path>]