There’s no need to escape the URL twice – get_pagenum_link() by default returns an escaped URL — when the second parameter is true, esc_url() is used; else, esc_url_raw() is used:
-
With
esc_url():http://localhost/wordpress/blog/?filter=23&orderby=oldest -
With
esc_url_raw():http://localhost/wordpress/blog/?filter=23&orderby=oldest
And the problem occurs when the base contains ?, or a query string, and that the URL is escaped (e.g. using esc_url()).
Because esc_url() converts & (ampersand) to its HTML entity, which is &, and when the base contains a query string, paginate_links() will parse the base value/URL, and when the & is escaped, anything after each & and the nearest = is treated as a query string name/key, and added to the base‘s query string:
http://localhost/wordpress/blog/?filter=23&orderby=oldest#038;orderby=oldest
In the above example, the URL is like that because paginate_links() (or more precisely, wp_parse_str() used in paginate_links()) interpreted #038;orderby as the key of oldest; i.e. #038;orderby=oldest — it should be &orderby=oldest where the key is orderby.
(But of course, the browser sees anything after the # as a URL fragment. And if you follow the link, on the next page, $_GET will not have an entry for #038;orderby; i.e. $_GET['#038;orderby'] doesn’t exist.)
So here are several ways to fix/avoid the problem:
-
Use
html_entity_decode()just as WordPress does viapaginate_links():'base' => str_replace( $big, '%#%', html_entity_decode( get_pagenum_link( $big ) ) ) -
When you call
get_pagenum_link(), set the second parameter set tofalse:'base' => str_replace( $big, '%#%', get_pagenum_link( $big, false ) ) -
Use
str_replace()to replace the&with&:'base' => str_replace( [ $big, '&' ], [ '%#%', '&' ], get_pagenum_link( $big ) )
Don’t worry about not escaping the base because paginate_links() actually escapes all the links/URLs in the returned result.
Friendly Warning: Some of the links in this answer point you to a huge HTML file / web page..