web-dev-qa-db-fra.com

Tri des résultats de recherche par terme de taxonomie

J'écris un plugin qui fait en sorte que les recherches ne fonctionnent que sur un type de publication personnalisé particulier et trie les résultats en fonction des taxonomies que l'utilisateur sélectionne dans sa configuration de base.

L'administrateur peut sélectionner jusqu'à 4 taxonomies du type de publication personnalisé. Chacune est alors un critère de tri qui doit être appliqué aux résultats de la recherche. L'administrateur peut ensuite sélectionner les termes de chaque taxonomie qui font que le message apparaisse avant les autres dans les résultats de recherche. La première taxonomie sélectionnée sera le critère de tri le plus important; les autres seront appliqués dans l'ordre.

J'ai besoin d'une requête dans les lignes de:

SELECT * FROM wp_posts ORDER BY choosen_terms_of_taxonomy1, choosen_terms_of_axonomy2, ...

Utilisons maintenant quelques exemples de données. Nous avons le type de poste course:

+----+------------+
| ID | post_title |
+----+------------+
| 12 | Cooking    |
+----+------------+
| 13 | Surfing    |
+----+------------+
| 14 | Building   |
+----+------------+
| 15 | Hacking    |
+----+------------+

Ensuite, nous avons deux taxonomies pour ce type de message personnalisé. L'un est place et l'autre est pricetag. La taxonomie place a les termes suivants:

+---------+------------+
| term_id |    slug    |
+---------+------------+
|      21 |  downtown  |
+---------+------------+
|      22 |  abroad    |
+---------+------------+

La taxonomie pricetag a les termes suivants:

+---------+------------+
| term_id |    slug    |
+---------+------------+
|      31 |  expensive |
+---------+------------+
|      32 |  cheap     |
+---------+------------+

Et finalement nous avons wp_term_relationships qui relie les cours aux termes de taxonomie de cette façon:

+-----------+------------+---------+
| object_id | post_title | term_id |
+-----------+------------+---------+
|        12 | Cooking    |      21 | (downtown)
+-----------+------------+---------+
|        12 | Cooking    |      32 | (cheap)
+-----------+------------+---------+
|        13 | Surfing    |      22 |    (abroad)
+-----------+------------+---------+
|        13 | Surfing    |      31 |    (expensive)
+-----------+------------+---------+
|        14 | Building   |      21 |       (downtown)
+-----------+------------+---------+
|        14 | Building   |      31 |       (expensive)
+-----------+------------+---------+
|        15 | Hacking    |      22 |          (abroad)
+-----------+------------+---------+
|        15 | Hacking    |      32 |          (cheap)
+-----------+------------+---------+

(Remarque: je sais que ce n'est pas la structure réelle de la table wp_term_relationships, mais je voulais la simplifier un peu pour les besoins de cette question.)

Supposons que l’administrateur ait choisi place comme première taxonomie à utiliser comme critère de tri et qu’il ait choisi d’afficher les cours downtown en premier (l’écran d’administration du plug-in est déjà terminé et il fournit déjà à l’administrateur l’interface utilisateur nécessaire pour effectuer de tels choix. ).

Dites ensuite que l’administrateur a choisi pricetag comme seconde taxonomie à utiliser comme critère de tri et qu’il souhaite que expensive cours apparaisse en premier. Notez qu’il s’agit d’un second critère, il a une priorité de tri moins importante que le premier. Par conséquent, l’administrateur souhaite commencer par les cours du centre-ville, puis par les cours coûteux du groupe des cours du centre-ville.

Maintenant, un utilisateur frontal recherche tous les cours sur le site Web et il devrait voir ces résultats dans cet ordre exact:

  1. Cours de construction (parce que c'est en ville et cher)
  2. Cours de cuisine (parce que c'est en ville et pas cher)
  3. Cours de surf (parce que c'est à l'étranger et cher)
  4. Cours de piratage (parce que c'est à l'étranger et pas cher)

Mon problème est d'écrire les clauses JOIN et ORDER BY correctes dans l'objet de requête Wordpress. J'ai déjà accroché posts_clauses et voici la requête SQL que je génère:

SELECT SQL_CALC_FOUND_ROWS  wp_posts.* FROM wp_posts 
  LEFT JOIN (wp_term_relationships, wp_term_taxonomy, wp_terms) 
    ON (wp_term_relationships.object_id = wp_posts.ID 
        AND wp_term_taxonomy.term_taxonomy_id = wp_term_relationships.term_taxonomy_id
        AND wp_terms.term_id = wp_term_taxonomy.term_id) 
  WHERE 1=1 AND (((wp_posts.post_title LIKE '%%') OR (wp_posts.post_excerpt LIKE '%%') OR (wp_posts.post_content LIKE '%%')))
            AND wp_posts.post_type IN 
                ('post', 'page', 'attachment', 'course') 
            AND (wp_posts.post_status = 'publish' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private') 
            AND wp_posts.post_type='course' 
  ORDER BY (wp_terms.slug LIKE 'downtown' AND wp_term_taxonomy.taxonomy LIKE 'place') DESC,
           (wp_terms.slug LIKE 'abroad' AND wp_term_taxonomy.taxonomy LIKE 'place') DESC,
           (wp_terms.slug LIKE 'expensive' AND wp_term_taxonomy.taxonomy LIKE 'pricetag') DESC,
           (wp_terms.slug LIKE 'cheap' AND wp_term_taxonomy.taxonomy LIKE 'pricetag') DESC,
           wp_posts.post_title LIKE '%%' DESC, wp_posts.post_date DESC
 LIMIT 0, 300

Cependant, cette requête a au moins deux problèmes:

  1. il retourne deux fois les résultats et je ne comprends pas pourquoi, car LEFT JOIN ne devrait pas produire de produit cartésien entre les tables spécifiées
  2. l'ordre de tri qui en résulte n'est pas clair pour moi (il semble que ce soit juste post_date DESC), mais il est clair que ce n'est PAS ce à quoi je m'attendais.

J'ai essayé de simplifier la requête en supprimant les clauses générées par Wordpress:

SELECT wp_posts.* FROM wp_posts 
  LEFT JOIN (wp_term_relationships, wp_term_taxonomy, wp_terms) 
     ON (wp_term_relationships.object_id = wp_posts.ID 
        AND wp_term_taxonomy.term_taxonomy_id = wp_term_relationships.term_taxonomy_id
        AND wp_terms.term_id = wp_term_taxonomy.term_id) 
  WHERE 1=1 AND wp_posts.post_type='course' 
  ORDER BY (wp_terms.slug LIKE 'downtown' AND wp_term_taxonomy.taxonomy LIKE 'place') DESC,
       (wp_terms.slug LIKE 'abroad' AND wp_term_taxonomy.taxonomy LIKE 'place') DESC,
       (wp_terms.slug LIKE 'expensive' AND wp_term_taxonomy.taxonomy LIKE 'pricetag') DESC,
       (wp_terms.slug LIKE 'cheap' AND wp_term_taxonomy.taxonomy LIKE 'pricetag') DESC

Celui-ci a exactement les mêmes problèmes, mais il est un peu plus facile à comprendre et renvoie les données comme si aucun ORDER BY n'y était présent.

Pouvez-vous m'aider s'il vous plaît?

6
Lucio Crusca

Malheureusement, bien que WP_Query prenne en charge le 'tax_query' arg, il ne prend pas en charge les commandes basées sur les conditions de publication. Vous devrez donc modifier la requête SQL, comme vous le faites maintenant. Cependant, vous construisez la clause ORDER BY de manière incorrecte, raison pour laquelle elle commande par post_date. Ce que vous devez faire, c'est utiliser une instruction CASE, comme ceci:

CASE 
    WHEN (wp_terms.slug LIKE 'downtown' AND wp_term_taxonomy.taxonomy LIKE 'place') THEN 1
    WHEN (wp_terms.slug LIKE 'abroad' AND wp_term_taxonomy.taxonomy LIKE 'place') THEN 0
END

Cela ordonnera en fonction de la priorité que vous attribuez à chacun des termes (1, 0, etc., la priorité étant la priorité la plus élevée, sauf si vous utilisez ASC au lieu de DESC pour la commande).

Parce que vous voulez commander ces deux taxonomies indépendamment, vous devez avoir deux jointures et deux déclarations de cas. (Voir ci-dessous par exemple.)

Vous devez également créer un GROUP BY sur l'ID de l'article pour éviter les doublons:

    $clauses['groupby'] = 'wptests_posts.ID';

Ainsi, votre requête finale ressemblera à quelque chose comme ceci (formaté pour en faciliter la lecture):

   SELECT SQL_CALC_FOUND_ROWS  wptests_posts.ID FROM wptests_posts 
            LEFT JOIN (
                wptests_term_relationships tr_place,
                wptests_term_taxonomy tt_place,
                wptests_terms t_place
            ) ON (
                tr_place.object_id = wptests_posts.ID 
                AND tt_place.term_taxonomy_id = tr_place.term_taxonomy_id
                AND tt_place.taxonomy = 'place'
                AND t_place.term_id = tt_place.term_id
            ) 

            LEFT JOIN (
                wptests_term_relationships tr_pricetag,
                wptests_term_taxonomy tt_pricetag,
                wptests_terms t_pricetag
            ) ON (
                tr_pricetag.object_id = wptests_posts.ID 
                AND tt_pricetag.term_taxonomy_id = tr_pricetag.term_taxonomy_id
                AND tt_pricetag.taxonomy = 'pricetag'
                AND t_pricetag.term_id = tt_pricetag.term_id
            ) 
   WHERE 1=1  AND wptests_posts.post_type = 'course' AND (wptests_posts.post_status = 'publish')
   GROUP BY wptests_posts.ID
   ORDER BY 
        (CASE 
            WHEN (t_place.slug LIKE 'downtown') THEN 1
            WHEN (t_place.slug LIKE 'abroad') THEN 0
        END) DESC, (CASE
            WHEN (t_pricetag.slug LIKE 'expensive') THEN 1
            WHEN (t_pricetag.slug LIKE 'cheap') THEN 0
        END) DESC,
        wptests_posts.post_date DESC
   LIMIT 0, 10

Voici un exemple de test PHPUnit qui montre que cela fonctionne, y compris un exemple de code permettant de générer les jointures et orderbys (utilisé pour générer la requête ci-dessus):

class My_Test extends WP_UnitTestCase {

    public function test() {

        // Create the post type.
        register_post_type( 'course' );

        // Create the posts.
        $cooking_post_id = $this->factory->post->create(
            array( 'post_title' => 'Cooking', 'post_type' => 'course' )
        );
        $surfing_post_id = $this->factory->post->create(
            array( 'post_title' => 'Surfing', 'post_type' => 'course' )
        );
        $building_post_id = $this->factory->post->create(
            array( 'post_title' => 'Building', 'post_type' => 'course' )
        );
        $hacking_post_id = $this->factory->post->create(
            array( 'post_title' => 'Hacking', 'post_type' => 'course' )
        );

        // Create the taxonomies.
        register_taxonomy( 'place', 'course' );
        register_taxonomy( 'pricetag', 'course' );

        // Create the terms.
        $downtown_term_id = wp_create_term( 'downtown', 'place' );
        $abroad_term_id = wp_create_term( 'abroad', 'place' );

        $expensive_term_id = wp_create_term( 'expensive', 'pricetag' );
        $cheap_term_id = wp_create_term( 'cheap', 'pricetag' );

        // Give the terms to the correct posts.
        wp_add_object_terms( $cooking_post_id, $downtown_term_id, 'place' );
        wp_add_object_terms( $cooking_post_id, $cheap_term_id, 'pricetag' );

        wp_add_object_terms( $surfing_post_id, $abroad_term_id, 'place' );
        wp_add_object_terms( $surfing_post_id, $expensive_term_id, 'pricetag' );

        wp_add_object_terms( $building_post_id, $downtown_term_id, 'place' );
        wp_add_object_terms( $building_post_id, $expensive_term_id, 'pricetag' );

        wp_add_object_terms( $hacking_post_id, $abroad_term_id, 'place' );
        wp_add_object_terms( $hacking_post_id, $cheap_term_id, 'pricetag' );

        $query = new WP_Query(
            array(
                'fields'    => 'ids',
                'post_type' => 'course',
            )
        );

        add_filter( 'posts_clauses', array( $this, 'filter_post_clauses' ) );

        $results = $query->get_posts();

        $this->assertSame(
            array(
                $building_post_id,
                $cooking_post_id,
                $surfing_post_id,
                $hacking_post_id,
            )
            , $results
        );
    }

    public function filter_post_clauses( $clauses ) {

        global $wpdb;

        $clauses['orderby'] = "
            (CASE 
                WHEN (t_place.slug LIKE 'downtown') THEN 1
                WHEN (t_place.slug LIKE 'abroad') THEN 0
            END) DESC, (CASE
                WHEN (t_pricetag.slug LIKE 'expensive') THEN 1
                WHEN (t_pricetag.slug LIKE 'cheap') THEN 0
            END) DESC,
            " . $clauses['orderby'];

        foreach ( array( 'place', 'pricetag' ) as $taxonomy ) {

            // Instead of interpolating directly here, you should use $wpdb->prepare() for $taxonomy.
            $clauses['join'] .= "
                LEFT JOIN (
                    $wpdb->term_relationships tr_$taxonomy,
                    $wpdb->term_taxonomy tt_$taxonomy,
                    $wpdb->terms t_$taxonomy
                ) ON (
                    tr_$taxonomy.object_id = $wpdb->posts.ID 
                    AND tt_$taxonomy.term_taxonomy_id = tr_$taxonomy.term_taxonomy_id
                    AND tt_$taxonomy.taxonomy = '$taxonomy'
                    AND t_$taxonomy.term_id = tt_$taxonomy.term_id
                ) 
                ";
        }

        $clauses['groupby'] = 'wptests_posts.ID';

        return $clauses;
    }
}
5
J.D.

Vous pouvez utiliser tax_query pour trier avec la taxonomie i.e.

  $args = array(
            posts_per_page => -1,
            post_type => 'your post type',
            tax_query' => array(
            'relation' => 'OR',
             array(
                  'taxonomy' => 'your custom taxonomy',
                  'field'    => 'slug',
                  'terms'    => $_REQUEST[your post requested],
                 ),
              array(
                    'taxonomy' => 'your 2nd custom taxonomy',
                    'field'    => 'slug',
                    'terms'    => $_REQUEST[your post requested],
                ),
            ),
        ):

       $query = new WP_Query( $args );
0
Abhishek Pandey