Sorting Categories by Custom Sort Order

A client recently asked me to change the order of a list of categories displayed in a sidebar. I opened the proper file, located the call to get_terms that collected the terms, and finally visited the Codex to find the proper argument to alter. To my astonishment, there was no way to order the category terms in a user specified manner. The terms could only be ordered by name or ID. It then dawned on me that, of course there is no argument that would sort the terms by a user specified order; there is nothing in the category admin that allows the user to even specify an order in the first place. Menus, widgets, and pages all have drag and drop or input fields that allow the user to specify the order to display the particular items. Within the category management, there is no way to do this. I was able to solve this problem, but it was much more complex than I initially thought it would be. (Note: If you are only interested in
quickly being able to sort your taxonomy terms, jump to the bottom of the page for a link to a plugin that will handle this for you.
)

This tutorial will show you how to sort categories by a specific user determined order. This tutorial will first teach you how to add the metadata to each category to specify the order and then how to filter the get_terms function to order the terms in the desired manner.

Creating the User Input Fields

To begin, we need to add a way for users to specify the order of the categories. WordPress pages manage page order with a text input called “Order” in each page.

Page Order

I will be using this as a model for handling the category order in this tutorial. In order to accomplish this, term metadata needs to be added to the category terms. For a primer on adding metadata to taxonomy terms, please refer to my previous article on the subject.

To begin, install the excellent Simple Term Meta plugin. This plugin will add a table to the WordPress database to manage term metadata, as well as make available a number of easy to use functions that make dealing with term metadata a breeze.

First, hook functions that display the order inputs on the category add and edit pages.

1
2
add_action('category_add_form_fields', 'category_metabox_add', 10, 1);
add_action('category_edit_form_fields', 'category_metabox_edit', 10, 1);

This code will place the contents of the category_metabox_add function in the “Add New Category” screen and the contents of the category_metabox_edit function in the “Edit Category” screen. The next step is to write these functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function category_metabox_add($tag) 
{
?>
	<div class="form-field">
		<label for="tax-order"></label>
 
		<p class="description"></p>
	</div>
 
	<tr class="form-field">
		<th scope="row" valign="top">
			<label for="tax-order"></label>
		</th>
		<td>
			<input name="tax-order" id="tax-order" type="text" value="term_id, 'tax-order', true); ?>" size="40" aria-required="true" />
			<p class="description"></p>
		</td>
	</tr>
<?php
}
 
function category_metabox_edit($tag) 
{
?>
	<tr class="form-field">
		<th scope="row" valign="top">
			<label for="tax-order"></label>
		</th>
		<td>
			<input name="tax-order" id="tax-order" type="text" value="term_id, 'tax-order', true); ?>" size="40" aria-required="true" />
			<p class="description"></p>
		</td>
	</tr>
<?php
}

These two functions add a new text input field to the Add and Edit Categories screens. The last thing needed is to save the “Order” fields on submit. This is accomplished by first hooking a save function to run when the categories are added or edit.

1
2
add_action('created_category', 'save_category_meta_data', 10, 1);	
add_action('edited_category', 'save_category_meta_data', 10, 1);

And second, by defining that function.

1
2
3
4
5
function save_category_meta_data($term_id)
{
    if (isset($_POST['tax-order'])) 
		update_term_meta( $term_id, 'tax-order', $_POST['tax-order']);       
}

If everything is working correctly, a new field in the “Add New Category” screen will be available.

Page Order

Additionally, a new field in the “Edit Category” screen will be available.

At this point, order metadata can be associated with each category. It can be added at the time of adding or editing a category. As a test example, imagine entering the following three categories:

  • Animals
  • Instruments
  • People

By default, WordPress will sort these categories by ID. For this example, imagine entering these categories in the following order:

  1. Instruments
  2. Animals
  3. People

The list would be sorted in that manner. Let’s imagine that in this example the desired sort order is:

  1. People
  2. Animals
  3. Instruments

To prepare for this sort, enter the “Order” metadata for each term as shown in the image below.

The order metadata will not have an effect on the current sorting of the variables until the next step is complete.

Modifying get_terms To Sort By Order

Now that the order has been specified, modification of the get_terms function can be made via a filter in order to change the sort order of the terms. Immediately prior to returning the terms in the get_terms, the “get_terms” filter is applied. As such, this offers an ideal place to alter the order of the terms.

To begin, a function that does this sorting needs to be associated with the filter.

1
add_filter('get_terms', 'custom_term_sort', 10, 3);

Prior to returning the terms in the call to the get_terms function, the custom_term_sort function will be executed. This function will be responsible for sorting the terms according to the order metadata. The following two function handle this heavy lifting.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
function custom_term_sort($terms, $taxonomies, $args)
{		
	// Controls behavior when get_terms is called at unusual times resulting in a terms array without objects
	$empty = false;
 
	// Create collector arrays
	$ordered_terms = array();
	$unordered_terms = array();
 
	// Add taxonomy order to terms
	foreach($terms as $term)
	{
		// Only set tax_order if value is an object
		if(is_object($term))
		{
			if($taxonomy_sort = get_term_meta($term->term_id, 'tax-order', true))
			{
				$term->tax_order = (int) $taxonomy_sort;
				$ordered_terms[] = $term;
			}
			else
			{
				$term->tax_order = (int) 0;
				$unordered_terms[] = $term;
			}
		}
		else
			$empty = true;
	}
 
	// Only sort by tax_order if there are items to sort, otherwise return the original array
	if(!$empty && count($ordered_terms) > 0)
		quickSort($ordered_terms);
	else
		return $terms;
 
	// Combine the newly ordered items with the unordered items and return
	return array_merge($ordered_terms, $unordered_terms);	
}
 
function quickSort(&$array)
{
	$cur = 1;
	$stack[1]['l'] = 0;
	$stack[1]['r'] = count($array)-1;
 
	do
	{
		$l = $stack[$cur]['l'];
		$r = $stack[$cur]['r'];
		$cur--;
 
		do
		{
			$i = $l;
			$j = $r;
			$tmp = $array[(int)( ($l+$r)/2 )];
 
			do
			{
				while( $array[$i]->tax_order tax_order )
				$i++;
 
				while( $tmp->tax_order tax_order )
			 	$j--;
 
				// swap elements from the two sides
				if( $i <= $j)
				{
					 $w = $array[$i];
					 $array[$i] = $array[$j];
					 $array[$j] = $w;
 
			 		$i++;
			 		$j--;
				}
 
			}while( $i <= $j );
 
			if( $i < $r )
			{
				$cur++;
				$stack[$cur]['l'] = $i;
				$stack[$cur]['r'] = $r;
			}
			$r = $j;
 
		}while( $l < $r );
 
	}while( $cur != 0 );
}

There is a lot going on here, so let me explain this step by step.

The first bit of code ensures that errors are not thrown when get_terms is called in unusual places. Then, dummy arrays are set up to collect and differentiate terms that have order metadata from those without order metadata.

1
2
3
4
5
6
// Controls behavior when get_terms is called at unusual times resulting in a terms array without objects
$empty = false;
 
// Create collector arrays
$ordered_terms = array();
$unordered_terms = array();

Next, a loop is initiated that cycles through the terms.

1
2
// Add taxonomy order to terms
foreach($terms as $term)

The code then tests to make sure that the term is an object. Since get_terms returns the terms as objects, something has gone seriously wrong if any term is not an object. As such, the else clause to the if statement sets the $empty variable to true which will later control what is returned from the function. The structure of this check is as follows.

1
2
3
4
5
6
7
// Only set tax_order if value is an object
if(is_object($term))
{
 
}
else
	$empty = true;

Now, if the term is an object, the “tax-order” property is added to it; however, before simply adding this property to the object, a test is done to ensure that “tax-order” metadata exists. If it does exist, the property is added and the term is added to the $ordered_terms array. If not, the “tax-order” property is added to the object with a value of “0″ and the term is added to the $unordered_terms because no order has been specified for the term. It is important to make sure to properly handle the terms that do not have an ordered specified to ensure that they are not lost when get_terms is filtered.

1
2
3
4
5
6
7
8
9
10
if($taxonomy_sort = get_term_meta($term->term_id, 'tax-order', true))
{
	$term->tax_order = (int) $taxonomy_sort;
	$ordered_terms[] = $term;
}
else
{
	$term->tax_order = (int) 0;
	$unordered_terms[] = $term;
}

The next bit of code finally sorts the array of objects by the “tax-order” property, but before doing so, two important checks are performed. First, if the $empty variable is “true”, the array of terms is not sorted. If you recall, the $empty variable is set to “true” if just one of the terms is not an object. If this is the case, the original array of terms is returned and the custom sort is not performed. The next check ensures that there are term objects to sort. If at least 1 term has the “tax-order” property, and has subsequently been placed in the $ordered_terms array, the sort will be performed. If not, the original array of terms is returned. If the two conditions are met, the quickSort function is executed on the array of terms. I will not discuss the quickSort function other than mention that it is an excellent function written by Paul Dixon that sorts an array of objects by a property of the object.

1
2
3
4
5
// Only sort by tax_order if there are items to sort, otherwise return the original array
if(!$empty && count($ordered_terms) > 0)
	quickSort($ordered_terms);
else
	return $terms;

Finally, the array of ordered items is merged with the array of unordered items and returned.

1
2
// Combine the newly ordered items with the unordered items and return
return array_merge($ordered_terms, $unordered_terms);

With that, the categories are sorted using the order metadata. As such, the original example would now sort as specified into the following order:

  1. Instruments
  2. Animals
  3. People

Adding This To Your Project

To make your life a little easier, you can simply grab the code from pastebin, which is ready to be copied and pasted to a functions.php file or a plugin. Just remember to install the Simple Term Meta plugin in order for the code to work.

And to make your life even easier, I recently released a plugin that will do all of this and more for you. My Custom Taxonomy Sort handles adding an order field to each taxonomy page and automatically sorts the terms by the desired order. It works properly in WordPress 3.1 and 3.2 and is ready to download now!

This article was authored by:

I am a Senior Web Engineer at 10up LLC. While I am well versed in general web development, WordPress is my bread and butter. I enjoy working on WordPress projects, writing plugins, and helping other learn about appreciate how incredible the WordPress software is. After about 8 years doing web development, I have finally launched my own website. I’d like to think it’s because I finally feel that I have something to say, but really, it’s actually because I was finally able to design a theme that I did not loathe.

Zack Tollman has authored 6 posts.Visit Website

Showing 2 Comments

  • ChellaDurai1

    This is very nice plugin and useful to me. Thanks.

    REPLY
  • thank you for this tutorial. i’ll be using the custom_term_sort and quicksort functions in my newest project.

    REPLY

Add Your Voice: