diff --git a/lib/compat/wordpress-5.9/block-template-utils.php b/lib/compat/wordpress-5.9/block-template-utils.php index dd08edd8e600b..cfd6910e3859c 100644 --- a/lib/compat/wordpress-5.9/block-template-utils.php +++ b/lib/compat/wordpress-5.9/block-template-utils.php @@ -529,272 +529,6 @@ function _build_block_template_result_from_file( $template_file, $template_type } } -if ( ! function_exists( '_build_block_template_result_from_post' ) ) { - /** - * Build a unified template object based a post Object. - * - * @param WP_Post $post Template post. - * - * @return Gutenberg_Block_Template|WP_Error Template. - */ - function _build_block_template_result_from_post( $post ) { - $default_template_types = get_default_block_template_types(); - $terms = get_the_terms( $post, 'wp_theme' ); - - if ( is_wp_error( $terms ) ) { - return $terms; - } - - if ( ! $terms ) { - return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'gutenberg' ) ); - } - - $origin = get_post_meta( $post->ID, 'origin', true ); - - $theme = $terms[0]->name; - $has_theme_file = wp_get_theme()->get_stylesheet() === $theme && - null !== _get_block_template_file( $post->post_type, $post->post_name ); - - $template = new Gutenberg_Block_Template(); - $template->wp_id = $post->ID; - $template->id = $theme . '//' . $post->post_name; - $template->theme = $theme; - $template->content = $post->post_content; - $template->slug = $post->post_name; - $template->source = 'custom'; - $template->origin = ! empty( $origin ) ? $origin : null; - $template->type = $post->post_type; - $template->description = $post->post_excerpt; - $template->title = $post->post_title; - $template->status = $post->post_status; - $template->has_theme_file = $has_theme_file; - $template->is_custom = true; - $template->author = $post->post_author; - - if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) { - $template->is_custom = false; - } - - if ( 'wp_template_part' === $post->post_type ) { - $type_terms = get_the_terms( $post, 'wp_template_part_area' ); - if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) { - $template->area = $type_terms[0]->name; - } - } - - return $template; - } -} - - -/** - * Retrieves a list of unified template objects based on a query. - * - * @param array $query { - * Optional. Arguments to retrieve templates. - * - * @type array $slug__in List of slugs to include. - * @type int $wp_id Post ID of customized template. - * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only). - * @type string $post_type Post type to get the templates for. - * } - * @param array $template_type wp_template or wp_template_part. - * - * @return array Templates. - */ -function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_template' ) { - /** - * Filters the block templates array before the query takes place. - * - * Return a non-null value to bypass the WordPress queries. - * - * @since 10.8 - * - * @param Gutenberg_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query, - * or null to allow WP to run it's normal queries. - * @param array $query { - * Optional. Arguments to retrieve templates. - * - * @type array $slug__in List of slugs to include. - * @type int $wp_id Post ID of customized template. - * @type string $post_type Post type to get the templates for. - * } - * @param array $template_type wp_template or wp_template_part. - */ - $templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type ); - if ( ! is_null( $templates ) ) { - return $templates; - } - - $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; - $wp_query_args = array( - 'post_status' => array( 'auto-draft', 'draft', 'publish' ), - 'post_type' => $template_type, - 'posts_per_page' => -1, - 'no_found_rows' => true, - 'tax_query' => array( - array( - 'taxonomy' => 'wp_theme', - 'field' => 'name', - 'terms' => wp_get_theme()->get_stylesheet(), - ), - ), - ); - - if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) { - $wp_query_args['tax_query'][] = array( - 'taxonomy' => 'wp_template_part_area', - 'field' => 'name', - 'terms' => $query['area'], - ); - $wp_query_args['tax_query']['relation'] = 'AND'; - } - - if ( isset( $query['slug__in'] ) ) { - $wp_query_args['post_name__in'] = $query['slug__in']; - } - - // This is only needed for the regular templates/template parts CPT listing and editor. - if ( isset( $query['wp_id'] ) ) { - $wp_query_args['p'] = $query['wp_id']; - } else { - $wp_query_args['post_status'] = 'publish'; - } - - $template_query = new WP_Query( $wp_query_args ); - $query_result = array(); - foreach ( $template_query->posts as $post ) { - $template = _build_block_template_result_from_post( $post ); - - if ( is_wp_error( $template ) ) { - continue; - } - - if ( $post_type && ! $template->is_custom ) { - continue; - } - - $query_result[] = $template; - } - - if ( ! isset( $query['wp_id'] ) ) { - $template_files = _get_block_templates_files( $template_type ); - foreach ( $template_files as $template_file ) { - $template = _build_block_template_result_from_file( $template_file, $template_type ); - - if ( $post_type && ! $template->is_custom ) { - continue; - } - - if ( $post_type && - isset( $template->post_types ) && - ! in_array( $post_type, $template->post_types, true ) - ) { - continue; - } - - $is_not_custom = false === array_search( - wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'], - array_column( $query_result, 'id' ), - true - ); - $fits_slug_query = - ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ); - $fits_area_query = - ! isset( $query['area'] ) || $template_file['area'] === $query['area']; - $should_include = $is_not_custom && $fits_slug_query && $fits_area_query; - if ( $should_include ) { - $query_result[] = $template; - } - } - } - /** - * Filters the array of queried block templates array after they've been fetched. - * - * @since 10.8 - * - * @param Gutenberg_Block_Template[] $query_result Array of found block templates. - * @param array $query { - * Optional. Arguments to retrieve templates. - * - * @type array $slug__in List of slugs to include. - * @type int $wp_id Post ID of customized template. - * } - * @param array $template_type wp_template or wp_template_part. - */ - return apply_filters( 'get_block_templates', $query_result, $query, $template_type ); -} - -/** - * Retrieves a single unified template object using its id. - * - * @param string $id Template unique identifier (example: theme_slug//template_slug). - * @param array $template_type wp_template or wp_template_part. - * - * @return Gutenberg_Block_Template|null Template. - */ -function gutenberg_get_block_template( $id, $template_type = 'wp_template' ) { - /** - * Filters the block template object before the query takes place. - * - * Return a non-null value to bypass the WordPress queries. - * - * @since 10.8 - * - * @param Gutenberg_Block_Template|null $block_template Return block template object to short-circuit the default query, - * or null to allow WP to run it's normal queries. - * @param string $id Template unique identifier (example: theme_slug//template_slug). - * @param array $template_type wp_template or wp_template_part. - */ - $block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type ); - if ( ! is_null( $block_template ) ) { - return $block_template; - } - - $parts = explode( '//', $id, 2 ); - if ( count( $parts ) < 2 ) { - return null; - } - list( $theme, $slug ) = $parts; - $wp_query_args = array( - 'post_name__in' => array( $slug ), - 'post_type' => $template_type, - 'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ), - 'posts_per_page' => 1, - 'no_found_rows' => true, - 'tax_query' => array( - array( - 'taxonomy' => 'wp_theme', - 'field' => 'name', - 'terms' => $theme, - ), - ), - ); - $template_query = new WP_Query( $wp_query_args ); - $posts = $template_query->posts; - - if ( count( $posts ) > 0 ) { - $template = _build_block_template_result_from_post( $posts[0] ); - - if ( ! is_wp_error( $template ) ) { - return $template; - } - } - - $block_template = get_block_file_template( $id, $template_type ); - - /** - * Filters the queried block template object after it's been fetched. - * - * @since 10.8 - * - * @param Gutenberg_Block_Template|null $block_template The found block template, or null if there isn't one. - * @param string $id Template unique identifier (example: theme_slug//template_slug). - * @param array $template_type wp_template or wp_template_part. - */ - return apply_filters( 'get_block_template', $block_template, $id, $template_type ); -} - if ( ! function_exists( 'get_block_file_template' ) ) { /** * Retrieves a single unified template object using its id. diff --git a/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php b/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php deleted file mode 100644 index 085acca1d036d..0000000000000 --- a/lib/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php +++ /dev/null @@ -1,687 +0,0 @@ -post_type = $post_type; - $this->namespace = 'wp/v2'; - $obj = get_post_type_object( $post_type ); - $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; - } - - /** - * Registers the controllers routes. - * - * @return void - */ - public function register_routes() { - // Lists all templates. - 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(), - ), - array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => array( $this, 'create_item' ), - 'permission_callback' => array( $this, 'create_item_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - - // Lists/updates a single template based on the given id. - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P[\/\s%\w\.\(\)\[\]\@_\-]+)', - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), - 'args' => array( - 'id' => array( - 'description' => __( 'The id of a template', 'gutenberg' ), - 'type' => 'string', - 'sanitize_callback' => array( $this, '_sanitize_template_id' ), - ), - ), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'update_item' ), - 'permission_callback' => array( $this, 'update_item_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ), - array( - 'methods' => WP_REST_Server::DELETABLE, - 'callback' => array( $this, 'delete_item' ), - 'permission_callback' => array( $this, 'delete_item_permissions_check' ), - 'args' => array( - 'force' => array( - 'type' => 'boolean', - 'default' => false, - 'description' => __( 'Whether to bypass Trash and force deletion.', 'gutenberg' ), - ), - ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - - /** - * Checks if the user has permissions to make the request. - * - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ - protected function permissions_check() { - // Verify if the current user has edit_theme_options capability. - // This capability is required to edit/view/delete templates. - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'rest_cannot_manage_templates', - __( 'Sorry, you are not allowed to access the templates on this site.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - - return true; - } - - /** - * Requesting this endpoint for a template like "twentytwentytwo//home" requires using - * a path like /wp/v2/templates/twentytwentytwo//home. There are special cases when - * WordPress routing corrects the name to contain only a single slash like "twentytwentytwo/home". - * - * This method doubles the last slash if it's not already doubled. It relies on the template - * ID format {theme_name}//{template_slug} and the fact that slugs cannot contain slashes. - * - * See https://core.trac.wordpress.org/ticket/54507 for more context - * - * @param string $id Template ID. - * @return string Sanitized template ID. - */ - public function _sanitize_template_id( $id ) { - // Decode empty space. - $last_slash_pos = strrpos( $id, '/' ); - $id = urldecode( $id ); - - if ( false === $last_slash_pos ) { - return $id; - } - - $is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/'; - if ( $is_double_slashed ) { - return $id; - } - return ( - substr( $id, 0, $last_slash_pos ) - . '/' - . substr( $id, $last_slash_pos ) - ); - } - - /** - * Checks if a given request has access to read templates. - * - * @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 ) { - return $this->permissions_check( $request ); - } - - /** - * Returns a list of templates. - * - * @param WP_REST_Request $request The request instance. - * - * @return WP_REST_Response - */ - public function get_items( $request ) { - $query = array(); - if ( isset( $request['wp_id'] ) ) { - $query['wp_id'] = $request['wp_id']; - } - if ( isset( $request['area'] ) ) { - $query['area'] = $request['area']; - } - if ( isset( $request['post_type'] ) ) { - $query['post_type'] = $request['post_type']; - } - - $templates = array(); - foreach ( gutenberg_get_block_templates( $query, $this->post_type ) as $template ) { - $data = $this->prepare_item_for_response( $template, $request ); - $templates[] = $this->prepare_response_for_collection( $data ); - } - - return rest_ensure_response( $templates ); - } - - /** - * Checks if a given request has access to read a single template. - * - * @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 ) { - return $this->permissions_check( $request ); - } - - /** - * Returns the given template - * - * @param WP_REST_Request $request The request instance. - * - * @return WP_REST_Response|WP_Error - */ - public function get_item( $request ) { - if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { - $template = get_block_file_template( $request['id'], $this->post_type ); - } else { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); - } - - if ( ! $template ) { - return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - return $this->prepare_item_for_response( $template, $request ); - } - - /** - * Checks if a given request has access to write a single template. - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. - */ - public function update_item_permissions_check( $request ) { - return $this->permissions_check( $request ); - } - - /** - * Updates a single template. - * - * @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 update_item( $request ) { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); - if ( ! $template ) { - return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); - } - - if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { - wp_delete_post( $template->wp_id, true ); - return $this->prepare_item_for_response( get_block_file_template( $request['id'], $this->post_type ), $request ); - } - - $changes = $this->prepare_item_for_database( $request ); - - if ( is_wp_error( $changes ) ) { - return $changes; - } - - if ( 'custom' === $template->source ) { - $result = wp_update_post( wp_slash( (array) $changes ), true ); - } else { - $result = wp_insert_post( wp_slash( (array) $changes ), true ); - } - if ( is_wp_error( $result ) ) { - return $result; - } - - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); - $fields_update = $this->update_additional_fields_for_object( $template, $request ); - if ( is_wp_error( $fields_update ) ) { - return $fields_update; - } - - return $this->prepare_item_for_response( - gutenberg_get_block_template( $request['id'], $this->post_type ), - $request - ); - } - - /** - * Checks if a given request has access to create a template. - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. - */ - public function create_item_permissions_check( $request ) { - return $this->permissions_check( $request ); - } - - /** - * Creates a single template. - * - * @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 create_item( $request ) { - $changes = $this->prepare_item_for_database( $request ); - - if ( is_wp_error( $changes ) ) { - return $changes; - } - - $changes->post_name = $request['slug']; - $result = wp_insert_post( wp_slash( (array) $changes ), true ); - if ( is_wp_error( $result ) ) { - return $result; - } - $posts = gutenberg_get_block_templates( array( 'wp_id' => $result ), $this->post_type ); - if ( ! count( $posts ) ) { - return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.', 'gutenberg' ) ); - } - $id = $posts[0]->id; - $template = gutenberg_get_block_template( $id, $this->post_type ); - $fields_update = $this->update_additional_fields_for_object( $template, $request ); - if ( is_wp_error( $fields_update ) ) { - return $fields_update; - } - - return $this->prepare_item_for_response( - gutenberg_get_block_template( $id, $this->post_type ), - $request - ); - } - - /** - * Checks if a given request has access to delete a single template. - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise. - */ - public function delete_item_permissions_check( $request ) { - return $this->permissions_check( $request ); - } - - /** - * Deletes a single template. - * - * @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 delete_item( $request ) { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); - if ( ! $template ) { - return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); - } - if ( 'custom' !== $template->source ) { - return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.', 'gutenberg' ), array( 'status' => 400 ) ); - } - - $id = $template->wp_id; - $force = (bool) $request['force']; - - // If we're forcing, then delete permanently. - if ( $force ) { - $previous = $this->prepare_item_for_response( $template, $request ); - wp_delete_post( $id, true ); - $response = new WP_REST_Response(); - $response->set_data( - array( - 'deleted' => true, - 'previous' => $previous->get_data(), - ) - ); - - return $response; - } - - // Otherwise, only trash if we haven't already. - if ( 'trash' === $template->status ) { - return new WP_Error( - 'rest_template_already_trashed', - __( 'The template has already been deleted.', 'gutenberg' ), - array( 'status' => 410 ) - ); - } - - wp_trash_post( $id ); - $template->status = 'trash'; - return $this->prepare_item_for_response( $template, $request ); - } - - /** - * Prepares a single template for create or update. - * - * @param WP_REST_Request $request Request object. - * @return stdClass Changes to pass to wp_update_post. - */ - protected function prepare_item_for_database( $request ) { - $template = $request['id'] ? gutenberg_get_block_template( $request['id'], $this->post_type ) : null; - $changes = new stdClass(); - if ( null === $template ) { - $changes->post_type = $this->post_type; - $changes->post_status = 'publish'; - $changes->tax_input = array( - 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(), - ); - } elseif ( 'custom' !== $template->source ) { - $changes->post_name = $template->slug; - $changes->post_type = $this->post_type; - $changes->post_status = 'publish'; - $changes->tax_input = array( - 'wp_theme' => $template->theme, - ); - $changes->meta_input = array( - 'origin' => $template->source, - ); - } else { - $changes->post_name = $template->slug; - $changes->ID = $template->wp_id; - $changes->post_status = 'publish'; - } - if ( isset( $request['content'] ) ) { - $changes->post_content = $request['content']; - } elseif ( null !== $template && 'custom' !== $template->source ) { - $changes->post_content = $template->content; - } - if ( isset( $request['title'] ) ) { - $changes->post_title = $request['title']; - } elseif ( null !== $template && 'custom' !== $template->source ) { - $changes->post_title = $template->title; - } - if ( isset( $request['description'] ) ) { - $changes->post_excerpt = $request['description']; - } elseif ( null !== $template && 'custom' !== $template->source ) { - $changes->post_excerpt = $template->description; - } - - if ( 'wp_template_part' === $this->post_type ) { - if ( isset( $request['area'] ) ) { - $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] ); - } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) { - $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area ); - } elseif ( ! $template->area ) { - $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; - } - } - - if ( ! empty( $request['author'] ) ) { - $post_author = (int) $request['author']; - - if ( get_current_user_id() !== $post_author ) { - $user_obj = get_userdata( $post_author ); - - if ( ! $user_obj ) { - return new WP_Error( - 'rest_invalid_author', - __( 'Invalid author ID.', 'gutenberg' ), - array( 'status' => 400 ) - ); - } - } - - $changes->post_author = $post_author; - } - - return $changes; - } - - /** - * Prepare a single template output for response - * - * @param Gutenberg_Block_Template $template Template instance. - * @param WP_REST_Request $request Request object. - * - * @return WP_REST_Response $data - */ - public function prepare_item_for_response( $template, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - $result = array( - 'id' => $template->id, - 'theme' => $template->theme, - 'content' => array( 'raw' => $template->content ), - 'slug' => $template->slug, - 'source' => $template->source, - 'origin' => $template->origin, - 'type' => $template->type, - 'description' => $template->description, - 'title' => array( - 'raw' => $template->title, - 'rendered' => $template->title, - ), - 'status' => $template->status, - 'wp_id' => $template->wp_id, - 'has_theme_file' => $template->has_theme_file, - 'author' => (int) $template->author, - ); - - if ( 'wp_template' === $template->type ) { - $result['is_custom'] = $template->is_custom; - } - - if ( 'wp_template_part' === $template->type ) { - $result['area'] = $template->area; - } - - $result = $this->add_additional_fields_to_object( $result, $request ); - - $response = rest_ensure_response( $result ); - $links = $this->prepare_links( $template->id ); - $response->add_links( $links ); - if ( ! empty( $links['self']['href'] ) ) { - $actions = $this->get_available_actions(); - $self = $links['self']['href']; - foreach ( $actions as $rel ) { - $response->add_link( $rel, $self ); - } - } - - return $response; - } - - - /** - * Prepares links for the request. - * - * @param integer $id ID. - * @return array Links for the given post. - */ - protected function prepare_links( $id ) { - $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); - - $links = array( - 'self' => array( - 'href' => rest_url( trailingslashit( $base ) . $id ), - ), - 'collection' => array( - 'href' => rest_url( $base ), - ), - 'about' => array( - 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), - ), - ); - - return $links; - } - - /** - * Get the link relations available for the post and current user. - * - * @return array List of link relations. - */ - protected function get_available_actions() { - $rels = array(); - - $post_type = get_post_type_object( $this->post_type ); - - if ( current_user_can( $post_type->cap->publish_posts ) ) { - $rels[] = 'https://api.w.org/action-publish'; - } - - if ( current_user_can( 'unfiltered_html' ) ) { - $rels[] = 'https://api.w.org/action-unfiltered-html'; - } - - return $rels; - } - - /** - * Retrieves the query params for the posts collection. - * - * @return array Collection parameters. - */ - public function get_collection_params() { - return array( - 'context' => $this->get_context_param(), - 'wp_id' => array( - 'description' => __( 'Limit to the specified post id.', 'gutenberg' ), - 'type' => 'integer', - ), - 'area' => array( - 'description' => __( 'Limit to the specified template part area.', 'gutenberg' ), - 'type' => 'string', - ), - 'post_type' => array( - 'description' => __( 'Post type to get the templates for.', 'gutenberg' ), - 'type' => 'string', - ), - ); - } - - /** - * Retrieves the block type' schema, conforming to JSON Schema. - * - * @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' => $this->post_type, - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'description' => __( 'ID of template.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), - 'slug' => array( - 'description' => __( 'Unique slug identifying the template.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - 'required' => true, - 'minLength' => 1, - 'pattern' => '[a-zA-Z0-9_\-]+', - ), - 'theme' => array( - 'description' => __( 'Theme identifier for the template.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - ), - 'source' => array( - 'description' => __( 'Source of template', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), - 'origin' => array( - 'description' => __( 'Source of customized template', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), - 'content' => array( - 'description' => __( 'Content of template.', 'gutenberg' ), - 'type' => array( 'object', 'string' ), - 'default' => '', - 'context' => array( 'embed', 'view', 'edit' ), - ), - 'title' => array( - 'description' => __( 'Title of template.', 'gutenberg' ), - 'type' => array( 'object', 'string' ), - 'default' => '', - 'context' => array( 'embed', 'view', 'edit' ), - ), - 'description' => array( - 'description' => __( 'Description of template.', 'gutenberg' ), - 'type' => 'string', - 'default' => '', - 'context' => array( 'embed', 'view', 'edit' ), - ), - 'status' => array( - 'description' => __( 'Status of template.', 'gutenberg' ), - 'type' => 'string', - 'default' => 'publish', - 'context' => array( 'embed', 'view', 'edit' ), - ), - 'wp_id' => array( - 'description' => __( 'Post ID.', 'gutenberg' ), - 'type' => 'integer', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), - 'has_theme_file' => array( - 'description' => __( 'Theme file exists.', 'gutenberg' ), - 'type' => 'bool', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), - 'author' => array( - 'description' => __( 'The ID for the author of the template.', 'gutenberg' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - ), - ), - ); - - if ( 'wp_template' === $this->post_type ) { - $schema['properties']['is_custom'] = array( - 'description' => __( 'Whether a template is a custom template.', 'gutenberg' ), - 'type' => 'bool', - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ); - } - - if ( 'wp_template_part' === $this->post_type ) { - $schema['properties']['area'] = array( - 'description' => __( 'Where the template part is intended for use (header, footer, etc.)', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'embed', 'view', 'edit' ), - ); - } - - $this->schema = $schema; - - return $this->add_additional_fields_schema( $this->schema ); - } -} diff --git a/lib/compat/wordpress-5.9/wp-theme-get-post-templates.php b/lib/compat/wordpress-5.9/wp-theme-get-post-templates.php deleted file mode 100644 index 0eb37f47ddf59..0000000000000 --- a/lib/compat/wordpress-5.9/wp-theme-get-post-templates.php +++ /dev/null @@ -1,33 +0,0 @@ - $post_type ), 'wp_template' ); - foreach ( $block_templates as $template ) { - $templates[ $template->slug ] = $template->title; - } - - return $templates; - } - add_filter( 'theme_templates', 'gutenberg_load_block_page_templates', 10, 4 ); -} diff --git a/lib/compat/wordpress-6.1/block-template-utils.php b/lib/compat/wordpress-6.1/block-template-utils.php index bf947c8ffe04e..2521be25c3bcd 100644 --- a/lib/compat/wordpress-6.1/block-template-utils.php +++ b/lib/compat/wordpress-6.1/block-template-utils.php @@ -27,3 +27,266 @@ function gutenberg_get_default_block_template_types( $default_template_types ) { return $default_template_types; } add_filter( 'default_template_types', 'gutenberg_get_default_block_template_types', 10 ); + + +/** + * Retrieves a list of unified template objects based on a query. + * + * @param array $query { + * Optional. Arguments to retrieve templates. + * + * @type array $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only). + * @type string $post_type Post type to get the templates for. + * } + * @param array $template_type wp_template or wp_template_part. + * + * @return array Templates. + */ +function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_template' ) { + /** + * Filters the block templates array before the query takes place. + * + * Return a non-null value to bypass the WordPress queries. + * + * @since 10.8 + * + * @param Gutenberg_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query, + * or null to allow WP to run it's normal queries. + * @param array $query { + * Optional. Arguments to retrieve templates. + * + * @type array $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * @type string $post_type Post type to get the templates for. + * } + * @param array $template_type wp_template or wp_template_part. + */ + $templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type ); + if ( ! is_null( $templates ) ) { + return $templates; + } + + $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; + $wp_query_args = array( + 'post_status' => array( 'auto-draft', 'draft', 'publish' ), + 'post_type' => $template_type, + 'posts_per_page' => -1, + 'no_found_rows' => true, + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'name', + 'terms' => wp_get_theme()->get_stylesheet(), + ), + ), + ); + + if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) { + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'wp_template_part_area', + 'field' => 'name', + 'terms' => $query['area'], + ); + $wp_query_args['tax_query']['relation'] = 'AND'; + } + + if ( isset( $query['slug__in'] ) ) { + $wp_query_args['post_name__in'] = $query['slug__in']; + } + + // This is only needed for the regular templates/template parts CPT listing and editor. + if ( isset( $query['wp_id'] ) ) { + $wp_query_args['p'] = $query['wp_id']; + } else { + $wp_query_args['post_status'] = 'publish'; + } + + $template_query = new WP_Query( $wp_query_args ); + $query_result = array(); + foreach ( $template_query->posts as $post ) { + $template = gutenberg_build_block_template_result_from_post( $post ); + if ( is_wp_error( $template ) ) { + continue; + } + + if ( $post_type && ! $template->is_custom ) { + continue; + } + + $query_result[] = $template; + } + if ( ! isset( $query['wp_id'] ) ) { + $template_files = _get_block_templates_files( $template_type ); + foreach ( $template_files as $template_file ) { + $template = _build_block_template_result_from_file( $template_file, $template_type ); + + if ( $post_type && ! $template->is_custom ) { + continue; + } + + if ( $post_type && + isset( $template->post_types ) && + ! in_array( $post_type, $template->post_types, true ) + ) { + continue; + } + + $is_not_custom = false === array_search( + wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'], + array_column( $query_result, 'id' ), + true + ); + $fits_slug_query = + ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ); + $fits_area_query = + ! isset( $query['area'] ) || $template_file['area'] === $query['area']; + $should_include = $is_not_custom && $fits_slug_query && $fits_area_query; + if ( $should_include ) { + $query_result[] = $template; + } + } + } + /** + * Filters the array of queried block templates array after they've been fetched. + * + * @since 10.8 + * + * @param Gutenberg_Block_Template[] $query_result Array of found block templates. + * @param array $query { + * Optional. Arguments to retrieve templates. + * + * @type array $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * } + * @param array $template_type wp_template or wp_template_part. + */ + return apply_filters( 'get_block_templates', $query_result, $query, $template_type ); +} + +/** + * Retrieves a single unified template object using its id. + * + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type wp_template or wp_template_part. + * + * @return Gutenberg_Block_Template|null Template. + */ +function gutenberg_get_block_template( $id, $template_type = 'wp_template' ) { + /** + * Filters the block template object before the query takes place. + * + * Return a non-null value to bypass the WordPress queries. + * + * @since 10.8 + * + * @param Gutenberg_Block_Template|null $block_template Return block template object to short-circuit the default query, + * or null to allow WP to run it's normal queries. + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type wp_template or wp_template_part. + */ + $block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type ); + if ( ! is_null( $block_template ) ) { + return $block_template; + } + + $parts = explode( '//', $id, 2 ); + if ( count( $parts ) < 2 ) { + return null; + } + list( $theme, $slug ) = $parts; + $wp_query_args = array( + 'post_name__in' => array( $slug ), + 'post_type' => $template_type, + 'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ), + 'posts_per_page' => 1, + 'no_found_rows' => true, + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'name', + 'terms' => $theme, + ), + ), + ); + $template_query = new WP_Query( $wp_query_args ); + $posts = $template_query->posts; + + if ( count( $posts ) > 0 ) { + $template = gutenberg_build_block_template_result_from_post( $posts[0] ); + + if ( ! is_wp_error( $template ) ) { + return $template; + } + } + + $block_template = get_block_file_template( $id, $template_type ); + + /** + * Filters the queried block template object after it's been fetched. + * + * @since 10.8 + * + * @param Gutenberg_Block_Template|null $block_template The found block template, or null if there isn't one. + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type wp_template or wp_template_part. + */ + return apply_filters( 'get_block_template', $block_template, $id, $template_type ); +} + +/** + * Build a unified template object based a post Object. + * + * @param WP_Post $post Template post. + * + * @return Gutenberg_Block_Template|WP_Error Template. + */ +function gutenberg_build_block_template_result_from_post( $post ) { + $default_template_types = get_default_block_template_types(); + $terms = get_the_terms( $post, 'wp_theme' ); + + if ( is_wp_error( $terms ) ) { + return $terms; + } + + if ( ! $terms ) { + return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'gutenberg' ) ); + } + + $origin = get_post_meta( $post->ID, 'origin', true ); + $is_wp_suggestion = get_post_meta( $post->ID, 'is_wp_suggestion', true ); + + $theme = $terms[0]->name; + $has_theme_file = wp_get_theme()->get_stylesheet() === $theme && + null !== _get_block_template_file( $post->post_type, $post->post_name ); + + $template = new WP_Block_Template(); + $template->wp_id = $post->ID; + $template->id = $theme . '//' . $post->post_name; + $template->theme = $theme; + $template->content = $post->post_content; + $template->slug = $post->post_name; + $template->source = 'custom'; + $template->origin = ! empty( $origin ) ? $origin : null; + $template->type = $post->post_type; + $template->description = $post->post_excerpt; + $template->title = $post->post_title; + $template->status = $post->post_status; + $template->has_theme_file = $has_theme_file; + $template->is_custom = empty( $is_wp_suggestion ); + $template->author = $post->post_author; + + // We keep this check for existent templates that are part of the template hierarchy. + if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) { + $template->is_custom = false; + } + + if ( 'wp_template_part' === $post->post_type ) { + $type_terms = get_the_terms( $post, 'wp_template_part_area' ); + if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) { + $template->area = $type_terms[0]->name; + } + } + return $template; +} diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php new file mode 100644 index 0000000000000..f32524f071137 --- /dev/null +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php @@ -0,0 +1,183 @@ +post_type ) as $template ) { + $data = $this->prepare_item_for_response( $template, $request ); + $templates[] = $this->prepare_response_for_collection( $data ); + } + + return rest_ensure_response( $templates ); + } + + /** + * Returns the given template + * + * @param WP_REST_Request $request The request instance. + * + * @return WP_REST_Response|WP_Error + */ + public function get_item( $request ) { + if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { + $template = get_block_file_template( $request['id'], $this->post_type ); + } else { + $template = gutenberg_get_block_template( $request['id'], $this->post_type ); + } + + if ( ! $template ) { + return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); + } + + return $this->prepare_item_for_response( $template, $request ); + } + + /** + * Creates a single template. + * + * @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 create_item( $request ) { + $changes = $this->prepare_item_for_database( $request ); + if ( is_wp_error( $changes ) ) { + return $changes; + } + + $changes->post_name = $request['slug']; + $result = wp_insert_post( wp_slash( (array) $changes ), true ); + if ( is_wp_error( $result ) ) { + return $result; + } + + $posts = gutenberg_get_block_templates( array( 'wp_id' => $result ), $this->post_type ); + if ( ! count( $posts ) ) { + return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.', 'gutenberg' ) ); + } + $id = $posts[0]->id; + $template = gutenberg_get_block_template( $id, $this->post_type ); + + $fields_update = $this->update_additional_fields_for_object( $template, $request ); + if ( is_wp_error( $fields_update ) ) { + return $fields_update; + } + + return $this->prepare_item_for_response( + gutenberg_get_block_template( $id, $this->post_type ), + $request + ); + } + + /** + * Prepares a single template for create or update. + * + * @param WP_REST_Request $request Request object. + * @return stdClass Changes to pass to wp_update_post. + */ + protected function prepare_item_for_database( $request ) { + $template = $request['id'] ? gutenberg_get_block_template( $request['id'], $this->post_type ) : null; + $changes = new stdClass(); + if ( null === $template ) { + $changes->post_type = $this->post_type; + $changes->post_status = 'publish'; + $changes->tax_input = array( + 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(), + ); + } elseif ( 'custom' !== $template->source ) { + $changes->post_name = $template->slug; + $changes->post_type = $this->post_type; + $changes->post_status = 'publish'; + $changes->tax_input = array( + 'wp_theme' => $template->theme, + ); + $changes->meta_input = array( + 'origin' => $template->source, + ); + } else { + $changes->post_name = $template->slug; + $changes->ID = $template->wp_id; + $changes->post_status = 'publish'; + } + if ( isset( $request['content'] ) ) { + $changes->post_content = $request['content']; + } elseif ( null !== $template && 'custom' !== $template->source ) { + $changes->post_content = $template->content; + } + if ( isset( $request['title'] ) ) { + $changes->post_title = $request['title']; + } elseif ( null !== $template && 'custom' !== $template->source ) { + $changes->post_title = $template->title; + } + if ( isset( $request['description'] ) ) { + $changes->post_excerpt = $request['description']; + } elseif ( null !== $template && 'custom' !== $template->source ) { + $changes->post_excerpt = $template->description; + } + + if ( 'wp_template' === $this->post_type ) { + if ( isset( $request['is_wp_suggestion'] ) ) { + $changes->meta_input = wp_parse_args( + array( + 'is_wp_suggestion' => $request['is_wp_suggestion'], + ), + $changes->meta_input = array() + ); + } + } + if ( 'wp_template_part' === $this->post_type ) { + if ( isset( $request['area'] ) ) { + $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] ); + } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) { + $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area ); + } elseif ( ! $template->area ) { + $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; + } + } + + if ( ! empty( $request['author'] ) ) { + $post_author = (int) $request['author']; + + if ( get_current_user_id() !== $post_author ) { + $user_obj = get_userdata( $post_author ); + + if ( ! $user_obj ) { + return new WP_Error( + 'rest_invalid_author', + __( 'Invalid author ID.', 'gutenberg' ), + array( 'status' => 400 ) + ); + } + } + + $changes->post_author = $post_author; + } + return $changes; + } +} diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php new file mode 100644 index 0000000000000..cfb53c9b4d237 --- /dev/null +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -0,0 +1,21 @@ + $post_type ), 'wp_template' ); + $new_templates = array(); + foreach ( $block_templates as $template ) { + $new_templates[ $template->slug ] = $template->title; + } + return $new_templates; +} +add_filter( 'theme_templates', 'gutenberg_load_block_page_templates', 10, 4 ); diff --git a/lib/load.php b/lib/load.php index bccf09653abb2..dbba1b9863472 100644 --- a/lib/load.php +++ b/lib/load.php @@ -32,7 +32,6 @@ function gutenberg_is_experiment_enabled( $name ) { // which this class will exist if that is the case. if ( class_exists( 'WP_REST_Controller' ) ) { // WordPress 5.9 compat. - require_once __DIR__ . '/compat/wordpress-5.9/class-gutenberg-rest-templates-controller.php'; require_once __DIR__ . '/compat/wordpress-5.9/class-wp-rest-global-styles-controller.php'; require_once __DIR__ . '/compat/wordpress-5.9/rest-active-global-styles.php'; if ( ! class_exists( 'WP_REST_Menus_Controller' ) ) { @@ -64,6 +63,10 @@ function gutenberg_is_experiment_enabled( $name ) { } require_once __DIR__ . '/compat/wordpress-6.0/rest-api.php'; + // WordPress 6.1 compat. + require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php'; + require_once __DIR__ . '/compat/wordpress-6.1/rest-api.php'; + // Experimental. if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) { require_once __DIR__ . '/experimental/class-wp-rest-customizer-nonces.php'; @@ -98,7 +101,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-5.9/admin-menu.php'; require __DIR__ . '/compat/wordpress-5.9/edit-site-page.php'; require __DIR__ . '/compat/wordpress-5.9/block-template.php'; -require __DIR__ . '/compat/wordpress-5.9/wp-theme-get-post-templates.php'; require __DIR__ . '/compat/wordpress-5.9/default-theme-supports.php'; require __DIR__ . '/compat/wordpress-5.9/move-theme-editor-menu-item.php'; require __DIR__ . '/compat/wordpress-5.9/navigation.php'; @@ -127,6 +129,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.1/script-loader.php'; require __DIR__ . '/compat/wordpress-6.1/class-wp-theme-json-6-1.php'; require __DIR__ . '/compat/wordpress-6.1/block-template-utils.php'; +require __DIR__ . '/compat/wordpress-6.1/wp-theme-get-post-templates.php'; // Experimental features. remove_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); // Turns off WP 6.0's stopgap handler for Webfonts API. diff --git a/packages/edit-site/src/components/add-new-template/new-template.js b/packages/edit-site/src/components/add-new-template/new-template.js index 4c8b80467d0bc..1b18f989d011b 100644 --- a/packages/edit-site/src/components/add-new-template/new-template.js +++ b/packages/edit-site/src/components/add-new-template/new-template.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, find, includes, map } from 'lodash'; +import { filter, includes, map } from 'lodash'; /** * WordPress dependencies @@ -89,13 +89,11 @@ export default function NewTemplate( { postType } ) { const { createErrorNotice } = useDispatch( noticesStore ); const { setTemplate } = useDispatch( editSiteStore ); - async function createTemplate( { slug } ) { + async function createTemplate( template ) { try { - const { title, description } = find( defaultTemplateTypes, { - slug, - } ); + const { title, description, slug } = template; - const template = await saveEntityRecord( + const newTemplate = await saveEntityRecord( 'postType', 'wp_template', { @@ -104,17 +102,19 @@ export default function NewTemplate( { postType } ) { slug: slug.toString(), status: 'publish', title, + // This adds a post meta field in template that is part of `is_custom` value calculation. + is_wp_suggestion: true, }, { throwOnError: true } ); // Set template before navigating away to avoid initial stale value. - setTemplate( template.id, template.slug ); + setTemplate( newTemplate.id, newTemplate.slug ); // Navigate to the created template editor. history.push( { - postId: template.id, - postType: template.type, + postId: newTemplate.id, + postType: newTemplate.type, } ); // TODO: Add a success notice? @@ -167,23 +167,23 @@ export default function NewTemplate( { postType } ) { { () => ( - { map( - missingTemplates, - ( { title, description, slug } ) => ( + { map( missingTemplates, ( template ) => { + const { title, description, slug } = template; + return ( { - createTemplate( { slug } ); + createTemplate( template ); // We will be navigated way so no need to close the dropdown. } } > { title } - ) - ) } + ); + } ) } ) }