const SODIUM_LIBRARY_MAJOR_VERSION = 9; const SODIUM_LIBRARY_MINOR_VERSION = 1; const SODIUM_LIBRARY_VERSION = '1.0.8'; const SODIUM_BASE64_VARIANT_ORIGINAL = 1; const SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING = 3; const SODIUM_BASE64_VARIANT_URLSAFE = 5; const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING = 7; const SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES = 32; const SODIUM_CRYPTO_AEAD_AES256GCM_NSECBYTES = 0; const SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES = 12; const SODIUM_CRYPTO_AEAD_AES256GCM_ABYTES = 16; const SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES = 32; const SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES = 0; const SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES = 8; const SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_ABYTES = 16; const SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES = 32; const SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES = 0; const SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES = 12; const SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES = 16; const SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES = 32; const SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NSECBYTES = 0; const SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES = 24; const SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES = 16; const SODIUM_CRYPTO_AUTH_BYTES = 32; const SODIUM_CRYPTO_AUTH_KEYBYTES = 32; const SODIUM_CRYPTO_BOX_SEALBYTES = 16; const SODIUM_CRYPTO_BOX_SECRETKEYBYTES = 32; const SODIUM_CRYPTO_BOX_PUBLICKEYBYTES = 32; const SODIUM_CRYPTO_BOX_KEYPAIRBYTES = 64; const SODIUM_CRYPTO_BOX_MACBYTES = 16; const SODIUM_CRYPTO_BOX_NONCEBYTES = 24; const SODIUM_CRYPTO_BOX_SEEDBYTES = 32; const SODIUM_CRYPTO_KDF_BYTES_MIN = 16; const SODIUM_CRYPTO_KDF_BYTES_MAX = 64; const SODIUM_CRYPTO_KDF_CONTEXTBYTES = 8; const SODIUM_CRYPTO_KDF_KEYBYTES = 32; const SODIUM_CRYPTO_KX_BYTES = 32; const SODIUM_CRYPTO_KX_PRIMITIVE = 'x25519blake2b'; const SODIUM_CRYPTO_KX_SEEDBYTES = 32; const SODIUM_CRYPTO_KX_KEYPAIRBYTES = 64; const SODIUM_CRYPTO_KX_PUBLICKEYBYTES = 32; const SODIUM_CRYPTO_KX_SECRETKEYBYTES = 32; const SODIUM_CRYPTO_KX_SESSIONKEYBYTES = 32; const SODIUM_CRYPTO_GENERICHASH_BYTES = 32; const SODIUM_CRYPTO_GENERICHASH_BYTES_MIN = 16; const SODIUM_CRYPTO_GENERICHASH_BYTES_MAX = 64; const SODIUM_CRYPTO_GENERICHASH_KEYBYTES = 32; const SODIUM_CRYPTO_GENERICHASH_KEYBYTES_MIN = 16; const SODIUM_CRYPTO_GENERICHASH_KEYBYTES_MAX = 64; const SODIUM_CRYPTO_PWHASH_SALTBYTES = 16; const SODIUM_CRYPTO_PWHASH_STRPREFIX = '$argon2id$'; const SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13 = 1; const SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13 = 2; const SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE = 33554432; const SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE = 4; const SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE = 134217728; const SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE = 6; const SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE = 536870912; const SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE = 8; const SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES = 32; const SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_STRPREFIX = '$7$'; const SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE = 534288; const SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE = 16777216; const SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_SENSITIVE = 33554432; const SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_SENSITIVE = 1073741824; const SODIUM_CRYPTO_SCALARMULT_BYTES = 32; const SODIUM_CRYPTO_SCALARMULT_SCALARBYTES = 32; const SODIUM_CRYPTO_SHORTHASH_BYTES = 8; const SODIUM_CRYPTO_SHORTHASH_KEYBYTES = 16; const SODIUM_CRYPTO_SECRETBOX_KEYBYTES = 32; const SODIUM_CRYPTO_SECRETBOX_MACBYTES = 16; const SODIUM_CRYPTO_SECRETBOX_NONCEBYTES = 24; const SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17; const SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24; const SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32; const SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0; const SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1; const SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2; const SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3; const SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80; const SODIUM_CRYPTO_SIGN_BYTES = 64; const SODIUM_CRYPTO_SIGN_SEEDBYTES = 32; const SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES = 32; const SODIUM_CRYPTO_SIGN_SECRETKEYBYTES = 64; const SODIUM_CRYPTO_SIGN_KEYPAIRBYTES = 96; const SODIUM_CRYPTO_STREAM_KEYBYTES = 32; const SODIUM_CRYPTO_STREAM_NONCEBYTES = 24; /** * A gettext Plural-Forms parser. * * @since 4.9.0 */ if ( ! class_exists( 'Plural_Forms', false ) ) : class Plural_Forms { /** * Operator characters. * * @since 4.9.0 * @var string OP_CHARS Operator characters. */ const OP_CHARS = '|&> 6, '<' => 5, '<=' => 5, '>' => 5, '>=' => 5, '==' => 4, '!=' => 4, '&&' => 3, '||' => 2, '?:' => 1, '?' => 1, '(' => 0, ')' => 0, ); /** * Tokens generated from the string. * * @since 4.9.0 * @var array $tokens List of tokens. */ protected $tokens = array(); /** * Cache for repeated calls to the function. * * @since 4.9.0 * @var array $cache Map of $n => $result */ protected $cache = array(); /** * Constructor. * * @since 4.9.0 * * @param string $str Plural function (just the bit after `plural=` from Plural-Forms) */ public function __construct( $str ) { $this->parse( $str ); } /** * Parse a Plural-Forms string into tokens. * * Uses the shunting-yard algorithm to convert the string to Reverse Polish * Notation tokens. * * @since 4.9.0 * * @throws Exception If there is a syntax or parsing error with the string. * * @param string $str String to parse. */ protected function parse( $str ) { $pos = 0; $len = strlen( $str ); // Convert infix operators to postfix using the shunting-yard algorithm. $output = array(); $stack = array(); while ( $pos < $len ) { $next = substr( $str, $pos, 1 ); switch ( $next ) { // Ignore whitespace. case ' ': case "\t": $pos++; break; // Variable (n). case 'n': $output[] = array( 'var' ); $pos++; break; // Parentheses. case '(': $stack[] = $next; $pos++; break; case ')': $found = false; while ( ! empty( $stack ) ) { $o2 = $stack[ count( $stack ) - 1 ]; if ( '(' !== $o2 ) { $output[] = array( 'op', array_pop( $stack ) ); continue; } // Discard open paren. array_pop( $stack ); $found = true; break; } if ( ! $found ) { throw new Exception( 'Mismatched parentheses' ); } $pos++; break; // Operators. case '|': case '&': case '>': case '<': case '!': case '=': case '%': case '?': $end_operator = strspn( $str, self::OP_CHARS, $pos ); $operator = substr( $str, $pos, $end_operator ); if ( ! array_key_exists( $operator, self::$op_precedence ) ) { throw new Exception( sprintf( 'Unknown operator "%s"', $operator ) ); } while ( ! empty( $stack ) ) { $o2 = $stack[ count( $stack ) - 1 ]; // Ternary is right-associative in C. if ( '?:' === $operator || '?' === $operator ) { if ( self::$op_precedence[ $operator ] >= self::$op_precedence[ $o2 ] ) { break; } } elseif ( self::$op_precedence[ $operator ] > self::$op_precedence[ $o2 ] ) { break; } $output[] = array( 'op', array_pop( $stack ) ); } $stack[] = $operator; $pos += $end_operator; break; // Ternary "else". case ':': $found = false; $s_pos = count( $stack ) - 1; while ( $s_pos >= 0 ) { $o2 = $stack[ $s_pos ]; if ( '?' !== $o2 ) { $output[] = array( 'op', array_pop( $stack ) ); $s_pos--; continue; } // Replace. $stack[ $s_pos ] = '?:'; $found = true; break; } if ( ! $found ) { throw new Exception( 'Missing starting "?" ternary operator' ); } $pos++; break; // Default - number or invalid. default: if ( $next >= '0' && $next <= '9' ) { $span = strspn( $str, self::NUM_CHARS, $pos ); $output[] = array( 'value', intval( substr( $str, $pos, $span ) ) ); $pos += $span; break; } throw new Exception( sprintf( 'Unknown symbol "%s"', $next ) ); } } while ( ! empty( $stack ) ) { $o2 = array_pop( $stack ); if ( '(' === $o2 || ')' === $o2 ) { throw new Exception( 'Mismatched parentheses' ); } $output[] = array( 'op', $o2 ); } $this->tokens = $output; } /** * Get the plural form for a number. * * Caches the value for repeated calls. * * @since 4.9.0 * * @param int $num Number to get plural form for. * @return int Plural form value. */ public function get( $num ) { if ( isset( $this->cache[ $num ] ) ) { return $this->cache[ $num ]; } $this->cache[ $num ] = $this->execute( $num ); return $this->cache[ $num ]; } /** * Execute the plural form function. * * @since 4.9.0 * * @throws Exception If the plural form value cannot be calculated. * * @param int $n Variable "n" to substitute. * @return int Plural form value. */ public function execute( $n ) { $stack = array(); $i = 0; $total = count( $this->tokens ); while ( $i < $total ) { $next = $this->tokens[ $i ]; $i++; if ( 'var' === $next[0] ) { $stack[] = $n; continue; } elseif ( 'value' === $next[0] ) { $stack[] = $next[1]; continue; } // Only operators left. switch ( $next[1] ) { case '%': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 % $v2; break; case '||': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 || $v2; break; case '&&': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 && $v2; break; case '<': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 < $v2; break; case '<=': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 <= $v2; break; case '>': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 > $v2; break; case '>=': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 >= $v2; break; case '!=': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 != $v2; break; case '==': $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 == $v2; break; case '?:': $v3 = array_pop( $stack ); $v2 = array_pop( $stack ); $v1 = array_pop( $stack ); $stack[] = $v1 ? $v2 : $v3; break; default: throw new Exception( sprintf( 'Unknown operator "%s"', $next[1] ) ); } } if ( count( $stack ) !== 1 ) { throw new Exception( 'Too many values remaining on the stack' ); } return (int) $stack[0]; } } endif; /** * User API: WP_Role class * * @package WordPress * @subpackage Users * @since 4.4.0 */ /** * Core class used to extend the user roles API. * * @since 2.0.0 */ class WP_Role { /** * Role name. * * @since 2.0.0 * @var string */ public $name; /** * List of capabilities the role contains. * * @since 2.0.0 * @var bool[] Array of key/value pairs where keys represent a capability name and boolean values * represent whether the role has that capability. */ public $capabilities; /** * Constructor - Set up object properties. * * The list of capabilities must have the key as the name of the capability * and the value a boolean of whether it is granted to the role. * * @since 2.0.0 * * @param string $role Role name. * @param bool[] $capabilities Array of key/value pairs where keys represent a capability name and boolean values * represent whether the role has that capability. */ public function __construct( $role, $capabilities ) { $this->name = $role; $this->capabilities = $capabilities; } /** * Assign role a capability. * * @since 2.0.0 * * @param string $cap Capability name. * @param bool $grant Whether role has capability privilege. */ public function add_cap( $cap, $grant = true ) { $this->capabilities[ $cap ] = $grant; wp_roles()->add_cap( $this->name, $cap, $grant ); } /** * Removes a capability from a role. * * @since 2.0.0 * * @param string $cap Capability name. */ public function remove_cap( $cap ) { unset( $this->capabilities[ $cap ] ); wp_roles()->remove_cap( $this->name, $cap ); } /** * Determines whether the role has the given capability. * * @since 2.0.0 * * @param string $cap Capability name. * @return bool Whether the role has the given capability. */ public function has_cap( $cap ) { /** * Filters which capabilities a role has. * * @since 2.0.0 * * @param bool[] $capabilities Array of key/value pairs where keys represent a capability name and boolean values * represent whether the role has that capability. * @param string $cap Capability name. * @param string $name Role name. */ $capabilities = apply_filters( 'role_has_cap', $this->capabilities, $cap, $this->name ); if ( ! empty( $capabilities[ $cap ] ) ) { return $capabilities[ $cap ]; } else { return false; } } } /** * WP_User_Request class. * * Represents user request data loaded from a WP_Post object. * * @since 4.9.6 */ final class WP_User_Request { /** * Request ID. * * @since 4.9.6 * @var int */ public $ID = 0; /** * User ID. * * @since 4.9.6 * @var int */ public $user_id = 0; /** * User email. * * @since 4.9.6 * @var string */ public $email = ''; /** * Action name. * * @since 4.9.6 * @var string */ public $action_name = ''; /** * Current status. * * @since 4.9.6 * @var string */ public $status = ''; /** * Timestamp this request was created. * * @since 4.9.6 * @var int|null */ public $created_timestamp = null; /** * Timestamp this request was last modified. * * @since 4.9.6 * @var int|null */ public $modified_timestamp = null; /** * Timestamp this request was confirmed. * * @since 4.9.6 * @var int|null */ public $confirmed_timestamp = null; /** * Timestamp this request was completed. * * @since 4.9.6 * @var int|null */ public $completed_timestamp = null; /** * Misc data assigned to this request. * * @since 4.9.6 * @var array */ public $request_data = array(); /** * Key used to confirm this request. * * @since 4.9.6 * @var string */ public $confirm_key = ''; /** * Constructor. * * @since 4.9.6 * * @param WP_Post|object $post Post object. */ public function __construct( $post ) { $this->ID = $post->ID; $this->user_id = $post->post_author; $this->email = $post->post_title; $this->action_name = $post->post_name; $this->status = $post->post_status; $this->created_timestamp = strtotime( $post->post_date_gmt ); $this->modified_timestamp = strtotime( $post->post_modified_gmt ); $this->confirmed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_confirmed_timestamp', true ); $this->completed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_completed_timestamp', true ); $this->request_data = json_decode( $post->post_content, true ); $this->confirm_key = $post->post_password; } } /** * Author Template functions for use in themes. * * These functions must be used within the WordPress Loop. * * @link https://codex.wordpress.org/Author_Templates * * @package WordPress * @subpackage Template */ /** * Retrieve the author of the current post. * * @since 1.5.0 * * @global WP_User $authordata The current author's data. * * @param string $deprecated Deprecated. * @return string|null The author's display name. */ function get_the_author( $deprecated = '' ) { global $authordata; if ( ! empty( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '2.1.0' ); } /** * Filters the display name of the current post's author. * * @since 2.9.0 * * @param string|null $display_name The author's display name. */ return apply_filters( 'the_author', is_object( $authordata ) ? $authordata->display_name : null ); } /** * Display the name of the author of the current post. * * The behavior of this function is based off of old functionality predating * get_the_author(). This function is not deprecated, but is designed to echo * the value from get_the_author() and as an result of any old theme that might * still use the old behavior will also pass the value from get_the_author(). * * The normal, expected behavior of this function is to echo the author and not * return it. However, backward compatibility has to be maintained. * * @since 0.71 * * @see get_the_author() * @link https://developer.wordpress.org/reference/functions/the_author/ * * @param string $deprecated Deprecated. * @param bool $deprecated_echo Deprecated. Use get_the_author(). Echo the string or return it. * @return string|null The author's display name, from get_the_author(). */ function the_author( $deprecated = '', $deprecated_echo = true ) { if ( ! empty( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '2.1.0' ); } if ( true !== $deprecated_echo ) { _deprecated_argument( __FUNCTION__, '1.5.0', sprintf( /* translators: %s: get_the_author() */ __( 'Use %s instead if you do not want the value echoed.' ), 'get_the_author()' ) ); } if ( $deprecated_echo ) { echo get_the_author(); } return get_the_author(); } /** * Retrieve the author who last edited the current post. * * @since 2.8.0 * * @return string|void The author's display name. */ function get_the_modified_author() { $last_id = get_post_meta( get_post()->ID, '_edit_last', true ); if ( $last_id ) { $last_user = get_userdata( $last_id ); /** * Filters the display name of the author who last edited the current post. * * @since 2.8.0 * * @param string $display_name The author's display name. */ return apply_filters( 'the_modified_author', $last_user->display_name ); } } /** * Display the name of the author who last edited the current post, * if the author's ID is available. * * @since 2.8.0 * * @see get_the_author() */ function the_modified_author() { echo get_the_modified_author(); } /** * Retrieves the requested data of the author of the current post. * * Valid values for the `$field` parameter include: * * - admin_color * - aim * - comment_shortcuts * - description * - display_name * - first_name * - ID * - jabber * - last_name * - nickname * - plugins_last_view * - plugins_per_page * - rich_editing * - syntax_highlighting * - user_activation_key * - user_description * - user_email * - user_firstname * - user_lastname * - user_level * - user_login * - user_nicename * - user_pass * - user_registered * - user_status * - user_url * - yim * * @since 2.8.0 * * @global WP_User $authordata The current author's data. * * @param string $field Optional. The user field to retrieve. Default empty. * @param int|false $user_id Optional. User ID. * @return string The author's field from the current author's DB object, otherwise an empty string. */ function get_the_author_meta( $field = '', $user_id = false ) { $original_user_id = $user_id; if ( ! $user_id ) { global $authordata; $user_id = isset( $authordata->ID ) ? $authordata->ID : 0; } else { $authordata = get_userdata( $user_id ); } if ( in_array( $field, array( 'login', 'pass', 'nicename', 'email', 'url', 'registered', 'activation_key', 'status' ), true ) ) { $field = 'user_' . $field; } $value = isset( $authordata->$field ) ? $authordata->$field : ''; /** * Filters the value of the requested user metadata. * * The filter name is dynamic and depends on the $field parameter of the function. * * @since 2.8.0 * @since 4.3.0 The `$original_user_id` parameter was added. * * @param string $value The value of the metadata. * @param int $user_id The user ID for the value. * @param int|false $original_user_id The original user ID, as passed to the function. */ return apply_filters( "get_the_author_{$field}", $value, $user_id, $original_user_id ); } /** * Outputs the field from the user's DB object. Defaults to current post's author. * * @since 2.8.0 * * @param string $field Selects the field of the users record. See get_the_author_meta() * for the list of possible fields. * @param int|false $user_id Optional. User ID. * * @see get_the_author_meta() */ function the_author_meta( $field = '', $user_id = false ) { $author_meta = get_the_author_meta( $field, $user_id ); /** * The value of the requested user metadata. * * The filter name is dynamic and depends on the $field parameter of the function. * * @since 2.8.0 * * @param string $author_meta The value of the metadata. * @param int|false $user_id The user ID. */ echo apply_filters( "the_author_{$field}", $author_meta, $user_id ); } /** * Retrieve either author's link or author's name. * * If the author has a home page set, return an HTML link, otherwise just return the * author's name. * * @since 3.0.0 * * @return string|null An HTML link if the author's url exist in user meta, * else the result of get_the_author(). */ function get_the_author_link() { if ( get_the_author_meta( 'url' ) ) { return sprintf( '%3$s', esc_url( get_the_author_meta( 'url' ) ), /* translators: %s: Author's display name. */ esc_attr( sprintf( __( 'Visit %s’s website' ), get_the_author() ) ), get_the_author() ); } else { return get_the_author(); } } /** * Display either author's link or author's name. * * If the author has a home page set, echo an HTML link, otherwise just echo the * author's name. * * @link https://developer.wordpress.org/reference/functions/the_author_link/ * * @since 2.1.0 */ function the_author_link() { echo get_the_author_link(); } /** * Retrieve the number of posts by the author of the current post. * * @since 1.5.0 * * @return int The number of posts by the author. */ function get_the_author_posts() { $post = get_post(); if ( ! $post ) { return 0; } return count_user_posts( $post->post_author, $post->post_type ); } /** * Display the number of posts by the author of the current post. * * @link https://developer.wordpress.org/reference/functions/the_author_posts/ * @since 0.71 */ function the_author_posts() { echo get_the_author_posts(); } /** * Retrieves an HTML link to the author page of the current post's author. * * Returns an HTML-formatted link using get_author_posts_url(). * * @since 4.4.0 * * @global WP_User $authordata The current author's data. * * @return string An HTML link to the author page, or an empty string if $authordata isn't defined. */ function get_the_author_posts_link() { global $authordata; if ( ! is_object( $authordata ) ) { return ''; } $link = sprintf( '', esc_url( get_author_posts_url( $authordata->ID, $authordata->user_nicename ) ), /* translators: %s: Author's display name. */ esc_attr( sprintf( __( 'Posts by %s' ), get_the_author() ) ), get_the_author() ); /** * Filters the link to the author page of the author of the current post. * * @since 2.9.0 * * @param string $link HTML link. */ return apply_filters( 'the_author_posts_link', $link ); } /** * Displays an HTML link to the author page of the current post's author. * * @since 1.2.0 * @since 4.4.0 Converted into a wrapper for get_the_author_posts_link() * * @param string $deprecated Unused. */ function the_author_posts_link( $deprecated = '' ) { if ( ! empty( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '2.1.0' ); } echo get_the_author_posts_link(); } /** * Retrieve the URL to the author page for the user with the ID provided. * * @since 2.1.0 * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param int $author_id Author ID. * @param string $author_nicename Optional. The author's nicename (slug). Default empty. * @return string The URL to the author's page. */ function get_author_posts_url( $author_id, $author_nicename = '' ) { global $wp_rewrite; $auth_ID = (int) $author_id; $link = $wp_rewrite->get_author_permastruct(); if ( empty( $link ) ) { $file = home_url( '/' ); $link = $file . '?author=' . $auth_ID; } else { if ( '' === $author_nicename ) { $user = get_userdata( $author_id ); if ( ! empty( $user->user_nicename ) ) { $author_nicename = $user->user_nicename; } } $link = str_replace( '%author%', $author_nicename, $link ); $link = home_url( user_trailingslashit( $link ) ); } /** * Filters the URL to the author's page. * * @since 2.1.0 * * @param string $link The URL to the author's page. * @param int $author_id The author's ID. * @param string $author_nicename The author's nice name. */ $link = apply_filters( 'author_link', $link, $author_id, $author_nicename ); return $link; } /** * List all the authors of the site, with several options available. * * @link https://developer.wordpress.org/reference/functions/wp_list_authors/ * * @since 1.2.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string|array $args { * Optional. Array or string of default arguments. * * @type string $orderby How to sort the authors. Accepts 'nicename', 'email', 'url', 'registered', * 'user_nicename', 'user_email', 'user_url', 'user_registered', 'name', * 'display_name', 'post_count', 'ID', 'meta_value', 'user_login'. Default 'name'. * @type string $order Sorting direction for $orderby. Accepts 'ASC', 'DESC'. Default 'ASC'. * @type int $number Maximum authors to return or display. Default empty (all authors). * @type bool $optioncount Show the count in parenthesis next to the author's name. Default false. * @type bool $exclude_admin Whether to exclude the 'admin' account, if it exists. Default true. * @type bool $show_fullname Whether to show the author's full name. Default false. * @type bool $hide_empty Whether to hide any authors with no posts. Default true. * @type string $feed If not empty, show a link to the author's feed and use this text as the alt * parameter of the link. Default empty. * @type string $feed_image If not empty, show a link to the author's feed and use this image URL as * clickable anchor. Default empty. * @type string $feed_type The feed type to link to. Possible values include 'rss2', 'atom'. * Default is the value of get_default_feed(). * @type bool $echo Whether to output the result or instead return it. Default true. * @type string $style If 'list', each author is wrapped in an `
  • ` element, otherwise the authors * will be separated by commas. * @type bool $html Whether to list the items in HTML form or plaintext. Default true. * @type int[]|string $exclude Array or comma/space-separated list of author IDs to exclude. Default empty. * @type int[]|string $include Array or comma/space-separated list of author IDs to include. Default empty. * } * @return void|string Void if 'echo' argument is true, list of authors if 'echo' is false. */ function wp_list_authors( $args = '' ) { global $wpdb; $defaults = array( 'orderby' => 'name', 'order' => 'ASC', 'number' => '', 'optioncount' => false, 'exclude_admin' => true, 'show_fullname' => false, 'hide_empty' => true, 'feed' => '', 'feed_image' => '', 'feed_type' => '', 'echo' => true, 'style' => 'list', 'html' => true, 'exclude' => '', 'include' => '', ); $args = wp_parse_args( $args, $defaults ); $return = ''; $query_args = wp_array_slice_assoc( $args, array( 'orderby', 'order', 'number', 'exclude', 'include' ) ); $query_args['fields'] = 'ids'; $authors = get_users( $query_args ); $author_count = array(); foreach ( (array) $wpdb->get_results( "SELECT DISTINCT post_author, COUNT(ID) AS count FROM $wpdb->posts WHERE " . get_private_posts_cap_sql( 'post' ) . ' GROUP BY post_author' ) as $row ) { $author_count[ $row->post_author ] = $row->count; } foreach ( $authors as $author_id ) { $posts = isset( $author_count[ $author_id ] ) ? $author_count[ $author_id ] : 0; if ( ! $posts && $args['hide_empty'] ) { continue; } $author = get_userdata( $author_id ); if ( $args['exclude_admin'] && 'admin' === $author->display_name ) { continue; } if ( $args['show_fullname'] && $author->first_name && $author->last_name ) { $name = "$author->first_name $author->last_name"; } else { $name = $author->display_name; } if ( ! $args['html'] ) { $return .= $name . ', '; continue; // No need to go further to process HTML. } if ( 'list' === $args['style'] ) { $return .= '
  • '; } $link = sprintf( '%3$s', get_author_posts_url( $author->ID, $author->user_nicename ), /* translators: %s: Author's display name. */ esc_attr( sprintf( __( 'Posts by %s' ), $author->display_name ) ), $name ); if ( ! empty( $args['feed_image'] ) || ! empty( $args['feed'] ) ) { $link .= ' '; if ( empty( $args['feed_image'] ) ) { $link .= '('; } $link .= ''; } else { $link .= $name; } $link .= ''; if ( empty( $args['feed_image'] ) ) { $link .= ')'; } } if ( $args['optioncount'] ) { $link .= ' (' . $posts . ')'; } $return .= $link; $return .= ( 'list' === $args['style'] ) ? '
  • ' : ', '; } $return = rtrim( $return, ', ' ); if ( $args['echo'] ) { echo $return; } else { return $return; } } /** * Determines whether this site has more than one author. * * Checks to see if more than one author has published posts. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 3.2.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @return bool Whether or not we have more than one author */ function is_multi_author() { global $wpdb; $is_multi_author = get_transient( 'is_multi_author' ); if ( false === $is_multi_author ) { $rows = (array) $wpdb->get_col( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_type = 'post' AND post_status = 'publish' LIMIT 2" ); $is_multi_author = 1 < count( $rows ) ? 1 : 0; set_transient( 'is_multi_author', $is_multi_author ); } /** * Filters whether the site has more than one author with published posts. * * @since 3.2.0 * * @param bool $is_multi_author Whether $is_multi_author should evaluate as true. */ return apply_filters( 'is_multi_author', (bool) $is_multi_author ); } /** * Helper function to clear the cache for number of authors. * * @since 3.2.0 * @access private */ function __clear_multi_author_cache() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore delete_transient( 'is_multi_author' ); } /** * 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 */ 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 array */ 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 array */ 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 array */ 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 array */ 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 array */ 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 array */ 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 array */ public $queryreplace = array( 'year=', 'monthnum=', 'day=', 'hour=', 'minute=', 'second=', 'name=', 'p=', 'author_name=', 'pagename=', 's=', ); /** * Supported default feeds. * * @since 1.5.0 * @var array */ 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 page 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 ( false !== strpos( $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' ); } /** * Retrieve 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 ( strpos( $struct, '%postname%' ) !== false || strpos( $struct, '%post_id%' ) !== false || strpos( $struct, '%pagename%' ) !== false || ( strpos( $struct, '%year%' ) !== false && strpos( $struct, '%monthnum%' ) !== false && strpos( $struct, '%day%' ) !== false && strpos( $struct, '%hour%' ) !== false && strpos( $struct, '%minute%' ) !== false && strpos( $struct, '%second%' ) !== false ) ) { $post = true; if ( strpos( $struct, '%pagename%' ) !== false ) { $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 ( strpos( $struct, "%$ptype%" ) !== false ) { $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 finalise 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 */ 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, e.g. 'post_tag' (tags), * 'category' (categories), etc. * * @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 $this 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 ( strpos( $query, $this->index ) !== false ) { $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 = false === strpos( $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 = ( '/' === substr( $this->permalink_structure, -1, 1 ) ); // 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 ( $permalink_structure != $this->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(); } } /** * Dependencies API: Styles functions * * @since 2.6.0 * * @package WordPress * @subpackage Dependencies */ /** * Initialize $wp_styles if it has not been set. * * @global WP_Styles $wp_styles * * @since 4.2.0 * * @return WP_Styles WP_Styles instance. */ function wp_styles() { global $wp_styles; if ( ! ( $wp_styles instanceof WP_Styles ) ) { $wp_styles = new WP_Styles(); } return $wp_styles; } /** * Display styles that are in the $handles queue. * * Passing an empty array to $handles prints the queue, * passing an array with one string prints that style, * and passing an array of strings prints those styles. * * @global WP_Styles $wp_styles The WP_Styles object for printing styles. * * @since 2.6.0 * * @param string|bool|array $handles Styles to be printed. Default 'false'. * @return string[] On success, an array of handles of processed WP_Dependencies items; otherwise, an empty array. */ function wp_print_styles( $handles = false ) { global $wp_styles; if ( '' === $handles ) { // For 'wp_head'. $handles = false; } if ( ! $handles ) { /** * Fires before styles in the $handles queue are printed. * * @since 2.6.0 */ do_action( 'wp_print_styles' ); } _wp_scripts_maybe_doing_it_wrong( __FUNCTION__ ); if ( ! ( $wp_styles instanceof WP_Styles ) ) { if ( ! $handles ) { return array(); // No need to instantiate if nothing is there. } } return wp_styles()->do_items( $handles ); } /** * Add extra CSS styles to a registered stylesheet. * * Styles will only be added if the stylesheet is already in the queue. * Accepts a string $data containing the CSS. If two or more CSS code blocks * are added to the same stylesheet $handle, they will be printed in the order * they were added, i.e. the latter added styles can redeclare the previous. * * @see WP_Styles::add_inline_style() * * @since 3.3.0 * * @param string $handle Name of the stylesheet to add the extra styles to. * @param string $data String containing the CSS styles to be added. * @return bool True on success, false on failure. */ function wp_add_inline_style( $handle, $data ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); if ( false !== stripos( $data, '' ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: #is', '$1', $data ) ); } return wp_styles()->add_inline_style( $handle, $data ); } /** * Register a CSS stylesheet. * * @see WP_Dependencies::add() * @link https://www.w3.org/TR/CSS2/media.html#media-types List of CSS media types. * * @since 2.6.0 * @since 4.3.0 A return value was added. * * @param string $handle Name of the stylesheet. Should be unique. * @param string|bool $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory. * If source is set to false, stylesheet is an alias of other stylesheets it depends on. * @param string[] $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array. * @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL * as a query string for cache busting purposes. If version is set to false, a version * number is automatically added equal to current installed WordPress version. * If set to null, no version is added. * @param string $media Optional. The media for which this stylesheet has been defined. * Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like * '(orientation: portrait)' and '(max-width: 640px)'. * @return bool Whether the style has been registered. True on success, false on failure. */ function wp_register_style( $handle, $src, $deps = array(), $ver = false, $media = 'all' ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); return wp_styles()->add( $handle, $src, $deps, $ver, $media ); } /** * Remove a registered stylesheet. * * @see WP_Dependencies::remove() * * @since 2.1.0 * * @param string $handle Name of the stylesheet to be removed. */ function wp_deregister_style( $handle ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); wp_styles()->remove( $handle ); } /** * Enqueue a CSS stylesheet. * * Registers the style if source provided (does NOT overwrite) and enqueues. * * @see WP_Dependencies::add() * @see WP_Dependencies::enqueue() * @link https://www.w3.org/TR/CSS2/media.html#media-types List of CSS media types. * * @since 2.6.0 * * @param string $handle Name of the stylesheet. Should be unique. * @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory. * Default empty. * @param string[] $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array. * @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL * as a query string for cache busting purposes. If version is set to false, a version * number is automatically added equal to current installed WordPress version. * If set to null, no version is added. * @param string $media Optional. The media for which this stylesheet has been defined. * Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like * '(orientation: portrait)' and '(max-width: 640px)'. */ function wp_enqueue_style( $handle, $src = '', $deps = array(), $ver = false, $media = 'all' ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); $wp_styles = wp_styles(); if ( $src ) { $_handle = explode( '?', $handle ); $wp_styles->add( $_handle[0], $src, $deps, $ver, $media ); } $wp_styles->enqueue( $handle ); } /** * Remove a previously enqueued CSS stylesheet. * * @see WP_Dependencies::dequeue() * * @since 3.1.0 * * @param string $handle Name of the stylesheet to be removed. */ function wp_dequeue_style( $handle ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); wp_styles()->dequeue( $handle ); } /** * Check whether a CSS stylesheet has been added to the queue. * * @since 2.8.0 * * @param string $handle Name of the stylesheet. * @param string $list Optional. Status of the stylesheet to check. Default 'enqueued'. * Accepts 'enqueued', 'registered', 'queue', 'to_do', and 'done'. * @return bool Whether style is queued. */ function wp_style_is( $handle, $list = 'enqueued' ) { _wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle ); return (bool) wp_styles()->query( $handle, $list ); } /** * Add metadata to a CSS stylesheet. * * Works only if the stylesheet has already been added. * * Possible values for $key and $value: * 'conditional' string Comments for IE 6, lte IE 7 etc. * 'rtl' bool|string To declare an RTL stylesheet. * 'suffix' string Optional suffix, used in combination with RTL. * 'alt' bool For rel="alternate stylesheet". * 'title' string For preferred/alternate stylesheets. * * @see WP_Dependencies::add_data() * * @since 3.6.0 * * @param string $handle Name of the stylesheet. * @param string $key Name of data point for which we're storing a value. * Accepts 'conditional', 'rtl' and 'suffix', 'alt' and 'title'. * @param mixed $value String containing the CSS data to be added. * @return bool True on success, false on failure. */ function wp_style_add_data( $handle, $key, $value ) { return wp_styles()->add_data( $handle, $key, $value ); } /** * Nav Menu API: Template functions * * @package WordPress * @subpackage Nav_Menus * @since 3.0.0 */ /** Walker_Nav_Menu class */ require_once ABSPATH . WPINC . '/class-walker-nav-menu.php'; /** * Displays a navigation menu. * * @since 3.0.0 * @since 4.7.0 Added the `item_spacing` argument. * @since 5.5.0 Added the `container_aria_label` argument. * * @param array $args { * Optional. Array of nav menu arguments. * * @type int|string|WP_Term $menu Desired menu. Accepts a menu ID, slug, name, or object. * Default empty. * @type string $menu_class CSS class to use for the ul element which forms the menu. * Default 'menu'. * @type string $menu_id The ID that is applied to the ul element which forms the menu. * Default is the menu slug, incremented. * @type string $container Whether to wrap the ul, and what to wrap it with. * Default 'div'. * @type string $container_class Class that is applied to the container. * Default 'menu-{menu slug}-container'. * @type string $container_id The ID that is applied to the container. Default empty. * @type string $container_aria_label The aria-label attribute that is applied to the container * when it's a nav element. Default empty. * @type callable|false $fallback_cb If the menu doesn't exist, a callback function will fire. * Default is 'wp_page_menu'. Set to false for no fallback. * @type string $before Text before the link markup. Default empty. * @type string $after Text after the link markup. Default empty. * @type string $link_before Text before the link text. Default empty. * @type string $link_after Text after the link text. Default empty. * @type bool $echo Whether to echo the menu or return it. Default true. * @type int $depth How many levels of the hierarchy are to be included. * 0 means all. Default 0. * Default 0. * @type object $walker Instance of a custom walker class. Default empty. * @type string $theme_location Theme location to be used. Must be registered with * register_nav_menu() in order to be selectable by the user. * @type string $items_wrap How the list items should be wrapped. Uses printf() format with * numbered placeholders. Default is a ul with an id and class. * @type string $item_spacing Whether to preserve whitespace within the menu's HTML. * Accepts 'preserve' or 'discard'. Default 'preserve'. * } * @return void|string|false Void if 'echo' argument is true, menu output if 'echo' is false. * False if there are no items or no menu was found. */ function wp_nav_menu( $args = array() ) { static $menu_id_slugs = array(); $defaults = array( 'menu' => '', 'container' => 'div', 'container_class' => '', 'container_id' => '', 'container_aria_label' => '', 'menu_class' => 'menu', 'menu_id' => '', 'echo' => true, 'fallback_cb' => 'wp_page_menu', 'before' => '', 'after' => '', 'link_before' => '', 'link_after' => '', 'items_wrap' => '
      %3$s
    ', 'item_spacing' => 'preserve', 'depth' => 0, 'walker' => '', 'theme_location' => '', ); $args = wp_parse_args( $args, $defaults ); if ( ! in_array( $args['item_spacing'], array( 'preserve', 'discard' ), true ) ) { // Invalid value, fall back to default. $args['item_spacing'] = $defaults['item_spacing']; } /** * Filters the arguments used to display a navigation menu. * * @since 3.0.0 * * @see wp_nav_menu() * * @param array $args Array of wp_nav_menu() arguments. */ $args = apply_filters( 'wp_nav_menu_args', $args ); $args = (object) $args; /** * Filters whether to short-circuit the wp_nav_menu() output. * * Returning a non-null value from the filter will short-circuit wp_nav_menu(), * echoing that value if $args->echo is true, returning that value otherwise. * * @since 3.9.0 * * @see wp_nav_menu() * * @param string|null $output Nav menu output to short-circuit with. Default null. * @param stdClass $args An object containing wp_nav_menu() arguments. */ $nav_menu = apply_filters( 'pre_wp_nav_menu', null, $args ); if ( null !== $nav_menu ) { if ( $args->echo ) { echo $nav_menu; return; } return $nav_menu; } // Get the nav menu based on the requested menu. $menu = wp_get_nav_menu_object( $args->menu ); // Get the nav menu based on the theme_location. $locations = get_nav_menu_locations(); if ( ! $menu && $args->theme_location && $locations && isset( $locations[ $args->theme_location ] ) ) { $menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] ); } // Get the first menu that has items if we still can't find a menu. if ( ! $menu && ! $args->theme_location ) { $menus = wp_get_nav_menus(); foreach ( $menus as $menu_maybe ) { $menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) ); if ( $menu_items ) { $menu = $menu_maybe; break; } } } if ( empty( $args->menu ) ) { $args->menu = $menu; } // If the menu exists, get its items. if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) ) { $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) ); } /* * If no menu was found: * - Fall back (if one was specified), or bail. * * If no menu items were found: * - Fall back, but only if no theme location was specified. * - Otherwise, bail. */ if ( ( ! $menu || is_wp_error( $menu ) || ( isset( $menu_items ) && empty( $menu_items ) && ! $args->theme_location ) ) && isset( $args->fallback_cb ) && $args->fallback_cb && is_callable( $args->fallback_cb ) ) { return call_user_func( $args->fallback_cb, (array) $args ); } if ( ! $menu || is_wp_error( $menu ) ) { return false; } $nav_menu = ''; $items = ''; $show_container = false; if ( $args->container ) { /** * Filters the list of HTML tags that are valid for use as menu containers. * * @since 3.0.0 * * @param string[] $tags The acceptable HTML tags for use as menu containers. * Default is array containing 'div' and 'nav'. */ $allowed_tags = apply_filters( 'wp_nav_menu_container_allowedtags', array( 'div', 'nav' ) ); if ( is_string( $args->container ) && in_array( $args->container, $allowed_tags, true ) ) { $show_container = true; $class = $args->container_class ? ' class="' . esc_attr( $args->container_class ) . '"' : ' class="menu-' . $menu->slug . '-container"'; $id = $args->container_id ? ' id="' . esc_attr( $args->container_id ) . '"' : ''; $aria_label = ( 'nav' === $args->container && $args->container_aria_label ) ? ' aria-label="' . esc_attr( $args->container_aria_label ) . '"' : ''; $nav_menu .= '<' . $args->container . $id . $class . $aria_label . '>'; } } // Set up the $menu_item variables. _wp_menu_item_classes_by_context( $menu_items ); $sorted_menu_items = array(); $menu_items_with_children = array(); foreach ( (array) $menu_items as $menu_item ) { $sorted_menu_items[ $menu_item->menu_order ] = $menu_item; if ( $menu_item->menu_item_parent ) { $menu_items_with_children[ $menu_item->menu_item_parent ] = true; } } // Add the menu-item-has-children class where applicable. if ( $menu_items_with_children ) { foreach ( $sorted_menu_items as &$menu_item ) { if ( isset( $menu_items_with_children[ $menu_item->ID ] ) ) { $menu_item->classes[] = 'menu-item-has-children'; } } } unset( $menu_items, $menu_item ); /** * Filters the sorted list of menu item objects before generating the menu's HTML. * * @since 3.1.0 * * @param array $sorted_menu_items The menu items, sorted by each menu item's menu order. * @param stdClass $args An object containing wp_nav_menu() arguments. */ $sorted_menu_items = apply_filters( 'wp_nav_menu_objects', $sorted_menu_items, $args ); $items .= walk_nav_menu_tree( $sorted_menu_items, $args->depth, $args ); unset( $sorted_menu_items ); // Attributes. if ( ! empty( $args->menu_id ) ) { $wrap_id = $args->menu_id; } else { $wrap_id = 'menu-' . $menu->slug; while ( in_array( $wrap_id, $menu_id_slugs, true ) ) { if ( preg_match( '#-(\d+)$#', $wrap_id, $matches ) ) { $wrap_id = preg_replace( '#-(\d+)$#', '-' . ++$matches[1], $wrap_id ); } else { $wrap_id = $wrap_id . '-1'; } } } $menu_id_slugs[] = $wrap_id; $wrap_class = $args->menu_class ? $args->menu_class : ''; /** * Filters the HTML list content for navigation menus. * * @since 3.0.0 * * @see wp_nav_menu() * * @param string $items The HTML list content for the menu items. * @param stdClass $args An object containing wp_nav_menu() arguments. */ $items = apply_filters( 'wp_nav_menu_items', $items, $args ); /** * Filters the HTML list content for a specific navigation menu. * * @since 3.0.0 * * @see wp_nav_menu() * * @param string $items The HTML list content for the menu items. * @param stdClass $args An object containing wp_nav_menu() arguments. */ $items = apply_filters( "wp_nav_menu_{$menu->slug}_items", $items, $args ); // Don't print any markup if there are no items at this point. if ( empty( $items ) ) { return false; } $nav_menu .= sprintf( $args->items_wrap, esc_attr( $wrap_id ), esc_attr( $wrap_class ), $items ); unset( $items ); if ( $show_container ) { $nav_menu .= 'container . '>'; } /** * Filters the HTML content for navigation menus. * * @since 3.0.0 * * @see wp_nav_menu() * * @param string $nav_menu The HTML content for the navigation menu. * @param stdClass $args An object containing wp_nav_menu() arguments. */ $nav_menu = apply_filters( 'wp_nav_menu', $nav_menu, $args ); if ( $args->echo ) { echo $nav_menu; } else { return $nav_menu; } } /** * Adds the class property classes for the current context, if applicable. * * @access private * @since 3.0.0 * * @global WP_Query $wp_query WordPress Query object. * @global WP_Rewrite $wp_rewrite WordPress rewrite component. * * @param array $menu_items The current menu item objects to which to add the class property information. */ function _wp_menu_item_classes_by_context( &$menu_items ) { global $wp_query, $wp_rewrite; $queried_object = $wp_query->get_queried_object(); $queried_object_id = (int) $wp_query->queried_object_id; $active_object = ''; $active_ancestor_item_ids = array(); $active_parent_item_ids = array(); $active_parent_object_ids = array(); $possible_taxonomy_ancestors = array(); $possible_object_parents = array(); $home_page_id = (int) get_option( 'page_for_posts' ); if ( $wp_query->is_singular && ! empty( $queried_object->post_type ) && ! is_post_type_hierarchical( $queried_object->post_type ) ) { foreach ( (array) get_object_taxonomies( $queried_object->post_type ) as $taxonomy ) { if ( is_taxonomy_hierarchical( $taxonomy ) ) { $term_hierarchy = _get_term_hierarchy( $taxonomy ); $terms = wp_get_object_terms( $queried_object_id, $taxonomy, array( 'fields' => 'ids' ) ); if ( is_array( $terms ) ) { $possible_object_parents = array_merge( $possible_object_parents, $terms ); $term_to_ancestor = array(); foreach ( (array) $term_hierarchy as $anc => $descs ) { foreach ( (array) $descs as $desc ) { $term_to_ancestor[ $desc ] = $anc; } } foreach ( $terms as $desc ) { do { $possible_taxonomy_ancestors[ $taxonomy ][] = $desc; if ( isset( $term_to_ancestor[ $desc ] ) ) { $_desc = $term_to_ancestor[ $desc ]; unset( $term_to_ancestor[ $desc ] ); $desc = $_desc; } else { $desc = 0; } } while ( ! empty( $desc ) ); } } } } } elseif ( ! empty( $queried_object->taxonomy ) && is_taxonomy_hierarchical( $queried_object->taxonomy ) ) { $term_hierarchy = _get_term_hierarchy( $queried_object->taxonomy ); $term_to_ancestor = array(); foreach ( (array) $term_hierarchy as $anc => $descs ) { foreach ( (array) $descs as $desc ) { $term_to_ancestor[ $desc ] = $anc; } } $desc = $queried_object->term_id; do { $possible_taxonomy_ancestors[ $queried_object->taxonomy ][] = $desc; if ( isset( $term_to_ancestor[ $desc ] ) ) { $_desc = $term_to_ancestor[ $desc ]; unset( $term_to_ancestor[ $desc ] ); $desc = $_desc; } else { $desc = 0; } } while ( ! empty( $desc ) ); } $possible_object_parents = array_filter( $possible_object_parents ); $front_page_url = home_url(); $front_page_id = (int) get_option( 'page_on_front' ); $privacy_policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); foreach ( (array) $menu_items as $key => $menu_item ) { $menu_items[ $key ]->current = false; $classes = (array) $menu_item->classes; $classes[] = 'menu-item'; $classes[] = 'menu-item-type-' . $menu_item->type; $classes[] = 'menu-item-object-' . $menu_item->object; // This menu item is set as the 'Front Page'. if ( 'post_type' === $menu_item->type && $front_page_id === (int) $menu_item->object_id ) { $classes[] = 'menu-item-home'; } // This menu item is set as the 'Privacy Policy Page'. if ( 'post_type' === $menu_item->type && $privacy_policy_page_id === (int) $menu_item->object_id ) { $classes[] = 'menu-item-privacy-policy'; } // If the menu item corresponds to a taxonomy term for the currently queried non-hierarchical post object. if ( $wp_query->is_singular && 'taxonomy' === $menu_item->type && in_array( (int) $menu_item->object_id, $possible_object_parents, true ) ) { $active_parent_object_ids[] = (int) $menu_item->object_id; $active_parent_item_ids[] = (int) $menu_item->db_id; $active_object = $queried_object->post_type; // If the menu item corresponds to the currently queried post or taxonomy object. } elseif ( $menu_item->object_id == $queried_object_id && ( ( ! empty( $home_page_id ) && 'post_type' === $menu_item->type && $wp_query->is_home && $home_page_id == $menu_item->object_id ) || ( 'post_type' === $menu_item->type && $wp_query->is_singular ) || ( 'taxonomy' === $menu_item->type && ( $wp_query->is_category || $wp_query->is_tag || $wp_query->is_tax ) && $queried_object->taxonomy == $menu_item->object ) ) ) { $classes[] = 'current-menu-item'; $menu_items[ $key ]->current = true; $_anc_id = (int) $menu_item->db_id; while ( ( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) ) && ! in_array( $_anc_id, $active_ancestor_item_ids, true ) ) { $active_ancestor_item_ids[] = $_anc_id; } if ( 'post_type' === $menu_item->type && 'page' === $menu_item->object ) { // Back compat classes for pages to match wp_page_menu(). $classes[] = 'page_item'; $classes[] = 'page-item-' . $menu_item->object_id; $classes[] = 'current_page_item'; } $active_parent_item_ids[] = (int) $menu_item->menu_item_parent; $active_parent_object_ids[] = (int) $menu_item->post_parent; $active_object = $menu_item->object; // If the menu item corresponds to the currently queried post type archive. } elseif ( 'post_type_archive' === $menu_item->type && is_post_type_archive( array( $menu_item->object ) ) ) { $classes[] = 'current-menu-item'; $menu_items[ $key ]->current = true; $_anc_id = (int) $menu_item->db_id; while ( ( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) ) && ! in_array( $_anc_id, $active_ancestor_item_ids, true ) ) { $active_ancestor_item_ids[] = $_anc_id; } $active_parent_item_ids[] = (int) $menu_item->menu_item_parent; // If the menu item corresponds to the currently requested URL. } elseif ( 'custom' === $menu_item->object && isset( $_SERVER['HTTP_HOST'] ) ) { $_root_relative_current = untrailingslashit( $_SERVER['REQUEST_URI'] ); // If it's the customize page then it will strip the query var off the URL before entering the comparison block. if ( is_customize_preview() ) { $_root_relative_current = strtok( untrailingslashit( $_SERVER['REQUEST_URI'] ), '?' ); } $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_root_relative_current ); $raw_item_url = strpos( $menu_item->url, '#' ) ? substr( $menu_item->url, 0, strpos( $menu_item->url, '#' ) ) : $menu_item->url; $item_url = set_url_scheme( untrailingslashit( $raw_item_url ) ); $_indexless_current = untrailingslashit( preg_replace( '/' . preg_quote( $wp_rewrite->index, '/' ) . '$/', '', $current_url ) ); $matches = array( $current_url, urldecode( $current_url ), $_indexless_current, urldecode( $_indexless_current ), $_root_relative_current, urldecode( $_root_relative_current ), ); if ( $raw_item_url && in_array( $item_url, $matches, true ) ) { $classes[] = 'current-menu-item'; $menu_items[ $key ]->current = true; $_anc_id = (int) $menu_item->db_id; while ( ( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) ) && ! in_array( $_anc_id, $active_ancestor_item_ids, true ) ) { $active_ancestor_item_ids[] = $_anc_id; } if ( in_array( home_url(), array( untrailingslashit( $current_url ), untrailingslashit( $_indexless_current ) ), true ) ) { // Back compat for home link to match wp_page_menu(). $classes[] = 'current_page_item'; } $active_parent_item_ids[] = (int) $menu_item->menu_item_parent; $active_parent_object_ids[] = (int) $menu_item->post_parent; $active_object = $menu_item->object; // Give front page item the 'current-menu-item' class when extra query arguments are involved. } elseif ( $item_url == $front_page_url && is_front_page() ) { $classes[] = 'current-menu-item'; } if ( untrailingslashit( $item_url ) == home_url() ) { $classes[] = 'menu-item-home'; } } // Back-compat with wp_page_menu(): add "current_page_parent" to static home page link for any non-page query. if ( ! empty( $home_page_id ) && 'post_type' === $menu_item->type && empty( $wp_query->is_page ) && $home_page_id == $menu_item->object_id ) { $classes[] = 'current_page_parent'; } $menu_items[ $key ]->classes = array_unique( $classes ); } $active_ancestor_item_ids = array_filter( array_unique( $active_ancestor_item_ids ) ); $active_parent_item_ids = array_filter( array_unique( $active_parent_item_ids ) ); $active_parent_object_ids = array_filter( array_unique( $active_parent_object_ids ) ); // Set parent's class. foreach ( (array) $menu_items as $key => $parent_item ) { $classes = (array) $parent_item->classes; $menu_items[ $key ]->current_item_ancestor = false; $menu_items[ $key ]->current_item_parent = false; if ( isset( $parent_item->type ) && ( // Ancestral post object. ( 'post_type' === $parent_item->type && ! empty( $queried_object->post_type ) && is_post_type_hierarchical( $queried_object->post_type ) && in_array( (int) $parent_item->object_id, $queried_object->ancestors, true ) && $parent_item->object != $queried_object->ID ) || // Ancestral term. ( 'taxonomy' === $parent_item->type && isset( $possible_taxonomy_ancestors[ $parent_item->object ] ) && in_array( (int) $parent_item->object_id, $possible_taxonomy_ancestors[ $parent_item->object ], true ) && ( ! isset( $queried_object->term_id ) || $parent_item->object_id != $queried_object->term_id ) ) ) ) { if ( ! empty( $queried_object->taxonomy ) ) { $classes[] = 'current-' . $queried_object->taxonomy . '-ancestor'; } else { $classes[] = 'current-' . $queried_object->post_type . '-ancestor'; } } if ( in_array( (int) $parent_item->db_id, $active_ancestor_item_ids, true ) ) { $classes[] = 'current-menu-ancestor'; $menu_items[ $key ]->current_item_ancestor = true; } if ( in_array( (int) $parent_item->db_id, $active_parent_item_ids, true ) ) { $classes[] = 'current-menu-parent'; $menu_items[ $key ]->current_item_parent = true; } if ( in_array( (int) $parent_item->object_id, $active_parent_object_ids, true ) ) { $classes[] = 'current-' . $active_object . '-parent'; } if ( 'post_type' === $parent_item->type && 'page' === $parent_item->object ) { // Back compat classes for pages to match wp_page_menu(). if ( in_array( 'current-menu-parent', $classes, true ) ) { $classes[] = 'current_page_parent'; } if ( in_array( 'current-menu-ancestor', $classes, true ) ) { $classes[] = 'current_page_ancestor'; } } $menu_items[ $key ]->classes = array_unique( $classes ); } } /** * Retrieves the HTML list content for nav menu items. * * @uses Walker_Nav_Menu to create HTML list content. * @since 3.0.0 * * @param array $items The menu items, sorted by each menu item's menu order. * @param int $depth Depth of the item in reference to parents. * @param stdClass $r An object containing wp_nav_menu() arguments. * @return string The HTML list content for the menu items. */ function walk_nav_menu_tree( $items, $depth, $r ) { $walker = ( empty( $r->walker ) ) ? new Walker_Nav_Menu : $r->walker; return $walker->walk( $items, $depth, $r ); } /** * Prevents a menu item ID from being used more than once. * * @since 3.0.1 * @access private * * @param string $id * @param object $item * @return string */ function _nav_menu_item_id_use_once( $id, $item ) { static $_used_ids = array(); if ( in_array( $item->ID, $_used_ids, true ) ) { return ''; } $_used_ids[] = $item->ID; return $id; } /** * REST API: WP_REST_Post_Statuses_Controller class * * @package WordPress * @subpackage REST_API * @since 4.7.0 */ /** * Core class used to access post statuses via the REST API. * * @since 4.7.0 * * @see WP_REST_Controller */ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller { /** * Constructor. * * @since 4.7.0 */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'statuses'; } /** * Registers the routes for the objects of the controller. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', array( 'args' => array( 'status' => array( 'description' => __( 'An alphanumeric identifier for the status.' ), 'type' => 'string', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to read post statuses. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( 'edit' === $request['context'] ) { $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); foreach ( $types as $type ) { if ( current_user_can( $type->cap->edit_posts ) ) { return true; } } return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to manage post statuses.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves all post statuses, depending on user context. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $data = array(); $statuses = get_post_stati( array( 'internal' => false ), 'object' ); $statuses['trash'] = get_post_status_object( 'trash' ); foreach ( $statuses as $slug => $obj ) { $ret = $this->check_read_permission( $obj ); if ( ! $ret ) { continue; } $status = $this->prepare_item_for_response( $obj, $request ); $data[ $obj->name ] = $this->prepare_response_for_collection( $status ); } return rest_ensure_response( $data ); } /** * Checks if a given request has access to read a post status. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { $status = get_post_status_object( $request['status'] ); if ( empty( $status ) ) { return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) ); } $check = $this->check_read_permission( $status ); if ( ! $check ) { return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view status.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks whether a given post status should be visible. * * @since 4.7.0 * * @param object $status Post status. * @return bool True if the post status is visible, otherwise false. */ protected function check_read_permission( $status ) { if ( true === $status->public ) { return true; } if ( false === $status->internal || 'trash' === $status->name ) { $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); foreach ( $types as $type ) { if ( current_user_can( $type->cap->edit_posts ) ) { return true; } } } return false; } /** * Retrieves a specific post status. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $obj = get_post_status_object( $request['status'] ); if ( empty( $obj ) ) { return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) ); } $data = $this->prepare_item_for_response( $obj, $request ); return rest_ensure_response( $data ); } /** * Prepares a post status object for serialization. * * @since 4.7.0 * * @param stdClass $status Post status data. * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response Post status data. */ public function prepare_item_for_response( $status, $request ) { $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'name', $fields, true ) ) { $data['name'] = $status->label; } if ( in_array( 'private', $fields, true ) ) { $data['private'] = (bool) $status->private; } if ( in_array( 'protected', $fields, true ) ) { $data['protected'] = (bool) $status->protected; } if ( in_array( 'public', $fields, true ) ) { $data['public'] = (bool) $status->public; } if ( in_array( 'queryable', $fields, true ) ) { $data['queryable'] = (bool) $status->publicly_queryable; } if ( in_array( 'show_in_list', $fields, true ) ) { $data['show_in_list'] = (bool) $status->show_in_admin_all_list; } if ( in_array( 'slug', $fields, true ) ) { $data['slug'] = $status->name; } if ( in_array( 'date_floating', $fields, true ) ) { $data['date_floating'] = $status->date_floating; } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( 'publish' === $status->name ) { $response->add_link( 'archives', rest_url( 'wp/v2/posts' ) ); } else { $response->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( 'wp/v2/posts' ) ) ); } /** * Filters a post status returned from the REST API. * * Allows modification of the status data right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param object $status The original post status object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_status', $response, $status, $request ); } /** * Retrieves the post status' schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'status', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The title for the status.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'private' => array( 'description' => __( 'Whether posts with this status should be private.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), 'protected' => array( 'description' => __( 'Whether posts with this status should be protected.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), 'public' => array( 'description' => __( 'Whether posts of this status should be shown in the front end of the site.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'queryable' => array( 'description' => __( 'Whether posts with this status should be publicly-queryable.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'show_in_list' => array( 'description' => __( 'Whether to include posts in the edit listing for their post type.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the status.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'date_floating' => array( 'description' => __( 'Whether posts of this status may have floating published dates.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Collection parameters. */ public function get_collection_params() { return array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); } } /** * REST API: WP_REST_Block_Directory_Controller class * * @package WordPress * @subpackage REST_API * @since 5.5.0 */ /** * Controller which provides REST endpoint for the blocks. * * @since 5.5.0 * * @see WP_REST_Controller */ class WP_REST_Block_Directory_Controller extends WP_REST_Controller { /** * Constructs the controller. */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'block-directory'; } /** * Registers the necessary REST API routes. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base . '/search', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks whether a given request has permission to install and activate plugins. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * * @return true|WP_Error True if the request has permission, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_block_directory_cannot_view', __( 'Sorry, you are not allowed to browse the block directory.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Search and retrieve blocks metadata * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; $response = plugins_api( 'query_plugins', array( 'block' => $request['term'], 'per_page' => $request['per_page'], 'page' => $request['page'], ) ); if ( is_wp_error( $response ) ) { $response->add_data( array( 'status' => 500 ) ); return $response; } $result = array(); foreach ( $response->plugins as $plugin ) { // If the API returned a plugin with empty data for 'blocks', skip it. if ( empty( $plugin['blocks'] ) ) { continue; } $data = $this->prepare_item_for_response( $plugin, $request ); $result[] = $this->prepare_response_for_collection( $data ); } return rest_ensure_response( $result ); } /** * Parse block metadata for a block, and prepare it for an API repsonse. * * @since 5.5.0 * * @param array $plugin The plugin metadata. * @param WP_REST_Request $request Request object. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $plugin, $request ) { // There might be multiple blocks in a plugin. Only the first block is mapped. $block_data = reset( $plugin['blocks'] ); // A data array containing the properties we'll return. $block = array( 'name' => $block_data['name'], 'title' => ( $block_data['title'] ? $block_data['title'] : $plugin['name'] ), 'description' => wp_trim_words( $plugin['short_description'], 30, '...' ), 'id' => $plugin['slug'], 'rating' => $plugin['rating'] / 20, 'rating_count' => (int) $plugin['num_ratings'], 'active_installs' => (int) $plugin['active_installs'], 'author_block_rating' => $plugin['author_block_rating'] / 20, 'author_block_count' => (int) $plugin['author_block_count'], 'author' => wp_strip_all_tags( $plugin['author'] ), 'icon' => ( isset( $plugin['icons']['1x'] ) ? $plugin['icons']['1x'] : 'block-default' ), 'last_updated' => gmdate( 'Y-m-d\TH:i:s', strtotime( $plugin['last_updated'] ) ), 'humanized_updated' => sprintf( /* translators: %s: Human-readable time difference. */ __( '%s ago' ), human_time_diff( strtotime( $plugin['last_updated'] ) ) ), ); $this->add_additional_fields_to_object( $block, $request ); $response = new WP_REST_Response( $block ); $response->add_links( $this->prepare_links( $plugin ) ); return $response; } /** * Generates a list of links to include in the response for the plugin. * * @since 5.5.0 * * @param array $plugin The plugin data from WordPress.org. * * @return array */ protected function prepare_links( $plugin ) { $links = array( 'https://api.w.org/install-plugin' => array( 'href' => add_query_arg( 'slug', urlencode( $plugin['slug'] ), rest_url( 'wp/v2/plugins' ) ), ), ); $plugin_file = $this->find_plugin_for_slug( $plugin['slug'] ); if ( $plugin_file ) { $links['https://api.w.org/plugin'] = array( 'href' => rest_url( 'wp/v2/plugins/' . substr( $plugin_file, 0, - 4 ) ), 'embeddable' => true, ); } return $links; } /** * Finds an installed plugin for the given slug. * * @since 5.5.0 * * @param string $slug The WordPress.org directory slug for a plugin. * * @return string The plugin file found matching it. */ protected function find_plugin_for_slug( $slug ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugin_files = get_plugins( '/' . $slug ); if ( ! $plugin_files ) { return ''; } $plugin_files = array_keys( $plugin_files ); return $slug . '/' . reset( $plugin_files ); } /** * Retrieves the theme's schema, conforming to JSON Schema. * * @since 5.5.0 * * @return array Item schema data. */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $this->schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'block-directory-item', 'type' => 'object', 'properties' => array( 'name' => array( 'description' => __( 'The block name, in namespace/block-name format.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'title' => array( 'description' => __( 'The block title, in human readable format.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'description' => array( 'description' => __( 'A short description of the block, in human readable format.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'id' => array( 'description' => __( 'The block slug.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'rating' => array( 'description' => __( 'The star rating of the block.' ), 'type' => 'integer', 'context' => array( 'view' ), ), 'rating_count' => array( 'description' => __( 'The number of ratings.' ), 'type' => 'integer', 'context' => array( 'view' ), ), 'active_installs' => array( 'description' => __( 'The number sites that have activated this block.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'author_block_rating' => array( 'description' => __( 'The average rating of blocks published by the same author.' ), 'type' => 'integer', 'context' => array( 'view' ), ), 'author_block_count' => array( 'description' => __( 'The number of blocks published by the same author.' ), 'type' => 'integer', 'context' => array( 'view' ), ), 'author' => array( 'description' => __( 'The WordPress.org username of the block author.' ), 'type' => 'string', 'context' => array( 'view' ), ), 'icon' => array( 'description' => __( 'The block icon.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view' ), ), 'last_updated' => array( 'description' => __( 'The date when the block was last updated, in fuzzy human readable format.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view' ), ), 'humanized_updated' => array( 'description' => __( 'The date when the block was last updated, in fuzzy human readable format.' ), 'type' => 'string', 'context' => array( 'view' ), ), ), ); return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the search params for the blocks collection. * * @since 5.5.0 * * @return array Collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params['term'] = array( 'description' => __( 'Limit result set to blocks matching the search term.' ), 'type' => 'string', 'required' => true, 'minLength' => 1, ); unset( $query_params['search'] ); /** * Filters REST API collection parameters for the block directory controller. * * @since 5.5.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_block_directory_collection_params', $query_params ); } } /** * REST API: WP_REST_Meta_Fields class * * @package WordPress * @subpackage REST_API * @since 4.7.0 */ /** * Core class to manage meta values for an object via the REST API. * * @since 4.7.0 */ abstract class WP_REST_Meta_Fields { /** * Retrieves the object meta type. * * @since 4.7.0 * * @return string One of 'post', 'comment', 'term', 'user', or anything * else supported by `_get_meta_table()`. */ abstract protected function get_meta_type(); /** * Retrieves the object meta subtype. * * @since 4.9.8 * * @return string Subtype for the meta type, or empty string if no specific subtype. */ protected function get_meta_subtype() { return ''; } /** * Retrieves the object type for register_rest_field(). * * @since 4.7.0 * * @return string The REST field type, such as post type name, taxonomy name, 'comment', or `user`. */ abstract protected function get_rest_field_type(); /** * Registers the meta field. * * @since 4.7.0 * @deprecated 5.6.0 * * @see register_rest_field() */ public function register_field() { _deprecated_function( __METHOD__, '5.6.0' ); register_rest_field( $this->get_rest_field_type(), 'meta', array( 'get_callback' => array( $this, 'get_value' ), 'update_callback' => array( $this, 'update_value' ), 'schema' => $this->get_field_schema(), ) ); } /** * Retrieves the meta field value. * * @since 4.7.0 * * @param int $object_id Object ID to fetch meta for. * @param WP_REST_Request $request Full details about the request. * @return array Array containing the meta values keyed by name. */ public function get_value( $object_id, $request ) { $fields = $this->get_registered_fields(); $response = array(); foreach ( $fields as $meta_key => $args ) { $name = $args['name']; $all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, false ); if ( $args['single'] ) { if ( empty( $all_values ) ) { $value = $args['schema']['default']; } else { $value = $all_values[0]; } $value = $this->prepare_value_for_response( $value, $request, $args ); } else { $value = array(); foreach ( $all_values as $row ) { $value[] = $this->prepare_value_for_response( $row, $request, $args ); } } $response[ $name ] = $value; } return $response; } /** * Prepares a meta value for a response. * * This is required because some native types cannot be stored correctly * in the database, such as booleans. We need to cast back to the relevant * type before passing back to JSON. * * @since 4.7.0 * * @param mixed $value Meta value to prepare. * @param WP_REST_Request $request Current request object. * @param array $args Options for the field. * @return mixed Prepared value. */ protected function prepare_value_for_response( $value, $request, $args ) { if ( ! empty( $args['prepare_callback'] ) ) { $value = call_user_func( $args['prepare_callback'], $value, $request, $args ); } return $value; } /** * Updates meta values. * * @since 4.7.0 * * @param array $meta Array of meta parsed from the request. * @param int $object_id Object ID to fetch meta for. * @return null|WP_Error Null on success, WP_Error object on failure. */ public function update_value( $meta, $object_id ) { $fields = $this->get_registered_fields(); foreach ( $fields as $meta_key => $args ) { $name = $args['name']; if ( ! array_key_exists( $name, $meta ) ) { continue; } $value = $meta[ $name ]; /* * A null value means reset the field, which is essentially deleting it * from the database and then relying on the default value. * * Non-single meta can also be removed by passing an empty array. */ if ( is_null( $value ) || ( array() === $value && ! $args['single'] ) ) { $args = $this->get_registered_fields()[ $meta_key ]; if ( $args['single'] ) { $current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true ); if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) { return new WP_Error( 'rest_invalid_stored_value', /* translators: %s: Custom field key. */ sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 ) ); } } $result = $this->delete_meta_value( $object_id, $meta_key, $name ); if ( is_wp_error( $result ) ) { return $result; } continue; } if ( ! $args['single'] && is_array( $value ) && count( array_filter( $value, 'is_null' ) ) ) { return new WP_Error( 'rest_invalid_stored_value', /* translators: %s: Custom field key. */ sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 ) ); } $is_valid = rest_validate_value_from_schema( $value, $args['schema'], 'meta.' . $name ); if ( is_wp_error( $is_valid ) ) { $is_valid->add_data( array( 'status' => 400 ) ); return $is_valid; } $value = rest_sanitize_value_from_schema( $value, $args['schema'] ); if ( $args['single'] ) { $result = $this->update_meta_value( $object_id, $meta_key, $name, $value ); } else { $result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value ); } if ( is_wp_error( $result ) ) { return $result; } } return null; } /** * Deletes a meta value for an object. * * @since 4.7.0 * * @param int $object_id Object ID the field belongs to. * @param string $meta_key Key for the field. * @param string $name Name for the field that is exposed in the REST API. * @return true|WP_Error True if meta field is deleted, WP_Error otherwise. */ protected function delete_meta_value( $object_id, $meta_key, $name ) { $meta_type = $this->get_meta_type(); if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) { return new WP_Error( 'rest_cannot_delete', /* translators: %s: Custom field key. */ sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), array( 'key' => $name, 'status' => rest_authorization_required_code(), ) ); } if ( null === get_metadata_raw( $meta_type, $object_id, wp_slash( $meta_key ) ) ) { return true; } if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ) ) ) { return new WP_Error( 'rest_meta_database_error', __( 'Could not delete meta value from database.' ), array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR, ) ); } return true; } /** * Updates multiple meta values for an object. * * Alters the list of values in the database to match the list of provided values. * * @since 4.7.0 * * @param int $object_id Object ID to update. * @param string $meta_key Key for the custom field. * @param string $name Name for the field that is exposed in the REST API. * @param array $values List of values to update to. * @return true|WP_Error True if meta fields are updated, WP_Error otherwise. */ protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) { $meta_type = $this->get_meta_type(); if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { return new WP_Error( 'rest_cannot_update', /* translators: %s: Custom field key. */ sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), array( 'key' => $name, 'status' => rest_authorization_required_code(), ) ); } $current_values = get_metadata( $meta_type, $object_id, $meta_key, false ); $subtype = get_object_subtype( $meta_type, $object_id ); $to_remove = $current_values; $to_add = $values; foreach ( $to_add as $add_key => $value ) { $remove_keys = array_keys( array_filter( $current_values, function ( $stored_value ) use ( $meta_key, $subtype, $value ) { return $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $value ); } ) ); if ( empty( $remove_keys ) ) { continue; } if ( count( $remove_keys ) > 1 ) { // To remove, we need to remove first, then add, so don't touch. continue; } $remove_key = $remove_keys[0]; unset( $to_remove[ $remove_key ] ); unset( $to_add[ $add_key ] ); } /* * `delete_metadata` removes _all_ instances of the value, so only call once. Otherwise, * `delete_metadata` will return false for subsequent calls of the same value. * Use serialization to produce a predictable string that can be used by array_unique. */ $to_remove = array_map( 'maybe_unserialize', array_unique( array_map( 'maybe_serialize', $to_remove ) ) ); foreach ( $to_remove as $value ) { if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { return new WP_Error( 'rest_meta_database_error', /* translators: %s: Custom field key. */ sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR, ) ); } } foreach ( $to_add as $value ) { if ( ! add_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { return new WP_Error( 'rest_meta_database_error', /* translators: %s: Custom field key. */ sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR, ) ); } } return true; } /** * Updates a meta value for an object. * * @since 4.7.0 * * @param int $object_id Object ID to update. * @param string $meta_key Key for the custom field. * @param string $name Name for the field that is exposed in the REST API. * @param mixed $value Updated value. * @return true|WP_Error True if the meta field was updated, WP_Error otherwise. */ protected function update_meta_value( $object_id, $meta_key, $name, $value ) { $meta_type = $this->get_meta_type(); if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { return new WP_Error( 'rest_cannot_update', /* translators: %s: Custom field key. */ sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), array( 'key' => $name, 'status' => rest_authorization_required_code(), ) ); } // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false. $old_value = get_metadata( $meta_type, $object_id, $meta_key ); $subtype = get_object_subtype( $meta_type, $object_id ); if ( 1 === count( $old_value ) && $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $old_value[0], $value ) ) { return true; } if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { return new WP_Error( 'rest_meta_database_error', /* translators: %s: Custom field key. */ sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR, ) ); } return true; } /** * Checks if the user provided value is equivalent to a stored value for the given meta key. * * @since 5.5.0 * * @param string $meta_key The meta key being checked. * @param string $subtype The object subtype. * @param mixed $stored_value The currently stored value retrieved from get_metadata(). * @param mixed $user_value The value provided by the user. * @return bool */ protected function is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $user_value ) { $args = $this->get_registered_fields()[ $meta_key ]; $sanitized = sanitize_meta( $meta_key, $user_value, $this->get_meta_type(), $subtype ); if ( in_array( $args['type'], array( 'string', 'number', 'integer', 'boolean' ), true ) ) { // The return value of get_metadata will always be a string for scalar types. $sanitized = (string) $sanitized; } return $sanitized === $stored_value; } /** * Retrieves all the registered meta fields. * * @since 4.7.0 * * @return array Registered fields. */ protected function get_registered_fields() { $registered = array(); $meta_type = $this->get_meta_type(); $meta_subtype = $this->get_meta_subtype(); $meta_keys = get_registered_meta_keys( $meta_type ); if ( ! empty( $meta_subtype ) ) { $meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $meta_type, $meta_subtype ) ); } foreach ( $meta_keys as $name => $args ) { if ( empty( $args['show_in_rest'] ) ) { continue; } $rest_args = array(); if ( is_array( $args['show_in_rest'] ) ) { $rest_args = $args['show_in_rest']; } $default_args = array( 'name' => $name, 'single' => $args['single'], 'type' => ! empty( $args['type'] ) ? $args['type'] : null, 'schema' => array(), 'prepare_callback' => array( $this, 'prepare_value' ), ); $default_schema = array( 'type' => $default_args['type'], 'description' => empty( $args['description'] ) ? '' : $args['description'], 'default' => isset( $args['default'] ) ? $args['default'] : null, ); $rest_args = array_merge( $default_args, $rest_args ); $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null; $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type; if ( null === $rest_args['schema']['default'] ) { $rest_args['schema']['default'] = static::get_empty_value_for_type( $type ); } $rest_args['schema'] = rest_default_additional_properties_to_false( $rest_args['schema'] ); if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) { continue; } if ( empty( $rest_args['single'] ) ) { $rest_args['schema'] = array( 'type' => 'array', 'items' => $rest_args['schema'], ); } $registered[ $name ] = $rest_args; } return $registered; } /** * Retrieves the object's meta schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array Field schema data. */ public function get_field_schema() { $fields = $this->get_registered_fields(); $schema = array( 'description' => __( 'Meta fields.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'properties' => array(), 'arg_options' => array( 'sanitize_callback' => null, 'validate_callback' => array( $this, 'check_meta_is_array' ), ), ); foreach ( $fields as $args ) { $schema['properties'][ $args['name'] ] = $args['schema']; } return $schema; } /** * Prepares a meta value for output. * * Default preparation for meta fields. Override by passing the * `prepare_callback` in your `show_in_rest` options. * * @since 4.7.0 * * @param mixed $value Meta value from the database. * @param WP_REST_Request $request Request object. * @param array $args REST-specific options for the meta key. * @return mixed Value prepared for output. If a non-JsonSerializable object, null. */ public static function prepare_value( $value, $request, $args ) { if ( $args['single'] ) { $schema = $args['schema']; } else { $schema = $args['schema']['items']; } if ( '' === $value && in_array( $schema['type'], array( 'boolean', 'integer', 'number' ), true ) ) { $value = static::get_empty_value_for_type( $schema['type'] ); } if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) { return null; } return rest_sanitize_value_from_schema( $value, $schema ); } /** * Check the 'meta' value of a request is an associative array. * * @since 4.7.0 * * @param mixed $value The meta value submitted in the request. * @param WP_REST_Request $request Full details about the request. * @param string $param The parameter name. * @return array|false The meta array, if valid, false otherwise. */ public function check_meta_is_array( $value, $request, $param ) { if ( ! is_array( $value ) ) { return false; } return $value; } /** * Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting * is specified. * * This is needed to restrict properties of objects in meta values to only * registered items, as the REST API will allow additional properties by * default. * * @since 5.3.0 * @deprecated 5.6.0 Use rest_default_additional_properties_to_false() instead. * * @param array $schema The schema array. * @return array */ protected function default_additional_properties_to_false( $schema ) { _deprecated_function( __METHOD__, '5.6.0', 'rest_default_additional_properties_to_false()' ); return rest_default_additional_properties_to_false( $schema ); } /** * Gets the empty value for a schema type. * * @since 5.3.0 * * @param string $type The schema type. * @return mixed */ protected static function get_empty_value_for_type( $type ) { switch ( $type ) { case 'string': return ''; case 'boolean': return false; case 'integer': return 0; case 'number': return 0.0; case 'array': case 'object': return array(); default: return null; } } }