Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions src/wp-includes/general-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -4709,11 +4709,28 @@ function paginate_links( $args = '' ) {
if ( $args['prev_next'] && $current && 1 < $current ) :
$link = str_replace( '%_%', 2 === $current ? '' : $args['format'], $args['base'] );
$link = str_replace( '%#%', $current - 1, $link );
/*
* Maybe add a trailing slash to the link according to the site's settings.
*
* Only add this for links without a query string or a fragment. Links with
* these components will have data changed if the trailing slash is added.
*
* This only affects sites with pretty permalinks, as sites without them
* enabled will include a query string parameter.
*
* For links to the base of a domain, a trailing slash is always added to
* the link as that's how browsers handle URLs.
*/
if ( in_array( wp_parse_url( $link, PHP_URL_PATH ), array( null, '/' ), true ) && ! str_contains( $link, '?' ) && ( ! str_contains( $link, '#' ) ) ) {
$link = trailingslashit( $link );
} elseif ( ! str_contains( $link, '?' ) && ( ! str_contains( $link, '#' ) ) ) {
$link = user_trailingslashit( $link, 'paged' );
}

if ( $add_args ) {
$link = add_query_arg( $add_args, $link );
}
$link .= $args['add_fragment'];
$link = get_option( 'permalink_structure' ) ? user_trailingslashit( $link, 'paged' ) : $link;

$page_links[] = sprintf(
'<a class="prev page-numbers" href="%s">%s</a>',
Expand Down Expand Up @@ -4742,11 +4759,27 @@ function paginate_links( $args = '' ) {
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 );
/*
* Maybe add a trailing slash to the link according to the site's settings.
*
* Only add this for links without a query string or a fragment. Links with
* these components will have data changed if the trailing slash is added.
*
* This only affects sites with pretty permalinks, as sites without them
* enabled will include a query string parameter.
*
* For links to the base of a domain, a trailing slash is always added to
* the link as that's how browsers handle URLs.
*/
if ( in_array( wp_parse_url( $link, PHP_URL_PATH ), array( null, '/' ), true ) && ! str_contains( $link, '?' ) && ( ! str_contains( $link, '#' ) ) ) {
$link = trailingslashit( $link );
} elseif ( ! str_contains( $link, '?' ) && ( ! str_contains( $link, '#' ) ) ) {
$link = user_trailingslashit( $link, 'paged' );
}
if ( $add_args ) {
$link = add_query_arg( $add_args, $link );
}
$link .= $args['add_fragment'];
$link = get_option( 'permalink_structure' ) ? user_trailingslashit( $link, 'paged' ) : $link;

$page_links[] = sprintf(
'<a class="page-numbers" href="%s">%s</a>',
Expand All @@ -4767,11 +4800,27 @@ function paginate_links( $args = '' ) {
if ( $args['prev_next'] && $current && $current < $total ) :
$link = str_replace( '%_%', $args['format'], $args['base'] );
$link = str_replace( '%#%', $current + 1, $link );
/*
* Maybe add a trailing slash to the link according to the site's settings.
*
* Only add this for links without a query string or a fragment. Links with
* these components will have data changed if the trailing slash is added.
*
* This only affects sites with pretty permalinks, as sites without them
* enabled will include a query string parameter.
*
* For links to the base of a domain, a trailing slash is always added to
* the link as that's how browsers handle URLs.
*/
if ( in_array( wp_parse_url( $link, PHP_URL_PATH ), array( null, '/' ), true ) && ! str_contains( $link, '?' ) && ( ! str_contains( $link, '#' ) ) ) {
$link = trailingslashit( $link );
} elseif ( ! str_contains( $link, '?' ) && ( ! str_contains( $link, '#' ) ) ) {
$link = user_trailingslashit( $link, 'paged' );
}
if ( $add_args ) {
$link = add_query_arg( $add_args, $link );
}
$link .= $args['add_fragment'];
$link = get_option( 'permalink_structure' ) ? user_trailingslashit( $link, 'paged' ) : $link;

$page_links[] = sprintf(
'<a class="next page-numbers" href="%s">%s</a>',
Expand Down
207 changes: 207 additions & 0 deletions tests/phpunit/tests/general/paginateLinks.php
Original file line number Diff line number Diff line change
Expand Up @@ -438,4 +438,211 @@ public function test_pagination_links_without_trailing_slash() {
'Previous link should not have trailing slash when permalink structure has no trailing slash'
);
}

/**
* @ticket 61393
* @ticket 63123
*
* @dataProvider data_search_terms
*
* @param string $search_term Search term - should only be the value passed to `s` parameter.
* @param string $not_expected Search term that is unexpected - should only be the value unexpected in the `s` parameter.
*/
public function test_pagination_links_does_not_modify_search_terms_with_trailing_slash_permalinks( $search_term, $not_expected ) {
$this->set_permalink_structure( '/%postname%/' );

$args = array(
'base' => 'http://example.org/%_%',
'format' => 'page/%#%',
'total' => 5,
'current' => 3,
'prev_next' => true,
'add_args' => array(
's' => $search_term,
),
);

$links = paginate_links( $args );

$this->assertStringNotContainsString(
"?s={$not_expected}\"",
$links,
'Search term should not be modified.'
);
}

/**
* @ticket 61393
* @ticket 63123
*
* @dataProvider data_search_terms
*
* @param string $search_term Search term - should only be the value passed to `s` parameter.
* @param string $not_expected Search term that is unexpected - should only be the value unexpected in the `s` parameter.
*/
public function test_pagination_links_does_not_modify_search_terms_in_base_with_trailing_slash_permalinks( $search_term, $not_expected ) {
$this->set_permalink_structure( '/%postname%/' );

$args = array(
'base' => "http://example.org/%_%/?s={$search_term}",
'format' => 'page/%#%',
'total' => 5,
'current' => 3,
'prev_next' => true,
'add_args' => array(
's' => $search_term,
),
);

$links = paginate_links( $args );

$this->assertStringNotContainsString(
"?s={$not_expected}\"",
$links,
'Search term should not be modified.'
);
}

/**
* @ticket 61393
* @ticket 63123
*
* @dataProvider data_search_terms
*
* @param string $search_term Search term - should only be the value passed to `s` parameter.
* @param string $not_expected Search term that is unexpected - should only be the value unexpected in the `s` parameter.
*/
public function test_pagination_links_does_not_modify_search_terms_without_trailing_slash_permalinks( $search_term, $not_expected ) {
$this->set_permalink_structure( '/%postname%' );

$args = array(
'base' => "http://example.org/%_%?s={$search_term}",
'format' => 'page/%#%',
'total' => 5,
'current' => 3,
'prev_next' => true,
);

$links = paginate_links( $args );

$this->assertStringNotContainsString(
"?s={$not_expected}\"",
$links,
'Search term should not be modified.'
);
}

/**
* @ticket 61393
* @ticket 63123
*
* @dataProvider data_search_terms
*
* @param string $search_term Search term - should only be the value passed to `s` parameter.
* @param string $not_expected Search term that is unexpected - should only be the value unexpected in the `s` parameter.
*/
public function test_pagination_links_does_not_modify_search_terms_in_base_without_trailing_slash_permalinks( $search_term, $not_expected ) {
$this->set_permalink_structure( '/%postname%' );

$args = array(
'base' => 'http://example.org/%_%',
'format' => 'page/%#%',
'total' => 5,
'current' => 3,
'prev_next' => true,
'add_args' => array(
's' => $search_term,
),
);

$links = paginate_links( $args );

$this->assertStringNotContainsString(
"?s={$not_expected}\"",
$links,
'Search term should not be modified.'
);
}

/**
* Data provider for test_pagination_links_does_not_modify_search_terms_* tests.
*
* @return array[] Data provider.
*/
public function data_search_terms() {
return array(
'search term without trailing slash' => array( 'search+term', 'search+term/' ),
'search term with trailing slash' => array( 'search+term/', 'search+term' ),
);
}

/**
* @ticket 61393
* @ticket 63123
*
* @dataProvider data_url_fragments
*
* @param string $url_fragment URL fragment - should only be the value following the `#`.
* @param string $not_expected URL fragment that is unexpected - should only be the value unexpected following the `#`.
*/
public function test_pagination_links_does_not_modify_url_fragments_with_trailing_slash_permalinks( $url_fragment, $not_expected ) {
$this->set_permalink_structure( '/%postname%/' );

$args = array(
'base' => "http://example.org/%_%#{$url_fragment}",
'format' => 'page/%#%',
'total' => 5,
'current' => 3,
'prev_next' => true,
);

$links = paginate_links( $args );

$this->assertStringNotContainsString(
"#{$not_expected}\"",
$links,
'URL fragments should not be modified to include a trailing slash.'
);
}

/**
* @ticket 61393
* @ticket 63123
*
* @dataProvider data_url_fragments
*
* @param string $url_fragment URL fragment - should only be the value following the `#`.
* @param string $not_expected URL fragment that is unexpected - should only be the value unexpected following the `#`.
*/
public function test_pagination_links_does_not_modify_url_fragments_without_trailing_slash_permalinks( $url_fragment, $not_expected ) {
$this->set_permalink_structure( '/%postname%' );

$args = array(
'base' => "http://example.org/%_%#{$url_fragment}",
'format' => 'page/%#%',
'total' => 5,
'current' => 3,
'prev_next' => true,
);

$links = paginate_links( $args );

$this->assertStringNotContainsString(
"#{$not_expected}\"",
$links,
'URL fragments should not be modified to include a trailing slash.'
);
}

/**
* Data provider for test_pagination_links_does_not_modify_url_fragments_* tests.
*
* @return array[] Data provider.
*/
public function data_url_fragments() {
return array(
'url fragment without trailing slash' => array( 'url-fragment', 'url-fragment/' ),
'url fragment with trailing slash' => array( 'url-fragment/', 'url-fragment' ),
);
}
}
Loading