Resetting Your Github Fork

Let’s say I want to contribute to a project on github. The project repository is at wp-cli/wp-cli. First I fork it, and then clone the resulting repository, scribu/wp-cli:

git clone --recursive git@github.com:scribu/wp-cli.git
cd wp-cli

Now, I make some commits to master, push them to my fork and open a pull request. Piece of cake:

git commit -m "awesome new feature"
git push

But, what happens if my pull request is rejected or only certain commits are accepted? I’m left with a dirty master branch. Oh noes!

There are two ways of solving this:

A. Delete my fork and create it again via the github interface. Can’t get any easier than that.

B. Use git reset:

git remote add upstream git://github.com/wp-cli/wp-cli.git
git fetch upstream
git branch backup
git reset --hard upstream/master
git push --force

If I made a mistake, I can rescue my commits by calling git checkout backup.

Github Tricks

What I’m about to show you are functionalities that I’ve actually needed at one point or another, but which aren’t featured proeminently in the github interface.

In both cases, start at a repository’s home page. For example:

https://github.com/andreascreten/wp-cli

See commits from a certain user

  1. Click the Stats & Graphs button.
  2. Click the Contributors tab.
  3. Click the name of the contributor you’re interested in.

You’ll end up with an URL like this:

https://github.com/andreascreten/wp-cli/commits/master?author=scribu

See issues assigned to a certain user

In Github Issues, it’s really easy to see tickets assigned to yourself, but no apparent way to view tickets assigned to others. There is a method, but it’s rather convoluted:

  1. Click the Issues button.
  2. Do a random search using the Issues & Milestones search box.
  3. Select “Assigned to” user from the dropdown on the left.

The final URL will be:

https://github.com/andreascreten/wp-cli/issues?assignee=andreascreten&state=all

For more hidden goodness, see GitHub Secrets and Git and GitHub Secrets.

vim and the Ubuntu clipboard

Two of my friends have recently become vim fanatics. I played with vim a little before, but now I’ve made it my primary editor.

One thing that bugged me was that, by default, you are not able to copy text from vim into other applications in Ubuntu. To fix this, you just need to do:

sudo apt-get install vim-gnome

Then, you can visually select a block of text and yank it to the clipboard using the "+y command.

To paste the contents of the clipboard into vim, you would use the "+p command.

Source: http://ubuntuforums.org/showthread.php?t=106262

Sortable Taxonomy Columns

After finding out how to make sortable columns based on custom fields, several people have asked how they could sort posts by taxonomy terms. So here’s how to do it:

Let’s presume we have a ‘product’ post type, with a ‘color’ taxonomy. Here’s some example code, just to get that out of the way.

So we already have a sortable ‘Color’ column, except clicking on it sorts posts by date. Let’s fix that.

Orderby subquery

function color_orderby( $orderby, $wp_query ) {
	global $wpdb;
 
	if ( isset( $wp_query->query['orderby'] ) && 'color' == $wp_query->query['orderby'] ) {
		$orderby = "(
			SELECT GROUP_CONCAT(name ORDER BY name ASC)
			FROM $wpdb->term_relationships
			INNER JOIN $wpdb->term_taxonomy USING (term_taxonomy_id)
			INNER JOIN $wpdb->terms USING (term_id)
			WHERE $wpdb->posts.ID = object_id
			AND taxonomy = 'color'
			GROUP BY object_id
		) ";
		$orderby .= ( 'ASC' == strtoupper( $wp_query->get('order') ) ) ? 'ASC' : 'DESC';
	}
 
	return $orderby;
}
add_filter( 'posts_orderby', 'color_orderby', 10, 2 );

Even if they have more than one term associated, the posts will be sorted correctly.

However, the query is not very efficient, since we’re basically doing a SELECT for each post. It will get very slow if you have thousands of posts. Let’s see if we can do better.

Derived table

function color_clauses( $clauses, $wp_query ) {
	global $wpdb;
 
	if ( isset( $wp_query->query['orderby'] ) && 'color' == $wp_query->query['orderby'] ) {
		$clauses['join'] .= " LEFT JOIN (
			SELECT object_id, GROUP_CONCAT(name ORDER BY name ASC) AS color
			FROM $wpdb->term_relationships
			INNER JOIN $wpdb->term_taxonomy USING (term_taxonomy_id)
			INNER JOIN $wpdb->terms USING (term_id)
			WHERE taxonomy = 'color'
			GROUP BY object_id
		) AS color_terms ON ($wpdb->posts.ID = color_terms.object_id)";
		$clauses['orderby'] = 'color_terms.color ';
		$clauses['orderby'] .= ( 'ASC' == strtoupper( $wp_query->get('order') ) ) ? 'ASC' : 'DESC';
	}
 
	return $clauses;
}
add_filter( 'posts_clauses', 'color_clauses', 10, 2 );

This is apparently faster, since we’re getting all the terms at once, but it still won’t scale, because a temporary table has to be created, which takes longer and longer, the more connections between posts and terms you have.

Direct JOINs

Mike Schinkel droped by and left an improved method in the comments. I just cleaned it up a bit and made it work with posts that don’t have any terms associated. Enjoy:

function color_clauses_mike( $clauses, $wp_query ) {
	global $wpdb;
 
	if ( isset( $wp_query->query['orderby'] ) && 'color' == $wp_query->query['orderby'] ) {
 
		$clauses['join'] .= <<<SQL
LEFT OUTER JOIN {$wpdb->term_relationships} ON {$wpdb->posts}.ID={$wpdb->term_relationships}.object_id
LEFT OUTER JOIN {$wpdb->term_taxonomy} USING (term_taxonomy_id)
LEFT OUTER JOIN {$wpdb->terms} USING (term_id)
SQL;
 
		$clauses['where'] .= " AND (taxonomy = 'color' OR taxonomy IS NULL)";
		$clauses['groupby'] = "object_id";
		$clauses['orderby']  = "GROUP_CONCAT({$wpdb->terms}.name ORDER BY name ASC) ";
		$clauses['orderby'] .= ( 'ASC' == strtoupper( $wp_query->get('order') ) ) ? 'ASC' : 'DESC';
	}
 
	return $clauses;
}
add_filter( 'posts_clauses', 'color_clauses_mike', 10, 2 );

WP-PageNavi: Version 2.74

Two new improvements come with this version:

Using wp_pagenavi() with custom queries

The old way (which still works):

query_posts( array( 'tag' => 'foo', 'paged' => get_query_var('paged') ) );
 
while ( have_posts() ) : the_post();
	the_title();
	// more stuff here
endwhile;
 
wp_pagenavi();
 
wp_reset_query();	// avoid errors further down the page

The new way (better, because it has less side-effects):

$my_query = new WP_Query( array( 'tag' => 'foo', 'paged' => get_query_var('paged') ) );
 
while ( $my_query->have_posts() ) : $my_query->the_post();
	the_title();
	// more stuff here
endwhile;
 
wp_pagenavi( array( 'query' => $my_query ) );
 
wp_reset_postdata();	// avoid errors further down the page

Notice that, in both cases, I included the ‘paged’ parameter. Without it, you would see the same posts on all pages.

‘smaller’ and ‘larger’ classes

Each link has now an additional ‘smaller’ or ‘larger’ class, depending on where it is, in relation to the current page.

For example, if you’re on page 2, the link to page 1 will have the ‘smaller’ class, while links to page 3, 4, 5 etc. will have the ‘larger’ class.

This allows even more customization via CSS.

Enjoy.

Inserting a banner between posts

It’s pretty easy to inject content between posts if you have direct access to The Loop in the theme:

$counter = 0;
 
while ( have_posts() ) : the_post();
 
	if ( 1 == $counter )
		echo '<div>Some banner</div>';
 
	$counter++;
 
	// the_title() etc.
 
endwhile;

If you want to do this without modifying the theme, the solution is not so obvious, but it exists:

function insert_between_posts( $post ) {
	global $wp_query;
 
	// Check if we're in the main loop
	if ( $wp_query->post != $post )
		return;
 
	// Check if we're at the right position
	if ( 1 != $wp_query->current_post )
		return;
 
	// Display the banner
	echo '<div>Some banner</div>';
}
add_action( 'the_post', 'insert_between_posts' );

Note that with this second approach, the banner will be displayed throughout the site. Fortunately, this can be easily controlled using conditional tags:

function insert_between_posts( $post ) {
	global $wp_query;
 
	// Check if we're in the right template
	if ( ! is_home() )
		return;
 
	// Check if we're in the main loop
	if ( $wp_query->post != $post )
		return;
 
	// Check if we're at the right position
	if ( 1 != $wp_query->current_post )
		return;
 
	// Display the banner
	echo '<div>Some banner</div>';
}
add_action( 'the_post', 'insert_between_posts' );

I hope this gives you some ideas for your next great plugin or child theme. Happy hacking!

Custom Sortable Columns

This summer I’ve been busy with my GSoC project, which involved making columns sortable on list-type screens in the WordPress administration area. Yesterday, that work was included in trunk, so if you’re on the bleeding edge (3.1-alpha), you should see something like this:

Sortable post columns

That’s all very nice, you say, but how do I make sortable columns of my own? First, let’s make a plain, old, non-sortable column:

// Register the column
function price_column_register( $columns ) {
	$columns['price'] = __( 'Price', 'my-plugin' );
 
	return $columns;
}
add_filter( 'manage_edit-post_columns', 'price_column_register' );

Nothing new here. We’re just using a well-known hook to add a Price column on the posts screen. Then, with another hook, we’re displaying the value, which is stored in a custom field in this case:

// Display the column content
function price_column_display( $column_name, $post_id ) {
	if ( 'price' != $column_name )
		return;
 
	$price = get_post_meta($post_id, 'price', true);
	if ( !$price )
		$price = '<em>' . __( 'undefined', 'my-plugin' ) . '</em>';
 
	echo $price;
}
add_action( 'manage_posts_custom_column', 'price_column_display', 10, 2 );

Now comes the interesting part:

// Register the column as sortable
function price_column_register_sortable( $columns ) {
	$columns['price'] = 'price';
 
	return $columns;
}
add_filter( 'manage_edit-post_sortable_columns', 'price_column_register_sortable' );

We first need to tell WordPress that this is a sortable column. As you can see, the hook name is very similar to the one we used to register the column in the first place. 1

There’s just one more thing we need to do. Since WordPress doesn’t know how to handle ‘orderby=price’, we’ll have to teach it, by altering the query variables:

function price_column_orderby( $vars ) {
	if ( isset( $vars['orderby'] ) && 'price' == $vars['orderby'] ) {
		$vars = array_merge( $vars, array(
			'meta_key' => 'price',
			'orderby' => 'meta_value_num'
		) );
	}
 
	return $vars;
}
add_filter( 'request', 'price_column_orderby' );

We’re basically telling it to order by the ‘price’ custom field we displayed before. 2

And that’s all there is to it. This is how it should look:

Result

Here’s the full code, for convenience: https://gist.github.com/906872

Update: With the CodePress Admin Columns plugin you can manage admin columns through a GUI.

Notes:

  1. The first ‘price’ represents the internal column name, while the second is the value sent to the ?orderby= query variable.
  2. More info on meta_value_num.

Yet Another “Open External Links In A New Window Using jQuery” Tutorial

In some cases, you need to prevent users from opening links to external sites in the same window.

And what’s the easiest way to do that? jQuery, of course! Here’s my take on it:

jQuery(document).delegate(‘a’, ‘click’, function() {
    var root = location.href.replace(location.pathname + location.search + location.hash, '');
 
    if ( !this.href ) return;
 
    if ( 0 != this.href.indexOf(root) ) {
        window.open(this.href);
        return false;
    }
});

What’s going on up there? Let’s break it down:

jQuery(document).delegate(‘a’, ‘click’, function() {

First, we’re using event delegation to bind a click handler to any <a> tag.

var root = location.href.replace(location.pathname + location.search + location.hash, '');

Now we’re calculating the root address, based on the current location. For example:

http://localhost:8080/path/?foo=bar#baz

will become

http://localhost:8080

    if ( !this.href ) return;

This is a safety check, for when there is no href attribute. this.href contains the absolute path for the clicked link.

    if ( 0 != this.href.indexOf(root) ) {
        window.open(this.href);
        return false;
    }

Finally, if the URL does not begin with the root we calculated before, it means it’s “external”. In this case, we open a new window / tab using window.open() and prevent the default action by returning false.

Update: Props to filosofo for pointing out that I should be using this.href instead of jQuery(this).attr('href').

Update 2: Use .delegate() instead of .live().

Extending the Category Walker

Categories are better than tags! Why? Because a category can have child categories, and grand-child categories and so on. You can do all sorts of interesting things with them.

Say you want to display only the categories related to a certain post, while maintaining the hierarchy.

You could try to painstakingly re-create the category tree from the bottom up and then display it somehow.

Or… you could choose to simply skip the categories you don’t want to have displayed. Makes sense? Read on.

Enter Walkers

A Walker is a special class for displaying various tree-like structures. WordPress uses them for displaying categories, pages and threaded comments.

The wp_list_categories() template tag uses one of them internally. It’s called Walker_Category. The nice bit is that you can replace the default walker with your own.

Walk Your Own Way

First we’ll need to make our own walker, using Walker_Category as a base:

class Post_Category_Walker extends Walker_Category {
 
	private $term_ids = array();
 
	function __construct( $post_id, $taxonomy )  {
		// fetch the list of term ids for the given post
		$this->term_ids = wp_get_post_terms( $post_id, $taxonomy, 'fields=ids' );
	}
 
	function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
		$display = false;
 
		$id = $element->term_id;
 
		if ( in_array( $id, $this->term_ids ) ) {
			// the current term is in the list
			$display = true;
		}
		elseif ( isset( $children_elements[ $id ] ) ) {
			// the current term has children
			foreach ( $children_elements[ $id ] as $child ) {
				if ( in_array( $child->term_id, $this->term_ids ) ) {
					// one of the term's children is in the list
					$display = true;
					// can stop searching now
					break;
				}
			}
		}
 
		if ( $display )
			parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );
	}
}

display_element() is a method in the Walker class. It’s called once for each element.

What happens here is that we first check if either the current element or one of its children is in the category list of the given post. If it is, we let Walker_Category do the actual displaying.

All that’s left to do now is pass our enhanced walker to wp_list_categories(). We’ll wrap it up in a function, for convenience:

function walk_post_categories( $post_id, $args = array() ) {
	$args = wp_parse_args( $args, array(
		'taxonomy' => 'category'
	) );
 
	$args['walker'] = new Post_Category_Walker( $post_id, $args['taxonomy'] );
 
	$output = wp_list_categories( $args );
	if ( $output )
		return $output;
}

And that’s about it. You now have a neat little template tag:

<?php walk_post_categories(123); ?>

You can even pass additional arguments to wp_list_categories:

<?php walk_post_categories(123, 'show_count=1&title_li='); ?>

Keep On Walking

Of course, this is just one example. You can use this approach with any type of walker. Let me know what other uses you’ve found.

WP-PageNavi: The Right Way To Use query_posts()

First rule of using query_posts(): don’t. Use the ‘request’ filter instead.

Ever since I’ve taken over development of the WP-PageNavi plugin, I keep seeing people opening support topics like this:

“PageNavi doesn’t show the correct posts”

“Page numbers don’t work with PageNavi”

etc.

Most of the time, your theme is just using query_posts() wrong.

So here is the correct way to make paging work using query_posts():

Say you have something like this:

query_posts('cat=8');

or like this:

query_posts( array( 'cat' => 8 ) );

If the file in question is a custom page template, replace it with this:

query_posts( array( 'cat' => 8, 'paged' => get_query_var('page') ) );

Otherwise, replace it with this:

query_posts( array( 'cat' => 8, 'paged' => get_query_var('paged') ) );

Note that pagination might not work correctly if you use query_posts() in other places besides page templates.

Also see the Codex page on query_posts().