Skip to content

Header, footer and navigation

The header and footer are on every page: a mega-menu built by hand from the header menu, a search box, a phone number and CTA from Global Options, a ReviewSolicitors rating widget, the Moneypenny live-chat script, GTM and legacy Universal Analytics, then a footer of four menus, office details, accreditation logos, a newsletter capture and a timed opening-hours popup.

Anything here appears on 100% of pages, so a mistake in this feature is a site-wide mistake.

The mega menu is hand-rolled rather than a nav walker because it has to inject non-menu content: a column of latest insights, a sticky-post pod, or a custom promo card, all configured per top-level menu item. WordPress nav walkers cannot easily do that, so inc/navigation.php reads the menu items itself and renders the tree directly.

The footer carries the trust signals a regulated firm needs — accreditations, a Cyber Essentials certificate, office addresses — and those come from content, not markup.

Path What it does
wp-content/themes/bpcollins/header.php <head>, third-party scripts, Organization JSON-LD, header markup, search forms
wp-content/themes/bpcollins/inc/navigation.php The desktop mega menu / dropdown menu
wp-content/themes/bpcollins/inc/extended-nav-walker.php ExtendedNavWalker — used for the mobile menu
wp-content/themes/bpcollins/inc/simple-nav-walker.php SimpleNavWalker — used for the footer link row
wp-content/themes/bpcollins/footer.php Newsletter, footer menus, offices, logos, social, popups
wp-content/themes/bpcollins/functions.php:46-53 Registered menu locations

Menu locations registered in strategiq_setup(): primary, footer-other, footer-services-business, footer-services-individual, footer-sectors, footer.

Desktop navigation. inc/navigation.php calls wp_get_nav_menu_items('header') — note: a menu identified by the name/slug header, not by a registered theme location. It flattens every item into an array carrying its ACF fields (show_latest_insights, is_mega_menu, toggle_custom_fields, custom_image, custom_title, custom_text, custom_link, column_layout), then links children to parents by menu_item_parent.

Each top-level item renders either:

  • a mega menu (is_mega_menu == 1) — a level-02level-05 nested list, plus two extra columns: site-header__faux-column showing two sticky posts when show_latest_insights is set, and site-header__post-column showing either the custom promo card (when toggle_custom_fields is on) or the single latest sticky post as a post-pod partial; or
  • a plain dropdown (site-header__dropdown-menu) with levels 02–04.

Active state is computed by comparing home_url($wp->request) . '/' against each item’s URL, descending up to six levels via nested foreach loops (inc/navigation.php:66-116).

Mobile navigation uses wp_nav_menu() with the primary theme location and ExtendedNavWalker, depth => 5 (header.php:380).

Header extras. Logo and phone number come from the logo_dark and telephone_number global options; the CTA button from header_cta + header_cta_icon. Two identical search forms exist — desktop (#searchform) and mobile (#searchformmobile) — both plain GET forms posting s to the site root. A ReviewSolicitors widget is embedded twice — desktop rswidget_0QfPE with the transparent template, mobile rswidget_0QfPE1 with wide-simple — both via rs.loadWidget(...) against business id 13861. The Trustpilot equivalents are present but commented out.

Above-the-fold CSS is inlined into <head> with file_get_contents(get_template_directory() . '/assets/css/abovethefold.css') (header.php:8-10), and a <noscript> block neutralises the GSAP fade-in classes so content is visible without JS.

Third-party scripts in <head>: Moneypenny live chat (loaded from a storage.googleapis.com URL, with callbacks that push events into the legacy ga object), Universal Analytics analytics.js with ga('create', ...), Google Tag Manager, and preconnect hints for Google Fonts and ajax.googleapis.com. A static Organization JSON-LD block with the firm’s address, phone, email, logo and social profiles is printed directly in header.php.

Footer. Optional newsletter band (enable_newsletter, footer_text, footer_button_text) opening a modal with a CF7 form (newsletter_popup_form + heading/image/content). Four wp_nav_menu() calls for the business services, individual services, sectors and “other” locations. An offices list built from up to 10 office posts ordered by menu_order, linking via each office’s page_link. A logo row: logo_light, a repeater of footer_logos with optional links, a hardcoded Cyber Essentials certificate iframe from registry.blockmarktech.com, and an optional raw footer_iframe field. Social links from the social_links repeater, rendered as background images. A footer link row using SimpleNavWalker, a copyright notice, and a UTM-tagged credit link back to StrategiQ.

Opening-hours popup. footer.php:378+ reads enable_opening_hours_popup, opening_popup_display_type and pop_up_display and shows one of three configured popups after a delay (popup_1_time default 5s, popup_2_time 3s, popup_3_time 0s), each with its own title, content, image and CTA fields.

flowchart TD
A[header.php] --> B[inline abovethefold.css]
A --> C[Moneypenny + Universal Analytics + GTM]
A --> D[static Organization JSON-LD]
A --> E[inc/navigation.php: wp_get_nav_menu_items header]
E --> F{is_mega_menu?}
F -->|yes| G[levels 02-05 + insights column + sticky pod or promo card]
F -->|no| H[dropdown levels 02-04]
A --> I[mobile: wp_nav_menu primary + ExtendedNavWalker]
J[footer.php] --> K[newsletter modal with CF7 form]
J --> L[4 footer menus + offices from office CPT]
J --> M[logos, Cyber Essentials iframe, social links]
J --> N[timed opening-hours popup]

Global Options ACF: logo_dark, logo_light, telephone_number, header_cta, header_cta_icon, copyright_notice, footer_logos, social_links, footer_iframe, enable_newsletter, footer_text, footer_button_text, newsletter_popup_form, newsletter_popup_heading, newsletter_popup_image, newsletter_popup_content, enable_opening_hours_popup, opening_popup_display_type, pop_up_display, and the popup_1/popup_2/ popup_3 field sets including their *_time delays.

Per menu item ACF: is_mega_menu, show_latest_insights, column_layout, toggle_custom_fields, custom_image, custom_title, custom_text, custom_link.

Hardcoded in header.php / footer.php (not options): the GTM container id, the Universal Analytics property id, the Moneypenny script URL, the Facebook domain-verification token, the ReviewSolicitors business id, the Cyber Essentials certificate iframe URL, and the Organization JSON-LD values.

Cookie consent is provided by the CookieHub plugin (a required plugin), not by theme code.

  • The desktop menu is bound to a menu named header, the mobile menu to the primary location. They are not guaranteed to be the same menu. If desktop and mobile navigation disagree, check whether the header menu is also assigned to primary.
  • wp_get_nav_menu_items('header') returns false if no such menu exists, and inc/navigation.php:13 immediately foreaches it — a renamed menu is a PHP warning and an empty header, not a graceful fallback.
  • Active-state detection compares full URLs and nests six levels deep (inc/navigation.php:66-116). Any URL mismatch — missing trailing slash, www, http/https, a query string — loses the highlight. This is also why canonicals are force-trailing-slashed elsewhere.
  • The inner foreach loops all reuse $child as the loop variable (inc/navigation.php:76-106), shadowing the outer loops. It happens to work because each level re-reads from $filtered_nav, but do not add logic after a nested loop expecting $child to be intact.
  • $check_child_elements uses array_search(...) on a column of parents (inc/navigation.php:119), which returns the array key of the first match — falsy when that key is 0. A top-level item whose first child happens to be the first element of $filtered_nav loses its has-children class and its dropdown.
  • Two live analytics implementations coexist: GTM and a directly embedded Universal Analytics snippet. ga() is also the transport for every Moneypenny event callback, so removing the UA snippet silently kills chat event tracking. Universal Analytics itself no longer processes data.
  • Moneypenny and ReviewSolicitors load synchronously in <head> (the RS widget script has no async), before wp_head(). They are render-blocking third parties on every page.
  • The Cyber Essentials badge is an <iframe> with a hardcoded certificate URL (footer.php:~296) — it expires when the certification does, and nothing in the CMS controls it.
  • footer_iframe is echoed raw — it is an editor-supplied HTML field with no filtering. Only trusted editors should have access.
  • Offices in the footer link to page_link, not the office permalink, because office is a non-public post type. An office with no page_link renders an empty href.
  • The footer offices query is capped at 10 (footer.php:180).
  • The primary location is registered but only used by the mobile menu; the desktop tree ignores it.
  • Adding an item to the mega menu → do it in Appearance → Menus on the header menu, and set the ACF fields on the parent item. No code change needed for up to five levels.
  • Adding a sixth level of nesting → the render loops stop at level-05 (inc/navigation.php:165-175) even though the active-state check descends six. Both need extending.
  • Adding a third-party script → prefer GTM. If it must be in the theme, add it to header.php below wp_head() unless it genuinely must run first, and give it async/defer.
  • Changing header markup → check the mobile menu, both search forms, and the two ReviewSolicitors widget containers; they have distinct DOM ids that the vendor script targets by string.
  • Deliberately not abstracted: inc/navigation.php renders the tree with explicit nested loops rather than recursion, because each level has different markup and extra columns. A recursive rewrite would need to preserve all of that — do not attempt it as a tidy-up.
  • Verify after any change: a top-level item with a mega menu, one with a plain dropdown, one with no children, a deep page (highlighting), mobile menu open/close, and both search boxes.

None. No automated tests exist in this repository. Header and footer changes affect every page, so check at least one page of each template type.