Architecture

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

TypeCreated or updated onDeleted onTerm name / slug / description
Post typewp_insert_post (active statuses only, skipping revisions and autosaves), untrashed_postafter_delete_posttitle / post_name / excerpt
Useruser_register, profile_updatedelete_user (before deletion, deliberately)display_name / user_nicename / description
BuddyPress groupgroups_create_group, groups_details_updatedgroups_delete_groupname / 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:

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.