web-dev-qa-db-fra.com

Commande de posts ayant plusieurs champs post-meta date

Bonjour les développeurs WordPress. Au fil des ans, j'ai trouvé des réponses à de nombreuses questions de WordPress. Je tiens à vous remercier pour votre aide. Aujourd'hui, j'ai moi-même une question et sollicite les conseils d'experts.

Je construis un site pour une salle de cinéma. J'ai un type de message personnalisé movies . J'utilise Types plugin pour stocker les dates et heures de chaque film sous la forme d'un champ de date/heure qui se répète. Il stocke la date et l'heure sous forme d'horodatage Unix dans une table postmeta avec la méta_key "wpcf-showtime". Chaque film a de nombreuses heures. Il existe donc un certain nombre de paires meta_key/meta_value ayant la même clé, par exemple.

post_id: 70, meta_key: wpcf-showtime, meta_value 1417548600
post_id: 70, meta_key: wpcf-showtime, meta_value 1417813200
post_id: 70, meta_key: wpcf-showtime, meta_value 1417896000

Pour la première page du site, je souhaite montrer tous les films en cours jusqu’au mercredi de la semaine prochaine, c’est-à-dire les films dont les horaires sont dorénavant définis, à l’exclusion des films antérieurs ou marqués comme futurs (taxonomie personnalisée).

Mon problème est de commander les films; Je veux leur montrer l'ordre du spectacle afin que le film ayant l'heure du spectacle la plus proche du moment présent soit la première.

C'est la requête que j'exécute:

$startdate = time();
$enddate = new DateTime('thursday next week');

$ongoing_movies = new WP_Query( array( 
    'post_type' => 'movies',
    'meta_key'  => 'wpcf-showtime',
    'orderby'   => 'meta_value_num', 
    'order'     => 'ASC',
    'tax_query' => array(
        array(
            'taxonomy' => 'genre',
            'field'    => 'slug',
            'terms'    => 'future', 
            'operator' => 'NOT IN'
        )
    ),
    'meta_query' => array(
        array(
            'key'      => 'wpcf-showtime',
            'value'    => array ( $startdate, $enddate->format('U')),
            'compare'  => 'BETWEEN', 
        ),
    )
));

Cela renvoie les films corrects, classés par heure de diffusion. Mais l'heure qu'il utilise pour le tri est la première occurrence de valeur pour la méta_key "wpcf-showtime" stockée pour ce film particulier. C'est à dire. le premier film affiché peut ne pas être celui qui a le prochain spectacle, mais celui qui a le plus tôt showtime stocké dans db.

J'apprécierais toute aide pour trouver une approche; action pre_get_posts, filtre posts_orderby?

4
Mikko Siikaniemi

1) En utilisant uniquement le filtre posts_orderby:

Il semble que vous souhaitiez commander les movies par le minimum des __ métadonnées wpcf-showtime.

Vous pouvez modifier la partie ORDER BY du code SQL généré par le WP_Query à l'aide du filtre posts_orderby.

Voici un exemple:

add_filter( 'posts_orderby', 'wpse_posts_orderby' );
$ongoing_movies = new WP_Query( $args );

où le rappel de filtre est défini comme:

/**
 *  Use MIN() on the meta value part ordering.
 */
function wpse_posts_orderby( $orderby )
{      
    global $wpdb;

    // Only run this callback once:
    remove_filter( current_filter(), __FUNCTION__ );

    // Replacement part:  
    $find    = "{$wpdb->postmeta}.meta_value+0 ";
    $replace = "mt1.meta_value+0 ";

    // Make sure we add MIN() to the correct part:    
    if( $find == str_ireplace( array( 'ASC', 'DESC' ), '', $orderby ) )
    {
        // Preserve the order direction:
        $orderby = str_ireplace(
            $find,
            "MIN( $replace) ",
            $orderby
        );
    }
    return $orderby;
}

Ici, nous utilisons mt1.meta_value, si la restriction de méta-valeur est le premier tableau d'arguments du meta_query.

Cela va changer:

ORDER BY wp_postmeta.meta_value+0 {ASC|DESC}

à

ORDER BY MIN( mt1.meta_value+0 ) {ASC|DESC}

2) Introduction d’un paramètre d’ordre personnalisé dans WP_Query

Vous pouvez essayer d'utiliser cette configuration:

$startdate = time();
$enddate = new DateTime('thursday next week');

$ongoing_movies = new WP_Query( array( 
    'post_type'    => 'movies',
    'meta_key'     => 'wpcf-showtime',
    'wpse_orderby' => 'meta_value_num_min',      // <-- New parameter to order by MIN!
    'orderby'      => 'meta_value_num', 
    'order'        => 'ASC',
    'tax_query'    => array(
        array(
            'taxonomy' => 'genre',
            'field'    => 'slug',
            'terms'    => 'future', 
            'operator' => 'NOT IN'
        )
    ),
    'meta_query' => array(
        array(
            'key'      => 'wpcf-showtime',
            'value'    => array ( $startdate, $enddate->format('U')),
            'compare'  => 'BETWEEN', 
            'type'     => 'UNSIGNED'     // <-- Let's override the default 'CHAR'
        ),
    )
));

où nous utilisons le plugin suivant pour supporter le paramètre wpse_orderby:

<?php
/**
 * Plugin Name: Modify the WP_Query ordering to support MAX and MIN
 * Description: Possible values of 'wpse_orderby' are  'meta_value_num_{min,max}'.
 * Plugin URI:  http://wordpress.stackexchange.com/a/173496/26350
 * Author:      Birgir Erlendsson (birgire)
 * Version:     0.0.2
 */ 

add_action( 'init', function(){
    if( class_exists( 'WPSE_Modify_Ordering' ) )
    {
        $o = new WPSE_Modify_Ordering;
        $o->init();
    }
});

class WPSE_Modify_Ordering
{
    private $type    = '';
    private $order   = '';
    private $orderby = '';

    public function init()
    {
        add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
    }

    public function pre_get_posts( WP_Query $q )
    {
        if( 
            in_array( 
                $q->get( 'wpse_orderby' ), 
                array( 'meta_value_num_min', 'meta_value_num_max' ) 
            ) 
        )
        {
            $this->type = strtoupper( substr( $q->get( 'wpse_orderby' ), -3 ) );
            $this->order = ( '' !== $q->get( 'order' ) ) ? $q->get( 'order' ) : 'ASC';
            add_filter( 'posts_orderby', array( $this, 'posts_orderby' ) );
        }
    }

    public function posts_orderby( $orderby )
    {
        remove_filter( current_filter(), array( $this, __FUNCTION__ ) );
        return " {$this->type}( mt1.meta_value+0 ) " . $this->order;
    }

} // end class

Le paramètre wpse_orderby remplace le paramètre natif orderby. Il supporte les valeurs meta_value_num_min et meta_value_num_max.

Ici, nous faisons la commande sur le mt1.meta_value, puisque la restriction de méta-valeur est le premier tableau d'arguments du meta_query.

La raison pour laquelle nous conservons le paramètre orderby est de l’utiliser comme un fallback si le plugin ci-dessus est désactivé .

J'espère que vous pourrez modifier cela davantage selon vos besoins.

4
birgire

C'est une question trompeusement complexe. il s'agit spécifiquement de la commande par contextuelle date dans la plage d'une requête, lorsqu'un objet peut avoir plusieurs dates; traditionnellement, le tri et le classement utilisent la valeur la plus basse du tableau de dates, ce qui entraîne souvent un classement incorrect lors de l'interrogation d'une plage de dates spécifique.

Pour référence, voici la viande épurée du code que j'ai utilisé dans ma propre approche:

// Wrap query in filters to modify behaviour
// ==============  
add_filter('posts_join_paged','doty_custom_posts_join');
add_filter('posts_orderby','doty_custom_posts_orderby');
$new_query = new WP_Query( $args );
remove_filter('posts_orderby','doty_custom_posts_orderby');
remove_filter('posts_join_paged','doty_custom_posts_join');

// Hooks into the 'WHERE' component of the SQL query
// ==============
function doty_custom_posts_join($where) {
  global $wpdb,$query_dates;
  $start_date = (int)$query_dates[0]-86400;
  $where .= "LEFT JOIN (
        SELECT post_id,($start_date - MIN(meta_value)) as orderdate
        FROM $wpdb->postmeta
        WHERE meta_key = 'dates'
        AND meta_value > $start_date 
        GROUP BY post_id) AS date_orders
        ON $wpdb->posts.ID = date_orders.post_id";
  return $where;
}

// Hooks into the 'ORDER BY' component of the SQL query
// ==============
function doty_custom_posts_orderby($orderby) {
  $orderby = " date_orders.orderdate DESC";
  return $orderby;
}

Il utilise posts_join_pages et posts_orderby pour modifier la façon dont WP génère la requête SQL résultant de la fonction WP_Query normale. Plus précisément, il:

  • Extrait la date de début de la plage d'une variable globale
  • Construit une nouvelle jointure LEFT pour créer une variable orderdate, sélectionnée à partir de la date la plus proche du tableau, et vérifie ensuite si cela se trouve dans le futur.
  • Utilise la variable orderdate la clé de tri

Je suis conscient d'avoir passé environ six heures à pirater cela ensemble, et que ce n'est pas le plus propre, mais j'espère que cela aura une certaine valeur quelque part.

1
Jono Alderson