22

Setting up a WP multisite instance - the client has an existing ontology / set of categories that they want to classify all content across the set of blogs. Also the desire is that any new categories would be added at the 'network blog' level and synced to the other blogs.

What's the best way of doing this?

2
  • i guess making categories assigned to a global variable and then import on theme init.
    – kaiser
    Mar 16, 2011 at 23:13
  • 4
    I think this question is the same as Share one taxonomy across multiple blogs in 3.0. That question did not get a good answer however. It is an interesting question, I'll offer a bounty for it.
    – Jan Fabry
    May 27, 2011 at 20:23

5 Answers 5

15
function __add_global_categories( $term_id )
{
    if ( get_current_blog_id() !== BLOG_ID_CURRENT_SITE || ( !$term = get_term( $term_id, 'category' ) ) )
        return $term_id; // bail

    if ( !$term->parent || ( !$parent = get_term( $term->parent, 'category' ) ) )
        $parent = null;

    global $wpdb;

    $blogs = $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs} WHERE site_id = '{$wpdb->siteid}'" );
    foreach ( $blogs as $blog ) {
        $wpdb->set_blog_id( $blog );

        if ( $parent && ( $_parent = get_term_by( 'slug', $parent->slug, 'category' ) ) )
            $_parent_ID = $_parent->term_id;
        else
            $_parent_ID = 0;

        wp_insert_term( $term->name, 'category',  array(
            'slug' => $term->slug,
            'parent' => $_parent_ID,
            'description' => $term->description
        ));
    }

    $wpdb->set_blog_id( BLOG_ID_CURRENT_SITE );
}
add_action( 'created_category', '__add_global_categories' );

This will run whenever a category is added on the main site. A few caveats/points worth mentioning;

  • If you have a lot of blogs, this function may get pretty intensive.
  • On average, we are running anywhere between 5 to 8 queries (possibly more) per blog - depending on the speed of your database, this function may need to be chunked.
  • Only newly added categories are 'synced'. Updating and deleting categories are not (code will need to be revised).
  • If a newly added category has a parent, and the parent cannot be found within the multisite blog in question, the category will be created with no parent (this should only be the case if the parent category was created before this function was installed).
3
  • 1
    Is there - or could there be - a plugin that does this? Along with edits and deletions? And a settings page to choose which taxonomies and which child sites to apply it to? Apr 20, 2011 at 9:02
  • In fact, would you object if I used your code as the starting point to write a plugin? Apr 20, 2011 at 9:10
  • No problem whatsoever - my answers fall under stack exchange's license, the cc-wiki with attribution required :) May 5, 2011 at 15:47
11
+50

Oh, sunday procrastination...

https://github.com/maugly/Network-Terminator

  • Alows to bulk add terms across network
  • You can select what sites will be affected
  • Works with custom taxonomies
  • Doesn't delete
  • Doesn't sync

This is something I've done in a last few hours and I have no time for more testing now. Anyway - it works for me! .)

Give it a try. There's also a 'test run' feature implemented so you can check the result before actually doing something.

Update -> Screenshots:

Before action:

Before action

After test run:

After test run

The plugin linked above adds user interface but pretty much everything important happens in this function:

        <?php function mau_add_network_terms($terms_to_add, $siteids, $testrun = false) {

        // check if this is multisite install
        if ( !is_multisite() )
            return 'This is not a multisite WordPress installation.';

        // very basic input check
        if ( empty($terms_to_add) || empty($siteids) || !is_array($terms_to_add) || !is_array($siteids) )
            return 'Nah, I eat only arrays!';

        if ($testrun) $log = '<p><em>No need to get excited. This is just a test run.</em></p>';
        else $log = '';

        // loop thru blogs
        foreach ($siteids as $blog_id) :

            switch_to_blog( absint($blog_id) );

            $log .= '<h4>'.get_blog_details(  $blog_id  )->blogname.':</h4>';
            $log .= '<ul id="ntlog">';

            // loop thru taxonomies
            foreach ( $terms_to_add as $taxonomy => $terms ) {

                // check if taxonomy exists
                if ( taxonomy_exists($taxonomy) ) {
                    // get taxonomy name
                    $tax_name = get_taxonomy($taxonomy);
                    $tax_name = $tax_name->labels->name;

                    //loop thru terms   
                    foreach ( $terms as $term ) {

                        // check if term exists
                        if ( term_exists($term, $taxonomy) ) {
                            $log .= "<li class='notice' ><em>$term already exists in the $tax_name taxonomy - not added!</em></li>";

                        } else {

                            // if it doesn't exist insert the $term to $taxonomy
                            $term = strip_tags($term);
                            $taxonomy = strip_tags($taxonomy);
                            if (!$testrun)
                                wp_insert_term( $term, $taxonomy );
                            $log .= "<li><b>$term</b> successfully added to the <b>$tax_name</b> taxonomy</li>"; 
                        }
                    }
                } else {
                    // tell our log that taxonomy doesn't exists
                    $log .= "<li class='notice'><em>The $tax_name taxonomy doesn't exist! Skipping...</em></li>"; 
                }
            }

            $log .= '</ul>';    

            // we're done here
            restore_current_blog();

        endforeach;
        if ($testrun) $log .= '<p><em>No need to get excited. This was just the test run.</em></p>';
        return $log;
    } ?>

I will come back and edit this with more info later (if needed).

It is far from perfect (read known issues in the plugin head).
Any feedback appreciated!

3
  • 3
    I like it when people create plugins in response to questions! You deserve the bounty!
    – Jan Fabry
    Jun 3, 2011 at 18:40
  • Thanks for your support @Jan Fabry. I'll be happy if someone beside me will actually find this thing useful.
    – Michal Mau
    Jun 3, 2011 at 20:15
  • 1
5

TheDeadMedic's answer looks good, but I ended up taking a different approach to the problem. Instead of duplicating the same terms across the many sites, I instead made the other sites use the home site's tables for terms.

add_action('init', 'central_taxonomies');

function central_taxonomies () {
  global $wpdb;

  $wpdb->terms = "wp_terms";
  $wpdb->term_taxonomy = "wp_term_taxonomy";
}

This replaces the table name wp_2_terms with wp_terms, etc. You should of course check in your database to make sure of the exact name of the tables, which might be different if you change your prefix.

You can run this from either a plugin or a theme (though I recommend a plugin). I may get round to publishing a plugin to do this at some point. There are two downsides to this approach:

  • It's only active on child sites that have the plugin activated. There's no way to enforce this from the parent site.
  • It applies to all the taxonomies, not just selected ones.

This approach is flexible - it can be adapted to pull categories from any blog, not just the central one.


Update: I've made this into a plugin, which can be activated site-wide if you need it to be: MU Central Taxonomies

5
  • There's a big problem with this approach: The relations between posts and terms might not be correct. The table term_relationships contains this relation based on post ID and term ID. But there's always chance that posts in subsites have same ID. Changing terms for 1 post might unpredictable affect on another post in another blog.
    – Anh Tran
    Nov 22, 2013 at 3:58
  • Correct, the term_relationships table shouldn't be included. I spotted and fixed that long ago in the plugin, but never updated this answer to match. Nov 22, 2013 at 9:47
  • Howdy, I came from the future! Is this approach still reliable? Any feedback would be welcome Apr 8, 2021 at 12:34
  • Ah, good folks of the past. Search for the filter "wpmu_drop_tables". Apr 8, 2021 at 12:56
  • We need to filter there to prevent term and term_taxonomy from being deleted when deleting a MU site. Apr 8, 2021 at 12:56
1

Yes this is possible. I built a plugin like this for WPMU ages ago (http://natureofmind.org/30/default-categories-for-new-blogs/ but no longer supported) more up-to-date would be the following two plugins: http://wordpress.org/extend/plugins/wpmu-new-blog-defaults/ and http://premium.wpmudev.org/project/new-blog-template

0

Complementing @Marcus Downing answer, I've added a protection to prevent deleting the tables when deleting a MU site.

This is intended to be used as a mu-plugin. If you want to use it elsewhere, change the "muplugins_loaded" hook to "plugins_loaded".

<?php

/**
 * Plugin name: Shared Taxonomies for Multi-site
 * Version: 0.1
 * Plugin Author: Marcus Downing, Lucas Bustamante
 * Author URI: https://wordpress.stackexchange.com/a/386318/27278
 * Description: All instances of multi-site share the same taxonomies as the main site of the network.
 */

$sharedTaxonomies = new SharedTaxonomies;

add_action('muplugins_loaded', [$sharedTaxonomies, 'shareTaxonomies'], 1, 0);
add_action('switch_blog', [$sharedTaxonomies, 'shareTaxonomies'], 1, 0);
add_filter('wpmu_drop_tables', [$sharedTaxonomies, 'protectGlobalTables'], 1, 2);

class SharedTaxonomies
{
    /**
     * Multi-sites uses term tables from the main site.
     */
    public function shareTaxonomies()
    {
        /**
         * @var wpdb $wpdb
         * @var WP_Object_Cache $wp_object_cache
         */
        global $wpdb, $wp_object_cache;

        $wpdb->terms         = $wpdb->base_prefix . 'terms';
        $wpdb->term_taxonomy = $wpdb->base_prefix . 'term_taxonomy';

        $wpdb->flush();
        $wp_object_cache->flush();
    }

    /**
     * Prevent global tables from being deleted when deleting a mu-site
     *
     * @param array $tables The tables to drop when deleting a mu-site.
     * @param int   $siteId The ID of the mu-site being deleted.
     *
     * @return array The tables to drop when deleting a mu-site, excluding the protected ones.
     */
    public function protectGlobalTables($tables, $siteId)
    {
        $protectedTables = ['terms', 'term_taxonomy'];

        return array_filter($tables, function ($tableName) use ($protectedTables) {
            return !in_array($tableName, $protectedTables);
        }, ARRAY_FILTER_USE_KEY);
    }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.