diff --git a/docs/reference-guides/block-api/block-patterns.md b/docs/reference-guides/block-api/block-patterns.md index 12fac7b479361..2d387630ea117 100644 --- a/docs/reference-guides/block-api/block-patterns.md +++ b/docs/reference-guides/block-api/block-patterns.md @@ -34,6 +34,7 @@ The properties available for block patterns are: - `viewportWidth` (optional): An integer specifying the intended width of the pattern to allow for a scaled preview of the pattern in the inserter. - `blockTypes` (optional): An array of block types that the pattern is intended to be used with. Each value needs to be the declared block's `name`. - `postTypes` (optional): An array of post types that the pattern is restricted to be used with. The pattern will only be available when editing one of the post types passed on the array, for all the other post types the pattern is not available at all. +- `templateTypes` (optional): An array of template types where the pattern makes sense e.g: '404' if the pattern is for a 404 page, single-post if the pattern is for showing a single post. - `inserter` (optional): By default, all patterns will appear in the inserter. To hide a pattern so that it can only be inserted programmatically, set the `inserter` to `false`. The following code sample registers a block pattern named 'my-plugin/my-awesome-pattern': diff --git a/lib/compat/wordpress-6.1/block-patterns.php b/lib/compat/wordpress-6.1/block-patterns.php deleted file mode 100644 index 7860aa6a1dd66..0000000000000 --- a/lib/compat/wordpress-6.1/block-patterns.php +++ /dev/null @@ -1,177 +0,0 @@ -

- * - * If applicable, this will collect from both parent and child theme. - * - * Other settable fields include: - * - * - Description - * - Viewport Width - * - Categories (comma-separated values) - * - Keywords (comma-separated values) - * - Block Types (comma-separated values) - * - Post Types (comma-separated values) - * - Inserter (yes/no) - * - * @since 6.0.0 - * @access private - */ -function gutenberg_register_theme_block_patterns() { - $default_headers = array( - 'title' => 'Title', - 'slug' => 'Slug', - 'description' => 'Description', - 'viewportWidth' => 'Viewport Width', - 'categories' => 'Categories', - 'keywords' => 'Keywords', - 'blockTypes' => 'Block Types', - 'postTypes' => 'Post Types', - 'inserter' => 'Inserter', - ); - - /* - * Register patterns for the active theme. If the theme is a child theme, - * let it override any patterns from the parent theme that shares the same slug. - */ - $themes = array(); - $stylesheet = get_stylesheet(); - $template = get_template(); - if ( $stylesheet !== $template ) { - $themes[] = wp_get_theme( $stylesheet ); - } - $themes[] = wp_get_theme( $template ); - - foreach ( $themes as $theme ) { - $dirpath = $theme->get_stylesheet_directory() . '/patterns/'; - if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) { - continue; - } - if ( file_exists( $dirpath ) ) { - $files = glob( $dirpath . '*.php' ); - if ( $files ) { - foreach ( $files as $file ) { - $pattern_data = get_file_data( $file, $default_headers ); - - if ( empty( $pattern_data['slug'] ) ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %s: file name. */ - __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'gutenberg' ), - $file - ), - '6.0.0' - ); - continue; - } - - if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %1s: file name; %2s: slug value found. */ - __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'gutenberg' ), - $file, - $pattern_data['slug'] - ), - '6.0.0' - ); - } - - if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) { - continue; - } - - // Title is a required property. - if ( ! $pattern_data['title'] ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %1s: file name; %2s: slug value found. */ - __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'gutenberg' ), - $file - ), - '6.0.0' - ); - continue; - } - - // For properties of type array, parse data as comma-separated. - foreach ( array( 'categories', 'keywords', 'blockTypes', 'postTypes' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = array_filter( - preg_split( - '/[\s,]+/', - (string) $pattern_data[ $property ] - ) - ); - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Parse properties of type int. - foreach ( array( 'viewportWidth' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = (int) $pattern_data[ $property ]; - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Parse properties of type bool. - foreach ( array( 'inserter' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = in_array( - strtolower( $pattern_data[ $property ] ), - array( 'yes', 'true' ), - true - ); - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Translate the pattern metadata. - $text_domain = $theme->get( 'TextDomain' ); - //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction - $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain ); - if ( ! empty( $pattern_data['description'] ) ) { - //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction - $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain ); - } - - // The actual pattern content is the output of the file. - ob_start(); - include $file; - $pattern_data['content'] = ob_get_clean(); - if ( ! $pattern_data['content'] ) { - continue; - } - - register_block_pattern( $pattern_data['slug'], $pattern_data ); - } - } - } - } -} -remove_action( 'init', '_register_theme_block_patterns' ); -add_action( 'init', 'gutenberg_register_theme_block_patterns' ); diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php index f3391389d9e38..5dd06d3aad855 100644 --- a/lib/compat/wordpress-6.1/rest-api.php +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -35,15 +35,6 @@ function gutenberg_update_post_types_rest_response( $response, $post_type ) { } add_filter( 'rest_prepare_post_type', 'gutenberg_update_post_types_rest_response', 10, 2 ); -/** - * Registers the block patterns REST API routes. - */ -function gutenberg_register_gutenberg_rest_block_patterns() { - $block_patterns = new Gutenberg_REST_Block_Patterns_Controller(); - $block_patterns->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_gutenberg_rest_block_patterns', 100 ); - /** * Exposes the site logo URL through the WordPress REST API. * diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php index a28566d441e7a..3628f54b6db43 100644 --- a/lib/compat/wordpress-6.2/block-patterns.php +++ b/lib/compat/wordpress-6.2/block-patterns.php @@ -111,3 +111,182 @@ function gutenberg_register_core_block_patterns() { } } add_action( 'init', 'gutenberg_register_core_block_patterns' ); + + +/** + * Block patterns registration from within a theme + * + * @package gutenberg + */ + +/** + * Register any patterns that the active theme may provide under its + * `./patterns/` directory. Each pattern is defined as a PHP file and defines + * its metadata using plugin-style headers. The minimum required definition is: + * + * /** + * * Title: My Pattern + * * Slug: my-theme/my-pattern + * * + * + * The output of the PHP source corresponds to the content of the pattern, e.g.: + * + *

+ * + * If applicable, this will collect from both parent and child theme. + * + * Other settable fields include: + * + * - Description + * - Viewport Width + * - Categories (comma-separated values) + * - Keywords (comma-separated values) + * - Block Types (comma-separated values) + * - Post Types (comma-separated values) + * - Inserter (yes/no) + * + * @since 6.0.0 + * @access private + */ +function gutenberg_register_theme_block_patterns() { + $default_headers = array( + 'title' => 'Title', + 'slug' => 'Slug', + 'description' => 'Description', + 'viewportWidth' => 'Viewport Width', + 'categories' => 'Categories', + 'keywords' => 'Keywords', + 'blockTypes' => 'Block Types', + 'postTypes' => 'Post Types', + 'templateTypes' => 'Template Types', + 'inserter' => 'Inserter', + ); + + /* + * Register patterns for the active theme. If the theme is a child theme, + * let it override any patterns from the parent theme that shares the same slug. + */ + $themes = array(); + $stylesheet = get_stylesheet(); + $template = get_template(); + if ( $stylesheet !== $template ) { + $themes[] = wp_get_theme( $stylesheet ); + } + $themes[] = wp_get_theme( $template ); + + foreach ( $themes as $theme ) { + $dirpath = $theme->get_stylesheet_directory() . '/patterns/'; + if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) { + continue; + } + if ( file_exists( $dirpath ) ) { + $files = glob( $dirpath . '*.php' ); + if ( $files ) { + foreach ( $files as $file ) { + $pattern_data = get_file_data( $file, $default_headers ); + + if ( empty( $pattern_data['slug'] ) ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %s: file name. */ + __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'gutenberg' ), + $file + ), + '6.0.0' + ); + continue; + } + + if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %1s: file name; %2s: slug value found. */ + __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'gutenberg' ), + $file, + $pattern_data['slug'] + ), + '6.0.0' + ); + } + + if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) { + continue; + } + + // Title is a required property. + if ( ! $pattern_data['title'] ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %1s: file name; %2s: slug value found. */ + __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'gutenberg' ), + $file + ), + '6.0.0' + ); + continue; + } + + // For properties of type array, parse data as comma-separated. + foreach ( array( 'categories', 'keywords', 'blockTypes', 'postTypes', 'templateTypes' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = array_filter( + preg_split( + '/[\s,]+/', + (string) $pattern_data[ $property ] + ) + ); + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Parse properties of type int. + foreach ( array( 'viewportWidth' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = (int) $pattern_data[ $property ]; + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Parse properties of type bool. + foreach ( array( 'inserter' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = in_array( + strtolower( $pattern_data[ $property ] ), + array( 'yes', 'true' ), + true + ); + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Translate the pattern metadata. + $text_domain = $theme->get( 'TextDomain' ); + //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction + $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain ); + if ( ! empty( $pattern_data['description'] ) ) { + //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction + $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain ); + } + + // The actual pattern content is the output of the file. + ob_start(); + include $file; + $pattern_data['content'] = ob_get_clean(); + if ( ! $pattern_data['content'] ) { + continue; + } + + register_block_pattern( $pattern_data['slug'], $pattern_data ); + } + } + } + } +} +remove_action( 'init', '_register_theme_block_patterns' ); +add_action( 'init', 'gutenberg_register_theme_block_patterns' ); diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller.php similarity index 96% rename from lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php rename to lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller.php index c053a678083da..60d93f4706c32 100644 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller.php @@ -129,6 +129,7 @@ public function prepare_item_for_response( $item, $request ) { 'keywords' => 'keywords', 'content' => 'content', 'inserter' => 'inserter', + 'templateTypes' => 'template_types', ); $data = array(); foreach ( $keys as $item_key => $rest_key ) { @@ -204,6 +205,12 @@ public function get_item_schema() { 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), + 'template_types' => array( + 'description' => __( 'An array of template types where the pattern fits.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), 'content' => array( 'description' => __( 'The pattern content.', 'gutenberg' ), 'type' => 'string', diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php index 023a79e3db95f..f58bfdf1172b1 100644 --- a/lib/compat/wordpress-6.2/rest-api.php +++ b/lib/compat/wordpress-6.2/rest-api.php @@ -83,3 +83,12 @@ function gutenberg_pattern_directory_collection_params_6_2( $query_params ) { return $query_params; } add_filter( 'rest_pattern_directory_collection_params', 'gutenberg_pattern_directory_collection_params_6_2' ); + +/** + * Registers the block patterns REST API routes. + */ +function gutenberg_register_gutenberg_rest_block_patterns() { + $block_patterns = new Gutenberg_REST_Block_Patterns_Controller(); + $block_patterns->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_gutenberg_rest_block_patterns', 100 ); diff --git a/lib/load.php b/lib/load.php index 70dbf94453525..79cd48ad1a3d6 100644 --- a/lib/load.php +++ b/lib/load.php @@ -45,11 +45,11 @@ 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-block-patterns-controller.php'; require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php'; require_once __DIR__ . '/compat/wordpress-6.1/rest-api.php'; // WordPress 6.2 compat. + require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php'; require_once __DIR__ . '/compat/wordpress-6.2/rest-api.php'; @@ -96,7 +96,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.1/wp-theme-get-post-templates.php'; require __DIR__ . '/compat/wordpress-6.1/script-loader.php'; require __DIR__ . '/compat/wordpress-6.1/date-settings.php'; -require __DIR__ . '/compat/wordpress-6.1/block-patterns.php'; require __DIR__ . '/compat/wordpress-6.1/edit-form-blocks.php'; require __DIR__ . '/compat/wordpress-6.1/template-parts-screen.php'; require __DIR__ . '/compat/wordpress-6.1/theme.php';