Usando meta query ('meta_query') con una consulta de búsqueda ('s')
Estoy tratando de construir una búsqueda que no solo busque en los valores predeterminados (título, contenido, etc.) sino también en 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 las entradas que coinciden tanto con la consulta de búsqueda Y la meta query, pero también me gustaría que devolviera entradas donde simplemente coincida con cualquiera de ellas.
¿Alguna idea?

He estado buscando durante horas una solución a este problema. La fusión de arrays no es el camino a seguir, especialmente cuando las consultas son complejas y necesitas poder añadir más consultas meta en el futuro. La solución, que es sencillamente hermosa, es cambiar 's' por uno que permita buscar tanto en títulos como en campos meta.
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;
// Solo se ejecuta una vez:
static $nr = 0;
if( 0 != $nr++ ) return $sql;
// WHERE modificado
$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'
);
// Si hay más de una consulta meta, usar 'OR'
if(count($meta_query) > 1) {
$meta_query['relation'] = 'OR';
}
// La Consulta
$args['post_type'] = "staff";
$args['_meta_or_title'] = $search_string; //ya no se usa 's'
$args['meta_query'] = $meta_query;
$the_query = new WP_Query($args);

¡Fantástico, esto funcionó muy bien para mí! ¡Gran solución! ¡Gracias!

Esta es una consulta, no una búsqueda. Si tienes plugins que funcionan en búsquedas, no funcionará. ¿Estoy equivocado?

@marek.m necesitarías personalizar el plugin (quizás usando un filtro si está disponible) para agregar la consulta de metadatos.

Mucho código puede reducirse usando 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 );

He optimizado un poco la respuesta de @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()
&& //tu condición adicional
) {
$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 puedes buscar por (título, contenido, extracto) o (campo meta) o (ambos).

Tuve el mismo problema, para mi nuevo sitio simplemente agregué un nuevo meta "title":
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));
}
Y luego... simplemente agregué algo como esto:
$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 alternativa?

En realidad no está mal, pero requiere que vuelvas a guardar todas las entradas existentes o que comiences a usarlo antes de empezar a agregar contenido.

@Berend probablemente podrías escribir una función que obtenga todas las entradas y las recorra actualizando el post_meta para cada una. Inclúyela en una plantilla o función personalizada, ejecútala una vez y luego descártala.

Siguiendo la sugerencia de Nick Perkins, tuve que combinar dos consultas de la siguiente manera:
$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);
// ahora usa funciones estándar del loop como the_title() etc.
endforeach; endif;

¿Sigue sin ser posible sin fusionar en 2016? Estoy editando la consulta de búsqueda a través de pre_get_posts, así que esto realmente no es una opción...

Bueno, es un poco un truco pero funciona. Necesitas agregar el filtro posts_clauses. Esta función de filtro verifica si alguna de las palabras de la consulta existe en el campo personalizado "speel" y el resto de la consulta permanece intacta.
function custom_search_where($pieces) {
// filtro para tu consulta
if (is_search() && !is_admin()) {
global $wpdb;
$keywords = explode(' ', get_query_var('s'));
$query = "";
foreach ($keywords as $word) {
// omitir posibles adverbios y números
if (is_numeric($word) || strlen($word) <= 2)
continue;
$query .= "((mypm1.meta_key = 'speel')";
$query .= " AND (mypm1.meta_value LIKE '%{$word}%')) OR ";
}
if (!empty($query)) {
// agregar a la cláusula where
$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);

Aquí hay otra forma, simplemente cambia la solicitud con el filtro 'posts_where_request'. Todo seguirá siendo el valor predeterminado excepto ('s' Y 'meta_query') => ('s' O 'meta_query').
AND ( ((posts.post_title LIKE 'Lily') OR (posts.post_excerpt LIKE 'Lily') OR (posts.post_content LIKE 'Lily')) )
AND ( ( postmeta.meta_key = 'author' AND postmeta.meta_value LIKE 'Lily' ) )
=>
AND (
( ( postmeta.meta_key = 'author' AND postmeta.meta_value LIKE 'Lily' ) )
OR
((posts.post_title LIKE 'Lily') OR (posts.post_excerpt LIKE 'Lily') OR (posts.post_content LIKE 'Lily'))
)
este es el código
function edit_request_wp_query( $where ) {
global $wpdb;
if ( strpos($where, $wpdb->postmeta.'.meta_key') && strpos($where, $wpdb->posts.'.post_title') ) {
$string = $where;
$index_meta = index_argument_in_request($string, $wpdb->postmeta.'.meta_key', $wpdb->postmeta.'.meta_value');
$meta_query = substr($string, $index_meta['start'], $index_meta['end']-$index_meta['start']);
$string = str_replace( $meta_query, '', $string );
$meta_query = ltrim($meta_query, 'AND').' OR ';
$index_s = index_argument_in_request($string, $wpdb->posts.'.post_title');
$insert_to = strpos($string, '(', $index_s['start'])+1;
$string = substr_replace($string, $meta_query, $insert_to, 0);
$where = $string;
}
return $where;
}
add_filter('posts_where_request', 'edit_request_wp_query');
function index_argument_in_request($string, $key_start, $key_end = '') {
if (!$key_end) $key_end = $key_start;
$index_key_start = strpos($string, $key_start);
$string_before = substr($string, 0, $index_key_start);
$index_start = strrpos($string_before, 'AND');
$last_index_key = strrpos($string, $key_end);
$index_end = strpos($string, 'AND', $last_index_key);
return ['start' => $index_start, 'end' => $index_end];
}

No encontré una solución para buscar múltiples palabras clave que puedan estar mezcladas en el título de la publicación, la descripción Y/O una o varias metadatos, así que hice mi propia adición a la función de búsqueda.
Todo lo que necesitas es agregar el siguiente código en function.php, y cada vez que uses el argumento 's' en una función estándar WP_Query() y quieras que también busque en uno o varios campos de metadatos, simplemente agregas un argumento 's_meta_keys'
que es un array de la(s) clave(s) de metadatos donde quieres buscar:
/************************************************************************\
|** **|
|** Permitir que la función de búsqueda de WP_Query() busque **|
|** múltiples palabras clave en metadatos además de post_title y **|
|** post_content **|
|** **|
|** Por rAthus @ Arkanite **|
|** Creado: 2020-08-18 **|
|** Actualizado: 2020-08-19 **|
|** **|
|** Solo usa el argumento habitual 's' y añade un argumento **|
|** 's_meta_keys' que contenga un array de la(s) clave(s) de **|
|** metadatos donde quieres buscar :) **|
|** **|
|** Ejemplo : **|
|** **|
|** $args = array( **|
|** 'numberposts' => -1, **|
|** 'post_type' => 'post', **|
|** 's' => $MY_SEARCH_STRING, **|
|** 's_meta_keys' => array('META_KEY_1','META_KEY_2'); **|
|** 'orderby' => 'date', **|
|** 'order' => 'DESC', **|
|** ); **|
|** $posts = new WP_Query($args); **|
|** **|
\************************************************************************/
add_action('pre_get_posts', 'my_search_query'); // agregar la función de búsqueda especial en cada consulta get_posts (esto incluye WP_Query())
function my_search_query($query) {
if ($query->is_search() and $query->query_vars and $query->query_vars['s'] and $query->query_vars['s_meta_keys']) { // si estamos buscando usando el argumento 's' y añadimos un argumento 's_meta_keys'
global $wpdb;
$search = $query->query_vars['s']; // obtener la cadena de búsqueda
$ids = array(); // iniciar array de IDs de publicaciones coincidentes por palabra clave buscada
foreach (explode(' ',$search) as $term) { // separar palabras clave y buscar resultados coincidentes para cada una
$term = trim($term); // eliminar espacios innecesarios
if (!empty($term)) { // verificar que la palabra clave no esté vacía
$query_posts = $wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE post_status='publish' AND ((post_title LIKE '%%%s%%') OR (post_content LIKE '%%%s%%'))", $term, $term); // buscar en título y contenido como lo hace la función normal
$ids_posts = [];
$results = $wpdb->get_results($query_posts);
if ($wpdb->last_error)
die($wpdb->last_error);
foreach ($results as $result)
$ids_posts[] = $result->ID; // recoger IDs de publicaciones coincidentes
$query_meta = [];
foreach($query->query_vars['s_meta_keys'] as $meta_key) // ahora construir una consulta de búsqueda para buscar en cada clave de metadatos deseada
$query_meta[] = $wpdb->prepare("meta_key='%s' AND meta_value LIKE '%%%s%%'", $meta_key, $term);
$query_metas = $wpdb->prepare("SELECT * FROM {$wpdb->postmeta} WHERE ((".implode(') OR (',$query_meta)."))");
$ids_metas = [];
$results = $wpdb->get_results($query_metas);
if ($wpdb->last_error)
die($wpdb->last_error);
foreach ($results as $result)
$ids_metas[] = $result->post_id; // recoger IDs de publicaciones coincidentes
$merged = array_merge($ids_posts,$ids_metas); // combinar los IDs de título, contenido y metadatos resultantes de ambas consultas
$unique = array_unique($merged); // eliminar duplicados
if (!$unique)
$unique = array(0); // si no hay resultados, agregar un ID "0" de lo contrario se devolverán todas las publicaciones
$ids[] = $unique; // agregar array de IDs coincidentes al array principal
}
}
if (count($ids)>1)
$intersected = call_user_func_array('array_intersect',$ids); // si hay varias palabras clave, mantener solo IDs que estén en todos los arrays coincidentes
else
$intersected = $ids[0]; // de lo contrario, mantener el único array de IDs coincidentes
$unique = array_unique($intersected); // eliminar duplicados
if (!$unique)
$unique = array(0); // si no hay resultados, agregar un ID "0" de lo contrario se devolverán todas las publicaciones
unset($query->query_vars['s']); // eliminar consulta de búsqueda normal
$query->set('post__in',$unique); // agregar un filtro por ID de publicación en su lugar
}
}
Ejemplo de uso:
$search= "palabras clave a buscar";
$args = array(
'numberposts' => -1,
'post_type' => 'post',
's' => $search,
's_meta_keys' => array('short_desc','tags');
'orderby' => 'date',
'order' => 'DESC',
);
$posts = new WP_Query($args);
Este ejemplo buscará las palabras clave "palabras clave a buscar" en los títulos de las publicaciones, descripciones y las claves de metadatos 'short_desc' y 'tags'.
Las palabras clave pueden encontrarse en uno o varios de los campos, en cualquier orden, devolverá cualquier publicación que tenga todas las palabras clave en cualquiera de los campos designados.
Obviamente puedes forzar la búsqueda en una lista de claves de metadatos que incluyas en la función y deshacerte de los argumentos adicionales si quieres que TODAS las consultas de búsqueda incluyan estas claves de metadatos :)
¡Espero que esto ayude a cualquiera que enfrente el mismo problema que yo!

Encontré una solución limpia en el núcleo de WordPress.
Los desarrolladores de WordPress ya tuvieron este problema para buscar en archivos adjuntos _wp_attached_file
meta y solucionaron este problema en esta función:
_filter_query_attachment_filenames()
Tomando la idea de esta función, escribí el siguiente código para buscar en metadatos:
/**
* Habilita la búsqueda en tablas postmeta y posts en una sola consulta
*
* @see _filter_query_attachment_filenames()
*/
add_filter( 'posts_clauses', function ( $clauses ) {
global $wpdb;
// Solo se ejecuta una vez:
static $counter = 0;
if ( 0 != $counter ++ ) {
return $clauses;
}
foreach (
[
'my_custom_meta_1',
'my_custom_meta_2',
] as $index => $meta_key
) {
// Añade un LEFT JOIN de la tabla postmeta para no afectar los JOINs existentes.
$clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS my_sql{$index} ON ( {$wpdb->posts}.ID = my_sql{$index}.post_id AND my_sql{$index}.meta_key = '{$meta_key}' )";
$clauses['where'] = preg_replace(
"/\({$wpdb->posts}.post_content (NOT LIKE|LIKE) (\'[^']+\')\)/",
"$0 OR ( my_sql{$index}.meta_value $1 $2 )",
$clauses['where']
);
}
return $clauses;
}, 999 );

Todas las soluciones anteriores solo devuelven resultados si existe una coincidencia en la meta clave "speel". Si tienes resultados en otros lugares pero no en este campo, no obtendrás nada. Nadie quiere eso.
Se necesita un LEFT JOIN. El siguiente código 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);

La respuesta de @satbir-kira funciona muy bien pero solo buscará en los metadatos y el título de la publicación. Si deseas que busque en metadatos, 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;
// Solo se ejecuta una vez:
static $nr = 0;
if( 0 != $nr++ ) return $sql;
// WHERE modificado
$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']; //ya no se usa 's'
$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'
)
);
Reemplaza $get['search']
con tu valor de búsqueda

@SubhojitMukherjee ¿Encontraste una solución cuando agregas un tax_query? Tengo exactamente el mismo problema que tú :)

¡Ok, encontré mi solución! Gracias a Sully (https://wordpress.stackexchange.com/questions/403040/query-to-get-result-by-title-or-meta-along-with-tax-query-parameter) Aquí el código para actualizar y que funcione con tax_query:

add_filter( 'get_meta_sql', function( $sql, $queries, $type ) use ( $title ){ global $wpdb; static $nr = 0; if( 'post' !== $type || 0 != $nr++ ) return $sql; $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; }, 10, 3); }

para mí funciona perfectamente el siguiente código:
$search_word = $_GET['id'];
$data['words'] = trim(urldecode($search_word));
$q1 = new WP_Query( array(
'post_type' => array('notas', 'productos'),
'posts_per_page' => -1,
's' => $search_word
));
$q2 = new WP_Query( array(
'post_type' => array('notas', 'productos'),
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'subtitulo',
'value' => $search_word,
'compare' => 'LIKE'
),
array(
'key' => 'thumbnail_bajada',
'value' => $search_word,
'compare' => 'LIKE'
)
)
));
$result = new WP_Query();
$result->posts = array_unique( array_merge( $q1->posts, $q2->posts ), SORT_REGULAR );
$result->post_count = count( $result->posts );

Esta es una gran solución pero necesitas corregir una cosa. Cuando llamas a 'post__in' necesitas establecer un array de IDs y $unique es un array de posts.
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(); //aquí inicializas tu array
foreach($posts as $post)
{
$array[] = $post->ID; //llena el array con los IDs de los posts
}
$posts = get_posts(array(
'post_type' => 'posts',
'post__in' => $array,
'post_status' => 'publish',
'posts_per_page' => -1
));

Descubrí que la respuesta de Asad Manzoors funcionó para mí. Si alguien la necesita, mi versión requería implementar paged
:
$search_query = trim(esc_html( get_search_query() ));
$posts_per_page = $wp_query->query_vars['posts_per_page'];
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$q1 = new WP_Query(array(
's' => $search_query,
'post_type' => array('page', 'post'),
'posts_per_page' => -1,
'fields' => 'ids'
));
$q2 = new WP_Query(array(
'fields' => 'ids',
'post_type' => array('page', 'post'),
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'custom_body',
'value' => $search_query,
'compare' => 'LIKE'
)
)
));
$unique = array_unique(array_merge($q1->posts, $q2->posts));
// Si no se encuentran posts, asegurarse de que $query no seleccione todos los posts.
if (!$unique) {
$unique = array(-1);
}
$query = new WP_Query(array(
'post_type' => array('page', 'post'),
'post__in' => $unique,
'paged' => $paged,
'post_status' => 'publish',
'posts_per_page' => $posts_per_page
));
