Free Download

I started using The Taxonomy Sort plugin to allow for displaying posts by a custom term order on several sites not too long ago and discovered some issues in the way it was designed. Since I loved the slick, intuitive interface I decided to re-code the problem areas. If you’re interested in the what and why, read on. If you just want the updated version of the plugin, grab it at the top or bottom of this post. Thanks to Risto Niinemets for writing it in the first place and being kind enough to share it — it’s much easier to tweak a plugin than create one and he deserves the credit for this.

tl;dr

This WordPress Taxonomy Term Order plugin allows you to drag-and-drop posts into the order you would like them displayed.

A CASE OF MISTAKEN IDENTITY

I had been happily using the plugin for a good six months or so on a few sites when I ran into a problem — custom term orders suddenly disappeared or seemed to get rearranged. I’d go in and reorder them, everything would be fixed, and then it would randomly happen again. Finally I figured it was time to dig into the code and see what was happening.

When you drag-and-drop a term the plugin fires off an ajax call to the function inline_edit_boxes(), passing an array of term ids and display order. The inline_edit_boxes function then stores those as post meta fields. Post meta fields? But terms aren’t posts and aren’t stored in the posts table. And therein lies the problem. Suppose you have a term with a term id of 112. When you drag-and-drop it to the top of the term list the plugin fires off that ajax call and stores the new display order (1) in the postmeta table. Where postmeta expects a post_id it instead saves the term_id. On the flip side, when displaying terms it checks the postmeta table to see if there are term order meta values for the terms being shown.

The issue was when a post and term with the same ID exist. You update your wonderful post about cats and it messes up the term order for your post categories.

WP doesn’t offer built-in meta fields for terms. If you want to use them — for things like controlling display order — you have to add your own table.

To do that we use two functions: create_table() simply checks to see if the custom table already exists and if not, it creates it:

public static function create_table($blog_id=''){
		global $wpdb;
		$prefix = ($blogid) ? $wpdb->prefix.$blogid."_" : $wpdb->prefix;
		$table_name = "{$prefix}dojo_term_order";
		if($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
			require_once(ABSPATH.'/wp-admin/includes/upgrade.php');
			$sql = "CREATE TABLE ".$table_name."(
			meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			term_id bigint(20) unsigned NOT NULL DEFAULT 0,
			meta_key varchar(255) DEFAULT NULL,
			meta_value longtext,
			PRIMARY KEY  (meta_id),
			KEY  term_id (term_id),
			KEY  meta_key (meta_key) )";
			dbDelta($sql);
		}
	 }

Why use the meta_key => meta_value format when we could simply record a display_order for each term? Because if we’re going to create a custom table we might as well make it easily extensible. If you’ve got something else you want to do / track when it comes to terms, now you’ve got a place to store it.

The second function is called when the plugin is first initialized and it runs create table for each blog on a multi-site network (if you’re dealing with WP multisite) or just for the current site if you’re not.

function network_activate($networkwide) {
    global $wpdb;

if (is_multisite()==true) {

// check if it is a network activation – if so, run the activation function for each blog id
if ($networkwide) {
$old_blog = $wpdb->blogid;
// Get all blog ids
$blogids = $wpdb->get_col(“SELECT blog_id FROM $wpdb->blogs”);
foreach ($blogids as $blog_id) {
switch_to_blog($blog_id);
self::create_table();
}
switch_to_blog($old_blog);
return;
}
}
self::create_table();
}

WHENEVER POSSIBLE, SIMPLIFY

The original version of the plugin was hooking into the get_terms filter. That filter is called after WP has fetched an array of term objects, already filtered by any search criteria, sorted by the default order, etc. The plugin then reorders the terms via a loop and PHP’s ksort function.

There are two problems with that approach:

  1. It’s unnecessary overhead. Just prior to fetching the terms WP applies the terms_clauses filter, which takes as one of its arguments each of the pieces of the SELECT query — the FIELDS to display, the WHERE clause, the JOIN clause, etc. Rather than getting all the terms in their default order, then manipulating them, we can simply hook in here and modify the query so we get the terms in the order we want from the get go.
  2. As coded the plugin failed when using some of the parameters of the get_terms() function. For example, suppose you wanted to use the fields parameter’s id=>name option. You’d expect an associative array of term ids => term names, but what you got was an empty array. No bueno.

By using the terms_clauses filter we can join our custom terms order table and then order by the values stored there without otherwise affecting the query.

Here’s the original function:

function reorder_terms( $objects ) {
		// we do not need empty objects
		if( empty( $objects ) ) return $objects;

// placeholder for ordered objects
$placeholder = array();

// invalid key counter (if key is not set)
$invalid_key = 9000;

// loop through objects
foreach ( $objects as $key => $object ) {
// increase invalid key count
$invalid_key++;

// continue if no term_id
if( !isset( $object->term_id ) ) continue;

// get the order key
$term_order = get_post_meta( $object->term_id, ‘thets_order’, true );

// use order key if exists, invalid key if not
$term_key = ( $term_order != “” && $term_order != 0 ) ? (int)$term_order : $invalid_key;

// add object to placeholder by it’s key
$placeholder[$term_key] = $object;
}

// sort by keys
ksort( $placeholder );

// return sorted objects
return $placeholder;
}

And here’s what replaces it:

function dojo_reorder_terms($pieces){
		global $wpdb;
			$pieces['join'] .= " LEFT JOIN {$wpdb->prefix}dojo_term_order AS tto ON t.term_id = tto.term_id ";
			$pieces['orderby'] = 'ORDER BY tto.meta_value';
		return $pieces;
	}

A lot cleaner and simpler and you get the same end result.

IMPORTANT: As written above the new term ordering will apply to all terms in all places (admin and front end). If you have a list of post categories ordered by category name, for example, you’ll want to drag and drop them into alphabetical order so that their term_order values match. Alternatively you could easily remove the filter via your theme’s functions.php file, ensuring that it only applies to selected taxonomies. I’ll probably add in an option on the taxonomy editing screen to turn on/off ordering by term_order at some point in the near future. If that’s a feature you’re interested in, let me know in the comments below.

TO DO LIST

As noted above, the next obvious improvement is to add the ability to control where/when the custom display orders are applied. I also removed the uninstall hook for the moment. Deactivating or deleting the plugin will stop it from having any further effect, but it will also leave behind the custom table. In a future version it should probably ask if you want to permanently delete those tables.

Have other features you’d like to see added? Let me know in the comments below and I’ll add them to the road map.

Free Download