The trickiest part of the latest updates to Twenty Links was getting the tag suggest function to work for users who aren’t logged in. I’m pretty much a n00b when it comes to jQuery — JavaScript in general has always been my weakest skill — so I went looking for a handy tutorial, and I found one: Using WordPress built-in tag auto complete script in your Plugins, posted a couple of years ago by Sudar Muthu. That did the trick — but only for logged-in users.
Why it doesn’t work
WordPress handles jQuery requests using the /wp-admin/admin-ajax.php
file. Here’s the catch:
The only cave[a]t to this method is that right now the
admin-ajax.php
file needs you to be logged in and therefore can only be used in admin pages. But in WordPress 2.9 even anonymous users can loadadmin-ajax.php
file. If you need use auto tag completing in blog pages, then you may have to wait till 2.9 is released.
Obviously that was written before 2.9 came out, so it’s not quite accurate. As it turned out, admin-ajax.php
does handle requests from anonymous users — but it kicks them back out to a callback function you specify. I didn’t quite understand what was going on until I actually opened the file and read the code. Here’s the relevant bit:
if ( ! is_user_logged_in() ) { // [some stuff having to do with autosave] if ( !empty( $_REQUEST['action'] ) ) do_action( 'wp_ajax_nopriv_' . $_REQUEST['action'] ); die('-1'); }
Translation: if the user isn’t logged in, call functions hooked to the relevant action prefixed with wp_ajax_nopriv_
. If there are no such functions, fail with an error message of -1.
Upshot: your users see this.
So, in order to get this working for anonymous visitors, you have to use that wp_ajax_nopriv_[something]
action.
How it’s done for authenticated users
Reading a little further down in admin-ajax.php
, I found the bit that handles tag suggestions:
switch ( $action = $_GET['action'] ) : // [other stuff] case 'ajax-tag-search' : if ( isset( $_GET['tax'] ) ) { $taxonomy = sanitize_key( $_GET['tax'] ); $tax = get_taxonomy( $taxonomy ); if ( ! $tax ) die( '0' ); if ( ! current_user_can( $tax->cap->assign_terms ) ) die( '-1' ); } else { die('0'); } $s = stripslashes( $_GET['q'] ); if ( false !== strpos( $s, ',' ) ) { $s = explode( ',', $s ); $s = $s[count( $s ) - 1]; } $s = trim( $s ); if ( strlen( $s ) < 2 ) die; // require 2 chars for matching $results = $wpdb->get_col( $wpdb->prepare( "SELECT t.name FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.name LIKE (%s)", $taxonomy, '%' . like_escape( $s ) . '%' ) ); echo join( $results, "n" ); die; break;
OK, so in order to avoid all those die()
conditions, we need to set the action to ajax-tag-search
, and the taxonomy has to be defined. I looked at Sudar’s code again. Sure enough, the query string sent to jQuery.suggest()
includes both those things: ?action=ajax-tag-search&tax=post_tag
. It’s only failing because the user never makes it this far if they’re not logged in. Even if they did get here, this code does an additional check to see if the user can assign tags: if (!current_user_can($tax->cap->assign_terms))
. We’ll need to get rid of that in the anonymous version of the function.
Duplicating the function for visitors
I went back to reading about the nopriv
callbacks. What I needed to do was define the function that would fetch tags from the database, just like the code in admin-ajax.php
, and hook it to the wp_ajax_nopriv_ajax-tag-search
action.
Here’s the code I added to the functions.php
file, just after Sudar’s code:
// autocompletion for non-logged-in users add_action('wp_ajax_nopriv_ajax-tag-search', 'add_autosuggest_20links_callback'); // cribbed from admin-ajax.php function add_autosuggest_20links_callback() { global $wpdb; if ( isset( $_GET['tax'] ) ) { $taxonomy = sanitize_key( $_GET['tax'] ); $tax = get_taxonomy( $taxonomy ); if ( ! $tax ) die( '0' ); } else { die('0'); } $s = stripslashes( $_GET['q'] ); if ( false !== strpos( $s, ',' ) ) { $s = explode( ',', $s ); $s = $s[count( $s ) - 1]; } $s = trim( $s ); if ( strlen( $s ) < 2 ) die; // require 2 chars for matching $results = $wpdb->get_col( $wpdb->prepare( "SELECT t.name FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.name LIKE (%s)", $taxonomy, '%' . like_escape( $s ) . '%' ) ); echo join( $results, "n" ); }
It’s exactly the same as the section from admin-ajax.php
, minus the capability check and the surrounding switch
.
And it works! See it in action on my links site. Grab the Twenty Links theme to see the completed functions.php
file.
Jared says
I’m just letting you know in advance that I plan on stealing this for Tasty ;)
Stephanie says
I expected you would!
Alex says
Great!!! I was looking exactly for this. Thank you!