Master Site for specific taxonomy in MultiSite

I want to create “city” taxonomy in all sites of MultiSite. But i want to show same listing in every blog and i want to edit it from only master site and not letting other blog owners edit it. I think only solution for this is setting a master site for specific taxonomy in MultiSite. So when someone try to use “city” taxonomy from other blogs, they will see master site’s city list. How can i do that?

<?php
$master_site = 1;
...when city taxonomy need:
switch_to_blog( $master_site );
...use list of "city" taxonomy"...
restore_current_blog();

Related posts

Leave a Reply

2 comments

  1. First when you register your custom taxonomy use the capabilities argument and define your custom capabilities:

    'capabilities' => array(
        'manage_terms' => 'manage_cities',
        'edit_terms'   => 'edit_cities',
        'delete_terms' => 'delete_cities',
        'assign_terms' => 'assign_cities'
    ),
    

    and only give your users the assign_terms capability this way they wont be able to create new terms, only YOU.

    Then use the nice solution you linked in the comment ,
    change the $taxonomies_to_sync array according to your taxonomy, and you should be fine.

  2. (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.