Tuesday, July 23, 2013

Control post sequence in WordPress archive pages

How to allow manual control of post display on WordPress archive (i.e. category listing) pages? Here's a solution that took me some time to scavenge from various posts, maybe someone will benefit from finding them in one place.

The best way I found is by sorting according to something called “menu order” on the category pages. This doesn't mean that the pages need to be listed in the menu, thankfully, so this attribute can be used regardless of your navigation structure.


Necessary conditions are that your posts need to have hierarchical structure (this is relevant for Custom Post Types, I'll explain later), and they need to support page attributes. Also, you need to tell The Loop used in the archive pages to sort by menu order instead of its default sorting.

Sorting Regular Posts

In order to implement this for regular posts, you need to enable support of page attributes. Add the following code to the functions.php file of your theme:
<?php
// To display menu order for posts
// Based on https://gist.github.com/chrisguitarguy/1301501
add_action( 'init', 'add_post_menu_order' );

function add_post_menu_order() {
   add_post_type_support( 'post', 'page-attributes' );
}

Note that the opening <?php bit is not needed if you've already inserted it the beginning of the functions.php file (I hope you know at least that much, but who knows? :))

After this is done, you'll see an additional attribute in the Quick Edit form on the Posts page within WordPress dashboard, as follows:

Too small, I know. Just click it :)

So now you can enter numerical values into it. Since all posts get created with this value as 0, and I needed to hand-picked posts to stay at the top, I chose descending sorting order — this means that the higher value you enter in this field, the closer to the top this post will be shown.

You may choose otherwise, if you need to keep new posts at the top and hand-pick the underdog posts instead.

Sorting Custom Post Types

Skip this part if you don't use a custom post type :)

For custom post types (CPT) it is a bit different, since regular posts are already configured in the CMS, so we added support for just one parameter. Yet a custom post type, if you use it, is created by you in the functions.php file, so you need to make sure that you create it with correct settings to support menu_order sequencing.

Here's an example of a custom post type I created:
<?php
function reg_special_posts() {
   $labels = array(
      'name'                => 'Special Posts',
      'singular_name'       => 'Special Post',
      'menu_name'           => 'Special Posts',
      'parent_item_colon'   => 'Parent Special Post:',
      'all_items'           => 'All Special Posts',
      'view_item'           => 'View Special Posts',
      'add_new_item'        => 'Add New Special Post',
      'add_new'             => 'New Special Post',
      'edit_item'           => 'Edit Special Post',
      'update_item'         => 'Update Special Post',
      'search_items'        => 'Search Special Posts',
      'not_found'           => 'No Special Posts found',
      'not_found_in_trash'  => 'No Special Posts found in Trash',
   );

   $rewrite = array(
      'slug'                => 'special-post',
      'with_front'          => true,
      'pages'               => true,
      'feeds'               => true,
   );

   $args = array(
      'label'               => 'special_posts',
      'description'         => 'Special Posts added on purpose',
      'labels'              => $labels,
      'supports'            => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', 'page-attributes'),
      'hierarchical'        => true, // needed for menu order
      'public'              => true,
      'show_ui'             => true,
      'show_in_menu'        => true,
      'show_in_nav_menus'   => true,
      'show_in_admin_bar'   => true,
      'menu_position'       => 10,
      'menu_icon'           => '',
      'can_export'          => true,
      'has_archive'         => true,
      'exclude_from_search' => false,
      'publicly_queryable'  => true,
      'rewrite'             => $rewrite,
      'capability_type'     => 'post' // needed for menu order
   );

   register_post_type( 'special_posts', $args );
}

// Hook into the 'init' action
add_action( 'init', 'reg_special_posts', 0 );
?>

So this is more or less standard, just make sure you've enabled hierarchical structure for your post type, and added “post” capability. After this is done, if done right, you'll see the same Order attribute in the Quick Edit form of your custom posts list.

Note: One thing it took me some time to figure our — don't specify a name that is too long for your custom post type, since it will just not be shown in the admin dashboard.

Actual Sorting!

So by now you probably noticed that the posts are still not sorted the way you want them to. Of course they aren't, we haven't told WordPress to use another criteria instead of the default (which is by post date, I believe). We can do it by adding some more code to our functions.php file:
<?php
// Change sorting sequence on specific category or post type
// Based on http://www.billerickson.net/customize-the-wordpress-query/

add_action( 'pre_get_posts', 'sorting_posts' );

function sorting_posts( $q ) {
   // For specific post type
   if( $q->is_main_query() && $q->get('post_type') == 'your_custom_type') {
      $q->set( 'orderby', 'menu_order' );
      $q->set( 'order', 'DESC' );
      // A bonus, you can control the post count as well :)
      $q->set( 'posts_per_page', 27);
   } else {
      // For specific category
      if( $q->is_main_query() && $q->get('category_name') == 'your_category_name') {
         $q->set( 'orderby', 'menu_order' );
         $q->set( 'order', 'DESC' );
      }
   }
}
?>

If you don't need to change the post count per page — remove the line that sets the posts_per_page value.

That's it. The posts should now be sorted according to the Order attribute of each post, either descending or ascending, depending on what you chose.

I need to mention that it is also possible modify the loop's query parameters on the template page itself, as follows:
<?php global $query_string;
   query_posts("{$query_string}&orderby=menu_order&order=DESC"); ?>

This may be your preferred solution, since it is easier to control which page is affected by this change. Choose what is best for you.

I hope this helps, take care!

8 comments:

  1. ALMOST EXACTLY what I need, except I want this to work on all category pages, regardless of category name. It's a literary press website. Each title in the catalogue has a post. These posts are numbered using the code you give above (awesome, thanks). Each title is in the category "Titles" and also in a specific imprint category "Sports," "Hiking," etc. So you can view all titles, or you can view titles in a specific imprint. I'd like to have just one function that orders the posts on a category numerically. Does that make sense? I'm thinking maybe there's some code I can insert in my category.php to specify numerical order all the time. THANKS!!!

    ReplyDelete
    Replies
    1. Hi Michael! Glad I could be of help to you (even if partial :))

      For your question — did you try to change the condition in the modification of functions.php file? In my code above it applies the changes if $q->is_main_query() AND the category name matches. So if you remove all the parts that you don't need, and just check that this the main query, and apply the change (being $q->set( 'orderby', 'menu_order' ); $q->set( 'order', 'DESC' );) to any category?

      You may also need to add checking whether this is a category page, yet I'd experiment with a simple condition first, and then see if I need to narrow the criteria down.

      I hope this helps!

      Delete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. I can't this to work. I have the most basic question - where is the function.php file? I have the CSS upgrade, and I go to the CSS Stylesheet Editor, and apply that first bit of code. Two observations - not all of the code will save, two the sort box doesn't appear. I really want to order my posts! I have spent a great deal of time moving my blog to Wordpress for this fucntionality alone, and I am super frustrated that I can't get it to work.

    ReplyDelete
    Replies
    1. Hi, the functions.php file needs to be in your theme folder — if it isn't there already (some themes do not have it initially) you'll need to create it — probably you'll need to upload it via FTP. And please note that it isn't a CSS file (which would define styles), it is a PHP file (which defines executable server-side code). Once you created this file, it should appear on the right-hand side list in your Admin / Appearance / Editor page in WP (for me it says Theme Functions (functions.php)). I hope this helps, please let me know if you need more info — and best of luck with your blog transfer!

      Delete
  4. hi Anton, this is exactly what I'm looking for!

    However, I've added the code to the fucntions.php page and have found the order field in the quick edit and changed these but it's not re-sequencing - any idea?

    thanks

    Adam

    ReplyDelete
    Replies
    1. Hi Adam,

      Did you also insert the sorting_posts() function? Sorting consists of two parts—allowing to enter order field per post, but also actually using this field to sort. You'll find it in “Actually Sorting!” section of my article above (I now see it isn't structured very well, sorry about that).

      I hope this helps!

      Delete