Designing flexible WordPress themes.
The average WordPress theme has different files for pages, single posts, archives and the front page; however, most of them are almost exactly the same except for inside the_loop. This is a quick tutorial on how to do the most with the fewest files, and includes a few methods to have custom templates by separating content display from structural elements.
WordPress looks first for special files and then defaults to the index.php file (as shown in this diagram). We can take advantage of this by using only an index.php file and then using conditionals to modify it. While it seems that this method would render the code less readable, it is actually far more readable, and far easier to modify.
Your theme should start off a header.php, footer.php, sidebar.php and an index.php. These files are the “code” files and are fairly self-explanatory, and at this point the only question should be whether you add just the header portion of the file to the header.php or everything including the calls to get_sidebar()—the same goes for the footer.php depending on your theme. This depends on how you use your them, if you plan to integrate it with bbPress and use the same header and sidebars, you should add everything before the normal loop to the header. However, we will focus on things outside of the header (this includes the navigation menus and such) and just focus on the_loop.
A “normal” index page looks a little like this. (Taken from the default WordPress theme.)
<?php get_header(); ?>
<div id="content" class="narrowcolumn">
<?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>
<div class="post" id="post-<?php the_ID(); ?>">
<h2><a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title(); ?>"><?php the_title(); ?></a></h2>
<small><?php the_time('F jS, Y') ?> <!-- by <?php the_author() ?> --></small>
<div class="entry">
<?php the_content('Read the rest of this entry »'); ?>
</div>
<p class="postmetadata">Posted in <?php the_category(', ') ?> | <?php edit_post_link('Edit', '', ' | '); ?> <?php comments_popup_link('No Comments »', '1 Comment »', '% Comments »'); ?></p>
</div>
<?php endwhile; ?>
<div class="navigation">
<div class="alignleft"><?php next_posts_link('« Previous Entries') ?></div>
<div class="alignright"><?php previous_posts_link('Next Entries »') ?></div>
</div>
<?php else : ?>
<h2 class="center">Not Found</h2>
<p class="center">Sorry, but you are looking for something that isn't here.</p>
<?php include (TEMPLATEPATH . "/searchform.php"); ?>
<?php endif; ?>
</div>
<?php get_sidebar(); ?>
<?php get_footer(); ?>
For most themes, the page and single pages look the same: the only part that really changes is the_loop itself, and even then the changes are usually minor, so if you want to change something outside the_loop, you have to change it in all files. However, by operating the majority of display elements out of the main files, we can make it infinitely extensible without overloading the end-user with large amounts of code to sort through.
The index.php file for the theme I use is very short and easily read.
<?php get_header(); ?>
<div id="wrap">
<div id="content">
<?php if (have_posts()) : ?>
<?php
if(is_home()){
include (TEMPLATEPATH . '/index-loop.php');
}elseif(is_page() || is_single()){
include (TEMPLATEPATH . '/single-loop.php');
}else{
include (TEMPLATEPATH . '/archive-loop.php');
}
?>
<?php else :?>
<h2 class="center">Not Found</h2>
<?php include (TEMPLATEPATH . '/not-found.php');?>
<?php endif; ?>
</div>
<?php get_sidebar(); ?>
</div>
<?php get_footer(); ?>
If we break it down: it calls the header, adds the two structural divs, and checks to see if there are any posts (of any kind). If there are no posts it includes an error page before including the sidebar, closing the divs and calling the footer. This part of the page is fairly normal and is used in most themes—not only is it used in most themes, but most of the main files all have this same code–every single theme file calls the footer, sidebar and header, shows an error message if there are no posts to display. This is a lot of wasted space. If you suddenly want to change the name of something or move the sidebars, you have to change every file which is just pointless. The difference in my index.php file is that there isn’t a full loop. It checks if there are posts using have_posts(), but where you would normally see a loop it just has some conditional statements, and for each condition, a different loop is included.
Each of the loops is in a different included file. (The loops are shown below, but just notice the similarities and major differences.)
Index loop:
<?php while (have_posts()) : the_post(); ?>
<h2 id="post-<?php the_ID(); ?>" class="b">
<a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title(); ?>"><?php the_title(); ?> </a>
</h2>
<div class="main main_border">
<small>
Posted on <?php /*backslashes escape chars*/ the_time('l \t\h\e jS \o\f F, Y \a\t g:i a ') ?> in <?php the_category(', ') ?> <?php edit_post_link('Edit','-');?>
</small>
<?php the_content('Continue reading "'.the_title('', '', false).'"')?>
<div class="small box">
//Some comment and UTW stuff
</div>
</div>
<?php endwhile; ?>
<div class="main" style="width:99%;">
<div class="main-nav-left" style="float:left;width:49%;"><?php posts_nav_link('','','« Previous Entries') ?></div>
<div class="main-nav-right" style="float:right;width:49%; text-align:right;"><?php posts_nav_link('','Next Entries »','') ?></div>
</div>
single-loop.php (handles both singles and pages because there is only a tiny difference between the two)
<?php while (have_posts()) : the_post(); ?>
<h2><?php the_title();?></h2>
<small>
<?php if(!is_page()){ /*Only difference between pages and singles*/?>
Posted on <?php /*backslashes escape chars*/ the_time('l \t\h\e jS \o\f F, Y \a\t g:i a ') ?> <!-- by <?php the_author() ?> -->in <?php the_category(', ') ?> <?php edit_post_link('Edit','-');?>
<?php }else{?>
<?php edit_post_link('Edit','-');?>
<?php } ?>
</small>
<div class="main">
<?php the_content('Read the rest of this entry »'); ?>
<?php wp_link_pages(); ?>
<div class="small box">
//some comment and UTW stuff
</div>
<?php posts_nav_link(' --- ', __('« Previous Page'), __('Next Page »')); ?>
</div>
<?php endwhile; ?>
And the archive-loop.php which handles categories, date archives, tags, searches etc.
<?php $post = $posts[0]; // Hack. Set $post so that the_date() works. ?>
<?php /* If this is a category archive */ if (is_category()) { ?>
<h2> Reading about <?php single_cat_title(); ?></h2>
<?php /* If this is a daily archive */ } elseif (is_day()) { ?>
<h2>Archive for <?php the_time('F jS, Y'); ?></h2>
<?php /* If this is a monthly archive */ } elseif (is_month()) { ?>
<h2>Archive for <?php the_time('F, Y'); ?></h2>
<?php /* If this is a yearly archive */ } elseif (is_year()) { ?>
<h2>Archive for <?php the_time('Y'); ?></h2>
<?php /* If this is a search */ } elseif (is_search()) { ?>
<h2>The Search Results For "<?php echo $s;?>"</h2>
<?php /* If this is an author archive */ } elseif (is_author()) { ?>
<h2>Author Archive</h2>
<?php /* If this is a paged archive */ } elseif (isset($_GET['paged']) && !empty($_GET['paged'])) { ?>
<h2>Archives</h2>
<?php } ?>
<div class="main" style="width:99%;">
<div class="main-nav-left" style="float:left;width:40%;"><?php previous_posts_link('« Previous Entries') ?></div>
<div class="main-nav-right" style="float:right;width:40%; text-align:right;"><?php next_posts_link('Next Entries »') ?></div>
</div>
<?php while (have_posts()) : the_post(); ?>
<h3 class="b"><a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link a <?php the_title(); ?>"><?php the_title(); ?></a></h3>
<div class="main main_border <?php echo anth::styles();?>">
<small>
<!-- Posted <?php the_time('j F Y') ?> by <?php the_author() ?> -->
<?php if(function_exists('UTW_ShowTagsForCurrentPost')){?>
<span class="utwtags">Keywords: <?php UTW_ShowTagsForCurrentPost("commalist") ?></span>
<?php } ?>
<?php edit_post_link('Edit','-');?>
</small>
<?php the_excerpt() ?>
</div>
<?php endwhile; ?>
<br/>
<div class="main" style="width:99%;">
<div class="main-nav-left" style="float:left;width:40%;"><?php previous_posts_link('« Previous Entries') ?></div>
<div class="main-nav-right" style="float:right;width:40%; text-align:right;"><?php next_posts_link('Next Entries »') ?></div>
</div>
The actual loops in each of these files starts with <?php while (have_posts()) : the_post(); ?>
, and you will notice that all the loops are similar with only a few cosmetic changes, the major difference is in the archive page which uses a large conditional block to set the title text, but this could have been done on the index.php page and used a completely different loop if you needed it.
Okay, so you get down to here and wonder why you haven’t heard anything new. well once we start using custom loops the possibilities are endless.
For example, as I posted about recently, on my index I use a special loop for items that are in the Asides category, but only on the index page, so inside of my is_home) conditional statement I also add the code:
<?php $in_cat = cat_loop();?>
<?php if($in_cat){?>
<?php include('cat_'.$in_cat.'.php');?>
<?php } ?>
Which calls the function: (added to the functions.php file)
function cat_loop(){
global $blog_id,$post, $wp_version;
if($wp_version >= 2.3){
global $object_term_cache;
$array = $object_term_cache[$blog_id][$post->ID]['category'];
}else{
global $category_cache;
$array = $category_cache[$blog_id][$post->ID];
}
while (list($cat) = each($array)) {
if(file_exists(dirname(__FILE__).'/cat_'.$cat.'.php')){
return $cat;
}
}
}
Basically the function loops through all the categories a post is in and if there is a custom loop for a category it uses it. This can easily be extended for author ids, name or anything else that you can test.
Let’s review: use as little repeated code as possible because when you do lots of tricks are available to you.