WordPress Theme Reviewer Chip Bennett explains how to incorporate the Settings API in your WordPress themes and build a robust and secure tabbed theme options page.
Category: Theme Tips
Post Formats and Content Templates in the Toolbox Theme
Toolbox 1.1 is on it’s way with support for two of WordPress 3.1’s new Post Formats — Aside and Gallery — and a new template structure based on a content.php
template. Let’s take a look at what’s new.
Update: It’s available now! Go get it and check it out.
So, Post Formats. I love them and I bet if you’re at all interested in WordPress theming you’re probably interested in them too. Toolbox 1.1 will add support for Aside and Gallery posts along the same lines as the new Twenty Ten theme will. If you’re familiar with the Twenty Ten theme you know what to expect. It already mimics this new feature with Categories. Asides have a hidden title on blog pages and Gallery posts have a thumbnail from your post’s gallery along with a sentence letting visitors know how many images are in the gallery. It’s pretty cool.
Continue reading “Post Formats and Content Templates in the Toolbox Theme”
Custom Menu Code Samples
Do you find yourself taking older themes and adding support for Custom Menus? Here are code samples that you can use for just that.
To be clear, this isn’t a full-blown tutorial for Custom Menus. See Justin Tadlock’s excellent post, Goodbye, headaches. Hello, menus! or the wp_nav_menu Codex page for all the juicy details.
The Perfect Blog Theme
What is the perfect blog theme? I’ve been thinking about it and I think I have the answer.
For the blogger: The perfect blog theme disappears into the background and doesn’t hinder their writing in any way—in fact, it encourages it.
For the reader: The perfect blog theme disappears into the background and doesn’t hinder their reading in any way—in fact, it encourages it.
Please note what I haven’t included in my answer. It’s important.
Know of any publicly released themes that fit my criteria? I’d really like to know about them. Let me know if you’ve found them, or just let me know your thoughts on the perfect blog theme by leaving a comment.
And don’t be afraid to argue! I’ve been known to be wrong!
Registering New Sidebars for Custom Page Templates The Smart Way
One of the cooler ideas for a new WordPress default Theme that’s come up has been the idea of including a not-so-blog-ish custom home page template with the Theme. Something optional that you could use if wanted your home page to look a little different. Maybe more of a traditional web site look (whatever that is) or a magazine look. Simply create a new page, assign this custom template to it, set it as your home page, and boom! New look.
I thought this was a great idea too. Especially if that custom page template was totally widgetized. Load up whatever dynamic content you want there with the Query Posts Widget or just use Text Widgets. The default styles of the new 2010 Theme can decide which of several widget areas is the ‘featured’ area and if you want something different—without wanting to change the markup—you can move things around with a Child Theme.
Great, right? Wrong.
You’d have to register new widget areas. Widget areas that would be confusingly unavailable if you weren’t using the custom home page template. Unless …
Here’s a really nifty function written up by Chris Gossmann that’ll check to see if a particular custom page template is active. Chris had to write a SQL query to get this to work so be thankful that he survived to tell us about it.
function is_pagetemplate_active($pagetemplate = '') { global $wpdb; $sql = "select meta_key from $wpdb->postmeta where meta_key like '_wp_page_template' and meta_value like '" . $pagetemplate . "'"; $result = $wpdb->query($sql); if ($result) { return TRUE; } else { return FALSE; } } // is_pagetemplate_active()
OK. Even by itself that function is kinda cool. There’s a few neat things you can do with it. Here’s one. Combine it with the following code for registering a new sidebar in the functions.php
file of your WordPress Theme:
function test_template_widgets_init() { if(is_pagetemplate_active('template-active.php')) { register_sidebar( array ( 'name' => 'Test Widget Area', 'id' => 'test-widget-area', 'before_widget' => '<li id="%1$s" class="widget-container %2$s">', 'after_widget' => "</li>", 'before_title' => '<h3 class="widget-title">', 'after_title' => '</h3>', ) ); } // end test for active template } // test_template_widgets_init() add_action ( 'init' , 'test_template_widgets_init' );
Can you guess what that does? Using our new conditional function, is_pagetemplate_active()
, we’re registering a new widget area only if, in this case, template-active.php
is being used by one of our pages. Completely bypassing our earlier problem of widget areas potentially hanging around without a page for them to be displayed on.
What do you think? Pretty cool, huh? I see lots of really neat possibilities here for custom page templates in WordPress Themes and in Child Themes.
Don’t Get Hacked: WordPress Security Tips
In case you didn’t hear ThemeShaper was hacked. You know what? It really sucks. I’ve got two tips and a plugin recommendation that I want to pass on to you so the same stupid thing doesn’t happen to your WordPress install. And these aren’t even my ideas! These are time tested and tried things that just plain work.
After that comes a list of some further plugins and resources that’ll help harden up your WordPress install and keep hackers at bay. So read on.
Do a Fresh Install of WordPress, Plugins, & Themes
Do a fresh install of all WordPress, your plugins, and themes. That means deleting a whole whack of WordPress files just like you were doing an upgrade. And deleting and re-installing ALL your themes and plugins. If you’ve done ANY customization to any one of these files go through them line by line or re-store a local version that never made it to your web server. And while you’re at it start keeping local copies of your edited themes and plugins that have never made it to your web server.
You’re doing this to help make sure your current setup isn’t already compromised.
Hardening WordPress with htaccess
The Blog Security blog has a great article on how to lock out anyone trying to mess with your WordPress files using htaccess. It’s dead simple to do and requires only cut-paste skills and FTP access to your server, and a quick trip to What’s My IP. Anyone can do it. Check it out now and harden your blog.
The Update Notifier Plugin
The single biggest exploitable entry point on any WordPress install is going to be outdated versions of WordPress, themes and plugins. If you don’t know how extreme this can get check out this comment from one of my readers.
I remember when something similar happened to me. Fortunately I managed to find someone willing to help who knew quite a bit more about WordPress than me. The breach was traced to a caching plugin that was out of date by about a week.
The Update Notifier Plugin helps solve this problem by checking the official repository on a regular schedule and sending you an email when it’s time to upgrade.
Further Security Resources and Plugins
40+ Thematic Resources, Tutorials and Links
The post Build WordPress Sites Fast With the Thematic Theme Framework by Es Developed is a great resource for anyone wanting to get started with the Thematic Theme. It lists over 40 resources and tutorials that’ll get you where you want to be with rapid WordPress Theme development.
And there’s some great explanation behind why you’d want to use the Thematic Theme. Under the heading Don’t Touch That Theme:
To create your theme, you don’t actually edit any of Thematic’s theme files. Instead, you make changes using a separate child theme.
It’s really powerful since you’re not actually touching any part of Thematic. You just get a nice starting place, without worries about future Thematic updates overwriting your code edits.
You don’t have to rewrite a bunch of code all over again–you’re just adding the bits that you want to be different. This works very much like the custom styles on WordPress.com hosted blogs.
Make sure you check out Build WordPress Sites Fast With the Thematic Theme Framework.
How To Reset & Rebuild WordPress Theme CSS & Define Your Layouts
Update: We’ve created a second edition of this article, with updated code samples and coverage of the latest theme development techniques. Check it out at How To Reset & Rebuild WordPress Theme CSS & Define Your Layouts. It’s part of The ThemeShaper WordPress Theme Tutorial: 2nd Edition.
CSS can be tricky. It can also be incredibly easy. I had a lot of help getting my head wrapped around CSS when I was first starting out and I take great pleasure in helping others the same way I was first helped: with solid code examples to learn from.
Here we’re going to layout a WordPress Theme CSS development arsenal for you:
- A stylesheet that resets default CSS across all web browsers and makes a sane standard we can work from
- Another stylesheet that rebuilds our typographical foundations in a smart way
- A stylesheet just for WordPress classes (keeping the first two pure so we can use them for non-WordPress projects)
- A series of 6 fluid stylesheets that will create ALL the common blog and website layouts you expect—and each one ready to adapt into a fixed width layout.
All the code we’ll talk about is open-source, under the GPL, and browse-able at the Your Theme Project Page. View the raw source for any one of these files and copy-paste at your leisure.
First things first, make a “style” directory in your Theme folder. That’s where we’ll be putting everything. Your CSS quiver, as it were. Ready to hit the target?
Reset CSS
Our Reset CSS is adapted from Eric Meyer’s famous Reset CSS with some minor, minor changes. Basically what it does is take all the typographical defaults of every browser and … obliterates them. You’re left with a squashy, gray mess.
It’s beautiful.
What this does is equalize the rendering of every browser, leaving you free to ignore countless individual quirks particular to each one.
Using it is simple. Add the following lines to your style.css
, at the very top, immediately after the initial comments section.
/* Reset default browser styles */ @import url('styles/reset.css');
Reload, your page and check it what reset.css
does in multiple browsers (if you can). It’s wonderfully gross, isn’t it?
Rebuild CSS
Our Rebuild CSS is my own personal invention adapted from an early version of the Blueprint CSS typography stylesheet and refined in the Thematic Project. What it does is swing back some vertical rhythm in our pages, but in a really smart way.
What I’ve tried to do with this iteration of my typography-rebuild CSS is combine the best of both worlds for web typography: using Pixels for font height, with relative line-height for the main declaration on the body element, and Ems for all subsequent vertical margins (like for paragraphs and lists).
What does this mean? It’s really easy to set your font height later—without doing any math work—and have all of your typographical elements follow suit with an appropriate vertical rhythm (the vertical space between type elements like paragraphs and lists).
Using rebuild.css
is also really easy. Just add the following lines immediately after your reset.css
import.
/* Rebuild default browser styles */ @import url('styles/rebuild.css');
The Basic WordPress Styles
There are some elements in WordPress that you’re just going to have to style every time. What I’ve done is taken those styles and put them in there own little corner called wp.css
.
Right now, what we’ve got in there are styles for floating all the images—including handling captions and image galleries. And! preset styles for simple pull-quotes. All you need to do is add a class of left or right to the blockquote tag and you’re ready to roll.
Can you guess how we’re going to use wp.css
?
/* Basic WordPress Styles */ @import url('styles/wp.css');
All The Layouts You’ll Ever Need
For your new theme, I’ve taken the rock-solid, indestructible layouts that shipped with the Sandbox Theme and adapted them for your new HTML structure. There are 6 in total. Each is a fluid layout (that stretches to fill the width of your browser window) but each one is easily adaptable to a fixed width layout.
Using anyone of these layouts is simple. Immediately after your basic WordPress styles import, import one of these layouts. Here’s how to import the 3 column layout, with the content in the center.
/* Import a basic layout */ @import url('styles/3c-b.css');
The simplest method of turning any one of these layouts into a fixed-width layout is to add a width and centering margin to the #wrapper div.
#wrapper { margin: 0 auto; width: 960px; }
Bonus: Styling The Menu
If you’ve never taken an unordered list (that’s the smart markup generated by wp_page_menu
) and styled it to look like a menu before it can seem kinda weird. As a bonus, here’s the CSS I use when I start out creating menus for WordPress Themes.
#access { margin: 0 0 1.5em 0; overflow: auto; } .skip-link { position:absolute; left:-9000px; } .menu ul { list-style: none; margin: 0; } .menu ul ul { display: none; } .menu li { display: inline; } .menu a { display: block; float: left; }
It’s pretty simple but it’ll put you on sure footing. Good luck!
How To Create a WordPress Theme
This post concludes the WordPress Themes Tutorial series that shows you how to create a powerful WordPress Theme from scratch. Read it from the beginning and code yourself up something awesome.
- WordPress Theme Tutorial Introduction
- Theme Development Tools
- Creating a Theme HTML Structure
- Template and Directory Structure
- The Header Template
- The Index Template
- The Single Post, Post Attachment, & 404 Templates
- The Comments Template
- The Search Template & The Page Template
- The Archive, Author, Category & Tags Template
- The Sidebar Template
- Reset-Rebuild Theme CSS & Define Your Layouts
If you have any suggestions for posts that will fit in this series or complement what we’ve done so far I’d be glad to hear them. Let me know in the comments.
The WordPress Theme Sidebar Template
The Archive, Author, Category & Tags Template
Update: We’ve created a second edition of this article, with updated code samples and coverage of the latest theme development techniques. Check it out at The Archive Template. It’s part of The ThemeShaper WordPress Theme Tutorial: 2nd Edition.
Much like we did with index.php, we’re going to get one master template done right and use it to build our other templates. Our master Template in this case is the Archive Template.
What archive.php does (and all it’s related templates) is show posts based on a select criteria. A date range, or posts by a certain author, a category, or a tag. So, basically, it’s a lot like index.php. If you can read the name of the template you can figure out what it’s going to spit out.
Let’s start again with our template-template from the previous lessons and build on top of it.
<?php get_header(); ?> <div id="container"> <div id="content"> <div id="nav-above" class="navigation"> </div><!-- #nav-above --> <div id="post-<?php the_ID(); ?>" <?php post_class(); ?>> </div><!-- #post-<?php the_ID(); ?> --> <div id="nav-below" class="navigation"> </div><!-- #nav-below --> </div><!-- #content --> </div><!-- #container --> <?php get_sidebar(); ?> <?php get_footer(); ?>
The Archive Template
Here’s the scheme of an Archive Template:
Call the_post()
Check to see what kind of template this is
Produce an appropriate template
Rewind the posts with rewind_posts()
Do the usual loopy WordPress stuff
Here’s the #content of your archive.php Template. Note the Conditional Tags at the top for checking to see what kind of template we’re in.
<?php the_post(); ?> <?php if ( is_day() ) : ?> <h1 class="page-title"><?php printf( __( 'Daily Archives: <span>%s</span>', 'your-theme' ), get_the_time(get_option('date_format')) ) ?></h1> <?php elseif ( is_month() ) : ?> <h1 class="page-title"><?php printf( __( 'Monthly Archives: <span>%s</span>', 'your-theme' ), get_the_time('F Y') ) ?></h1> <?php elseif ( is_year() ) : ?> <h1 class="page-title"><?php printf( __( 'Yearly Archives: <span>%s</span>', 'your-theme' ), get_the_time('Y') ) ?></h1> <?php elseif ( isset($_GET['paged']) && !empty($_GET['paged']) ) : ?> <h1 class="page-title"><?php _e( 'Blog Archives', 'your-theme' ) ?></h1> <?php endif; ?> <?php rewind_posts(); ?> <?php global $wp_query; $total_pages = $wp_query->max_num_pages; if ( $total_pages > 1 ) { ?> <div id="nav-above" class="navigation"> <div class="nav-previous"><?php next_posts_link(__( '<span class="meta-nav">&laquo;</span> Older posts', 'your-theme' )) ?></div> <div class="nav-next"><?php previous_posts_link(__( 'Newer posts <span class="meta-nav">&raquo;</span>', 'your-theme' )) ?></div> </div><!-- #nav-above --> <?php } ?> <?php while ( have_posts() ) : the_post(); ?> <div id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <h2 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php printf( __('Permalink to %s', 'your-theme'), the_title_attribute('echo=0') ); ?>" rel="bookmark"><?php the_title(); ?></a></h2> <div class="entry-meta"> <span class="meta-prep meta-prep-author"><?php _e('By ', 'your-theme'); ?></span> <span class="author vcard"><a class="url fn n" href="<?php echo get_author_link( false, $authordata->ID, $authordata->user_nicename ); ?>" title="<?php printf( __( 'View all posts by %s', 'your-theme' ), $authordata->display_name ); ?>"><?php the_author(); ?></a></span> <span class="meta-sep"> | </span> <span class="meta-prep meta-prep-entry-date"><?php _e('Published ', 'your-theme'); ?></span> <span class="entry-date"><abbr class="published" title="<?php the_time('Y-m-dTH:i:sO') ?>"><?php the_time( get_option( 'date_format' ) ); ?></abbr></span> <?php edit_post_link( __( 'Edit', 'your-theme' ), "<span class="meta-sep">|</span>ntttttt<span class="edit-link">", "</span>nttttt" ) ?> </div><!-- .entry-meta --> <div class="entry-summary"> <?php the_excerpt( __( 'Continue reading <span class="meta-nav">&raquo;</span>', 'your-theme' ) ); ?> </div><!-- .entry-summary --> <div class="entry-utility"> <span class="cat-links"><span class="entry-utility-prep entry-utility-prep-cat-links"><?php _e( 'Posted in ', 'your-theme' ); ?></span><?php echo get_the_category_list(', '); ?></span> <span class="meta-sep"> | </span> <?php the_tags( '<span class="tag-links"><span class="entry-utility-prep entry-utility-prep-tag-links">' . __('Tagged ', 'your-theme' ) . '</span>', ", ", "</span>ntttttt<span class="meta-sep">|</span>n" ) ?> <span class="comments-link"><?php comments_popup_link( __( 'Leave a comment', 'your-theme' ), __( '1 Comment', 'your-theme' ), __( '% Comments', 'your-theme' ) ) ?></span> <?php edit_post_link( __( 'Edit', 'your-theme' ), "<span class="meta-sep">|</span>ntttttt<span class="edit-link">", "</span>ntttttn" ) ?> </div><!-- #entry-utility --> </div><!-- #post-<?php the_ID(); ?> --> <?php endwhile; ?> <?php global $wp_query; $total_pages = $wp_query->max_num_pages; if ( $total_pages > 1 ) { ?> <div id="nav-below" class="navigation"> <div class="nav-previous"><?php next_posts_link(__( '<span class="meta-nav">&laquo;</span> Older posts', 'your-theme' )) ?></div> <div class="nav-next"><?php previous_posts_link(__( 'Newer posts <span class="meta-nav">&raquo;</span>', 'your-theme' )) ?></div> </div><!-- #nav-below --> <?php } ?>
The Author Template
Not a lot is going to change with our Author Template. You’re going to like this one. Copy archive.php and rename it author.php. All we need to change is the page title section.
<h1 class="page-title author"><?php printf( __( 'Author Archives: <span class="vcard">%s</span>', 'your-theme' ), "<a class='url fn n' href='$authordata->user_url' title='$authordata->display_name' rel='me'>$authordata->display_name</a>" ) ?></h1> <?php $authordesc = $authordata->user_description; if ( !empty($authordesc) ) echo apply_filters( 'archive_meta', '<div class="archive-meta">' . $authordesc . '</div>' ); ?>
Easy, right?
The Category Template
The Category Template is another simple template now that we have a proper Archive Template. Copy archive.php and rename it category.php.
Now open up functions.php. We’re going to drop a custom function—from the brilliant Sandbox Theme—in there that’s going to make our Category Template a little more usable.
// For category lists on category archives: Returns other categories except the current one (redundant) function cats_meow($glue) { $current_cat = single_cat_title( '', false ); $separator = "n"; $cats = explode( $separator, get_the_category_list($separator) ); foreach ( $cats as $i => $str ) { if ( strstr( $str, ">$current_cat<" ) ) { unset($cats[$i]); break; } } if ( empty($cats) ) return false; return trim(join( $glue, $cats )); } // end cats_meow
Our custom function cats_meow() removes the current category from category pages. In other words, it gets rid of redundant categories in that list of categories we have just underneath the excerpt of our post.
Now, back in category.php, replace the page title section with the following code:
<h1 class="page-title"><?php _e( 'Category Archives:', 'your-theme' ) ?> <span><?php single_cat_title() ?></span></span></h1> <?php $categorydesc = category_description(); if ( !empty($categorydesc) ) echo apply_filters( 'archive_meta', '<div class="archive-meta">' . $categorydesc . '</div>' ); ?>
And in the .entry-utility div, replace …
<span class="cat-links"><span class="entry-utility-prep entry-utility-prep-cat-links"><?php _e( 'Posted in ', 'your-theme' ); ?></span><?php echo get_the_category_list(', '); ?></span>
with the modified …
<?php if ( $cats_meow = cats_meow(', ') ) : // Returns categories other than the one queried ?> <span class="cat-links"><?php printf( __( 'Also posted in %s', 'your-theme' ), $cats_meow ) ?></span> <span class="meta-sep"> | </span> <?php endif ?>
The Tags Template
The Tags Template is almost identical to the Category Template, except, well, it’s for Tags. You know the drill: copy archive.php and rename it tag.php.
We’ve also got a custom function—again, from the brilliant Sandbox Theme—for our functions.php called tag_ur_it(). It works just like cats_meow() except it removes redundant tags.
// For tag lists on tag archives: Returns other tags except the current one (redundant) function tag_ur_it($glue) { $current_tag = single_tag_title( '', '', false ); $separator = "n"; $tags = explode( $separator, get_the_tag_list( "", "$separator", "" ) ); foreach ( $tags as $i => $str ) { if ( strstr( $str, ">$current_tag<" ) ) { unset($tags[$i]); break; } } if ( empty($tags) ) return false; return trim(join( $glue, $tags )); } // end tag_ur_it
Now, in tag.php, replace your page title with:
<h1 class="page-title"><?php _e( 'Tag Archives:', 'your-theme' ) ?> <span><?php single_tag_title() ?></span></h1>
and in .entry-utility, replace …
<?php the_tags( '<span class="tag-links"><span class="entry-utility-prep entry-utility-prep-tag-links">' . __('Tagged ', 'your-theme' ) . '</span>', ", ", "</span>ntttttt<span class="meta-sep">|</span>n" ) ?>
with the modified …
<?php if ( $tag_ur_it = tag_ur_it(', ') ) : // Returns tags other than the one queried ?> <span class="tag-links"><?php printf( __( 'Also tagged %s', 'your-theme' ), $tag_ur_it ) ?></span> <?php endif; ?>
And that’s it!
How To Create a WordPress Theme
This post is part of a WordPress Themes Tutorial that will show you how to create a powerful WordPress Theme from scratch. Read it from the beginning and code yourself up something awesome.
WordPress Theme Tutorial Introduction
Theme Development Tools
Creating a Theme HTML Structure
Template and Directory Structure
The Header Template
The Index Template
The Single Post, Post Attachment, & 404 Templates
The Comments Template
The Search Template & The Page Template
The Archive, Author, Category & Tags Template
The Sidebar Template
Reset-Rebuild Theme CSS & Define Your Layouts
The WordPress Theme Search Template and Page Template
The Search Template and The Page Template are vital to any complete WordPress Theme. And they’re both really easy to code. For both these two Templates we’ll start with our template-template again but, of course, each one is going to take it’s own different path.
Update: We’ve created a second edition of this article, with updated code samples and coverage of the latest theme development techniques. Check it out at The WordPress Theme Search Template and Page Template. It’s part of The ThemeShaper WordPress Theme Tutorial: 2nd Edition.
The Search Template and The Page Template are vital to any complete WordPress Theme. And they’re both really easy to code. For both these two Templates we’ll start with our template-template again.
<?php get_header(); ?> <div id="container"> <div id="content"> <div id="nav-above" class="navigation"> </div><!-- #nav-above --> <div id="post-<?php the_ID(); ?>" <?php post_class(); ?>> </div><!-- #post-<?php the_ID(); ?> --> <div id="nav-below" class="navigation"> </div><!-- #nav-below --> </div><!-- #content --> </div><!-- #container --> <?php get_sidebar(); ?> <?php get_footer(); ?>
But, of course, each one is going to take it’s own different path.
The Search Template
In search.php
we’re going to reintroduce the loop back into our Template. This time with an IF statement—in case we don’t have any posts to loop through.
Here’s how it’ll work: IF we have posts, or, in other words, if there are posts that match the terms we’re searching for, THEN loop through them, sorta just like in index.php
, but IF we don’t have posts to loop through, or, if there aren’t any posts that match our search terms, give our searchers another chance at this searching business.
In code, it would look like this:
<?php get_header(); ?> <div id="container"> <div id="content"> <?php if ( have_posts() ) : ?> <?php while ( have_posts() ) : the_post() ?> <!-- this is our loop --> <?php endwhile; ?> <?php else : ?> <!-- here's where we'll put a search form if there're no posts --> <?php endif; ?> </div><!-- #content --> </div><!-- #container --> <?php get_sidebar(); ?> <?php get_footer(); ?>
Pretty straightforward, right? Almost.
I like to keep all my index-ey Templates the same: Post Title, Meta, Content (or excerpt), Utility links. But when WordPress searches for posts it also searches through Pages, which don’t need the post meta or utility links displayed. So, in our loop, we’re going to check and see if we’re dealing with a post or a page.
<?php if ( $post->post_type == 'post' ) { ?> <?php } ?>
Wrap any code with that IF statement and it will only show if we’re dealing with a page. Now that we understand what’s going on, here’s what the #content div of our search template will look like:
<?php if ( have_posts() ) : ?> <h1 class="page-title"><?php _e( 'Search Results for: ', 'your-theme' ); ?><span><?php the_search_query(); ?></span></h1> <?php global $wp_query; $total_pages = $wp_query->max_num_pages; if ( $total_pages > 1 ) { ?> <div id="nav-above" class="navigation"> <div class="nav-previous"><?php next_posts_link(__( '<span class="meta-nav">«</span> Older posts', 'your-theme' )) ?></div> <div class="nav-next"><?php previous_posts_link(__( 'Newer posts <span class="meta-nav">»</span>', 'your-theme' )) ?></div> </div><!-- #nav-above --> <?php } ?> <?php while ( have_posts() ) : the_post() ?> <div id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <h2 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php printf( __('Permalink to %s', 'your-theme'), the_title_attribute('echo=0') ); ?>" rel="bookmark"><?php the_title(); ?></a></h2> <?php if ( $post->post_type == 'post' ) { ?> <div class="entry-meta"> <span class="meta-prep meta-prep-author"><?php _e('By ', 'your-theme'); ?></span> <span class="author vcard"><a class="url fn n" href="<?php echo get_author_link( false, $authordata->ID, $authordata->user_nicename ); ?>" title="<?php printf( __( 'View all posts by %s', 'your-theme' ), $authordata->display_name ); ?>"><?php the_author(); ?></a></span> <span class="meta-sep"> | </span> <span class="meta-prep meta-prep-entry-date"><?php _e('Published ', 'your-theme'); ?></span> <span class="entry-date"><abbr class="published" title="<?php the_time('Y-m-dTH:i:sO') ?>"><?php the_time( get_option( 'date_format' ) ); ?></abbr></span> <?php edit_post_link( __( 'Edit', 'your-theme' ), "<span class="meta-sep">|</span>ntttttt<span class="edit-link">", "</span>nttttt" ) ?> </div><!-- .entry-meta --> <?php } ?> <div class="entry-summary"> <?php the_excerpt( __( 'Continue reading <span class="meta-nav">»</span>', 'your-theme' ) ); ?> <?php wp_link_pages('before=<div class="page-link">' . __( 'Pages:', 'your-theme' ) . '&after=</div>') ?> </div><!-- .entry-summary --> <?php if ( $post->post_type == 'post' ) { ?> <div class="entry-utility"> <span class="cat-links"><span class="entry-utility-prep entry-utility-prep-cat-links"><?php _e( 'Posted in ', 'your-theme' ); ?></span><?php echo get_the_category_list(', '); ?></span> <span class="meta-sep"> | </span> <?php the_tags( '<span class="tag-links"><span class="entry-utility-prep entry-utility-prep-tag-links">' . __('Tagged ', 'your-theme' ) . '</span>', ", ", "</span>ntttttt<span class="meta-sep">|</span>n" ) ?> <span class="comments-link"><?php comments_popup_link( __( 'Leave a comment', 'your-theme' ), __( '1 Comment', 'your-theme' ), __( '% Comments', 'your-theme' ) ) ?></span> <?php edit_post_link( __( 'Edit', 'your-theme' ), "<span class="meta-sep">|</span>ntttttt<span class="edit-link">", "</span>ntttttn" ) ?> </div><!-- #entry-utility --> <?php } ?> </div><!-- #post-<?php the_ID(); ?> --> <?php endwhile; ?> <?php global $wp_query; $total_pages = $wp_query->max_num_pages; if ( $total_pages > 1 ) { ?> <div id="nav-below" class="navigation"> <div class="nav-previous"><?php next_posts_link(__( '<span class="meta-nav">«</span> Older posts', 'your-theme' )) ?></div> <div class="nav-next"><?php previous_posts_link(__( 'Newer posts <span class="meta-nav">»</span>', 'your-theme' )) ?></div> </div><!-- #nav-below --> <?php } ?> <?php else : ?> <div id="post-0" class="post no-results not-found"> <h2 class="entry-title"><?php _e( 'Nothing Found', 'your-theme' ) ?></h2> <div class="entry-content"> <p><?php _e( 'Sorry, but nothing matched your search criteria. Please try again with some different keywords.', 'your-theme' ); ?></p> <?php get_search_form(); ?> </div><!-- .entry-content --> </div> <?php endif; ?>
The Page Template
You know what the Page Template is for. WordPress thinks of it as a post out of chronological order. We think of it as, well a page. But largely, it’s defined by what it doesn’t have: all the usual trappings of a blog post.
Except comments. Sometimes pages have comments. I don’t like them there. You might one day. So we’ll have to deal with that somehow. How about with … custom fields. If you want comments on a page you can add a custom field with Name and Value of “comments” to your page. Sounds good to me.
Here’s what you’ll need to for a perfect WordPress Page Template:
<?php get_header(); ?> <div id="container"> <div id="content"> <?php the_post(); ?> <div id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <h1 class="entry-title"><?php the_title(); ?></h1> <div class="entry-content"> <?php the_content(); ?> <?php wp_link_pages('before=<div class="page-link">' . __( 'Pages:', 'your-theme' ) . '&after=</div>') ?> <?php edit_post_link( __( 'Edit', 'your-theme' ), '<span class="edit-link">', '</span>' ) ?> </div><!-- .entry-content --> </div><!-- #post-<?php the_ID(); ?> --> <?php if ( get_post_custom_values('comments') ) comments_template() // Add a custom field with Name and Value of "comments" to enable comments on this page ?> </div><!-- #content --> </div><!-- #container --> <?php get_sidebar(); ?> <?php get_footer(); ?>
How To Create a WordPress Theme
This post is part of a WordPress Themes Tutorial that will show you how to create a powerful WordPress Theme from scratch. Read it from the beginning and code yourself up something awesome.
- WordPress Theme Tutorial Introduction
- Theme Development Tools
- Creating a Theme HTML Structure
- Template and Directory Structure
- The Header Template
- The Index Template
- The Single Post, Post Attachment, & 404 Templates
- The Comments Template
- The Search Template & The Page Template
- The Archive, Author, Category & Tags Template
- The Sidebar Template
- Reset-Rebuild Theme CSS & Define Your Layouts
The WordPress Theme Comments Template
Update: We’ve created a second edition of this article, with updated code samples and coverage of the latest theme development techniques. Check it out at The WordPress Theme Comments Template. It’s part of The ThemeShaper WordPress Theme Tutorial: 2nd Edition.
I hate the Comments Template. There, I said it. It can be a confusing mess. In version 2.7, WordPress introduced a simpler way of producing Comments Templates—which was no help if you wanted to separate your comments and trackbacks or have custom comment markup. It’s still confusing.
Luckily for you, I’ve sorted it out. Confusing still, yes. But sorted out. For this tutorial on the Comments Template I’m basically going to walk you through what’s going to happen, show you some custom code snippets you’ll need to add to your functions.php
, and then drop the whole thing on you. Hopefully, it’ll start to make sense. But at they very least you’ll have a wicked comments template.
Let’s take a look at a quick list of what’s going on in this Template.
- Prevent loading for bots and on password protected posts
- Check if there are comments
- Count the number of comments and trackbacks (or pings)
- If there are comments, show the comments—with navigation for paginated comments
- If there are trackbacks, show the trackbacks
- If comments are open, show the comments “respond” form
That’s a lot of stuff going on for one template. But written out like that, it’s pretty straightforward.
Custom Callbacks for Comments and Trackbacks
Now, with WordPress 2.7 came the function wp_list_comments()
that conveniently spits out an ordered list of comments and trackbacks markup for your post (threaded too). Convenient if you want that. And we don’t. We want separated threaded comments and trackbacks, with our own custom markup.
To make the comments template code I’m going to give you work, you’ll need a set of custom callbacks for your list of Comments and Trackbacks. Add the following 2 functions to your theme functions.php
file.
// Custom callback to list comments in the your-theme style function custom_comments($comment, $args, $depth) { $GLOBALS['comment'] = $comment; $GLOBALS['comment_depth'] = $depth; ?> <li id="comment-<?php comment_ID() ?>" <?php comment_class() ?>> <div class="comment-author vcard"><?php commenter_link() ?></div> <div class="comment-meta"><?php printf(__('Posted %1$s at %2$s <span class="meta-sep">|</span> <a href="%3$s" title="Permalink to this comment">Permalink</a>', 'your-theme'), get_comment_date(), get_comment_time(), '#comment-' . get_comment_ID() ); edit_comment_link(__('Edit', 'your-theme'), ' <span class="meta-sep">|</span> <span class="edit-link">', '</span>'); ?></div> <?php if ($comment->comment_approved == '0') _e("ttttt<span class='unapproved'>Your comment is awaiting moderation.</span>n", 'your-theme') ?> <div class="comment-content"> <?php comment_text() ?> </div> <?php // echo the comment reply link if($args['type'] == 'all' || get_comment_type() == 'comment') : comment_reply_link(array_merge($args, array( 'reply_text' => __('Reply','your-theme'), 'login_text' => __('Log in to reply.','your-theme'), 'depth' => $depth, 'before' => '<div class="comment-reply-link">', 'after' => '</div>' ))); endif; ?> <?php } // end custom_comments
// Custom callback to list pings function custom_pings($comment, $args, $depth) { $GLOBALS['comment'] = $comment; ?> <li id="comment-<?php comment_ID() ?>" <?php comment_class() ?>> <div class="comment-author"><?php printf(__('By %1$s on %2$s at %3$s', 'your-theme'), get_comment_author_link(), get_comment_date(), get_comment_time() ); edit_comment_link(__('Edit', 'your-theme'), ' <span class="meta-sep">|</span> <span class="edit-link">', '</span>'); ?></div> <?php if ($comment->comment_approved == '0') _e('ttttt<span class="unapproved">Your trackback is awaiting moderation.</span>n', 'your-theme') ?> <div class="comment-content"> <?php comment_text() ?> </div> <?php } // end custom_pings
Those look kind of hairy don’t they? But you’re better off for it. Now you have access to the comments markup. I think the markup I’ve got in there is pretty sweet and will let you make a lot of changes with just CSS alone, but if you do want to alter the markup, well, there it is.
We’ll also need a special custom function that the custom_comments()
is calling. This function will markup the gravatar we’re using so it fits into the microformat schema for hcard.
// Produces an avatar image with the hCard-compliant photo class function commenter_link() { $commenter = get_comment_author_link(); if ( ereg( '<a[^>]* class=[^>]+>', $commenter ) ) { $commenter = ereg_replace( '(<a[^>]* class=['"]?)', '\1url ' , $commenter ); } else { $commenter = ereg_replace( '(<a )/', '\1class="url "' , $commenter ); } $avatar_email = get_comment_author_email(); $avatar = str_replace( "class='avatar", "class='photo avatar", get_avatar( $avatar_email, 80 ) ); echo $avatar . ' <span class="fn n">' . $commenter . '</span>'; } // end commenter_link
If you want to change the default size of your gravatar just change the 80 in get_avatar( $avatar_email, 80 ) )
. The 80 is the size in pixels of your gravatar.
The Comments Template
I haven’t scared you away have I? I’ll be honest, it’s not that scary. Here’s the comments template with some helpful PHP comments that should guide you along in understanding what’s happening.
<?php /* The Comments Template — with, er, comments! */ ?> <div id="comments"> <?php /* Run some checks for bots and password protected posts */ ?> <?php $req = get_option('require_name_email'); // Checks if fields are required. if ( 'comments.php' == basename($_SERVER['SCRIPT_FILENAME']) ) die ( 'Please do not load this page directly. Thanks!' ); if ( ! empty($post->post_password) ) : if ( $_COOKIE['wp-postpass_' . COOKIEHASH] != $post->post_password ) : ?> <div class="nopassword"><?php _e('This post is password protected. Enter the password to view any comments.', 'your-theme') ?></div> </div><!-- .comments --> <?php return; endif; endif; ?> <?php /* See IF there are comments and do the comments stuff! */ ?> <?php if ( have_comments() ) : ?> <?php /* Count the number of comments and trackbacks (or pings) */ $ping_count = $comment_count = 0; foreach ( $comments as $comment ) get_comment_type() == "comment" ? ++$comment_count : ++$ping_count; ?> <?php /* IF there are comments, show the comments */ ?> <?php if ( ! empty($comments_by_type['comment']) ) : ?> <div id="comments-list" class="comments"> <h3><?php printf($comment_count > 1 ? __('<span>%d</span> Comments', 'your-theme') : __('<span>One</span> Comment', 'your-theme'), $comment_count) ?></h3> <?php /* If there are enough comments, build the comment navigation */ ?> <?php $total_pages = get_comment_pages_count(); if ( $total_pages > 1 ) : ?> <div id="comments-nav-above" class="comments-navigation"> <div class="paginated-comments-links"><?php paginate_comments_links(); ?></div> </div><!-- #comments-nav-above --> <?php endif; ?> <?php /* An ordered list of our custom comments callback, custom_comments(), in functions.php */ ?> <ol> <?php wp_list_comments('type=comment&callback=custom_comments'); ?> </ol> <?php /* If there are enough comments, build the comment navigation */ ?> <?php $total_pages = get_comment_pages_count(); if ( $total_pages > 1 ) : ?> <div id="comments-nav-below" class="comments-navigation"> <div class="paginated-comments-links"><?php paginate_comments_links(); ?></div> </div><!-- #comments-nav-below --> <?php endif; ?> </div><!-- #comments-list .comments --> <?php endif; /* if ( $comment_count ) */ ?> <?php /* If there are trackbacks(pings), show the trackbacks */ ?> <?php if ( ! empty($comments_by_type['pings']) ) : ?> <div id="trackbacks-list" class="comments"> <h3><?php printf($ping_count > 1 ? __('<span>%d</span> Trackbacks', 'your-theme') : __('<span>One</span> Trackback', 'your-theme'), $ping_count) ?></h3> <?php /* An ordered list of our custom trackbacks callback, custom_pings(), in functions.php */ ?> <ol> <?php wp_list_comments('type=pings&callback=custom_pings'); ?> </ol> </div><!-- #trackbacks-list .comments --> <?php endif /* if ( $ping_count ) */ ?> <?php endif /* if ( $comments ) */ ?> <?php /* If comments are open, build the respond form */ ?> <?php if ( 'open' == $post->comment_status ) : ?> <div id="respond"> <h3><?php comment_form_title( __('Post a Comment', 'your-theme'), __('Post a Reply to %s', 'your-theme') ); ?></h3> <div id="cancel-comment-reply"><?php cancel_comment_reply_link() ?></div> <?php if ( get_option('comment_registration') && !$user_ID ) : ?> <p id="login-req"><?php printf(__('You must be <a href="%s" title="Log in">logged in</a> to post a comment.', 'your-theme'), get_option('siteurl') . '/wp-login.php?redirect_to=' . get_permalink() ) ?></p> <?php else : ?> <div class="formcontainer"> <form id="commentform" action="<?php echo get_option('siteurl'); ?>/wp-comments-post.php" method="post"> <?php if ( $user_ID ) : ?> <p id="login"><?php printf(__('<span class="loggedin">Logged in as <a href="%1$s" title="Logged in as %2$s">%2$s</a>.</span> <span class="logout"><a href="%3$s" title="Log out of this account">Log out?</a></span>', 'your-theme'), get_option('siteurl') . '/wp-admin/profile.php', wp_specialchars($user_identity, true), wp_logout_url(get_permalink()) ) ?></p> <?php else : ?> <p id="comment-notes"><?php _e('Your email is <em>never</em> published nor shared.', 'your-theme') ?> <?php if ($req) _e('Required fields are marked <span class="required">*</span>', 'your-theme') ?></p> <div id="form-section-author" class="form-section"> <div class="form-label"><label for="author"><?php _e('Name', 'your-theme') ?></label> <?php if ($req) _e('<span class="required">*</span>', 'your-theme') ?></div> <div class="form-input"><input id="author" name="author" type="text" value="<?php echo $comment_author ?>" size="30" maxlength="20" tabindex="3" /></div> </div><!-- #form-section-author .form-section --> <div id="form-section-email" class="form-section"> <div class="form-label"><label for="email"><?php _e('Email', 'your-theme') ?></label> <?php if ($req) _e('<span class="required">*</span>', 'your-theme') ?></div> <div class="form-input"><input id="email" name="email" type="text" value="<?php echo $comment_author_email ?>" size="30" maxlength="50" tabindex="4" /></div> </div><!-- #form-section-email .form-section --> <div id="form-section-url" class="form-section"> <div class="form-label"><label for="url"><?php _e('Website', 'your-theme') ?></label></div> <div class="form-input"><input id="url" name="url" type="text" value="<?php echo $comment_author_url ?>" size="30" maxlength="50" tabindex="5" /></div> </div><!-- #form-section-url .form-section --> <?php endif /* if ( $user_ID ) */ ?> <div id="form-section-comment" class="form-section"> <div class="form-label"><label for="comment"><?php _e('Comment', 'your-theme') ?></label></div> <div class="form-textarea"><textarea id="comment" name="comment" cols="45" rows="8" tabindex="6"></textarea></div> </div><!-- #form-section-comment .form-section --> <div id="form-allowed-tags" class="form-section"> <p><span><?php _e('You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes:', 'your-theme') ?></span> <code><?php echo allowed_tags(); ?></code></p> </div> <?php do_action('comment_form', $post->ID); ?> <div class="form-submit"><input id="submit" name="submit" type="submit" value="<?php _e('Post Comment', 'your-theme') ?>" tabindex="7" /><input type="hidden" name="comment_post_ID" value="<?php echo $id; ?>" /></div> <?php comment_id_fields(); ?> <?php /* Just … end everything. We're done here. Close it up. */ ?> </form><!-- #commentform --> </div><!-- .formcontainer --> <?php endif /* if ( get_option('comment_registration') && !$user_ID ) */ ?> </div><!-- #respond --> <?php endif /* if ( 'open' == $post->comment_status ) */ ?> </div><!-- #comments -->
And that’s it. You’ve got a pretty sweet custom Comments Template to call your very own.
How To Create a WordPress Theme
This post is part of a WordPress Themes Tutorial that will show you how to create a powerful WordPress Theme from scratch. Read it from the beginning and code yourself up something awesome.
- WordPress Theme Tutorial Introduction
- Theme Development Tools
- Creating a Theme HTML Structure
- Template and Directory Structure
- The Header Template
- The Index Template
- The Single Post, Post Attachment, & 404 Templates
- The Comments Template
- The Search Template & The Page Template
- The Archive, Author, Category & Tags Template
- The Sidebar Template
- Reset-Rebuild Theme CSS & Define Your Layouts