Skip to content

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.

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.

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

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]
  • Global Options ACF options page: hub_navigation (the hub bar links), tagged_services (fallback service filter list), article_fallback_image (used by BPPost::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 by BPPost::event_dates().
  • The knowledge-hub and news page slugs are hardcoded in rewrites (functions.php:371-372), pagination formats, the canonical filter and breadcrumb overrides (inc/template-functions.php:249-295).
  • The experts-section marker protocol is broken. block-hub-search.js:137-144 splits the AJAX response on <!--EXPERTS_SECTION_START--> / <!--EXPERTS_SECTION_END-->, but hub_filter() does not emit those comments — it echoes <div id="experts-section-container"> directly. So parts[1] is always undefined, expertsContent stays empty, #experts-section is 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.
  • $output is echoed even when unset. If no filter is active the handler echoes RESET and then echo $output on an undefined variable (functions.php:960-963).
  • Filtering is meta LIKE, not taxonomy. Services and authors match against serialised ACF relationship values with compare => LIKE, so an ID like 12 can match 120 in principle. Contrast with the people directory, which was deliberately moved to taxonomy queries.
  • hub_filter() depends on the referer. Without HTTP_REFERER the parent-service deep link silently stops applying.
  • Page sizes differ by path: 4 per page filtered (functions.php:680), 6 unfiltered (block-hub-grid.php:8), and block-hub-search.php computes an unused 12-per-page query (block-hub-search.php:28-34). Changing “posts per page” means finding all of them.
  • hub-grid on the News page queries post_type => news (block-hub-grid.php:20-22) — a post type that is not registered (its registration is commented out at inc/cpt.php:3-39). Templates single-news.php and breadcrumb handling for news also still exist. Treat the News path as legacy and verify against production before changing it.
  • Insights are post, only relabelled. Anything reading get_post_type() sees post; 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).
  • New filter dimension → add the control in block-hub-search.php with class js-hub-filters, extend updateURL() and the load-time param restore in block-hub-search.js, then add the $_POST branch in hub_filter(). Also add it to the “any filter set?” condition at functions.php:811, or the handler will return RESET and appear to do nothing.
  • Changing result card markup → edit partials/post-card.php for the server-rendered grid and the string-built e-card--split markup inside hub_filter() (functions.php:875-929).
  • Changing the block order on category/news pages → edit category.php / page-news.php directly, 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 loads assets/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.