As an addition to my article on How to Use the Thesis Custom Loop API, here is a Custom Loop starter template you can use for your own loops. Every loop you can customize in Thesis 1.8 is stubbed in, and comments before each method provide helpful reference information. All you need to do is replace the one-line stub methods with your own custom loop code. You can delete any loop method you’re not customizing, or leave them in place for the future; they simply apply the appropriate default Thesis loop.
(The comments more than double the file size. If you find them distracting, here’s a truly minimal version.)
Thesis Custom Loop Template
/**
* These are the different Thesis loops that can be customized.
*
* Replace the calls to the default Thesis loops with your own code.
* You can also delete any unused loop methods.
*
* @link http://codex.wordpress.org/Conditional_Tags More details of
* which page requests trigger which specific loops.
* @link https://aldosoft.com/blog/2011/01/the-thesis-custom-loop-api/ Detailed
* article about how to use the Custom Loop API.
*/
class my_looper extends thesis_custom_loop {
/**
* The loop for the blog index page.
*
* By default this is the home or top-level index page.
* Can be changed to any page using Settings > Reading > Posts Page.
*
* This loop is used by the {@link archive()} loop (and all child loops)
* unless Thesis > Design Options > Display Options > Archives is
* set to Titles Only.
*/
function home() {
thesis_loop::home();
}
/**
* The loop for the front page of the site if configured in
* Settings > Reading > Front Page.
*
* Has no effect if no front page is set.
*
* By default, delegates to the {@link page()} loop.
*/
function front() {
thesis_loop::front();
}
/**
* The loop for displaying an individual post, i.e. the
* one-article-per-page view of a post.
*
* Sometimes referred to as "single-entry pages".
*/
function single() {
thesis_loop::single();
}
/**
* The loop for displaying an individual attachment on a post,
* usually an image.
*
* By default, delegates to the {@link single()} loop.
*/
function attachment() {
thesis_loop::attachment();
}
/**
* The loop for a static, individual page.
*/
function page() {
thesis_loop::page();
}
/**
* Default loop for archive-style pages.
*
* This loop is the default for category, tag, tax, author,
* day, month, year, and search pages.
*
* Important: Delegates to {@link home()} unless
* Thesis > Design Options > Display Options > Archives is
* set to Titles Only.
*/
function archive() {
thesis_loop::archive();
}
/**
* Loop for category pages.
*
* By default, delegates to the {@link archive()} loop.
*/
function category() {
thesis_loop::category();
}
/**
* Loop for tag pages.
*
* By default, delegates to the {@link archive()} loop.
*/
function tag() {
thesis_loop::tag();
}
/**
* Loop for custom taxonomy pages.
*
* Has no effect unless you have defined a custom taxonomy.
*
* By default, delegates to the {@link archive()} loop.
*/
function tax() {
thesis_loop::tax();
}
/**
* Loop for author pages.
*
* By default, delegates to the {@link archive()} loop.
*/
function author() {
thesis_loop::author();
}
/**
* Loop for year-level date archive pages.
*
* By default, delegates to the {@link archive()} loop.
*/
function year() {
thesis_loop::year();
}
/**
* Loop for month-level date archive pages.
*
* By default, delegates to the {@link archive()} loop.
*/
function month() {
thesis_loop::month();
}
/**
* Loop for day-level date archive pages.
*
* By default, delegates to the {@link archive()} loop.
*/
function day() {
thesis_loop::day();
}
/**
* Loop for search results page.
*
* By default, delegates to the {@link archive()} loop IF THERE ARE RESULTS.
* By default, just shows a search form if there are no results.
*
* A custom search loop needs to deal with both possibilities; see the
* {@link thesis_loop::search()} for example of how to do so.
*/
function search() {
thesis_loop::search();
}
/**
* Loop for the "404 Not Found" error page.
*
* Runs when the URL requested could not be mapped to any page.
*/
function fourohfour() {
thesis_loop::fourohfour();
}
/**
* Loop for page without any posts/pages (an empty query).
*
* Runs when the URL requested maps to a legitimate page, but there
* is no post data to display. Should be rare.
*/
function nothing() {
thesis_loop::nothing();
}
/* PRIVATE HELPER METHODS */
/**
* Merge new query parameters with existing parameters.
*
* This preserves existing request parameters, for compatibility
* with plugins, etc.
*
* New parameters overwrite existing parameters with same name.
*
* @access private
* @param array $new_params Parameters for a new query via
* {@link query_posts()} or a new {@link WP_Query}.
* @global $wp_query is used to get the current request parameters.
*
* @return array The merged parameters array, ready for passing to
* {@link WP_Query::query_posts()} or
* {@link WP_Query::get_posts()}.
*/
private function preserve_query_params($new_params = array()) {
global $wp_query;
return(array_merge($wp_query->query, $new_params));
}
/**
* Retrieve the current "paged" parameter from the request.
*
* This is used for "paging" through the query results.
* See {@link https://aldosoft.com/blog/2011/01/thesis-custom-loop-template/
* for details.}
*
* This method deals with a WordPress idiosyncracy / inconsistency in the
* name of this parameter.
*
* @access private
* @uses WP_Query::get_query_var()
*
* @return int The current paged parameter.
*/
private function get_paged_param() {
if(is_front_page()) {
$paged = (get_query_var('page') ? get_query_var('page') : 1);
}
else {
$paged = (get_query_var('paged') ? get_query_var('paged') : 1);
}
return($paged);
}
}
$custom_looper = new my_looper;
About the Helper Methods
I’ve added two private methods that virtually every Custom Loop will find a way to make use of. (Lines 197-200 and 217-225.) These helpers simplify your loop code by factoring out dealing with some idiosyncrasies of WordPress request parameters.
The first helper makes preserving existing request parameters easier, and more complete:
class my_looper extends thesis_custom_loop {
function home() {
$new_args = array(
'orderby' => 'title',
'order' => 'ASC',
);
query_posts($this->preserve_query_params($new_args));
thesis_loop::home();
}
}
$custom_looper = new my_looper;
This loop is functionally (almost) identical to the first loop example in the Custom Loop API article. It changes the sort order of the home page, but otherwise leaves the formatting alone. At first blush it’s just a more complicated version of the earlier example, but let’s look at what’s different:
- Lines 4-7 define an array of query arguments, which will override existing request parameters. Recall from the earlier article that using an array is generally easier when you have more than one or two parameters to add.
- Line 8 is what’s interesting, instead of concatenating together pieces of a query string, I use the new helper method,
preserve_query_params()
. The cool thing about the helper is that it accesses the$wp_query
global to extract all of the request parameters, not merely those that are in the query string. This is more likely to preserve compatibility with plugins, etc. - Line 9, delegate to the built-in Thesis
home()
loop, just as I did before.
The preserve_query_params()
method will generally be useful in any Custom Loop where you modify the existing query; it’s unnecessary in situations where you are creating a new query, unless that query needs to “copy” parameters from the default query.
The second helper method, get_paged_param()
, is for use when you want to enable paging through the results of a new> query, that is, when you are adding a list of items that would not normally display on the URL requested. The portfolio page example in the Custom Loop API article is a good example of where this is useful. Here’s an abbreviated version for discussion:
function page() {
if(is_page('portfolio')) {
// Get request var to maintain paging through list of portfolio posts
$paged = $this->get_paged_param();
// Loop #1: Show the requested page's content
while (have_posts()) { // Standard WordPress
the_post(); // Standard WordPress
// ... HTML output
}
wp_reset_query();
// Loop #2: Add a list of portfolio posts
$portfolio_query_args = array(
'paged' => $paged,
'post_type' => 'post',
'post_status' => 'publish',
'tag' => 'portfolio',
);
query_posts($portfolio_query_args);
if(have_posts()) { while(have_posts()) {
the_post();
// ... HTML output
}}
}
// else ...
}
The basic idea is that one page of this site is a portfolio, which will display a stylized list of posts which have been tagged as “portfolio”. The page itself contains static content to introduce the portfolio, so we’re adding a second query to get the portfolio items. The paging issue arises when we have more portfolio posts than will fit in the list (WordPress has a default of 10 here, it can be changed in Settings > Reading > Blog pages show at most).
If we want to be able to “page” backwards and see older portfolio items, we need to enable the “paging controls”, the “Previous Entries” and “Next Entries” links that appear at the bottom of the page in Thesis archive views. These links are auto-generated by Thesis and WordPress functions; all we need to do to enable them is to preserve the “paged” parameter that is part of the request, and feed it to the new query.
So, on line 4 we use the get_paged_param()
helper method to grab that parameter. The helper deals with an inconsistency in WordPress, where the parameter is named one thing on most pages, and another on the front page.
Lines 7-11 handle the initial Loop, which is simply displaying the static page content. (And, obviously, line 9 hides a lot of the details; see the portfolio example for the full code.)
Line 14 starts our second Loop, and line 15 is where we add the $paged
value to the parameters for the new query. This is the query that will return multiple results, potentially more than will display on one page, and which we want to page through. Adding the $paged
parameter tells the query which section of the results to actually retrieve.
On line 20 we run the query, and lines 22-25 deal with looping through the results of the query and outputting the portfolio list HTML for each portfolio post.
Now, there’s additional complexity here that might not be apparent. The WordPress query_posts()
documentation explicitly states that you should only use query_posts()
to modify the main page Loop. That’s a good rule of thumb, and if you choose to not follow it, you should have a good reason.
In this case, the reason is pagination. If the Loop above were to use get_posts()
or create a new WP_Query
object manually, it would certainly retrieve the portfolio posts just as well as using query_posts()
, without making the code appreciably more complicated.
But the automatic display of the paging controls will only happen for the query results last returned by query_posts()
. If the above loop did not use query_posts()
, the default Thesis pagination wouldn’t happen, and I would have to write my own pagination code, which would add a lot more code to the loop. This way is easier.
How does it work? WordPress pagination runs off the global $wp_query
variable. When you leave your loop (or any hook affecting the “main” query), you need to leave $wp_query
holding the results you want paginated and with a valid/preserved $paged
parameter. If you do that, pagination runs automatically.
Anyway, to at least wrap up a long story, hopefully this starter template and the helper methods will make your Custom Loops easier to write. Happy looping!
Curious…
From the function above the line I would have to remove from the is:
thesis_loop::home();
??? Then I can add a standard WordPress loop like I would write for any standard non-thesis theme? Am I conceptualizing this correctly?
If so that would really simply things for us who are PHP beginners. Any help is appreciated.
@John: I wouldn’t recommend that a true beginner start their Thesis customization career with the Custom Loop API. It requires you to understand some fairly complex aspects of both WordPress and Thesis, and that’s a tough nut to crack unless you’re already a little experienced with programming in PHP. Thesis has some very easy-to-use customization features, both in the web interface, and via the custom.css stylesheet. Once you’re confident with that, Thesis hooks and filters give you fantastic flexibility, and the opportunity to work on your PHP and WordPress skills. It’s only when you’ve mastered those that I would recommend trying to take on the Custom Loop API.
But, if you’re the type who learns best by jumping in the deep end ;-), here’s an answer to your question. Most simply, yes, you replace the call to
thesis_loop::home()
with your own custom loop code. See my original article How to Use the Thesis Custom Loop API for some examples of what you would replace it with.But to add to that, you asked if you can then add a standard WordPress Loop like you would for any other (non-Thesis) theme. The answer is, sorta. That is, you can pretty much do that and it will often work. But Thesis works differently from standard WordPress themes, and unless you either (a) add in the expected Thesis hooks to your new loop, or (b) have a specific reason for omitting them, you may find that your customizations don’t behave the way you expect. Read through the last two examples in the other article for more details here.
HTH!
Thanks for the kind reply; all good advices. I will not pretend to know PHP however, I am comfortable enough with it to manipulate any non-thesis theme/loop into a web site that looks and functions how I would like it. As far as the web interface is concerned I always feel limited by push button control and have already reached that point with the interface. That being said I am 100% comfortable wilth CSS and HTML and have modified custom.css to great extent successfully.
Although relatively a rookie when it comes to hooks and filters, I have already made some adjustments to my custom-functions.php file successfully and have an understanding of how that works. This is however, I want what would traditional be my “page.php”, “single.php” and “index.php” files to have different out, content wise and style wise. I also what to have the ability to has some custom templates files as well. Being able to write a custom look for is_home(), is_page(), and is_single(), etc is important to me and I see the Custom Loop API as my only option.
I have already read you other article you mentioned, however much of it went way over my head. The problem I am having is that the PHP code you use to generate the loop seems way more complex that the standard WordPress loop I am used to composing/modifying. Hence my question about can I just drop a standard WordPress loop in there. Or does thesis require something else? My question to you is: “Why would my standard WordPress loop behave differently. Is there something else that thesis needs in order to process the loop? I feel like I am very close to being able to produce what I am looking for, and this seems to be the missing link.
If essence I am looking to do something like this:
if page then produce a loop like “this”
if single then produce a loop like “that”
etc…
@John: To answer your last question first, this is an inherent part of the Custom Loop API. If your
my_looper::page()
method gets called, you’re already effectively inside aif is_page()
block. The if statement is outside of yourmy_looper
class, but it’s there.To at least partially answer your other questions, I think a reasonable thing to do would be to just go ahead and put in a standard WordPress Loop into your custom loop method, and see what happens. Just pretend that each one of your
my_looper
methods is one of those standard WordPress template files, e.g.,page.php
,single.php
, etc. As my first article points out, you’ll lose the Thesis standard hooks, but you can add those back in afterwards.Finally, I’d be remise if I didn’t repeat my advice from the first article to at least investigate doing what you want to do with Thesis hooks and filters instead of the Custom Loop API. You can do a lot with hooks and filters, including use your old friends
is_page()
etc. in standard if blocks. You can very much change the look and output of different kinds of pages with hooks and filters, if you learn how to use them well. Think of the Custom Loop API as a hammer; you can make any problem look like a nail if you want to, but sometimes it’s a lot easier to use a screwdriver instead.Once again you’ve hit a home run. I especially appreciate the description of why query posts is necessary for pagination. It’s also a very timely post as I’m working on custom loops for custom post types and taxonomies. Thanks again.
Hi Alderete,
This is a wonderful tutorial and a working code for the thesis custom loop api. I want to know the custom loop code to achieve the homepage like Famousbloggers.net. The custom loop api is still not very clear to you so am asking help from you. I think the custom loop for famousbloggers is wonderful and one can learn from it a lot.
Thanks.
@Prasenjit: You will have to be more specific about what feature of famousbloggers.net you’re interested in replicating. There’s a lot going on on their home page! (Also, and please don’t take this the wrong way, but my time is limited, so I can’t take on every requested example. Please don’t be disappointed if I can’t accommodate your request.)
I know I’ve already said this, but it is SO NICE to have pagination working like it should. I really appreciate all the work and explanations.
Thanks for posting this. I was using function archive() to customize my category pages only to discover it broke the search functionality. Switching the function category() fixed the problem. Seems obvious but then again, it wasn’t obvious that search() is a subset of archive(). Now I know :)
What a great resource! I had something very similar in my “Thesis toolbox”, but those two private functions are a great addition! I just wanted to say “Thanks!”
This has been amazingly helpful – thank you!
I’ve run into a problem that has me banging my head against the wall. I have a custom post type called Resources. I set has_archive to true. So far so good; however, I need a custom loop because pretty much the whole “post” is custom meta boxes. That’s where I’m stuck. I tried using is_post_type_archive inside your archive function; no dice. (It’s not returning true, so I continue to get the default archive behavior.)
I know I can turn off the archives for the post type, create a page called Resources, throw the loop in there, yada yada yada, but that seems so inelegant, you know?
Appreciate any insights!
@Becky: I haven’t worked with custom post types at all, so I’m not sure if the following will work. But here’s a couple thoughts:
archive()
Thesis loop with theis_post_type_archive()
WordPress function, much as I did in thecategory()
loop example in my original article.is_post_type_archive('Resources')
as the conditional test to enter your customized loop. There are several WordPress functions for conditionally testing for custom types, so see the WordPress Codex for more details.If I haven’t guessed correctly, let me know, and I’ll see if I can think of any further hints. HTH!
Thanks Alderete! I just used the Custom Loop API to change the content for one my pages for a Custom Post Type, and updated with some of your helper functions as well.
Question though: Do you think it’s safe to access the $GLOBAL[‘paged’] variable and skip the get_paged_param function? Or is it safer to use the function?
I’ve tested it both ways and it works, but wanted to see what your thoughts were.
@Parin: I think you mean, is it safe to use the global variable `$GLOBAL[‘paged’]` instead of `get_query_var()`; the `get_paged_param()` method is simply a convenience wrapper that deals with the irritating difference in spelling depending on the context. If you want to use the global, you still need to deal with the difference between ‘page’ and ‘paged’.
With regard to `$GLOBAL[‘paged’]` vs. `get_query_var(‘paged’)`, it’s a matter of using a global value directly, or using the WordPress API to get the same information. Today, as you note, both work. But who knows what tomorrow will bring. The purpose of the API method is to make it possible to revise the implementation of WordPress without affecting compatibility with themes, plugins, etc. If you use the global directly, your code will break if WordPress stops using it, or uses it differently. You won’t get a warning, it’ll just stop working, possibly crashing your site. If you use the API method, though, you should at least start getting deprecation warnings if the method is being retired. (The WordPress developers are pretty good about this.)
A good rule of thumb is _never_ use a global variable if there is an API method that gives you access to the same information. That’s the way I’d go. (And did.)