Utilisation d'une méta-requête ('meta_query') avec une requête de recherche ('s')

25

Essayer de construire une recherche qui recherche non seulement les valeurs par défaut (titre, contenu, etc.) mais aussi un champ personnalisé spécifique.

Ma requête actuelle:

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

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

Cela renvoie des messages qui correspondent à la fois à la requête de recherche ET à la méta-requête, mais j'aimerais également qu'il renvoie également les messages où il correspond simplement à l'un d'eux.

Des idées?

luke
la source
Ce que vous voulez faire n'est pas possible en utilisant une seule requête de recherche. Vous devez exécuter les deux requêtes séparément, puis les fusionner ensemble. Cela a été décrit dans cette autre réponse. Il devrait vous aider à le faire. wordpress.stackexchange.com/questions/55519/…
Nick Perkins

Réponses:

20

Je cherche depuis des heures une solution à ce problème. La fusion de tableaux n'est pas la voie à suivre, surtout lorsque les requêtes sont complexes et que vous devez être en mesure d'ajouter des méta-requêtes à l'avenir. La solution la plus simple est de remplacer «s» par celui qui permet à la fois de rechercher des titres et des méta champs.

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

Usage:

$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
la source
c'est la bonne façon,
Zorox
Fantastique, cela a très bien fonctionné pour moi. Excellente solution! Merci!
Fabiano
Il s'agit d'une requête, pas d'une recherche. Si vous avez des plugins qui fonctionnent sur la recherche, cela ne fonctionne pas. Ai-je tort?
marek.m
5

Beaucoup de code peut être réduit en utilisant une version modifiée de cette réponse .

$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
la source
Cela a très bien fonctionné pour moi (d'autant plus que j'avais besoin d'utiliser WP_Query au lieu de get_posts). J'ai dû modifier votre ligne post_count pour qu'elle soit: $result->post_count = count( $result->posts );parce que je n'obtenais qu'un seul résultat autrement.
GreatBlakes
4

J'ai optimisé @Stabir Kira répondre un peu

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

Vous pouvez maintenant rechercher par (titre, contenu, extrait) ou (champ méta) ou (les deux).

Sebastian Piskorski
la source
3

Selon la suggestion de Nick Perkins , j'ai dû fusionner deux requêtes comme ceci:

$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
la source
1
N'est-ce pas encore possible sans fusionner en 2016? J'édite la requête de recherche via pre_get_posts, donc ce n'est vraiment pas une option ...
trainoasis
@trainoasis Je ne pense pas. Je l'ai essayé depuis 2 heures et une recherche Google m'a amené ici.
Umair Khan Jadoon
2

Eh bien, c'est une sorte de hack mais ça marche. Vous devez ajouter un filtre posts_clauses. Cette fonction de filtrage vérifie si l'un des mots de la requête existe dans le champ personnalisé "speel" et la requête restante reste intacte.

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);
M
la source
2

j'ai eu le même problème, pour mon nouveau site je viens d'ajouter une nouvelle méta "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)); 
}

Et puis .. ajoutez simplement quelque chose comme ça:

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

Que pensez-vous de cette solution de contournement?

MarcoO
la source
1
Ce n'est pas mal en fait, mais vous oblige à ré-enregistrer tous les messages existants ou à commencer à l'utiliser avant de commencer à ajouter du contenu.
Berend
@Berend vous pourriez probablement écrire une fonction qui récupère tous les articles et les boucle en mettant à jour le post_meta pour chacun. Incluez-le dans un modèle ou une fonction personnalisé, exécutez-le une fois, puis jetez-le.
Slam
1

Toutes les solutions ci-dessus ne renvoient des résultats que si une correspondance existe dans la méta-clé de séparation. Si vous avez des résultats ailleurs mais pas dans ce domaine, vous n'obtiendrez rien. Personne ne veut ça.

Une jointure gauche est nécessaire. Ce qui suit en créera un.

           $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
la source
0

C'est une excellente solution mais vous devez corriger une chose. Lorsque vous appelez «post__in», vous devez définir un tableau d'ID et $ unique est un tableau de messages.

Exemple:

$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
la source
0

La réponse @ satbir-kira fonctionne très bien, mais elle ne fera que rechercher dans la méta et le titre du message. Si vous voulez qu'il recherche par méta, titre et contenu, voici la version modifiée.

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

Et voici son utilisation:

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

Remplacez $get['search']par votre valeur de recherche

davexpression
la source