The article has been updated with a working example on how I’ve implemented the atomic hook approach in WP Framework. It’s also WordPress 2.9 compatible for custom post types and taxonomies.
In the last article, we looked at a simple way for making your template files more cleaner and DRY using do_atomic(), a function that registers three predefined do_actions() for any particular location. In this article, we’re going to step it up a notch and talk about context and how we can create context sensitive action hooks.
Let’s talk about what we can do now. WordPress allows you to create action hooks at any point in your WordPress theme to allow other functions to be executed at that particular location. Well that’s pretty cool. But what about context? If you’ve ever used any sandbox-based theme, you should be familiar with the class generating functions it pours into the body tag and other sections throughout your theme. Those functions give CSS clear context as to what page is being displayed and a lot more specifics.
So if we had a #header div that displayed our site’s name and tagline, those class generating functions would allow us to style the #header div differently for pages, archives, or any other template without adding any if, else logic to the actual template file.
<!--BEGIN #header--> <div id="header"> <h1 id="logo"><a href="<?php bloginfo( 'url' ); ?>"><?php bloginfo( 'name' ) ?></a></h1> <p id="tagline"><?php bloginfo( 'description' ) ?></p> <!--END #header--> </div>
#header { background-image: url( '../images/header.jpg' ); }
.page #header { background-image: url( '../images/header-page.jpg' ); }
.single #header { background-image: url( '../images/header-single.jpg' ); }
So that got me thinking, why not do this for action hooks too?
Chris Jean wrote another version of the get_context() function which you should definitely check out.
/**
* Retrive the context of the queried template.
*
* @since 0.4
* @return array $query_context
*/
function get_query_context() {
global $wp_query, $query_context;
/* If $query_context->context has been set, don't run through the conditionals again. Just return the variable. */
if ( is_array( $query_context->context ) )
return $query_context->context;
$query_context->context = array();
/* Front page of the site. */
if ( is_front_page() )
$query_context->context[] = 'home';
/* Blog page. */
if ( is_home() )
$query_context->context[] = 'blog';
/* Singular views. */
elseif ( is_singular() ) {
$query_context->context[] = 'singular';
$query_context->context[] = "singular-{$wp_query->post->post_type}";
$query_context->context[] = "singular-{$wp_query->post->post_type}-{$wp_query->post->ID}";
}
/* Archive views. */
elseif ( is_archive() ) {
$query_context->context[] = 'archive';
/* Taxonomy archives. */
if ( is_tax() || is_category() || is_tag() ) {
$term = $wp_query->get_queried_object();
$query_context->context[] = 'taxonomy';
$query_context->context[] = $term->taxonomy;
$query_context->context[] = "{$term->taxonomy}-" . sanitize_html_class( $term->slug, $term->term_id );
}
/* User/author archives. */
elseif ( is_author() ) {
$query_context->context[] = 'user';
$query_context->context[] = 'user-' . sanitize_html_class( get_the_author_meta( 'user_nicename', get_query_var( 'author' ) ), $wp_query->get_queried_object_id() );
}
/* Time/Date archives. */
else {
if ( is_date() ) {
$query_context->context[] = 'date';
if ( is_year() )
$query_context->context[] = 'year';
if ( is_month() )
$query_context->context[] = 'month';
if ( get_query_var( 'w' ) )
$query_context->context[] = 'week';
if ( is_day() )
$query_context->context[] = 'day';
}
if ( is_time() ) {
$query_context->context[] = 'time';
if ( get_query_var( 'hour' ) )
$query_context->context[] = 'hour';
if ( get_query_var( 'minute' ) )
$query_context->context[] = 'minute';
}
}
}
/* Search results. */
elseif ( is_search() )
$query_context->context[] = 'search';
/* Error 404 pages. */
elseif ( is_404() )
$query_context->context[] = 'error-404';
return $query_context->context;
}
get_query_context() returns an array containing the context of the current queried template. And it only runs once per page, even if there are multiple calls to it, so there’s no performance lost. get_query_context() is also custom post type aware.
Now, that we have some context, let’s put it into action. Here’s the new and suped-up do_action() — do_atomic():
function do_atomic( $tag = '', $args = '' ) {
if ( !$tag )
return false;
/* Do actions on the basic hook. */
do_action( $tag, $args );
/* Loop through context array and fire actions on a contextual scale. */
foreach ( (array) get_query_context() as $context )
do_action( "{$tag}_{$context}", $args );
}
So now using do_atomic(), we can treat our action hooks just like we always did. In addition, do_atomic() registers up to 22 different contexts, so if you’d like to display something specific to page templates or archives-based templates, do_atomic() allows you to do so without creating any additional code.
Let’s go back to our header.php and replace the #header div with this:
<!--BEGIN #header--> <div id="header"> <?php do_atomic( 'theme_header' ); ?> <!--END #header--> </div>
And now, when a user queries a page template, you’ll have four actions fired in this order:
- add_action( ‘theme_header’, ‘my_custom_header’ );
- add_action( ‘theme_header_singular’, ‘my_custom_singular_header’ );
- add_action( ‘theme_header_page’, ‘my_custom_page_header’ );
- add_action( ‘theme_header_page-id-5′, ‘my_custom_page_foobar_header’ );
This way, you can specify a variety of looks for the header all while keeping your template files and functions clean and clutter free of if, else and is_* statements. And if by any chance you have more than one of those contextual hooks fired on a given page, simple call remove_action(); so only the desired one gets executed.
I was just thinking about contextual hooks the other day and how to best implement them. This is definitely something I might be implementing into the Hybrid framework at some point. It might save me from having to teach WP conditional tags to so many people.
Why did you choose that particular order? More specifically, why use the
abstractcontext before the basic action hook?