',
$args['next_text'],
$args['in_same_term'],
$args['excluded_terms'],
$args['taxonomy']
);
// Only add markup if there's somewhere to navigate to.
if ( $previous || $next ) {
$navigation = _navigation_markup( $previous . $next, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
}
return $navigation;
}
/**
* Displays the navigation to next/previous post, when applicable.
*
* @since 4.1.0
*
* @param array $args Optional. See get_the_post_navigation() for available arguments.
* Default empty array.
*/
function the_post_navigation( $args = array() ) {
echo get_the_post_navigation( $args );
}
/**
* Returns the navigation to next/previous set of posts, when applicable.
*
* @since 4.1.0
* @since 5.3.0 Added the `aria_label` parameter.
* @since 5.5.0 Added the `class` parameter.
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param array $args {
* Optional. Default posts navigation arguments. Default empty array.
*
* @type string $prev_text Anchor text to display in the previous posts link.
* Default 'Older posts'.
* @type string $next_text Anchor text to display in the next posts link.
* Default 'Newer posts'.
* @type string $screen_reader_text Screen reader text for the nav element.
* Default 'Posts navigation'.
* @type string $aria_label ARIA label text for the nav element. Default 'Posts'.
* @type string $class Custom class for the nav element. Default 'posts-navigation'.
* }
* @return string Markup for posts links.
*/
function get_the_posts_navigation( $args = array() ) {
global $wp_query;
$navigation = '';
// Don't print empty markup if there's only one page.
if ( $wp_query->max_num_pages > 1 ) {
// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
$args['aria_label'] = $args['screen_reader_text'];
}
$args = wp_parse_args(
$args,
array(
'prev_text' => __( 'Older posts' ),
'next_text' => __( 'Newer posts' ),
'screen_reader_text' => __( 'Posts navigation' ),
'aria_label' => __( 'Posts' ),
'class' => 'posts-navigation',
)
);
$next_link = get_previous_posts_link( $args['next_text'] );
$prev_link = get_next_posts_link( $args['prev_text'] );
if ( $prev_link ) {
$navigation .= '
' . $prev_link . '
';
}
if ( $next_link ) {
$navigation .= '
' . $next_link . '
';
}
$navigation = _navigation_markup( $navigation, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
}
return $navigation;
}
/**
* Displays the navigation to next/previous set of posts, when applicable.
*
* @since 4.1.0
*
* @param array $args Optional. See get_the_posts_navigation() for available arguments.
* Default empty array.
*/
function the_posts_navigation( $args = array() ) {
echo get_the_posts_navigation( $args );
}
/**
* Retrieves a paginated navigation to next/previous set of posts, when applicable.
*
* @since 4.1.0
* @since 5.3.0 Added the `aria_label` parameter.
* @since 5.5.0 Added the `class` parameter.
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param array $args {
* Optional. Default pagination arguments, see paginate_links().
*
* @type string $screen_reader_text Screen reader text for navigation element.
* Default 'Posts navigation'.
* @type string $aria_label ARIA label text for the nav element. Default 'Posts'.
* @type string $class Custom class for the nav element. Default 'pagination'.
* }
* @return string Markup for pagination links.
*/
function get_the_posts_pagination( $args = array() ) {
global $wp_query;
$navigation = '';
// Don't print empty markup if there's only one page.
if ( $wp_query->max_num_pages > 1 ) {
// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
$args['aria_label'] = $args['screen_reader_text'];
}
$args = wp_parse_args(
$args,
array(
'mid_size' => 1,
'prev_text' => _x( 'Previous', 'previous set of posts' ),
'next_text' => _x( 'Next', 'next set of posts' ),
'screen_reader_text' => __( 'Posts navigation' ),
'aria_label' => __( 'Posts' ),
'class' => 'pagination',
)
);
/**
* Filters the arguments for posts pagination links.
*
* @since 6.1.0
*
* @param array $args {
* Optional. Default pagination arguments, see paginate_links().
*
* @type string $screen_reader_text Screen reader text for navigation element.
* Default 'Posts navigation'.
* @type string $aria_label ARIA label text for the nav element. Default 'Posts'.
* @type string $class Custom class for the nav element. Default 'pagination'.
* }
*/
$args = apply_filters( 'the_posts_pagination_args', $args );
// Make sure we get a string back. Plain is the next best thing.
if ( isset( $args['type'] ) && 'array' === $args['type'] ) {
$args['type'] = 'plain';
}
// Set up paginated links.
$links = paginate_links( $args );
if ( $links ) {
$navigation = _navigation_markup( $links, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
}
}
return $navigation;
}
/**
* Displays a paginated navigation to next/previous set of posts, when applicable.
*
* @since 4.1.0
*
* @param array $args Optional. See get_the_posts_pagination() for available arguments.
* Default empty array.
*/
function the_posts_pagination( $args = array() ) {
echo get_the_posts_pagination( $args );
}
/**
* Wraps passed links in navigational markup.
*
* @since 4.1.0
* @since 5.3.0 Added the `aria_label` parameter.
* @access private
*
* @param string $links Navigational links.
* @param string $css_class Optional. Custom class for the nav element.
* Default 'posts-navigation'.
* @param string $screen_reader_text Optional. Screen reader text for the nav element.
* Default 'Posts navigation'.
* @param string $aria_label Optional. ARIA label for the nav element.
* Defaults to the value of `$screen_reader_text`.
* @return string Navigation template tag.
*/
function _navigation_markup( $links, $css_class = 'posts-navigation', $screen_reader_text = '', $aria_label = '' ) {
if ( empty( $screen_reader_text ) ) {
$screen_reader_text = /* translators: Hidden accessibility text. */ __( 'Posts navigation' );
}
if ( empty( $aria_label ) ) {
$aria_label = $screen_reader_text;
}
$template = '
';
/**
* Filters the navigation markup template.
*
* Note: The filtered template HTML must contain specifiers for the navigation
* class (%1$s), the screen-reader-text value (%2$s), placement of the navigation
* links (%3$s), and ARIA label text if screen-reader-text does not fit that (%4$s):
*
*
*
* @since 4.4.0
*
* @param string $template The default template.
* @param string $css_class The class passed by the calling function.
* @return string Navigation template.
*/
$template = apply_filters( 'navigation_markup_template', $template, $css_class );
return sprintf( $template, sanitize_html_class( $css_class ), esc_html( $screen_reader_text ), $links, esc_attr( $aria_label ) );
}
/**
* Retrieves the comments page number link.
*
* @since 2.7.0
*
* @global WP_Rewrite $wp_rewrite WordPress rewrite component.
*
* @param int $pagenum Optional. Page number. Default 1.
* @param int $max_page Optional. The maximum number of comment pages. Default 0.
* @return string The comments page number link URL.
*/
function get_comments_pagenum_link( $pagenum = 1, $max_page = 0 ) {
global $wp_rewrite;
$pagenum = (int) $pagenum;
$result = get_permalink();
if ( 'newest' === get_option( 'default_comments_page' ) ) {
if ( $pagenum != $max_page ) {
if ( $wp_rewrite->using_permalinks() ) {
$result = user_trailingslashit( trailingslashit( $result ) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged' );
} else {
$result = add_query_arg( 'cpage', $pagenum, $result );
}
}
} elseif ( $pagenum > 1 ) {
if ( $wp_rewrite->using_permalinks() ) {
$result = user_trailingslashit( trailingslashit( $result ) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged' );
} else {
$result = add_query_arg( 'cpage', $pagenum, $result );
}
}
$result .= '#comments';
/**
* Filters the comments page number link for the current request.
*
* @since 2.7.0
*
* @param string $result The comments page number link.
*/
return apply_filters( 'get_comments_pagenum_link', $result );
}
/**
* Retrieves the link to the next comments page.
*
* @since 2.7.1
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param string $label Optional. Label for link text. Default empty.
* @param int $max_page Optional. Max page. Default 0.
* @return string|void HTML-formatted link for the next page of comments.
*/
function get_next_comments_link( $label = '', $max_page = 0 ) {
global $wp_query;
if ( ! is_singular() ) {
return;
}
$page = get_query_var( 'cpage' );
if ( ! $page ) {
$page = 1;
}
$next_page = (int) $page + 1;
if ( empty( $max_page ) ) {
$max_page = $wp_query->max_num_comment_pages;
}
if ( empty( $max_page ) ) {
$max_page = get_comment_pages_count();
}
if ( $next_page > $max_page ) {
return;
}
if ( empty( $label ) ) {
$label = __( 'Newer Comments »' );
}
/**
* Filters the anchor tag attributes for the next comments page link.
*
* @since 2.7.0
*
* @param string $attributes Attributes for the anchor tag.
*/
$attr = apply_filters( 'next_comments_link_attributes', '' );
return sprintf(
'%3$s',
esc_url( get_comments_pagenum_link( $next_page, $max_page ) ),
$attr,
preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&$1', $label )
);
}
/**
* Displays the link to the next comments page.
*
* @since 2.7.0
*
* @param string $label Optional. Label for link text. Default empty.
* @param int $max_page Optional. Max page. Default 0.
*/
function next_comments_link( $label = '', $max_page = 0 ) {
echo get_next_comments_link( $label, $max_page );
}
/**
* Retrieves the link to the previous comments page.
*
* @since 2.7.1
*
* @param string $label Optional. Label for comments link text. Default empty.
* @return string|void HTML-formatted link for the previous page of comments.
*/
function get_previous_comments_link( $label = '' ) {
if ( ! is_singular() ) {
return;
}
$page = get_query_var( 'cpage' );
if ( (int) $page <= 1 ) {
return;
}
$previous_page = (int) $page - 1;
if ( empty( $label ) ) {
$label = __( '« Older Comments' );
}
/**
* Filters the anchor tag attributes for the previous comments page link.
*
* @since 2.7.0
*
* @param string $attributes Attributes for the anchor tag.
*/
$attr = apply_filters( 'previous_comments_link_attributes', '' );
return sprintf(
'%3$s',
esc_url( get_comments_pagenum_link( $previous_page ) ),
$attr,
preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&$1', $label )
);
}
/**
* Displays the link to the previous comments page.
*
* @since 2.7.0
*
* @param string $label Optional. Label for comments link text. Default empty.
*/
function previous_comments_link( $label = '' ) {
echo get_previous_comments_link( $label );
}
/**
* Displays or retrieves pagination links for the comments on the current post.
*
* @see paginate_links()
* @since 2.7.0
*
* @global WP_Rewrite $wp_rewrite WordPress rewrite component.
*
* @param string|array $args Optional args. See paginate_links(). Default empty array.
* @return void|string|array Void if 'echo' argument is true and 'type' is not an array,
* or if the query is not for an existing single post of any post type.
* Otherwise, markup for comment page links or array of comment page links,
* depending on 'type' argument.
*/
function paginate_comments_links( $args = array() ) {
global $wp_rewrite;
if ( ! is_singular() ) {
return;
}
$page = get_query_var( 'cpage' );
if ( ! $page ) {
$page = 1;
}
$max_page = get_comment_pages_count();
$defaults = array(
'base' => add_query_arg( 'cpage', '%#%' ),
'format' => '',
'total' => $max_page,
'current' => $page,
'echo' => true,
'type' => 'plain',
'add_fragment' => '#comments',
);
if ( $wp_rewrite->using_permalinks() ) {
$defaults['base'] = user_trailingslashit( trailingslashit( get_permalink() ) . $wp_rewrite->comments_pagination_base . '-%#%', 'commentpaged' );
}
$args = wp_parse_args( $args, $defaults );
$page_links = paginate_links( $args );
if ( $args['echo'] && 'array' !== $args['type'] ) {
echo $page_links;
} else {
return $page_links;
}
}
/**
* Retrieves navigation to next/previous set of comments, when applicable.
*
* @since 4.4.0
* @since 5.3.0 Added the `aria_label` parameter.
* @since 5.5.0 Added the `class` parameter.
*
* @param array $args {
* Optional. Default comments navigation arguments.
*
* @type string $prev_text Anchor text to display in the previous comments link.
* Default 'Older comments'.
* @type string $next_text Anchor text to display in the next comments link.
* Default 'Newer comments'.
* @type string $screen_reader_text Screen reader text for the nav element. Default 'Comments navigation'.
* @type string $aria_label ARIA label text for the nav element. Default 'Comments'.
* @type string $class Custom class for the nav element. Default 'comment-navigation'.
* }
* @return string Markup for comments links.
*/
function get_the_comments_navigation( $args = array() ) {
$navigation = '';
// Are there comments to navigate through?
if ( get_comment_pages_count() > 1 ) {
// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
$args['aria_label'] = $args['screen_reader_text'];
}
$args = wp_parse_args(
$args,
array(
'prev_text' => __( 'Older comments' ),
'next_text' => __( 'Newer comments' ),
'screen_reader_text' => __( 'Comments navigation' ),
'aria_label' => __( 'Comments' ),
'class' => 'comment-navigation',
)
);
$prev_link = get_previous_comments_link( $args['prev_text'] );
$next_link = get_next_comments_link( $args['next_text'] );
if ( $prev_link ) {
$navigation .= '
' . $prev_link . '
';
}
if ( $next_link ) {
$navigation .= '
' . $next_link . '
';
}
$navigation = _navigation_markup( $navigation, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
}
return $navigation;
}
/**
* Displays navigation to next/previous set of comments, when applicable.
*
* @since 4.4.0
*
* @param array $args See get_the_comments_navigation() for available arguments. Default empty array.
*/
function the_comments_navigation( $args = array() ) {
echo get_the_comments_navigation( $args );
}
/**
* Retrieves a paginated navigation to next/previous set of comments, when applicable.
*
* @since 4.4.0
* @since 5.3.0 Added the `aria_label` parameter.
* @since 5.5.0 Added the `class` parameter.
*
* @see paginate_comments_links()
*
* @param array $args {
* Optional. Default pagination arguments.
*
* @type string $screen_reader_text Screen reader text for the nav element. Default 'Comments navigation'.
* @type string $aria_label ARIA label text for the nav element. Default 'Comments'.
* @type string $class Custom class for the nav element. Default 'comments-pagination'.
* }
* @return string Markup for pagination links.
*/
function get_the_comments_pagination( $args = array() ) {
$navigation = '';
// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
$args['aria_label'] = $args['screen_reader_text'];
}
$args = wp_parse_args(
$args,
array(
'screen_reader_text' => __( 'Comments navigation' ),
'aria_label' => __( 'Comments' ),
'class' => 'comments-pagination',
)
);
$args['echo'] = false;
// Make sure we get a string back. Plain is the next best thing.
if ( isset( $args['type'] ) && 'array' === $args['type'] ) {
$args['type'] = 'plain';
}
$links = paginate_comments_links( $args );
if ( $links ) {
$navigation = _navigation_markup( $links, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
}
return $navigation;
}
/**
* Displays a paginated navigation to next/previous set of comments, when applicable.
*
* @since 4.4.0
*
* @param array $args See get_the_comments_pagination() for available arguments. Default empty array.
*/
function the_comments_pagination( $args = array() ) {
echo get_the_comments_pagination( $args );
}
/**
* Retrieves the URL for the current site where the front end is accessible.
*
* Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
* if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
* If `$scheme` is 'http' or 'https', is_ssl() is overridden.
*
* @since 3.0.0
*
* @param string $path Optional. Path relative to the home URL. Default empty.
* @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
* 'http', 'https', 'relative', 'rest', or null. Default null.
* @return string Home URL link with optional path appended.
*/
function home_url( $path = '', $scheme = null ) {
return get_home_url( null, $path, $scheme );
}
/**
* Retrieves the URL for a given site where the front end is accessible.
*
* Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
* if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
* If `$scheme` is 'http' or 'https', is_ssl() is overridden.
*
* @since 3.0.0
*
* @param int|null $blog_id Optional. Site ID. Default null (current site).
* @param string $path Optional. Path relative to the home URL. Default empty.
* @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
* 'http', 'https', 'relative', 'rest', or null. Default null.
* @return string Home URL link with optional path appended.
*/
function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
$orig_scheme = $scheme;
if ( empty( $blog_id ) || ! is_multisite() ) {
$url = get_option( 'home' );
} else {
switch_to_blog( $blog_id );
$url = get_option( 'home' );
restore_current_blog();
}
if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ), true ) ) {
if ( is_ssl() ) {
$scheme = 'https';
} else {
$scheme = parse_url( $url, PHP_URL_SCHEME );
}
}
$url = set_url_scheme( $url, $scheme );
if ( $path && is_string( $path ) ) {
$url .= '/' . ltrim( $path, '/' );
}
/**
* Filters the home URL.
*
* @since 3.0.0
*
* @param string $url The complete home URL including scheme and path.
* @param string $path Path relative to the home URL. Blank string if no path is specified.
* @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https',
* 'relative', 'rest', or null.
* @param int|null $blog_id Site ID, or null for the current site.
*/
return apply_filters( 'home_url', $url, $path, $orig_scheme, $blog_id );
}
/**
* Retrieves the URL for the current site where WordPress application files
* (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
*
* Returns the 'site_url' option with the appropriate protocol, 'https' if
* is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
* overridden.
*
* @since 3.0.0
*
* @param string $path Optional. Path relative to the site URL. Default empty.
* @param string|null $scheme Optional. Scheme to give the site URL context. See set_url_scheme().
* @return string Site URL link with optional path appended.
*/
function site_url( $path = '', $scheme = null ) {
return get_site_url( null, $path, $scheme );
}
/**
* Retrieves the URL for a given site where WordPress application files
* (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
*
* Returns the 'site_url' option with the appropriate protocol, 'https' if
* is_ssl() and 'http' otherwise. If `$scheme` is 'http' or 'https',
* `is_ssl()` is overridden.
*
* @since 3.0.0
*
* @param int|null $blog_id Optional. Site ID. Default null (current site).
* @param string $path Optional. Path relative to the site URL. Default empty.
* @param string|null $scheme Optional. Scheme to give the site URL context. Accepts
* 'http', 'https', 'login', 'login_post', 'admin', or
* 'relative'. Default null.
* @return string Site URL link with optional path appended.
*/
function get_site_url( $blog_id = null, $path = '', $scheme = null ) {
if ( empty( $blog_id ) || ! is_multisite() ) {
$url = get_option( 'siteurl' );
} else {
switch_to_blog( $blog_id );
$url = get_option( 'siteurl' );
restore_current_blog();
}
$url = set_url_scheme( $url, $scheme );
if ( $path && is_string( $path ) ) {
$url .= '/' . ltrim( $path, '/' );
}
/**
* Filters the site URL.
*
* @since 2.7.0
*
* @param string $url The complete site URL including scheme and path.
* @param string $path Path relative to the site URL. Blank string if no path is specified.
* @param string|null $scheme Scheme to give the site URL context. Accepts 'http', 'https', 'login',
* 'login_post', 'admin', 'relative' or null.
* @param int|null $blog_id Site ID, or null for the current site.
*/
return apply_filters( 'site_url', $url, $path, $scheme, $blog_id );
}
/**
* Retrieves the URL to the admin area for the current site.
*
* @since 2.6.0
*
* @param string $path Optional. Path relative to the admin URL. Default empty.
* @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
* 'http' or 'https' can be passed to force those schemes.
* @return string Admin URL link with optional path appended.
*/
function admin_url( $path = '', $scheme = 'admin' ) {
return get_admin_url( null, $path, $scheme );
}
/**
* Retrieves the URL to the admin area for a given site.
*
* @since 3.0.0
*
* @param int|null $blog_id Optional. Site ID. Default null (current site).
* @param string $path Optional. Path relative to the admin URL. Default empty.
* @param string $scheme Optional. The scheme to use. Accepts 'http' or 'https',
* to force those schemes. Default 'admin', which obeys
* force_ssl_admin() and is_ssl().
* @return string Admin URL link with optional path appended.
*/
function get_admin_url( $blog_id = null, $path = '', $scheme = 'admin' ) {
$url = get_site_url( $blog_id, 'wp-admin/', $scheme );
if ( $path && is_string( $path ) ) {
$url .= ltrim( $path, '/' );
}
/**
* Filters the admin area URL.
*
* @since 2.8.0
* @since 5.8.0 The `$scheme` parameter was added.
*
* @param string $url The complete admin area URL including scheme and path.
* @param string $path Path relative to the admin area URL. Blank string if no path is specified.
* @param int|null $blog_id Site ID, or null for the current site.
* @param string|null $scheme The scheme to use. Accepts 'http', 'https',
* 'admin', or null. Default 'admin', which obeys force_ssl_admin() and is_ssl().
*/
return apply_filters( 'admin_url', $url, $path, $blog_id, $scheme );
}
/**
* Retrieves the URL to the includes directory.
*
* @since 2.6.0
*
* @param string $path Optional. Path relative to the includes URL. Default empty.
* @param string|null $scheme Optional. Scheme to give the includes URL context. Accepts
* 'http', 'https', or 'relative'. Default null.
* @return string Includes URL link with optional path appended.
*/
function includes_url( $path = '', $scheme = null ) {
$url = site_url( '/' . WPINC . '/', $scheme );
if ( $path && is_string( $path ) ) {
$url .= ltrim( $path, '/' );
}
/**
* Filters the URL to the includes directory.
*
* @since 2.8.0
* @since 5.8.0 The `$scheme` parameter was added.
*
* @param string $url The complete URL to the includes directory including scheme and path.
* @param string $path Path relative to the URL to the wp-includes directory. Blank string
* if no path is specified.
* @param string|null $scheme Scheme to give the includes URL context. Accepts
* 'http', 'https', 'relative', or null. Default null.
*/
return apply_filters( 'includes_url', $url, $path, $scheme );
}
/**
* Retrieves the URL to the content directory.
*
* @since 2.6.0
*
* @param string $path Optional. Path relative to the content URL. Default empty.
* @return string Content URL link with optional path appended.
*/
function content_url( $path = '' ) {
$url = set_url_scheme( WP_CONTENT_URL );
if ( $path && is_string( $path ) ) {
$url .= '/' . ltrim( $path, '/' );
}
/**
* Filters the URL to the content directory.
*
* @since 2.8.0
*
* @param string $url The complete URL to the content directory including scheme and path.
* @param string $path Path relative to the URL to the content directory. Blank string
* if no path is specified.
*/
return apply_filters( 'content_url', $url, $path );
}
/**
* Retrieves a URL within the plugins or mu-plugins directory.
*
* Defaults to the plugins directory URL if no arguments are supplied.
*
* @since 2.6.0
*
* @param string $path Optional. Extra path appended to the end of the URL, including
* the relative directory if $plugin is supplied. Default empty.
* @param string $plugin Optional. A full path to a file inside a plugin or mu-plugin.
* The URL will be relative to its directory. Default empty.
* Typically this is done by passing `__FILE__` as the argument.
* @return string Plugins URL link with optional paths appended.
*/
function plugins_url( $path = '', $plugin = '' ) {
$path = wp_normalize_path( $path );
$plugin = wp_normalize_path( $plugin );
$mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );
if ( ! empty( $plugin ) && str_starts_with( $plugin, $mu_plugin_dir ) ) {
$url = WPMU_PLUGIN_URL;
} else {
$url = WP_PLUGIN_URL;
}
$url = set_url_scheme( $url );
if ( ! empty( $plugin ) && is_string( $plugin ) ) {
$folder = dirname( plugin_basename( $plugin ) );
if ( '.' !== $folder ) {
$url .= '/' . ltrim( $folder, '/' );
}
}
if ( $path && is_string( $path ) ) {
$url .= '/' . ltrim( $path, '/' );
}
/**
* Filters the URL to the plugins directory.
*
* @since 2.8.0
*
* @param string $url The complete URL to the plugins directory including scheme and path.
* @param string $path Path relative to the URL to the plugins directory. Blank string
* if no path is specified.
* @param string $plugin The plugin file path to be relative to. Blank string if no plugin
* is specified.
*/
return apply_filters( 'plugins_url', $url, $path, $plugin );
}
/**
* Retrieves the site URL for the current network.
*
* Returns the site URL with the appropriate protocol, 'https' if
* is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
* overridden.
*
* @since 3.0.0
*
* @see set_url_scheme()
*
* @param string $path Optional. Path relative to the site URL. Default empty.
* @param string|null $scheme Optional. Scheme to give the site URL context. Accepts
* 'http', 'https', or 'relative'. Default null.
* @return string Site URL link with optional path appended.
*/
function network_site_url( $path = '', $scheme = null ) {
if ( ! is_multisite() ) {
return site_url( $path, $scheme );
}
$current_network = get_network();
if ( 'relative' === $scheme ) {
$url = $current_network->path;
} else {
$url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
}
if ( $path && is_string( $path ) ) {
$url .= ltrim( $path, '/' );
}
/**
* Filters the network site URL.
*
* @since 3.0.0
*
* @param string $url The complete network site URL including scheme and path.
* @param string $path Path relative to the network site URL. Blank string if
* no path is specified.
* @param string|null $scheme Scheme to give the URL context. Accepts 'http', 'https',
* 'relative' or null.
*/
return apply_filters( 'network_site_url', $url, $path, $scheme );
}
/**
* Retrieves the home URL for the current network.
*
* Returns the home URL with the appropriate protocol, 'https' is_ssl()
* and 'http' otherwise. If `$scheme` is 'http' or 'https', `is_ssl()` is
* overridden.
*
* @since 3.0.0
*
* @param string $path Optional. Path relative to the home URL. Default empty.
* @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
* 'http', 'https', or 'relative'. Default null.
* @return string Home URL link with optional path appended.
*/
function network_home_url( $path = '', $scheme = null ) {
if ( ! is_multisite() ) {
return home_url( $path, $scheme );
}
$current_network = get_network();
$orig_scheme = $scheme;
if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ), true ) ) {
$scheme = is_ssl() ? 'https' : 'http';
}
if ( 'relative' === $scheme ) {
$url = $current_network->path;
} else {
$url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
}
if ( $path && is_string( $path ) ) {
$url .= ltrim( $path, '/' );
}
/**
* Filters the network home URL.
*
* @since 3.0.0
*
* @param string $url The complete network home URL including scheme and path.
* @param string $path Path relative to the network home URL. Blank string
* if no path is specified.
* @param string|null $orig_scheme Scheme to give the URL context. Accepts 'http', 'https',
* 'relative' or null.
*/
return apply_filters( 'network_home_url', $url, $path, $orig_scheme );
}
/**
* Retrieves the URL to the admin area for the network.
*
* @since 3.0.0
*
* @param string $path Optional path relative to the admin URL. Default empty.
* @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
* and is_ssl(). 'http' or 'https' can be passed to force those schemes.
* @return string Admin URL link with optional path appended.
*/
function network_admin_url( $path = '', $scheme = 'admin' ) {
if ( ! is_multisite() ) {
return admin_url( $path, $scheme );
}
$url = network_site_url( 'wp-admin/network/', $scheme );
if ( $path && is_string( $path ) ) {
$url .= ltrim( $path, '/' );
}
/**
* Filters the network admin URL.
*
* @since 3.0.0
* @since 5.8.0 The `$scheme` parameter was added.
*
* @param string $url The complete network admin URL including scheme and path.
* @param string $path Path relative to the network admin URL. Blank string if
* no path is specified.
* @param string|null $scheme The scheme to use. Accepts 'http', 'https',
* 'admin', or null. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
*/
return apply_filters( 'network_admin_url', $url, $path, $scheme );
}
/**
* Retrieves the URL to the admin area for the current user.
*
* @since 3.0.0
*
* @param string $path Optional. Path relative to the admin URL. Default empty.
* @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
* and is_ssl(). 'http' or 'https' can be passed to force those schemes.
* @return string Admin URL link with optional path appended.
*/
function user_admin_url( $path = '', $scheme = 'admin' ) {
$url = network_site_url( 'wp-admin/user/', $scheme );
if ( $path && is_string( $path ) ) {
$url .= ltrim( $path, '/' );
}
/**
* Filters the user admin URL for the current user.
*
* @since 3.1.0
* @since 5.8.0 The `$scheme` parameter was added.
*
* @param string $url The complete URL including scheme and path.
* @param string $path Path relative to the URL. Blank string if
* no path is specified.
* @param string|null $scheme The scheme to use. Accepts 'http', 'https',
* 'admin', or null. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
*/
return apply_filters( 'user_admin_url', $url, $path, $scheme );
}
/**
* Retrieves the URL to the admin area for either the current site or the network depending on context.
*
* @since 3.1.0
*
* @param string $path Optional. Path relative to the admin URL. Default empty.
* @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
* and is_ssl(). 'http' or 'https' can be passed to force those schemes.
* @return string Admin URL link with optional path appended.
*/
function self_admin_url( $path = '', $scheme = 'admin' ) {
if ( is_network_admin() ) {
$url = network_admin_url( $path, $scheme );
} elseif ( is_user_admin() ) {
$url = user_admin_url( $path, $scheme );
} else {
$url = admin_url( $path, $scheme );
}
/**
* Filters the admin URL for the current site or network depending on context.
*
* @since 4.9.0
*
* @param string $url The complete URL including scheme and path.
* @param string $path Path relative to the URL. Blank string if no path is specified.
* @param string $scheme The scheme to use.
*/
return apply_filters( 'self_admin_url', $url, $path, $scheme );
}
/**
* Sets the scheme for a URL.
*
* @since 3.4.0
* @since 4.4.0 The 'rest' scheme was added.
*
* @param string $url Absolute URL that includes a scheme
* @param string|null $scheme Optional. Scheme to give $url. Currently 'http', 'https', 'login',
* 'login_post', 'admin', 'relative', 'rest', 'rpc', or null. Default null.
* @return string URL with chosen scheme.
*/
function set_url_scheme( $url, $scheme = null ) {
$orig_scheme = $scheme;
if ( ! $scheme ) {
$scheme = is_ssl() ? 'https' : 'http';
} elseif ( 'admin' === $scheme || 'login' === $scheme || 'login_post' === $scheme || 'rpc' === $scheme ) {
$scheme = is_ssl() || force_ssl_admin() ? 'https' : 'http';
} elseif ( 'http' !== $scheme && 'https' !== $scheme && 'relative' !== $scheme ) {
$scheme = is_ssl() ? 'https' : 'http';
}
$url = trim( $url );
if ( str_starts_with( $url, '//' ) ) {
$url = 'http:' . $url;
}
if ( 'relative' === $scheme ) {
$url = ltrim( preg_replace( '#^\w+://[^/]*#', '', $url ) );
if ( '' !== $url && '/' === $url[0] ) {
$url = '/' . ltrim( $url, "/ \t\n\r\0\x0B" );
}
} else {
$url = preg_replace( '#^\w+://#', $scheme . '://', $url );
}
/**
* Filters the resulting URL after setting the scheme.
*
* @since 3.4.0
*
* @param string $url The complete URL including scheme and path.
* @param string $scheme Scheme applied to the URL. One of 'http', 'https', or 'relative'.
* @param string|null $orig_scheme Scheme requested for the URL. One of 'http', 'https', 'login',
* 'login_post', 'admin', 'relative', 'rest', 'rpc', or null.
*/
return apply_filters( 'set_url_scheme', $url, $scheme, $orig_scheme );
}
/**
* Retrieves the URL to the user's dashboard.
*
* If a user does not belong to any site, the global user dashboard is used. If the user
* belongs to the current site, the dashboard for the current site is returned. If the user
* cannot edit the current site, the dashboard to the user's primary site is returned.
*
* @since 3.1.0
*
* @param int $user_id Optional. User ID. Defaults to current user.
* @param string $path Optional path relative to the dashboard. Use only paths known to
* both site and user admins. Default empty.
* @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin()
* and is_ssl(). 'http' or 'https' can be passed to force those schemes.
* @return string Dashboard URL link with optional path appended.
*/
function get_dashboard_url( $user_id = 0, $path = '', $scheme = 'admin' ) {
$user_id = $user_id ? (int) $user_id : get_current_user_id();
$blogs = get_blogs_of_user( $user_id );
if ( is_multisite() && ! user_can( $user_id, 'manage_network' ) && empty( $blogs ) ) {
$url = user_admin_url( $path, $scheme );
} elseif ( ! is_multisite() ) {
$url = admin_url( $path, $scheme );
} else {
$current_blog = get_current_blog_id();
if ( $current_blog && ( user_can( $user_id, 'manage_network' ) || in_array( $current_blog, array_keys( $blogs ), true ) ) ) {
$url = admin_url( $path, $scheme );
} else {
$active = get_active_blog_for_user( $user_id );
if ( $active ) {
$url = get_admin_url( $active->blog_id, $path, $scheme );
} else {
$url = user_admin_url( $path, $scheme );
}
}
}
/**
* Filters the dashboard URL for a user.
*
* @since 3.1.0
*
* @param string $url The complete URL including scheme and path.
* @param int $user_id The user ID.
* @param string $path Path relative to the URL. Blank string if no path is specified.
* @param string $scheme Scheme to give the URL context. Accepts 'http', 'https', 'login',
* 'login_post', 'admin', 'relative' or null.
*/
return apply_filters( 'user_dashboard_url', $url, $user_id, $path, $scheme );
}
/**
* Retrieves the URL to the user's profile editor.
*
* @since 3.1.0
*
* @param int $user_id Optional. User ID. Defaults to current user.
* @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
* and is_ssl(). 'http' or 'https' can be passed to force those schemes.
* @return string Dashboard URL link with optional path appended.
*/
function get_edit_profile_url( $user_id = 0, $scheme = 'admin' ) {
$user_id = $user_id ? (int) $user_id : get_current_user_id();
if ( is_user_admin() ) {
$url = user_admin_url( 'profile.php', $scheme );
} elseif ( is_network_admin() ) {
$url = network_admin_url( 'profile.php', $scheme );
} else {
$url = get_dashboard_url( $user_id, 'profile.php', $scheme );
}
/**
* Filters the URL for a user's profile editor.
*
* @since 3.1.0
*
* @param string $url The complete URL including scheme and path.
* @param int $user_id The user ID.
* @param string $scheme Scheme to give the URL context. Accepts 'http', 'https', 'login',
* 'login_post', 'admin', 'relative' or null.
*/
return apply_filters( 'edit_profile_url', $url, $user_id, $scheme );
}
/**
* Returns the canonical URL for a post.
*
* When the post is the same as the current requested page the function will handle the
* pagination arguments too.
*
* @since 4.6.0
*
* @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`.
* @return string|false The canonical URL. False if the post does not exist
* or has not been published yet.
*/
function wp_get_canonical_url( $post = null ) {
$post = get_post( $post );
if ( ! $post ) {
return false;
}
if ( 'publish' !== $post->post_status ) {
return false;
}
$canonical_url = get_permalink( $post );
// If a canonical is being generated for the current page, make sure it has pagination if needed.
if ( get_queried_object_id() === $post->ID ) {
$page = get_query_var( 'page', 0 );
if ( $page >= 2 ) {
if ( ! get_option( 'permalink_structure' ) ) {
$canonical_url = add_query_arg( 'page', $page, $canonical_url );
} else {
$canonical_url = trailingslashit( $canonical_url ) . user_trailingslashit( $page, 'single_paged' );
}
}
$cpage = get_query_var( 'cpage', 0 );
if ( $cpage ) {
$canonical_url = get_comments_pagenum_link( $cpage );
}
}
/**
* Filters the canonical URL for a post.
*
* @since 4.6.0
*
* @param string $canonical_url The post's canonical URL.
* @param WP_Post $post Post object.
*/
return apply_filters( 'get_canonical_url', $canonical_url, $post );
}
/**
* Outputs rel=canonical for singular queries.
*
* @since 2.9.0
* @since 4.6.0 Adjusted to use `wp_get_canonical_url()`.
*/
function rel_canonical() {
if ( ! is_singular() ) {
return;
}
$id = get_queried_object_id();
if ( 0 === $id ) {
return;
}
$url = wp_get_canonical_url( $id );
if ( ! empty( $url ) ) {
echo '' . "\n";
}
}
/**
* Returns a shortlink for a post, page, attachment, or site.
*
* This function exists to provide a shortlink tag that all themes and plugins can target.
* A plugin must hook in to provide the actual shortlinks. Default shortlink support is
* limited to providing ?p= style links for posts. Plugins can short-circuit this function
* via the {@see 'pre_get_shortlink'} filter or filter the output via the {@see 'get_shortlink'}
* filter.
*
* @since 3.0.0
*
* @param int $id Optional. A post or site ID. Default is 0, which means the current post or site.
* @param string $context Optional. Whether the ID is a 'site' ID, 'post' ID, or 'media' ID. If 'post',
* the post_type of the post is consulted. If 'query', the current query is consulted
* to determine the ID and context. Default 'post'.
* @param bool $allow_slugs Optional. Whether to allow post slugs in the shortlink. It is up to the plugin how
* and whether to honor this. Default true.
* @return string A shortlink or an empty string if no shortlink exists for the requested resource or if shortlinks
* are not enabled.
*/
function wp_get_shortlink( $id = 0, $context = 'post', $allow_slugs = true ) {
/**
* Filters whether to preempt generating a shortlink for the given post.
*
* Returning a value other than false from the filter will short-circuit
* the shortlink generation process, returning that value instead.
*
* @since 3.0.0
*
* @param false|string $return Short-circuit return value. Either false or a URL string.
* @param int $id Post ID, or 0 for the current post.
* @param string $context The context for the link. One of 'post' or 'query',
* @param bool $allow_slugs Whether to allow post slugs in the shortlink.
*/
$shortlink = apply_filters( 'pre_get_shortlink', false, $id, $context, $allow_slugs );
if ( false !== $shortlink ) {
return $shortlink;
}
$post_id = 0;
if ( 'query' === $context && is_singular() ) {
$post_id = get_queried_object_id();
$post = get_post( $post_id );
} elseif ( 'post' === $context ) {
$post = get_post( $id );
if ( ! empty( $post->ID ) ) {
$post_id = $post->ID;
}
}
$shortlink = '';
// Return `?p=` link for all public post types.
if ( ! empty( $post_id ) ) {
$post_type = get_post_type_object( $post->post_type );
if ( 'page' === $post->post_type && get_option( 'page_on_front' ) == $post->ID && 'page' === get_option( 'show_on_front' ) ) {
$shortlink = home_url( '/' );
} elseif ( $post_type && $post_type->public ) {
$shortlink = home_url( '?p=' . $post_id );
}
}
/**
* Filters the shortlink for a post.
*
* @since 3.0.0
*
* @param string $shortlink Shortlink URL.
* @param int $id Post ID, or 0 for the current post.
* @param string $context The context for the link. One of 'post' or 'query',
* @param bool $allow_slugs Whether to allow post slugs in the shortlink. Not used by default.
*/
return apply_filters( 'get_shortlink', $shortlink, $id, $context, $allow_slugs );
}
/**
* Injects rel=shortlink into the head if a shortlink is defined for the current page.
*
* Attached to the {@see 'wp_head'} action.
*
* @since 3.0.0
*/
function wp_shortlink_wp_head() {
$shortlink = wp_get_shortlink( 0, 'query' );
if ( empty( $shortlink ) ) {
return;
}
echo "\n";
}
/**
* Sends a Link: rel=shortlink header if a shortlink is defined for the current page.
*
* Attached to the {@see 'wp'} action.
*
* @since 3.0.0
*/
function wp_shortlink_header() {
if ( headers_sent() ) {
return;
}
$shortlink = wp_get_shortlink( 0, 'query' );
if ( empty( $shortlink ) ) {
return;
}
header( 'Link: <' . $shortlink . '>; rel=shortlink', false );
}
/**
* Displays the shortlink for a post.
*
* Must be called from inside "The Loop"
*
* Call like the_shortlink( __( 'Shortlinkage FTW' ) )
*
* @since 3.0.0
*
* @param string $text Optional The link text or HTML to be displayed. Defaults to 'This is the short link.'
* @param string $title Optional The tooltip for the link. Must be sanitized. Defaults to the sanitized post title.
* @param string $before Optional HTML to display before the link. Default empty.
* @param string $after Optional HTML to display after the link. Default empty.
*/
function the_shortlink( $text = '', $title = '', $before = '', $after = '' ) {
$post = get_post();
if ( empty( $text ) ) {
$text = __( 'This is the short link.' );
}
if ( empty( $title ) ) {
$title = the_title_attribute( array( 'echo' => false ) );
}
$shortlink = wp_get_shortlink( $post->ID );
if ( ! empty( $shortlink ) ) {
$link = '' . $text . '';
/**
* Filters the short link anchor tag for a post.
*
* @since 3.0.0
*
* @param string $link Shortlink anchor tag.
* @param string $shortlink Shortlink URL.
* @param string $text Shortlink's text.
* @param string $title Shortlink's title attribute.
*/
$link = apply_filters( 'the_shortlink', $link, $shortlink, $text, $title );
echo $before, $link, $after;
}
}
/**
* Retrieves the avatar URL.
*
* @since 4.2.0
*
* @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
* user email, WP_User object, WP_Post object, or WP_Comment object.
* @param array $args {
* Optional. Arguments to use instead of the default arguments.
*
* @type int $size Height and width of the avatar in pixels. Default 96.
* @type string $default URL for the default image or a default type. Accepts '404' (return
* a 404 instead of a default image), 'retro' (8bit), 'RoboHash' (robohash),
* 'monsterid' (monster), 'wavatar' (cartoon face), 'indenticon' (the "quilt"),
* 'mystery', 'mm', or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF),
* or 'gravatar_default' (the Gravatar logo). Default is the value of the
* 'avatar_default' option, with a fallback of 'mystery'.
* @type bool $force_default Whether to always show the default image, never the Gravatar. Default false.
* @type string $rating What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are
* judged in that order. Default is the value of the 'avatar_rating' option.
* @type string $scheme URL scheme to use. See set_url_scheme() for accepted values.
* Default null.
* @type array $processed_args When the function returns, the value will be the processed/sanitized $args
* plus a "found_avatar" guess. Pass as a reference. Default null.
* }
* @return string|false The URL of the avatar on success, false on failure.
*/
function get_avatar_url( $id_or_email, $args = null ) {
$args = get_avatar_data( $id_or_email, $args );
return $args['url'];
}
/**
* Check if this comment type allows avatars to be retrieved.
*
* @since 5.1.0
*
* @param string $comment_type Comment type to check.
* @return bool Whether the comment type is allowed for retrieving avatars.
*/
function is_avatar_comment_type( $comment_type ) {
/**
* Filters the list of allowed comment types for retrieving avatars.
*
* @since 3.0.0
*
* @param array $types An array of content types. Default only contains 'comment'.
*/
$allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment' ) );
return in_array( $comment_type, (array) $allowed_comment_types, true );
}
/**
* Retrieves default data about the avatar.
*
* @since 4.2.0
*
* @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash,
* user email, WP_User object, WP_Post object, or WP_Comment object.
* @param array $args {
* Optional. Arguments to use instead of the default arguments.
*
* @type int $size Height and width of the avatar image file in pixels. Default 96.
* @type int $height Display height of the avatar in pixels. Defaults to $size.
* @type int $width Display width of the avatar in pixels. Defaults to $size.
* @type string $default URL for the default image or a default type. Accepts '404' (return
* a 404 instead of a default image), 'retro' (8bit), 'monsterid' (monster),
* 'wavatar' (cartoon face), 'indenticon' (the "quilt"), 'mystery', 'mm',
* or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), or
* 'gravatar_default' (the Gravatar logo). Default is the value of the
* 'avatar_default' option, with a fallback of 'mystery'.
* @type bool $force_default Whether to always show the default image, never the Gravatar. Default false.
* @type string $rating What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are
* judged in that order. Default is the value of the 'avatar_rating' option.
* @type string $scheme URL scheme to use. See set_url_scheme() for accepted values.
* Default null.
* @type array $processed_args When the function returns, the value will be the processed/sanitized $args
* plus a "found_avatar" guess. Pass as a reference. Default null.
* @type string $extra_attr HTML attributes to insert in the IMG element. Is not sanitized. Default empty.
* }
* @return array {
* Along with the arguments passed in `$args`, this will contain a couple of extra arguments.
*
* @type bool $found_avatar True if an avatar was found for this user,
* false or not set if none was found.
* @type string|false $url The URL of the avatar that was found, or false.
* }
*/
function get_avatar_data( $id_or_email, $args = null ) {
$args = wp_parse_args(
$args,
array(
'size' => 96,
'height' => null,
'width' => null,
'default' => get_option( 'avatar_default', 'mystery' ),
'force_default' => false,
'rating' => get_option( 'avatar_rating' ),
'scheme' => null,
'processed_args' => null, // If used, should be a reference.
'extra_attr' => '',
)
);
if ( is_numeric( $args['size'] ) ) {
$args['size'] = absint( $args['size'] );
if ( ! $args['size'] ) {
$args['size'] = 96;
}
} else {
$args['size'] = 96;
}
if ( is_numeric( $args['height'] ) ) {
$args['height'] = absint( $args['height'] );
if ( ! $args['height'] ) {
$args['height'] = $args['size'];
}
} else {
$args['height'] = $args['size'];
}
if ( is_numeric( $args['width'] ) ) {
$args['width'] = absint( $args['width'] );
if ( ! $args['width'] ) {
$args['width'] = $args['size'];
}
} else {
$args['width'] = $args['size'];
}
if ( empty( $args['default'] ) ) {
$args['default'] = get_option( 'avatar_default', 'mystery' );
}
switch ( $args['default'] ) {
case 'mm':
case 'mystery':
case 'mysteryman':
$args['default'] = 'mm';
break;
case 'gravatar_default':
$args['default'] = false;
break;
}
$args['force_default'] = (bool) $args['force_default'];
$args['rating'] = strtolower( $args['rating'] );
$args['found_avatar'] = false;
/**
* Filters whether to retrieve the avatar URL early.
*
* Passing a non-null value in the 'url' member of the return array will
* effectively short circuit get_avatar_data(), passing the value through
* the {@see 'get_avatar_data'} filter and returning early.
*
* @since 4.2.0
*
* @param array $args Arguments passed to get_avatar_data(), after processing.
* @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash,
* user email, WP_User object, WP_Post object, or WP_Comment object.
*/
$args = apply_filters( 'pre_get_avatar_data', $args, $id_or_email );
if ( isset( $args['url'] ) ) {
/** This filter is documented in wp-includes/link-template.php */
return apply_filters( 'get_avatar_data', $args, $id_or_email );
}
$email_hash = '';
$user = false;
$email = false;
if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) {
$id_or_email = get_comment( $id_or_email );
}
// Process the user identifier.
if ( is_numeric( $id_or_email ) ) {
$user = get_user_by( 'id', absint( $id_or_email ) );
} elseif ( is_string( $id_or_email ) ) {
if ( str_contains( $id_or_email, '@md5.gravatar.com' ) ) {
// MD5 hash.
list( $email_hash ) = explode( '@', $id_or_email );
} else {
// Email address.
$email = $id_or_email;
}
} elseif ( $id_or_email instanceof WP_User ) {
// User object.
$user = $id_or_email;
} elseif ( $id_or_email instanceof WP_Post ) {
// Post object.
$user = get_user_by( 'id', (int) $id_or_email->post_author );
} elseif ( $id_or_email instanceof WP_Comment ) {
if ( ! is_avatar_comment_type( get_comment_type( $id_or_email ) ) ) {
$args['url'] = false;
/** This filter is documented in wp-includes/link-template.php */
return apply_filters( 'get_avatar_data', $args, $id_or_email );
}
if ( ! empty( $id_or_email->user_id ) ) {
$user = get_user_by( 'id', (int) $id_or_email->user_id );
}
if ( ( ! $user || is_wp_error( $user ) ) && ! empty( $id_or_email->comment_author_email ) ) {
$email = $id_or_email->comment_author_email;
}
}
if ( ! $email_hash ) {
if ( $user ) {
$email = $user->user_email;
}
if ( $email ) {
$email_hash = md5( strtolower( trim( $email ) ) );
}
}
if ( $email_hash ) {
$args['found_avatar'] = true;
$gravatar_server = hexdec( $email_hash[0] ) % 3;
} else {
$gravatar_server = rand( 0, 2 );
}
$url_args = array(
's' => $args['size'],
'd' => $args['default'],
'f' => $args['force_default'] ? 'y' : false,
'r' => $args['rating'],
);
if ( is_ssl() ) {
$url = 'https://secure.gravatar.com/avatar/' . $email_hash;
} else {
$url = sprintf( 'http://%d.gravatar.com/avatar/%s', $gravatar_server, $email_hash );
}
$url = add_query_arg(
rawurlencode_deep( array_filter( $url_args ) ),
set_url_scheme( $url, $args['scheme'] )
);
/**
* Filters the avatar URL.
*
* @since 4.2.0
*
* @param string $url The URL of the avatar.
* @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash,
* user email, WP_User object, WP_Post object, or WP_Comment object.
* @param array $args Arguments passed to get_avatar_data(), after processing.
*/
$args['url'] = apply_filters( 'get_avatar_url', $url, $id_or_email, $args );
/**
* Filters the avatar data.
*
* @since 4.2.0
*
* @param array $args Arguments passed to get_avatar_data(), after processing.
* @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash,
* user email, WP_User object, WP_Post object, or WP_Comment object.
*/
return apply_filters( 'get_avatar_data', $args, $id_or_email );
}
/**
* Retrieves the URL of a file in the theme.
*
* Searches in the stylesheet directory before the template directory so themes
* which inherit from a parent theme can just override one file.
*
* @since 4.7.0
*
* @param string $file Optional. File to search for in the stylesheet directory.
* @return string The URL of the file.
*/
function get_theme_file_uri( $file = '' ) {
$file = ltrim( $file, '/' );
if ( empty( $file ) ) {
$url = get_stylesheet_directory_uri();
} elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
$url = get_stylesheet_directory_uri() . '/' . $file;
} else {
$url = get_template_directory_uri() . '/' . $file;
}
/**
* Filters the URL to a file in the theme.
*
* @since 4.7.0
*
* @param string $url The file URL.
* @param string $file The requested file to search for.
*/
return apply_filters( 'theme_file_uri', $url, $file );
}
/**
* Retrieves the URL of a file in the parent theme.
*
* @since 4.7.0
*
* @param string $file Optional. File to return the URL for in the template directory.
* @return string The URL of the file.
*/
function get_parent_theme_file_uri( $file = '' ) {
$file = ltrim( $file, '/' );
if ( empty( $file ) ) {
$url = get_template_directory_uri();
} else {
$url = get_template_directory_uri() . '/' . $file;
}
/**
* Filters the URL to a file in the parent theme.
*
* @since 4.7.0
*
* @param string $url The file URL.
* @param string $file The requested file to search for.
*/
return apply_filters( 'parent_theme_file_uri', $url, $file );
}
/**
* Retrieves the path of a file in the theme.
*
* Searches in the stylesheet directory before the template directory so themes
* which inherit from a parent theme can just override one file.
*
* @since 4.7.0
*
* @param string $file Optional. File to search for in the stylesheet directory.
* @return string The path of the file.
*/
function get_theme_file_path( $file = '' ) {
$file = ltrim( $file, '/' );
if ( empty( $file ) ) {
$path = get_stylesheet_directory();
} elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
$path = get_stylesheet_directory() . '/' . $file;
} else {
$path = get_template_directory() . '/' . $file;
}
/**
* Filters the path to a file in the theme.
*
* @since 4.7.0
*
* @param string $path The file path.
* @param string $file The requested file to search for.
*/
return apply_filters( 'theme_file_path', $path, $file );
}
/**
* Retrieves the path of a file in the parent theme.
*
* @since 4.7.0
*
* @param string $file Optional. File to return the path for in the template directory.
* @return string The path of the file.
*/
function get_parent_theme_file_path( $file = '' ) {
$file = ltrim( $file, '/' );
if ( empty( $file ) ) {
$path = get_template_directory();
} else {
$path = get_template_directory() . '/' . $file;
}
/**
* Filters the path to a file in the parent theme.
*
* @since 4.7.0
*
* @param string $path The file path.
* @param string $file The requested file to search for.
*/
return apply_filters( 'parent_theme_file_path', $path, $file );
}
/**
* Retrieves the URL to the privacy policy page.
*
* @since 4.9.6
*
* @return string The URL to the privacy policy page. Empty string if it doesn't exist.
*/
function get_privacy_policy_url() {
$url = '';
$policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
if ( ! empty( $policy_page_id ) && get_post_status( $policy_page_id ) === 'publish' ) {
$url = (string) get_permalink( $policy_page_id );
}
/**
* Filters the URL of the privacy policy page.
*
* @since 4.9.6
*
* @param string $url The URL to the privacy policy page. Empty string
* if it doesn't exist.
* @param int $policy_page_id The ID of privacy policy page.
*/
return apply_filters( 'privacy_policy_url', $url, $policy_page_id );
}
/**
* Displays the privacy policy link with formatting, when applicable.
*
* @since 4.9.6
*
* @param string $before Optional. Display before privacy policy link. Default empty.
* @param string $after Optional. Display after privacy policy link. Default empty.
*/
function the_privacy_policy_link( $before = '', $after = '' ) {
echo get_the_privacy_policy_link( $before, $after );
}
/**
* Returns the privacy policy link with formatting, when applicable.
*
* @since 4.9.6
* @since 6.2.0 Added 'privacy-policy' rel attribute.
*
* @param string $before Optional. Display before privacy policy link. Default empty.
* @param string $after Optional. Display after privacy policy link. Default empty.
* @return string Markup for the link and surrounding elements. Empty string if it
* doesn't exist.
*/
function get_the_privacy_policy_link( $before = '', $after = '' ) {
$link = '';
$privacy_policy_url = get_privacy_policy_url();
$policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
$page_title = ( $policy_page_id ) ? get_the_title( $policy_page_id ) : '';
if ( $privacy_policy_url && $page_title ) {
$link = sprintf(
'%s',
esc_url( $privacy_policy_url ),
esc_html( $page_title )
);
}
/**
* Filters the privacy policy link.
*
* @since 4.9.6
*
* @param string $link The privacy policy link. Empty string if it
* doesn't exist.
* @param string $privacy_policy_url The URL of the privacy policy. Empty string
* if it doesn't exist.
*/
$link = apply_filters( 'the_privacy_policy_link', $link, $privacy_policy_url );
if ( $link ) {
return $before . $link . $after;
}
return '';
}
/**
* Returns an array of URL hosts which are considered to be internal hosts.
*
* By default the list of internal hosts is comprised of the host name of
* the site's home_url() (as parsed by wp_parse_url()).
*
* This list is used when determining if a specificed URL is a link to a page on
* the site itself or a link offsite (to an external host). This is used, for
* example, when determining if the "nofollow" attribute should be applied to a
* link.
*
* @see wp_is_internal_link
*
* @since 6.2.0
*
* @return string[] An array of URL hosts.
*/
function wp_internal_hosts() {
static $internal_hosts;
if ( empty( $internal_hosts ) ) {
/**
* Filters the array of URL hosts which are considered internal.
*
* @since 6.2.0
*
* @param string[] $internal_hosts An array of internal URL hostnames.
*/
$internal_hosts = apply_filters(
'wp_internal_hosts',
array(
wp_parse_url( home_url(), PHP_URL_HOST ),
)
);
$internal_hosts = array_unique(
array_map( 'strtolower', (array) $internal_hosts )
);
}
return $internal_hosts;
}
/**
* Determines whether or not the specified URL is of a host included in the internal hosts list.
*
* @see wp_internal_hosts()
*
* @since 6.2.0
*
* @param string $link The URL to test.
* @return bool Returns true for internal URLs and false for all other URLs.
*/
function wp_is_internal_link( $link ) {
$link = strtolower( $link );
if ( in_array( wp_parse_url( $link, PHP_URL_SCHEME ), wp_allowed_protocols(), true ) ) {
return in_array( wp_parse_url( $link, PHP_URL_HOST ), wp_internal_hosts(), true );
}
return false;
}
/**
* Taxonomy API: Core category-specific functionality
*
* @package WordPress
* @subpackage Taxonomy
*/
/**
* Retrieves a list of category objects.
*
* If you set the 'taxonomy' argument to 'link_category', the link categories
* will be returned instead.
*
* @since 2.1.0
*
* @see get_terms() Type of arguments that can be changed.
*
* @param string|array $args {
* Optional. Arguments to retrieve categories. See get_terms() for additional options.
*
* @type string $taxonomy Taxonomy to retrieve terms for. Default 'category'.
* }
* @return array List of category objects.
*/
function get_categories( $args = '' ) {
$defaults = array( 'taxonomy' => 'category' );
$args = wp_parse_args( $args, $defaults );
/**
* Filters the taxonomy used to retrieve terms when calling get_categories().
*
* @since 2.7.0
*
* @param string $taxonomy Taxonomy to retrieve terms from.
* @param array $args An array of arguments. See get_terms().
*/
$args['taxonomy'] = apply_filters( 'get_categories_taxonomy', $args['taxonomy'], $args );
// Back compat.
if ( isset( $args['type'] ) && 'link' === $args['type'] ) {
_deprecated_argument(
__FUNCTION__,
'3.0.0',
sprintf(
/* translators: 1: "type => link", 2: "taxonomy => link_category" */
__( '%1$s is deprecated. Use %2$s instead.' ),
'type => link',
'taxonomy => link_category'
)
);
$args['taxonomy'] = 'link_category';
}
$categories = get_terms( $args );
if ( is_wp_error( $categories ) ) {
$categories = array();
} else {
$categories = (array) $categories;
foreach ( array_keys( $categories ) as $k ) {
_make_cat_compat( $categories[ $k ] );
}
}
return $categories;
}
/**
* Retrieves category data given a category ID or category object.
*
* If you pass the $category parameter an object, which is assumed to be the
* category row object retrieved the database. It will cache the category data.
*
* If you pass $category an integer of the category ID, then that category will
* be retrieved from the database, if it isn't already cached, and pass it back.
*
* If you look at get_term(), then both types will be passed through several
* filters and finally sanitized based on the $filter parameter value.
*
* @since 1.5.1
*
* @param int|object $category Category ID or category row object.
* @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
* correspond to a WP_Term object, an associative array, or a numeric array,
* respectively. Default OBJECT.
* @param string $filter Optional. How to sanitize category fields. Default 'raw'.
* @return object|array|WP_Error|null Category data in type defined by $output parameter.
* WP_Error if $category is empty, null if it does not exist.
*/
function get_category( $category, $output = OBJECT, $filter = 'raw' ) {
$category = get_term( $category, 'category', $output, $filter );
if ( is_wp_error( $category ) ) {
return $category;
}
_make_cat_compat( $category );
return $category;
}
/**
* Retrieves a category based on URL containing the category slug.
*
* Breaks the $category_path parameter up to get the category slug.
*
* Tries to find the child path and will return it. If it doesn't find a
* match, then it will return the first category matching slug, if $full_match,
* is set to false. If it does not, then it will return null.
*
* It is also possible that it will return a WP_Error object on failure. Check
* for it when using this function.
*
* @since 2.1.0
*
* @param string $category_path URL containing category slugs.
* @param bool $full_match Optional. Whether full path should be matched.
* @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
* correspond to a WP_Term object, an associative array, or a numeric array,
* respectively. Default OBJECT.
* @return WP_Term|array|WP_Error|null Type is based on $output value.
*/
function get_category_by_path( $category_path, $full_match = true, $output = OBJECT ) {
$category_path = rawurlencode( urldecode( $category_path ) );
$category_path = str_replace( '%2F', '/', $category_path );
$category_path = str_replace( '%20', ' ', $category_path );
$category_paths = '/' . trim( $category_path, '/' );
$leaf_path = sanitize_title( basename( $category_paths ) );
$category_paths = explode( '/', $category_paths );
$full_path = '';
foreach ( (array) $category_paths as $pathdir ) {
$full_path .= ( '' !== $pathdir ? '/' : '' ) . sanitize_title( $pathdir );
}
$categories = get_terms(
array(
'taxonomy' => 'category',
'get' => 'all',
'slug' => $leaf_path,
)
);
if ( empty( $categories ) ) {
return;
}
foreach ( $categories as $category ) {
$path = '/' . $leaf_path;
$curcategory = $category;
while ( ( 0 !== $curcategory->parent ) && ( $curcategory->parent !== $curcategory->term_id ) ) {
$curcategory = get_term( $curcategory->parent, 'category' );
if ( is_wp_error( $curcategory ) ) {
return $curcategory;
}
$path = '/' . $curcategory->slug . $path;
}
if ( $path === $full_path ) {
$category = get_term( $category->term_id, 'category', $output );
_make_cat_compat( $category );
return $category;
}
}
// If full matching is not required, return the first cat that matches the leaf.
if ( ! $full_match ) {
$category = get_term( reset( $categories )->term_id, 'category', $output );
_make_cat_compat( $category );
return $category;
}
}
/**
* Retrieves a category object by category slug.
*
* @since 2.3.0
*
* @param string $slug The category slug.
* @return object|false Category data object on success, false if not found.
*/
function get_category_by_slug( $slug ) {
$category = get_term_by( 'slug', $slug, 'category' );
if ( $category ) {
_make_cat_compat( $category );
}
return $category;
}
/**
* Retrieves the ID of a category from its name.
*
* @since 1.0.0
*
* @param string $cat_name Category name.
* @return int Category ID on success, 0 if the category doesn't exist.
*/
function get_cat_ID( $cat_name ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid
$cat = get_term_by( 'name', $cat_name, 'category' );
if ( $cat ) {
return $cat->term_id;
}
return 0;
}
/**
* Retrieves the name of a category from its ID.
*
* @since 1.0.0
*
* @param int $cat_id Category ID.
* @return string Category name, or an empty string if the category doesn't exist.
*/
function get_cat_name( $cat_id ) {
$cat_id = (int) $cat_id;
$category = get_term( $cat_id, 'category' );
if ( ! $category || is_wp_error( $category ) ) {
return '';
}
return $category->name;
}
/**
* Checks if a category is an ancestor of another category.
*
* You can use either an ID or the category object for both parameters.
* If you use an integer, the category will be retrieved.
*
* @since 2.1.0
*
* @param int|object $cat1 ID or object to check if this is the parent category.
* @param int|object $cat2 The child category.
* @return bool Whether $cat2 is child of $cat1.
*/
function cat_is_ancestor_of( $cat1, $cat2 ) {
return term_is_ancestor_of( $cat1, $cat2, 'category' );
}
/**
* Sanitizes category data based on context.
*
* @since 2.3.0
*
* @param object|array $category Category data.
* @param string $context Optional. Default 'display'.
* @return object|array Same type as $category with sanitized data for safe use.
*/
function sanitize_category( $category, $context = 'display' ) {
return sanitize_term( $category, 'category', $context );
}
/**
* Sanitizes data in single category key field.
*
* @since 2.3.0
*
* @param string $field Category key to sanitize.
* @param mixed $value Category value to sanitize.
* @param int $cat_id Category ID.
* @param string $context What filter to use, 'raw', 'display', etc.
* @return mixed Value after $value has been sanitized.
*/
function sanitize_category_field( $field, $value, $cat_id, $context ) {
return sanitize_term_field( $field, $value, $cat_id, 'category', $context );
}
/* Tags */
/**
* Retrieves all post tags.
*
* @since 2.3.0
*
* @param string|array $args {
* Optional. Arguments to retrieve tags. See get_terms() for additional options.
*
* @type string $taxonomy Taxonomy to retrieve terms for. Default 'post_tag'.
* }
* @return WP_Term[]|int|WP_Error Array of 'post_tag' term objects, a count thereof,
* or WP_Error if any of the taxonomies do not exist.
*/
function get_tags( $args = '' ) {
$defaults = array( 'taxonomy' => 'post_tag' );
$args = wp_parse_args( $args, $defaults );
$tags = get_terms( $args );
if ( empty( $tags ) ) {
$tags = array();
} else {
/**
* Filters the array of term objects returned for the 'post_tag' taxonomy.
*
* @since 2.3.0
*
* @param WP_Term[]|int|WP_Error $tags Array of 'post_tag' term objects, a count thereof,
* or WP_Error if any of the taxonomies do not exist.
* @param array $args An array of arguments. @see get_terms()
*/
$tags = apply_filters( 'get_tags', $tags, $args );
}
return $tags;
}
/**
* Retrieves a post tag by tag ID or tag object.
*
* If you pass the $tag parameter an object, which is assumed to be the tag row
* object retrieved from the database, it will cache the tag data.
*
* If you pass $tag an integer of the tag ID, then that tag will be retrieved
* from the database, if it isn't already cached, and passed back.
*
* If you look at get_term(), both types will be passed through several filters
* and finally sanitized based on the $filter parameter value.
*
* @since 2.3.0
*
* @param int|WP_Term|object $tag A tag ID or object.
* @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
* correspond to a WP_Term object, an associative array, or a numeric array,
* respectively. Default OBJECT.
* @param string $filter Optional. How to sanitize tag fields. Default 'raw'.
* @return WP_Term|array|WP_Error|null Tag data in type defined by $output parameter.
* WP_Error if $tag is empty, null if it does not exist.
*/
function get_tag( $tag, $output = OBJECT, $filter = 'raw' ) {
return get_term( $tag, 'post_tag', $output, $filter );
}
/* Cache */
/**
* Removes the category cache data based on ID.
*
* @since 2.1.0
*
* @param int $id Category ID
*/
function clean_category_cache( $id ) {
clean_term_cache( $id, 'category' );
}
/**
* Updates category structure to old pre-2.3 from new taxonomy structure.
*
* This function was added for the taxonomy support to update the new category
* structure with the old category one. This will maintain compatibility with
* plugins and themes which depend on the old key or property names.
*
* The parameter should only be passed a variable and not create the array or
* object inline to the parameter. The reason for this is that parameter is
* passed by reference and PHP will fail unless it has the variable.
*
* There is no return value, because everything is updated on the variable you
* pass to it. This is one of the features with using pass by reference in PHP.
*
* @since 2.3.0
* @since 4.4.0 The `$category` parameter now also accepts a WP_Term object.
* @access private
*
* @param array|object|WP_Term $category Category row object or array.
*/
function _make_cat_compat( &$category ) {
if ( is_object( $category ) && ! is_wp_error( $category ) ) {
$category->cat_ID = $category->term_id;
$category->category_count = $category->count;
$category->category_description = $category->description;
$category->cat_name = $category->name;
$category->category_nicename = $category->slug;
$category->category_parent = $category->parent;
} elseif ( is_array( $category ) && isset( $category['term_id'] ) ) {
$category['cat_ID'] = &$category['term_id'];
$category['category_count'] = &$category['count'];
$category['category_description'] = &$category['description'];
$category['cat_name'] = &$category['name'];
$category['category_nicename'] = &$category['slug'];
$category['category_parent'] = &$category['parent'];
}
}
/**
* Rewrite API: WP_Rewrite class
*
* @package WordPress
* @subpackage Rewrite
* @since 1.5.0
*/
/**
* Core class used to implement a rewrite component API.
*
* The WordPress Rewrite class writes the rewrite module rules to the .htaccess
* file. It also handles parsing the request to get the correct setup for the
* WordPress Query class.
*
* The Rewrite along with WP class function as a front controller for WordPress.
* You can add rules to trigger your page view and processing using this
* component. The full functionality of a front controller does not exist,
* meaning you can't define how the template files load based on the rewrite
* rules.
*
* @since 1.5.0
*/
#[AllowDynamicProperties]
class WP_Rewrite {
/**
* Permalink structure for posts.
*
* @since 1.5.0
* @var string
*/
public $permalink_structure;
/**
* Whether to add trailing slashes.
*
* @since 2.2.0
* @var bool
*/
public $use_trailing_slashes;
/**
* Base for the author permalink structure (example.com/$author_base/authorname).
*
* @since 1.5.0
* @var string
*/
public $author_base = 'author';
/**
* Permalink structure for author archives.
*
* @since 1.5.0
* @var string
*/
public $author_structure;
/**
* Permalink structure for date archives.
*
* @since 1.5.0
* @var string
*/
public $date_structure;
/**
* Permalink structure for pages.
*
* @since 1.5.0
* @var string
*/
public $page_structure;
/**
* Base of the search permalink structure (example.com/$search_base/query).
*
* @since 1.5.0
* @var string
*/
public $search_base = 'search';
/**
* Permalink structure for searches.
*
* @since 1.5.0
* @var string
*/
public $search_structure;
/**
* Comments permalink base.
*
* @since 1.5.0
* @var string
*/
public $comments_base = 'comments';
/**
* Pagination permalink base.
*
* @since 3.1.0
* @var string
*/
public $pagination_base = 'page';
/**
* Comments pagination permalink base.
*
* @since 4.2.0
* @var string
*/
public $comments_pagination_base = 'comment-page';
/**
* Feed permalink base.
*
* @since 1.5.0
* @var string
*/
public $feed_base = 'feed';
/**
* Comments feed permalink structure.
*
* @since 1.5.0
* @var string
*/
public $comment_feed_structure;
/**
* Feed request permalink structure.
*
* @since 1.5.0
* @var string
*/
public $feed_structure;
/**
* The static portion of the post permalink structure.
*
* If the permalink structure is "/archive/%post_id%" then the front
* is "/archive/". If the permalink structure is "/%year%/%postname%/"
* then the front is "/".
*
* @since 1.5.0
* @var string
*
* @see WP_Rewrite::init()
*/
public $front;
/**
* The prefix for all permalink structures.
*
* If PATHINFO/index permalinks are in use then the root is the value of
* `WP_Rewrite::$index` with a trailing slash appended. Otherwise the root
* will be empty.
*
* @since 1.5.0
* @var string
*
* @see WP_Rewrite::init()
* @see WP_Rewrite::using_index_permalinks()
*/
public $root = '';
/**
* The name of the index file which is the entry point to all requests.
*
* @since 1.5.0
* @var string
*/
public $index = 'index.php';
/**
* Variable name to use for regex matches in the rewritten query.
*
* @since 1.5.0
* @var string
*/
public $matches = '';
/**
* Rewrite rules to match against the request to find the redirect or query.
*
* @since 1.5.0
* @var string[]
*/
public $rules;
/**
* Additional rules added external to the rewrite class.
*
* Those not generated by the class, see add_rewrite_rule().
*
* @since 2.1.0
* @var string[]
*/
public $extra_rules = array();
/**
* Additional rules that belong at the beginning to match first.
*
* Those not generated by the class, see add_rewrite_rule().
*
* @since 2.3.0
* @var string[]
*/
public $extra_rules_top = array();
/**
* Rules that don't redirect to WordPress' index.php.
*
* These rules are written to the mod_rewrite portion of the .htaccess,
* and are added by add_external_rule().
*
* @since 2.1.0
* @var string[]
*/
public $non_wp_rules = array();
/**
* Extra permalink structures, e.g. categories, added by add_permastruct().
*
* @since 2.1.0
* @var array[]
*/
public $extra_permastructs = array();
/**
* Endpoints (like /trackback/) added by add_rewrite_endpoint().
*
* @since 2.1.0
* @var array[]
*/
public $endpoints;
/**
* Whether to write every mod_rewrite rule for WordPress into the .htaccess file.
*
* This is off by default, turning it on might print a lot of rewrite rules
* to the .htaccess file.
*
* @since 2.0.0
* @var bool
*
* @see WP_Rewrite::mod_rewrite_rules()
*/
public $use_verbose_rules = false;
/**
* Could post permalinks be confused with those of pages?
*
* If the first rewrite tag in the post permalink structure is one that could
* also match a page name (e.g. %postname% or %author%) then this flag is
* set to true. Prior to WordPress 3.3 this flag indicated that every page
* would have a set of rules added to the top of the rewrite rules array.
* Now it tells WP::parse_request() to check if a URL matching the page
* permastruct is actually a page before accepting it.
*
* @since 2.5.0
* @var bool
*
* @see WP_Rewrite::init()
*/
public $use_verbose_page_rules = true;
/**
* Rewrite tags that can be used in permalink structures.
*
* These are translated into the regular expressions stored in
* `WP_Rewrite::$rewritereplace` and are rewritten to the query
* variables listed in WP_Rewrite::$queryreplace.
*
* Additional tags can be added with add_rewrite_tag().
*
* @since 1.5.0
* @var string[]
*/
public $rewritecode = array(
'%year%',
'%monthnum%',
'%day%',
'%hour%',
'%minute%',
'%second%',
'%postname%',
'%post_id%',
'%author%',
'%pagename%',
'%search%',
);
/**
* Regular expressions to be substituted into rewrite rules in place
* of rewrite tags, see WP_Rewrite::$rewritecode.
*
* @since 1.5.0
* @var string[]
*/
public $rewritereplace = array(
'([0-9]{4})',
'([0-9]{1,2})',
'([0-9]{1,2})',
'([0-9]{1,2})',
'([0-9]{1,2})',
'([0-9]{1,2})',
'([^/]+)',
'([0-9]+)',
'([^/]+)',
'([^/]+?)',
'(.+)',
);
/**
* Query variables that rewrite tags map to, see WP_Rewrite::$rewritecode.
*
* @since 1.5.0
* @var string[]
*/
public $queryreplace = array(
'year=',
'monthnum=',
'day=',
'hour=',
'minute=',
'second=',
'name=',
'p=',
'author_name=',
'pagename=',
's=',
);
/**
* Supported default feeds.
*
* @since 1.5.0
* @var string[]
*/
public $feeds = array( 'feed', 'rdf', 'rss', 'rss2', 'atom' );
/**
* Determines whether permalinks are being used.
*
* This can be either rewrite module or permalink in the HTTP query string.
*
* @since 1.5.0
*
* @return bool True, if permalinks are enabled.
*/
public function using_permalinks() {
return ! empty( $this->permalink_structure );
}
/**
* Determines whether permalinks are being used and rewrite module is not enabled.
*
* Means that permalink links are enabled and index.php is in the URL.
*
* @since 1.5.0
*
* @return bool Whether permalink links are enabled and index.php is in the URL.
*/
public function using_index_permalinks() {
if ( empty( $this->permalink_structure ) ) {
return false;
}
// If the index is not in the permalink, we're using mod_rewrite.
return preg_match( '#^/*' . $this->index . '#', $this->permalink_structure );
}
/**
* Determines whether permalinks are being used and rewrite module is enabled.
*
* Using permalinks and index.php is not in the URL.
*
* @since 1.5.0
*
* @return bool Whether permalink links are enabled and index.php is NOT in the URL.
*/
public function using_mod_rewrite_permalinks() {
return $this->using_permalinks() && ! $this->using_index_permalinks();
}
/**
* Indexes for matches for usage in preg_*() functions.
*
* The format of the string is, with empty matches property value, '$NUM'.
* The 'NUM' will be replaced with the value in the $number parameter. With
* the matches property not empty, the value of the returned string will
* contain that value of the matches property. The format then will be
* '$MATCHES[NUM]', with MATCHES as the value in the property and NUM the
* value of the $number parameter.
*
* @since 1.5.0
*
* @param int $number Index number.
* @return string
*/
public function preg_index( $number ) {
$match_prefix = '$';
$match_suffix = '';
if ( ! empty( $this->matches ) ) {
$match_prefix = '$' . $this->matches . '[';
$match_suffix = ']';
}
return "$match_prefix$number$match_suffix";
}
/**
* Retrieves all pages and attachments for pages URIs.
*
* The attachments are for those that have pages as parents and will be
* retrieved.
*
* @since 2.5.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @return array Array of page URIs as first element and attachment URIs as second element.
*/
public function page_uri_index() {
global $wpdb;
// Get pages in order of hierarchy, i.e. children after parents.
$pages = $wpdb->get_results( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'page' AND post_status != 'auto-draft'" );
$posts = get_page_hierarchy( $pages );
// If we have no pages get out quick.
if ( ! $posts ) {
return array( array(), array() );
}
// Now reverse it, because we need parents after children for rewrite rules to work properly.
$posts = array_reverse( $posts, true );
$page_uris = array();
$page_attachment_uris = array();
foreach ( $posts as $id => $post ) {
// URL => page name.
$uri = get_page_uri( $id );
$attachments = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'attachment' AND post_parent = %d", $id ) );
if ( ! empty( $attachments ) ) {
foreach ( $attachments as $attachment ) {
$attach_uri = get_page_uri( $attachment->ID );
$page_attachment_uris[ $attach_uri ] = $attachment->ID;
}
}
$page_uris[ $uri ] = $id;
}
return array( $page_uris, $page_attachment_uris );
}
/**
* Retrieves all of the rewrite rules for pages.
*
* @since 1.5.0
*
* @return string[] Page rewrite rules.
*/
public function page_rewrite_rules() {
// The extra .? at the beginning prevents clashes with other regular expressions in the rules array.
$this->add_rewrite_tag( '%pagename%', '(.?.+?)', 'pagename=' );
return $this->generate_rewrite_rules( $this->get_page_permastruct(), EP_PAGES, true, true, false, false );
}
/**
* Retrieves date permalink structure, with year, month, and day.
*
* The permalink structure for the date, if not set already depends on the
* permalink structure. It can be one of three formats. The first is year,
* month, day; the second is day, month, year; and the last format is month,
* day, year. These are matched against the permalink structure for which
* one is used. If none matches, then the default will be used, which is
* year, month, day.
*
* Prevents post ID and date permalinks from overlapping. In the case of
* post_id, the date permalink will be prepended with front permalink with
* 'date/' before the actual permalink to form the complete date permalink
* structure.
*
* @since 1.5.0
*
* @return string|false Date permalink structure on success, false on failure.
*/
public function get_date_permastruct() {
if ( isset( $this->date_structure ) ) {
return $this->date_structure;
}
if ( empty( $this->permalink_structure ) ) {
$this->date_structure = '';
return false;
}
// The date permalink must have year, month, and day separated by slashes.
$endians = array( '%year%/%monthnum%/%day%', '%day%/%monthnum%/%year%', '%monthnum%/%day%/%year%' );
$this->date_structure = '';
$date_endian = '';
foreach ( $endians as $endian ) {
if ( str_contains( $this->permalink_structure, $endian ) ) {
$date_endian = $endian;
break;
}
}
if ( empty( $date_endian ) ) {
$date_endian = '%year%/%monthnum%/%day%';
}
/*
* Do not allow the date tags and %post_id% to overlap in the permalink
* structure. If they do, move the date tags to $front/date/.
*/
$front = $this->front;
preg_match_all( '/%.+?%/', $this->permalink_structure, $tokens );
$tok_index = 1;
foreach ( (array) $tokens[0] as $token ) {
if ( '%post_id%' === $token && ( $tok_index <= 3 ) ) {
$front = $front . 'date/';
break;
}
$tok_index++;
}
$this->date_structure = $front . $date_endian;
return $this->date_structure;
}
/**
* Retrieves the year permalink structure without month and day.
*
* Gets the date permalink structure and strips out the month and day
* permalink structures.
*
* @since 1.5.0
*
* @return string|false Year permalink structure on success, false on failure.
*/
public function get_year_permastruct() {
$structure = $this->get_date_permastruct();
if ( empty( $structure ) ) {
return false;
}
$structure = str_replace( '%monthnum%', '', $structure );
$structure = str_replace( '%day%', '', $structure );
$structure = preg_replace( '#/+#', '/', $structure );
return $structure;
}
/**
* Retrieves the month permalink structure without day and with year.
*
* Gets the date permalink structure and strips out the day permalink
* structures. Keeps the year permalink structure.
*
* @since 1.5.0
*
* @return string|false Year/Month permalink structure on success, false on failure.
*/
public function get_month_permastruct() {
$structure = $this->get_date_permastruct();
if ( empty( $structure ) ) {
return false;
}
$structure = str_replace( '%day%', '', $structure );
$structure = preg_replace( '#/+#', '/', $structure );
return $structure;
}
/**
* Retrieves the day permalink structure with month and year.
*
* Keeps date permalink structure with all year, month, and day.
*
* @since 1.5.0
*
* @return string|false Year/Month/Day permalink structure on success, false on failure.
*/
public function get_day_permastruct() {
return $this->get_date_permastruct();
}
/**
* Retrieves the permalink structure for categories.
*
* If the category_base property has no value, then the category structure
* will have the front property value, followed by 'category', and finally
* '%category%'. If it does, then the root property will be used, along with
* the category_base property value.
*
* @since 1.5.0
*
* @return string|false Category permalink structure on success, false on failure.
*/
public function get_category_permastruct() {
return $this->get_extra_permastruct( 'category' );
}
/**
* Retrieves the permalink structure for tags.
*
* If the tag_base property has no value, then the tag structure will have
* the front property value, followed by 'tag', and finally '%tag%'. If it
* does, then the root property will be used, along with the tag_base
* property value.
*
* @since 2.3.0
*
* @return string|false Tag permalink structure on success, false on failure.
*/
public function get_tag_permastruct() {
return $this->get_extra_permastruct( 'post_tag' );
}
/**
* Retrieves an extra permalink structure by name.
*
* @since 2.5.0
*
* @param string $name Permalink structure name.
* @return string|false Permalink structure string on success, false on failure.
*/
public function get_extra_permastruct( $name ) {
if ( empty( $this->permalink_structure ) ) {
return false;
}
if ( isset( $this->extra_permastructs[ $name ] ) ) {
return $this->extra_permastructs[ $name ]['struct'];
}
return false;
}
/**
* Retrieves the author permalink structure.
*
* The permalink structure is front property, author base, and finally
* '/%author%'. Will set the author_structure property and then return it
* without attempting to set the value again.
*
* @since 1.5.0
*
* @return string|false Author permalink structure on success, false on failure.
*/
public function get_author_permastruct() {
if ( isset( $this->author_structure ) ) {
return $this->author_structure;
}
if ( empty( $this->permalink_structure ) ) {
$this->author_structure = '';
return false;
}
$this->author_structure = $this->front . $this->author_base . '/%author%';
return $this->author_structure;
}
/**
* Retrieves the search permalink structure.
*
* The permalink structure is root property, search base, and finally
* '/%search%'. Will set the search_structure property and then return it
* without attempting to set the value again.
*
* @since 1.5.0
*
* @return string|false Search permalink structure on success, false on failure.
*/
public function get_search_permastruct() {
if ( isset( $this->search_structure ) ) {
return $this->search_structure;
}
if ( empty( $this->permalink_structure ) ) {
$this->search_structure = '';
return false;
}
$this->search_structure = $this->root . $this->search_base . '/%search%';
return $this->search_structure;
}
/**
* Retrieves the page permalink structure.
*
* The permalink structure is root property, and '%pagename%'. Will set the
* page_structure property and then return it without attempting to set the
* value again.
*
* @since 1.5.0
*
* @return string|false Page permalink structure on success, false on failure.
*/
public function get_page_permastruct() {
if ( isset( $this->page_structure ) ) {
return $this->page_structure;
}
if ( empty( $this->permalink_structure ) ) {
$this->page_structure = '';
return false;
}
$this->page_structure = $this->root . '%pagename%';
return $this->page_structure;
}
/**
* Retrieves the feed permalink structure.
*
* The permalink structure is root property, feed base, and finally
* '/%feed%'. Will set the feed_structure property and then return it
* without attempting to set the value again.
*
* @since 1.5.0
*
* @return string|false Feed permalink structure on success, false on failure.
*/
public function get_feed_permastruct() {
if ( isset( $this->feed_structure ) ) {
return $this->feed_structure;
}
if ( empty( $this->permalink_structure ) ) {
$this->feed_structure = '';
return false;
}
$this->feed_structure = $this->root . $this->feed_base . '/%feed%';
return $this->feed_structure;
}
/**
* Retrieves the comment feed permalink structure.
*
* The permalink structure is root property, comment base property, feed
* base and finally '/%feed%'. Will set the comment_feed_structure property
* and then return it without attempting to set the value again.
*
* @since 1.5.0
*
* @return string|false Comment feed permalink structure on success, false on failure.
*/
public function get_comment_feed_permastruct() {
if ( isset( $this->comment_feed_structure ) ) {
return $this->comment_feed_structure;
}
if ( empty( $this->permalink_structure ) ) {
$this->comment_feed_structure = '';
return false;
}
$this->comment_feed_structure = $this->root . $this->comments_base . '/' . $this->feed_base . '/%feed%';
return $this->comment_feed_structure;
}
/**
* Adds or updates existing rewrite tags (e.g. %postname%).
*
* If the tag already exists, replace the existing pattern and query for
* that tag, otherwise add the new tag.
*
* @since 1.5.0
*
* @see WP_Rewrite::$rewritecode
* @see WP_Rewrite::$rewritereplace
* @see WP_Rewrite::$queryreplace
*
* @param string $tag Name of the rewrite tag to add or update.
* @param string $regex Regular expression to substitute the tag for in rewrite rules.
* @param string $query String to append to the rewritten query. Must end in '='.
*/
public function add_rewrite_tag( $tag, $regex, $query ) {
$position = array_search( $tag, $this->rewritecode, true );
if ( false !== $position && null !== $position ) {
$this->rewritereplace[ $position ] = $regex;
$this->queryreplace[ $position ] = $query;
} else {
$this->rewritecode[] = $tag;
$this->rewritereplace[] = $regex;
$this->queryreplace[] = $query;
}
}
/**
* Removes an existing rewrite tag.
*
* @since 4.5.0
*
* @see WP_Rewrite::$rewritecode
* @see WP_Rewrite::$rewritereplace
* @see WP_Rewrite::$queryreplace
*
* @param string $tag Name of the rewrite tag to remove.
*/
public function remove_rewrite_tag( $tag ) {
$position = array_search( $tag, $this->rewritecode, true );
if ( false !== $position && null !== $position ) {
unset( $this->rewritecode[ $position ] );
unset( $this->rewritereplace[ $position ] );
unset( $this->queryreplace[ $position ] );
}
}
/**
* Generates rewrite rules from a permalink structure.
*
* The main WP_Rewrite function for building the rewrite rule list. The
* contents of the function is a mix of black magic and regular expressions,
* so best just ignore the contents and move to the parameters.
*
* @since 1.5.0
*
* @param string $permalink_structure The permalink structure.
* @param int $ep_mask Optional. Endpoint mask defining what endpoints are added to the structure.
* Accepts a mask of:
* - `EP_ALL`
* - `EP_NONE`
* - `EP_ALL_ARCHIVES`
* - `EP_ATTACHMENT`
* - `EP_AUTHORS`
* - `EP_CATEGORIES`
* - `EP_COMMENTS`
* - `EP_DATE`
* - `EP_DAY`
* - `EP_MONTH`
* - `EP_PAGES`
* - `EP_PERMALINK`
* - `EP_ROOT`
* - `EP_SEARCH`
* - `EP_TAGS`
* - `EP_YEAR`
* Default `EP_NONE`.
* @param bool $paged Optional. Whether archive pagination rules should be added for the structure.
* Default true.
* @param bool $feed Optional. Whether feed rewrite rules should be added for the structure.
* Default true.
* @param bool $forcomments Optional. Whether the feed rules should be a query for a comments feed.
* Default false.
* @param bool $walk_dirs Optional. Whether the 'directories' making up the structure should be walked
* over and rewrite rules built for each in-turn. Default true.
* @param bool $endpoints Optional. Whether endpoints should be applied to the generated rewrite rules.
* Default true.
* @return string[] Array of rewrite rules keyed by their regex pattern.
*/
public function generate_rewrite_rules( $permalink_structure, $ep_mask = EP_NONE, $paged = true, $feed = true, $forcomments = false, $walk_dirs = true, $endpoints = true ) {
// Build a regex to match the feed section of URLs, something like (feed|atom|rss|rss2)/?
$feedregex2 = '';
foreach ( (array) $this->feeds as $feed_name ) {
$feedregex2 .= $feed_name . '|';
}
$feedregex2 = '(' . trim( $feedregex2, '|' ) . ')/?$';
/*
* $feedregex is identical but with /feed/ added on as well, so URLs like /feed/atom
* and /atom are both possible
*/
$feedregex = $this->feed_base . '/' . $feedregex2;
// Build a regex to match the trackback and page/xx parts of URLs.
$trackbackregex = 'trackback/?$';
$pageregex = $this->pagination_base . '/?([0-9]{1,})/?$';
$commentregex = $this->comments_pagination_base . '-([0-9]{1,})/?$';
$embedregex = 'embed/?$';
// Build up an array of endpoint regexes to append => queries to append.
if ( $endpoints ) {
$ep_query_append = array();
foreach ( (array) $this->endpoints as $endpoint ) {
// Match everything after the endpoint name, but allow for nothing to appear there.
$epmatch = $endpoint[1] . '(/(.*))?/?$';
// This will be appended on to the rest of the query for each dir.
$epquery = '&' . $endpoint[2] . '=';
$ep_query_append[ $epmatch ] = array( $endpoint[0], $epquery );
}
}
// Get everything up to the first rewrite tag.
$front = substr( $permalink_structure, 0, strpos( $permalink_structure, '%' ) );
// Build an array of the tags (note that said array ends up being in $tokens[0]).
preg_match_all( '/%.+?%/', $permalink_structure, $tokens );
$num_tokens = count( $tokens[0] );
$index = $this->index; // Probably 'index.php'.
$feedindex = $index;
$trackbackindex = $index;
$embedindex = $index;
/*
* Build a list from the rewritecode and queryreplace arrays, that will look something
* like tagname=$matches[i] where i is the current $i.
*/
$queries = array();
for ( $i = 0; $i < $num_tokens; ++$i ) {
if ( 0 < $i ) {
$queries[ $i ] = $queries[ $i - 1 ] . '&';
} else {
$queries[ $i ] = '';
}
$query_token = str_replace( $this->rewritecode, $this->queryreplace, $tokens[0][ $i ] ) . $this->preg_index( $i + 1 );
$queries[ $i ] .= $query_token;
}
// Get the structure, minus any cruft (stuff that isn't tags) at the front.
$structure = $permalink_structure;
if ( '/' !== $front ) {
$structure = str_replace( $front, '', $structure );
}
/*
* Create a list of dirs to walk over, making rewrite rules for each level
* so for example, a $structure of /%year%/%monthnum%/%postname% would create
* rewrite rules for /%year%/, /%year%/%monthnum%/ and /%year%/%monthnum%/%postname%
*/
$structure = trim( $structure, '/' );
$dirs = $walk_dirs ? explode( '/', $structure ) : array( $structure );
$num_dirs = count( $dirs );
// Strip slashes from the front of $front.
$front = preg_replace( '|^/+|', '', $front );
// The main workhorse loop.
$post_rewrite = array();
$struct = $front;
for ( $j = 0; $j < $num_dirs; ++$j ) {
// Get the struct for this dir, and trim slashes off the front.
$struct .= $dirs[ $j ] . '/'; // Accumulate. see comment near explode('/', $structure) above.
$struct = ltrim( $struct, '/' );
// Replace tags with regexes.
$match = str_replace( $this->rewritecode, $this->rewritereplace, $struct );
// Make a list of tags, and store how many there are in $num_toks.
$num_toks = preg_match_all( '/%.+?%/', $struct, $toks );
// Get the 'tagname=$matches[i]'.
$query = ( ! empty( $num_toks ) && isset( $queries[ $num_toks - 1 ] ) ) ? $queries[ $num_toks - 1 ] : '';
// Set up $ep_mask_specific which is used to match more specific URL types.
switch ( $dirs[ $j ] ) {
case '%year%':
$ep_mask_specific = EP_YEAR;
break;
case '%monthnum%':
$ep_mask_specific = EP_MONTH;
break;
case '%day%':
$ep_mask_specific = EP_DAY;
break;
default:
$ep_mask_specific = EP_NONE;
}
// Create query for /page/xx.
$pagematch = $match . $pageregex;
$pagequery = $index . '?' . $query . '&paged=' . $this->preg_index( $num_toks + 1 );
// Create query for /comment-page-xx.
$commentmatch = $match . $commentregex;
$commentquery = $index . '?' . $query . '&cpage=' . $this->preg_index( $num_toks + 1 );
if ( get_option( 'page_on_front' ) ) {
// Create query for Root /comment-page-xx.
$rootcommentmatch = $match . $commentregex;
$rootcommentquery = $index . '?' . $query . '&page_id=' . get_option( 'page_on_front' ) . '&cpage=' . $this->preg_index( $num_toks + 1 );
}
// Create query for /feed/(feed|atom|rss|rss2|rdf).
$feedmatch = $match . $feedregex;
$feedquery = $feedindex . '?' . $query . '&feed=' . $this->preg_index( $num_toks + 1 );
// Create query for /(feed|atom|rss|rss2|rdf) (see comment near creation of $feedregex).
$feedmatch2 = $match . $feedregex2;
$feedquery2 = $feedindex . '?' . $query . '&feed=' . $this->preg_index( $num_toks + 1 );
// Create query and regex for embeds.
$embedmatch = $match . $embedregex;
$embedquery = $embedindex . '?' . $query . '&embed=true';
// If asked to, turn the feed queries into comment feed ones.
if ( $forcomments ) {
$feedquery .= '&withcomments=1';
$feedquery2 .= '&withcomments=1';
}
// Start creating the array of rewrites for this dir.
$rewrite = array();
// ...adding on /feed/ regexes => queries.
if ( $feed ) {
$rewrite = array(
$feedmatch => $feedquery,
$feedmatch2 => $feedquery2,
$embedmatch => $embedquery,
);
}
// ...and /page/xx ones.
if ( $paged ) {
$rewrite = array_merge( $rewrite, array( $pagematch => $pagequery ) );
}
// Only on pages with comments add ../comment-page-xx/.
if ( EP_PAGES & $ep_mask || EP_PERMALINK & $ep_mask ) {
$rewrite = array_merge( $rewrite, array( $commentmatch => $commentquery ) );
} elseif ( EP_ROOT & $ep_mask && get_option( 'page_on_front' ) ) {
$rewrite = array_merge( $rewrite, array( $rootcommentmatch => $rootcommentquery ) );
}
// Do endpoints.
if ( $endpoints ) {
foreach ( (array) $ep_query_append as $regex => $ep ) {
// Add the endpoints on if the mask fits.
if ( $ep[0] & $ep_mask || $ep[0] & $ep_mask_specific ) {
$rewrite[ $match . $regex ] = $index . '?' . $query . $ep[1] . $this->preg_index( $num_toks + 2 );
}
}
}
// If we've got some tags in this dir.
if ( $num_toks ) {
$post = false;
$page = false;
/*
* Check to see if this dir is permalink-level: i.e. the structure specifies an
* individual post. Do this by checking it contains at least one of 1) post name,
* 2) post ID, 3) page name, 4) timestamp (year, month, day, hour, second and
* minute all present). Set these flags now as we need them for the endpoints.
*/
if ( str_contains( $struct, '%postname%' )
|| str_contains( $struct, '%post_id%' )
|| str_contains( $struct, '%pagename%' )
|| ( str_contains( $struct, '%year%' )
&& str_contains( $struct, '%monthnum%' )
&& str_contains( $struct, '%day%' )
&& str_contains( $struct, '%hour%' )
&& str_contains( $struct, '%minute%' )
&& str_contains( $struct, '%second%' ) )
) {
$post = true;
if ( str_contains( $struct, '%pagename%' ) ) {
$page = true;
}
}
if ( ! $post ) {
// For custom post types, we need to add on endpoints as well.
foreach ( get_post_types( array( '_builtin' => false ) ) as $ptype ) {
if ( str_contains( $struct, "%$ptype%" ) ) {
$post = true;
// This is for page style attachment URLs.
$page = is_post_type_hierarchical( $ptype );
break;
}
}
}
// If creating rules for a permalink, do all the endpoints like attachments etc.
if ( $post ) {
// Create query and regex for trackback.
$trackbackmatch = $match . $trackbackregex;
$trackbackquery = $trackbackindex . '?' . $query . '&tb=1';
// Create query and regex for embeds.
$embedmatch = $match . $embedregex;
$embedquery = $embedindex . '?' . $query . '&embed=true';
// Trim slashes from the end of the regex for this dir.
$match = rtrim( $match, '/' );
// Get rid of brackets.
$submatchbase = str_replace( array( '(', ')' ), '', $match );
// Add a rule for at attachments, which take the form of /some-text.
$sub1 = $submatchbase . '/([^/]+)/';
// Add trackback regex /trackback/...
$sub1tb = $sub1 . $trackbackregex;
// And /feed/(atom|...)
$sub1feed = $sub1 . $feedregex;
// And /(feed|atom...)
$sub1feed2 = $sub1 . $feedregex2;
// And /comment-page-xx
$sub1comment = $sub1 . $commentregex;
// And /embed/...
$sub1embed = $sub1 . $embedregex;
/*
* Add another rule to match attachments in the explicit form:
* /attachment/some-text
*/
$sub2 = $submatchbase . '/attachment/([^/]+)/';
// And add trackbacks /attachment/trackback.
$sub2tb = $sub2 . $trackbackregex;
// Feeds, /attachment/feed/(atom|...)
$sub2feed = $sub2 . $feedregex;
// And feeds again on to this /attachment/(feed|atom...)
$sub2feed2 = $sub2 . $feedregex2;
// And /comment-page-xx
$sub2comment = $sub2 . $commentregex;
// And /embed/...
$sub2embed = $sub2 . $embedregex;
// Create queries for these extra tag-ons we've just dealt with.
$subquery = $index . '?attachment=' . $this->preg_index( 1 );
$subtbquery = $subquery . '&tb=1';
$subfeedquery = $subquery . '&feed=' . $this->preg_index( 2 );
$subcommentquery = $subquery . '&cpage=' . $this->preg_index( 2 );
$subembedquery = $subquery . '&embed=true';
// Do endpoints for attachments.
if ( ! empty( $endpoints ) ) {
foreach ( (array) $ep_query_append as $regex => $ep ) {
if ( $ep[0] & EP_ATTACHMENT ) {
$rewrite[ $sub1 . $regex ] = $subquery . $ep[1] . $this->preg_index( 3 );
$rewrite[ $sub2 . $regex ] = $subquery . $ep[1] . $this->preg_index( 3 );
}
}
}
/*
* Now we've finished with endpoints, finish off the $sub1 and $sub2 matches
* add a ? as we don't have to match that last slash, and finally a $ so we
* match to the end of the URL
*/
$sub1 .= '?$';
$sub2 .= '?$';
/*
* Post pagination, e.g. /2/
* Previously: '(/[0-9]+)?/?$', which produced '/2' for page.
* When cast to int, returned 0.
*/
$match = $match . '(?:/([0-9]+))?/?$';
$query = $index . '?' . $query . '&page=' . $this->preg_index( $num_toks + 1 );
// Not matching a permalink so this is a lot simpler.
} else {
// Close the match and finalize the query.
$match .= '?$';
$query = $index . '?' . $query;
}
/*
* Create the final array for this dir by joining the $rewrite array (which currently
* only contains rules/queries for trackback, pages etc) to the main regex/query for
* this dir
*/
$rewrite = array_merge( $rewrite, array( $match => $query ) );
// If we're matching a permalink, add those extras (attachments etc) on.
if ( $post ) {
// Add trackback.
$rewrite = array_merge( array( $trackbackmatch => $trackbackquery ), $rewrite );
// Add embed.
$rewrite = array_merge( array( $embedmatch => $embedquery ), $rewrite );
// Add regexes/queries for attachments, attachment trackbacks and so on.
if ( ! $page ) {
// Require /attachment/stuff form for pages because of confusion with subpages.
$rewrite = array_merge(
$rewrite,
array(
$sub1 => $subquery,
$sub1tb => $subtbquery,
$sub1feed => $subfeedquery,
$sub1feed2 => $subfeedquery,
$sub1comment => $subcommentquery,
$sub1embed => $subembedquery,
)
);
}
$rewrite = array_merge(
array(
$sub2 => $subquery,
$sub2tb => $subtbquery,
$sub2feed => $subfeedquery,
$sub2feed2 => $subfeedquery,
$sub2comment => $subcommentquery,
$sub2embed => $subembedquery,
),
$rewrite
);
}
}
// Add the rules for this dir to the accumulating $post_rewrite.
$post_rewrite = array_merge( $rewrite, $post_rewrite );
}
// The finished rules. phew!
return $post_rewrite;
}
/**
* Generates rewrite rules with permalink structure and walking directory only.
*
* Shorten version of WP_Rewrite::generate_rewrite_rules() that allows for shorter
* list of parameters. See the method for longer description of what generating
* rewrite rules does.
*
* @since 1.5.0
*
* @see WP_Rewrite::generate_rewrite_rules() See for long description and rest of parameters.
*
* @param string $permalink_structure The permalink structure to generate rules.
* @param bool $walk_dirs Optional. Whether to create list of directories to walk over.
* Default false.
* @return array An array of rewrite rules keyed by their regex pattern.
*/
public function generate_rewrite_rule( $permalink_structure, $walk_dirs = false ) {
return $this->generate_rewrite_rules( $permalink_structure, EP_NONE, false, false, false, $walk_dirs );
}
/**
* Constructs rewrite matches and queries from permalink structure.
*
* Runs the action {@see 'generate_rewrite_rules'} with the parameter that is an
* reference to the current WP_Rewrite instance to further manipulate the
* permalink structures and rewrite rules. Runs the {@see 'rewrite_rules_array'}
* filter on the full rewrite rule array.
*
* There are two ways to manipulate the rewrite rules, one by hooking into
* the {@see 'generate_rewrite_rules'} action and gaining full control of the
* object or just manipulating the rewrite rule array before it is passed
* from the function.
*
* @since 1.5.0
*
* @return string[] An associative array of matches and queries.
*/
public function rewrite_rules() {
$rewrite = array();
if ( empty( $this->permalink_structure ) ) {
return $rewrite;
}
// robots.txt -- only if installed at the root.
$home_path = parse_url( home_url() );
$robots_rewrite = ( empty( $home_path['path'] ) || '/' === $home_path['path'] ) ? array( 'robots\.txt$' => $this->index . '?robots=1' ) : array();
// favicon.ico -- only if installed at the root.
$favicon_rewrite = ( empty( $home_path['path'] ) || '/' === $home_path['path'] ) ? array( 'favicon\.ico$' => $this->index . '?favicon=1' ) : array();
// Old feed and service files.
$deprecated_files = array(
'.*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\.php$' => $this->index . '?feed=old',
'.*wp-app\.php(/.*)?$' => $this->index . '?error=403',
);
// Registration rules.
$registration_pages = array();
if ( is_multisite() && is_main_site() ) {
$registration_pages['.*wp-signup.php$'] = $this->index . '?signup=true';
$registration_pages['.*wp-activate.php$'] = $this->index . '?activate=true';
}
// Deprecated.
$registration_pages['.*wp-register.php$'] = $this->index . '?register=true';
// Post rewrite rules.
$post_rewrite = $this->generate_rewrite_rules( $this->permalink_structure, EP_PERMALINK );
/**
* Filters rewrite rules used for "post" archives.
*
* @since 1.5.0
*
* @param string[] $post_rewrite Array of rewrite rules for posts, keyed by their regex pattern.
*/
$post_rewrite = apply_filters( 'post_rewrite_rules', $post_rewrite );
// Date rewrite rules.
$date_rewrite = $this->generate_rewrite_rules( $this->get_date_permastruct(), EP_DATE );
/**
* Filters rewrite rules used for date archives.
*
* Likely date archives would include `/yyyy/`, `/yyyy/mm/`, and `/yyyy/mm/dd/`.
*
* @since 1.5.0
*
* @param string[] $date_rewrite Array of rewrite rules for date archives, keyed by their regex pattern.
*/
$date_rewrite = apply_filters( 'date_rewrite_rules', $date_rewrite );
// Root-level rewrite rules.
$root_rewrite = $this->generate_rewrite_rules( $this->root . '/', EP_ROOT );
/**
* Filters rewrite rules used for root-level archives.
*
* Likely root-level archives would include pagination rules for the homepage
* as well as site-wide post feeds (e.g. `/feed/`, and `/feed/atom/`).
*
* @since 1.5.0
*
* @param string[] $root_rewrite Array of root-level rewrite rules, keyed by their regex pattern.
*/
$root_rewrite = apply_filters( 'root_rewrite_rules', $root_rewrite );
// Comments rewrite rules.
$comments_rewrite = $this->generate_rewrite_rules( $this->root . $this->comments_base, EP_COMMENTS, false, true, true, false );
/**
* Filters rewrite rules used for comment feed archives.
*
* Likely comments feed archives include `/comments/feed/` and `/comments/feed/atom/`.
*
* @since 1.5.0
*
* @param string[] $comments_rewrite Array of rewrite rules for the site-wide comments feeds, keyed by their regex pattern.
*/
$comments_rewrite = apply_filters( 'comments_rewrite_rules', $comments_rewrite );
// Search rewrite rules.
$search_structure = $this->get_search_permastruct();
$search_rewrite = $this->generate_rewrite_rules( $search_structure, EP_SEARCH );
/**
* Filters rewrite rules used for search archives.
*
* Likely search-related archives include `/search/search+query/` as well as
* pagination and feed paths for a search.
*
* @since 1.5.0
*
* @param string[] $search_rewrite Array of rewrite rules for search queries, keyed by their regex pattern.
*/
$search_rewrite = apply_filters( 'search_rewrite_rules', $search_rewrite );
// Author rewrite rules.
$author_rewrite = $this->generate_rewrite_rules( $this->get_author_permastruct(), EP_AUTHORS );
/**
* Filters rewrite rules used for author archives.
*
* Likely author archives would include `/author/author-name/`, as well as
* pagination and feed paths for author archives.
*
* @since 1.5.0
*
* @param string[] $author_rewrite Array of rewrite rules for author archives, keyed by their regex pattern.
*/
$author_rewrite = apply_filters( 'author_rewrite_rules', $author_rewrite );
// Pages rewrite rules.
$page_rewrite = $this->page_rewrite_rules();
/**
* Filters rewrite rules used for "page" post type archives.
*
* @since 1.5.0
*
* @param string[] $page_rewrite Array of rewrite rules for the "page" post type, keyed by their regex pattern.
*/
$page_rewrite = apply_filters( 'page_rewrite_rules', $page_rewrite );
// Extra permastructs.
foreach ( $this->extra_permastructs as $permastructname => $struct ) {
if ( is_array( $struct ) ) {
if ( count( $struct ) === 2 ) {
$rules = $this->generate_rewrite_rules( $struct[0], $struct[1] );
} else {
$rules = $this->generate_rewrite_rules( $struct['struct'], $struct['ep_mask'], $struct['paged'], $struct['feed'], $struct['forcomments'], $struct['walk_dirs'], $struct['endpoints'] );
}
} else {
$rules = $this->generate_rewrite_rules( $struct );
}
/**
* Filters rewrite rules used for individual permastructs.
*
* The dynamic portion of the hook name, `$permastructname`, refers
* to the name of the registered permastruct.
*
* Possible hook names include:
*
* - `category_rewrite_rules`
* - `post_format_rewrite_rules`
* - `post_tag_rewrite_rules`
*
* @since 3.1.0
*
* @param string[] $rules Array of rewrite rules generated for the current permastruct, keyed by their regex pattern.
*/
$rules = apply_filters( "{$permastructname}_rewrite_rules", $rules );
if ( 'post_tag' === $permastructname ) {
/**
* Filters rewrite rules used specifically for Tags.
*
* @since 2.3.0
* @deprecated 3.1.0 Use {@see 'post_tag_rewrite_rules'} instead.
*
* @param string[] $rules Array of rewrite rules generated for tags, keyed by their regex pattern.
*/
$rules = apply_filters_deprecated( 'tag_rewrite_rules', array( $rules ), '3.1.0', 'post_tag_rewrite_rules' );
}
$this->extra_rules_top = array_merge( $this->extra_rules_top, $rules );
}
// Put them together.
if ( $this->use_verbose_page_rules ) {
$this->rules = array_merge( $this->extra_rules_top, $robots_rewrite, $favicon_rewrite, $deprecated_files, $registration_pages, $root_rewrite, $comments_rewrite, $search_rewrite, $author_rewrite, $date_rewrite, $page_rewrite, $post_rewrite, $this->extra_rules );
} else {
$this->rules = array_merge( $this->extra_rules_top, $robots_rewrite, $favicon_rewrite, $deprecated_files, $registration_pages, $root_rewrite, $comments_rewrite, $search_rewrite, $author_rewrite, $date_rewrite, $post_rewrite, $page_rewrite, $this->extra_rules );
}
/**
* Fires after the rewrite rules are generated.
*
* @since 1.5.0
*
* @param WP_Rewrite $wp_rewrite Current WP_Rewrite instance (passed by reference).
*/
do_action_ref_array( 'generate_rewrite_rules', array( &$this ) );
/**
* Filters the full set of generated rewrite rules.
*
* @since 1.5.0
*
* @param string[] $rules The compiled array of rewrite rules, keyed by their regex pattern.
*/
$this->rules = apply_filters( 'rewrite_rules_array', $this->rules );
return $this->rules;
}
/**
* Retrieves the rewrite rules.
*
* The difference between this method and WP_Rewrite::rewrite_rules() is that
* this method stores the rewrite rules in the 'rewrite_rules' option and retrieves
* it. This prevents having to process all of the permalinks to get the rewrite rules
* in the form of caching.
*
* @since 1.5.0
*
* @return string[] Array of rewrite rules keyed by their regex pattern.
*/
public function wp_rewrite_rules() {
$this->rules = get_option( 'rewrite_rules' );
if ( empty( $this->rules ) ) {
$this->matches = 'matches';
$this->rewrite_rules();
if ( ! did_action( 'wp_loaded' ) ) {
add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
return $this->rules;
}
update_option( 'rewrite_rules', $this->rules );
}
return $this->rules;
}
/**
* Retrieves mod_rewrite-formatted rewrite rules to write to .htaccess.
*
* Does not actually write to the .htaccess file, but creates the rules for
* the process that will.
*
* Will add the non_wp_rules property rules to the .htaccess file before
* the WordPress rewrite rules one.
*
* @since 1.5.0
*
* @return string
*/
public function mod_rewrite_rules() {
if ( ! $this->using_permalinks() ) {
return '';
}
$site_root = parse_url( site_url() );
if ( isset( $site_root['path'] ) ) {
$site_root = trailingslashit( $site_root['path'] );
}
$home_root = parse_url( home_url() );
if ( isset( $home_root['path'] ) ) {
$home_root = trailingslashit( $home_root['path'] );
} else {
$home_root = '/';
}
$rules = "\n";
$rules .= "RewriteEngine On\n";
$rules .= "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n";
$rules .= "RewriteBase $home_root\n";
// Prevent -f checks on index.php.
$rules .= "RewriteRule ^index\.php$ - [L]\n";
// Add in the rules that don't redirect to WP's index.php (and thus shouldn't be handled by WP at all).
foreach ( (array) $this->non_wp_rules as $match => $query ) {
// Apache 1.3 does not support the reluctant (non-greedy) modifier.
$match = str_replace( '.+?', '.+', $match );
$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
}
if ( $this->use_verbose_rules ) {
$this->matches = '';
$rewrite = $this->rewrite_rules();
$num_rules = count( $rewrite );
$rules .= "RewriteCond %{REQUEST_FILENAME} -f [OR]\n" .
"RewriteCond %{REQUEST_FILENAME} -d\n" .
"RewriteRule ^.*$ - [S=$num_rules]\n";
foreach ( (array) $rewrite as $match => $query ) {
// Apache 1.3 does not support the reluctant (non-greedy) modifier.
$match = str_replace( '.+?', '.+', $match );
if ( str_contains( $query, $this->index ) ) {
$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
} else {
$rules .= 'RewriteRule ^' . $match . ' ' . $site_root . $query . " [QSA,L]\n";
}
}
} else {
$rules .= "RewriteCond %{REQUEST_FILENAME} !-f\n" .
"RewriteCond %{REQUEST_FILENAME} !-d\n" .
"RewriteRule . {$home_root}{$this->index} [L]\n";
}
$rules .= "\n";
/**
* Filters the list of rewrite rules formatted for output to an .htaccess file.
*
* @since 1.5.0
*
* @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
*/
$rules = apply_filters( 'mod_rewrite_rules', $rules );
/**
* Filters the list of rewrite rules formatted for output to an .htaccess file.
*
* @since 1.5.0
* @deprecated 1.5.0 Use the {@see 'mod_rewrite_rules'} filter instead.
*
* @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
*/
return apply_filters_deprecated( 'rewrite_rules', array( $rules ), '1.5.0', 'mod_rewrite_rules' );
}
/**
* Retrieves IIS7 URL Rewrite formatted rewrite rules to write to web.config file.
*
* Does not actually write to the web.config file, but creates the rules for
* the process that will.
*
* @since 2.8.0
*
* @param bool $add_parent_tags Optional. Whether to add parent tags to the rewrite rule sets.
* Default false.
* @return string IIS7 URL rewrite rule sets.
*/
public function iis7_url_rewrite_rules( $add_parent_tags = false ) {
if ( ! $this->using_permalinks() ) {
return '';
}
$rules = '';
if ( $add_parent_tags ) {
$rules .= '';
}
$rules .= '
';
if ( $add_parent_tags ) {
$rules .= '
';
}
/**
* Filters the list of rewrite rules formatted for output to a web.config.
*
* @since 2.8.0
*
* @param string $rules Rewrite rules formatted for IIS web.config.
*/
return apply_filters( 'iis7_url_rewrite_rules', $rules );
}
/**
* Adds a rewrite rule that transforms a URL structure to a set of query vars.
*
* Any value in the $after parameter that isn't 'bottom' will result in the rule
* being placed at the top of the rewrite rules.
*
* @since 2.1.0
* @since 4.4.0 Array support was added to the `$query` parameter.
*
* @param string $regex Regular expression to match request against.
* @param string|array $query The corresponding query vars for this rewrite rule.
* @param string $after Optional. Priority of the new rule. Accepts 'top'
* or 'bottom'. Default 'bottom'.
*/
public function add_rule( $regex, $query, $after = 'bottom' ) {
if ( is_array( $query ) ) {
$external = false;
$query = add_query_arg( $query, 'index.php' );
} else {
$index = ! str_contains( $query, '?' ) ? strlen( $query ) : strpos( $query, '?' );
$front = substr( $query, 0, $index );
$external = $front !== $this->index;
}
// "external" = it doesn't correspond to index.php.
if ( $external ) {
$this->add_external_rule( $regex, $query );
} else {
if ( 'bottom' === $after ) {
$this->extra_rules = array_merge( $this->extra_rules, array( $regex => $query ) );
} else {
$this->extra_rules_top = array_merge( $this->extra_rules_top, array( $regex => $query ) );
}
}
}
/**
* Adds a rewrite rule that doesn't correspond to index.php.
*
* @since 2.1.0
*
* @param string $regex Regular expression to match request against.
* @param string $query The corresponding query vars for this rewrite rule.
*/
public function add_external_rule( $regex, $query ) {
$this->non_wp_rules[ $regex ] = $query;
}
/**
* Adds an endpoint, like /trackback/.
*
* @since 2.1.0
* @since 3.9.0 $query_var parameter added.
* @since 4.3.0 Added support for skipping query var registration by passing `false` to `$query_var`.
*
* @see add_rewrite_endpoint() for full documentation.
* @global WP $wp Current WordPress environment instance.
*
* @param string $name Name of the endpoint.
* @param int $places Endpoint mask describing the places the endpoint should be added.
* Accepts a mask of:
* - `EP_ALL`
* - `EP_NONE`
* - `EP_ALL_ARCHIVES`
* - `EP_ATTACHMENT`
* - `EP_AUTHORS`
* - `EP_CATEGORIES`
* - `EP_COMMENTS`
* - `EP_DATE`
* - `EP_DAY`
* - `EP_MONTH`
* - `EP_PAGES`
* - `EP_PERMALINK`
* - `EP_ROOT`
* - `EP_SEARCH`
* - `EP_TAGS`
* - `EP_YEAR`
* @param string|bool $query_var Optional. Name of the corresponding query variable. Pass `false` to
* skip registering a query_var for this endpoint. Defaults to the
* value of `$name`.
*/
public function add_endpoint( $name, $places, $query_var = true ) {
global $wp;
// For backward compatibility, if null has explicitly been passed as `$query_var`, assume `true`.
if ( true === $query_var || null === $query_var ) {
$query_var = $name;
}
$this->endpoints[] = array( $places, $name, $query_var );
if ( $query_var ) {
$wp->add_query_var( $query_var );
}
}
/**
* Adds a new permalink structure.
*
* A permalink structure (permastruct) is an abstract definition of a set of rewrite rules;
* it is an easy way of expressing a set of regular expressions that rewrite to a set of
* query strings. The new permastruct is added to the WP_Rewrite::$extra_permastructs array.
*
* When the rewrite rules are built by WP_Rewrite::rewrite_rules(), all of these extra
* permastructs are passed to WP_Rewrite::generate_rewrite_rules() which transforms them
* into the regular expressions that many love to hate.
*
* The `$args` parameter gives you control over how WP_Rewrite::generate_rewrite_rules()
* works on the new permastruct.
*
* @since 2.5.0
*
* @param string $name Name for permalink structure.
* @param string $struct Permalink structure (e.g. category/%category%)
* @param array $args {
* Optional. Arguments for building rewrite rules based on the permalink structure.
* Default empty array.
*
* @type bool $with_front Whether the structure should be prepended with `WP_Rewrite::$front`.
* Default true.
* @type int $ep_mask The endpoint mask defining which endpoints are added to the structure.
* Accepts a mask of:
* - `EP_ALL`
* - `EP_NONE`
* - `EP_ALL_ARCHIVES`
* - `EP_ATTACHMENT`
* - `EP_AUTHORS`
* - `EP_CATEGORIES`
* - `EP_COMMENTS`
* - `EP_DATE`
* - `EP_DAY`
* - `EP_MONTH`
* - `EP_PAGES`
* - `EP_PERMALINK`
* - `EP_ROOT`
* - `EP_SEARCH`
* - `EP_TAGS`
* - `EP_YEAR`
* Default `EP_NONE`.
* @type bool $paged Whether archive pagination rules should be added for the structure.
* Default true.
* @type bool $feed Whether feed rewrite rules should be added for the structure. Default true.
* @type bool $forcomments Whether the feed rules should be a query for a comments feed. Default false.
* @type bool $walk_dirs Whether the 'directories' making up the structure should be walked over
* and rewrite rules built for each in-turn. Default true.
* @type bool $endpoints Whether endpoints should be applied to the generated rules. Default true.
* }
*/
public function add_permastruct( $name, $struct, $args = array() ) {
// Back-compat for the old parameters: $with_front and $ep_mask.
if ( ! is_array( $args ) ) {
$args = array( 'with_front' => $args );
}
if ( func_num_args() === 4 ) {
$args['ep_mask'] = func_get_arg( 3 );
}
$defaults = array(
'with_front' => true,
'ep_mask' => EP_NONE,
'paged' => true,
'feed' => true,
'forcomments' => false,
'walk_dirs' => true,
'endpoints' => true,
);
$args = array_intersect_key( $args, $defaults );
$args = wp_parse_args( $args, $defaults );
if ( $args['with_front'] ) {
$struct = $this->front . $struct;
} else {
$struct = $this->root . $struct;
}
$args['struct'] = $struct;
$this->extra_permastructs[ $name ] = $args;
}
/**
* Removes a permalink structure.
*
* @since 4.5.0
*
* @param string $name Name for permalink structure.
*/
public function remove_permastruct( $name ) {
unset( $this->extra_permastructs[ $name ] );
}
/**
* Removes rewrite rules and then recreate rewrite rules.
*
* Calls WP_Rewrite::wp_rewrite_rules() after removing the 'rewrite_rules' option.
* If the function named 'save_mod_rewrite_rules' exists, it will be called.
*
* @since 2.0.1
*
* @param bool $hard Whether to update .htaccess (hard flush) or just update rewrite_rules option (soft flush). Default is true (hard).
*/
public function flush_rules( $hard = true ) {
static $do_hard_later = null;
// Prevent this action from running before everyone has registered their rewrites.
if ( ! did_action( 'wp_loaded' ) ) {
add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
$do_hard_later = ( isset( $do_hard_later ) ) ? $do_hard_later || $hard : $hard;
return;
}
if ( isset( $do_hard_later ) ) {
$hard = $do_hard_later;
unset( $do_hard_later );
}
update_option( 'rewrite_rules', '' );
$this->wp_rewrite_rules();
/**
* Filters whether a "hard" rewrite rule flush should be performed when requested.
*
* A "hard" flush updates .htaccess (Apache) or web.config (IIS).
*
* @since 3.7.0
*
* @param bool $hard Whether to flush rewrite rules "hard". Default true.
*/
if ( ! $hard || ! apply_filters( 'flush_rewrite_rules_hard', true ) ) {
return;
}
if ( function_exists( 'save_mod_rewrite_rules' ) ) {
save_mod_rewrite_rules();
}
if ( function_exists( 'iis7_save_url_rewrite_rules' ) ) {
iis7_save_url_rewrite_rules();
}
}
/**
* Sets up the object's properties.
*
* The 'use_verbose_page_rules' object property will be set to true if the
* permalink structure begins with one of the following: '%postname%', '%category%',
* '%tag%', or '%author%'.
*
* @since 1.5.0
*/
public function init() {
$this->extra_rules = array();
$this->non_wp_rules = array();
$this->endpoints = array();
$this->permalink_structure = get_option( 'permalink_structure' );
$this->front = substr( $this->permalink_structure, 0, strpos( $this->permalink_structure, '%' ) );
$this->root = '';
if ( $this->using_index_permalinks() ) {
$this->root = $this->index . '/';
}
unset( $this->author_structure );
unset( $this->date_structure );
unset( $this->page_structure );
unset( $this->search_structure );
unset( $this->feed_structure );
unset( $this->comment_feed_structure );
$this->use_trailing_slashes = str_ends_with( $this->permalink_structure, '/' );
// Enable generic rules for pages if permalink structure doesn't begin with a wildcard.
if ( preg_match( '/^[^%]*%(?:postname|category|tag|author)%/', $this->permalink_structure ) ) {
$this->use_verbose_page_rules = true;
} else {
$this->use_verbose_page_rules = false;
}
}
/**
* Sets the main permalink structure for the site.
*
* Will update the 'permalink_structure' option, if there is a difference
* between the current permalink structure and the parameter value. Calls
* WP_Rewrite::init() after the option is updated.
*
* Fires the {@see 'permalink_structure_changed'} action once the init call has
* processed passing the old and new values
*
* @since 1.5.0
*
* @param string $permalink_structure Permalink structure.
*/
public function set_permalink_structure( $permalink_structure ) {
if ( $this->permalink_structure !== $permalink_structure ) {
$old_permalink_structure = $this->permalink_structure;
update_option( 'permalink_structure', $permalink_structure );
$this->init();
/**
* Fires after the permalink structure is updated.
*
* @since 2.8.0
*
* @param string $old_permalink_structure The previous permalink structure.
* @param string $permalink_structure The new permalink structure.
*/
do_action( 'permalink_structure_changed', $old_permalink_structure, $permalink_structure );
}
}
/**
* Sets the category base for the category permalink.
*
* Will update the 'category_base' option, if there is a difference between
* the current category base and the parameter value. Calls WP_Rewrite::init()
* after the option is updated.
*
* @since 1.5.0
*
* @param string $category_base Category permalink structure base.
*/
public function set_category_base( $category_base ) {
if ( get_option( 'category_base' ) !== $category_base ) {
update_option( 'category_base', $category_base );
$this->init();
}
}
/**
* Sets the tag base for the tag permalink.
*
* Will update the 'tag_base' option, if there is a difference between the
* current tag base and the parameter value. Calls WP_Rewrite::init() after
* the option is updated.
*
* @since 2.3.0
*
* @param string $tag_base Tag permalink structure base.
*/
public function set_tag_base( $tag_base ) {
if ( get_option( 'tag_base' ) !== $tag_base ) {
update_option( 'tag_base', $tag_base );
$this->init();
}
}
/**
* Constructor - Calls init(), which runs setup.
*
* @since 1.5.0
*/
public function __construct() {
$this->init();
}
}