YKKY
YKKY

Reputation: 605

WordPress: multiple search terms or join results from multiple WP_Queries

I've got the following code:

$args = array(
    's'                 => 'Apple',
    'cat'               => 80,
    'posts_per_page'    => -1,
    'orderby'           => date,
    'order'             => desc,
);
$query = new WP_Query( $args );
echo $query->found_posts.'<br /><br />';
if ( $query->have_posts() ) {
    while ( $query->have_posts() ) { $query->the_post();
        echo the_time("Y.m.d"); echo ': '; echo the_title(); echo '<br />';
    }
}

It searches category 80 for any post that contains the word Apple and then successfully outputs the list of such posts like

2

2016.10.10: Apple pie
2016.10.01: Apple juice

If I also want to display all the posts that contains the word Banana then I just repeat the code with 's' => 'Apple' replaced with 's' => 'Banana' and get something like

1

2016.10.05: Banana fresh

Quite easy until I want to output both search results using the single list like

3

2016.10.10: Apple pie
2016.10.05: Banana fresh
2016.10.01: Apple juice

Here I need your help as I have no idea how to achieve that.

So far I've tried

$args = array(
    's'                 => array('Apple','Banana'),
    'cat'               => 80,
    'posts_per_page'    => -1,
    'orderby'           => date,
    'order'             => desc,
);

and

$args = array(
    array('s' => 'Apple',),
    array('s' => 'Banana',),
    'cat'               => 80,
    'posts_per_page'    => -1,
    'orderby'           => date,
    'order'             => desc,
);

Both return all posts from the given category even if there is only one element in the subarray, so it seems 's' couldn't be put in a subarray or contain array as the argument. Or I have mistyped somewhere but I can not catch where.

Upvotes: 1

Views: 3518

Answers (2)

YKKY
YKKY

Reputation: 605

While I was waiting for the ready answer, I was also researching the PHP manual and WP Codex, so I've ended up with the following working solution.

In case someone would search for the answer for the same question, here it is:

<?php

// Searching for posts with "Apple" in their content within category with the id 80
$args_apple = array('s' => 'Apple', 'cat' => 80, 'posts_per_page' => -1);
$query_apple = new WP_Query( $args_apple );
wp_reset_query();

// Searching for posts with "Banana" in their content within category with the id 80
$args_banana = array('s' => 'Banana', 'cat' => 80, 'posts_per_page' => -1);
$query_banana = new WP_Query( $args_banana );
wp_reset_query();

// Searching for posts with "Orange" in their content within category with the id 80
$args_orange = array('s' => 'Orange', 'cat' => 80, 'posts_per_page' => -1);
$query_orange = new WP_Query( $args_orange );
wp_reset_query();

// Joining results, excluding duplicates with wrapping array_merge() into array_unique()
$result = new WP_Query();
$result->posts = array_unique(array_merge(
    $query_apple->posts,
    $query_banana->posts,
    $query_orange->posts
), SORT_REGULAR); // While PHP manual states this parameter is
                  // optional for array_unique(), in this case
                  // it is obligatory

// Showing the number of results
$result->post_count = count( $result->posts );
echo $result->post_count.'<br /><br />';

// Sorting results by date
usort($result->posts, 'order_by_date');
function order_by_date( $a, $b ) {
    $a_date = $a->post_date;
    $b_date = $b->post_date;
    return ( strcmp($a_date, $b_date ) > 0 ) ? -1 : 1; // Sort descending
//  return ( strcmp($a_date, $b_date ) > 0 ) ? 1 : -1; // Sort ascending
}

// The search results output loop
if ( $result->have_posts() ) {
    while ( $result->have_posts() ) { $result->the_post();
        // Start making your own output
        echo the_time("Y.m.d");
        echo ': ';
        echo the_title();
        echo '<br />';
        // Stop making your own output
    }
}
wp_reset_query();

?>

The only thing I don't like with all code above is an extra function to sort merged array items. But I've spent a good half of the day to find a way to sort items of merged array with internal parameters, with no success.

The result is like

7

2016.10.21: Banana
2016.10.15: Sliced orange
2016.10.09: Banana fresh
2016.10.07: Apple juice
2016.10.05: Apple pie
2016.10.03: Orange
2016.10.01: Apple

The number of merged arrays seems to be unlimited or so, just don't forget to use unique $args_name and $query_name for each WP_Query().

And always close each query with wp_reset_query().

Upvotes: 0

random_user_name
random_user_name

Reputation: 26180

While your solution clearly works, I'd like to submit another possible solution that leverages the filters that WordPress offers. In this way, I believe this solution is more consistent with "The WordPress Way":

// Hook into the WP_Query filter to modify the search query
add_filter( 'posts_search', 'make_search_any_terms', 10, 2 );

/**
 * Function to convert search to "any" terms instead of "all" terms.
 * WordPress automatically passes in the two arguments.
 * 
 * @param string $search
 * @param WP_Query $query
 *
 * @return string
 */
function make_search_any_terms( $search, $query ) {
    // If it's not a search, then do nothing
    if ( empty( $query->is_search ) ) {
        return $search;
    }

    global $wpdb;

    $search_terms = get_query_var( 'search_terms' );

    // This code adapted from WP_Query "parse_search" function
    $search    = '';
    $searchand = '';
    foreach ( $search_terms as $term ) {
        $like                        = '%' . $wpdb->esc_like( $term ) . '%';
        $q['search_orderby_title'][] = $wpdb->prepare( "$wpdb->posts.post_title LIKE %s", $like );

        $like = '%' . $wpdb->esc_like( $term ) . '%';
        $search .= $wpdb->prepare( "{$searchand}(($wpdb->posts.post_title LIKE %s) OR ($wpdb->posts.post_content LIKE %s))", $like, $like );
        // This is the significant change - originally is "AND", change to "OR"
        $searchand = ' OR ';
    }

    if ( ! empty( $search ) ) {
        $search = " AND ({$search}) ";
        if ( ! is_user_logged_in() ) {
            $search .= " AND ($wpdb->posts.post_password = '') ";
        }
    }

    return $search;
}

To use it with a SINGLE search query:

$args = array(
    's'                 => 'Apple Banana Orange',
    // ...
);

$results = WP_Query( $args );

You will get all results that have either apple or banana or orange.

In this way, you could have a single query and a single set of results, and simply loop through it.

NOTE: The above code is normally put in your theme's functions.php file.

Upvotes: 5

Related Questions