Going Loopy With WordPress

Posted on Thu, 4th May 2006 at 11:35 under Reviews, WordPress, Education, Coding

By far the most important component of WordPress, as for any CMS, is The Loop or engine. The purpose of the Loop is to select from the available content that which the reader has requested then deliver that content, item by item, through the rest of the processing system. In short, the Loop decides what posts can be seen.

The WordPress loop is to most blog owners, I suspect, a few lines of barely-comprehensible code in some of their theme files. This is a good thing. Most blog owners, like most car owners, prudently avoid disaster by going nowhere near the engine. Other blog owners however, like some car owners, are intolerably curious as to how things work and don’t mind breaking things for fun.

I won’t go anywhere near a real engine, but a software engine poses no threat to my fingers, so in I go.

The Blog Big Picture

Every page a blog delivers by request goes through this basic process.

  1. Accept Incoming Request
    Bootstrap
    The blog software is started by the web server, initialises itself, examines the request and initialises the remainder of the system.
  2. Header
    Top Banner
    Construct and output all document structure information (usually HTML) and the standard page heading. Often includes site navigation links.
  3. Loop Content
    The Blog
    Selects, retrieves and outputs content items in response to the request.
  4. Auxiliary Content (Optional)
    The Sidebar
    Outputs other content items not selected by the loop, usually for in-depth navigation, archives, mini-applications and the like.
  5. Footer
    The Bottom
    Output the standard page footing and remainder of the document structure.
  6. End

The WordPress Loop

The reference implementation of the WordPress loop is contained in the file wp-content/themes/default/index.php. It looks like this.

get_header();

<div id=’content’>

if (have_posts()) :

while (have_posts()) : the_post();

OUTPUT POST

endwhile;

else :

NOT FOUND

endif;

</div>

get_sidebar();

get_footer();

The core of the loop is highlighted and the WordPress function calls that comprise the loop are emphasised. There are two; have_posts() and the_post().

Have You Any Posts?

The implementation of have_posts() is found in wp-includes/classes.php in class WP_Query. It returns true if any posts remain in the loop, triggering action ‘loop_end’ on the last post in the loop. If no posts remain in the loop, false is returned.

Give Me The Next Post

The implementation of the_post() is found next to that of have_posts(). It retrieves and prepares the next post in the loop, triggering action ‘loop_start’ on the first post in the loop.

Still no sign of the post retrieval query, so follow the chain back from the_post() through next_post(), which simply gets an element from an array. The array contains the posts in the loop, so they must have been retreived elsewhere, probably in the WP_Query constructor.

WordPress Class WP_Query

This class implements the WordPress loop, as evidenced by member variable $in_the_loop which is set true by the_post() and false by have_posts() when none remain.

function get_posts()

This is it. The loop query constructor and it’s a doody! It begins by triggering action ‘pre_get_posts’, followed by several hundred lines converting the request query string into a SQL query. The following filters are applied; ‘posts_where’, ‘posts_join’, ‘posts_where_paged’, ‘posts_groupby’, ‘posts_join_paged’ and ‘posts_orderby’. The complete SQL query is filtered through ‘posts_request’ and the result set, after security preprocessing, is filtered through ‘the_posts’.

The result set security pre-processing is unfortunate. It filters out future dated posts except for logged-in users so that they are never passed to the filter. But that’s by-the-by.

Playing With The Loop

I have a simple example. Build a plugin to ensure that only logged-in users may see and access password-protected posts, which is a simple matter of conditionally adding post_password='’ to the loop query WHERE clause via filter ‘posts_where’.

Take An Empty Plugin…

<?php // Mandatory section: plugin header and descriptive commentary.

/*
Plugin Name:
Plugin URI:
Description:
Version:
Author: Copyright ©2006 Paul Mitchell aka Libertus
Author URI:
*/

?>

Add some meaningful description…

<?php // Mandatory section: plugin header and descriptive commentary.

/*
Plugin Name: Protected Posts Filter
Plugin URI:
Description: Filter out password-protected posts unless user is logged-in
Version: ∞
Author: Copyright ©2006 Paul Mitchell aka Libertus
Author URI:
*/

?>

Add a ‘posts_where’ filter only when the user is not logged-in…

<?php // Mandatory section: plugin header and descriptive commentary.

/*
Plugin Name: Protected Posts Filter
Plugin URI:
Description: Filter out password-protected posts unless user is logged-in
Version: ∞
Author: Copyright ©2006 Paul Mitchell aka Libertus
Author URI:
*/

if( !$user_ID ) { // global $user_ID is non-zero if the current user is logged-in
 add_filter( ‘posts_where’, ‘filter_protected_posts’ );
 function filter_protected_posts( $where ) { return $where . ” AND post_password='’”; }
}
?>

Looks good. Does it work? :)

No! Another Way!

User management happens after plugin initialisation, meaning that global $user_ID will not have a value. No problems. A simple change.

add_action( ‘pre_get_posts’, ‘maybe_filter_protected_posts’ );
function maybe_filter_protected_posts() {
 global $user_ID;
 if( !$user_ID ) { // global $user_ID is non-zero if the current user is logged-in
  add_filter( ‘posts_where’, ‘filter_protected_posts’ );
  function filter_protected_posts( $where ) { return $where . ” AND post_password='’”; }
 }
}

That’s better. The plugin hooks in just before the loop, well after user management, then conditionally adds the filter. The public can no longer see password protected posts, via the loop anyway.

Moving On… Scheduled Posts From A Specific Category

This is a bit more complicated. The WordPress loop contains a lot of code to explicitly forbid the viewing of future-dated posts by the public. I want to relax that a little, but doing so via the query filters is not possible. The solution is to requery for the desired posts and append them to those selected by the loop. Presumably, the very purpose of the ‘the_posts’ filter, which is where my next plugin shall hook.

That Empty Plugin Again

Pre-filled with the description to save space.

<?php // Mandatory section: plugin header and descriptive commentary.
/*
Plugin Name: Publicise Upcoming Events
Plugin URI:
Description: Allow the public to see future-dated posts from a specified category
Version: ∞
Author: Copyright ©2006 Paul Mitchell aka Libertus
Author URI:
*/
?>

Requirements

The plugin must hook in to the ‘the_posts’ filter and append a list of future-dated posts from a user-specified category. That will require a function to execute the query and return the posts, an administrative page to set which categories will be publicised and the necessary plumbing to make it all work.

Hooking In

<?php // WordPress hooking
// Add a “Upcoming Events” administrative page to the “Manage” menu
add_action( ‘admin_menu’, ‘admin_menu_upcoming_events’ );

// Add an action at loop start to conditionally add the publication filter
add_action( ‘pre_get_posts’, ‘maybe_filter_add_upcoming_events’ );

?>

Add The Filter Functions And Upcoming Events Query

<?php // Upcoming events filter and query
// Query all future-dated posts in a specified category
function upcoming_events_query( $category_id ) {
global $wpdb;
$now = current_time( ‘mysql’, 1 );
$query = “SELECT * FROM $wpdb->posts LEFT JOIN $wpdb->post2cat ON ID=post_id WHERE post_status=’publish’ AND post_date_gmt > ‘$now’ AND category_id = $category_id ORDER BY post_date_gmt DESC”;

return $wpdb->get_results( $query );
}

// If current user is not logged-in, add a filter to the post loop
// which adds upcoming events
function maybe_filter_add_upcoming_events( $posts ) {
global $user_ID;
if( !$user_ID ) add_filter( ‘the_posts’, ‘filter_add_upcoming_events’ );
}

// Filter posts selected by loop, prepend upcoming events
function filter_add_upcoming_events( $posts ) {
$category_id = get_option( ‘upcoming_events_cat’ );
if( $category_id ) $posts = array_splice( $posts, 0, 0, upcoming_events_query( $category_id ) );
return $posts;
}
?>

I Would Do An Admin Page, But…

… that’s a job for someone else! :) I just have to get some code working.

You may notice that the code retreives the category for upcoming events from the WordPress options. All the administrative page has to do is manage that one value. For testing, I’ll set it manually.

Here’s One I Prepared Earlier

The solution to relaxing the restrictions on future-dates posts is by no means simple. After a bit of spitting and polishing, I ended up with the following plugin code.

Leave a Reply

You may also log in to post a comment.

XHTML:

If you want to <q>tag</q>, please balance these; a, i, em, b, strong, u, blockquote, q, ul, li, ol, abbr, code, pre, sub and sup.