0

I am working on a custom widget which displays posts from a selected taxonomy (category or tag). This widget contains a textbox which the user enters an ID of a taxonomy (category or tag) to exclude from posts. This ID can vary from widget to widget as multiple instances of this widget will appear on the same page excluding different category or tags. I am looking for a way to pass the $exclude variable from widget() to the excludeTheID() function which is located in functions.php

What I have tried: Setting the filter to a wrapper function that calls the excludeTheID() function and passes in $exclude, but NULL is returned.

For the sake of this example, here is a mock of what I am trying to accomplish:

class testWidget extends WP_Widget {

    public function __construct() {
        // .......
    }

    public function form( $instance ) {
        // This widget contains a text field ($exclude)  that the user is to enter a taxonomy ID into.
    }

    public function update( $new_instance, $old_instance ) {
        // .......
    }

    public function widget( $args, $instance ) {
        // this value represents what the user would enter in the textbox of the widget
        $exclude = 15;
        // .......

        // This function does the work
        add_filter( "posts_where", "excludeTheID" );    

        // Create a new query
        $loop = new WP_Query();

        // Remove the filter
        remove_filter( "posts_where", "excludeTheID" );
    }
}

// FROM Functions.php: 
// External function responsible for generating the new WHERE clause using the  value in $exclude.

function excludeTheID( $where, $exclude )
{   

    $clauses = array(
        array(
            'taxonomy'  => 'category',
            'field'     => 'id',
            'terms'     => $exclude,
            'operator' => 'NOT IN',
        ),
    );

    // Access the global WordPress DB variable
    global $wpdb;

    // Create the new WHERE Query string using the above arrays
    $tax_sql = get_tax_sql( $clauses, $wpdb->posts, 'ID' );
    $where .= $tax_sql['where'];

    return $where;
}

I do not want to use globals to accomplish this as each widget will be passing a unique value for $exclude in every instance of that widget on a page.

Any guidance would be greatly appreciated :)

Thanks.

3 Answers 3

0

Firstly, you will need your code to store the exclude value somewhere.

Since you don't want to use a global ( rightly so ), you have few remaining options:

  • a class
  • the class you already have
  • a closure
  • standard APIs

Option 1 a new object/class

Here we create an object, that holds the category you're excluding, and some logic to exclude it.

class wpse140557_exclude {
    $exclude = 0;
    public __construct( $exclude ) {
        $this->exclude = $exclude;
        add_filter( 'posts_where', array( $this, 'exclude_filter' ) );
    }
    public function exclude_filter( ... ) {
        // etc... using $this->exclude
    }
     public function remove_filter() {
        remove_filter( 'posts_where', array( $this, 'exclude_filter' ) );
     }
}

$exclude = new wpse140557_exclude( 12 ); // excluding category 12

// do some stuff/loops

$exclude->remove_filter(); // remove our filter

Option 2 closures

Here we use a closure, these require PHP 5.3+ to use

$exclude_closure = function (.. args...) use ( $exclude ) {
    // exclusion code $exclude
}

add_filter( 'posts_where', $exclude_closure );

// do stuff

remove_filter('posts_where', $exclude_closure );

Option 3, the class you already have

Move the function into your widget class, then use:

add_filter( "posts_where", array( $this, "excludeTheID" ) );

// do stuff

remove_filter( "posts_where", array( $this, "excludeTheID" ) );

Then use $this->exclude to access/set your category to exclude. This works as a less generic version of Option 1.

Option 4, WP_Query

If you had looked at the official documentation, you would have found there is a section titled "Exclude Posts Belonging to Category"

This shows you 2 ways of doing what you want without needing your additional function:

$query = new WP_Query( 'cat=-12,-34,-56' );

and

$query = new WP_Query( array( 'category__not_in' => array( 2, 6 ) ) );

Similar parameters exist for tags and other taxonomies

Homework

I suggest you read up on the following:

2
  • Regarding Option 4. I did look at the official documentation, and I did consider that as my first option. Unfortunately, I am using passing arguments to the WP_Query object, one of which is tax_query with an OR relation. This means that my custom AND clause will not act as an AND, but an OR once it gets added to the tax_query array.
    – Shane
    Apr 7, 2014 at 18:24
  • Then I leave Option 4 as something useful for other users, and recommend the other options depending which you're most comfortable with
    – Tom J Nowell
    Apr 7, 2014 at 18:29
0

I see two approaches to this, depending on rest of your architecture.

Make filter part method of widget class, instead of function.

Assign data to its object property internally and method will have clean access to it, without polluting global namespace.

Make custom query variable.

Pass your data to the WP_Query constructor and have filter function check for it. You will need to make sure it is reasonably unique and maybe tweak some things so that WP_Query does not discard it (been a while since I last done this, don't remember exact implementation details).

2
  • There is also the function object pattern and closures
    – Tom J Nowell
    Apr 7, 2014 at 18:00
  • @TomJNowell given the code example in question closure would be pain to remove afterwards though
    – Rarst
    Apr 7, 2014 at 18:03
0

First of all 'posts_where' filter (just like all other 'posts_*' filters) is not very reliable, because you assume that where clause is the standard one, but if other plugin use same filter before or after the your, result is not the expected, in best case, and in worst case you get only errors for bad sql query.

After that, why use a filter when you can direclty build query args?

If the aim is to ave an external function you can use outside the widget, let the fucntion return query array instead of apply a filter.

Rough code as proof of concept:

// in widget class

public function widget( $args, $instance ) {

    $exclude = $instance['exclude']; // this is what user have write in the widget form

    $query = new WP_Query( get_excluded_term_args( $exclude ) );

    if ( $query->have_posts() ) { while ( $query->have_posts() ) {
       $query->the_post();
       // echo stuff
    } wp_reset_postdata(); }

}


// in functions.php

function get_excluded_term_args( $exclude ) {
  return array(
    array(
        'taxonomy'  => 'category',
        'field'     => 'id',
        'terms'     => $exclude,
        'operator' => 'NOT IN',
    ),
  );
}

In this way:

  • you don't rely on 'posts_where' filter that is not very reliable
  • you can re-use get_excluded_term_args to get the query args and merge with other arguments to build complex queries
  • it works

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.