web-dev-qa-db-fra.com

Tri des messages en fonction du terme auquel ils appartiennent

J'ai une taxonomie appelée portfolio_cat avec ses catégories. Alors maintenant, je dois créer un curseur avec ces catégories comme titre et leurs articles de publication. Comment je peux faire ça? De quelle boucle ai-je besoin pour pouvoir classer les sliders dans leurs catégories?

Je ne sais pas comment personnaliser cette boucle pour s'intégrer

<?php
   $query = new WP_Query( array('post_type' => 'portfolio', 'posts_per_page' => 7, 'order' => ASC ) );
while ( $query->have_posts() ) : $query->the_post(); 
?>
1
Anahit DEV

Je ne vais pas discuter de la mise en place d'un curseur, c'est trop large. Il existe plusieurs tutoriels sur la mise en oeuvre de simples curseurs.

Le vrai problème ici est la requête elle-même. Trier une requête en fonction des termes auxquels une publication appartient est une opération assez lourde, et si elle n'est pas effectuée correctement ( pas d'infraction, comme vous l'avez fait dans votre réponse ), elle peut être vraiment très chère. La méthode trop utilisée consistant à obtenir tous les termes, puis à les parcourir en boucle et à exécuter une requête pour chaque terme peut vous empêcher de participer à une opération effectuant des appels à 100 db, et ce sur un site de petite taille contenant seulement quelques termes et publications. Les sites très volumineux peuvent emballer la viande sur les appels à la base de données, ce qui permet de constater que votre indice de référencement est bloqué au point mort et que le temps de chargement d'une page est dans le rouge.

J'ai fait quelques articles sur ce sujet spécifique , je voulais donc le marquer comme un doublon, mais j'estime qu'il faut redorer le blason une approche différente.

Ce que j'ai fait dans le passé est de tout faire ( interroger tous les articles, puis de trier le résultat et de les afficher ) en une seule opération. Ce résultat de requête complet a ensuite été stocké dans un transitoire. Le problème ici est que nous stockons une quantité énorme de données sérialisées dans le transitoire, ce qui, encore une fois, n’est pas vraiment ce que les transitoires devraient faire.

Ce que je vais faire ici est de construire un script de tri dynamique qui fera tout le travail difficile et ne stockera que les identifiants de publication dans un transitoire (. Cela évitera de stocker de grandes quantités de données sérialisées pouvant être lent ). Nous utiliserons ensuite les identifiants de poste triés du transitoire pour obtenir les messages dont nous aurons besoin. Comme les publications sont enregistrées dans une mémoire cache, nous n’avons pas de mal à obtenir les publications souhaitées. Nous interrogeons et renvoyons les publications de la mémoire cache si elles sont déjà présentes.

QUELQUES NOTES IMPORTANTES:

  • Bien que j'ai testé le code, il n'a pas été testé complètement. Sur mon installation, il n'y a pas de bugs évidents à ce stade. Vous devez cependant tester cela complètement avec le débogage activé sur une installation de test locale avant de passer cela en production.

  • Pour ce qu’il vaut, le code nécessite au moins PHP 5.4, mais vous avez déjà au moins PHP 5.5, ou mieux PHP7 ;-)

  • Il existe quelques paramètres orderby qui ne sont pas exceptés par le code. Vous pouvez l'étendre si nécessaire. Vérifiez le post recherche que j'ai lié à. J'ai fait beaucoup de choses différentes avec usort()

  • Le code utilise le premier terme renvoyé par get_the_terms. Par conséquent, les publications comportant plusieurs termes peuvent être triées de manière incorrecte en raison de l'utilisation du premier terme.

LES SCRIPTS DE TRI:

Nous allons obtenir tous les articles en fonction de nos besoins, mais nous ne renverrons que les propriétés d’objet de publication dont nous avons besoin pour économiser les ressources. Nous utiliserons ensuite usort() pour trier les messages en fonction des termes. Au lieu de créer quelque chose de statique que vous devrez modifier si nécessaire, nous allons rendre cette dynamique afin que vous puissiez la réutiliser autant que vous le souhaitez en ne modifiant que les valeurs des paramètres passés.

/**
 * Function to return an ordered list of post id's according to the terms they belong to.
 *
 * This function accepts four parameters
 * @param (string) $taxonomy The taxonomy to get posts and terms from
 * @param (array) $args An array of arguments valid in 'WP_Query'
 * @param (array) $term_args An array of arguments valid with 'get_terms'
 * @param (array) $sort An array of parameters used to sort posts in 'usort()'
 * @return $ids
 */
function get_sorted_query_results( 
    $taxonomy = 'category', 
    $args = [], 
    $term_args = [], 
    $sort = [] 
) {
    /** 
     * To avoid unexpected ordering, we will avoid any odering that is not done by a WP_Post
     * property. If we incounter any ordering outside that, we would simply return false and
     * stop execution
     */
    $avoid_this_ordering = [
        'none', 
        'type', 
        'Rand', 
        'comment_count', 
        'meta_value', 
        'meta_value_num', 
        'post__in'
    ];
    if ( isset( $args['orderby'] ) ) {
        if ( in_array( $args['orderby'], $avoid_this_ordering ) )
            return null;
    }

    // Validate and sanitize the taxonomy name
    if (    'category' !== $taxonomy
         && 'post_tag' !== $taxonomy
    ) {
        $taxonomy = filter_var( $taxonomy, FILTER_SANITIZE_STRING );
        if ( !taxonomy_exists( $taxonomy ) )
           return null;
    }

    // Now that we have run important test, set the transient

    // Set a transient to store the post ID's. Excellent for performance
    $transient_name = 'pobt_' . md5( $taxonomy . json_encode( array_merge( $args, $term_args, $sort ) ) );
    if ( false === ( $ids = get_transient ( $transient_name ) ) ) {

        // Set up a variable to hold an array of post id and id that should not be duplicated       
        // Set our query defaults
        $defaults = [
            'posts_per_page' => -1,
            'order'          => 'DESC',
            'orderby'        => 'date'
        ];
        /**
         * If tax_query isn't explicitely set, we will set a default tax_query to ensure we only get posts
         * from the specific taxonomy. Avoid adding and using category and tag parameters
         */
        if ( !isset( $args['tax_query'] ) ) {   
            // Add 'fields'=>'ids' to $term_args to get only term ids
            $term_args['fields'] = 'ids';
            $terms = get_terms( $taxonomy, $term_args );
            if ( $terms ) { // No need to check for WP_Error because we already now the taxonomy exist
                $defaults['tax_query'] = [
                    [
                        'taxonomy' => $taxonomy,
                        'terms'    => $terms
                    ]
                ];
            } else {
                // To avoid unexpected output, return null
                return null;
            }
        }
        // Merge the defaults wih the incoming $args
        $query_args = $defaults;
        if ( $args )
            $query_args = wp_parse_args( $args, $defaults );

        // Make sure that 'fields' is always set to 'all' and cannot be overridden
        $query_args['fields'] = 'all';
        // Always allow filters to modify get_posts()
        $query_args['suppress_filters'] = false;

        /**
         * Create two separate arrays:
         * - one to hold numeric values like dates and ID's 
         * one with lettering strings like names and slugs. 
         * 
         * This will ensure very reliable sorting and also we
         * will use this to get only specific post fields
         */
        $orderby_num_array = [
            'date'       => 'post_date', 
            'modified'   => 'post_modified', 
            'ID'         => 'ID', 
            'menu_order' => 'menu_order',
            'parent'     => 'post_parent',
            'author'     => 'post_author'
        ];
        $orderby_letter_array = [
            'title'      => 'post_title',
            'name'       => 'post_name',
        ];
        // Merge the two arrays to use the combine array in our filter_has_var
        $orderby_comb_array = array_merge( $orderby_num_array, $orderby_letter_array );

        //Now we will filter get_posts to just return the post fields we need
        add_filter( 'posts_fields', function ( $fields ) 
            use ( 
                $query_args, 
                $orderby_comb_array 
            )
        {
            global $wpdb;

            remove_filter( current_filter(), __FUNCTION__ );

            // If $query_args['orderby'] is ID, just get ids
            if ( 'ID' === $query_args['orderby'] ) { 
                $fields = "$wpdb->posts.ID";
            } else { 
                $extra_field = $orderby_comb_array[$query_args['orderby']];
                $fields = "$wpdb->posts.ID, $wpdb->posts.$extra_field";
            }

            return $fields;
        });

        // Now we can query our desired posts
        $q = get_posts( $query_args );

        if ( !$q ) 
            return null;

        /**
         * We must now set defaults to sort by. 'order' will the order in which terms should be sorted
         * 'orderby' by defualt will be the value used to sort posts by in the query. This will be used
         * when a post has the same term as another post, then posts should be sorted by this value
         */
        $sort_defaults = [
            'order'   => 'ASC', // This will sort terms from a-z
            'orderby' => $query_args['orderby'] // Use the default query order
        ];

        // Merge the defaults and incoming args
        $sorting_args = $sort_defaults;
        if ( $sort )
            $sorting_args = wp_parse_args( $sort, $sort_defaults );

        /**
         * Now we can loop through the posts and sort them with usort()
         *
         * There is a bug in usort causing the following error:
         * usort(): Array was modified by the user comparison function
         * @see https://bugs.php.net/bug.php?id=50688
         * The only workaround is to suppress the error reporting
         * by using the @ sign before usort in versions before PHP7
         *
         * This bug have been fixed in PHP 7, so you can remove @ in you're on PHP 7
         */
        @usort( $q, function ( $a, $b ) 
            use ( 
                $taxonomy, 
                $sorting_args, 
                $orderby_num_array, 
                $orderby_letter_array 
            )
        {   

            /**
             * Get the respective terms from the posts. We will use the first
             * term's name. We can safely dereference the array as we already
             * made sure that we get posts that has terms from the selected taxonomy
             */
            $post_terms_a = get_the_terms( $a, $taxonomy )[0]->name;
            $post_terms_b = get_the_terms( $b, $taxonomy )[0]->name;

            // First sort by terms
            if ( $post_terms_a !== $post_terms_b ) {
                if ( 'ASC' === $sorting_args['order'] ) {
                    return strcasecmp( $post_terms_a, $post_terms_b );
                } else { 
                    return strcasecmp( $post_terms_b, $post_terms_a );
                }
            }

            /**
             * If we reached this point, terms are the same, we need to sort the posts by 
             * $query_args['orderby']
             */
            if ( in_array( $sorting_args['orderby'], $orderby_num_array ) ) {
                if ( 'ASC' === $sorting_args['order'] ) {
                    return $a->$orderby_num_array[$sorting_args['orderby']] < $b->$orderby_num_array[$sorting_args['orderby']];
                } else { 
                    return $a->$orderby_num_array[$sorting_args['orderby']] > $b->$orderby_num_array[$sorting_args['orderby']];
                }
            } elseif ( in_array( $sorting_args['orderby'], $orderby_letter_array ) ) { 
                if ( 'ASC' === $sorting_args['order'] ) {
                    return strcasecmp( 
                        $a->$orderby_num_array[$sorting_args['orderby']], 
                        $b->$orderby_num_array[$sorting_args['orderby']] 
                    );
                } else { 
                    return strcasecmp( 
                        $b->$orderby_num_array[$sorting_args['orderby']], 
                        $a->$orderby_num_array[$sorting_args['orderby']] 
                    );
                }
            }
        });

        // Get all the post id's from the posts
        $ids = wp_list_pluck( $q, 'ID' );

        set_transient( $transient_name, $ids, 30*DAY_IN_SECONDS );  
    }
    return $ids;
}

J'ai commenté le code pour le rendre facile à suivre. Quelques notes sur les paramètres cependant

  • $taxonomy. Ceci est la taxonomie pour obtenir des termes et des messages de

  • $args. Un tableau de tous les arguments valides acceptables dans WP_Query . Il y a quelques valeurs par défaut définies,

    $defaults = [
        'posts_per_page' => -1,
        'order'          => 'DESC',
        'orderby'        => 'date'
    ];
    

    vous pouvez donc utiliser ce paramètre pour définir des extras tels que 'post_type' et un tax_query si vous avez besoin de messages plus spécifiques

  • $term_args. Un tableau de paramètres valide avec get_terms. Ceci est utilisé pour obtenir plus de termes spécifiques à partir de $ taxonomy.

  • $sort Un tableau d'arguments utilisé pour déterminer l'ordre de tri dans usort(). Les arguments valides sont order et orderby

N'oubliez pas que si vous n'avez pas besoin de définir un paramètre et si vous devez en définir un adjacent, transmettez un tableau vide, à l'exception de $taxonomy qui doit être défini sur une taxonomie valide.

EXEMPLE

$taxonomy = 'my_taxonomy';
$args = [
    'post_type' => 'my_post_type'
];
$term_args = [];
$sort = [
    'order' => 'DESC'
];
$q = get_sorted_query_results( $taxonomy, $args, $term_args, $sort );

Ceci, sur mon installation, exécute 3 requêtes de base de données en +/- 0.002 secondes.

Nous avons enregistré tous les identifiants de poste triés pertinents dans un transitoire qui expire tous les 30 jours. Nous devons également le vider lorsque nous publions un nouveau message, mettons à jour, supprimons ou supprimons sa publication. De manière très générique, vous pouvez utiliser le hook transition_post_status pour purger le transitoire dans les cas ci-dessus.

add_action( 'transition_post_status', function ()
{
    global $wpdb;
    $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient%_pobt_%')" );
    $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient_timeout%_pobt_%')" );
});

Il existe de nombreux exemples sur le site destinés à des utilisations spécifiques afin de cibler des modifications de statut de publication spécifiques. Vous avez également l'objet de publication disponible afin que vous puissiez cibler des types de publication, des auteurs, etc.

Maintenant que vous avez un tableau d'identifiants triés, vous pouvez maintenant interroger les publications complètes. Ici, nous utiliserions le paramètre post__in et la valeur orderby pour renvoyer les publications désirées déjà triées.

De l'exemple ci-dessus, nous ferions

if ( $q ) { // Very important check to avoid undesired results if $q is empty
    $query_args = [
        'post__in' => $q,
        'order'    => 'ASC',
        'orderby'  => 'post__in',
        // Any other parameters
    ];
    $query = new WP_Query( $query_args );
    // Run your loop, see code below
}

Il ne nous reste plus qu'à afficher les noms de termes sous forme d'en-têtes. Ceci est assez simple, comparez le terme du post en cours avec le terme du post précédent, s'ils ne correspondent pas, affichez le nom du terme, sinon, ne rien afficher

if ( $query->have_posts() ) {
    // Define variable to hold previous post term name
    $term_string = '';
    while ( $query->have_posts() ) {
    $query->the_post();
        // Get the post terms. Use the first term's name
        $term_name = get_the_terms( get_the_ID(), 'TAXONOMY_NAME_HERE' )[0]->name;
        // Display the taxonomy name if previous and current post term name don't match
        if ( $term_string != $term_name )
            echo '<h2>' . $term_name . '</h2>'; // Add styling and tags to suite your needs

        // Update the $term_string variable
        $term_string = $term_name;

        // REST OF YOUR LOOP

    }
    wp_reset_postdata();
}

Voici la requête complète telle que je l'ai exécutée sur mon installation

$taxonomy = 'category';
$args = [
    'post_type' => 'post'
];
$term_args = [];
$sort = [
    'order' => 'DESC'
];
$q = get_sorted_query_results( $taxonomy, $args, $term_args, $sort );

if ( $q ) { // Very important check to avoid undesired results if $q is empty
    $query_args = [
        'posts_per_page' => count( $q ),
        'post__in'       => $q,
        'order'          => 'ASC',
        'orderby'        => 'post__in',
        // Any other parameters
    ];
    $query = new WP_Query( $query_args );

    if ( $query->have_posts() ) {
        // Define variable to hold previous post term name
        $term_string = '';
        while ( $query->have_posts() ) {
        $query->the_post();
            // Get the post terms. Use the first term's name
            $term_name = get_the_terms( get_the_ID(), 'category' )[0]->name;
            // Display the taxonomy name if previous and current post term name don't match
            if ( $term_string != $term_name )
                echo '<h2>' . $term_name . '</h2>'; // Add styling and tags to suite your needs

            // Update the $term_string variable
            $term_string = $term_name;

            // REST OF YOUR LOOP
            the_title();

        }
        wp_reset_postdata();
    }
}

En test avec 21 messages et 6 catégories, l’opération dans son ensemble m’a coûté 7 requêtes en 0,1 seconde

Avec la méthode facile trop utilisée

$terms= get_terms( 'category' );
foreach ( $terms as $term ) {
    echo $term->name;
    $args = [
        'tax_query' => [
            [
                'taxonomy' => 'category',
                'terms' => $term->term_id,
            ]
        ],
    ];
    $query = new WP_Query( $args );
    while ( $query->have_posts() ) {
        $query->the_post();

        the_title();
    }
    wp_reset_postdata();
}

J'ai eu 32 requêtes en 0,2 seconde. Cela fait 25 requêtes ( 5 fois plus ) de plus, et ce n’est que sur 6 catégories et 21 publications.

2
Pieter Goosen

Vous pouvez faire ceci:

$args = array(
    'posts_per_page' => '-1',
    'post_type' => 'portfolio'
    'tax_query' => array(
        array(
            'taxonomy' => 'portfolio_cat',
            'field' => 'name',
            'terms' => array('cat1', 'cat2', 'cat3'))
        )
    )
);
$wp_query = new WP_Query( $args );
3
Pmpr

Voici l'exemple de travail

<?php
$member_group_terms = get_terms( 'member_group' );
 ?>
Then, loop through each one, running a new query each time:

<?php
 foreach ( $member_group_terms as $member_group_term ) {
 $member_group_query = new WP_Query( array(
    'post_type' => 'member',
    'tax_query' => array(
        array(
            'taxonomy' => 'member_group',
            'field' => 'slug',
            'terms' => array( $member_group_term->slug ),
            'operator' => 'IN'
        )
    )
) );
?>
<h2><?php echo $member_group_term->name; ?></h2>
<ul>
<?php
if ( $member_group_query->have_posts() ) : while ( $member_group_query->have_posts() ) : $member_group_query->the_post(); ?>
    <li><?php echo the_title(); ?></li>
<?php endwhile; endif; ?>
</ul>
<?php
// Reset things, for good measure
$member_group_query = null;
wp_reset_postdata();
}
?>
1
Anahit DEV