Можно ли упорядочить ссылки Next / Prev Post по порядку меню или по мета-ключу?

32

У меня есть серия сообщений, которые упорядочены по значению meta_key. Они также могут быть расположены в порядке меню, если это необходимо.

Почтовые ссылки следующая / предыдущая (порождаемые next_post_link, previous_post_linkили posts_nav_linkвсе навигации по хронологии. В то время как я понимаю , это поведение по умолчанию, я не понимаю , как изменить его. Я обнаружил , что он отображает до adjacent_post_link в ссылке-template.php, но тогда это начинает казаться довольно жестко закодированным. Рекомендуется переписать это с нуля, чтобы заменить его, или есть лучшее решение.

Джоди Уоррен
источник
2
Вот идеальный плагин для вашей проблемы: wordpress.org/support/topic/… wordpress.org/extend/plugins/… Спасибо, Амбросайт! :)
miguelb
1
Обратите внимание, что второй ответ, кажется, дает правильный результат.
Томас

Ответы:

29

Понимание внутренних органов

Порядок «сортировки» смежных (следующий / предыдущий) постов на самом деле не является порядком «сортировки». Это отдельный запрос для каждого запроса / страницы, но он сортирует запрос по post_date- или родительскому посту, если у вас есть иерархическая публикация в качестве отображаемого в данный момент объекта.

Когда вы посмотрите на внутренности next_post_link(), то увидите, что это в основном оболочка API adjacent_post_link(). Последняя функция вызывает get_adjacent_post()внутренне с $previousаргументом / флагом, установленным для получения bool(true|false)ссылки на следующую или предыдущую запись.

Что фильтровать?

Углубившись в него, вы увидите, что get_adjacent_post() ссылка «Источник» имеет несколько хороших фильтров для своего вывода (он же результат запроса): (Имя фильтра / Аргументы)

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

Таким образом, вы можете многое сделать с этим. Это начинается с фильтрации WHEREпредложения, а также JOINтаблицы ed и ORDER BYоператора.

Результат кэшируется в памяти для текущего запроса, поэтому он не добавляет дополнительных запросов, если вы вызываете эту функцию несколько раз на одной странице.

Автоматическое построение запросов

Как отметил @StephenHarris в комментариях, есть основная функция, которая может пригодиться при построении SQL-запроса: get_meta_sql()- Примеры в Кодексе . По сути, эта функция просто используется для построения оператора meta SQL, который используется WP_Query, но вы можете использовать его и в этом случае (или в других). Аргумент, который вы добавляете в него, является массивом, точно таким же, который добавляется к WP_Query.

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

Возвращаемое значение представляет собой массив:

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

Так что вы можете использовать $sql['join']и $sql['where']в вашем обратном вызове.

Зависимости, которые нужно иметь в виду

В вашем случае проще всего было бы перехватить его в небольшом (mu) плагине или в файле themes.php вашего файла и изменить его в зависимости от $adjacent = $previous ? 'previous' : 'next';переменной и $order = $previous ? 'DESC' : 'ASC';переменной:

Фактические имена фильтров

Итак, имена фильтров:

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

Завёрнутый как плагин

... и обратный вызов фильтра будет (например) примерно таким:

<?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. Просто для информации, (@magnakai), если вы делаете что-то подобное для мета-запросов, посмотритеget_meta_sql()
Стивен Харрис
+1 тебе @StephenHarris! Не видел этого раньше. Короткий вопрос: как я прочитал из источника, что вы должны передать полностью определенный объект запроса, как бы вы сделали это с вышеупомянутыми фильтрами? Насколько я вижу, передаются только строки запроса, так как запрос выполняется после фильтров.
Кайзер
2
Нет, $meta_queryэто просто массив, который вы передадите WP_Queryдля meta_queryаргумента, в: В этом примере: $meta_sql = get_meta_sql( $meta_query, 'post', $wpdb->posts, 'ID');- это генерирует JOINи WHEREчасть запроса, которая должна быть добавлена.
Стивен Харрис
@StephenHarris Прекрасный момент для редактирования одного (моего) ответа.
Кайзер
@ StefhenHarris, у меня проблемы с применением вывода get_meta_sql () - можете ли вы помочь объединить точки?
Джоди Уоррен
21

Ответ Кайзера удивителен и тщателен, однако простого изменения предложения ORDER BY недостаточно, если вы не menu_orderсоответствуете вашему хронологическому порядку.

Я не могу взять кредит на себя, но я нашел следующий код в этой сущности :

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

Я изменил имена функций для WP.SE.

Если вы измените только предложение ORDER BY, запрос все равно будет искать сообщения, которые больше или меньше текущей даты публикации. Если ваши сообщения не в хронологическом порядке, вы не получите правильный пост.

Это изменяет предложение where для поиска постов, где menu_order больше или меньше, чем menu_order текущего поста, в дополнение к изменению предложения orderby.

Предложение orderby также не должно быть жестко запрограммировано для использования DESC, так как оно должно будет переключаться в зависимости от того, получаете ли вы ссылку на следующий или предыдущий пост.

jjeaton
источник
3
Одно примечание: WHEREпункт ищет 'YYYY-mm-dd HH:mm:ss'. Если это не выполнено, это не сработает. Поскольку это значение задается не БД, а приложением, вам сначала нужно будет проверить этот формат при построении регулярного выражения.
Кайзер
5

Пытался зацепиться без успеха. Может быть, это просто проблема моей конфигурации, но для тех, кто не может заставить хук работать, вот самое простое решение:

<?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; ?>
Сабольч Палл
источник
Через несколько часов , пытаясь получить get_previous_post_where, get_previous_post_joinи get_previous_post_sortиграть хорошо с типами пользовательских почтовых и сложного заказа , который включает в себя мета - ключи, я сдался и использовал это. Благодарность!
SquareCandy
То же самое здесь, я не только хотел упорядочить по меню, но и искать сообщения с определенным meta_key и meta_value, поэтому это был лучший метод. Единственное изменение, которое я сделал, было заключить его в функцию.
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

На основании @Szabolcs Полла ответа Я создал этот вспомогательный класс с методами хелперов , чтобы иметь возможность получить сообщения типа по заказу меню и получить следующий и предыдущий пост по заказу меню , а также. Я дополнительно добавил условия, чтобы проверить, является ли текущий пост первым или последним постом, чтобы получить последний или первый пост соответственно.

Например:

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

Полный класс:

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

Я нахожу этот небольшой плагин действительно удобным: http://wordpress.org/plugins/wp-query-powered-adjacent-post-link/

WP_Query Powered Adjussian Post Link - это плагин для разработчиков. Он добавляет функцию wpqpapl();в WordPress, которая может возвращать информацию о предыдущем и следующем посте в текущий. Он принимает аргументы для использования в WP_Queryклассе.

any_h
источник
0

Это сработало для меня:

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

Взято из: https://stackoverflow.com/questions/16495117/how-to-skip-sure-links-on-adjacent-posts-in-wordpress

Филипп
источник
-1

Я нашел гораздо более простой способ добиться пост-навигации на основе метаключ, без необходимости изменять functions.php.

Мой пример: у вас есть products.php, и вы хотите переключаться между продуктами. Предыдущий продукт - следующий, более дешевый, следующий - более дорогой.

Вот мое решение для 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 за этот ответ. Как это может быть лучшим решением, если вы используете, query_postsкогда кодекс заявляет, что его не следует использовать.
Питер Гусен
но это работает. так альтернатива это WP_Query или как?
Кент Миллер
Да, WP_Queryследует использовать как в предыдущих ответах.
Питер Гусен
1
@KentMiller, на странице кодекса есть информативная диаграмма , и вам также может пригодиться этот вопрос . Стоит ознакомиться с этими соглашениями.
Джоди Уоррен