Os links Próximo / Postagem anterior podem ser ordenados por ordem de menu ou por uma meta-chave?


32

Eu tenho uma série de postagens ordenadas por um valor meta_key. Eles também podem ser organizados por ordem do menu, se necessário.

Os postar links próxima / prev (gerados por next_post_link, previous_post_linkou posts_nav_linktudo navegar pela cronologia. Embora eu entenda esse comportamento padrão, eu não entendo como mudar isso. Descobri que ele mapeia até adjacent_post_link no link-template.php, mas então começa a parecer bastante codificado.Recomenda-se reescrever isso do zero para substituí-lo ou existe uma solução melhor.


2
Aqui está o plugin perfeito para o seu problema: wordpress.org/support/topic/… wordpress.org/extend/plugins/… Obrigado Ambrosite! :)
miguelb

1
Observe que a segunda resposta parece produzir o resultado correto.
Thomas

Respostas:


29

Entendendo os internos

A ordem de "classificação" das postagens adjacentes (próximas / anteriores) não é realmente uma "ordem" de classificação. É uma consulta separada em cada solicitação / página, mas classifica a consulta pelo post_date- ou pelo pai da postagem, se você tiver uma postagem hierárquica como objeto atualmente exibido.

Quando você examina as partes internas de next_post_link(), vê que é basicamente um wrapper de API para adjacent_post_link(). A função posterior chama get_adjacent_post()internamente com o $previousargumento / sinalizador definido bool(true|false)para pegar o link de postagem seguinte ou anterior.

O que filtrar?

Depois de aprofundar o assunto, você verá que o get_adjacent_post() link Origem possui alguns filtros interessantes para sua saída (também conhecido como resultado da consulta): (Nome / Argumentos do Filtro)

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

Então você pode fazer muito com isso. Isso começa com a filtragem da WHEREcláusula, bem como a JOINtabela ed e a ORDER BYinstrução

O resultado é armazenado em cache na memória para a solicitação atual, portanto, não adiciona consultas adicionais se você chamar essa função várias vezes em uma única página.

Construção de consulta automática

Como @StephenHarris apontou nos comentários, há uma função principal que pode ser útil ao criar a Consulta SQL: get_meta_sql()- Exemplos no Codex . Basicamente, essa função é usada apenas para criar a instrução meta SQL usada WP_Query, mas você pode usá-la neste caso (ou em outros) também. O argumento que você coloca nele é uma matriz, exatamente o mesmo que seria adicionado a a WP_Query.

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

O valor de retorno é uma matriz:

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

Então você pode usar $sql['join']e $sql['where']no seu retorno de chamada.

Dependências a serem lembradas

No seu caso, o mais fácil seria interceptá-lo em um pequeno plugin (mu) ou no arquivo functions.php do seu tema e alterá-lo dependendo da $adjacent = $previous ? 'previous' : 'next';variável e da $order = $previous ? 'DESC' : 'ASC';variável:

Os nomes reais do filtro

Portanto, os nomes dos filtros são:

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

Embrulhado como um plugin

... e o retorno de chamada do filtro seria (por exemplo) algo como o seguinte:

<?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. Apenas para informação, (@magnakai) se fazer algo como isso por meta consultas, vejaget_meta_sql()
Stephen Harris

+1 a você @StephenHarris! Não vi este antes. Pergunta curta: Como li na fonte que você precisa passar um objeto de consulta totalmente qualificado, como você faria isso com os filtros mencionados acima? Tanto quanto eu posso ver, há apenas cadeias de consulta passadas, pois a consulta é executada após os filtros.
Kaiser

2
não, $meta_queryé apenas a matriz para a qual você passaria WP_Queryo meta_queryargumento,: Neste exemplo: $meta_sql = get_meta_sql( $meta_query, 'post', $wpdb->posts, 'ID');- isso gera a parte JOINe WHEREda consulta que precisaria ser adicionada.
Stephen Harris

@StephenHarris Momento perfeito para editar uma (minha) resposta.
Kaiser

@StephenHarris, estou tendo problemas para aplicar a saída de get_meta_sql () - você pode ajudar a juntar os pontos?
Jodi Warren #

21

A resposta de Kaiser é impressionante e completa, no entanto, apenas alterar a cláusula ORDER BY não é suficiente, a menos que você menu_ordercorresponda à sua ordem cronológica.

Não posso me responsabilizar por isso, mas encontrei o seguinte código nesta lista :

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

Eu modifiquei os nomes das funções para o WP.SE.

Se você alterar apenas a cláusula ORDER BY, a consulta ainda procurará postagens maiores ou menores que a data atual. Se suas postagens não estiverem em ordem cronológica, você não receberá a postagem correta.

Isso altera a cláusula where para procurar postagens onde o menu_ordem é maior ou menor que o menu_ordem da postagem atual, além de modificar a cláusula orderby.

A cláusula orderby também não deve ser codificada para usar DESC, pois será necessário alternar com base no fato de você estar obtendo o link da postagem seguinte ou anterior.


3
Uma observação: a WHEREcláusula procura 'YYYY-mm-dd HH:mm:ss'. Se isso não for cumprido, não funcionará. Como o valor não é definido pelo banco de dados, mas pelo aplicativo, você deverá verificar esse formato primeiro ao criar a expressão regular.
Kaiser #

5

Tentei me conectar sem sucesso. Pode ser apenas um problema da minha configuração, mas para aqueles que não conseguem fazer o gancho funcionar, aqui está a solução mais simples:

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

depois de algumas horas de tentar obter get_previous_post_where, get_previous_post_joine get_previous_post_sortpara jogar bonito com tipos de pós personalizado e ordenação complexo que inclui chaves de meta, eu desisti e usou isso. Obrigado!
squarecandy

O mesmo aqui, não apenas eu queria encomendar por ordem do menu, mas também procurar postagens com uma meta_key e meta_value específicas, portanto esse era o melhor método. A única alteração que fiz foi envolvê-lo em uma função.
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

Com base na resposta de @Szabolcs Páll, criei essa classe de utilitário com métodos auxiliares para poder obter postagens do tipo por ordem de menu e também a próxima e a publicação anterior por ordem de menu. Além disso, adicionei condições para verificar se a postagem atual é a primeira ou a última postagem para obter a última ou a primeira postagem, respectivamente.

Por exemplo:

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

A 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

Isso funcionou para mim:

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

Retirado de: https://stackoverflow.com/questions/16495117/how-to-skip-About-links-on-adjacent-posts-in-wordpress


-1

Eu encontrei uma maneira muito mais fácil de obter uma pós-navegação baseada em meta-chave, sem a necessidade de modificar o functions.php.

Meu exemplo: você tem um products.php e deseja alternar entre produtos. O produto anterior é o próximo mais barato, o próximo produto o próximo mais caro.

Aí vem minha solução para 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 para esta resposta. Como isso pode ser uma solução melhor se você estiver usando query_postsquando o codex declara que não deve ser usado.
Pieter Goosen

mas funciona. então a alternativa é WP_Query ou o quê?
Kent Miller

Sim, WP_Querydeve ser usado como nas respostas anteriores.
Pieter Goosen

1
@KentMiller, há um diagrama informativo na página do codex , e você também pode achar essa pergunta útil. Vale a pena se familiarizar com essas convenções.
Jodi Warren
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.