it-swarm.dev

Come si può correggere l'impaginazione per i loop personalizzati?

Ho aggiunto una query personalizzata/secondaria a un file modello/modello di pagina personalizzato; come posso fare in modo che WordPress usi la mia query personalizzata per l'impaginazione, invece di usare l'impaginazione del ciclo di query principale?

Appendice

Ho modificato la query del ciclo principale tramite query_posts() . Perché l'impaginazione non funziona e come risolverlo?

119
Chip Bennett

Il problema

Per impostazione predefinita, in un dato contesto, WordPress utilizza la query principale per determinare l'impaginazione. L'oggetto query principale è memorizzato nel $wp_query globale, che viene anche utilizzato per generare il ciclo di query principale:

if ( have_posts() ) : while ( have_posts() ) : the_post();

Quando usi una query personalizzata , crei un oggetto query completamente separato:

$custom_query = new WP_Query( $custom_query_args );

E quella query viene emessa tramite un ciclo completamente separato:

if ( $custom_query->have_posts() ) : 
    while ( $custom_query->have_posts() ) : 
        $custom_query->the_post();

Ma i tag del modello di impaginazione, inclusi previous_posts_link() , next_posts_link() , posts_nav_link() e paginate_links() , basano il loro output sul oggetto query principale, $wp_query. o non può essere impaginato Se il contesto corrente è un modello di pagina personalizzato, ad esempio, l'oggetto principale $wp_query sarà composto solo da singolo post- quello dell'ID della pagina a cui è assegnato il modello di pagina personalizzato .

Se il contesto corrente è un indice di archivio di qualche tipo, il principale $wp_query può consistere di post sufficienti a causare l'impaginazione, che porta alla parte successiva del problema: per l'oggetto principale $wp_query, WordPress passerà un parametro paged alla query, basato sulla variabile di query URL paged. Quando viene recuperata la query, verrà utilizzato il parametro paged per determinare quale set di post impaginati restituire. Se viene cliccato un link di paginazione visualizzato e viene caricata la pagina successiva, la tua query personalizzata non avrà modo di sapere che l'impaginazione è cambiata.

La soluzione

Passaggio corretto del parametro di paging alla query personalizzata

Supponendo che la query personalizzata usi un array args:

$custom_query_args = array(
    // Custom query parameters go here
);

Dovrai passare il parametro paged corretto all'array. È possibile farlo recuperando la variabile di query URL utilizzata per determinare la pagina corrente, tramite get_query_var() :

get_query_var( 'paged' );

È quindi possibile aggiungere tale parametro all'array args della query personalizzata:

$custom_query_args['paged'] = get_query_var( 'paged' ) 
    ? get_query_var( 'paged' ) 
    : 1;

Nota: Se la tua pagina è a front page statica , assicurati di usare page invece di paged come prima pagina statica utilizza page e non paged. Questo è quello che dovresti avere per una prima pagina statica

$custom_query_args['paged'] = get_query_var( 'page' ) 
    ? get_query_var( 'page' ) 
    : 1;

Ora, quando viene recuperata la query personalizzata, verrà restituita la serie corretta di post impaginati.

Utilizzo dell'oggetto di query personalizzato per le funzioni di impaginazione

Affinché le funzioni di impaginazione producano l'output corretto, ovvero i collegamenti precedenti/successivi/di pagina relativi alla query personalizzata, WordPress deve essere obbligato a riconoscere la query personalizzata. Ciò richiede un po 'di "hack": sostituire l'oggetto principale $wp_query con l'oggetto query personalizzato, $custom_query:

Hack l'oggetto query principale

  1. Eseguire il backup dell'oggetto query principale: $temp_query = $wp_query
  2. Null l'oggetto di query principale: $wp_query = NULL;
  3. Sostituire la query personalizzata nell'oggetto di query principale: $wp_query = $custom_query;

    $temp_query = $wp_query;
    $wp_query   = NULL;
    $wp_query   = $custom_query;
    

Questo "hack" deve essere fatto prima di chiamare qualsiasi funzione di paginazione

Reimposta l'oggetto query principale

Una volta emesse le funzioni di impaginazione, reimpostare l'oggetto query principale:

$wp_query = NULL;
$wp_query = $temp_query;

Correzione delle funzioni di impaginazione

La funzione previous_posts_link() funzionerà normalmente, indipendentemente dall'impaginazione. Determina semplicemente la pagina corrente e quindi emette il link per page - 1. Tuttavia, è necessaria una correzione per next_posts_link() per l'output corretto. Questo perché next_posts_link() utilizza il parametro max_num_pages:

<?php next_posts_link( $label , $max_pages ); ?>

Come con altri parametri di query, per impostazione predefinita la funzione utilizzerà max_num_pages per l'oggetto $wp_query principale. Per forzare next_posts_link() a rendere conto dell'oggetto $custom_query, è necessario passare max_num_pages alla funzione. Puoi recuperare questo valore dall'oggetto $custom_query: $custom_query->max_num_pages:

<?php next_posts_link( 'Older Posts' , $custom_query->max_num_pages ); ?>

Mettere tutto insieme

Di seguito è riportato un costrutto di base di un ciclo di query personalizzato con funzioni di impaginazione correttamente funzionanti:

// Define custom query parameters
$custom_query_args = array( /* Parameters go here */ );

// Get current page and append to custom query parameters array
$custom_query_args['paged'] = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;

// Instantiate custom query
$custom_query = new WP_Query( $custom_query_args );

// Pagination fix
$temp_query = $wp_query;
$wp_query   = NULL;
$wp_query   = $custom_query;

// Output custom query loop
if ( $custom_query->have_posts() ) :
    while ( $custom_query->have_posts() ) :
        $custom_query->the_post();
        // Loop output goes here
    endwhile;
endif;
// Reset postdata
wp_reset_postdata();

// Custom query loop pagination
previous_posts_link( 'Older Posts' );
next_posts_link( 'Newer Posts', $custom_query->max_num_pages );

// Reset main query object
$wp_query = NULL;
$wp_query = $temp_query;

Addendum: che dire di query_posts()?

query_posts() per loop secondari

Se stai usando query_posts() per generare un ciclo personalizzato, piuttosto che creare un'istanza di un oggetto separato per la query personalizzata tramite WP_Query() , allora sei _doing_it_wrong() , e si verificheranno diversi problemi ( non il il minimodei quali saranno problemi di impaginazione). Il primo passo per risolvere tali problemi sarà convertire l'uso improprio di query_posts() in una chiamata WP_Query() corretta.

Usando query_posts() per modificare il Main Loop

Se vuoi semplicemente modificare i parametri per ciclo principale query- come cambiare i post per pagina, o escludere una categoria - potresti essere tentato di usare query_posts() . Ma non dovresti ancora. Quando si utilizza query_posts() , si forza WordPress a replacel'oggetto query principale. (WordPress esegue effettivamente una seconda query e sovrascrive $wp_query.) Il problema, tuttavia, è che fa questa sostituzione troppo tardi in il processo per aggiornare l'impaginazione.

La soluzione è di filtra la query principale prima che i messaggi vengano recuperati, tramite l'hook pre_get_posts.

Invece di aggiungerlo al file modello di categoria (category.php):

query_posts( array(
    'posts_per_page' => 5
) );

Aggiungi quanto segue a functions.php:

function wpse120407_pre_get_posts( $query ) {
    // Test for category archive index
    // and ensure that the query is the main query
    // and not a secondary query (such as a nav menu
    // or recent posts widget output, etc.
    if ( is_category() && $query->is_main_query() ) {
        // Modify posts per page
        $query->set( 'posts_per_page', 5 ); 
    }
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );

Invece di aggiungere questo al file del modello di indice dei post del blog (home.php):

query_posts( array(
    'cat' => '-5'
) );

Aggiungi quanto segue a functions.php:

function wpse120407_pre_get_posts( $query ) {
    // Test for main blog posts index
    // and ensure that the query is the main query
    // and not a secondary query (such as a nav menu
    // or recent posts widget output, etc.
    if ( is_home() && $query->is_main_query() ) {
        // Exclude category ID 5
        $query->set( 'category__not_in', array( 5 ) ); 
    }
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );

In questo modo, WordPress utilizzerà l'oggetto $wp_query già modificato per determinare l'impaginazione, senza richiedere la modifica del modello.

Quando usare quale funzione

Ricerca questa domanda e risposta e questa domanda e risposta per capire come e quando usare WP_Query , pre_get_posts e query_posts() .

205
Chip Bennett

Io uso questo codice per il loop personalizzato con paginazione:

<?php
if ( get_query_var('paged') ) {
    $paged = get_query_var('paged');
} elseif ( get_query_var('page') ) { // 'page' is used instead of 'paged' on Static Front Page
    $paged = get_query_var('page');
} else {
    $paged = 1;
}

$custom_query_args = array(
    'post_type' => 'post', 
    'posts_per_page' => get_option('posts_per_page'),
    'paged' => $paged,
    'post_status' => 'publish',
    'ignore_sticky_posts' => true,
    //'category_name' => 'custom-cat',
    'order' => 'DESC', // 'ASC'
    'orderby' => 'date' // modified | title | name | ID | Rand
);
$custom_query = new WP_Query( $custom_query_args );

if ( $custom_query->have_posts() ) :
    while( $custom_query->have_posts() ) : $custom_query->the_post(); ?>

        <article <?php post_class(); ?>>
            <h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
            <small><?php the_time('F jS, Y') ?> by <?php the_author_posts_link() ?></small>
            <div><?php the_excerpt(); ?></div>
        </article>

    <?php
    endwhile;
    ?>

    <?php if ($custom_query->max_num_pages > 1) : // custom pagination  ?>
        <?php
        $orig_query = $wp_query; // fix for pagination to work
        $wp_query = $custom_query;
        ?>
        <nav class="prev-next-posts">
            <div class="prev-posts-link">
                <?php echo get_next_posts_link( 'Older Entries', $custom_query->max_num_pages ); ?>
            </div>
            <div class="next-posts-link">
                <?php echo get_previous_posts_link( 'Newer Entries' ); ?>
            </div>
        </nav>
        <?php
        $wp_query = $orig_query; // fix for pagination to work
        ?>
    <?php endif; ?>

<?php
    wp_reset_postdata(); // reset the query 
else:
    echo '<p>'.__('Sorry, no posts matched your criteria.').'</p>';
endif;
?>

Fonte:

21
webvitaly

Fantastico come sempre Chip. Come addendum a questo, si consideri la situazione in cui si utilizza un modello di pagina globale collegato a una pagina per un "testo introduttivo" ed è seguito da una sottoquery che si desidera venga cercata.

Usando paginate_links () come si menziona sopra, con i valori predefiniti (e supponendo che i permalink siano attivati) i collegamenti di paginazione verranno impostati su mysite.ca/page-slug/page/# che è adorabile ma genererà errori 404 perché WordPress non lo sa particolare struttura dell'URL e in effetti cercherà una pagina figlia di "pagina" che sia figlia di "page-slug".

Il trucco consiste nell'inserire una regola di riscrittura elegante che si applica solo a quella particolare pagina della pagina "pseudo archivio" che accetta la struttura /page/#/ e la riscrive su una stringa di query che WordPress PUO comprendere, ovvero mysite.ca/?pagename=page-slug&paged=#. Nota pagename e paged not name e page (che mi ha causato letteralmente ORE di dolore, motivando questa risposta qui!).

Ecco la regola di reindirizzamento:

add_rewrite_rule( "page-slug/page/([0-9]{1,})/?$", 'index.php?pagename=page-slug&paged=$matches[1]', "top" );

Come sempre, quando si cambiano le regole di riscrittura, ricordarsi di svuotare i permalink visitando Impostazioni> Permalink nel back-end di amministrazione.

Se si dispone di più pagine che si comportano in questo modo (ad esempio, quando si gestiscono più tipi di post personalizzati), è consigliabile evitare di creare una nuova regola di riscrittura per ogni slug di pagina. Possiamo scrivere un'espressione regolare più generica che funzioni per qualsiasi slug di pagina che identificate.

Un approccio è di seguito:

function wpse_120407_pseudo_archive_rewrite(){
    // Add the slugs of the pages that are using a Global Template to simulate being an "archive" page
    $pseudo_archive_pages = array(
        "all-movies",
        "all-actors"
    );

    $slug_clause = implode( "|", $pseudo_archive_pages );
    add_rewrite_rule( "($slug_clause)/page/([0-9]{1,})/?$", 'index.php?pagename=$matches[1]&paged=$matches[2]', "top" );
}
add_action( 'init', 'wpse_120407_pseudo_archive_rewrite' );

Svantaggi/avvertimenti

Uno svantaggio di questo approccio che mi fa vomitare un po 'in bocca è l'hard-coding del Page slug. Se un amministratore cambia la pagina di questa pagina pseudo-archivio, sei tostato: la regola di riscrittura non corrisponderà più e otterrai il temuto 404.

Non sono sicuro di poter pensare a una soluzione alternativa per questo metodo, ma sarebbe bello se fosse il modello di pagina globale che in qualche modo ha attivato la regola di riscrittura. Un giorno potrei rivisitare questa risposta se nessun altro ha violato quel particolare pazzo.

5
Tom Auger

Ho modificato la query del ciclo principale tramite query_posts(). Perché l'impaginazione non funziona e come risolverlo?

Grande risposta Il chip creato deve essere modificato oggi.
Per qualche tempo abbiamo una variabile $wp_the_query che dovrebbe essere uguale a $wp_query globale subito dopo l'esecuzione della query principale.

Questo è il motivo per cui questa parte dalla risposta di Chip:

Hack l'oggetto query principale

non è più necessario Possiamo dimenticare questa parte con la creazione della variabile temporanea.

// Pagination fix
$temp_query = $wp_query;
$wp_query   = NULL;
$wp_query   = $custom_query;

Quindi ora possiamo chiamare:

$wp_query   = $wp_the_query;

o ancora meglio possiamo chiamare:

wp_reset_query();

Tutto il resto delineato da Chip rimane. Dopo quella query-reset-part puoi chiamare le funzioni di impaginazione che sono f($wp_query), - esse dipendono da $wp_query globale.


Per migliorare ulteriormente la meccanica di impaginazione e dare più libertà alla funzione query_posts ho creato questo possibile miglioramento:

https://core.trac.wordpress.org/ticket/39483

2
prosti
global $wp_query;
        $paged = get_query_var('paged', 1);

    $args = array( 
        'post_type' => '{your_post_type_name}',
        'meta_query' => array('{add your meta query argument if need}'),  
        'orderby' => 'modified',
        'order' => 'DESC',
        'posts_per_page' => 20,
        'paged' => $paged 
    );
    $query = new WP_Query($args);

    if($query->have_posts()):
        while ($query->have_posts()) : $query->the_post();
            //add your code here
        endwhile;
        wp_reset_query();

        //manage pagination based on custom Query.
        $GLOBALS['wp_query']->max_num_pages = $query->max_num_pages;
        the_posts_pagination(array(
            'mid_size' => 1,
            'prev_text' => __('Previous page', 'patelextensions'),
            'next_text' => __('Next page', 'patelextensions'),
            'before_page_number' => '<span class="meta-nav screen-reader-text">' . __('Page', 'patelextensions') . ' </span>',
        ));
    else:
    ?>
        <div class="container text-center"><?php echo _d('Result not found','30'); ?></div>
    <?php
        endif;
    ?>
1
ravi patel