How to Sync Categories Across a WordPress Multisite Network from a “Master” Site

I’ve been tasked at WebDevStudios to create a script which will allow a user to control categories across all sites in a WordPress Multisite Network from the main site.

Here’s what I’ve done so far. This isn’t fully functional yet, but from here we can now fire off this function manually, or maybe use WP-Cron to sync the network periodically.

Let me know what you think.

<?php
/*
Plugin Name: Multisite Category Sync
Version: 0.1
*/

// CONSTANTS
define('MS_CATEGORY_TAXONOMY', 'category');

function ms_category_sync_add_menu() {
	add_submenu_page( 'wpmu-admin.php', __('Category Sync'), __('Category Sync'), 'manage-options', 'ms-category-sync-'.basename(__FILE__), 'ms_category_sync_page');
}
add_action('admin_menu', 'ms_category_sync_add_menu');

function ms_category_sync_page() {
	
	if ( !current_user_can('manage_options') )
    	wp_die( __('You do not have sufficient permissions to access this page.') );

	$ms_category_sync = 'ms-category-sync';
	
	if ( isset($_POST[ $ms_category_sync ]) && $_POST[ $ms_category_sync ] == '1' ) { 
		ms_category_sync(); ?>
		<div class="updated"><p><strong><?php _e('Category synchronization completed.'); ?></strong></p></div>
	<?php }
	
	echo '<div class="wrap">';
    echo '<h2>' . __( 'Sync Categories Across Network') . '</h2>';
	?>
	
	<form name="ms-category-sync" method="post" action="">
	<input type="hidden" name="<?php echo $ms_category_sync; ?>" value="1">
	<p class="submit">
	<input type="submit" name="Submit" class="button-primary" value="<?php esc_attr_e('Sync Categories') ?>" />
	</p>
	</form>
	
	<?php
	echo '</div>';
}

// Category Sync
function ms_category_sync() {
	
	// Globals
	global $wpdb;
	global $switched;
	global $current_site;
	
	// Master Site?
	if ($current_site->id == 1) :
	
		// Get Master Categories
		$query = "SELECT * FROM ".$wpdb->prefix."terms a, ".$wpdb->prefix."term_taxonomy b where a.term_id = b.term_id and b.taxonomy = 'category'";
		$mastercategories = $wpdb->get_results($query);
		//print_r($mastercategories); echo '<br /><br />'; // For Debugging
		
		// Get An Array of Slugs
		$masterslugs = array();
		foreach ($mastercategories as $mastercategory) :
			$masterslugs[] = $mastercategory->slug;
		endforeach;
	
		// Query Sites in Network
		$query = $wpdb->prepare("select blog_id from $wpdb->blogs");
		$sites = $wpdb->get_results($query);
		
		// Use This Later
		$childrencategories = array();

		// For Each Site
		foreach ($sites as $site) :
			// Skip Master Site
			if ($site->blog_id != 1) :				
				// Switch
				switch_to_blog($site->blog_id);
				// Get Categories
				$query = "SELECT * FROM ".$wpdb->prefix."terms a, ".$wpdb->prefix."term_taxonomy b where a.term_id = b.term_id and b.taxonomy = 'category'";
				$categories = $wpdb->get_results($query);
				//print_r($categories); echo '<br /><br />'; // For Debugging
				// If a Category shouldn't be here, delete it
				foreach ($categories as $key => $category) :
					if (!in_array($category->slug, $masterslugs)) :
						wp_delete_term( $category->term_id, MS_CATEGORY_TAXONOMY );
					endif;
				endforeach;
				$query = "SELECT * FROM ".$wpdb->prefix."terms a, ".$wpdb->prefix."term_taxonomy b where a.term_id = b.term_id and b.taxonomy = 'category'";
				$categories = $wpdb->get_results($query);
				//print_r($categories); echo '<br /><br />'; // For Debugging
				// Updated Array of Slugs
				$updatedslugs = array();
				foreach ($categories as $category) :
					$updatedslugs[] = $category->slug;
				endforeach;
				// If a Category needs to be here, add it
				foreach ($mastercategories as $key => $mastercategory) :
					if (!in_array($mastercategory->slug, $updatedslugs)) :
						// Does this Category have a parent?
						if ($mastercategory->parent == 0) :
							$term = wp_insert_term($mastercategory->name, MS_CATEGORY_TAXONOMY, array('description' => $mastercategory->category_description, 'slug' => $mastercategory->slug, 'parent' => '0'));
						// Let's come back to this and go through the rest so we get ALL parent ID's
						else :
							foreach ($mastercategories as $i => $cat) :
								if ($mastercategory->parent == $cat->term_id) :
									$parentcatslug = $mastercategories[$i]->slug;
									$childrencategories[] = array('name' => $mastercategory->name, 'description' => $mastercategory->category_description, 'slug' => $mastercategory->slug, 'parent_slug' => $parentcatslug);
								endif;
							endforeach;
						endif;
					endif;
				endforeach;
				$query = "SELECT * FROM ".$wpdb->prefix."terms a, ".$wpdb->prefix."term_taxonomy b where a.term_id = b.term_id and b.taxonomy = 'category'";
				$categories = $wpdb->get_results($query);
				//print_r($categories); echo '<br /><br />'; // For Debugging
				// Now we can deal with the child categories
				if ($childrencategories) :
					foreach ($childrencategories as $key => $childcategory) :
						foreach ($categories as $i => $cat) :
							if ($childcategory['parent_slug'] == $cat->slug) :
								$childcategoryparent = $categories[$i]->term_id;
								if ($childcategoryparent) :
									// Insert Child Category
									$term = wp_insert_term($childcategory['name'], MS_CATEGORY_TAXONOMY, array('description' => $childcategory['description'], 'slug' => $childcategory['slug'], 'parent' => $childcategoryparent));
								endif;
							endif;
						endforeach;
					endforeach;
				endif;
				// Restore
				restore_current_blog();
			endif;
		endforeach;
		// Clear Term Cache For Each Site
		foreach ($sites as $site) :
			// Skip Master Site
			if ($site->blog_id != 1) :				
				// Switch
				switch_to_blog($site->blog_id);
				$query = "SELECT * FROM ".$wpdb->prefix."terms a, ".$wpdb->prefix."term_taxonomy b where a.term_id = b.term_id and b.taxonomy = 'category'";
				$categories = $wpdb->get_results($query);
				foreach ($categories as $cat) :
					ms_category_sync_clean_term_cache($cat->term_id, MS_CATEGORY_TAXONOMY, true, true);
				endforeach;
				restore_current_blog();
			endif;
		endforeach;	
	endif;
}

// Debug
function ms_category_sync_debug() {
	if (isset($_GET['ms_category_sync_debug'])) :
		ms_category_sync();
	endif;
}
add_action( 'admin_init', 'ms_category_sync_debug' );

function ms_category_sync_clean_term_cache($ids, $taxonomy = '', $clean_taxonomy = true, $force_clean_taxonomy = false) {
	global $wpdb;
	static $cleaned = array();

	if ( !is_array($ids) )
		$ids = array($ids);

	$taxonomies = array();
	// If no taxonomy, assume tt_ids.
	if ( empty($taxonomy) ) {
		$tt_ids = array_map('intval', $ids);
		$tt_ids = implode(', ', $tt_ids);
		$terms = $wpdb->get_results("SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)");
		$ids = array();
		foreach ( (array) $terms as $term ) {
			$taxonomies[] = $term->taxonomy;
			$ids[] = $term->term_id;
			wp_cache_delete($term->term_id, $term->taxonomy);
		}
		$taxonomies = array_unique($taxonomies);
	} else {
		$taxonomies = array($taxonomy);
		foreach ( $taxonomies as $taxonomy ) {
			foreach ( $ids as $id ) {
				wp_cache_delete($id, $taxonomy);
			}
		}
	}

	foreach ( $taxonomies as $taxonomy ) {
		if ( isset($cleaned[$taxonomy]) && ! $force_clean_taxonomy )
			continue;
		$cleaned[$taxonomy] = true;

		if ( $clean_taxonomy ) {
			wp_cache_delete('all_ids', $taxonomy);
			wp_cache_delete('get', $taxonomy);
			
			// clear get_terms cache
			$cache_keys = wp_cache_get( 'get_terms:cache_keys', 'terms' );
			if ( ! empty( $cache_keys ) ) {
				foreach ( $cache_keys as $key => $cache_taxonomies ) {
					if ( in_array( $taxonomy, $cache_taxonomies ) ) {
						wp_cache_delete( "get_terms:{$key}", 'terms' );
						unset( $cache_keys[$key] );
					}
				}
			} else {
				$cache_keys = array();
			}
			wp_cache_set( 'get_terms:cache_keys', $cache_keys, 'terms' );
			
			delete_option("{$taxonomy}_children");
			// Regenerate {$taxonomy}_children
			_get_term_hierarchy($taxonomy);
		}

		do_action('clean_term_cache', $ids, $taxonomy);
	}
}

Tags: , , , , , , , , ,

6 Responses to “How to Sync Categories Across a WordPress Multisite Network from a “Master” Site”

  1. Quint February 17, 2012 at 10:47 pm #

    Hello Scott,

    Have you per chance done more work on your script and turned it into a plugin? I’ve been googling quite awhile and haven’t found a solution to managing categories/tags from the main site such that they can be used on the sub-sites on a multisite network.

    How would I implement what you have done? If you know of a better solution, could you let me know? Thank you!

    • todd February 27, 2012 at 5:26 pm #

      hey quint,

      all you need to do is copy and paste the code above into a blank php file, then name that file something like: “mu-category-sync.php and upload it to your “/wp-content/plugins” folder. You should then see the plugin in your Plugins admin menu, where can then activate.

      Note: don’t “double click” to select all the code above to copy it as it won’t work. Instead, highlight all the code with your mouse then copy it.

      good luck.

      todd

      • Quint March 4, 2012 at 12:22 am #

        Hello Todd,

        Thank you for responding and writing the script. Question: From a “categories” standpoint, will it only sync to the first child level; that is, it won’t go down to the grandchildren and beyond?
        This is what I found when I execute the script.

        Quint

  2. todd February 27, 2012 at 5:38 pm #

    Hey Scott,

    Thanks for the plugin, it worked I think close to how you intended it for me. I’m on: 3.3.1 + BP 1.5.4.

    The result of running the sync was that some of the sites in my network ended up with the category structure of the Main site. Other sites (for reasons I can’t determine) ended up with about 5 copies of each category from the Main site.

    Thought you’d like to know.

    best,

    todd

  3. Jeffrey Lin October 13, 2012 at 5:37 am #

    Thanks for sharing this scott. Also, this comment form login options is interesting. Is it a plugin I can find or a customization you did?

    • scottbasgaard October 19, 2012 at 11:03 am #

      Hey Jeffrey. It’s an add-on for Jetpack by Automattic. http://jetpack.me/ Cheers!

Leave a Reply