2 edited body
source | link

The solution suggested by @sanchothefat is OK, but leads to problems when pagination is used. For example, you have url like domain.com/?s=&cat=1+2. When you click on page 2, the URL become domain.com/?s=&cat=1+2 but redirect_canonical() defined in wp-includes/canonical.php tries to modify url to domain.com/?s&cat=12 which is not the same... I found easier to me to rewrite category_search_logic() rather investigate redirect_canonical() logic and apply additional filters to it.

This is my way to buypass the canonical logic:

function category_search_logic( $query ) {
    if ( ! isset( $_GET['categories'] ) )
        return $query;

    // split cat query on a space to get IDs separated by '+'',' in URL
    $cats = explode(',', $_GET['categories'] );

    if ( count( $cats ) > 1 ) {
        unset($_GET['categories']);
        $query->query_vars[ 'category__and' ] = $cats;
    }

    return $query;
}

The solution suggested by @sanchothefat is OK, but leads to problems when pagination is used. For example, you have url like domain.com/?s=&cat=1+2. When you click on page 2, the URL become domain.com/?s=&cat=1+2 but redirect_canonical() defined in wp-includes/canonical.php tries to modify url to domain.com/?s&cat=12 which is not the same... I found easier to me to rewrite category_search_logic() rather investigate redirect_canonical() logic and apply additional filters to it.

This is my way to buypass the canonical logic:

function category_search_logic( $query ) {
    if ( ! isset( $_GET['categories'] ) )
        return $query;

    // split cat query on a space to get IDs separated by '+' in URL
    $cats = explode(',', $_GET['categories'] );

    if ( count( $cats ) > 1 ) {
        unset($_GET['categories']);
        $query->query_vars[ 'category__and' ] = $cats;
    }

    return $query;
}

The solution suggested by @sanchothefat is OK, but leads to problems when pagination is used. For example, you have url like domain.com/?s=&cat=1+2. When you click on page 2, the URL become domain.com/?s=&cat=1+2 but redirect_canonical() defined in wp-includes/canonical.php tries to modify url to domain.com/?s&cat=12 which is not the same... I found easier to me to rewrite category_search_logic() rather investigate redirect_canonical() logic and apply additional filters to it.

This is my way to buypass the canonical logic:

function category_search_logic( $query ) {
    if ( ! isset( $_GET['categories'] ) )
        return $query;

    // split cat query on a space to get IDs separated by ',' in URL
    $cats = explode(',', $_GET['categories'] );

    if ( count( $cats ) > 1 ) {
        unset($_GET['categories']);
        $query->query_vars[ 'category__and' ] = $cats;
    }

    return $query;
}
1
source | link

The solution suggested by @sanchothefat is OK, but leads to problems when pagination is used. For example, you have url like domain.com/?s=&cat=1+2. When you click on page 2, the URL become domain.com/?s=&cat=1+2 but redirect_canonical() defined in wp-includes/canonical.php tries to modify url to domain.com/?s&cat=12 which is not the same... I found easier to me to rewrite category_search_logic() rather investigate redirect_canonical() logic and apply additional filters to it.

This is my way to buypass the canonical logic:

function category_search_logic( $query ) {
    if ( ! isset( $_GET['categories'] ) )
        return $query;

    // split cat query on a space to get IDs separated by '+' in URL
    $cats = explode(',', $_GET['categories'] );

    if ( count( $cats ) > 1 ) {
        unset($_GET['categories']);
        $query->query_vars[ 'category__and' ] = $cats;
    }

    return $query;
}