Edit 3 – revised per G.M.’s suggestions. Code runs error free, but adjacent post filter still not filtering. Changes noted in code comments, current question noted at bottom of post.
Edit 2 – reflects G.M.’s code suggestion below. I’m confused about where the modified join/where filter function should go, so I added more of the mu-plugin code structure.
Edit 1 – reflects Kaiser’s comment below, examples provided. Also, I was mistaken about which code was filtering my posts. Question does not relate to the Members plugin.
I have a mu-plugin filtering my WP query so that in the index loop, a visitor only sees posts they can read. It does this by offering post author the ability to assign a multi-level membership taxonomy for each post.
At the lowest (public) level, there is no membership taxonomy attached to the post.
Here is the query filter in the mu-plugin:
if ( ! class_exists ( 'Site_Members_Taxonomy' ) ) {
class Site_Members_Taxonomy
{
//define( 'LANG', 'some_textdomain' ); deleted LANG, was throwing errors
public $post_types = array( 'post', 'page', 'gallery' );
public $tax = 'membership';
public $tax_label; // CHANGE: made these three vars public
public function __construct()
{
$this->tax_label = __( 'Membership' );
// plugin defines taxonomy and membership meta and then:
add_action( 'pre_get_posts', array( &$this, 'filter_query' ) );
/**
* Modify the query based on membership taxonomy
*/
function filter_query( $query ) {
//quit right now if in admin
if( is_admin() ) return;
$tax_query = '';
$terms = $this->get_terms_to_exclude(); // see function below
if( $terms )
$tax_query = array(
array(
'taxonomy' => $this->tax,
'field' => 'slug',
'terms' => $terms,
'operator'=>'NOT IN'
)
);
// if after all that we have a tax query to make, lets do it!
if ( $tax_query )
set_query_var('tax_query', $tax_query);
}
} //end class
global $Site_Members_Taxonomy;
$Site_Members_Taxonomy = new Site_Members_Taxonomy();
}
// finally, outside the class definition, there is a function `members_can_view` to be used as a conditional to hide/show various template tags etc.
So far, so good. This filter works great.
The problem is, in single-post view, next_post_link()
and previous_post_link()
don’t get the filter, so visitors can navigate to posts they cannot read. I want to filter the links to these posts to show the link only to the next post they can actually read.
I’d basically like to use the above query filter on get_adjacent_post().
In WP core trac for link-template.php (as well as in the post Kaiser linked to below) I found that next_post_link()
, get_next_post()
and get_adjacent_post()
run the adjacent post query through the following filters:
get_{$adjacent}_post_join
get_{$adjacent}_post_where
get_{$adjacent}_post_sort
REWRITE JOIN/WHERE FILTER
I tried the following rewrite of JOIN/WHERE suggested in G.M.’s answer. As he says, it is very closely based on the wp-core function, with tr and tt given different names and the excluded terms passed directly to the function.
This function is currently active on the site and not throwing errors, however it’s not filtering the adjacent posts query correctly either.
Current Question: When converting $excluded_terms from slugs to IDs in the function filter_adjacent below, am I correctly building the array? How can I log the excluded terms and find out what’s going on?
// Filters for get_adjacent_posts() so they don't show up in single-post navigation
// These filters are added within function __construct{} noted above
// CHANGE: &$this to $this
add_filter( 'get_previous_post_where', array( $this, 'filter_adjacent' ) );
add_filter( 'get_next_post_where', array( $this, 'filter_adjacent' ) );
add_filter( 'get_previous_post_join', array( $this, 'filter_adjacent' ) );
add_filter( 'get_next_post_join', array( $this, 'filter_adjacent' ) );
/**
* These functions added within mu-plugin class:
*
* First, modify excluded terms based on membership taxonomy
*
* NOTE: Excluded terms may be an array
*/
function get_terms_to_exclude() {
// if a real admin or "level1" we won't change tax query at all
if( current_user_can ( 'manage_options' ) || current_user_can ( 'view_level1_posts' ) )
return false;
// default/public will exclude all upper level posts
// in other words, all posts with membership taxonomy
$terms = array( 'level1','level2','level3' );
// if a level3 reader we'll exclude level1 and level2 posts
if( current_user_can ( 'view_level3_posts' ) )
$terms = array( 'level1', 'level2' );
// if at level2 level we'll exclude level1 level posts
if( current_user_can ( 'view_level2_posts' ) )
$terms = array( 'level1' );
return $terms;
}
/**
* Next, use these terms to filter the get_adjacent_post JOIN/WHERE
*/
function filter_adjacent( $clause ) {
if ( substr_count( current_filter(), '_post_join' ) ) { // filtering join
if ( empty($clause) ) $clause = '';
global $wpdb;
$clause .=
" INNER JOIN {$wpdb->term_relationships} AS trmship ON p.ID = trmship.object_id
INNER JOIN {$wpdb->term_taxonomy} ttmship
ON trmship.term_taxonomy_id = ttmship.term_taxonomy_id";
return $clause;
} elseif ( substr_count( current_filter(), '_post_where' ) ) { // filtering where
$excluded_term_slugs = get_terms_to_exclude();
if ( ! $excluded_term_slugs ) return $clause; // nothing to filter if no terms
$excluded_terms = array(); // we needs term ids, let's convert slug in terms ids
foreach ( $excluded_term_slugs as $slug ) {
$t = get_term_by( 'slug', $slug, 'membership' );
if ( ! $t || is_wp_error($t) ) continue;
$excluded_terms[] = $t;
}
// something wrong in get_level_term_to_exclude()?
if ( empty($excluded_terms) ) return $where;
$posts_in_ex_terms_sql =
" AND ttmship.taxonomy = 'membership'
AND ttmship.term_id NOT IN (" . implode( $excluded_terms, ',' ) . ')';
// return filtered where clause
return $clause. $posts_in_ex_terms_sql;
}
}
First of all I suggest you to use a function to return the terms to exclude, that will help you to get them in different places without having to repeat code, e.g. in the ‘pre_get_posts’ filter and the adjacent post filters.
So:
I removed comments for sake of semplicity, however you code is well commented and mine is taken from there. I just put the check for admins at function top.
After that, generally speacking,
JOIN
clause can’t filter anything withoutWHERE
, andWHERE
will fail if using a table name not defined inJOIN
, so does not exists a join method and a where method, but exists a join and where method.In core, the filter for excluded terms is done both via
'WHERE'
andJOIN
, in fact at line #1183 you can see:inside the
if ( ! empty( $excluded_terms ) ) {
statement on line #1154and on line #1141:
The
$posts_in_ex_terms_sql
is set at line #1173 in this way:I think that you can just copy code used by core when
$excluded_terms
is not empty… if it works for core should works also for you.So you can mimic core workflow using
"get_{$adjacent}_post_where"
filter and"get_{$adjacent}_post_join"
.So the function
filter_adjacent
should act on join and where clause, a simple check oncurrent_filter
can do the trick.this is how your class should appear using my tips:
I’ve used a different alias for taxonomy tables in this way the filter will work even if when
get_adjacent_post
is called any terms are passed to for$excluded_terms
argument and /or$in_same_term
is set to true.Code is completely untested, let me know if it works…