File structure (as of 1.1.4)

The former two god classes (LH_BP_Group_Taxonomies_Admin, LH_BP_Group_Taxonomies_Frontend) were split into eight focused, single-concern files. This wasn’t a mechanical file-per-method split — each cluster was kept together specifically where its methods share request-scoped instance state, and split apart everywhere else:

  • class-admin-bulk-tags.php (LH_BP_Group_Taxonomies_Admin_Bulk_Tags) — the edit-tags.php-emulation cluster (submenu registration, fake post-type registration, the whole add/edit/delete-tag action dispatcher). Kept together: these all read/write $admin_screens/$admin_pages instance state.
  • class-admin-list-tables.php (LH_BP_Group_Taxonomies_Admin_List_Tables) — Groups admin list table integration: taxonomy filter dropdowns, columns, term edit/delete link targets. No shared state with anything else.
  • class-admin-term-columns.php (LH_BP_Group_Taxonomies_Admin_Term_Columns) — term list table column customization (Posts count → Groups count). No shared state.
  • class-admin-group-metabox.php (LH_BP_Group_Taxonomies_Admin_Group_Metabox) — the single-group admin edit screen’s tag metabox. Delegates rendering/saving to LH_BP_Group_Taxonomies_Tag_Editor.
  • class-frontend.php (LH_BP_Group_Taxonomies_Frontend, slimmed) — directory SQL filtering (parse_select()/parse_total()/parse_groups_query()), BP theme-compat handling, and single-tag display. Kept together deliberately: one hook resolves “which tag is this directory view for” into $this->term/$this->taxonomy/$this->tax_query, and several later, unrelated-looking hook callbacks (theme-compat, count filters, tag-info display) depend on that having already run. Splitting this further would need a shared state object between classes rather than a mechanical extraction, so it stayed as one class.
  • class-frontend-tag-editor.php (LH_BP_Group_Taxonomies_Tag_Editor) — renders and saves the tag-selection checkboxes on group creation/edit. All-static, no shared state — called from both the front-end flow and Admin_Group_Metabox.
  • class-frontend-group-lifecycle.php (LH_BP_Group_Taxonomies_Group_Lifecycle) — visibility-change term-count fixup, cleanup on group deletion, single-group template hierarchy entry. No shared state with anything else.
  • class-taxonomy.php (LH_BP_Group_Taxonomies_Taxonomy) — unchanged, the data layer described below.

Groups directory filtering

Implemented via parse_select() / parse_total() in LH_BP_Group_Taxonomies_Frontend, hooked on BuddyPress’s own bp_groups_get_paged_groups_sql / bp_groups_get_total_groups_sql filters. parse_groups_query() (hooked on bp_before_has_groups_parse_args) collects every matching taxonomy present — either a directory URL segment or a $_GET param — into a WP_Tax_Query-shaped multi-clause array (AND relation across taxonomies). parse_select()/parse_total() then build a WP_Tax_Query from that array and inject its JOIN/WHERE SQL directly into BuddyPress’s group query (WP_Tax_Query::get_sql( 'g', 'id' )) — the same technique BuddyPress’s own native group_type filtering uses internally. Because $wpdb‘s term-table properties resolve to whichever site is current, this join correctly scopes to the current site’s own term tables even on a network-activated install where BuddyPress’s bp_groups table is centralised on the main site.

The first taxonomy/term matched is also kept on $this->taxonomy / $this->term for single-tag display contexts that only show one tag name — when multiple taxonomies are active at once, only the first is named in that display copy, though all are applied to the actual filtering.

An earlier PHP-side implementation (restrict_by_taxonomy(), using get_objects_in_term() + array_intersect() against $_GET) was removed in 1.0.1 in favour of the SQL-join approach above. Verified live on Princes Park Touch (site ID 2) that it correctly resolves to the site’s own term tables (e.g. lhero_2_term_relationships), not shared/network tables.

Reusable tax_query API

LH_BP_Group_Taxonomies_Taxonomy::get_group_ids_by_tax_query( array $tax_query ) returns matching group IDs for an arbitrary WP_Tax_Query-shaped argument — a single flat clause, or multiple clauses combined with a top-level relation key, exactly as WordPress core’s native tax_query argument works elsewhere (e.g. WP_Query). It runs standalone against BuddyPress’s own groups table via $wpdb, so it’s callable from any PHP code, not just the groups-directory-page context parse_select()/parse_total() are tied to.

// Multiple clauses, AND
$group_ids = LH_BP_Group_Taxonomies_Taxonomy::get_group_ids_by_tax_query( array(
    'relation' => 'AND',
    array( 'taxonomy' => 'lh_group_tags', 'terms' => 12, 'field' => 'term_id' ),
    array( 'taxonomy' => 'lh_comps_team_season', 'terms' => 'season-1', 'field' => 'slug' ),
) );

As of 1.1.0, lh-competitions‘s return_common_group_ids_by_term_id() and return_common_group_ids_by_term_slug() use this method internally instead of independently calling get_objects_in_term().

Forward-compatibility note: this array shape deliberately mirrors a generic tax_query argument BuddyPress core is adding to BP_Groups_Group::get() (landed on trunk June 2026, not yet in a stable release — confirmed absent from the BuddyPress 14.4.0 build installed on this network as of the 1.1.0 changelog entry). Once this network upgrades to a BuddyPress release that includes it, callers can switch to groups_get_groups( array( 'tax_query' => $tax_query ) ) directly, and this method (along with parse_select()/parse_total()) can likely be retired in favour of BuddyPress’s native support.

MCP abilities load order

The abilities file loads on plugins_loaded (priority 20), independent of the bp_loaded/bp_init-based bootstrap used for the BuddyPress-facing functionality (LH_BP_Group_Taxonomies_Taxonomy starts on bp_init, priority 11). Guarded on function_exists( 'wp_register_ability' ) as defense-in-depth, though the plugin’s declared minimum (WordPress 6.9+, see Requirements below) already guarantees the Abilities API is present.

Requirements

  • WordPress 6.9+ (declared via the plugin’s Requires at least header — the bundled Abilities API is what actually needs 6.9, not the BuddyPress-facing functionality itself, which would run fine on 6.0)
  • PHP 8.1+
  • BuddyPress 12.0+ with the Groups component active