Ask your WordPress questions! Pay money and get answers fast! Comodo Trusted Site Seal
Official PayPal Seal

WP_Query and pre_get_posts WordPress

  • SOLVED

Hello,

I have the following code in my plugin :

On the init action :


register_post_type( 'wpgdacces_acces',
array(
'labels' => array(
'name' => __( 'Access', 'gestion-des-acces' )
),
'public' => true,
'has_archive' => true,
'rewrite' => array('slug' => _x('acces','Slug used in permalinks',
'gestion-des-acces'))
)
);
register_taxonomy_for_object_type('post_tag', 'page');
register_taxonomy_for_object_type('category', 'page');


So a new custom post type is registered, with no taxonomy. Then the wordpress built-in taxonomies 'post_tag' and 'category' are assigned to the 'page' post type.

Then, on the 'pre_get_posts' action :


if ( !is_admin() && !$query->is_singular() /* && $query->is_main_query() */ ) {
if( is_home() && $query->is_main_query() ){
$query->set('post_type', array('post','wpgdacces_acces', 'page'));
}
$query_tax_param = array(
'relation' => 'OR',
array(
'relation' => 'AND',
array( 'taxonomy' => 'category',
'field' => 'slug',
'terms' => array( 'private' ),
'operator' => 'IN'),
array( 'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => array('private'),
'operator' => 'IN')
),
array(
'relation' => 'AND',
array( 'taxonomy' => 'category',
'field' => 'slug',
'operator' => 'NOT EXISTS'
),
array( 'taxonomy' => 'post_tag',
'field' => 'slug',
'operator' => 'NOT EXISTS'
)
)
);
$query->set( 'tax_query', $query_tax_param );
}
}


I am working on Wordpress 4.3, with the default theme, and I am trying to alter the WP_Query on the blog home page, to show posts, or pages, or posts from custom post type 'wpgdacces_acces', ONLY IF : the 'category' and 'post_tag' taxonomies are registered for this post and have a value of 'private' OR if both taxonomies are set to an empty value, or not registered at all for the given post...

In the admin, the 2 default taxonomies are only registered for standard posts (default behavior) and for pages (through the additional calls to register_taxonomy_for_object_type() in the code above), but not for wpgdacces_acces posts... All pages have no terms set for both taxonomies, and only one article has the 'private' value for both taxonomies.

So the issue : the function hooked on 'pre_get_posts' only return the one standard post which has 'private' terms for both taxonomies, not the pages, and not the 'wpgdacces_acces' posts. Additionally, when I modify the function hooked on 'pre_get_posts' to run both conditions separately (no terms or tax not registered on the one side, terms set for both tax to : 'private' on the other side), I have the correct results... but not both conditions togethers :-S...

I hope you can help...

Answers (1)

2015-09-09

dimadin answers:

I wasn't aware of this before, but you can't use tax query where you want to search for posts that don't have any term from some taxonomy (your second limitation) with any other tax query. Reason for this are some complicated SQL queries that are made with this.

That mean that the only other way is to have two separate queries and to merge results of them when you modify main query so that you are searching only from selected post IDs.

Problem with this is that you don't want to filter 'pre_get_posts' in those two new queries because you'll end with infinity loops.

This is the reason why I used class object to limit use of 'pre_get_posts' filter only for those queries that need it.

That is how I ended up with this solution which was tested and worked for me. Yes, it creates more database requests but it is the only solution I am aware of right now that uses your approach with modifying main query. Maybe some caching can improve it, which I could show example if you want.


class MD_Custom_Home_Loop {
/**
* Should 'pre_get_posts' filter be used.
*
* @access protected
*
* @var bool
*/
protected $no_pre_get_posts = false;

/**
* Add hooks.
*
* @access public
*/
public function __construct() {
add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
}

/**
* Modify query on home page.
*
* @access public
*
* @param WP_Query $query The WP_Query instance (passed by reference).
*/
public function pre_get_posts( &$query ) {
if ( ! is_admin() && !$query->is_singular() /* && $query->is_main_query() */ && ! $this->no_pre_get_posts ) {
if ( is_home() && $query->is_main_query() ) {
// Only query againt this post types
$query->set( 'post_type', array( 'post', 'wpgdacces_acces', 'page' ) );
}

// Only query againt post IDs from custom loop
$query->set( 'post__in', $this->get_ids() );

// Use 'pre_get_posts' filter in the future again
$this->no_pre_get_posts = false;
}
}

/**
* Get IDs of posts in custom home page loop.
*
* @access protected
*
* @return array $ids An array of post IDs for custom loop.
*/
protected function get_ids() {
// Don't use 'pre_get_posts' filter
$this->no_pre_get_posts = true;

// Create basic parameters
$query_one_args = $query_two_args = array(
'post_type' => array( 'post', 'page', 'wpgdacces_acces' ),
'fields' => 'ids',
);

// Add tax query for the first parameter
$query_one_args['tax_query'] = array(
'relation' => 'AND',
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => array( 'private' ),
'operator' => 'IN'
),
array(
'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => array( 'private' ),
'operator' => 'IN'
),
);

// Get first query post IDs
$query_one = get_posts( $query_one_args );

// Add tax query for the second parameter
$query_two_args['tax_query'] = array(
'relation' => 'AND',
array(
'taxonomy' => 'category',
'field' => 'slug',
'operator' => 'NOT EXISTS'
),
array(
'taxonomy' => 'post_tag',
'field' => 'slug',
'operator' => 'NOT EXISTS'
),
);

// Get second query post IDs
$query_two = get_posts( $query_two_args );

// Merge those IDs and get only unique from them
$ids = array_unique( array_merge( $query_one, $query_two ) );

return $ids;
}
}
new MD_Custom_Home_Loop;


tom-hkwebs comments:

Hello,

thank you a lot for your answer and for the certain quality of the solution you shared!

I will probably actually use your solution. Yet, I would like to search a little more why the generated SQL statement does not return the expected results... There are filters provided by the WP_Query object which do allow to alter the SQL statement after the 'pre_get_posts' hook, so if I know what is not correct in the SQL statement, it should be possible to correct it and to get the expected result without having to make additional database queries :-).

I have printed the SQL statement on the 'wp_footer' action if you want to take a look, maybe the error will appear obvious to you!


SELECT SQL_CALC_FOUND_ROWS wptom_posts.ID
FROM wptom_posts
INNER JOIN wptom_term_relationships
ON (wptom_posts.ID = wptom_term_relationships.object_id)
INNER JOIN wptom_term_relationships
AS tt1
ON (wptom_posts.ID = tt1.object_id)
WHERE 1=1
AND (
(
wptom_term_relationships.term_taxonomy_id IN (7)
AND
tt1.term_taxonomy_id IN (8)
)
OR
(
NOT EXISTS (
SELECT 1
FROM wptom_term_relationships
INNER JOIN wptom_term_taxonomy
ON wptom_term_taxonomy.term_taxonomy_id = wptom_term_relationships.term_taxonomy_id
WHERE wptom_term_taxonomy.taxonomy = 'category'
AND wptom_term_relationships.object_id = wptom_posts.ID
)
AND
NOT EXISTS (
SELECT 1
FROM wptom_term_relationships
INNER JOIN wptom_term_taxonomy
ON wptom_term_taxonomy.term_taxonomy_id =
wptom_term_relationships.term_taxonomy_id
WHERE wptom_term_taxonomy.taxonomy = 'post_tag'
AND wptom_term_relationships.object_id = wptom_posts.ID
)
)
) AND wptom_posts.post_type
IN ('post', 'wpgdacces_acces', 'page')
AND (wptom_posts.post_status = 'publish'
OR wptom_posts.post_author = 1
AND wptom_posts.post_status = 'private')
GROUP BY wptom_posts.ID
ORDER BY wptom_posts.post_date
DESC LIMIT 0, 10


dimadin comments:

Honestly, I am not MySQL expert and didn't investigate too much to see what is going on. I am aware that you can get SQL query, that is how I knew that problem exists.

Problem I found is that INNER JOIN are only added with second limitation and problem is somewhere there:


INNER JOIN wptom_term_relationships

ON (wptom_posts.ID = wptom_term_relationships.object_id)

INNER JOIN wptom_term_relationships

AS tt1

ON (wptom_posts.ID = tt1.object_id)


tom-hkwebs comments:

Ok... Thank you very much :-) !