function update_user_level_from_caps() { global $wpdb; $this->user_level = array_reduce( array_keys( $this->allcaps ), array( $this, 'level_reduction' ), 0 ); update_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level', $this->user_level ); } /** * Adds capability and grant or deny access to capability. * * @since 2.0.0 * * @param string $cap Capability name. * @param bool $grant Whether to grant capability to user. */ public function add_cap( $cap, $grant = true ) { $this->caps[ $cap ] = $grant; update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Removes capability from user. * * @since 2.0.0 * * @param string $cap Capability name. */ public function remove_cap( $cap ) { if ( ! isset( $this->caps[ $cap ] ) ) { return; } unset( $this->caps[ $cap ] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Removes all of the capabilities of the user. * * @since 2.1.0 * * @global wpdb $wpdb WordPress database abstraction object. */ public function remove_all_caps() { global $wpdb; $this->caps = array(); delete_user_meta( $this->ID, $this->cap_key ); delete_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level' ); $this->get_role_caps(); } /** * Returns whether the user has the specified capability. * * This function also accepts an ID of an object to check against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to * map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * $user->has_cap( 'edit_posts' ); * $user->has_cap( 'edit_post', $post->ID ); * $user->has_cap( 'edit_post_meta', $post->ID, $meta_key ); * * While checking against a role in place of a capability is supported in part, this practice is discouraged as it * may produce unreliable results. * * @since 2.0.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * * @see map_meta_cap() * * @param string $cap Capability name. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the user has the given capability, or, if an object ID is passed, whether the user has * the given capability for that object. */ public function has_cap( $cap, ...$args ) { if ( is_numeric( $cap ) ) { _deprecated_argument( __FUNCTION__, '2.0.0', __( 'Usage of user levels is deprecated. Use capabilities instead.' ) ); $cap = $this->translate_level_to_cap( $cap ); } $caps = map_meta_cap( $cap, $this->ID, ...$args ); // Multisite super admin has all caps by definition, Unless specifically denied. if ( is_multisite() && is_super_admin( $this->ID ) ) { if ( in_array( 'do_not_allow', $caps, true ) ) { return false; } return true; } // Maintain BC for the argument passed to the "user_has_cap" filter. $args = array_merge( array( $cap, $this->ID ), $args ); /** * Dynamically filter a user's capabilities. * * @since 2.0.0 * @since 3.7.0 Added the `$user` parameter. * * @param bool[] $allcaps Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. * @param string[] $caps Required primitive capabilities for the requested capability. * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability. * @type int $1 Concerned user ID. * @type mixed ...$2 Optional second and further parameters, typically object ID. * } * @param WP_User $user The user object. */ $capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this ); // Everyone is allowed to exist. $capabilities['exist'] = true; // Nobody is allowed to do things they are not allowed to do. unset( $capabilities['do_not_allow'] ); // Must have ALL requested caps. foreach ( (array) $caps as $cap ) { if ( empty( $capabilities[ $cap ] ) ) { return false; } } return true; } /** * Converts numeric level to level capability name. * * Prepends 'level_' to level number. * * @since 2.0.0 * * @param int $level Level number, 1 to 10. * @return string */ public function translate_level_to_cap( $level ) { return 'level_' . $level; } /** * Sets the site to operate on. Defaults to the current site. * * @since 3.0.0 * @deprecated 4.9.0 Use WP_User::for_site() * * @param int $blog_id Optional. Site ID, defaults to current site. */ public function for_blog( $blog_id = '' ) { _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); $this->for_site( $blog_id ); } /** * Sets the site to operate on. Defaults to the current site. * * @since 4.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $site_id Site ID to initialize user capabilities for. Default is the current site. */ public function for_site( $site_id = '' ) { global $wpdb; if ( ! empty( $site_id ) ) { $this->site_id = absint( $site_id ); } else { $this->site_id = get_current_blog_id(); } $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; $this->caps = $this->get_caps_data(); $this->get_role_caps(); } /** * Gets the ID of the site for which the user's capabilities are currently initialized. * * @since 4.9.0 * * @return int Site ID. */ public function get_site_id() { return $this->site_id; } /** * Gets the available user capabilities data. * * @since 4.9.0 * * @return bool[] List of capabilities keyed by the capability name, * e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`. */ private function get_caps_data() { $caps = get_user_meta( $this->ID, $this->cap_key, true ); if ( ! is_array( $caps ) ) { return array(); } return $caps; } } perties Array containing the properties of the style name, label, * style_handle (name of the stylesheet to be enqueued), * inline_style (string containing the CSS to be added). * @return bool True if the block style was registered with success and false otherwise. */ function register_block_style( $block_name, $style_properties ) { return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties ); } /** * Unregisters a block style. * * @since 5.3.0 * * @param string $block_name Block type name including namespace. * @param string $block_style_name Block style name. * @return bool True if the block style was unregistered with success and false otherwise. */ function unregister_block_style( $block_name, $block_style_name ) { return WP_Block_Styles_Registry::get_instance()->unregister( $block_name, $block_style_name ); } /** * Checks whether the current block type supports the feature requested. * * @since 5.8.0 * * @param WP_Block_Type $block_type Block type to check for support. * @param array $feature Path to a specific feature to check support for. * @param mixed $default_value Optional. Fallback value for feature support. Default false. * @return bool Whether the feature is supported. */ function block_has_support( $block_type, $feature, $default_value = false ) { $block_support = $default_value; if ( $block_type && property_exists( $block_type, 'supports' ) ) { $block_support = _wp_array_get( $block_type->supports, $feature, $default_value ); } return true === $block_support || is_array( $block_support ); } /** * Converts typography keys declared under `supports.*` to `supports.typography.*`. * * Displays a `_doing_it_wrong()` notice when a block using the older format is detected. * * @since 5.8.0 * * @param array $metadata Metadata for registering a block type. * @return array Filtered metadata for registering a block type. */ function wp_migrate_old_typography_shape( $metadata ) { if ( ! isset( $metadata['supports'] ) ) { return $metadata; } $typography_keys = array( '__experimentalFontFamily', '__experimentalFontStyle', '__experimentalFontWeight', '__experimentalLetterSpacing', '__experimentalTextDecoration', '__experimentalTextTransform', 'fontSize', 'lineHeight', ); foreach ( $typography_keys as $typography_key ) { $support_for_key = _wp_array_get( $metadata['supports'], array( $typography_key ), null ); if ( null !== $support_for_key ) { _doing_it_wrong( 'register_block_type_from_metadata()', sprintf( /* translators: 1: Block type, 2: Typography supports key, e.g: fontSize, lineHeight, etc. 3: block.json, 4: Old metadata key, 5: New metadata key. */ __( 'Block "%1$s" is declaring %2$s support in %3$s file under %4$s. %2$s support is now declared under %5$s.' ), $metadata['name'], "$typography_key", 'block.json', "supports.$typography_key", "supports.typography.$typography_key" ), '5.8.0' ); _wp_array_set( $metadata['supports'], array( 'typography', $typography_key ), $support_for_key ); unset( $metadata['supports'][ $typography_key ] ); } } return $metadata; } /** * Helper function that constructs a WP_Query args array from * a `Query` block properties. * * It's used in Query Loop, Query Pagination Numbers and Query Pagination Next blocks. * * @since 5.8.0 * @since 6.1.0 Added `query_loop_block_query_vars` filter and `parents` support in query. * * @param WP_Block $block Block instance. * @param int $page Current query's page. * * @return array Returns the constructed WP_Query arguments. */ function build_query_vars_from_query_block( $block, $page ) { $query = array( 'post_type' => 'post', 'order' => 'DESC', 'orderby' => 'date', 'post__not_in' => array(), ); if ( isset( $block->context['query'] ) ) { if ( ! empty( $block->context['query']['postType'] ) ) { $post_type_param = $block->context['query']['postType']; if ( is_post_type_viewable( $post_type_param ) ) { $query['post_type'] = $post_type_param; } } if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) { $sticky = get_option( 'sticky_posts' ); if ( 'only' === $block->context['query']['sticky'] ) { /* * Passing an empty array to post__in will return have_posts() as true (and all posts will be returned). * Logic should be used before hand to determine if WP_Query should be used in the event that the array * being passed to post__in is empty. * * @see https://core.trac.wordpress.org/ticket/28099 */ $query['post__in'] = ! empty( $sticky ) ? $sticky : array( 0 ); $query['ignore_sticky_posts'] = 1; } else { $query['post__not_in'] = array_merge( $query['post__not_in'], $sticky ); } } if ( ! empty( $block->context['query']['exclude'] ) ) { $excluded_post_ids = array_map( 'intval', $block->context['query']['exclude'] ); $excluded_post_ids = array_filter( $excluded_post_ids ); $query['post__not_in'] = array_merge( $query['post__not_in'], $excluded_post_ids ); } if ( isset( $block->context['query']['perPage'] ) && is_numeric( $block->context['query']['perPage'] ) ) { $per_page = absint( $block->context['query']['perPage'] ); $offset = 0; if ( isset( $block->context['query']['offset'] ) && is_numeric( $block->context['query']['offset'] ) ) { $offset = absint( $block->context['query']['offset'] ); } $query['offset'] = ( $per_page * ( $page - 1 ) ) + $offset; $query['posts_per_page'] = $per_page; } // Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility. if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) { $tax_query = array(); if ( ! empty( $block->context['query']['categoryIds'] ) ) { $tax_query[] = array( 'taxonomy' => 'category', 'terms' => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ), 'include_children' => false, ); } if ( ! empty( $block->context['query']['tagIds'] ) ) { $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ), 'include_children' => false, ); } $query['tax_query'] = $tax_query; } if ( ! empty( $block->context['query']['taxQuery'] ) ) { $query['tax_query'] = array(); foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) { if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) { $query['tax_query'][] = array( 'taxonomy' => $taxonomy, 'terms' => array_filter( array_map( 'intval', $terms ) ), 'include_children' => false, ); } } } if ( isset( $block->context['query']['order'] ) && in_array( strtoupper( $block->context['query']['order'] ), array( 'ASC', 'DESC' ), true ) ) { $query['order'] = strtoupper( $block->context['query']['order'] ); } if ( isset( $block->context['query']['orderBy'] ) ) { $query['orderby'] = $block->context['query']['orderBy']; } if ( isset( $block->context['query']['author'] ) ) { if ( is_array( $block->context['query']['author'] ) ) { $query['author__in'] = array_filter( array_map( 'intval', $block->context['query']['author'] ) ); } elseif ( is_string( $block->context['query']['author'] ) ) { $query['author__in'] = array_filter( array_map( 'intval', explode( ',', $block->context['query']['author'] ) ) ); } elseif ( is_int( $block->context['query']['author'] ) && $block->context['query']['author'] > 0 ) { $query['author'] = $block->context['query']['author']; } } if ( ! empty( $block->context['query']['search'] ) ) { $query['s'] = $block->context['query']['search']; } if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical( $query['post_type'] ) ) { $query['post_parent__in'] = array_filter( array_map( 'intval', $block->context['query']['parents'] ) ); } } /** * Filters the arguments which will be passed to `WP_Query` for the Query Loop Block. * * Anything to this filter should be compatible with the `WP_Query` API to form * the query context which will be passed down to the Query Loop Block's children. * This can help, for example, to include additional settings or meta queries not * directly supported by the core Query Loop Block, and extend its capabilities. * * Please note that this will only influence the query that will be rendered on the * front-end. The editor preview is not affected by this filter. Also, worth noting * that the editor preview uses the REST API, so, ideally, one should aim to provide * attributes which are also compatible with the REST API, in order to be able to * implement identical queries on both sides. * * @since 6.1.0 * * @param array $query Array containing parameters for `WP_Query` as parsed by the block context. * @param WP_Block $block Block instance. * @param int $page Current query's page. */ return apply_filters( 'query_loop_block_query_vars', $query, $block, $page ); } /** * Helper function that returns the proper pagination arrow HTML for * `QueryPaginationNext` and `QueryPaginationPrevious` blocks based * on the provided `paginationArrow` from `QueryPagination` context. * * It's used in QueryPaginationNext and QueryPaginationPrevious blocks. * * @since 5.9.0 * * @param WP_Block $block Block instance. * @param bool $is_next Flag for handling `next/previous` blocks. * @return string|null The pagination arrow HTML or null if there is none. */ function get_query_pagination_arrow( $block, $is_next ) { $arrow_map = array( 'none' => '', 'arrow' => array( 'next' => '→', 'previous' => '←', ), 'chevron' => array( 'next' => '»', 'previous' => '«', ), ); if ( ! empty( $block->context['paginationArrow'] ) && array_key_exists( $block->context['paginationArrow'], $arrow_map ) && ! empty( $arrow_map[ $block->context['paginationArrow'] ] ) ) { $pagination_type = $is_next ? 'next' : 'previous'; $arrow_attribute = $block->context['paginationArrow']; $arrow = $arrow_map[ $block->context['paginationArrow'] ][ $pagination_type ]; $arrow_classes = "wp-block-query-pagination-$pagination_type-arrow is-arrow-$arrow_attribute"; return ""; } return null; } /** * Helper function that constructs a comment query vars array from the passed * block properties. * * It's used with the Comment Query Loop inner blocks. * * @since 6.0.0 * * @param WP_Block $block Block instance. * @return array Returns the comment query parameters to use with the * WP_Comment_Query constructor. */ function build_comment_query_vars_from_block( $block ) { $comment_args = array( 'orderby' => 'comment_date_gmt', 'order' => 'ASC', 'status' => 'approve', 'no_found_rows' => false, ); if ( is_user_logged_in() ) { $comment_args['include_unapproved'] = array( get_current_user_id() ); } else { $unapproved_email = wp_get_unapproved_comment_author_email(); if ( $unapproved_email ) { $comment_args['include_unapproved'] = array( $unapproved_email ); } } if ( ! empty( $block->context['postId'] ) ) { $comment_args['post_id'] = (int) $block->context['postId']; } if ( get_option( 'thread_comments' ) ) { $comment_args['hierarchical'] = 'threaded'; } else { $comment_args['hierarchical'] = false; } if ( get_option( 'page_comments' ) === '1' || get_option( 'page_comments' ) === true ) { $per_page = get_option( 'comments_per_page' ); $default_page = get_option( 'default_comments_page' ); if ( $per_page > 0 ) { $comment_args['number'] = $per_page; $page = (int) get_query_var( 'cpage' ); if ( $page ) { $comment_args['paged'] = $page; } elseif ( 'oldest' === $default_page ) { $comment_args['paged'] = 1; } elseif ( 'newest' === $default_page ) { $max_num_pages = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages; if ( 0 !== $max_num_pages ) { $comment_args['paged'] = $max_num_pages; } } // Set the `cpage` query var to ensure the previous and next pagination links are correct // when inheriting the Discussion Settings. if ( 0 === $page && isset( $comment_args['paged'] ) && $comment_args['paged'] > 0 ) { set_query_var( 'cpage', $comment_args['paged'] ); } } } return $comment_args; } /** * Helper function that returns the proper pagination arrow HTML for * `CommentsPaginationNext` and `CommentsPaginationPrevious` blocks based on the * provided `paginationArrow` from `CommentsPagination` context. * * It's used in CommentsPaginationNext and CommentsPaginationPrevious blocks. * * @since 6.0.0 * * @param WP_Block $block Block instance. * @param string $pagination_type Optional. Type of the arrow we will be rendering. * Accepts 'next' or 'previous'. Default 'next'. * @return string|null The pagination arrow HTML or null if there is none. */ function get_comments_pagination_arrow( $block, $pagination_type = 'next' ) { $arrow_map = array( 'none' => '', 'arrow' => array( 'next' => '→', 'previous' => '←', ), 'chevron' => array( 'next' => '»', 'previous' => '«', ), ); if ( ! empty( $block->context['comments/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ] ) ) { $arrow_attribute = $block->context['comments/paginationArrow']; $arrow = $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ]; $arrow_classes = "wp-block-comments-pagination-$pagination_type-arrow is-arrow-$arrow_attribute"; return ""; } return null; } /** * Strips all HTML from the content of footnotes, and sanitizes the ID. * This function expects slashed data on the footnotes content. * * @access private * @since 6.3.2 * * @param string $footnotes JSON encoded string of an array containing the content and ID of each footnote. * @return string Filtered content without any HTML on the footnote content and with the sanitized id. */ function _wp_filter_post_meta_footnotes( $footnotes ) { $footnotes_decoded = json_decode( $footnotes, true ); if ( ! is_array( $footnotes_decoded ) ) { return ''; } $footnotes_sanitized = array(); foreach ( $footnotes_decoded as $footnote ) { if ( ! empty( $footnote['content'] ) && ! empty( $footnote['id'] ) ) { $footnotes_sanitized[] = array( 'id' => sanitize_key( $footnote['id'] ), 'content' => wp_unslash( wp_filter_post_kses( wp_slash( $footnote['content'] ) ) ), ); } } return wp_json_encode( $footnotes_sanitized ); } /** * Adds the filters to filter footnotes meta field. * * @access private * @since 6.3.2 */ function _wp_footnotes_kses_init_filters() { add_filter( 'sanitize_post_meta_footnotes', '_wp_filter_post_meta_footnotes' ); } /** * Removes the filters that filter footnotes meta field. * * @access private * @since 6.3.2 */ function _wp_footnotes_remove_filters() { remove_filter( 'sanitize_post_meta_footnotes', '_wp_filter_post_meta_footnotes' ); } /** * Registers the filter of footnotes meta field if the user does not have unfiltered_html capability. * * @access private * @since 6.3.2 */ function _wp_footnotes_kses_init() { _wp_footnotes_remove_filters(); if ( ! current_user_can( 'unfiltered_html' ) ) { _wp_footnotes_kses_init_filters(); } } /** * Initializes footnotes meta field filters when imported data should be filtered. * * This filter is the last being executed on force_filtered_html_on_import. * If the input of the filter is true it means we are in an import situation and should * enable kses, independently of the user capabilities. * So in that case we call _wp_footnotes_kses_init_filters; * * @access private * @since 6.3.2 * * @param string $arg Input argument of the filter. * @return string Input argument of the filter. */ function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) { // force_filtered_html_on_import is true we need to init the global styles kses filters. if ( $arg ) { _wp_footnotes_kses_init_filters(); } return $arg; }