I am pretty new in WordPress theme development and I’m not so into PHP (I came from Java and C#) and have the following situation in this custom theme
As you can see in the homepage I first show a section (named Articoli in evidenza) containing the featured posts (I’ve implemented it using a specific tag) and under it there is another area (named Ultimi Articoli) that contains the latest post that are not the featured post.
To do it I use this code:
<section id="blog-posts">
<header class="header-sezione">
<h2>Articoli in evidenza</h2>
</header>
<!--<?php query_posts('tag=featured');?>-->
<?php
$featured = new WP_Query('tag=featured');
if ($featured->have_posts()) :
while ($featured->have_posts()) : $featured->the_post();
/*
* Include the post format-specific template for the content. If you want to
* use this in a child theme, then include a file called called content-___.php
* (where ___ is the post format) and that will be used instead.
*/
get_template_part('content', get_post_format());
endwhile;
wp_reset_postdata();
else :
// If no content, include the "No posts found" template.
get_template_part('content', 'none');
endif;
?>
<header class="header-sezione">
<h2>Ultimi Articoli</h2>
</header>
<?php
// get the term using the slug and the tag taxonomy
$term = get_term_by( 'slug', 'featured', 'post_tag' );
// pass the term_id to tag__not_in
query_posts( array( 'tag__not_in' => array ( $term->term_id )));
?>
<?php
if (have_posts()) :
// Start the Loop.
while (have_posts()) : the_post();
/*
* Include the post format-specific template for the content. If you want to
* use this in a child theme, then include a file called called content-___.php
* (where ___ is the post format) and that will be used instead.
*/
get_template_part('content', get_post_format());
endwhile;
else :
// If no content, include the "No posts found" template.
get_template_part('content', 'none');
endif;
?>
</section>
It works fine, but I have some doubts about the quality of this solution and how exactly it works.
To select all the featured posts, I use this line that creates a new WP_Query
object that defines a query having the specific tag featured
:
$featured = new WP_Query('tag=featured');
Then I iterate on this query result using its have_posts()
method.
So, from what I have understand, this is not the WordPress main query, but it is a new query created by me. From what I understand, it is better create a new query (as done) and not use the main query when I want perform this kind of operation.
Is it true, or am I missing something? If it is true, can you explain me, why it is better to create a new custom query and not modifying the WordPress main query?
Ok, going on. I show all the posts that don’t have the ‘featured’ tag. To do this, I use this code snippet, which on the contrary, modifies the main query:
<?php
// get the term using the slug and the tag taxonomy
$term = get_term_by( 'slug', 'featured', 'post_tag' );
// pass the term_id to tag__not_in
query_posts( array( 'tag__not_in' => array ( $term->term_id )));
?>
<?php
if (have_posts()) :
// Start the Loop.
while (have_posts()) : the_post();
get_template_part('content', get_post_format());
endwhile;
else :
// If no content, include the "No posts found" template.
get_template_part('content', 'none');
endif;
?>
So I think, this is pretty horrible. Is it true?
UPDATE:
To do the same operation I found this function (in the great answer below) that I have added to functions.php
function exclude_featured_tag( $query ) {
if ( $query->is_home() && $query->is_main_query() ) {
$query->set( 'tag__not_in', 'array(ID OF THE FEATURED TAG)' );
}
}
add_action( 'pre_get_posts', 'exclude_featured_tag' );
This function has a hook that is called after the query variable object is created, but before the actual query is run.
So, from what I have understand, it takes a query object as input parameter and modifies (actually filters) it by selecting all the posts excluding a specific tag (in my case the featured
tag posts)
So, how can I use the previous query (the one used to show the featured posts) with this function to show only the not featured posts in my theme? Or do I have to create a new query?
Your actual question is basically when to run a custom query and when to make use of the main query. Lets break it down in three parts
PART ONE
When to run a custom query (This is not a definitive list)
-
To create custom content sliders
-
To create a featured content area in a page
-
On page.php templates if you need to display posts -
If you require custom content on a static front page -
Display related, popular or informational posts
-
Any other secondary or supplementary content outside the scope of the main query
When to make use of the main query.
To display the primary content on
-
On your homepage and the page set as a blogpage in the backend
-
All archive pages which includes templates like archive.php, category.php, author.php, taxonomy.php, tag.php and date.php
-
UPDATE: Display custom content on true pages and a static front page (see Using pre_get_posts on true pages and static front pages)
PART TWO
To select all the featured posts I use this line that create a new WP_Query object that define a query having the specific tag featured:
So, from what I have understand, this is not the WordPres main query but it is a new query created by me. From what I have understand it is better create a new query (as done) and not use the main query when I want perform this kind of operations
Correct. This falls out of scope for the main query. This is secondary or supplementary content which cannot be created with the main query. You SHOULD ALWAYS use either WP_Query
or get_posts
to create your custom queries.
NEVER USE query_posts
to create custom queries, or even any other query. My emphasis.
Note: This function isn’t meant to be used by plugins or themes. As explained later, there are better, more performant options to alter the main query. query_posts() is overly simplistic and problematic way to modify main query of a page by replacing it with new instance of the query. It is inefficient (re-runs SQL queries) and will outright fail in some circumstances (especially often when dealing with posts pagination).
Moving on
Ok, going on I show all the posts that have not the featured tag, to do this I use this code snippet that on the contrary modify the main query:
query_posts( array( 'tag__not_in' => array ( $term->term_id )));
So I think that this is pretty horrible. Is it true?
That is all wrong and your statement is unfortunately true. As said before, NEVER use query_posts
. It runs a complete new query, which is bad for performance, and it most cases breaks pagination which is an integral part of the main query for pagination to work correctly.
This is your primary content, so you should be using the main query with the default loop, which should look like this, and this is all you need
<?php
if (have_posts()) :
// Start the Loop.
while (have_posts()) : the_post();
get_template_part('content', get_post_format());
endwhile;
else :
// If no content, include the "No posts found" template.
get_template_part('content', 'none');
endif;
?>
You can completely get rid of this part, delete it, burn it and forget about it
<?
// get the term using the slug and the tag taxonomy
$term = get_term_by( 'slug', 'featured', 'post_tag' );
// pass the term_id to tag__not_in
query_posts( array( 'tag__not_in' => array ( $term->term_id )));
?>
OK, once you’ve done that, you’ll see that posts from the feature tag appear in your home page using the main query and default loop.
The correct way of removing this tag from the homepage is with pre_get_posts
. This is the proper way to alter the main query and the hook you should always use to make changes to your primary content loop.
So, the code with pre_get_posts
is correct and this is the function that you should use. Just one thing, always do a check that you are not on an admin page because pre_get_posts
alters the back end as well. So this is the proper code to use in functions.php
to remove posts tagged featured from the homepage
add_action( 'pre_get_posts', 'exclude_featured_tag' );
function exclude_featured_tag( $query )
{
if ( !is_admin()
&& $query->is_home()
&& $query->is_main_query()
) {
$query->set( 'tag__not_in', [ID OF THE FEATURED TAG] );
}
}
PART THREE
Extra reading material which will be helpful in future
-
Conditional tags
-
When should you use WP_Query vs query_posts() vs get_posts()?
-
When to use WP_query(), query_posts() and pre_get_posts
-
Query Overview
-
Guidance with The Loop for CMS