Czy linki Next / Prev Post można uporządkować według kolejności menu lub według klucza meta?


32

Mam serię postów uporządkowanych według wartości meta_key. W razie potrzeby można je również uporządkować według kolejności menu.

Linki do następnego / poprzedniego postu (generowane przez next_post_link, previous_post_linklub posts_nav_linkwszystkie nawigują według chronologii. Chociaż rozumiem to domyślne zachowanie, nie rozumiem, jak to zmienić. Stwierdziłem, że mapuje ono na sąsiedni_post_link w link-template.php, ale wtedy zaczyna się wydawać dość mocno zakodowany. Czy zaleca się przepisanie tego od zera, aby go zastąpić, czy też istnieje lepsze rozwiązanie.


2
Oto idealna wtyczka do twojego problemu: wordpress.org/support/topic/… wordpress.org/extend/plugins/... Dziękujemy Ambrosite! :)
miguelb,

1
Zauważ, że druga odpowiedź wydaje się dawać poprawny wynik.
Thomas

Odpowiedzi:


29

Zrozumienie elementów wewnętrznych

Kolejność „sortowania” sąsiednich (następnych / poprzednich) postów nie jest tak naprawdę „kolejnością” sortowania. Jest to osobne zapytanie dla każdego żądania / strony, ale sortuje zapytanie wedługpost_date - lub pocztowego rodzica, jeśli masz hierarchiczną posadę aktualnie wyświetlanego obiektu.

Kiedy spojrzysz na elementy wewnętrzne next_post_link(), zobaczysz, że jest to po prostu opakowanie API adjacent_post_link(). Późniejsza funkcja wywołuje get_adjacent_post()wewnętrznie z ustawionym $previousargumentem / flagąbool(true|false) aby pobrać link do następnego lub poprzedniego postu.

Co filtrować?

Po głębszym zagłębieniu się w niego zobaczysz, że get_adjacent_post() link źródłowy ma kilka ładnych filtrów dla swojego wyniku (inaczej wynik zapytania): (Nazwa filtru / Argumenty)

  • "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"`

Możesz więc dużo z tym zrobić . Zaczyna się od filtrowania WHEREklauzuli, a także JOINtabeli ed i plikuORDER BY instrukcji.

Wynik jest buforowany w pamięci dla bieżącego żądania, więc nie dodaje dodatkowych zapytań, jeśli wywołasz tę funkcję wiele razy na jednej stronie.

Automatyczne budowanie zapytań

Jak zauważył @StephenHarris w komentarzach, istnieje podstawowa funkcja, która może się przydać podczas budowania zapytania SQL: get_meta_sql()- Przykłady w Kodeksie . Zasadniczo ta funkcja służy tylko do budowania meta-SQL instrukcji, do której się przydaje WP_Query, ale możesz jej użyć również w tym przypadku (lub innych). Argument, który do niego wrzucisz, to tablica, dokładnie taka sama, jak w przypadku WP_Query.

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

Zwracana wartość to tablica:

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

Więc możesz użyć $sql['join']i$sql['where'] w zwrotnego.

Zależności, o których należy pamiętać

W twoim przypadku najłatwiej byłoby przechwycić go w małej wtyczce (mu) lub w pliku motywów funkcji.php i zmienić w zależności od $adjacent = $previous ? 'previous' : 'next';zmiennej i$order = $previous ? 'DESC' : 'ASC'; zmiennej:

Rzeczywiste nazwy filtrów

Więc nazwy filtrów to:

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

Zapakowane jako wtyczka

... a wywołanie zwrotne filtra byłoby (na przykład) czymś w rodzaju:

<?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. Tylko dla informacji (@magnakai), jeśli robisz coś takiego w przypadku zapytań meta, sprawdźget_meta_sql()
Stephen Harris,

+1 dla ciebie @StephenHarris! Nie widziałem tego wcześniej. Krótkie pytanie: jak czytam ze źródła, że ​​musisz przekazać w pełni kwalifikowany obiekt zapytania, jak zrobiłbyś to z wyżej wymienionymi filtrami? O ile widzę, przekazywane są tylko ciągi zapytania, ponieważ zapytanie jest wykonywane po filtrach.
kaiser

2
Nie, $meta_queryjest tylko tablica chcielibyśmy podać WP_Querydla meta_queryargument: W tym przykładzie: $meta_sql = get_meta_sql( $meta_query, 'post', $wpdb->posts, 'ID');- to generujeJOIN i WHEREczęści zapytania, które muszą zostać dodane.
Stephen Harris,

@StephenHarris Idealny moment na edycję jednej (mojej) odpowiedzi.
kaiser

@StephenHarris, mam problem ze stosowaniem danych wyjściowych get_meta_sql () - czy możesz pomóc połączyć kropki?
Jodi Warren,

21

Odpowiedź Kaiser jest niesamowita i dokładna, jednak sama zmiana klauzuli ORDER BY nie wystarczy, chyba że menu_orderpasuje do kolejności chronologicznej.

Nie mogę tego przypisać, ale w tej liście znalazłem następujący kod :

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

Zmodyfikowałem nazwy funkcji dla WP.SE.

Jeśli zmienisz tylko klauzulę ORDER BY, zapytanie nadal będzie wyszukiwać posty większe lub mniejsze niż bieżąca data ogłoszenia. Jeśli Twoje posty nie są uporządkowane chronologicznie, nie dostaniesz właściwego postu.

To zmienia klauzulę where, aby szukać postów, w których menu_order jest większy lub mniejszy niż menu_order bieżącego postu, oprócz modyfikowania klauzuli orderby.

Klauzula orderby również nie powinna być zakodowana na stałe, aby używać DESC, ponieważ będzie musiała się przełączać w zależności od tego, czy otrzymujesz link do następnego czy poprzedniego postu.


3
Jedna uwaga: WHEREklauzula wygląda 'YYYY-mm-dd HH:mm:ss'. Jeśli to nie zostanie spełnione, nie będzie działać. Ponieważ wartość nie jest ustawiona przez DB, ale przez Aplikację, będziesz musiał najpierw sprawdzić ten format podczas budowania wyrażenia Regular.
kaiser

5

Próbowałem podłączyć się bez powodzenia. Może to być tylko problem z moją konfiguracją, ale dla tych, którzy nie mogą sprawić, by hak działał, oto najprostsze rozwiązanie:

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

po kilku godzinach próbując dostać get_previous_post_where, get_previous_post_joina get_previous_post_sortdo gry ładne z niestandardowych typów pocztowych i kompleksowego uporządkowania, który zawiera klucze meta, dałem się i użył tego. Dzięki!
squarecandy

To samo tutaj, nie tylko chciałem zamówić według kolejności menu, ale także poszukać postów z określonym meta_key i meta_value, dlatego była to najlepsza metoda. Jedyną zmianą, którą wprowadziłem, było zawinięcie jej w funkcję.
MrCarrot

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

Na podstawie odpowiedzi @Szabolcs Páll Utworzyłem tę klasę użytkową z metod pomocniczych, aby móc uzyskać wiadomości typu według kolejności menu i dostać następny i poprzedni post przez kolejności menu, jak również. Dodatkowo dodałem warunki, aby sprawdzić, czy bieżący post jest pierwszym lub ostatnim postem, aby uzyskać odpowiednio ostatni lub pierwszy post.

Na przykład:

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

Pełna klasa:

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

To działało dla mnie:

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

Zaczerpnięte z: https://stackoverflow.com/questions/16495117/how-to-skip-certain-links-on-adjacent-posts-in-wordpress


-1

Znalazłem znacznie łatwiejszy sposób na osiągnięcie nawigacji postowej opartej na meta-klawiszach, bez potrzeby modyfikowania funkcji.php.

Mój przykład: masz products.php i chcesz przełączać się między produktami. Poprzedni produkt jest kolejnym tańszym, następny produkt kolejnym droższym.

Oto moje rozwiązanie dla 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 za tę odpowiedź. Jak może to być lepsze rozwiązanie, jeśli używasz, query_postsgdy kodeks stwierdza, że ​​nie należy go używać.
Pieter Goosen

ale to działa. więc alternatywą jest WP_Query czy co?
Kent Miller

Tak, WP_Querynależy stosować jak w poprzednich odpowiedziach.
Pieter Goosen

1
@KentMiller, na stronie kodeksu znajduje się pouczający schemat , a to pytanie może być również przydatne. Warto zapoznać się z tymi konwencjami.
Jodi Warren
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.