Dissecting WordPress Themes Part 2: The Loop

In the previous article, Dissecting WordPress Themes Part 1: Creating a Theme, we created a new WordPress theme called Trace. The purpose of this theme is to help us learn the mechanics of how a theme functions by displaying simple messages on the browser page to trace the execution of the theme’s various template files. So far, we have a style sheet, style.css, containing only the required comment block that serves to identify the theme to WordPress. We also have four template files, including the required index.php, containing a mixture of HTML and PHP that cooperate to generate a simple web page.

In this article, we’ll continue developing our theme to include retrieving posts from the WordPress database using the Loop and displaying some basic information about each. We’ll add a simple CSS rule to our style sheet in order to demonstrate how it gets linked in the generated output. We’ll also take a look at a couple of important WordPress settings and how they affect post retrieval and URL generation.

Our index.php file currently includes calls to get_header(), get_sidebar(), and get_footer() to include the header.php, sidebar.php, and footer.php files, respectively. These PHP functions are called template tags. Although they do more than merely including their associated template files, for our purposes here that’s their function. There are many other template tags in WordPress that are available to template and plugin developers for adding functionality to their sites.

Prepare the Database

Next, we’ll augment our index.php file to retrieve some posts from the WordPress database and include them in the generated web page. But first, let’s start from a common baseline by deleting the sample post and page that are created during the WordPress installation. To do that, log in to the Administration Screen and navigate to the Posts→All Posts page. Hover your mouse over the Hello world! sample post and click the Trash link that appears below the post title. If you have any other posts in your installation, delete those as well.

Trash a post

On the Posts page, click the Trash link to remove a post.

Now visit the Pages→All Pages page, hover over the Sample Page page title, and click the Trash link that appears below the page title. If you have any other pages in your installation, delete those also.

Pages Page

On the Pages page, you can click the Trash link to remove a page.

Check for Posts

Now, back to index.php. Our intention on the site’s home page is to display a list of posts in reverse-chronological order. However, if there are no posts (as is the case now), we want to display a message to that effect. To achieve this, we’ll use a simple if-else statement as shown below. Add the highlighted lines (4-8) to your index.php file.

<?php
  get_header();
  echo "<p>In index.php</p>\n";
  if (have_posts()) {
      echo "<p>There is a post to display</p>\n";
  } else {
      echo "<p>There are no posts to display</p>\n";
  }
  get_sidebar();
  get_footer();
?>

The WordPress have_posts() call will return true if there are any posts and false if there are no posts. Hover your mouse over the site name in the top toolbar (Lab 1 in my environment) and click the Visit Site link that appears below the site’s name.

Visit Site Link

In the toolbar the Visit Site link provdes a convenient way to view your site.

Since you just deleted all of the posts in the database, the else part of the if-else statement will execute and you will see the message There are no posts to display as shown below.

There are currently no posts to display

The messages allow us to trace the execution of the template files. There are no posts to display since we cleaned out the database.

Next, let’s create a new post. Return to the Administration Screen and select the Posts→Add New link from the menu. In the title field, enter the post title Post 1, and in the editor below the title, enter the text This is Post 1. Leave the other fields with their default values and click the Publish button on the right as shown.

Add New Post

On the Add New Post page we can enter a post title and contents. We’ll add more information to the post in later articles.

Once again, click the Visit Site link. Since there is now a post in the database, the have_posts() call returns true and you should see the message There is a post to display.

There is a post to display

Now that we have a post in the database, our trace message indicates that there is a post to display.

Code the Loop

Next, let’s add some code to actually display posts in place of the message. For that, we’ll engage the famous WordPress Loop (note the capital “L” in proper reverence). Additionally, we’ll add some HTML table markup to make our posts a little easier to read. Examine the revised index.php code below.

<?php
get_header();
echo "<p>In index.php</p>\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th></tr>\n";
	while (have_posts()) {   // start the loop
		the_post();
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td>", get_the_title(), "</td>";
		echo "</tr>\n";
		}
	echo "</table>\n";
} else {
	echo "<p>no posts to display</p>\n";
}

get_sidebar();
get_footer();
?>

Within the if block we now have a while loop that iterates through posts in the database. It will continue to execute as long as the have_posts() call returns true. Once have_posts() returns false, the loop will end. That brings to mind several questions:

  1. Which posts will be retrieved by the Loop?
  2. How many posts will be retrieved?
  3. In what order will they be retrieved?

The interesting thing about the Loop is that “it depends.” We’ll examine this concept in detail soon.

Note: You will often see the Loop coded like this:

if (have_posts()) : while (have_posts()) : the_post();
  loop code here
endwhile; else:
  error message
endif;

This idiom uses the “alternative” PHP syntax for control structures (if, while, for, foreach, and switch), where the opening brace is replaced with a colon (:) and the closing brace is replaced with a keyword (endif, endwhile, endfor, endforeach, and endswitch). While this syntax has the advantage of putting conditional and loop control expressions all on the same line, I find it a little confusing because it makes it a bit more difficult (at least for me) to see the block structure of the code. I prefer to use the normal PHP syntax:

if (have_posts()) {
  while (have_posts()) {
    the_post();
    loop code here
  }
} else {
  error message
}

The two coding styles are equivalent and you should choose the one that is easier for you to understand.

Just prior to the while loop, we echo the <table> tag and a header row with the post ID and Title headers. Inside the while loop the first statement we come across is a call to the_post(). This function advances the current post pointer to the next post.

Following the call to the_post() are calls to get_the_ID() to retrieve the post’s internal unique identifier and get_the_title() to retrieve the post’s title. The values returned are wrapped in table row and data tags to format the results. If you refresh your browser, you should now see the post we entered earlier.

Displaying the post

Using The Loop we can retrieve posts from the database and display them in the browser.

Link the Style Sheet

Let’s make a small addition to our style sheet to make the post data a little easier to read. We’ll add a thin border around the table cells. To accomplish this we need to add a couple of CSS rules to style.css and arrange for the style sheet to be linked to the page. First, add the rules to style.css as follows. The first rule adds a 1 pixel, solid, black border around the table and its cells, while the second rule eliminates the double borders caused by the first rule.

/*
Theme Name: Trace
Theme URI: http://webterranean.net/
Description: A theme to trace WordPress Themes.
Author: Charlie Butler
Author URI: http://webterranean.net/
Version: 1.0
Tags: white, one-column, fixed-width
License: GNU General Public License
License URI: license.txt
General Comments (optional).
*/

table,
th,
td {
   border: 1px solid black;
}

table {
   border-collapse: collapse;
}

Next, we’ll need to add a <head> section to header.php with the code below. The <link> element tells the browser to load the style sheet for our site. We’re using the blog_info(‘stylesheet_url’) call to get the location of our style sheet, which is inserted as the value of the href attribute.

<?php
  echo "<!DOCTYPE html>\n";
  echo "<html>\n";
  echo "<head>\n";
  echo "<link rel='stylesheet' type='text/css' href='";
       bloginfo('stylesheet_url'); echo "'/>\n";
  echo "</head>\n";
  echo "<body>\n";
  echo "<p>In header.php</p>\n";
?>

Save your changes and refresh your browser. Your page should now look like the snapshot below.

Post table with borders

Due to our small CSS addition, the post table has borders.

If you view the source for this page you can see that we now have a <head> section with a <link> element, referencing our style sheet. The blog_info() call echos the full URL to retrieve our style sheet from the themes/trace directory.

<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' type='text/css' href='http://localhost/lab1/wp-content/themes/trace/style.css'/>
</head>
<body>
<p>In header.php</p>
<p>In index.php</p>
<table>
<tr><th>ID</th><th>Title</th></tr>
<tr><td>6</td><td>Post 1</td></tr>
</table>
<p>In sidebar.php</p>
<p>In footer.php</p>
</body>
</html>

Back to the Loop

Let’s now see if we can answer our questions from above. In keeping with the spirit of the Loop, we’ll answer them in reverse chronological order. :)

Using the Posts→Add New page, add five more posts as shown in the table below. Be sure to click Publish to add each post to the database.

Note: Feel free to add more imaginative posts if you’re feeling creative.
Post Title Post Contents
Post 2 This is Post 2.
Post 3 This is Post 3.
Post 4 This is Post 4.
Post 5 This is Post 5.
Post 6 This is Post 6.

Now, click the Visit Site link again. You should see page output similar to that below (your IDs may be different depending upon other activity in your database).

Adding more posts

As we add more posts, The Loop retrieves them from the database and displays them in the browser. By default, posts are displayed in reverse-chronological order.

Notice that posts are listed in reverse order of entry. This probably doesn’t surpise you because you’re used to seeing blog posts displayed in reverse-chronological order. We didn’t have to take any special action to make that happen—that’s the default order of retrieval for the Loop. That anwers question #3 from above.

So far, the Loop has returned all of the posts that we entered. Is there a limit? Go back to the Administration Screen and navigate to the Settings→Reading page. In the Blog pages show at most setting, change the value to 5 and click Save Changes.

Blog pages show at most setting

The Blog pages show at most setting controls the number of posts retrieved for each page.

Click Visit Site again. Notice that now only five of the six posts we entered are displayed. The oldest post, Post 1, is not displayed. That answers question #2 from above; the number of posts retrieved by the loop is governed by the Blog pages show at most setting.

Posts are limited by setting

Some posts are not shown due to the Blog pages show at most setting limit.

Page Navigation

This brings up another question: once we exceed the Blog pages show at most setting, how do we get to the other posts? We need to add some page navigation links. WordPress template tags come to the rescue. We can add calls to next_posts_link() and previous_posts_link() to show older and newer posts, respectively. Add these lines to index.php as shown below. The string passed to each call is used as the link display text.

<?php
get_header();
echo "<p>In index.php</p>\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th></tr>\n";
	while (have_posts()) {   // start the loop
		the_post();
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td>", get_the_title(), "</td>";
		echo "</tr>\n";
		}
	echo "</table>\n";
	next_posts_link("Older posts");
	echo "<br/>";
	previous_posts_link("Newer posts");
} else {
	echo "<p>no posts to display</p>\n";
}

get_sidebar();
get_footer();
?>

Refresh your browser. You should now see the Older posts link. If you hover over the link you’ll notice that the generated URL is http://localhost/lab1/page/2/.

Older posts link

The Older Posts link is displayed indicating that there are some older posts that are available, but not displayed.

Click the Older posts link. You now see the first post (Post 1) and the Newer posts link. The Older posts link is not showing because you are viewing the oldest post. Note that the URL for this link is http://localhost/lab1, which will take you back to the most recent posts.

Newer posts link

The Newer posts link is displayed, indicating that there are newer posts that are available, but not displayed.

If there are more than two pages of posts available, you will see both the Older posts and Newer posts links allowing you to page backwards and forwards. You can test this by setting the Blog pages show at most setting to 2 posts.

Go back to the Settings→Reading page and change the Blog pages show at most setting back to its default value of 10. Click Save Changes. Click Visit Site and you should see all six posts once again with no page navigation links displayed. This indicates that you are viewing all available posts.

Link the Post Title

At this point our index.php is a crude representation of a blog’s home page, showing a list of posts in reverse-chronological order. Next, let’s add a link to each post’s title that will navigate to a new page showing only the selected post. Take a look at the revised index.php code below.

<?php
get_header(); 
echo "<p>In index.php</p>\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th></tr>\n";
	while (have_posts()) {   // start the loop
		the_post();
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td><a href='", get_permalink(), "'>",
			get_the_title(), "</a></td>";
		echo "</tr>\n";
		}
	echo "</table>\n";
	next_posts_link("Older posts");
	echo "<br/>";
	previous_posts_link("Newer posts");
} else {
	echo "<p>no posts to display</p>\n";
}
    
get_sidebar();
get_footer();
?>

Here, we’ve added a call to the get_permalink() function and inserted its output in an anchor element. The returned URL becomes the value of the href attribute while the post’s title is used as the link text. Go ahead and click Visit Site again to see the results.

Linked post titles

Post titles are now hyper-linked so that you can navigate to them individually.

As you can see, each post’s title is now a hyperlink. Hover your mouse over each hyperlink to see what URL is generated for each post. The generated URLs are of the form http://localhost/lab1/?p={ID}. The query string portion of the URL corresponds to each post’s internal ID. What will happen if we click on one of those links? After all, we haven’t created any additional pages to serve as destinations for the links. Go ahead and click on the link in the first post (Post 1). You should see the browser output below.

Single-post view

Using the same Loop in index.php, we now see only the selected post.

First notice that only the post we selected is showing in the table. Also notice that the trace message In index.php announces that we are still running our index.php template file. But we didn’t make any changes to the Loop code in index.php to isolate a single post. Now, click your browser’s Back button to return to the full list of posts and select a different one. Once again, we see just the selected post on the index.php page. So, now we’ve answered question #1 above. If you’re on a page that is requesting a single post via a URL query string, the Loop will return only that post. However, if you don’t specify a particular post, it will return all posts (subject to the Blog posts show at most setting in Settings→Reading as described earlier).

Permalinks

Let’s take a look at another WordPress setting. Navigate to the Settings→Permalinks page in the Administration Screen. Notice that the default setting that contains the query string in the URL is selected. Change that setting to Day and name. Click Save Changes.

Permalinks setting

The Permalinks setting affects the format of the generated URLs.

Click Visit Site. You see the same page as before, listing all of the posts. However, if you once again hover over the hyperlinks, you’ll notice that the format of the link has changed. The URLs generated by get_permalink() no longer have a query string and the format has changed to http://localhost/lab1/{year}/{month}/{day}/{post-slug}. If you click on the links, you will again see the post selected, but with a different URL. For example, the page for Post 3 is shown next.

URLs in day/name format

Here, the single-post URL format has changed to day/name format, but no changes were required to the Loop code.

The year, month, and day segments of the URL correspond to the publish date. By default, the post slug is formed from the post title with spaces replaced by hyphens, but this can be customized on the Edit Post page.

In this article we coded a basic Loop to retrieve posts from the WordPress database and display some basic information about each using template tags. We also demonstrated how to link style.css to the index.php page and introduced a simple CSS rule to format a table of posts. We explored the function of two WordPress settings: Blog pages show at most n posts and Permalink. In addition, we added some primitive page navigation to move through pages of posts. We then wrapped up by linking post titles and demonstrating the effect on Loop behavior when selecting a single post.

In the next article of the series, we’ll begin our exploration of the WordPress template hierarchy by looking at the single-post template files.

References

Speak Your Mind

*