Использование мета-запроса ('meta_query') с поисковым запросом ('s')

25

Попытка построить поиск, который ищет не только значения по умолчанию (заголовок, контент и т. Д.), Но и определенное настраиваемое поле.

Мой текущий запрос:

$args = array(
  'post_type' => 'post',
  's' => $query,
  'meta_query' => array(
     array(
       'key' => 'speel',
       'value' => $query,
       'compare' => 'LIKE'
     )
   )
);

$search = new WP_Query( $args )
...

Это возвращает посты, которые соответствуют как поисковому запросу, так и мета-запросу, но я также хотел бы, чтобы он также возвращал посты, в которых он просто соответствует одному из них.

Любые идеи?

Люк
источник
То, что вы хотите сделать, невозможно, используя только один поисковый запрос. Вам нужно будет выполнить оба запроса по отдельности, а затем объединить их вместе. Это было описано в другом ответе. Это должно помочь вам, как это сделать. wordpress.stackexchange.com/questions/55519/…
Ник Перкинс

Ответы:

20

Я часами искал решение этой проблемы. Объединение массивов - это не тот путь, особенно когда запросы сложные, и вы должны иметь возможность добавлять их к мета-запросам в будущем. Решение, которое упрощенно выглядит, состоит в том, чтобы изменить 's' на то, которое позволяет искать заголовки и мета-поля.

add_action( 'pre_get_posts', function( $q )
{
    if( $title = $q->get( '_meta_or_title' ) )
    {
        add_filter( 'get_meta_sql', function( $sql ) use ( $title )
        {
            global $wpdb;

            // Only run once:
            static $nr = 0; 
            if( 0 != $nr++ ) return $sql;

            // Modified WHERE
            $sql['where'] = sprintf(
                " AND ( %s OR %s ) ",
                $wpdb->prepare( "{$wpdb->posts}.post_title like '%%%s%%'", $title),
                mb_substr( $sql['where'], 5, mb_strlen( $sql['where'] ) )
            );

            return $sql;
        });
    }
});

Использование:

$meta_query = array();
$args = array();
$search_string = "test";

$meta_query[] = array(
    'key' => 'staff_name',
    'value' => $search_string,
    'compare' => 'LIKE'
);
$meta_query[] = array(
    'key' => 'staff_email',
    'value' => $search_string,
    'compare' => 'LIKE'
);

//if there is more than one meta query 'or' them
if(count($meta_query) > 1) {
    $meta_query['relation'] = 'OR';
}

// The Query
$args['post_type'] = "staff";
$args['_meta_or_title'] = $search_string; //not using 's' anymore
$args['meta_query'] = $meta_query;



$the_query = new WP_Query($args)
Сатбир Кира
источник
это правильный путь,
Zorox
Фантастика, это очень хорошо сработало для меня. Отличное решение! Благодарность!
Фабиано
Это запрос, а не поиск. Если у вас есть плагины, которые работают в поиске, он не работает. Я ошибся?
marek.m
5

Много кода можно уменьшить, используя модифицированную версию этого ответа .

$q1 = new WP_Query( array(
    'post_type' => 'post',
    'posts_per_page' => -1,
    's' => $query
));

$q2 = new WP_Query( array(
    'post_type' => 'post',
    'posts_per_page' => -1,
    'meta_query' => array(
        array(
           'key' => 'speel',
           'value' => $query,
           'compare' => 'LIKE'
        )
     )
));

$result = new WP_Query();
$result->posts = array_unique( array_merge( $q1->posts, $q2->posts ), SORT_REGULAR );
$result->post_count = count( $result->posts );
A.Jesin
источник
Это отлично сработало для меня (тем более что мне нужно было использовать WP_Query вместо get_posts). Мне пришлось изменить строку post_count, чтобы она была: $result->post_count = count( $result->posts );потому что иначе я получал только 1 результат.
GreatBlakes
4

Я немного оптимизировал ответ @Stabir Kira

function wp78649_extend_search( $query ) {
    $search_term = filter_input( INPUT_GET, 's', FILTER_SANITIZE_NUMBER_INT) ?: 0;
    if (
        $query->is_search
        && !is_admin()
        && $query->is_main_query()
        && //your extra condition
    ) {
        $query->set('meta_query', [
            [
                'key' => 'meta_key',
                'value' => $search_term,
                'compare' => '='
            ]
        ]);

        add_filter( 'get_meta_sql', function( $sql )
        {
            global $wpdb;

            static $nr = 0;
            if( 0 != $nr++ ) return $sql;

            $sql['where'] = mb_eregi_replace( '^ AND', ' OR', $sql['where']);

            return $sql;
        });
    }
    return $query;
}
add_action( 'pre_get_posts', 'wp78649_extend_search');

Теперь вы можете осуществлять поиск по (заголовок, содержание, выделение) или (мета-поле) или (оба).

Себастьян Пискорский
источник
3

Согласно предложению Ника Перкинса , мне пришлось объединить два запроса следующим образом:

$q1 = get_posts(array(
        'fields' => 'ids',
        'post_type' => 'post',
        's' => $query
));

$q2 = get_posts(array(
        'fields' => 'ids',
        'post_type' => 'post',
        'meta_query' => array(
            array(
               'key' => 'speel',
               'value' => $query,
               'compare' => 'LIKE'
            )
         )
));

$unique = array_unique( array_merge( $q1, $q2 ) );

$posts = get_posts(array(
    'post_type' => 'posts',
    'post__in' => $unique,
    'post_status' => 'publish',
    'posts_per_page' => -1
));

if( $posts ) : foreach( $posts as $post ) :
     setup_postdata($post);

     // now use standard loop functions like the_title() etc.     

enforeach; endif;
Люк
источник
1
Разве это не возможно без слияния в 2016 году? Я редактирую поисковый запрос через pre_get_posts, так что это на самом деле не вариант ...
trainoasis
@trainoasis Я так не думаю. Я пробовал это с прошлых 2 часов, и поиск Google получил меня здесь.
Умайр Хан Джадун
2

Ну, это своего рода хак, но это работает. Вам необходимо добавить фильтр posts_clauses. Эта проверка функции фильтра для любого слова запроса существует в настраиваемом поле «speel», а оставшийся запрос остается неизменным.

function custom_search_where($pieces) {

    // filter for your query
    if (is_search() && !is_admin()) {

        global $wpdb;

        $keywords = explode(' ', get_query_var('s'));
        $query = "";
        foreach ($keywords as $word) {

            // skip possible adverbs and numbers
            if (is_numeric($word) || strlen($word) <= 2) 
                continue;

            $query .= "((mypm1.meta_key = 'speel')";
            $query .= " AND (mypm1.meta_value  LIKE '%{$word}%')) OR ";
        }

        if (!empty($query)) {
            // add to where clause
            $pieces['where'] = str_replace("(((wp_posts.post_title LIKE '%", "( {$query} ((wp_posts.post_title LIKE '%", $pieces['where']);

            $pieces['join'] = $pieces['join'] . " INNER JOIN {$wpdb->postmeta} AS mypm1 ON ({$wpdb->posts}.ID = mypm1.post_id)";
        }
    }
    return ($pieces);
}
add_filter('posts_clauses', 'custom_search_where', 20, 1);
МИСТЕР
источник
2

у меня была такая же проблема, для моего нового сайта я просто добавил новую мета "заголовок":

functions.php

add_action('save_post', 'title_to_meta');

function title_to_meta($post_id)
{
    update_post_meta($post_id, 'title', get_the_title($post_id)); 
}

А потом .. просто добавьте что-то вроде этого:

$sub = array('relation' => 'OR');

$sub[] = array(
    'key'     => 'tags',
    'value'   => $_POST['q'],
    'compare' => 'LIKE',
);

$sub[] = array(
    'key'     => 'description',
    'value'   => $_POST['q'],
    'compare' => 'LIKE',
);

$sub[] = array(
    'key'     => 'title',
    'value'   => $_POST['q'],
    'compare' => 'LIKE',
);

$params['meta_query'] = $sub;

Что вы думаете об этом обходном пути?

MarcoO
источник
1
Это на самом деле неплохо, но требует, чтобы вы либо заново сохранили все существующие посты, либо начали использовать их, прежде чем начинать добавлять контент.
Беренд
@ Возможно, вы могли бы написать функцию, которая получает все сообщения и просматривает их, обновляя post_meta для каждого. Включите его в пользовательский шаблон или функцию, запустите его один раз, а затем отмените.
Шлем
1

Все вышеперечисленные решения возвращают результаты, только если в метаключе speel существует совпадение. Если у вас есть результаты в другом месте, но не в этой области, вы ничего не получите. Никто не хочет этого.

Требуется левое соединение. Следующее создаст один.

           $meta_query_args = array(
              'relation' => 'OR',
              array(
                'key' => 'speel',
                'value' => $search_term,
                'compare' => 'LIKE',
              ),
              array(
                'key' => 'speel',
                'compare' => 'NOT EXISTS',
              ),
            );
            $query->set('meta_query', $meta_query_args);
Тим
источник
0

Это отличное решение, но вам нужно исправить одну вещь. Когда вы вызываете 'post__in', вам нужно установить массив идентификаторов, а $ unique - это массив постов.

пример:

$q1 = get_posts(array(
        'fields' => 'ids',
        'post_type' => 'post',
        's' => $query
));

$q2 = get_posts(array(
        'fields' => 'ids',
        'post_type' => 'post',
        'meta_query' => array(
            array(
               'key' => 'speel',
               'value' => $query,
               'compare' => 'LIKE'
            )
         )
));

$unique = array_unique( array_merge( $q1->posts, $q2->posts ) );

$array = array(); //here you initialize your array

foreach($posts as $post)
{
    $array[] = $post->ID; //fill the array with post ID
}


$posts = get_posts(array(
    'post_type' => 'posts',
    'post__in' => $array,
    'post_status' => 'publish',
    'posts_per_page' => -1
));
Габриэль Бустос
источник
0

Ответ @ satbir-kira прекрасно работает, но он будет искать только по мета и названию поста. Если вы хотите, чтобы он осуществлял поиск по мета, названию и содержимому, вот измененная версия.

    add_action( 'pre_get_posts', function( $q )
    {
      if( $title = $q->get( '_meta_or_title' ) )
      {
        add_filter( 'get_meta_sql', function( $sql ) use ( $title )
        {
          global $wpdb;

          // Only run once:
          static $nr = 0;
          if( 0 != $nr++ ) return $sql;

          // Modified WHERE
          $sql['where'] = sprintf(
              " AND ( (%s OR %s) OR %s ) ",
              $wpdb->prepare( "{$wpdb->posts}.post_title like '%%%s%%'", $title),
              $wpdb->prepare( "{$wpdb->posts}.post_content like '%%%s%%'", $title),
              mb_substr( $sql['where'], 5, mb_strlen( $sql['where'] ) )
          );

          return $sql;
        });
      }
    });

И вот его использование:

$args['_meta_or_title'] = $get['search']; //not using 's' anymore

$args['meta_query'] = array(
  'relation' => 'OR',
  array(
    'key' => '_ltc_org_name',
    'value' => $get['search'],
    'compare' => 'LIKE'
  ),
  array(
    'key' => '_ltc_org_school',
    'value' => $get['search'],
    'compare' => 'LIKE'
  ),
  array(
    'key' => '_ltc_district_address',
    'value' => $get['search'],
    'compare' => 'LIKE'
  )
);

Заменить $get['search']на значение поиска

davexpression
источник