I link Post successivo / precedente possono essere ordinati in base all'ordine di menu o tramite una meta chiave?


32

Ho una serie di post ordinati per valore meta_key. Potrebbero anche essere organizzati per ordine di menu, se necessario.

I collegamenti post successivo / precedente (generati da next_post_link, previous_post_linko posts_nav_linktutti navigano in base alla cronologia. Mentre comprendo questo comportamento predefinito, non capisco come modificarlo. Ho scoperto che è mappato su adiacente_post_link in link-template.php, ma allora inizia a sembrare abbastanza codificato. Si consiglia di riscriverlo da zero per sostituirlo, oppure esiste una soluzione migliore.


2
Ecco il plugin perfetto per il tuo problema: wordpress.org/support/topic/… wordpress.org/extend/plugins/… Grazie Ambrosite! :)
miguelb

1
Si noti che la seconda risposta sembra produrre il risultato corretto.
Thomas

Risposte:


29

Comprensione degli interni

L'ordine di "ordinamento" dei post adiacenti (successivo / precedente) non è in realtà un "ordinamento" di ordinamento. È una query separata su ogni richiesta / pagina, ma ordina la query in base al post_date- o al genitore post se si dispone di un post gerarchico come oggetto attualmente visualizzato.

Quando dai un'occhiata all'interno di next_post_link(), allora vedi che è fondamentalmente un wrapper API per adjacent_post_link(). La funzione successiva chiama get_adjacent_post()internamente con l' $previousargomento / flag impostato bool(true|false)per afferrare il post successivo o precedente.

Cosa filtrare?

Dopo aver scavato più a fondo, vedrai che il get_adjacent_post() link Sorgente ha alcuni filtri utili per il suo output (aka risultato della query): (Nome filtro / Argomenti)

  • "get_{$adjacent}_post_join"

    $join
    // Only if `$in_same_cat`
    // or: ! empty( $excluded_categories` 
    // and then: 
    // " INNER JOIN $wpdb->term_relationships AS tr 
    //     ON p.ID = tr.object_id 
    // INNER JOIN $wpdb->term_taxonomy tt 
    //     ON tr.term_taxonomy_id = tt.term_taxonomy_id"; 
    // and if $in_same_cat then it APPENDS: 
    // " AND tt.taxonomy = 'category' 
    // AND tt.term_id IN (" . implode(',', $cat_array) . ")";
    $in_same_cat
    $excluded_categories
  • "get_{$adjacent}_post_where"

    $wpdb->prepare(
          // $op = $previous ? '<' : '>'; | $current_post_date
           "WHERE p.post_date $op %s "
          // $post->post_type
          ."AND p.post_type = %s "
          // $posts_in_ex_cats_sql = " AND tt.taxonomy = 'category' 
          // AND tt.term_id NOT IN (" . implode($excluded_categories, ',') . ')'; 
          // OR empty string if $in_same_cat || ! empty( $excluded_categories
          ."AND p.post_status = 'publish' $posts_in_ex_cats_sql "
        ",
        $current_post_date,
        $post->post_type
    )
    $in_same_cat
    $excluded_categories
  • "get_{$adjacent}_post_sort"

    "ORDER BY p.post_date $order LIMIT 1"`

Quindi puoi fare molto con esso. Ciò inizia con il filtraggio della WHEREclausola, nonché della JOINtabella ed e ORDER BYdell'istruzione.

Il risultato viene memorizzato nella cache per la richiesta corrente, quindi non aggiunge ulteriori query se si chiama quella funzione più volte su una singola pagina.

Creazione automatica di query

Come sottolineato da @StephenHarris nei commenti, c'è una funzione principale che potrebbe tornare utile quando si crea la query SQL: get_meta_sql()- Esempi in Codex . Fondamentalmente questa funzione viene utilizzata solo per creare l'istruzione meta SQL in cui viene utilizzata WP_Query, ma è possibile utilizzarla anche in questo caso (o in altri). L'argomento che ci si lancia è un array, esattamente lo stesso che si aggiungerebbe a WP_Query.

$meta_sql = get_meta_sql(
    $meta_query,
    'post',
    $wpdb->posts,
    'ID'
);

Il valore restituito è un array:

$sql => (array) 'join' => array(),
        (array) 'where' => array()

Quindi puoi usare $sql['join']e $sql['where']nel tuo callback.

Dipendenze da tenere a mente

Nel tuo caso, la cosa più semplice sarebbe intercettarlo in un piccolo plugin (mu) o nel tuo file Functions.php di temi e modificarlo a seconda della $adjacent = $previous ? 'previous' : 'next';variabile e della $order = $previous ? 'DESC' : 'ASC';variabile:

I nomi dei filtri effettivi

Quindi i nomi dei filtri sono:

  • get_previous_post_join, get_next_post_join
  • get_previous_post_where, get_next_post_where
  • get_previous_post_sort, get_next_post_sort

Avvolto come un plugin

... e il callback del filtro sarebbe (per esempio) qualcosa di simile al seguente:

<?php
/** Plugin Name: (#73190) Alter adjacent post link sort order */
function wpse73190_adjacent_post_sort( $orderby )
{
    return "ORDER BY p.menu_order DESC LIMIT 1";
}
add_filter( 'get_previous_post_sort', 'wpse73190_adjacent_post_sort' );
add_filter( 'get_next_post_sort', 'wpse73190_adjacent_post_sort' );

2
+1. Solo per informazione, (@magnakai) se fai qualcosa del genere per meta-query, controllaget_meta_sql()
Stephen Harris,

+1 a te @StephenHarris! Non l'ho mai visto prima. Breve domanda: mentre leggo dalla fonte che devi passare un oggetto query completo, come faresti con i filtri sopra menzionati? Per quanto posso vedere ci sono solo le stringhe di query passate, poiché la query viene eseguita dopo i filtri.
Kaiser

2
no, $meta_queryè solo l'array a cui passeresti WP_Queryper l' meta_queryargomento,: In questo esempio: $meta_sql = get_meta_sql( $meta_query, 'post', $wpdb->posts, 'ID');- questo genera la JOINe WHEREparte della query che dovrebbe essere aggiunta.
Stephen Harris,

@StephenHarris Momento perfetto per modificare una (mia) risposta.
Kaiser

@StephenHarris, sto riscontrando problemi nell'applicazione dell'output di get_meta_sql () - puoi aiutarmi a unire i punti?
Jodi Warren,

21

La risposta di Kaiser è fantastica e completa, tuttavia cambiare la clausola ORDER BY non è sufficiente a meno che non menu_orderabbini il tuo ordine cronologico.

Non posso prendermi il merito per questo, ma ho trovato il seguente codice in questa sintesi :

<?php
/**
 * Customize Adjacent Post Link Order
 */
function wpse73190_gist_adjacent_post_where($sql) {
  if ( !is_main_query() || !is_singular() )
    return $sql;

  $the_post = get_post( get_the_ID() );
  $patterns = array();
  $patterns[] = '/post_date/';
  $patterns[] = '/\'[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\'/';
  $replacements = array();
  $replacements[] = 'menu_order';
  $replacements[] = $the_post->menu_order;
  return preg_replace( $patterns, $replacements, $sql );
}
add_filter( 'get_next_post_where', 'wpse73190_gist_adjacent_post_where' );
add_filter( 'get_previous_post_where', 'wpse73190_gist_adjacent_post_where' );

function wpse73190_gist_adjacent_post_sort($sql) {
  if ( !is_main_query() || !is_singular() )
    return $sql;

  $pattern = '/post_date/';
  $replacement = 'menu_order';
  return preg_replace( $pattern, $replacement, $sql );
}
add_filter( 'get_next_post_sort', 'wpse73190_gist_adjacent_post_sort' );
add_filter( 'get_previous_post_sort', 'wpse73190_gist_adjacent_post_sort' );

Ho modificato i nomi delle funzioni per WP.SE.

Se modifichi solo la clausola ORDER BY, la query continua a cercare post maggiori o minori della data di post corrente. Se i tuoi post non sono in ordine cronologico, non otterrai il post giusto.

Ciò modifica la clausola where per cercare post in cui menu_order è maggiore o minore del menu_order del post corrente, oltre a modificare la clausola orderby.

Inoltre, la clausola orderby non dovrebbe essere codificata per usare DESC in quanto dovrà passare in base al fatto che tu stia ottenendo il post successivo o precedente.


3
Una nota: la WHEREclausola cerca 'YYYY-mm-dd HH:mm:ss'. Se ciò non viene soddisfatto, non funzionerà. Poiché il valore non è impostato dal DB, ma dall'applicazione, sarà necessario prima verificare quel formato quando si crea l'espressione regolare.
Kaiser,

5

Ho provato ad agganciarmi senza successo. Potrebbe essere solo un problema della mia configurazione, ma per coloro che non riescono a far funzionare l'hook, ecco la soluzione più semplice:

<?php
    $all_posts = new WP_Query(array(
        'orderby' => 'menu_order',
        'order' => 'ASC',
        'posts_per_page' => -1
    ));

    foreach($all_posts->posts as $key => $value) {
        if($value->ID == $post->ID){
            $nextID = $all_posts->posts[$key + 1]->ID;
            $prevID = $all_posts->posts[$key - 1]->ID;
            break;
        }
    }
?>
<?php if($prevID): ?>
    <span class="prev">
        <a href="<?= get_the_permalink($prevID) ?>" rel="prev"><?= get_the_title($prevID) ?></a>
    </span>
<?php endif; ?>
<?php if($nextID): ?>
    <span class="next">
        <a href="<?= get_the_permalink($nextID) ?>" rel="next"><?= get_the_title($nextID) ?></a>
    </span>
<?php endif; ?>

dopo poche ore di cercare di ottenere get_previous_post_where, get_previous_post_joine get_previous_post_sortdi bel gioco con i tipi di messaggi personalizzati e l'ordinamento complesso che comprende chiavi meta, ho rinunciato e usato questo. Grazie!
squarecandy

Lo stesso qui, non solo volevo ordinare per Ordine menu, ma anche cercare post con uno specifico meta_key e meta_value, quindi questo era il metodo migliore. L'unica modifica che ho apportato è stata racchiuderla in una funzione.
MrCarrot il

4
function wpse73190_gist_adjacent_post_sort( $sql ) {
    $pattern = '/post_date/';
    $replacement = 'menu_order';

    return preg_replace( $pattern, $replacement, $sql );
}

add_filter( 'get_next_post_sort', 'wpse73190_gist_adjacent_post_sort' );
add_filter( 'get_previous_post_sort', 'wpse73190_gist_adjacent_post_sort' );

1

Sulla base della risposta di @Szabolcs Páll, ho creato questa classe di utilità con metodi di supporto per essere in grado di ottenere post di tipo per ordine di menu e anche il post precedente e successivo per ordine di menu. Ho inoltre aggiunto le condizioni per verificare se il post corrente è il primo o l'ultimo post per ottenere rispettivamente l'ultimo o il primo post.

Per esempio:

// $currentPost is first by menu order
getPreviousPostByMenuOrder($postType, $$currentPost->ID)
// returns => last post by menu order

// $currentPost is last by menu order
getPreviousPostByMenuOrder($postType, $$currentPost->ID)
// returns => first post by menu order

La classe completa:

class PostMenuOrderUtils {

    public static function getPostsByMenuOrder($postType){
        $args =[
            'post_type' => $postType,
            'orderby' => 'menu_order',
            'order' => 'ASC',
            'posts_per_page' => -1
        ];

        $posts = get_posts($args);

        return $posts;
    }

    public static function getNextPostByMenuOrder($postType, $postID){
        $posts = self::getPostsByMenuOrder($postType);

        $nextPost = null;

        foreach($posts as $key => $value) {
            if($value->ID == $postID){
                $nextPost = $posts[$key] !== end($posts) ? $posts[$key + 1] : $posts[0];

                break;
            }
        }

        return $nextPost;
    }

    public static function getPreviousPostByMenuOrder($postType, $postID){
        $posts = self::getPostsByMenuOrder($postType);


        $prevPost = null;

        foreach($posts as $key => $value) {
            if($value->ID == $postID){
                $prevPost = $key !== 0 ? $posts[$key - 1] : end($posts);
                break;
            }
        }

        return $prevPost;
    }

}


0

Questo ha funzionato per me:

add_filter( 'get_previous_post_where', 'so16495117_mod_adjacent_bis' );
add_filter( 'get_next_post_where', 'so16495117_mod_adjacent_bis' );
function so16495117_mod_adjacent_bis( $where ) {
    global $wpdb;
    return $where . " AND p.ID NOT IN ( SELECT post_id FROM $wpdb->postmeta WHERE ($wpdb->postmeta.post_id = p.ID ) AND $wpdb->postmeta.meta_key = 'archive' AND $wpdb->postmeta.meta_value = 1 )";
}

Tratto da: https://stackoverflow.com/questions/16495117/how-to-skip-certain-links-on-adjacent-posts-in-wordpress


-1

Ho trovato un modo molto più semplice per ottenere una post-navigazione basata su meta-chiave, senza la necessità di modificare funzioni.php.

Il mio esempio: hai un products.php e vuoi passare da un prodotto all'altro. Il prodotto precedente è il successivo più economico, il prodotto successivo il successivo più costoso.

Ecco la mia soluzione per single.php :

<div class="post_navigation">

<?php

// Prepare loop
$args = (
'post_type' => 'products',
'post_status' => 'publish',
'meta_key' => 'price',
'orderby' => 'meta_value_num',
'order' => 'ASC',
'posts_per_page' => -1
);
query_posts($args);

// Initialize array in which the IDs of ALL products posts will be stored
$posts = array();

// ... and now let's start the loop
while ( have_posts() ) : the_post();
$posts[] += $post->ID;
endwhile;

// Reset Query
wp_reset_query();

// Identify the position of the current product within the $posts-array 
$current = array_search(get_the_ID(), $posts);

// Identify ID of previous product
$prevID = $posts[$current-1];

// Identify ID of next product
$nextID = $posts[$current+1];

// Link "previous product"
if (!empty($prevID)) { ?>
<a href="/?p=<?php echo $prevID; ?>">previous product</a>
<?php }
// Link "next product"
if (!empty($nextID)) { ?>
<a href="/?p=<?php echo $nextID; ?>">next product</a>

<?php } ?>

-10 per questa risposta. Come può essere una soluzione migliore se stai usando query_postsquando il codice afferma che non dovrebbe essere usato.
Pieter Goosen,

ma funziona. quindi l'alternativa è WP_Query o cosa?
Kent Miller,

Sì, WP_Querydovrebbe essere usato come nelle risposte precedenti.
Pieter Goosen,

1
@KentMiller, c'è un diagramma informativo sulla pagina del codice e potresti trovare utile questa domanda . Vale la pena familiarizzare con queste convenzioni.
Jodi Warren,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.