Declaring a shadow taxonomy
A taxonomy becomes a shadow taxonomy when its registration arguments include shadow, a string or array naming what it shadows: user, bp_group or a post type name. register_taxonomy() keeps unknown arguments on the taxonomy object, which is where the plugin reads it.
register_taxonomy( 'group_tags', array( 'post', 'attachment' ), array(
'label' => 'Group Tags',
'shadow' => 'bp_group',
) );
The map of shadowed type to taxonomies is built from get_taxonomies() and cached in a static variable, but only once init has run, so taxonomies registered late are not missed. Shadowable types are limited by lh_shadow_taxonomies_return_object_whitelist (default user, bp_group, post); in the lookup helpers an unknown type falls back to post. In practice each shadow taxonomy should shadow one type: the admin column handler is chosen by the first match of bp_group, then user, then a post type.
Linking a term to its object
Each shadow term carries term meta _lh_shadow_taxonomies-{type}-object_id holding the ID of the object it stands for, e.g. _lh_shadow_taxonomies-bp_group-object_id. The public static helpers LH_Shadow_taxonomies_plugin::get_shadowed_term( $object_id, $taxonomy, $type ) and get_shadowed_object_id( $term_id, $type ) look the link up in either direction, returning false when there is none. Groups also get group meta _lh_shadow_taxonomies-is_shadowed, the time their terms were last synced successfully.
Keeping terms in step
| Type | Created or updated on | Deleted on | Term name / slug / description |
|---|---|---|---|
| Post type |
wp_insert_post (active statuses only, skipping revisions and autosaves), untrashed_post
|
after_delete_post |
title / post_name / excerpt |
| User |
user_register, profile_update
|
delete_user (before deletion, deliberately) |
display_name / user_nicename / description |
| BuddyPress group |
groups_create_group, groups_details_updated
|
groups_delete_group |
name / slug / description |
Only posts in an active status are shadowed (default publish, filterable). Trashing a post leaves its term in place; only permanent deletion removes it. Term insert and update failures are written to the debug log when debugging is enabled for the plugin.
Cron: backfill and reconciliation
Activation schedules a daily lh_shadow_taxonomies_process event and a one-off lh_shadow_taxonomies_initial event; deactivation clears both. Both run the same two passes, sharing a 20-second time limit:
-
Backfill: for each shadow taxonomy, creates terms for one batch (default 50) of objects that have none yet. A large site therefore takes several daily runs to backfill fully.
- Users and posts are found with a single prepared query per taxonomy: an
ID NOT IN ( subquery )anti-join against termmeta, so already-shadowed IDs never come back to PHP and the query stays the same size however many objects are shadowed. Users are limited to members of the current site on multisite; posts to the active statuses. - BuddyPress groups go through
groups_get_groups()instead, so plugins that limit groups to a site (such as LH BuddyPress Site Only Groups) still apply. The already-shadowed group IDs are passed as itsexcludelist, which is bounded by the site’s group count. The exclusion is not added to BuddyPress’s SQL, because BuddyPress caches that query under a key only group changes invalidate. - IDs returned by the blacklist filter are excluded in every case.
- Users and posts are found with a single prepared query per taxonomy: an
-
Reconciliation: re-fires
lh_shadow_taxonomies_term_assignedfor a batch of 50 existing term assignments per taxonomy, withtagged_by0 andtagged_atempty, so listeners can pick up assignments made by bulk edits, imports or direct writes. The batch is always the lowest object IDs, and listeners must be idempotent.
Admin and term ordering
On admin_init every shadow taxonomy’s term list gets a Shadow column linking to the user’s profile edit screen, the group page or the post. The plugin also returns false on wp_term_order_taxonomy_supported for shadow taxonomies, so WP Term Order does not try to manage their order.
Structure and storage
A single file, lh-shadow-taxonomies.php, holding the singleton LH_Shadow_taxonomies_plugin (namespace lh_shadow_taxonomies, text domain lh-shadow-taxonomies). Hooks are registered in plugin_init() on plugins_loaded, behind the lh_shadow_taxonomies_init_plugin filter. No options, custom tables, settings screens or abilities: all state is in term meta and BuddyPress group meta.