Author Archive

How To Create Custom Taxonomies In WordPress


  

WordPress 3 introduced custom taxonomies as a core feature. The following release of 3.1 included many features to enhance support of custom taxonomies. Better import and export handling, advanced queries with tax_query, hierarchical support, body classes and a bunch of wonderful functions to play with were all part of the package.

Let’s take an in-depth look at how to create your own custom taxonomies in WordPress, including a few advanced development examples that you can begin using in your WordPress themes and plugins today.

Taxonomies: Bringing order to chaos in WordPress

Taxonomies In WordPress

WordPress’ custom taxonomies make it possible to structure large amounts of content in a logical, well-organized way. In WordPress, categories are set up as a hierarchal taxonomy, and tags are set up as a multifaceted taxonomy.

Taxonomy content can be displayed in a theme using taxonomy templates. Within a template, there are ample ways to display your data with built-in taxonomy functions.

Built-In Taxonomies

WordPress offers four built-in taxonomies out of the box:

  1. Categories (hierarchal),
  2. Tags (multifaceted),
  3. Links (multifaceted),
  4. Navigation menu (hierarchal).

Custom Taxonomies

WordPress provides a new method of grouping content by allowing you to create your own custom taxonomies. The core developers have created the register_taxonomy() function to handle the heavy lifting for us. All you have to do is understand how to configure all of the settings to suit your needs.

A Practical Example: Content By Location

A business that operates in multiple locations could benefit from organizing its content by location to allow visitors to browse news in their locality. A large news organization could organize its content by world region (Africa, Asia, Europe, Latin America, Middle East, US & Canada), as the BBC does in its “World� section.

How the BBC uses a location taxonomy

Create a Custom Taxonomy

In WordPress, you can create (or “register�) a new taxonomy by using the register_taxonomy() function. Each taxonomy option is documented in detail in the WordPress Codex.

WordPress custom taxonomy: Posts by location

/**
 * Add custom taxonomies
 *
 * Additional custom taxonomies can be defined here
 * http://codex.wordpress.org/Function_Reference/register_taxonomy
 */
function add_custom_taxonomies() {
	// Add new "Locations" taxonomy to Posts
	register_taxonomy('location', 'post', array(
		// Hierarchical taxonomy (like categories)
		'hierarchical' => true,
		// This array of options controls the labels displayed in the WordPress Admin UI
		'labels' => array(
			'name' => _x( 'Locations', 'taxonomy general name' ),
			'singular_name' => _x( 'Location', 'taxonomy singular name' ),
			'search_items' =>  __( 'Search Locations' ),
			'all_items' => __( 'All Locations' ),
			'parent_item' => __( 'Parent Location' ),
			'parent_item_colon' => __( 'Parent Location:' ),
			'edit_item' => __( 'Edit Location' ),
			'update_item' => __( 'Update Location' ),
			'add_new_item' => __( 'Add New Location' ),
			'new_item_name' => __( 'New Location Name' ),
			'menu_name' => __( 'Locations' ),
		),
		// Control the slugs used for this taxonomy
		'rewrite' => array(
			'slug' => 'locations', // This controls the base slug that will display before each term
			'with_front' => false, // Don't display the category base before "/locations/"
			'hierarchical' => true // This will allow URL's like "/locations/boston/cambridge/"
		),
	));
}
add_action( 'init', 'add_custom_taxonomies', 0 );

After adding this to your theme’s functions.php file, you should see a new taxonomy under the “Posts� menu in the admin sidebar. It works just like categories but is separate and independent.

WordPress custom taxonomy: Posts by location

After adding a few terms to your new taxonomy, you can begin to organize the content in your posts by location. A new “Locations� box will appear to the right of your posts in the WordPress admin area. Use this the way you would categories.

Let’s use this “location� taxonomy as a jumping-off point to learn more about working with taxonomy functions and content.

Create a Taxonomy Template for Your Theme

Taxonomies: Bringing order to chaos in WordPress

When you add a custom taxonomy to a WordPress theme, you can display its content using one of WordPress’ taxonomy theme templates.

  • taxonomy-{taxonomy}-{slug}.php
    We could use this to create a theme template for a particular location, such as taxonomy-location-boston.php for the term “boston.�
  • taxonomy-{taxonomy}.php
    If the taxonomy were location, WordPress would look for taxonomy-location.php.
  • taxonomy.php
    This template is used for all custom taxonomies.
  • archive.php
    If no taxonomy-specific template is found, then the taxonomy that lists pages will use the archive template.
  • index.php
    If no other template is found, then this will be used.

Let’s use taxonomy-location.php to display our content. The template file could look something like this:

<?php
/**
 * Locations taxonomy archive
 */
get_header();
$term = get_term_by( 'slug', get_query_var( 'term' ), get_query_var( 'taxonomy' ) );
?>
<div class="wrapper">
	<div class="primary-content">
		<h1 class="archive-title"><?php echo apply_filters( 'the_title', $term->name ); ?> News</h1>

		<?php if ( !empty( $term->description ) ): ?>
		<div class="archive-description">
			<?php echo esc_html($term->description); ?>
		</div>
		<?php endif; ?>

		<?php if ( have_posts() ): while ( have_posts() ): the_post(); ?>

		<div id="post-<?php the_ID(); ?>" <?php post_class('post clearfix'); ?>>
			<h2 class="post-title"><a href="<?php the_permalink(); ?>" rel="bookmark"><?php the_title(); ?></a></h2>
			<div class="content clearfix">
				<div class="post-info">
					<p><?php the_time(get_option('date_format')); ?> by <?php the_author_posts_link(); ?></p>
				</div><!--// end .post-info -->
				<div class="entry">
					<?php the_content( __('Full story…') ); ?>
				</div>
			</div>
		</div><!--// end #post-XX -->

		<?php endwhile; ?>

		<div class="navigation clearfix">
			<div class="alignleft"><?php next_posts_link('« Previous Entries') ?></div>
			<div class="alignright"><?php previous_posts_link('Next Entries »') ?></div>
		</div>

		<?php else: ?>

		<h2 class="post-title">No News in <?php echo apply_filters( 'the_title', $term->name ); ?></h2>
		<div class="content clearfix">
			<div class="entry">
				<p>It seems there isn't anything happening in <strong><?php echo apply_filters( 'the_title', $term->name ); ?></strong> right now. Check back later, something is bound to happen soon.</p>
			</div>
		</div>

		<?php endif; ?>
	</div><!--// end .primary-content -->

	<div class="secondary-content">
		<?php get_sidebar(); ?>
	</div><!--// end .secondary-content -->

<?php get_footer(); ?>

(Normally, we would load a template part for the loop, but for the sake of simplicity, I’ve skipped that step. As an alternative to get_term_by(), we could use single_term_title() and term_description() on this taxonomy archive template to display or retrieve the title and description of the taxonomy term.)

In this example, we’ve used a function called get_term_by() to retrieve all of the data associated with a taxonomy term in the form of an object. The object returned by the get_term_by() function contains the following details about the term:

  • ID
    325
  • name
    Boston
  • slug
    boston
  • group
    0
  • taxonomy
    location
  • taxonomy ID
    325
  • description
    If you need to know the latest news in Boston, then look no further.
  • parent
    0 (or the ID)
  • count
    1 (i.e. the number of posts with this term selected)

We’ve used this object, then, to display information about the current term name and description in the taxonomy-location.php template.

Using Taxonomy Conditionals

Conditional tags can be used in WordPress to determine what content is displayed on a particular page depending on the conditions met by the page. Taxonomy templates have their own set of conditionals:

  • is_tax()
    When any taxonomy archive page is being displayed.
  • is_tax( 'location' )
    When a taxonomy archive page for the “location� taxonomy is being displayed.
  • is_tax( 'location', 'boston')
    When the archive page for the “location� taxonomy with the slug of “boston� is being displayed.
  • is_tax( 'location', array( 'boston', 'new-york', 'philadelphia' ) )
    Returns true when the “location� taxonomy archive being displayed has a slug of either “boston,� “new-york� or “philadelphia.�
  • taxonomy_exists()
    When a particular taxonomy is registered via register_taxonomy().

Working With Taxonomy Functions

Many functions for working with taxonomies are available in WordPress. Let’s go through a few commons examples of how to use them in practice.

Display a List of Taxonomy Terms

Most navigation systems begin with an unordered list. You can generate an unordered list of links to taxonomy archive pages using the wp_list_categories() function. This function is very customizable and can handle most of the scenarios that you’ll encounter as a theme developer.

/**
 * Create an unordered list of links to active location archives
 */
$locations_list = wp_list_categories( array(
  'taxonomy' => 'location',
  'orderby' => 'name',
  'show_count' => 0,
  'pad_counts' => 0,
  'hierarchical' => 1,
  'echo' => 0,
  'title_li' => 'Locations'
) );

// Make sure there are terms with articles
if ( $locations_list )
	echo '<ul class="locations-list">' . $locations_list . '</ul>';

If you encounter a situation that requires a custom structure, I would recommend exploring the Walker class or the wp_get_object_terms() function.

Create a Taxonomy Tag Cloud

A tag cloud provides a great way for users to browse content. The wp_tag_cloud() function makes creating a tag cloud with a custom taxonomy easy.

WordPress taxonomy term cloud example

Let’s use it to display a tag cloud of our location terms:

// Locations tag cloud
<?php
$locations_cloud = wp_tag_cloud( array(
	'taxonomy' => 'location',
	'echo' => 0
) );

// Make sure there are terms with articles
if ( $locations_cloud ): ?>
<h2>News by Location</h2>
<div class="locations-cloud">
	<?php echo $locations_cloud; ?>
</div>
<?php endif; ?>

Get All Terms in a Taxonomy

You will often need to work with a full list of terms in a taxonomy, and the get_terms() function can be quite handy for this. Let’s use it to show off the number of locations to which we’re providing news:

<?php
// Get a list of all terms in a taxonomy
$terms = get_terms( "location", array(
	'hide_empty' => 0,
) );
$locations = array();
if ( count($terms) > 0 ):
	foreach ( $terms as $term )
		$locations[] = $term->name;

	$locations_str = implode(', ', $locations);
?>
<h2>Nationwide Coverage</h2>
<p>We cover stories around the country in places like <?php echo $locations_str; ?> and more. If we're not the best source for the latest news in your area, let us know!</p>
<?php endif; ?>

This will output the following HTML:

<h2>Nationwide Coverage</h2>
<p>We cover stories around the country in places like Boston, London, New York, San Francisco and more. If we're not the best source for the latest news in your area, let us know!</p>

This simple approach may not show it, but the get_terms() function is incredibly powerful. It allows you to get terms from multiple taxonomies at once by passing an array that contains the names of your taxonomies as the first parameter.

Working With WP_Query and tax_query

The WP_Query class enables you to create a custom loop. WordPress 3.1 introduced a new parameter for the class called tax_query, which allows you to display content from a taxonomy in many unique ways.

Let’s use it to create a list of the most recent news posts in Boston.

<?php
/**
 * Display a list of the most recent news in Boston
 *
 * @class WP_Query http://codex.wordpress.org/Class_Reference/WP_Query
 */
$locations_query = new WP_Query( array(
	'post_type' => 'post',
	'posts_per_page' => 10,
	'tax_query' => array(
		array(
			'taxonomy' => 'location',
			'field' => 'slug',
			'terms' => 'boston'
		)
	)
) );
// Display the custom loop
if ( $locations_query->have_posts() ): ?>
<h2>Latest News in Boston</h2>
<ul class="postlist">
	<?php while ( $locations_query->have_posts() ) : $locations_query->the_post(); ?>
	<li><span class="date"><?php the_time(get_option('date_format')); ?></span> – <a href="<?php the_permalink(); ?>" rel="bookmark"><?php the_title(); ?></a></li>
	<?php endwhile; wp_reset_postdata(); ?>
</ul><!--// end .postlist -->
<?php endif; ?>

We could easily make this set-up dynamic by using the get_term_by() or get_terms() functions that we discussed earlier.

Attaching Additional Data to a Taxonomy

Each taxonomy term has specific data associated with it. Out of the box, WordPress allows you to store the following information for each taxonomy term:

  • Name,
  • Slug,
  • Parent,
  • Description.

But what if you need to store more information, such as an image for the taxonomy term or a title and description for search engines, or maybe even attach the term to a particular author the way a traditional news column does? Since WordPress 2.9, developers have been able to attach additional meta data to posts, pages, custom post types, comments and users using the add_metadata(), update_metadata() and get_metadata() functions. But this does not include taxonomies such as tags and categories.

With the help of the Taxonomy Metadata plugin, we can attach meta data to taxonomy terms for both built-in and custom taxonomies. This enables us to create additional taxonomy fields that will be stored in a new taxonomymeta database table.

Note to Multisite developers: I’ve encountered issues using the Taxonomy Metadata plugin on a WordPress Multisite installation. Activating the plugin network-wide results in data not being saved. Instead, activate the plugin individually for each website, and it will work properly.

(Knowing the story behind this technique and what it might mean for future WordPress upgrades is important. Currently, there is a debate on the WordPress Trac project about the best method for this. The method I’ll show you is one suggested by various WordPress core developers. But I strongly urge you to review the Trac project and the Codex. A standard approach could very well be built into WordPress in the future, which would therefore be more practical than what I am about to show you.)

The prerequisite to all of the examples below is to install and activate the Taxonomy Metadata plugin.

Add Search Engine Title and Description Fields to Categories and Tags

We will use action hooks to gracefully attach additional fields to our taxonomies without editing WordPress’ core. If you’ve made it this far, then you probably have a working knowledge of WordPress filters and actions. To learn about working with hooks, I highly suggest Daniel Pataki’s article on the subject.

WordPress taxonomy meta data: Search engine title and description fields added to categories and tags

Let’s start by adding a text input and a textarea field to the “Add New� and “Edit� term pages on the WordPress admin screen. We do this by placing the following functions in our theme or plugin.

The taxonomy_metadata_add() function attaches the fields to the /wp-admin/edit-tags.php?taxonomy=%taxonomy% page.
The %taxonomy% item in the URL above will change depending on the term you are editing.

/**
 * Add additional fields to the taxonomy add view
 * e.g. /wp-admin/edit-tags.php?taxonomy=category
 */
function taxonomy_metadata_add( $tag ) {
	// Only allow users with capability to publish content
	if ( current_user_can( 'publish_posts' ) ): ?>
	<div class="form-field">
		<label for="meta_title"><?php _e('Search Engine Title'); ?></label>
		<input name="meta_title" id="meta_title" type="text" value="" size="40" />
		<p class="description"><?php _e('Title display in search engines is limited to 70 chars'); ?></p>
	</div>

	<div class="form-field">
		<label for="meta_description"><?php _e('Search Engine Description'); ?></label>
		<textarea name="meta_description" id="meta_description" rows="5" cols="40"></textarea>
		<p class="description"><?php _e('The meta description will be limited to 156 chars by search engines.'); ?></p>
	</div>
	<?php endif;
}

The taxonomy_metadata_edit() function attaches the fields to the /wp-admin/edit-tags.php?action=edit&taxonomy=%taxonomy%&tag_ID=%id%&post_type=%post_type% page.
The %taxonomy%, %id% and %post_type% items in the URL above will change depending on the term you are editing.

We’ll use the get_metadata function here to display any saved data that exists in the form.

/**
 * Add additional fields to the taxonomy edit view
 * e.g. /wp-admin/edit-tags.php?action=edit&taxonomy=category&tag_ID=27&post_type=post
 */
function taxonomy_metadata_edit( $tag ) {
	// Only allow users with capability to publish content
	if ( current_user_can( 'publish_posts' ) ): ?>
	<tr class="form-field">
		<th scope="row" valign="top">
			<label for="meta_title"><?php _e('Search Engine Title'); ?></label>
		</th>
		<td>
			<input name="meta_title" id="meta_title" type="text" value="<?php echo get_term_meta($tag->term_id, 'meta_title', true); ?>" size="40" />
			<p class="description"><?php _e('Title display in search engines is limited to 70 chars'); ?></p>
		</td>
	</tr>

	<tr class="form-field">
		<th scope="row" valign="top">
			<label for="meta_description"><?php _e('Search Engine Description'); ?></label>
		</th>
		<td>
			<textarea name="meta_description" id="meta_description" rows="5" cols="40"><?php echo get_term_meta($tag->term_id, 'meta_description', true); ?></textarea>
			<p class="description"><?php _e('Title display in search engines is limited to 70 chars'); ?></p>
		</td>
	</tr>
	<?php endif;
}

These two functions control the output of the form fields. I’ve used HTML that follows WordPress’ UI patterns and styles guidelines for the admin area.

Saving the form’s data to the taxonomymeta database table
Now that we’ve added the form fields, we’ll need to process and save the data with the update_term_meta function that is provided by the plugin.

/**
 * Save taxonomy metadata
 *
 * Currently the Taxonomy Metadata plugin is needed to add a few features to the WordPress core
 * that allow us to store this information into a new database table
 *
 *	http://wordpress.org/extend/plugins/taxonomy-metadata/
 */
function save_taxonomy_metadata( $term_id ) {
	if ( isset($_POST['meta_title']) )
		update_term_meta( $term_id, 'meta_title', esc_attr($_POST['meta_title']) );

	if ( isset($_POST['meta_description']) )
		update_term_meta( $term_id, 'meta_description', esc_attr($_POST['meta_description']) );
}

Add the new taxonomy fields
Now that everything is in place, we’ll use action hooks to load our new functions in all the right places. By hooking the following function into the admin_init action, we ensure that it runs only on the admin side of WordPress. First, we need to make sure that the functions added by the Taxonomy Metadata plugin are available. Next, we use the get_taxonomies() function to attach the new taxonomy fields to every public taxonomy, including the built-in tags and categories.

/**
 * Add additional taxonomy fields to all public taxonomies
 */
function taxonomy_metadata_init() {
	// Require the Taxonomy Metadata plugin
	if( !function_exists('update_term_meta') || !function_exists('get_term_meta') ) return false;

	// Get a list of all public custom taxonomies
	$taxonomies = get_taxonomies( array(
		'public'   => true,
		'_builtin' => true
	), 'names', 'and');

	// Attach additional fields onto all custom, public taxonomies
	if ( $taxonomies ) {
		foreach ( $taxonomies  as $taxonomy ) {
			// Add fields to "add" and "edit" term pages
			add_action("{$taxonomy}_add_form_fields", 'taxonomy_metadata_add', 10, 1);
			add_action("{$taxonomy}_edit_form_fields", 'taxonomy_metadata_edit', 10, 1);
			// Process and save the data
			add_action("created_{$taxonomy}", 'save_taxonomy_metadata', 10, 1);
			add_action("edited_{$taxonomy}", 'save_taxonomy_metadata', 10, 1);
		}
	}
}
add_action('admin_init', 'taxonomy_metadata_init');

That’s it. We’re done!

You should now see two additional fields in your tags, categories and public custom taxonomies. As mentioned at the beginning of this section, the technique can be used to handle many different scenarios. This basic framework for storing and retrieving information associated with a taxonomy should have you well on your way to mastering the management of taxonomy content.

In Conclusion

I hope you better understand how to organize WordPress content with the help of taxonomies. Whether hierarchal or multifaceted, a well-implemented taxonomy will simplify the way content is organized and displayed on a website. WordPress has all of the tools you need to create custom taxonomies and to group your content in new and exciting ways. How you use them is up to you!

Additional Resources

(al)


© Kevin Leary for Smashing Magazine, 2012.


WordPress Multisite: Practical Functions And Methods





 



 


Multisite is a powerful new feature that arrived with the release of WordPress 3.0. It allows website managers to host multiple independent websites with a single installation of WordPress. Although each “website� in a network is independent, there are many ways to share settings, code and content throughout the entire network.

WordPress Multisite

Since the beginning of the year, I’ve been developing themes and plugins for a WordPress Multisite-powered content network. During that time I’ve learned many powerful tips and tricks unique to Multisite. This guide will introduce you to a few Multisite-specific functions, along with real-world programming examples that you can begin using today. Hopefully, it will open your eyes to a few of the new possibilities available in Multisite.

Why Use Multisite?

Multisite is a great option for freelancers, businesses and organizations that manage multiple WordPress websites. Whether you’re a freelancer who wants to provide hosting and maintenance to clients, a college organization looking to centralize the management of your websites, or a large news publisher trying to isolate silos for different departments, Multisite is the answer.

Managing multiple websites with a single installation of WordPress enables you to easily upgrade the core, plugins and themes for every website in a network. You can share functionality across multiple websites with network plugins, as well as standardize design elements across multiple websites using a parent theme.

Overview of Benefits

  • Users are able to easily access and manage multiple websites with a single user account and profile.
  • Users can access a particular website or every website using the same account.
  • Information from one website can be completely isolated from others.
  • Information from one website can be easily shared with others.
  • Theme functionality can be shared across multiple websites using a parent-child theme relationship or a functionality plugin.
  • Updates and upgrades can be rolled out across multiple websites in less time, reducing overhead and maintenance costs.
  • Customizations to WordPress can be efficiently distributed in a centralized, cascading method using network-wide plugins.

I won’t explain how to install and configure Multisite. If you need help, plenty of great articles are available in the WordPress Codex.

Working With Multisite Functions

Multisite-enabled WordPress installations contain additional functions and features that theme developers can use to improve the experience of a website. If you find yourself developing themes and plugins for WordPress Multisite, consider the following tips to customize and improve the connectivity of the network.

Displaying Information About a Network

You might find yourself in a situation where you would like to display the number of websites or users in your network. Providing a link to the network’s primary website would also be nice, so that visitors can learn more about your organization.

Multisite stores global options in the wp_sitemeta database table, such as the network’s name (site_name), the administrator’s email address (admin_email) and the primary website’s URL (siteurl). To access these options, you can use the get_site_option() function.

In this example, I’ve used the get_site_option() function along with get_blog_count() and get_user_count() to display a sentence with details about a network.

<?php if( is_multisite() ): ?>

   The <?php echo esc_html( get_site_option( 'site_name' ) ); ?> network currently powers <?php echo get_blog_count(); ?> websites and <?php echo get_user_count(); ?> users.

<?php endif; ?>

This small snippet of code will display the following HTML:

The Smashing Magazine network currently powers 52 websites and 262 users.

Many useful Multisite functions can be found in the /wp-includes/ms-functions.php file. I highly suggest browsing the Trac project yourself. It’s a great way to find new functions and to become familiar with WordPress coding standards.

Build a Network Navigation Menu

Many networks have consistent dynamic navigation that appears on all websites, making it easy for visitors to browse the network. Using the $wpdb database class, along with the get_site_url(), home_url(), get_current_blog_id(), switch_to_blog() and restore_current_blog() functions, we can create a fully dynamic network menu, including a class (.current-site-item) to highlight the current website.

The SQL query we’ve created in this example has the potential to become very large, possibly causing performance issues. For this reason, we’ll use the Transients API, which enables us to temporarily store a cached version of the results as network website “transients� in the sitemeta table using the set_site_transient() and get_site_transient() functions.

Transients provide a simple and standardized way to store cached data in the database for a set period of time, after which the data expires and is deleted. It’s very similar to storing information with the Options API, except that it has the added value of an expiration time. Transients are also sped up by caching plugins, whereas normal options aren’t. Due to the nature of the expiration process, never assume that a transient is in the database when writing code.

The SQL query will run every two hours, and the actual data will be returned from the transient, making things much more efficient. I’ve included two parameters, $size and $expires, allowing you to control the number of posts returned and the expiration time for the transient.

One of the most powerful elements of this example is the use of switch_to_blog() and restore_current_blog(). These two Multisite functions enable us to temporarily switch to another website (by ID), gather information or content, and then switch back to the original website.

Add the following to your theme’s functions.php file:

/**
 * Build a list of all websites in a network
 */
function wp_list_sites( $expires = 7200 ) {
   if( !is_multisite() ) return false;

   // Because the get_blog_list() function is currently flagged as deprecated
   // due to the potential for high consumption of resources, we'll use
   // $wpdb to roll out our own SQL query instead. Because the query can be
   // memory-intensive, we'll store the results using the Transients API
   if ( false === ( $site_list = get_transient( 'multisite_site_list' ) ) ) {
      global $wpdb;
      $site_list = $wpdb->get_results( $wpdb->prepare('SELECT * FROM wp_blogs ORDER BY blog_id') );
      // Set the Transient cache to expire every two hours
      set_site_transient( 'multisite_site_list', $site_list, $expires );
   }

   $current_site_url = get_site_url( get_current_blog_id() );

   $html = '
    ' . "\n"; foreach ( $site_list as $site ) { switch_to_blog( $site->blog_id ); $class = ( home_url() == $current_site_url ) ? ' class="current-site-item"' : ''; $html .= "\t" . '
  • ' . get_bloginfo('name') . '
  • ' . "\n"; restore_current_blog(); } $html .= '
' . "\n\n"; return $html; }

(Please note: The get_blog_list() function is currently deprecated due to the potential for a high consumption of resources if a network contains more than 1000 websites. Currently, there is no replacement function, which is why I have used a custom $wpdb query in its place. In future, WordPress developers will probably release a better alternative. I suggest checking for a replacement before implementing this example on an actual network.)

This function first verifies that Multisite is enabled and, if it’s not, returns false. First, we gather a list of IDs of all websites in the network, sorting them in ascending order using our custom $wpdb query. Next, we iterate through each website in the list, using switch_to_blog() to check whether it is the current website, and adding the .current-site-item class if it is. Then, we use the name and link for that website to create a list item for our menu, returning to the original website using restore_current_blog(). When the loop is complete, we return the complete unordered list to be outputted in our theme. It’s that simple.

To use this in your theme, call the wp_list_sites() function where you want the network menu to be displayed. Because the function first checks for a Multisite-enabled installation, you should verify that the returned value is not false before displaying the corresponding HTML.

<?php
// Multisite Network Menu
$network_menu = wp_list_sites();
if( $network_menu ):
?>
<?php echo $network_menu; ?>
<?php endif; ?>

List Recent Posts Across an Entire Network

If the websites in your network share similar topics, you may want to create a list of the most recent posts across all websites. Unfortunately, WordPress does not have a built-in function to do this, but with a little help from the $wpdb database class, you can create a custom database query of the latest posts across your network.

This SQL query also has the potential to become very large. For this reason, we’ll use the Transients API again in a method very similar to what is used in the wp_list_sites() function.

Start by adding the wp_recent_across_network() function to your theme’s functions.php file.

/**
 * List recent posts across a Multisite network
 *
 * @uses get_blog_list(), get_blog_permalink()
 *
 * @param int $size The number of results to retrieve
 * @param int $expires Seconds until the transient cache expires
 * @return object Contains the blog_id, post_id, post_date and post_title
 */
function wp_recent_across_network( $size = 10, $expires = 7200 ) {
   if( !is_multisite() ) return false;

   // Cache the results with the WordPress Transients API
   // Get any existing copy of our transient data
   if ( ( $recent_across_network = get_site_transient( 'recent_across_network' ) ) === false ) {

      // No transient found, regenerate the data and save a new transient
      // Prepare the SQL query with $wpdb
      global $wpdb;

      $base_prefix = $wpdb->get_blog_prefix(0);
      $base_prefix = str_replace( '1_', '' , $base_prefix );

      // Because the get_blog_list() function is currently flagged as deprecated
      // due to the potential for high consumption of resources, we'll use
      // $wpdb to roll out our own SQL query instead. Because the query can be
      // memory-intensive, we'll store the results using the Transients API
      if ( false === ( $site_list = get_site_transient( 'multisite_site_list' ) ) ) {
         global $wpdb;
         $site_list = $wpdb->get_results( $wpdb->prepare('SELECT * FROM wp_blogs ORDER BY blog_id') );
         set_site_transient( 'multisite_site_list', $site_list, $expires );
      }

      $limit = absint($size);

      // Merge the wp_posts results from all Multisite websites into a single result with MySQL "UNION"
      foreach ( $site_list as $site ) {
         if( $site == $site_list[0] ) {
            $posts_table = $base_prefix . "posts";
         } else {
            $posts_table = $base_prefix . $site->blog_id . "_posts";
         }

         $posts_table = esc_sql( $posts_table );
         $blogs_table = esc_sql( $base_prefix . 'blogs' );

         $query .= "(SELECT $posts_table.ID, $posts_table.post_title, $posts_table.post_date, $blogs_table.blog_id FROM $posts_table, $blogs_table\n";
         $query .= "\tWHERE $posts_table.post_type = 'post'\n";
         $query .= "\tAND $posts_table.post_status = 'publish'\n";
         $query .= "\tAND $blogs_table.blog_id = {$site->blog_id})\n";

         if( $site !== end($site_list) )
            $query .= "UNION\n";
         else
            $query .= "ORDER BY post_date DESC LIMIT 0, $limit";
      }

      // Sanitize and run the query
      $query = $wpdb->prepare($query);
      $recent_across_network = $wpdb->get_results( $query );

      // Set the Transients cache to expire every two hours
      set_site_transient( 'recent_across_network', $recent_across_network, 60*60*2 );
   }

   // Format the HTML output
   $html = '


';

   return $html;
}

Using this function in your theme is simple. Be certain to check the return value before outputting HTML to avoid conflicts with non-Multisite installations.

<?php
// Display recent posts across the entire network
$recent_network_posts = wp_recent_across_network();
if( $recent_network_posts ):
?>
<?php echo $recent_network_posts; ?>
<?php endif; ?>

Retrieve a Single Post from Another Website in the Network

In certain situations, you may find it useful to refer to a single page, post or post type from another website in your network. The get_blog_post() function makes this process simple.

For example, you may want to display the_content() from an “About� page on the primary website in your network.

<?php
// Display "About" page content from the network's primary website
$about_page = get_blog_post( 1, 317 );
if( $about_page ):
?>
<?php echo $about_page->post_content; ?>
<?php endif; ?>

Did you notice that the entire $post object is returned? In this example, we’ve used only the_content(), but far more information is available for other circumstances.

Set Up Global Variables Across a Network

Starting any WordPress project in a solid local development environment is always important. You might find it handy to have a global variable that determines whether a website is “live� or “staging.� In Multisite, you can achieve this using a network-activated plugin that contains the following handy function, assuming that your local host contains localhost in the URL:

/**
 * Define network globals
 */
function ms_define_globals() {
   global $blog_id;
   $GLOBALS['staging'] = ( strstr( $_SERVER['SERVER_NAME'], 'localhost' ) ) ? true : false;
}
add_action( 'init', 'ms_define_globals', 1 );

When would you use this $staging variable? I use it to display development-related messages, notifications and information to improve my workflow.

Display the Page Request Information in a Local Environment

I use the $staging global variable to display the number of queries and page-request speed for every page across a network in my local environment.

/**
 * Display page request info
 *
 * @requires $staging
 */
function wp_page_request_info() {
   global $staging;
   if ( $staging ): ?>
      <?php echo get_num_queries(); ?> queries in <?php timer_stop(1); ?> seconds.
   <?php endif;
}
add_action( 'wp_footer', 'wp_page_request_info', 1000 );

This is only one of many ways you can use the ms_define_globals() function. I’ve used it to define, find and replace URLs in the content delivery network, to detect mobile devices and user agents, and to filter local attachment URLs.

Conclusion

There is tremendous value in the simplicity of managing multiple websites in a single installation of WordPress. Leveraging WordPress Multisite is quickly becoming a requisite skill among WordPress developers. These techniques should provide a solid foundation for you to build on, so that you can be the next WordPress Multisite theme rock star!

Other Resources

(al)


© Kevin Leary for Smashing Magazine, 2011.


  •   
  • Copyright © 1996-2010 BlogmyQuery - BMQ. All rights reserved.
    iDream theme by Templates Next | Powered by WordPress