Problem with sorting entries in a plugin’s admin interface table

I’m developing a WordPress plugin that will display a publicly accessible noticeboard on a page.

I’ve been asked to add the means to delete existing posts from the noticeboard using the WordPress admin interface, and I’ve used this tutorial for guidance on creating the table to hold the details of each entry.

Read More

It’s working to the extent that I can display the table and move between pages, but there’s rather an odd error with the sorting. The hyperlink for sorting by each individual column only shows the first character of the column name in the orderby parameter, thus breaking this functionality.

For instance, the link for sorting by the description is

http://example.com/wordpress/wp-admin/options-general.php?page=Noticeboard&orderby=d&order=desc

and should be

http://example.com/wordpress/wp-admin/options-general.php?page=Noticeboard&orderby=description&order=desc

… which works.

Now, I’ve taken a look in wp-admin/includes/class-wp-list-table.php, in the print_column_headers method, and the problem appears to arise on line 679, which reads as follows:

list( $orderby, $desc_first ) = $sortable[$column_key];

If I do a var_dump of $sortable[$column_key], I get exactly the value I would expect, which in this case is a string with a value of "title", since that’s the first column I want to show. But the actual values held by $orderby and $desc_first are "t" and "i", so it appears list() is treating $sortable[$column_key] as an array and returning the first two elements.

I’m assuming this isn’t a bug in WordPress as I could not find any mention of a bug like this in the version of WordPress I’m using for development purposes, so I think the most likely explanation is that I’ve done something wrong somewhere along the line. It looks like where I define the get_sortable_columns method, I should be passing through an array containing two or more values as the value for each element in the $sortable associative array.

Here’s the code I’ve written for the admin:

<?php
// Retrieve any recaptcha keys sent via POST
if($_POST['recaptcha_public_key'])
{
    update_option('recaptcha_public_key', $_POST['recaptcha_public_key']);
}

if($_POST['recaptcha_private_key'])
{
    update_option('recaptcha_private_key', $_POST['recaptcha_private_key']);
}

// Create a class for the post list table
class Post_List_Table extends WP_List_Table {
    /*
     * Constructor
     */
    function __construct() {
        parent::__construct( array(
            'singular'=> 'wp_list_post',
            'plural' => 'wp_list_posts',
            'ajax' => false
        ) );
    }

    function get_columns() {
        return $columns = array(
            'title'=>__('Title'),
            'description'=>__('Description'),
            'submissionDate'=>__('Date submitted'),
            'name'=>__('Author name'),
            'email'=>__('Author email'),
            'post_title'=>__('Page this has been posted on')
        );
    }

    public function get_sortable_columns() {
        return $sortable = array(
            'title'=>'title',
            'description'=>'description',
            'submissionDate'=>'submissionDate',
            'name'=>'name',
            'email'=>'email',
            'post_title'=>'post_title'
        );
    }

    function prepare_items() {
        global $wpdb, $_wp_column_headers;
        $screen = get_current_screen();

        // Prepare query
        $query = "SELECT ".$wpdb->prefix."noticeboard_posts.title, ".$wpdb->prefix."noticeboard_posts.description, ".$wpdb->prefix."noticeboard_posts.submissionDate, ".$wpdb->prefix."noticeboard_author.name, ".$wpdb->prefix."noticeboard_author.email, ".$wpdb->prefix."posts.post_title FROM ".$wpdb->prefix."noticeboard_posts INNER JOIN ".$wpdb->prefix."noticeboard_author ON ".$wpdb->prefix."noticeboard_posts.authorId = ".$wpdb->prefix."noticeboard_author.authorId INNER JOIN ".$wpdb->prefix."posts ON ".$wpdb->prefix."noticeboard_posts.pageID = ".$wpdb->prefix."posts.ID";

        // Parameters that are going to be used to order the result
        $orderby = !empty($_GET["orderby"]) ? mysql_real_escape_string($_GET["orderby"]) : 'ASC';
        $order = !empty($_GET["order"]) ? mysql_real_escape_string($_GET["order"]) : '';
        if(!empty($orderby) & !empty($order)){ $query.=' ORDER BY '.$orderby.' '.$order; }

        // Parameters for pagination
        $totalitems = $wpdb->query($query);

        // How many to display per page?
        $perpage = 5;

        // Which page is this?
        $paged = !empty($_GET["paged"]) ? mysql_real_escape_string($_GET["paged"]) : '';

        // Page number
        if(empty($paged) || !is_numeric($paged) || $paged<=0 ){ $paged=1; }

        // How many pages do we have in total?
        $totalpages = ceil($totalitems/$perpage);

        //adjust the query to take pagination into account
        if(!empty($paged) && !empty($perpage)){
            $paged = (int)$paged;
            $offset = ($paged - 1) * $perpage;
            $query .=' LIMIT '.(int)$offset.', '.(int)$perpage;
        }

        // Register the pagination
        $this->set_pagination_args( array(
            "total_items" => $totalitems,
            "total_pages" => $totalpages,
            "per_page" => $perpage,
        ) );

        // The pagination links are automatically built according to those parameters

        // Register the columns
        $columns = $this->get_columns();
        $hidden = array();
        $sortable = $this->get_sortable_columns();
        $this->_column_headers = array($columns, $hidden, $sortable);

        // Fetch the items
        $this->items = $wpdb->get_results($query);
    }

    function display_rows() {

        // Get the records registered in the prepare_items method
        $records = $this->items;

        // Get the columns registered in the get_columns and get_sortable_columns methods
        list ( $columns, $hidden ) = $this->get_column_info();

        // Loop for each record
        if(!empty($records)){foreach($records as $rec){

            // Open the line
            echo '<tr id="record_'.$rec->postId.'">';
            foreach ( $columns as $column_name => $column_display_name ) {

                // Style attributes for each column
                $class = "class='$column_name column-$column_name'";
                $style = "";
                if ( in_array( $column_name, $hidden ) ) $style = ' style="display:none;"';
                $attributes = $class . $style;

                //Display the cell
                switch ( $column_name ) {
                case "title": echo '<td '.$attributes.'><strong>'.stripslashes($rec->title).'</strong></td>';  break;
                case "description": echo '<td '.$attributes.'>'.stripslashes($rec->description).'</td>'; break;
                case "submissionDate": echo '<td '.$attributes.'>'.stripslashes(date("j F Y", strtotime($rec->submissionDate))).'</td>'; break;
                case "name": echo '<td '.$attributes.'>'.$rec->name.'</td>'; break;
                case "email": echo '<td '.$attributes.'>'.$rec->email.'</td>'; break;
                case "post_title": echo '<td '.$attributes.'>'.$rec->post_title.'</td>'; break;
            }
        }

        //Close the line
        echo '</tr>';
    }}
    }
}
?>
<div class="wrap">
    <h2>Noticeboard administration</h2>
    <form name="notification_admin_form" method="post" action="<?php echo str_replace( '%7E', '~', $_SERVER['REQUEST_URI']); ?>">
        <p><label>reCAPTCHA public key <input type="text" name="recaptcha_public_key" size="60" value="<?php echo get_option('recaptcha_public_key', '');?>" /></label></p>
        <p><label>reCAPTCHA private key <input type="text" name="recaptcha_private_key" size="60" value="<?php echo get_option('recaptcha_private_key', '');?>" /></label></p>
        <input type="submit" name="Submit" value="Update reCAPTCHA key details" />
    </form>
<h2>Edit posts</h2>

<?php
//Our class extends the WP_List_Table class, so we need to make sure that it's there
if(!class_exists('WP_List_Table')){
    require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
}

//Prepare Table of elements
$wp_post_table = new Post_List_Table();
$wp_post_table->prepare_items();

//Table of elements
$wp_post_table->display();
?>
</div>

Any ideas as to where I might have gone wrong?

Related posts

Leave a Reply

1 comment

  1. Figured this out myself after kaiser pointed me towards the appropriate entry on the Codex, specifically the part at http://codex.wordpress.org/Class_Reference/WP_List_Table#Notice:_Sortable_Columns which I’d managed to miss. Problem was with the get_sortable_columns method, which should look like this:

        public function get_sortable_columns() {
        return $sortable = array(
            'title'=>array('title',true),
            'description'=>array('description',true),
            'submissionDate'=>array('submissionDate',true),
            'name'=>array('name',true),
            'email'=>array('email',true),
            'post_title'=>array('post_title',true)
        );
    }
    

    Guess the tutorial I was using must have been incorrect.