Utilizzare meta query ('meta_query') con una query di ricerca ('s')
Sto cercando di costruire una ricerca che non solo cerchi nei campi predefiniti (titolo, contenuto ecc.) ma anche in uno specifico campo personalizzato.
La mia query attuale:
$args = array(
'post_type' => 'post',
's' => $query,
'meta_query' => array(
array(
'key' => 'speel',
'value' => $query,
'compare' => 'LIKE'
)
)
);
$search = new WP_Query( $args )
...
Questo restituisce i post che corrispondono sia alla query di ricerca CHE alla meta query, ma vorrei che restituisse anche i post dove corrisponde semplicemente uno dei due criteri.
Qualche idea?

Ho cercato per ore una soluzione a questo problema. L'unione degli array non è la strada da percorrere, specialmente quando le query sono complesse e devi poter aggiungere meta query in futuro. La soluzione, che è semplicemente bellissima, consiste nel cambiare 's' in modo che permetta di cercare sia nei titoli che nei campi 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;
// Esegui solo una volta:
static $nr = 0;
if( 0 != $nr++ ) return $sql;
// WHERE modificato
$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;
});
}
});
Utilizzo:
$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'
);
//se c'è più di una meta query, usa 'OR'
if(count($meta_query) > 1) {
$meta_query['relation'] = 'OR';
}
// La Query
$args['post_type'] = "staff";
$args['_meta_or_title'] = $search_string; //non uso più 's'
$args['meta_query'] = $meta_query;

Questa è una query, non una ricerca. Se hai plugin che lavorano sulla ricerca, non funziona. Mi sbaglio?

@marek.m dovresti personalizzare il plugin (magari utilizzando un filtro se disponibile) per aggiungere la meta query.

Molto codice può essere ridotto utilizzando una versione modificata di questa risposta.
$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 );

Ho ottimizzato un po' la risposta di @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()
&& //tua condizione extra
) {
$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');
Ora puoi cercare per (titolo, contenuto, excerpt) o (campo meta) o (entrambi).

Ho avuto lo stesso problema, per il mio nuovo sito ho semplicemente aggiunto un nuovo 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));
}
E poi... ho aggiunto qualcosa del genere:
$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;
Cosa ne pensi di questa soluzione alternativa?

In realtà non è male, ma richiede di salvare nuovamente tutti i post esistenti o iniziare a usarlo prima di cominciare ad aggiungere contenuti.

@Berend potresti probabilmente scrivere una funzione che recupera tutti i post e li scorre aggiornando il post_meta per ciascuno. Includila in un template o funzione personalizzata, eseguila una volta e poi eliminala.

Come suggerito da Nick Perkins, ho dovuto unire due query in questo modo:
$q1 = get_posts(array(
'fields' => 'ids', // Seleziona solo gli ID dei post
'post_type' => 'post', // Tipo di post
's' => $query // Termine di ricerca
));
$q2 = get_posts(array(
'fields' => 'ids', // Seleziona solo gli ID dei post
'post_type' => 'post', // Tipo di post
'meta_query' => array( // Query sui campi personalizzati
array(
'key' => 'speel', // Chiave del campo personalizzato
'value' => $query, // Valore da cercare
'compare' => 'LIKE' // Operatore di confronto
)
)
));
// Unisce e rimuove i duplicati dagli array di ID
$unique = array_unique( array_merge( $q1, $q2 ) );
// Recupera i post completi basati sugli ID univoci
$posts = get_posts(array(
'post_type' => 'posts', // Tipo di post
'post__in' => $unique, // Array di ID da includere
'post_status' => 'publish', // Solo post pubblicati
'posts_per_page' => -1 // Mostra tutti i risultati
));
if( $posts ) : foreach( $posts as $post ) :
setup_postdata($post);
// ora puoi usare le funzioni standard del loop come the_title() etc.
endforeach; endif;

Non è ancora possibile senza unire nel 2016? Sto modificando la query di ricerca tramite pre_get_posts, quindi questa non è davvero un'opzione...

Beh, è una specie di hack ma funziona. Devi aggiungere il filtro posts_clauses. Questa funzione di filtro verifica se una qualsiasi delle parole della query esiste nel campo personalizzato "speel" mantenendo intatto il resto della query.
function custom_search_where($pieces) {
// filtro per la tua query
if (is_search() && !is_admin()) {
global $wpdb;
$keywords = explode(' ', get_query_var('s'));
$query = "";
foreach ($keywords as $word) {
// salta possibili avverbi e numeri
if (is_numeric($word) || strlen($word) <= 2)
continue;
$query .= "((mypm1.meta_key = 'speel')";
$query .= " AND (mypm1.meta_value LIKE '%{$word}%')) OR ";
}
if (!empty($query)) {
// aggiunge alla clausola 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);

Ecco un altro modo, basta modificare la richiesta con il filtro 'posts_where_request'. Tutto rimarrà invariato tranne ('s' AND 'meta_query') => ('s' OR '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'))
)
questo è il codice
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];
}

Non riuscivo a trovare una soluzione per cercare più parole chiave che potessero essere combinate nel titolo del post, nella descrizione E/O in una o più meta, quindi ho creato la mia aggiunta alla funzione di ricerca.
Tutto ciò che devi fare è aggiungere il seguente codice nel file function.php, e ogni volta che usi l'argomento 's' in una normale funzione WP_Query() e vuoi che cerchi anche in uno o più campi meta, basta aggiungere un argomento 's_meta_keys'
che è un array delle chiavi meta in cui vuoi cercare:
/************************************************************************\
|** **|
|** Permette alla funzione di ricerca WP_Query() di cercare **|
|** più parole chiave nei meta oltre a post_title e post_content **|
|** **|
|** By rAthus @ Arkanite **|
|** Created: 2020-08-18 **|
|** Updated: 2020-08-19 **|
|** **|
|** Usa il solito argomento 's' e aggiungi un argomento 's_meta_keys' **|
|** contenente un array delle chiavi meta in cui vuoi cercare :) **|
|** **|
|** Esempio : **|
|** **|
|** $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'); // aggiunge la funzione di ricerca speciale ad ogni query get_posts (include 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']) { // se stiamo cercando usando l'argomento 's' e abbiamo aggiunto un argomento 's_meta_keys'
global $wpdb;
$search = $query->query_vars['s']; // ottiene la stringa di ricerca
$ids = array(); // inizializza l'array degli ID di post corrispondenti per ogni parola chiave cercata
foreach (explode(' ',$search) as $term) { // suddivide le parole chiave e cerca risultati corrispondenti per ciascuna
$term = trim($term); // rimuove spazi non necessari
if (!empty($term)) { // verifica che la parola chiave non sia vuota
$query_posts = $wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE post_status='publish' AND ((post_title LIKE '%%%s%%') OR (post_content LIKE '%%%s%%'))", $term, $term); // cerca nel titolo e nel contenuto come fa la funzione normale
$ids_posts = [];
$results = $wpdb->get_results($query_posts);
if ($wpdb->last_error)
die($wpdb->last_error);
foreach ($results as $result)
$ids_posts[] = $result->ID; // raccoglie gli ID dei post corrispondenti
$query_meta = [];
foreach($query->query_vars['s_meta_keys'] as $meta_key) // ora costruisce una query di ricerca per cercare in ogni chiave meta desiderata
$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; // raccoglie gli ID dei post corrispondenti
$merged = array_merge($ids_posts,$ids_metas); // unisce gli ID risultanti da entrambe le query
$unique = array_unique($merged); // rimuove duplicati
if (!$unique)
$unique = array(0); // se nessun risultato, aggiunge un ID "0" altrimenti verranno restituiti tutti i post
$ids[] = $unique; // aggiunge l'array di ID corrispondenti nell'array principale
}
}
if (count($ids)>1)
$intersected = call_user_func_array('array_intersect',$ids); // se più parole chiave mantiene solo gli ID presenti in tutti gli array corrispondenti
else
$intersected = $ids[0]; // altrimenti mantiene il singolo array di ID corrispondenti
$unique = array_unique($intersected); // rimuove duplicati
if (!$unique)
$unique = array(0); // se nessun risultato, aggiunge un ID "0" altrimenti verranno restituiti tutti i post
unset($query->query_vars['s']); // rimuove la normale query di ricerca
$query->set('post__in',$unique); // aggiunge un filtro per ID post invece
}
}
Esempio di utilizzo:
$search= "parole da cercare";
$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);
Questo esempio cercherà le parole "parole da cercare" nei titoli dei post, nelle descrizioni e nelle chiavi meta 'short_desc' e 'tags'.
Le parole chiave possono essere trovate in uno o più campi, in qualsiasi ordine, restituirà qualsiasi post che contenga tutte le parole chiave in uno qualsiasi dei campi designati.
Ovviamente puoi forzare la ricerca in una lista di chiavi meta che includi nella funzione ed eliminare gli argomenti extra se vuoi che TUTTE le query di ricerca includano queste chiavi meta :)
Spero che questo aiuti chiunque affronti lo stesso problema che ho avuto io!

Ho trovato una soluzione pulita nel core di WordPress.
Gli sviluppatori di WordPress avevano già questo problema per la ricerca negli allegati _wp_attached_file
meta e hanno risolto il problema in questa funzione:
_filter_query_attachment_filenames()
Prendendo spunto da questa funzione, ho scritto il seguente codice per cercare nei metadati:
/**
* Abilita la ricerca nelle tabelle postmeta e posts in una singola query
*
* @see _filter_query_attachment_filenames()
*/
add_filter( 'posts_clauses', function ( $clauses ) {
global $wpdb;
// Esegui solo una volta:
static $counter = 0;
if ( 0 != $counter ++ ) {
return $clauses;
}
foreach (
[
'my_custom_meta_1',
'my_custom_meta_2',
] as $index => $meta_key
) {
// Aggiunge un LEFT JOIN della tabella postmeta per non interferire con i JOIN esistenti.
$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 );

Tutte le soluzioni sopra indicate restituiscono risultati solo se esiste una corrispondenza nella meta chiave "speel". Se hai risultati altrove ma non in questo campo, non otterrai nulla. Nessuno vuole questo.
È necessario un left join. Il seguente codice ne creerà 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 risposta di @satbir-kira funziona bene ma cercherà solo nei meta e nel titolo del post. Se vuoi effettuare la ricerca nei meta, nel titolo e nel contenuto, ecco la versione modificata.
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;
// Esegui solo una volta:
static $nr = 0;
if( 0 != $nr++ ) return $sql;
// WHERE modificato
$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;
});
}
});
Ed ecco come utilizzarlo:
$args['_meta_or_title'] = $get['search']; //non uso più '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'
)
);
Sostituisci $get['search']
con il tuo valore di ricerca

@SubhojitMukherjee Hai trovato una soluzione quando aggiungi un tax_query? Ho esattamente lo stesso problema di te :)

Ok ho trovato la soluzione! Grazie a Sully (https://wordpress.stackexchange.com/questions/403040/query-to-get-result-by-title-or-meta-along-with-tax-query-parameter) Ecco il codice da aggiornare per farlo funzionare 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); }

per me funziona perfettamente il seguente codice:
$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 );

Questa è un'ottima soluzione ma devi sistemare una cosa. Quando chiami 'post__in' devi impostare un array di ID e $unique è un array di post.
esempio:
$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(); //qui inizializzi il tuo array
foreach($posts as $post)
{
$array[] = $post->ID; //riempi l'array con gli ID dei post
}
$posts = get_posts(array(
'post_type' => 'posts',
'post__in' => $array,
'post_status' => 'publish',
'posts_per_page' => -1
));

Ho scoperto che la risposta di Asad Manzoors ha funzionato per me. Se qualcuno ne avesse bisogno, la mia versione richiedeva l'implementazione di 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));
// Se non vengono trovati post, assicurarsi che $query non selezioni tutti i post
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
));
