I’ve been intrigued for a while by the fact the core wp_term_relationships
table has a term_order
column, as this hints that you could allow the user to explicitly order terms in a taxonomy. Today I did some investigation of this tantalising possibility.
If you dig through the wp-includes/taxonomy.php
functions you only see ordering by term_order
in two places; the first is that you can pass a value of term_order
for the orderby
index in the optional third parameter, args
, for wp_get_object_terms
, the second is that if the taxonomy has the sort
flag as a parameter then terms are set in the explicit order they are passed to wp_set_object_terms
(as long as the append
parameter is false, which it is by default). Interestingly the sort
parameter, which you can pass when calling register_taxonomy
to register your taxonomy, is not documented.
(By the way, I don’t think any of this is a great conspiracy, I think it’s just a part of the WordPress taxonomy API which is undeveloped and mostly unexposed.)
Updated I’ve made two proof of concept plugins, see below or in this gist. The first, Ordered Post Tags (ordered-post-tags.php
in the gist) overrides the core post tag taxonomy with an startlingly similar taxonomy which has that sort
flag set as true, and the second, Ordered Post Tags – API (ordered-post-tags-api.php
in the gist) provides some APIfunctions/template tags for use with the first.
You’ll notice that the plugin is complicated by the fact that the term ordering is not supported by any of the WordPress template tags or taxonomy API until you get to the fairly fundamental wp_get_object_terms
API function; this means I’ve duplicated an awful lot of code from core in the plugin. A patch to core could deal with this situation more elegantly by providing an additional argument to specify that you wanted terms returned/printed in term order.
Updated I’m contributing to trac ticket #9547 which, if accepted, will add the ability to output terms ordered by term_order
.
Ordered Post Tags plugin
<?php /* Plugin Name: Ordered Post Tags Plugin URI: https://gist.github.com/3217544 Description: Proof of concept plugin, not for production use. Converts post tags to an ordered taxonomy. Version: 0.1 Author: Simon Wheatley @ Code for the People Ltd Author URI: http://codeforthepeople.com */ /** * Copyright 2012 Code for the People Ltd. All rights reserved * Released under the GPL license * http://www.opensource.org/licenses/gpl-license.php * * This is an add-on for WordPress * http://wordpress.org/ * * ********************************************************************** * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * ********************************************************************** */ /** * Hooks the WordPress init action to interfere with the * post_tag taxonomy. * * @return void **/ function opt_init() { // Get the current args for the post_tag taxonomy $post_tag_tax = get_taxonomy( 'post_tag', ARRAY_A ); // Convert to an array $post_tag_args = opt_convert_to_array( $post_tag_tax ); // Now make it sortable $post_tag_args[ 'sort' ] = true; // Exploit a feature whereby you can overwrite a taxonomy :) register_taxonomy( 'post_tag', 'post', $post_tag_args ); } add_action( 'init', 'opt_init' ); /** * A utility function that recursively traverses an object * and turns it into an associative array. * * @param mixed $thing Might be an object, might not be * @return array A (possibly multidimensional) array if an object (or array) was passed in **/ function opt_convert_to_array( & $thing ) { $thing = get_object_vars( $thing ); if ( is_array( $thing ) ) foreach ( $thing as $key => & $_thing ) if ( is_object( $_thing ) ) $thing[ $key ] = opt_convert_to_array( $_thing ); return $thing; } ?>
Ordered Post Tags – API plugin
<?php /* Plugin Name: Ordered Post Tags – Template Tags & API Plugin URI: https://gist.github.com/3217544 Description: Proof of concept plugin, not for production use. Provides template tags and an API for those functions. Won't work without Ordered Post Tags POC plugin. Version: 0.1 Author: Simon Wheatley @ Code for the People Ltd Author URI: http://codeforthepeople.com */ /** * Copyright 2012 Code for the People Ltd. All rights reserved * Released under the GPL license * http://www.opensource.org/licenses/gpl-license.php * * This is an add-on for WordPress * http://wordpress.org/ * * ********************************************************************** * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * ********************************************************************** */ // NOW SOME TEMPLATING FUNCTIONALITY FOR DEMO PURPOSES // =================================================== // N.B. Lots of duplicate code follows, were we to come to implement this in core // it would be sensible to enhance the function equivalents of the functions // below to accept an additional argument(s) to specify term_order. /** * Retrieve the terms of the taxonomy that are attached to the post * in term_order. * * @param int $id Post ID. * @param string $taxonomy Taxonomy name. * @return array|bool False on failure. Array of term objects on success. */ function get_the_terms_ordered( $id, $taxonomy ) { global $post; $id = (int) $id; if ( !$id ) { if ( empty( $post->ID ) ) return false; else $id = (int) $post->ID; } $terms = get_object_term_cache( $id, $taxonomy ); $terms = wp_cache_get($id, $taxonomy . '_ordered_relationships'); if ( false === $terms ) { $terms = wp_get_object_terms( $id, $taxonomy, array( 'orderby' => 'term_order' ) ); wp_cache_add($id, $terms, $taxonomy . '_ordered_relationships'); } $terms = apply_filters( 'get_the_terms_ordered', $terms, $id, $taxonomy ); if ( empty( $terms ) ) return false; return $terms; } /** * Retrieve a post's terms as a list with specified format ordered by term_order. * * @param int $id Post ID. * @param string $taxonomy Taxonomy name. * @param string $before Optional. Before list. * @param string $sep Optional. Separate items using this. * @param string $after Optional. After list. * @return string */ function get_the_term_list_ordered( $id, $taxonomy, $before = '', $sep = '', $after = '' ) { $terms = get_the_terms_ordered( $id, $taxonomy ); if ( is_wp_error( $terms ) ) return $terms; if ( empty( $terms ) ) return false; foreach ( $terms as $term ) { $link = get_term_link( $term, $taxonomy ); if ( is_wp_error( $link ) ) return $link; $term_links[] = '<a href="' . esc_url( $link ) . '" rel="tag">' . $term->name . '</a>'; } $term_links = apply_filters( "term_links-$taxonomy", $term_links ); return $before . join( $sep, $term_links ) . $after; } /** * Retrieve the tags for a post formatted as a string ordered by term_order. * * @uses apply_filters() Calls 'the_tags' filter on string list of tags. * * @param string $before Optional. Before tags. * @param string $sep Optional. Between tags. * @param string $after Optional. After tags. * @param int $id Optional. Post ID. Defaults to the current post. * @return string */ function get_the_tag_list_ordered( $before = '', $sep = '', $after = '', $id = 0 ) { return apply_filters( 'the_tags', get_the_term_list_ordered( $id, 'post_tag', $before, $sep, $after ), $before, $sep, $after, $id ); } /** * Print the tags for a post in term_order. * * * @param string $before Optional. Before list. * @param string $sep Optional. Separate items using this. * @param string $after Optional. After list. * @return string */ function the_tags_ordered( $before = null, $sep = ', ', $after = '' ) { if ( null === $before ) $before = __('Tags: '); echo get_the_tag_list_ordered($before, $sep, $after); } ?>
I’d love to see this functionality more supported by core. Also take a look at the Term Menu Order plugin by jameslafferty (I added a few things to it as well)
@bill – Sorry to be a downer, but that plugin looks like it adds columns to the core WordPress tables which isn’t something I’d recommend in the normal course of events. As I’m demonstrating here with the two plugins (I split them up between your comment and this reply) the ordering functionality exists already in core, just buried a fair way down. (Not that I can criticise, I’m always creating functionality and discovering it’s already in core.)