Knowledge Hub
The Knowledge Hub is the firm’s content marketing archive at /knowledge-hub/:
articles (“Insights”) filterable by service, author and month, plus category
landing pages and a “Related Experts” strip that surfaces solicitors matching the
chosen service.
Why it exists
Section titled “Why it exists”Insights are the firm’s main organic-search entry point. The filtering exists so
a visitor arriving from a service page can narrow to that practice area, and so
a solicitor’s own articles can be linked from their profile. The service filter
accepts a parent-service query parameter precisely so service pages can deep
link into a pre-filtered hub (functions.php:711-722).
WordPress core post is reused as the Insight type rather than a custom post
type. Everything in wp-admin is relabelled to say “Knowledge Hub” /
“Insight” (functions.php:317-339) — the underlying type is still post.
Entry points
Section titled “Entry points”| Path | What it does |
|---|---|
wp-content/themes/bpcollins/functions.php:670 |
hub_filter() — the hubFilter AJAX handler; returns filtered results, pagination and the experts block |
wp-content/themes/bpcollins/blocks/hub-search/block-hub-search.php |
The filter UI (services / authors / dates), category radios, and the server-rendered experts strip |
wp-content/themes/bpcollins/blocks/hub-search/block-hub-search.js |
Reads query params, posts the filter form, swaps results in |
wp-content/themes/bpcollins/blocks/hub-grid/block-hub-grid.php |
The unfiltered “Latest Insights” grid, 6 per page |
wp-content/themes/bpcollins/blocks/hub-bar/block-hub-bar.php |
The hub sub-navigation bar, driven by a Global Options field |
wp-content/themes/bpcollins/category.php |
Category landing pages |
wp-content/themes/bpcollins/page-news.php |
The News page template — same block stack as a category page |
wp-content/themes/bpcollins/single.php |
A single Insight |
How it works
Section titled “How it works”Page composition. The hub landing page is a normal WordPress page whose
content is ACF blocks. Category and News pages don’t use the editor at all —
category.php and page-news.php require the block templates directly in a
fixed order (hub-bar → hero → hub-search → featured-article → hub-grid → cta-bar
→ form-block → logo-carousel), enqueuing each block’s built CSS/JS by hand at the
top of the file.
On a category page, category.php:24 checks an ACF field category_content on
the term. If it is set, the hub-search/featured/grid trio is replaced by a single
content-block — that is how an editor turns a category into an editorial page
instead of a listing.
Filter options. block-hub-search.php queries up to 1000 published posts to
collect their specialist_services and author ACF values plus their F Y
dates, and builds the three multi-select dropdowns from the distinct values
(block-hub-search.php:43-127). Services can come from the collected values
(service_filter_source = automatic, restricted to top-level service posts) or
from a manual list (block-hub-search.php:67-106). On a category page the source
is forced to automatic. The author dropdown is rebuilt separately from all
team posts excluding the operations position (block-hub-search.php:129-143).
Filtering. Changing any .js-hub-filters control calls updateURL() (which
pushes services / authors / dates params into the address bar without a
reload) then posts the form to admin-ajax.php with action=hubFilter
(block-hub-search.js:203-244). On load, the JS reads those same params back and
re-applies them, including the legacy parent-service and by-author single
params (block-hub-search.js:20-53).
hub_filter() builds a WP_Query over post with 4 per page. Services and
authors become meta_query LIKE comparisons against the serialised ACF
relationship values; dates become a date_query with year+month
(functions.php:684-797). Service filtering also walks two levels of child
services and adds each descendant ID as another LIKE clause
(functions.php:738-771), so filtering by “Property” catches articles tagged to
its children.
hub_filter() also reads $_SERVER['HTTP_REFERER'] to recover a
parent-service param when the form itself carries no service selection
(functions.php:705-722) — the AJAX POST has no query string of its own.
When no filter is active the handler echoes the literal string RESET; the JS
treats that as “show the unfiltered blocks again” and re-displays the featured
article, grid-list and hub-grid blocks it had hidden
(block-hub-search.js:175-188).
Related Experts. Both the server render and hub_filter() query up to 6
team posts whose services taxonomy terms match the selected service posts
(matched by post slug → term slug), and render a compact list with a “Meet the
Team” button when exactly one service is selected
(block-hub-search.php:326-427, functions.php:967-1057).
Pagination. Filtered pagination is emitted by hub_filter() with the
hardcoded format /knowledge-hub/page/%#% (functions.php:941), matched by the
rewrite at functions.php:371. Unfiltered pagination in block-hub-grid.php
uses ?paged=%#% plus hand-built prev/next links. Yoast canonical and title are
patched for paginated hub pages (functions.php:1163-1183) so page 2 gets its
own canonical and a “ - Page N“ title suffix.
A single Insight (single.php) shows content, related services and sectors
from the ACF fields, and an author card built from the author relationship
field resolved through BPTeam. BPPost (inc/classes/BPPost.php) is the
accessor for all of it, including article_tags(), which chooses the tag to show
based on Yoast’s primary category, the current service/sector context, or the
parent-service param — including reading it out of the referer for AJAX
requests (BPPost.php:155-172).
flowchart TD A[hub page: hub-search + featured-article + hub-grid] --> B{filter changed?} B -->|no| C[unfiltered blocks visible] B -->|yes| D[updateURL pushes params] D --> E[POST admin-ajax hubFilter] E --> F{any filter set?} F -->|no| G[echo RESET] --> C F -->|yes| H[WP_Query: meta LIKE services/authors + date_query] H --> I[results + /knowledge-hub/page/N pagination] H --> J[Related Experts from team services taxonomy] I --> K[JS fills #filtered-results, hides unfiltered blocks]Configuration
Section titled “Configuration”- Global Options ACF options page:
hub_navigation(the hub bar links),tagged_services(fallback service filter list),article_fallback_image(used byBPPost::featured_image()). - Per-block ACF:
service_filter_source,service_filter_options. - Per-category ACF:
category_content— when set, the category page renders a content block instead of the listing. - Per-post ACF:
specialist_services,sectors,author,people, plus the event date fields used byBPPost::event_dates(). - The
knowledge-hubandnewspage slugs are hardcoded in rewrites (functions.php:371-372), pagination formats, the canonical filter and breadcrumb overrides (inc/template-functions.php:249-295).
Invariants and gotchas
Section titled “Invariants and gotchas”- The experts-section marker protocol is broken.
block-hub-search.js:137-144splits the AJAX response on<!--EXPERTS_SECTION_START-->/<!--EXPERTS_SECTION_END-->, buthub_filter()does not emit those comments — it echoes<div id="experts-section-container">directly. Soparts[1]is always undefined,expertsContentstays empty,#experts-sectionis hidden, and the experts markup ends up inside#filtered-results. If you are asked “why don’t the related experts update when I filter”, this is why. hub_filter()contains a duplicated experts block, the second copy almost entirely commented out (functions.php:1061-1155). Both copies emit an empty<div id="experts-section-container"></div>in the no-services branch, so the response can contain that element twice.$outputis echoed even when unset. If no filter is active the handler echoesRESETand thenecho $outputon an undefined variable (functions.php:960-963).- Filtering is meta
LIKE, not taxonomy. Services and authors match against serialised ACF relationship values withcompare => LIKE, so an ID like12can match120in principle. Contrast with the people directory, which was deliberately moved to taxonomy queries. hub_filter()depends on the referer. WithoutHTTP_REFERERtheparent-servicedeep link silently stops applying.- Page sizes differ by path: 4 per page filtered (
functions.php:680), 6 unfiltered (block-hub-grid.php:8), andblock-hub-search.phpcomputes an unused 12-per-page query (block-hub-search.php:28-34). Changing “posts per page” means finding all of them. hub-gridon the News page queriespost_type => news(block-hub-grid.php:20-22) — a post type that is not registered (its registration is commented out atinc/cpt.php:3-39). Templatessingle-news.phpand breadcrumb handling fornewsalso still exist. Treat the News path as legacy and verify against production before changing it.- Insights are
post, only relabelled. Anything readingget_post_type()seespost; the label change is admin-only (functions.php:317-339). - Category queries are forced to 6 per page globally by
add_blog_post_to_query()(functions.php:275).
Changing it safely
Section titled “Changing it safely”- New filter dimension → add the control in
block-hub-search.phpwith classjs-hub-filters, extendupdateURL()and the load-time param restore inblock-hub-search.js, then add the$_POSTbranch inhub_filter(). Also add it to the “any filter set?” condition atfunctions.php:811, or the handler will returnRESETand appear to do nothing. - Changing result card markup → edit
partials/post-card.phpfor the server-rendered grid and the string-builte-card--splitmarkup insidehub_filter()(functions.php:875-929). - Changing the block order on category/news pages → edit
category.php/page-news.phpdirectly, and add the block slug to the enqueue array at the top of the same file or its CSS/JS will not load. - Fixing the experts marker bug → either emit the two HTML comments around the
experts container in
hub_filter(), or simplify the JS to stop splitting. Prefer the JS side; the markers exist nowhere else. - Rebuild assets from
wp-content/themes/bpcollins(npx gulp watch) — the page loadsassets/js/hub-search/block-hub-search.min.js. - Deliberately not abstracted: category and news pages hardcode their block stack rather than using the editor, so an editor cannot reorder them. That is intentional — those pages must stay consistent.
- Manual verification: load
/knowledge-hub/, pick a service, confirm the URL gains?services=<id>and results narrow; reload to confirm the filter re-applies from the URL; page to 2; clear all filters and confirm the unfiltered blocks return; then visit a service page’s hub link with?parent-service=<id>.
None. No test runner or test directory exists in this repository, and
bitbucket-pipelines.yml only deploys. Verification is manual.