(disclaimer: this involves a core WP hack, tread carefully)
I have a Multisite network where I wanted the primary blog (id == 1) to set the taxonomy activity_type
for all other sites on the network.
I used the code below to include hooks when creating, editing or deleting terms in that custom taxonomy which then mimics that task on all subsites. What is not covered here is restricting users on other sites from adding/editing/deleting terms - I used a combination of jQuery, CSS and remove_action
functions to hide those UI elements from my subsites.
//When an activity type is added to the primary site, also add it to the sub-sites
function as_act_type_add ($term_id, $tt_id, $taxonomy){
$new_act_type = get_term($term_id, 'activity_type', ARRAY_A);
//get parent data if necessary
$parent_term = get_term_by('term_id', $new_act_type['parent'], 'activity_type', ARRAY_A);
$sites = as_network_ids();
foreach($sites as $site){
switch_to_blog($site);
$exist_check = term_exists($new_act_type['name'], 'activity_type'); //check if the created term exists on the subsite already
$site_parent = term_exists( $parent_term['name'], 'activity_type' ); //check if the parent term exists
$parent_term_id = $site_parent['term_id']; // get numeric term id
if(is_array($exist_check)){
//do nothing - in the future, we want this to update the existing term but punt for now
} else {
wp_insert_term(
$new_act_type['name'], // the term
'activity_type', // the taxonomy
array(
'description'=> $new_act_type['description'],
'slug' => $new_act_type['slug'],
'parent' => $parent_term_id
)
);
//since we might have added children, you have to delete the cache, or else they wont display right away
delete_option("activity_type_children");
} //end term_exists
restore_current_blog();
} //end foreach
//and delete cache for main site
delete_option("activity_type_children");
}
//triggered whenever a term is added
add_action("create_activity_type", "as_act_type_add", 10, 3);
function as_act_type_update($term_id, $tt_id, $taxonomy){
//the term hasnt been changed yet, but we want to get it anyway in case the name has been changed
$legacy_term = get_term($term_id, 'activity_type', ARRAY_A);
$edited_act_type = $_POST;
if($edited_act_type['parent']){
$parent_term = get_term_by('term_id', $edited_act_type["parent"], 'activity_type', ARRAY_A);
}
$sites = as_network_ids();
foreach($sites as $site){
switch_to_blog($site);
//cehck to see if the updated term exists on the site
$exist_check = term_exists($legacy_term['name'], 'activity_type');
if($parent_term){
//check to make sure we have the parent
$site_parent_term = term_exists($parent_term['name'], 'activity_type');
}
//if so, update it
if(is_array($exist_check)){
//since wp_update_term calls the edit_term hook, we need to unhook to avoid an infinite loop
remove_action("edit_activity_type", "as_act_type_update", 10, 3);
//update that fool
wp_update_term(
$exist_check['term_id'], // the term id
'activity_type', // the taxonomy
array(
'name' => $edited_act_type['name'],
'description' => $edited_act_type['description'],
'slug' => $edited_act_type['slug'],
'parent' => $site_parent_term['term_id'] //if this doesnt exist, WP will default to 0, or no parent
)
);
} //end is_array check
restore_current_blog();
} //end foreach
} //end update term func
add_action("edit_activity_type", "as_act_type_update", 10, 3);
function as_act_type_delete($term_id, $tt_id, $taxonomy){
//somehow this also works with bulk delete- I didnt think it would but apprarently it does. sacrificial goat must have worked.
$deleted_act_type = get_site_transient( 'as_last_activity_type_delete' ); //temp transient set in wp-includes/taxonomy.php to store deleted term data
if ( false === ( $deleted_act_type)) {
//do nothing if no transient available
} else {
//we have the deleted term data fromt he transient, so use it here
$sites = as_network_ids();
foreach($sites as $site){
switch_to_blog($site);
$exist_check = term_exists($deleted_act_type->name, 'activity_type');
if(is_array($exist_check)){
//unhook action to avoid infinite loop
remove_action("delete_activity_type", "as_act_type_delete", 10, 3);
wp_delete_term($exist_check['term_id'], 'activity_type');
//add_action("delete_activity_type", "as_act_type_delete", 10, 3);
} //end exist check
restore_current_blog();
} //end foreach
//now clear the data from the transient
delete_site_transient( 'as_last_activity_type_delete' );
} //end false check for transient
} //end update term
add_action("delete_activity_type", "as_act_type_delete", 9, 3);
This also requires a hack to WP core (yes, beware of this carinal sin for it is a terrible transgression). In wp-includes/taxonomy.php
find line 1767 or the $term = (int) $term;
line within the wp_delete_term
function. There, place this line:
set_site_transient('as_last_activity_type_delete',get_term($term,'activity_type'));
I might also leaving yourself a comment here and also tattooing a reminder of this change somewhere because you will have to manually update taxonomy.php
every time you update WordPress. This sucks but the reason we're setting this transient is so we can lookup the deleted term by its slug rather than its ID. The action hook delete_{tax_type}
gives us the term_id for the deleted term but we can't be sure that will be the same on all of our network sites, so we have to get the term slug before it is deleted... thus the core hack. I don't love it, I'm not proud of it, but it works. If you only care about pushing terms to your network sites and dont care about removing them from your main site, you could actually skip this and not have to hack core - I needed to be able to create and delete from a single site and this was the only way I found.
You will also notice a line in my code where this happens $sites = as_network_ids();
. That is simply a helper function I've created elsewhere that returns a list of all the network blog ids along with some extra data I use for a lot of other functions. You can get the blog list using this function (warning: it is officially deprecated but hasn't been replaced yet) or by writing your own SQL.