函数文档

paginate_links()

💡 云策文档标注

概述

paginate_links() 函数用于生成归档文章页面的分页链接,技术上可应用于任何需要分页的区域。它通过参数控制链接的格式、显示方式和返回类型,默认用于处理 WP_Query 的分页。

关键要点

  • 核心功能:生成分页链接,支持自定义 URL 基础(base)和格式(format),其中 base 需包含 %_% 占位符,format 需包含 %#% 占位符以替换页码。
  • 返回类型:通过 'type' 参数控制,可选 'plain'(默认,返回字符串)、'array'(返回数组)或 'list'(返回无序 HTML 列表)。
  • 关键参数:包括 'total'(总页数)、'current'(当前页码)、'show_all'(是否显示所有页面)、'end_size' 和 'mid_size'(控制页面显示范围)、'prev_next'(是否包含上一页/下一页链接)。
  • 辅助功能:支持通过 'add_args' 添加查询参数,'before_page_number' 和 'after_page_number' 增强链接可访问性,'aria_current' 设置 ARIA 属性。
  • 默认行为:基于当前查询自动设置 total 和 current,使用 WP_Query 的 max_num_pages 和 'paged' 查询变量。

代码示例

echo paginate_links( array(
    'base'    => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
    'format'  => '?paged=%#%',
    'current' => max( 1, get_query_var('paged') ),
    'total'   => $wp_query->max_num_pages
) );

注意事项

  • base 参数应使用完整 URL(如 https://example.com/page/%_%),否则可能导致链接错误。
  • 在 PHP 8 及以上版本中,注意类型转换,例如使用 $big = '999999999'; 以避免字符串替换失败。
  • 为提升可访问性,建议使用 'before_page_number' 和 'after_page_number' 为屏幕阅读器添加上下文。
  • 当 total 小于 2 时,函数返回 void,即无输出。

📄 原文内容

Retrieves paginated links for archive post pages.

Description

Technically, the function can be used to create paginated link list for any area. The ‘base’ argument is used to reference the url, which will be used to create the paginated links. The ‘format’ argument is then used for replacing the page number. It is however, most likely and by default, to be used on the archive post pages.

The ‘type’ argument controls format of the returned value. The default is ‘plain’, which is just a string with the links separated by a newline character. The other possible values are either ‘array’ or ‘list’. The ‘array’ value will return an array of the paginated link list to offer full control of display. The ‘list’ value will place all of the paginated links in an unordered HTML list.

The ‘total’ argument is the total amount of pages and is an integer. The ‘current’ argument is the current page number and is also an integer.

An example of the ‘base’ argument is “http://example.com/all_posts.php%_%” and the ‘%_%’ is required. The ‘%_%’ will be replaced by the contents of in the ‘format’ argument. An example for the ‘format’ argument is “?page=%#%” and the ‘%#%’ is also required. The ‘%#%’ will be replaced with the page number.

You can include the previous and next links in the list by setting the ‘prev_next’ argument to true, which it is by default. You can set the previous text, by using the ‘prev_text’ argument. You can set the next text by setting the ‘next_text’ argument.

If the ‘show_all’ argument is set to true, then it will show all of the pages instead of a short list of the pages near the current page. By default, the ‘show_all’ is set to false and controlled by the ‘end_size’ and ‘mid_size’ arguments. The ‘end_size’ argument is how many numbers on either the start and the end list edges, by default is 1. The ‘mid_size’ argument is how many numbers to either side of current page, but not including current page.

It is possible to add query vars to the link by using the ‘add_args’ argument and see add_query_arg() for more information.

The ‘before_page_number’ and ‘after_page_number’ arguments allow users to augment the links themselves. Typically this might be to add context to the numbered links so that screen reader users understand what the links are for.
The text strings are added before and after the page number – within the anchor tag.

Parameters

$argsstring|arrayoptional
Array or string of arguments for generating paginated links for archives.

  • base string
    Base of the paginated url. Default empty.
  • format string
    Format for the pagination structure. Default empty.
  • total int
    The total amount of pages. Default is the value WP_Query‘s max_num_pages or 1.
  • current int
    The current page number. Default is 'paged' query var or 1.
  • aria_current string
    The value for the aria-current attribute. Possible values are 'page', 'step', 'location', 'date', 'time', 'true', 'false'. Default is 'page'.
  • show_all bool
    Whether to show all pages. Default false.
  • end_size int
    How many numbers on either the start and the end list edges.
    Default 1.
  • mid_size int
    How many numbers to either side of the current pages. Default 2.
  • prev_next bool
    Whether to include the previous and next links in the list. Default true.
  • prev_text string
    The previous page text. Default ‘« Previous’.
  • next_text string
    The next page text. Default ‘Next »’.
  • type string
    Controls format of the returned value. Possible values are 'plain', 'array' and 'list'. Default is 'plain'.
  • add_args array
    An array of query args to add. Default false.
  • add_fragment string
    A string to append to each link. Default empty.
  • before_page_number string
    A string to appear before the page number. Default empty.
  • after_page_number string
    A string to append after the page number. Default empty.

Return

string|string[]|void String of page links or array of page links, depending on 'type' argument.
Void if total number of pages is less than 2.

Source

function paginate_links( $args = '' ) {
	global $wp_query, $wp_rewrite;

	// Setting up default values based on the current URL.
	$pagenum_link = html_entity_decode( get_pagenum_link() );
	$url_parts    = explode( '?', $pagenum_link );

	// Get max pages and current page out of the current query, if available.
	$total   = isset( $wp_query->max_num_pages ) ? $wp_query->max_num_pages : 1;
	$current = get_query_var( 'paged' ) ? (int) get_query_var( 'paged' ) : 1;

	// Append the format placeholder to the base URL.
	$pagenum_link = trailingslashit( $url_parts[0] ) . '%_%';

	// URL base depends on permalink settings.
	$format  = $wp_rewrite->using_index_permalinks() && ! strpos( $pagenum_link, 'index.php' ) ? 'index.php/' : '';
	$format .= $wp_rewrite->using_permalinks() ? user_trailingslashit( $wp_rewrite->pagination_base . '/%#%', 'paged' ) : '?paged=%#%';

	$defaults = array(
		'base'               => $pagenum_link, // http://example.com/all_posts.php%_% : %_% is replaced by format (below).
		'format'             => $format, // ?page=%#% : %#% is replaced by the page number.
		'total'              => $total,
		'current'            => $current,
		'aria_current'       => 'page',
		'show_all'           => false,
		'prev_next'          => true,
		'prev_text'          => __( '« Previous' ),
		'next_text'          => __( 'Next »' ),
		'end_size'           => 1,
		'mid_size'           => 2,
		'type'               => 'plain',
		'add_args'           => array(), // Array of query args to add.
		'add_fragment'       => '',
		'before_page_number' => '',
		'after_page_number'  => '',
	);

	$args = wp_parse_args( $args, $defaults );

	if ( ! is_array( $args['add_args'] ) ) {
		$args['add_args'] = array();
	}

	// Merge additional query vars found in the original URL into 'add_args' array.
	if ( isset( $url_parts[1] ) ) {
		// Find the format argument.
		$format       = explode( '?', str_replace( '%_%', $args['format'], $args['base'] ) );
		$format_query = isset( $format[1] ) ? $format[1] : '';
		wp_parse_str( $format_query, $format_args );

		// Find the query args of the requested URL.
		wp_parse_str( $url_parts[1], $url_query_args );

		// Remove the format argument from the array of query arguments, to avoid overwriting custom format.
		foreach ( $format_args as $format_arg => $format_arg_value ) {
			unset( $url_query_args[ $format_arg ] );
		}

		$args['add_args'] = array_merge( $args['add_args'], urlencode_deep( $url_query_args ) );
	}

	// Who knows what else people pass in $args.
	$total = (int) $args['total'];
	if ( $total < 2 ) {
		return;
	}
	$current  = (int) $args['current'];
	$end_size = (int) $args['end_size']; // Out of bounds? Make it the default.
	if ( $end_size < 1 ) {
		$end_size = 1;
	}
	$mid_size = (int) $args['mid_size'];
	if ( $mid_size < 0 ) {
		$mid_size = 2;
	}

	$add_args   = $args['add_args'];
	$r          = '';
	$page_links = array();
	$dots       = false;

	if ( $args['prev_next'] && $current && 1 < $current ) :
		$link = str_replace( '%_%', 2 === $current ? '' : $args['format'], $args['base'] );
		$link = str_replace( '%#%', $current - 1, $link );
		if ( $add_args ) {
			$link = add_query_arg( $add_args, $link );
		}
		$link .= $args['add_fragment'];

		$page_links[] = sprintf(
			'<a class="prev page-numbers" href="%s">%s</a>',
			/**
			 * Filters the paginated links for the given archive pages.
			 *
			 * @since 3.0.0
			 *
			 * @param string $link The paginated link URL.
			 */
			esc_url( apply_filters( 'paginate_links', $link ) ),
			$args['prev_text']
		);
	endif;

	for ( $n = 1; $n <= $total; $n++ ) :
		if ( $n === $current ) :
			$page_links[] = sprintf(
				'<span aria-current="%s" class="page-numbers current">%s</span>',
				esc_attr( $args['aria_current'] ),
				$args['before_page_number'] . number_format_i18n( $n ) . $args['after_page_number']
			);

			$dots = true;
		else :
			if ( $args['show_all'] || ( $n <= $end_size || ( $current && $n >= $current - $mid_size && $n <= $current + $mid_size ) || $n > $total - $end_size ) ) :
				$link = str_replace( '%_%', 1 === $n ? '' : $args['format'], $args['base'] );
				$link = str_replace( '%#%', $n, $link );
				if ( $add_args ) {
					$link = add_query_arg( $add_args, $link );
				}
				$link .= $args['add_fragment'];

				$page_links[] = sprintf(
					'<a class="page-numbers" href="%s">%s</a>',
					/** This filter is documented in wp-includes/general-template.php */
					esc_url( apply_filters( 'paginate_links', $link ) ),
					$args['before_page_number'] . number_format_i18n( $n ) . $args['after_page_number']
				);

				$dots = true;
			elseif ( $dots && ! $args['show_all'] ) :
				$page_links[] = '<span class="page-numbers dots">' . __( '…' ) . '</span>';

				$dots = false;
			endif;
		endif;
	endfor;

	if ( $args['prev_next'] && $current && $current < $total ) :
		$link = str_replace( '%_%', $args['format'], $args['base'] );
		$link = str_replace( '%#%', $current + 1, $link );
		if ( $add_args ) {
			$link = add_query_arg( $add_args, $link );
		}
		$link .= $args['add_fragment'];

		$page_links[] = sprintf(
			'<a class="next page-numbers" href="%s">%s</a>',
			/** This filter is documented in wp-includes/general-template.php */
			esc_url( apply_filters( 'paginate_links', $link ) ),
			$args['next_text']
		);
	endif;

	switch ( $args['type'] ) {
		case 'array':
			return $page_links;

		case 'list':
			$r .= "<ul class='page-numbers'>nt<li>";
			$r .= implode( "</li>nt<li>", $page_links );
			$r .= "</li>n</ul>n";
			break;

		default:
			$r = implode( "n", $page_links );
			break;
	}

	/**
	 * Filters the HTML output of paginated links for archives.
	 *
	 * @since 5.7.0
	 *
	 * @param string $r    HTML output.
	 * @param array  $args An array of arguments. See paginate_links()
	 *                     for information on accepted arguments.
	 */
	$r = apply_filters( 'paginate_links_output', $r, $args );

	return $r;
}

Hooks

apply_filters( ‘paginate_links’, string $link )

Filters the paginated links for the given archive pages.

apply_filters( ‘paginate_links_output’, string $r, array $args )

Filters the HTML output of paginated links for archives.

Changelog

Version Description
4.9.0 Added the aria_current argument.
2.1.0 Introduced.

User Contributed Notes

  1. Skip to note 8 content

    Example With a Custom Query
    When querying a loop with new WP_Query set the total parameter to the max_num_pages property of the WP_Query object.

    Example of a custom query:

     5,
    	'category_name' => 'gallery',
    	'paged' => $paged,
    );
    
    $the_query = new WP_Query( $args );
    ?>
    <!-- the loop etc.. -->

    Example of paginate_links parameters adapted to the custom query above:

     str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
    	'format' => '?paged=%#%',
    	'current' => max( 1, get_query_var('paged') ),
    	'total' => $the_query->max_num_pages
    ) );
    ?>

  2. Skip to note 9 content

    Improving Accessibility
    To add context to the numbered links to ensure that screen reader users understand what the links are for:

     str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
    	'format' => '?paged=%#%',
    	'current' => max( 1, get_query_var('paged') ),
    	'total' => $wp_query->max_num_pages,
            'before_page_number' => '<span class="screen-reader-text">'.$translated.' </span>'
    ) );
    ?>

  3. Skip to note 10 content

    You might notice that paginate_links always gives back HTML, even if you’re setting type to array, which makes it a bit hard if you want to completely customise the HTML structure of your pagination. I wrote a little function that gives you back an array of objects instead of HTML that might make this a bit easier.

    function wpdocs_get_paginated_links( $query ) {
        // When we're on page 1, 'paged' is 0, but we're counting from 1,
        // so we're using max() to get 1 instead of 0
        $currentPage = max( 1, get_query_var( 'paged', 1 ) );
    
        // This creates an array with all available page numbers, if there
        // is only *one* page, max_num_pages will return 0, so here we also
        // use the max() function to make sure we'll always get 1
        $pages = range( 1, max( 1, $query->max_num_pages ) );
    
        // Now, map over $pages and return the page number, the url to that
        // page and a boolean indicating whether that number is the current page
        return array_map( function( $page ) use ( $currentPage ) {
            return ( object ) array(
                "isCurrent" => $page == $currentPage,
                "page" => $page,
                "url" => get_pagenum_link( $page )
            );
        }, $pages );
    }

    And use it like this (given that $query is the name of your custom query):

    <ul>
        
        <li>
            isCurrent ): ?>
                <strong>page ) ?></strong>
            
                <a href="<?php esc_attr_e( $link->url ) ?>">
                    page ) ?>
                </a>
            
        </li>
        
    </ul>

  4. Skip to note 12 content

    Note that the base argument should be fully qualified, e.g. https://example.com/my-post/%_%#some-id, not just %_%#some-id.

    If the base argument doesn’t contain the absolute URL, then when you are on the second page the link to the first page will not work; instead it will link to the current page. This is because the “Previous” link is set to the base argument with the %_% part removed, and no extra ?paged=%#% part added. Your browser then interprets this to mean the current page, so the “Previous” link points to the current page.