Skip to content

Site chrome and navigation

The header, footer, mega menu, mobile menu, country switcher and the logged-in/logged-out state in the header. All of it is shared across the whole multisite network and varies by blog id.

One theme serves six or seven country sites with the same design but different menus, LMS links and localisation. The nav is a three-level mega menu that WordPress’s default walker cannot render, so the theme carries both a bespoke renderer and two custom walkers.

The logged-in state is injected client-side because the pages are cached by WP Engine — the server-rendered HTML must be identical for guests and members.

PathWhat it does
wp-content/themes/supplychainschool/header.phpHeader markup, logo, nav, search, country selector, LMS buttons
wp-content/themes/supplychainschool/footer.phpFooter, four menu locations
wp-content/themes/supplychainschool/functions.php:471supply_chain_mega_menu() — the bespoke three-level renderer
wp-content/themes/supplychainschool/inc/classes/MegaMenuWalker.phpFallback walker for other blogs
wp-content/themes/supplychainschool/inc/classes/MobileMenuWalker.phpMobile menu walker
wp-content/themes/supplychainschool/inc/simple-nav-walker.phpA plain walker for footer menus
wp-content/themes/supplychainschool/functions.php:48Registers four menu locations
wp-content/themes/supplychainschool/inc/geolocation-functions.phpCountry suggestion popup
wp-content/themes/supplychainschool/inc/sitemap-shortcode.php[simple-sitemap]

primary-menu, footer, footer-notices, footer-topics (functions.php:48). Note the primary location is registered as primary-menu but header.php:227 requests 'theme_location' => 'primary' on the fallback path — the two do not match, so the fallback renders WordPress’s default “no menu assigned” behaviour rather than the primary menu. Only the mega-menu path (which passes 'primary-menu') works as intended.

header.php:218 branches on blog id:

  • Blogs 1, 2, 6, 7 (UK, Australia, Carbon, USA) → supply_chain_mega_menu('primary-menu').
  • Everything else (France, SBCC) → wp_nav_menu() with MegaMenuWalker.

supply_chain_mega_menu() (functions.php:471) fetches all menu items with wp_get_nav_menu_items(), then for each top-level item calls get_menu_item_children() (functions.php:603) — which re-fetches the whole menu each time. For a menu with N top-level items and M children this is O(N×M) calls to wp_get_nav_menu_items(). It is cached by WordPress’s object cache within a request, but the loop is still doing a lot of array walking.

Three shapes come out of it:

ConditionMarkup
Children that themselves have childrenli.has-mega-menu + .mega-menu with a two-column nested nav
Children with no grandchildrenli.has-standard-menu + .standard-menu
No childrenplain li

Each dropdown gets a “View all X” link whose text comes from view_all_link_text() (functions.php:585) — “Contact us” for Contact, “View all Learning resources” for Learn, otherwise “View all {title}”. For the Learn item the URL is rewritten to LMS_URL . get_lms_url('resources'), i.e. it points at Moodle rather than WordPress (functions.php:524 and :560).

header.php renders two empty containers, .header-logged-in and .mobile-logged-in, plus static Log In / Sign Up links. The theme JS fills them in, using the strings localised at functions.php:160: Dashboard, Logout, Welcome, along with ajax_url and theme_uri under the scss global.

This is why the header does not break under page caching: the guest markup is what gets cached and the member state is a client-side swap.

Two independent mechanisms:

  1. The header selector (header.php:258 and again for mobile at :330) — a hard-coded list of UK, IE, US, AU with absolute production URLs. Australia and the SBCC/Climate Action Hub sites are not both represented.
  2. The geolocation popup (inc/geolocation-functions.php) — reads Cloudflare’s HTTP_CF_IPCOUNTRY header, defaults to GB, and enqueues country-popup.js with a suggested URL.

CMS_LOG_IN_PATH (/log-in, or /se-connecter on the French site) and TOPICS_SLUG (topics / sujets) come from the per-blog constants in LMSClient.php — see [[moodle-sso-and-sessions]]. get_topic_url() (functions.php:451) walks a term’s parents to build a nested topic URL under TOPICS_SLUG.

Blog 3 (France) also gets a different stylesheet: navigation-legacy.css instead of navigation.css (functions.php:139).

  • X-UA-Compatible: IE=edge sent for IE user agents (functions.php:305).
  • wp_generator and wlwmanifest_link removed from wp_head (functions.php:319).
  • Google Tag Manager in header.php (conditional).
  • [simple-sitemap] shortcode lists all public post types except attachment, product, tipsites, ordering page then post first (inc/sitemap-shortcode.php:9).
  • Menus are assigned in Appearance → Menus, per subsite.
  • The logo is an ACF option field logo on the Company Info options page, falling back to assets/images/logo.svg (header.php:193).
  • Typekit fonts load from //use.typekit.net/vgd7cqv.css (functions.php:136).
  • No environment variables.
  • The primary vs primary-menu mismatch at header.php:227 means the non-mega-menu blogs (France, SBCC) do not render the registered primary menu location. If a French menu “won’t show”, start there.
  • Adding a country site means editing three hard-coded lists: the blog-id branch at header.php:218, the desktop country selector at :258, the mobile one at :330, and the $country_sites map in inc/geolocation-functions.php:14. Nothing derives these from get_sites().
  • The geolocation popup dereferences $country_sites[$visitor_country] without checking the key exists (inc/geolocation-functions.php:31), so a visitor from an unlisted country produces a PHP warning. The staging branch of that map only has GB and AU, and maps AU to the Ireland staging URL.
  • $current_country in the same file is initialised to '' and never set — the comment says “Map blog ID to country code” and the whole same-country check is commented out below (:33). The popup therefore has no idea which site you are already on.
  • get_menu_item_children() re-fetches the entire menu per call. Do not add another nested level without restructuring; the cost compounds.
  • Menu output is unescaped — echo $menu_item->url and echo $title throughout supply_chain_mega_menu(). Menu content is admin-entered, but it is worth knowing before pulling any of it from an external source.
  • Header markup is duplicated between desktop and mobile (search form, country selector, LMS buttons). Change one, check the other.
  • Nav structure changes go in supply_chain_mega_menu() for blogs 1/2/6/7 and in MegaMenuWalker for the rest. Both must be updated, or the smaller sites diverge.
  • New “View all” wording: view_all_link_text() only. There is a duplicate switch inline at functions.php:554 that predates the helper and computes $view_all_text into a variable which is then never used — the helper’s return value is what renders. Delete the dead switch rather than editing it.
  • Header/footer CSS lives in resources/assets/sass/; header.scss and sidebar.scss in the Carbon theme are separate. Run npm run prod and commit assets/.
  • Verify by hand on at least two blogs — one mega-menu blog and one fallback blog — logged out and logged in, at desktop and mobile widths. The logged-in swap is JS, so check with caching enabled.
  • Deliberately not abstracted: the two renderers. The mega menu needs three levels with a specific two-column DOM that a Walker_Nav_Menu cannot express cleanly; the walker path is the simpler legacy fallback.

None.

Related: [[moodle-sso-and-sessions]], [[acf-flexible-content-blocks]], [[site-search]], [[forms-spam-and-marketing-tracking]].