Utilizarea unei interogări meta ('meta_query') împreună cu o interogare de căutare ('s')
Încerc să construiesc o căutare care să nu caute doar în câmpurile implicite (titlu, conținut etc) ci și într-un câmp personalizat specific.
Interogarea mea actuală:
$args = array(
'post_type' => 'post',
's' => $query,
'meta_query' => array(
array(
'key' => 'speel',
'value' => $query,
'compare' => 'LIKE'
)
)
);
$search = new WP_Query( $args )
...
Aceasta returnează postări care se potrivesc atât cu interogarea de căutare CÂT ȘI cu interogarea meta, dar aș dori să returneze și postări care se potrivesc cu oricare dintre ele.
Aveți vreo idee?

Am căutat ore întregi o soluție pentru această problemă. Unirea array-urilor nu este soluția, mai ales când interogările sunt complexe și trebuie să poți adăuga la meta queries în viitor. Soluția care este simplu și frumoasă este să schimbi 's' cu unul care permite căutarea atât în titluri cât și în câmpurile 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;
// Rulează doar o dată:
static $nr = 0;
if( 0 != $nr++ ) return $sql;
// WHERE modificat
$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;
});
}
});
Utilizare:
$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'
);
//dacă există mai mult de un meta query, le combinăm cu 'OR'
if(count($meta_query) > 1) {
$meta_query['relation'] = 'OR';
}
// Interogarea
$args['post_type'] = "staff";
$args['_meta_or_title'] = $search_string; //nu mai folosim 's'
$args['meta_query'] = $meta_query;

Fantastic, a funcționat excelent pentru mine. Soluție grozavă! Mulțumesc!

Aceasta este o interogare, nu o căutare. Dacă ai pluginuri care funcționează pe căutare, nu va funcționa. Mă înșel?

@marek.m va trebui să personalizezi plugin-ul (poate folosind un filtru dacă este disponibil) pentru a adăuga meta query-ul.

Nu funcționează dacă adaug parametrul tax_query împreună cu

O mulțime de cod poate fi redusă folosind o versiune modificată a acestui răspuns.
$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 );

Am optimizat puțin răspunsul lui @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()
&& //condiția ta suplimentară
) {
$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');
Acum poți căuta după (titlu, conținut, rezumat) sau (câmp meta) sau (ambele).

Am avut aceeași problemă, pentru noul meu site am adăugat doar un nou 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));
}
Apoi.. doar adaugă ceva de genul acesta:
$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;
Ce părere ai despre această soluție alternativă?

Nu e chiar rău, dar necesită fie să resalvezi toate postările existente, fie să începi să-l folosești înainte de a adăuga conținut.

@Berend ai putea probabil să scrii o funcție care să preia toate postările și să le parcurgă actualizând post_meta pentru fiecare. Include-o într-un șablon personalizat sau funcție, rulează-o o dată, apoi elimin-o.

Conform sugestiei lui Nick Perkins, a trebuit să combin două interogări astfel:
$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);
// acum poți folosi funcții standard de loop precum the_title() etc.
endforeach; endif;

Este încă imposibil fără a face merge în 2016? Editez interogările de căutare prin pre_get_posts, așa că această opțiune chiar nu este fezabilă...

Ei bine, este un fel de hack, dar funcționează. Trebuie să adaugi un filtru posts_clauses. Această funcție de filtru verifică dacă oricare dintre cuvintele din interogare există în câmpul personalizat "speel", iar restul interogării rămâne neschimbat.
function custom_search_where($pieces) {
// filtru pentru interogarea ta
if (is_search() && !is_admin()) {
global $wpdb;
$keywords = explode(' ', get_query_var('s'));
$query = "";
foreach ($keywords as $word) {
// sări peste adverbe și numere posibile
if (is_numeric($word) || strlen($word) <= 2)
continue;
$query .= "((mypm1.meta_key = 'speel')";
$query .= " AND (mypm1.meta_value LIKE '%{$word}%')) OR ";
}
if (!empty($query)) {
// adaugă la clauza 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);

Iată o altă metodă, schimbă doar cererea cu filtrul 'posts_where_request'. Totul va rămâne implicit, cu excepția ('s' ȘI 'meta_query') => ('s' SAU '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'))
)
acesta este codul
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];
}

Nu am găsit nicio soluție pentru a căuta mai multe cuvinte cheie care pot fi amestecate fie în titlul postării, descriere ȘI/SAU una sau mai multe metadate, așa că am creat propria mea adăugare la funcția de căutare.
Tot ce trebuie să faci este să adaugi următorul cod în function.php, și ori de câte ori folosești argumentul 's' într-o funcție standard WP_Query() și vrei să caute și într-unul sau mai multe câmpuri meta, pur și simplu adaugi un argument 's_meta_keys'
care este un array cu cheile meta pe care vrei să le incluzi în căutare:
/************************************************************************\
|** **|
|** Permite funcției de căutare WP_Query() să caute mai multe **|
|** cuvinte cheie în metadate în plus față de post_title și **|
|** post_content **|
|** **|
|** De rAthus @ Arkanite **|
|** Creat: 2020-08-18 **|
|** Actualizat: 2020-08-19 **|
|** **|
|** Folosește argumentul obișnuit 's' și adaugă un argument **|
|** 's_meta_keys' care conține un array cu cheile meta în care **|
|** dorești să cauți :) **|
|** **|
|** Exemplu : **|
|** **|
|** $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'); // adaugă funcția specială de căutare la fiecare query get_posts (asta 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']) { // dacă căutăm folosind argumentul 's' și am adăugat un argument 's_meta_keys'
global $wpdb;
$search = $query->query_vars['s']; // obține șirul de căutare
$ids = array(); // inițializează array cu ID-urile postărilor care se potrivesc cu fiecare cuvânt cheie
foreach (explode(' ',$search) as $term) { // despachetează cuvintele cheie și caută rezultate pentru fiecare
$term = trim($term); // elimină spațiile inutile
if (!empty($term)) { // verifică dacă cuvântul cheie nu este gol
$query_posts = $wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE post_status='publish' AND ((post_title LIKE '%%%s%%') OR (post_content LIKE '%%%s%%'))", $term, $term); // caută în titlu și conținut ca funcția 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; // colectează ID-urile postărilor care se potrivesc
$query_meta = [];
foreach($query->query_vars['s_meta_keys'] as $meta_key) // construiește o interogare pentru a căuta în fiecare cheie meta dorită
$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; // colectează ID-urile postărilor care se potrivesc
$merged = array_merge($ids_posts,$ids_metas); // combină ID-urile din titlu, conținut și metadate rezultate din ambele interogări
$unique = array_unique($merged); // elimină duplicatele
if (!$unique)
$unique = array(0); // dacă nu există rezultate, adaugă un ID "0" altfel vor fi returnate toate postările
$ids[] = $unique; // adaugă array-ul cu ID-uri potrivite în array-ul principal
}
}
if (count($ids)>1)
$intersected = call_user_func_array('array_intersect',$ids); // dacă sunt mai multe cuvinte cheie, păstrează doar ID-urile găsite în toate array-urile
else
$intersected = $ids[0]; // altfel păstrează array-ul unic cu ID-uri potrivite
$unique = array_unique($intersected); // elimină duplicatele
if (!$unique)
$unique = array(0); // dacă nu există rezultate, adaugă un ID "0" altfel vor fi returnate toate postările
unset($query->query_vars['s']); // elimină interogarea normală de căutare
$query->set('post__in',$unique); // adaugă un filtru după ID-ul postării în schimb
}
}
Exemplu de utilizare:
$search= "cuvinte cheie de căutat";
$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);
Acest exemplu va căuta cuvintele cheie "cuvinte cheie de căutat" în titlurile postărilor, descrieri și în cheile meta 'short_desc' și 'tags'.
Cuvintele cheie pot fi găsite într-unul sau mai multe dintre câmpuri, în orice ordine, va returna orice postare care are toate cuvintele cheie în oricare dintre câmpurile desemnate.
Poți evident să forțezi căutarea într-o listă de chei meta pe care le incluzi în funcție și să scapi de argumentele suplimentare dacă vrei ca TOATE interogările de căutare să includă aceste chei meta :)
Sper că acest lucru va ajuta pe oricine se confruntă cu aceeași problemă ca mine!

Am găsit o soluție elegantă în nucleul WordPress.
Dezvoltatorii WordPress au avut deja această problemă pentru căutarea în fișierele atașate _wp_attached_file
meta și au rezolvat-o în această funcție:
_filter_query_attachment_filenames()
Preluând ideea din această funcție, am scris următorul cod pentru căutarea în metadate:
/**
* Activează căutarea în tabelele postmeta și posts într-o singură interogare
*
* @see _filter_query_attachment_filenames()
*/
add_filter( 'posts_clauses', function ( $clauses ) {
global $wpdb;
// Rulează o singură dată:
static $counter = 0;
if ( 0 != $counter ++ ) {
return $clauses;
}
foreach (
[
'my_custom_meta_1',
'my_custom_meta_2',
] as $index => $meta_key
) {
// Adaugă un LEFT JOIN al tabelului postmeta pentru a nu afecta JOIN-urile existente.
$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 );

Toate soluțiile de mai sus returnează rezultate doar dacă există o potrivire în cheia meta 'speel'. Dacă aveți rezultate în alte părți, dar nu în acest câmp, nu veți obține nimic. Nimeni nu dorește asta.
Este necesară o uniune (join) la stânga. Următorul cod va crea una.
$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);

Răspunsul lui @satbir-kira funcționează excelent, dar va căuta doar în meta-date și titlul postării. Dacă doriți să căutați în meta-date, titlu și conținut, iată versiunea modificată.
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;
// Rulează o singură dată:
static $nr = 0;
if( 0 != $nr++ ) return $sql;
// WHERE modificat
$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;
});
}
});
Și iată cum se folosește:
$args['_meta_or_title'] = $get['search']; //nu se mai folosește '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'
)
);
Înlocuiți $get['search']
cu valoarea dumneavoastră de căutare

@SubhojitMukherjee Ai găsit o soluție când adaugi un tax_query? Am exact aceeași problemă ca tine :)

Ok, am găsit soluția! Mulțumesc lui Sully (https://wordpress.stackexchange.com/questions/403040/query-to-get-result-by-title-or-meta-along-with-tax-query-parameter) Iată codul care trebuie actualizat pentru a funcționa cu 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); }

pentru mine funcționează perfect următorul cod:
$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 );

Aceasta este o soluție excelentă, dar trebuie să corectezi un lucru. Când apelezi 'post__in' trebuie să setezi un array de id-uri și $unique este un array de postări.
exemplu:
$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(); //aici inițializezi array-ul tău
foreach($posts as $post)
{
$array[] = $post->ID; //completează array-ul cu ID-urile postărilor
}
$posts = get_posts(array(
'post_type' => 'posts',
'post__in' => $array,
'post_status' => 'publish',
'posts_per_page' => -1
));

Am descoperit că răspunsul lui Asad Manzoors a funcționat pentru mine. Dacă cineva are nevoie, versiunea mea a necesitat implementarea lui 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));
// Dacă nu s-au găsit postări, asigură-te că interogarea $query nu selectează fiecare postare.
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
));
