Uso de metaconsulta ('meta_query') con una consulta de búsqueda ('s')

25

Intentar crear una búsqueda que no solo busque los valores predeterminados (título, contenido, etc.) sino también un campo personalizado específico.

Mi consulta actual:

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

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

Esto devuelve publicaciones que coinciden con la consulta de búsqueda Y la metaconsulta, pero también me gustaría que también devuelva publicaciones donde simplemente coincida con cualquiera de ellas.

¿Algunas ideas?

luke
fuente
Lo que desea hacer no es posible con solo una consulta de búsqueda. Debería ejecutar ambas consultas por separado y luego combinarlas. Esto se ha descrito en esta otra respuesta. Debería ayudarte con cómo hacerlo. wordpress.stackexchange.com/questions/55519/…
Nick Perkins

Respuestas:

20

He estado buscando durante horas una solución a este problema. La combinación de matrices no es el camino a seguir, especialmente cuando las consultas son complejas y debe poder agregarlas a meta consultas en el futuro. La solución que es simplistamente hermosa es cambiar 's' por una que permita buscar títulos y metacampos.

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;
        });
    }
});

Uso:

$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)
Satbir Kira
fuente
esta es la manera correcta,
Zorox
Fantástico, esto funcionó muy bien para mí. ¡Gran solución! ¡Gracias!
Fabiano
Esta es una consulta, no una búsqueda. Si tiene complementos que funcionan en la búsqueda, no funciona. ¿Me equivoco?
marek.m
5

Se puede reducir una gran cantidad de código utilizando una versión modificada de esta respuesta .

$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
fuente
Esto funcionó muy bien para mí (especialmente porque necesitaba usar WP_Query en lugar de get_posts). Tuve que modificar su línea post_count para ser: $result->post_count = count( $result->posts );porque de lo contrario solo obtenía 1 resultado.
GreatBlakes
4

He optimizado la respuesta de @Stabir Kira un poco

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');

Ahora puede buscar por (título, contenido, excreto) o (metacampo) o (ambos).

Sebastian Piskorski
fuente
3

Según la sugerencia de Nick Perkins , tuve que fusionar dos consultas así:

$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;
luke
fuente
1
¿Todavía no es posible sin fusionarse en 2016? Estoy editando consulta de búsqueda a través de pre_get_posts, así que esto no es realmente una opción ...
trainoasis
@trainoasis No lo creo. Lo he estado intentando desde las últimas 2 horas y una búsqueda en Google me trajo aquí.
Umair Khan Jadoon el
2

Bueno, es una especie de truco, pero funciona. Necesita agregar el filtro posts_clauses. Esta verificación de la función de filtro para cualquiera de las palabras de consulta existe en el campo personalizado "speel" y la consulta restante permanece intacta.

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);
SEÑOR
fuente
2

Tuve el mismo problema, para mi nuevo sitio, acabo de agregar un nuevo "título" meta:

funciones.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)); 
}

Y luego ... solo agrega algo como eso:

$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;

¿Qué opinas sobre esta solución?

MarcoO
fuente
1
En realidad, eso no es malo, pero requiere que vuelva a guardar todas las publicaciones existentes o comience a usarlo antes de comenzar a agregar contenido.
Berend
@Berend probablemente podría escribir una función que obtenga todas las publicaciones y recorra las actualizaciones de post_meta para cada una. Inclúyalo en una plantilla o función personalizada, ejecútelo una vez y luego deséchelo.
Slam
1

Todas las soluciones anteriores solo devuelven resultados si existe una coincidencia en la metaclave speel. Si tiene resultados en otro lugar pero no en este campo, no obtendrá nada. Nadie quiere eso.

Se necesita una unión izquierda. Lo siguiente creará uno.

           $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);
Tim
fuente
0

Esta es una gran solución, pero necesita arreglar una cosa. Cuando llama a 'post__in', necesita establecer una matriz de identificadores y $ unique es una matriz de publicaciones.

ejemplo:

$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
));
Gabriel Bustos
fuente
0

La respuesta de @ satbir-kira funciona muy bien, pero solo buscará a través del meta y el título de la publicación. Si desea buscar a través de meta, título y contenido, aquí está la versión modificada.

    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;
        });
      }
    });

Y aquí está su uso:

$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'
  )
);

Reemplace $get['search']con su valor de búsqueda

davexpression
fuente