Alternative to WP-pagenavi Plugin (Pagination for WordPress)
Since version 4.1 in WordPress, a native similar function has appeared: the_posts_pagination()
I came across the topic that the popular WordPress plugin wp-pagenavi overloads the server unnecessarily. After analyzing its code, I found out that it is just a myth, although it can still be optimized to some extent, and that's what this post is dedicated to.
While it may be difficult to imagine other plugins without the ability to configure them in the admin panel, wp-pagenavi seems easy to me. It's enough to set up the navigation once and forget about it. And perhaps there are those who think the same? That's why I decided to abandon wp-pagenavi and replace it with my own function. I wrote the function after studying the code of wp-pagenavi, partially taking the code from there. All CSS classes of wp-pagenavi are preserved, consequently, replacing wp-pagenavi with my version is not difficult at all.
To replace it, you need to copy the following function into the template file functions.php. Also, you need to copy the CSS styles of wp-pagenavi into your stylesheet file (usually style.css). Transferring styles is also useful because there will no longer be a need to connect the stylesheet file, which is one less HTTP request.
GitHub/** * A wp_pagenavi alternative. Creates pagination links on archive pages. * * @param array $args Function arguments. * @param WP_Query $wp_query WP_Query object on which pagination is based. Defaults to global variable $wp_query. * * @return string Pagination HTML code. * * @link https://wp-kama.ru/8 * @author Timur Kamaev * @require WP 5.9 * @version 3.0 */ function kama_pagenavi( $args = [], $wp_query = null ){ $default = [ 'before' => '', // Text before the navigation. 'after' => '', // Text after the navigation. 'echo' => true, // Return or output the result. 'text_num_page' => '', // Text before the pagination. // {current} - current. // {last} - last (eg: 'Page {current} of {last}' will result in: "Page 4 of 60"). 'num_pages' => 10, // How many links to show. 'step_link' => 10, // Links with step (if 10, then: 1,2,3...10,20,30. Use 0 if such links are not needed. 'dotright_text' => '…', // Intermediate text "before". 'dotright_text2' => '…', // Intermediate text "after". 'back_text' => '« back', // Text "go to the previous page". Use 0 if this link is not needed. 'next_text' => 'forward »', // Text "go to the next page". Use 0 if this link is not needed. 'first_page_text' => '« to start', // Text "to the first page". Use 0 if the page number should be shown instead of the text. 'last_page_text' => 'to end »', // Text "to the last page". Use 0 if the page number should be shown instead of the text. ]; // Compat with v2.5: kama_pagenavi( $before = '', $after = '', $echo = true, $args = [] ) $fargs = func_get_args(); if( $fargs && is_string( $fargs[0] ) ){ $default['before'] = $fargs[0] ?? ''; $default['after'] = $fargs[1] ?? ''; $default['echo'] = $fargs[2] ?? true; $args = $fargs[3] ?? []; $wp_query = $GLOBALS['wp_query']; // !!! after $default } if( ! $wp_query ){ wp_reset_query(); global $wp_query; } if( ! $args ){ $args = []; } /** * Allows you to set default parameters. * * @param array $default_args */ $default = apply_filters( 'kama_pagenavi_args', $default ); $rg = (object) array_merge( $default, $args ); $paged = (int) ( $wp_query->get( 'paged' ) ?: 1 ); $max_page = (int) $wp_query->max_num_pages; // navigation no needed if( $max_page < 2 ){ return ''; } $pages_to_show = (int) $rg->num_pages; $pages_to_show_minus_1 = $pages_to_show - 1; $half_page_start = (int) floor( $pages_to_show_minus_1 / 2 ); // how many links before the current page $half_page_end = (int) ceil( $pages_to_show_minus_1 / 2 ); // how many links after the current page $start_page = $paged - $half_page_start; // first page $end_page = $paged + $half_page_end; // last page (conventionally) if( $start_page <= 0 ){ $start_page = 1; } if( (int) ( $end_page - $start_page ) !== (int) $pages_to_show_minus_1 ){ $end_page = $start_page + $pages_to_show_minus_1; } if( $end_page > $max_page ){ $start_page = $max_page - $pages_to_show_minus_1; $end_page = $max_page; } if( $start_page <= 0 ){ $start_page = 1; } // create a base to call get_pagenum_link once $link_base = str_replace( PHP_INT_MAX, '___', get_pagenum_link( PHP_INT_MAX ) ); $first_url = get_pagenum_link( 1 ); if( ! str_contains( $first_url, '?' ) ){ $first_url = user_trailingslashit( $first_url ); } // gather elements $els = []; if( $rg->text_num_page ){ $rg->text_num_page = preg_replace( '/{current}|{last}/', '%s', $rg->text_num_page ); $els['pages'] = sprintf( '<span class="pages">' . $rg->text_num_page . '</span>', $paged, $max_page ); } // back if( $rg->back_text && $paged !== 1 ){ $els['prev'] = sprintf( '<a class="prev" href="%s">%s</a>', ( ( $paged - 1 ) === 1 ? $first_url : str_replace( '___', ( $paged - 1 ), $link_base ) ), $rg->back_text ); } // to the beginning if( $start_page >= 2 && $pages_to_show < $max_page ){ $els['first'] = sprintf( '<a class="first" href="%s">%s</a>', $first_url, ( $rg->first_page_text ?: 1 ) ); if( $rg->dotright_text && $start_page !== 2 ){ $els[] = '<span class="extend">' . $rg->dotright_text . '</span>'; } } // pagination for( $i = $start_page; $i <= $end_page; $i++ ){ if( $i === $paged ){ $els['current'] = '<span class="current">' . $i . '</span>'; } elseif( $i === 1 ){ $els[] = sprintf( '<a href="%s">1</a>', $first_url ); } else{ $els[] = sprintf( '<a href="%s">%s</a>', str_replace( '___', (string) $i, $link_base ), $i ); } } // links with step $dd = 0; if( $rg->step_link && $end_page < $max_page ){ for( $i = $end_page + 1; $i <= $max_page; $i++ ){ if( 0 === ( $i % $rg->step_link) && $i !== $rg->num_pages ){ if( ++$dd === 1 ){ $els[] = '<span class="extend">' . $rg->dotright_text2 . '</span>'; } $els[] = sprintf( '<a href="%s">%s</a>', str_replace( '___', (string) $i, $link_base ), $i ); } } } // to the end if( $end_page < $max_page ){ if( $rg->dotright_text && $end_page !== ( $max_page - 1 ) ){ $els[] = '<span class="extend">' . $rg->dotright_text2 . '</span>'; } $els['last'] = sprintf( '<a class="last" href="%s">%s</a>', str_replace( '___', $max_page, $link_base ), $rg->last_page_text ?: $max_page ); } // forward if( $rg->next_text && $paged !== $end_page ){ $els['next'] = sprintf( '<a class="next" href="%s">%s</a>', str_replace( '___', ( $paged + 1 ), $link_base ), $rg->next_text ); } /** * Allow to change pagenavi elements. * * @param array $elements */ $els = apply_filters( 'kama_pagenavi_elements', $els ); $html = $rg->before . '<div class="wp-pagenavi">' . implode( '', $els ) . '</div>' . $rg->after; /** * Allow to change final output HTML code of pagenavi. * * @param string $html */ $html = apply_filters( 'kama_pagenavi', $html ); if( $rg->echo ){ echo $html; } return $html; } /** * CHANGELOG: * * 3.0 (14.12.2023) * - Requires at least PHP 7.0. * - Requires at least WP 5.9. * - PHP 8.1 support. Typehint improvements. * - Removed capability to pass $wp_query in first parameter. * 2.8 (14.02.2022) * - Minor improvements. * 2.7 (02.11.2018) * - In $args you can specify the second parameter $wp_query, when $args can be left empty. * - Code edits - fixed bugs, rebuilt the collection of elements into an array. * - New hook `kama_pagenavi_elements`. * 2.6 (20.10.2018) * - Removed extract(). * - Moved parameters $before, $after, $echo to $args (old version will work). * 2.5 - 2.5.1 * - Automatic reset of the main query. */
The settings are described directly in the code and are identical to the settings of wp-pagenavi, with the only difference being that instead of the text "to the last page", you can display the number of the last page.
After the function is installed and the CSS styles are transferred, change the wp_pagenavi code in the template to this:
<?php kama_pagenavi(); ?>
If you have something like this in your code, then you need to change all wp_pagenavi to kama_pagenavi:
if(function_exists('wp_pagenavi')) { wp_pagenavi( '<center>', '</center>' ); }
CSS styles for the code
As I mentioned earlier, the CSS classes coincide with wp-pagenavi. For convenience, I'm posting all CSS rules here:
.wp-pagenavi{ margin:2em auto; text-align:center; } .wp-pagenavi > *{ display:inline-block; padding:.0em .5em; margin:.1em; border:1px solid #93a8bc; border-radius:3px; color:#465366; } .wp-pagenavi a, .wp-pagenavi a:hover{ text-decoration:none; } .wp-pagenavi a{ background-color: #FFFFFF; } .wp-pagenavi a:hover{ border-color:#7d95ac; } .wp-pagenavi .pages{ } .wp-pagenavi .current{ border-color:#465366; color: #465366; } .wp-pagenavi .extend{ color: #465366; } .wp-pagenavi .first{ } .wp-pagenavi .last{ } .wp-pagenavi .prev{ border-color:rgba(0,0,0,0); } .wp-pagenavi .next{ border-color:rgba(0,0,0,0); }
In my code, there are 4 new classes: first (to the beginning), last (to the end), prev (back), next (forward).
A good selection of styles can be taken from here.
If navigation is displayed twice
I also want to draw the attention of those for whom navigation is displayed twice on the page (above and below the loop). To avoid performing the same operations for building navigation twice, it makes more sense to do the following: build the navigation once (use the function), then store the result in a variable, and simply output this variable the second time. It looks like this:
// where the navigation needs to be displayed for the first time // get the navigation and store it in a variable $navigation = kama_pagenavi('', '', false); // display the variable on the screen echo $navigation; /* Here goes the post output - Loop */ // where the navigation needs to be displayed for the second time // Since the navigation is already stored in the variable $navigation, it can simply be displayed on the screen. echo $navigation;
Updates
December 17, 2013
Version 2.0. Adjusted the code, removed unnecessary calls to the get_pagenum_link() function, which made the code work much faster without losing quality.
May 11, 2010
Moved the back/forward links, now like this:
"back "to the beginning ... 11 12 13 14 15 16 17 18 ... to the end" "forward"**
The latest version of the function is above.
May 2, 2010
Added back/forward links, for example:
"**to the beginning "back ... 11 12 13 14 15 16 17 18 ... "forward" to the end**"**
They can be turned off (see settings).
Removed a bug of this type:
1 ... 2 3 4 5 6 7 8 ... 50 or 1 ... 21 22 23 24 25 26 27 28 ... 29
That is, where it is not needed, the texts "before" and "after" navigation are removed (in this example, it's the ellipsis).
Reversible Pagination for WordPress
The idea of reversible (reverse) pagination belongs to sholo, who expressed it on a famous forum known to us - mywordpress.ru. I became interested in seeing how it would look, so I modified the code slightly.
This code is based on an old version of the main code...
/** * Alternative to wp_pagenavi - reversible pagination */ function kama_pagenavi( $before = '', $after = '', $echo = true ) { /* ================ Settings ================ */ $text_num_page = ''; // Text for the number of pages. {current} will be replaced with the current page, and {last} with the last. Example: 'Page {current} of {last}' = Page 4 of 60 $num_pages = 10; // how many links to show $stepLink = 10; // after navigation, links with a certain step (value = number (which step) or'', if not necessary to show). Example: 1,2,3...10,20,30 $dotright_text = '…'; // intermediate text "to". $dotright_text2 = '…'; // intermediate text "after". $backtext = '<<<'; // text "go to the previous page". Set '', if this link is not needed. $nexttext = '>>>'; // text "go to the next page". Set '', if this link is not needed. $first_page_text = '« last'; // text "to the first page" or set '', if the page number should be displayed instead of the text. $last_page_text = 'first »'; // text "to the last page" or write '', if the page number should be displayed instead of the text. /* ================ End of Settings ================ */ global $wp_query; $posts_per_page = (int) $wp_query->query_vars['posts_per_page']; $paged = (int) $wp_query->query_vars['paged']; $max_page = $wp_query->max_num_pages; if( $max_page <= 1 ){ return false; } if( empty( $paged ) || $paged == 0 ){ $paged = 1; } $pages_to_show = intval( $num_pages ); $pages_to_show_minus_1 = $pages_to_show - 1; $half_page_start = floor( $pages_to_show_minus_1 / 2 ); //how many links before the current page $half_page_end = ceil( $pages_to_show_minus_1 / 2 ); //how many links after the current page $start_page = $paged - $half_page_start; //first page $end_page = $paged + $half_page_end; //last page (conditionally) if( $start_page <= 0 ){ $start_page = 1; } if( ( $end_page - $start_page ) != $pages_to_show_minus_1 ){ $end_page = $start_page + $pages_to_show_minus_1; } if( $end_page > $max_page ){ $start_page = $max_page - $pages_to_show_minus_1; $end_page = (int) $max_page; } if( $start_page <= 0 ){ $start_page = 1; } $out = '';//display navigation $out .= $before . "<div class='wp-pagenavi'>\n"; if( $text_num_page ){ $text_num_page = preg_replace( '!{current}|{last}!', '%s', $text_num_page ); $out .= sprintf( "<span class='pages'>$text_num_page</span>", $paged, $max_page ); } if( $backtext && $paged != 1 ){ $out .= '<a href="' . get_pagenum_link( ( $paged - 1 ) ) . '">' . $backtext . '</a>'; } if( $start_page >= 2 && $pages_to_show < $max_page ){ $out .= '<a href="' . get_pagenum_link() . '">' . ( $first_page_text ? $first_page_text : $max_page ) . '</a>'; if( $dotright_text && $start_page != 2 ){ $out .= '<span class="extend">' . $dotright_text . '</span>'; } } for( $i = $start_page; $i <= $end_page; $i++ ){ if( $i == $paged ){ $out .= '<span class="current">' . ( $max_page - $i + 1 ) . '</span>'; } else{ $out .= '<a href="' . get_pagenum_link( $i ) . '">' . ( $max_page - $i + 1 ) . '</a>'; } } //links with step if( $stepLink && $end_page < $max_page ){ for( $i = $end_page + 1; $i <= $max_page; $i++ ){ if( $i % $stepLink == 0 && $i !== $num_pages ){ if( ++$dd == 1 ){ $out .= '<span class="extend">' . $dotright_text2 . '</span>'; } $out .= '<a href="' . get_pagenum_link( $i ) . '">' . ( $max_page - $i + 1 ) . '</a>'; } } } if( $end_page < $max_page ){ if( $dotright_text && $end_page != ( $max_page - 1 ) ){ $out .= '<span class="extend">' . $dotright_text2 . '</span>'; } $out .= '<a href="' . get_pagenum_link( $max_page ) . '">' . ( $last_page_text ? $last_page_text : 1 ) . '</a>'; } if( $nexttext && $paged != $end_page ){ $out .= '<a href="' . get_pagenum_link( ( $paged + 1 ) ) . '">' . $nexttext . '</a>'; } $out .= "</div>" . $after . "\n"; if( $echo ){ echo $out; } return $out; }