From dcb8d714d3f12640cd69e573de3ab6ffb82f8008 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Tue, 8 Oct 2024 11:11:00 -0400 Subject: [PATCH 01/13] Tinkering with exporting both synced and unsynced patterns --- includes/create-theme/theme-patterns.php | 90 ++++++++++++------------ 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/includes/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php index 44991853..eb34d322 100644 --- a/includes/create-theme/theme-patterns.php +++ b/includes/create-theme/theme-patterns.php @@ -71,7 +71,7 @@ public static function create_pattern_link( $attributes ) { return ''; } - public static function replace_local_pattern_references( $pattern ) { + public static function replace_local_synced_pattern_references( $pattern ) { // Find any references to pattern in templates $templates_to_update = array(); $args = array( @@ -139,9 +139,6 @@ public static function prepare_pattern_for_export( $pattern, $options = null ) { * Copy the local patterns as well as any media to the theme filesystem. */ public static function add_patterns_to_theme( $options = null ) { - $base_dir = get_stylesheet_directory(); - $patterns_dir = $base_dir . DIRECTORY_SEPARATOR . 'patterns'; - $pattern_query = new WP_Query( array( 'post_type' => 'wp_block', @@ -150,53 +147,58 @@ public static function add_patterns_to_theme( $options = null ) { ); if ( $pattern_query->have_posts() ) { - // If there is no patterns folder, create it. - if ( ! is_dir( $patterns_dir ) ) { - wp_mkdir_p( $patterns_dir ); - } - foreach ( $pattern_query->posts as $pattern ) { $pattern = self::pattern_from_wp_block( $pattern ); $pattern = self::prepare_pattern_for_export( $pattern, $options ); - $pattern_exists = false; // Check pattern is synced before adding to theme. - if ( 'unsynced' !== $pattern->sync_status ) { - // Check pattern name doesn't already exist before creating the file. - $existing_patterns = glob( $patterns_dir . DIRECTORY_SEPARATOR . '*.php' ); - foreach ( $existing_patterns as $existing_pattern ) { - if ( strpos( $existing_pattern, $pattern->name . '.php' ) !== false ) { - $pattern_exists = true; - } - } - - if ( $pattern_exists ) { - return new WP_Error( - 'pattern_already_exists', - sprintf( - /* Translators: Pattern name. */ - __( - 'A pattern with this name already exists: "%s".', - 'create-block-theme' - ), - $pattern->name - ) - ); - } - - // Create the pattern file. - $pattern_file = $patterns_dir . $pattern->name . '.php'; - file_put_contents( - $patterns_dir . DIRECTORY_SEPARATOR . $pattern->name . '.php', - $pattern->content - ); - - self::replace_local_pattern_references( $pattern ); - - // Remove it from the database to ensure that these patterns are loaded from the theme. - wp_delete_post( $pattern->id, true ); + + if ( 'unsynced' === $pattern->sync_status ) { + self::add_unsynced_pattern_to_theme( $pattern ); + } + else { + self::add_synced_pattern_to_theme( $pattern ); + } } } } + + public static function add_synced_pattern_to_theme($pattern) + { + $patterns_dir = get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'synced-patterns' . DIRECTORY_SEPARATOR; + $pattern_file = $patterns_dir . $pattern->name . '.php'; + + // If there is no patterns folder, create it. + if ( ! is_dir( $patterns_dir ) ) { + wp_mkdir_p( $patterns_dir ); + } + + // Create the pattern file. + file_put_contents( $pattern_file, $pattern->content); + + // Replace references in the templates + self::replace_local_synced_pattern_references($pattern); + + // Remove it from the database to ensure that these patterns are loaded from the theme. + // Don't remove it... we want to keep the synced patterns in the database too. + // wp_delete_post($pattern->id, true); + } + + public static function add_unsynced_pattern_to_theme($pattern) + { + $patterns_dir = get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'patterns' . DIRECTORY_SEPARATOR; + $pattern_file = $patterns_dir . $pattern->name . '.php'; + + // If there is no patterns folder, create it. + if ( ! is_dir( $patterns_dir ) ) { + wp_mkdir_p( $patterns_dir ); + } + + // Create the pattern file. + file_put_contents( $pattern_file, $pattern->content); + + // Remove it from the database to ensure that these patterns are loaded from the theme. + wp_delete_post($pattern->id, true); + } } From c51adda32763be3cc1099e0ba998d6a0e4b85991 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Wed, 20 Nov 2024 12:33:37 -0500 Subject: [PATCH 02/13] WIP, exporting and managing both synced and unsynced patterns --- includes/class-create-block-theme.php | 190 +++++++++++++++++++++-- includes/create-theme/theme-patterns.php | 30 ++-- src/editor-sidebar/save-panel.js | 4 +- 3 files changed, 190 insertions(+), 34 deletions(-) diff --git a/includes/class-create-block-theme.php b/includes/class-create-block-theme.php index f3436484..b87520a1 100644 --- a/includes/class-create-block-theme.php +++ b/includes/class-create-block-theme.php @@ -9,18 +9,19 @@ * @author WordPress.org */ #[AllowDynamicProperties] -class CBT_Plugin { +class CBT_Plugin +{ /** * Define the core functionality of the plugin. * * @since 0.0.2 */ - public function __construct() { + public function __construct() + { $this->load_dependencies(); $this->define_admin_hooks(); - } /** @@ -29,23 +30,23 @@ public function __construct() { * @since 0.0.2 * @access private */ - private function load_dependencies() { + private function load_dependencies() + { /** * The class responsible for orchestrating the actions and filters of the * core plugin. */ - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-create-block-theme-loader.php'; + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-create-block-theme-loader.php'; /** * The class responsible for defining all actions that occur in the admin area. */ - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-create-block-theme-api.php'; - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-create-block-theme-editor-tools.php'; - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-create-block-theme-admin-landing.php'; + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-create-block-theme-api.php'; + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-create-block-theme-editor-tools.php'; + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-create-block-theme-admin-landing.php'; $this->loader = new CBT_Plugin_Loader(); - } /** @@ -55,7 +56,8 @@ private function load_dependencies() { * @since 0.0.2 * @access private */ - private function define_admin_hooks() { + private function define_admin_hooks() + { $plugin_api = new CBT_Theme_API(); $editor_tools = new CBT_Editor_Tools(); $admin_landing = new CBT_Admin_Landing(); @@ -66,7 +68,173 @@ private function define_admin_hooks() { * * @since 0.0.2 */ - public function run() { + public function run() + { $this->loader->run(); } } + +function CBT_render_pattern($pattern_file) { + ob_start(); + include $pattern_file; + return ob_get_clean(); +} + + +function CBT_get_theme_block_patterns() +{ + + $registry = WP_Block_Patterns_Registry::get_instance(); + + $default_headers = array( + 'title' => 'Title', + 'slug' => 'Slug', + 'description' => 'Description', + 'viewportWidth' => 'Viewport Width', + 'inserter' => 'Inserter', + 'categories' => 'Categories', + 'keywords' => 'Keywords', + 'blockTypes' => 'Block Types', + 'postTypes' => 'Post Types', + 'templateTypes' => 'Template Types', + 'synced' => 'Synced', + ); + + $all_patterns = array(); + $themes = array(); + $theme = wp_get_theme(); + $themes[] = $theme; + + if ($theme->parent()) { + $themes[] = $theme->parent(); + } + + foreach ($themes as $theme) { + + $pattern_files = glob($theme->get_stylesheet_directory() . '/patterns/*.php'); + + foreach ($pattern_files as $pattern_file) { + + $pattern_data = get_file_data( $pattern_file, $default_headers ); + + if ($registry->is_registered($pattern_data['slug'])) { + continue; + } + + $pattern_data['content'] = CBT_render_pattern($pattern_file); + + $all_patterns[] = array( + 'id' => '8888' . crc32($pattern_data['slug']), + 'file_path' => $pattern_file, + 'slug' => $pattern_data['slug'] ?? null, + 'status' => 'publish', + 'type' => 'wp_block', + 'title' => array( + 'raw' => $pattern_data['title'] ?? null, + ), + 'content' => array( + 'raw' => $pattern_data['content'] ?? null, + 'protected' => false, + 'block_version' => null, + ), + 'excerpt' => array( + 'raw' => $pattern_data['description'] ?? null, + 'rendered' => null, + 'protected' => false, + ), + 'wp_pattern_category' => array(), + 'wp_pattern_sync_status' => $pattern_data['synced'] === 'yes' ? "" : "unsynced", + );; + } + } + + return $all_patterns; +} + +// Add in the block patterns from the theme to the collection of blocks +function CBT_filter_blocks_api_response($response, $server, $request) +{ + if ($request->get_route() !== '/wp/v2/blocks') { + return $response; + } + + $data = $response->get_data(); + $patterns = CBT_get_theme_block_patterns(); + + $response->set_data(array_merge($data, $patterns)); + + return $response; +} + +// Handle CBT block updates +function CBT_filter_block_update($result, $server, $request) +{ + $route = $request->get_route(); + + if ( strpos( $route, '/wp/v2/blocks/' ) !== 0 ) { + return $result; + } + + + if ( ! str_contains($route, '8888')) { + return $result; + } + + + $encoded_pattern_slug = (int)ltrim(strstr($route, '8888'), '8888'); + $theme_patterns = CBT_get_theme_block_patterns(); + + + + // if a pattern with a matching slug exists in the theme, do work on it + foreach ($theme_patterns as $pattern) { + if (crc32($pattern['slug']) === $encoded_pattern_slug) { + + $pattern_slug = $pattern['slug']; + + // if the request is a GET, return the pattern content + if ($request->get_method() === 'GET') { + return rest_ensure_response($pattern); + } + // if the request is a PUT or POST, create/update the pattern content file + if ($request->get_method() === 'PUT' || $request->get_method() === 'POST') { + // die('trying' . var_dump($pattern)); + $block_content = $request->get_param('content'); + $categories = implode(', ',$pattern['wp_pattern_category']); + $synced_status = $pattern['wp_pattern_sync_status'] === '' ? 'yes' : 'no'; + $file_content = << + {$block_content} + PHP; + + file_put_contents($pattern['file_path'], $file_content); + + $pattern['content']['raw'] = $block_content; + return rest_ensure_response($pattern); + } + // if the request is a DELETE, delete the pattern content file + if ($request->get_method() === 'DELETE') { + unlink($pattern['file_path']); + return rest_ensure_response($pattern); + } + + } + + } + + return $result; +} + + +// don't register the theme block patterns +remove_action('init', '_register_theme_block_patterns'); +// add the theme block patterns to the block collection +add_filter('rest_post_dispatch', 'CBT_filter_blocks_api_response', 10, 3); +add_filter( 'rest_pre_dispatch', 'CBT_filter_block_update', 10, 3 ); diff --git a/includes/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php index eb34d322..d465692c 100644 --- a/includes/create-theme/theme-patterns.php +++ b/includes/create-theme/theme-patterns.php @@ -30,12 +30,14 @@ public static function pattern_from_wp_block( $pattern_post ) { $pattern_category_list = get_the_terms( $pattern->id, 'wp_pattern_category' ); $pattern->categories = ! empty( $pattern_category_list ) ? join( ', ', wp_list_pluck( $pattern_category_list, 'name' ) ) : ''; $pattern->sync_status = get_post_meta( $pattern->id, 'wp_pattern_sync_status', true ); + $pattern->is_synced = $pattern->sync_status === 'unsynced' ? 'no' : 'yes'; $pattern->content = <<title} * Slug: {$pattern->slug} * Categories: {$pattern->categories} + * Synced: {$pattern->is_synced} */ ?> {$pattern_post->post_content} @@ -72,38 +74,25 @@ public static function create_pattern_link( $attributes ) { } public static function replace_local_synced_pattern_references( $pattern ) { - // Find any references to pattern in templates - $templates_to_update = array(); - $args = array( - 'post_type' => array( 'wp_template', 'wp_template_part' ), - 'posts_per_page' => -1, - 's' => 'wp:block {"ref":' . $pattern->id . '}', - ); - $find_pattern_refs = new WP_Query( $args ); - if ( $find_pattern_refs->have_posts() ) { - foreach ( $find_pattern_refs->posts as $post ) { - $slug = $post->post_name; - array_push( $templates_to_update, $slug ); - } - } - $templates_to_update = array_unique( $templates_to_update ); - // Only update templates that reference the pattern - CBT_Theme_Templates::add_templates_to_local( 'all', null, null, $options, $templates_to_update ); + // If we save patterns we have to update the templates (or none of the templates). + CBT_Theme_Templates::add_templates_to_local( 'all', null, null, null ); // List all template and pattern files in the theme $base_dir = get_stylesheet_directory(); $patterns = glob( $base_dir . DIRECTORY_SEPARATOR . 'patterns' . DIRECTORY_SEPARATOR . '*.php' ); + $synced_patterns = glob( $base_dir . DIRECTORY_SEPARATOR . 'synced-patterns' . DIRECTORY_SEPARATOR . '*.php' ); $templates = glob( $base_dir . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . '*.html' ); $template_parts = glob( $base_dir . DIRECTORY_SEPARATOR . 'template-parts' . DIRECTORY_SEPARATOR . '*.html' ); // Replace references to the local patterns in the theme - foreach ( array_merge( $patterns, $templates, $template_parts ) as $file ) { + foreach ( array_merge( $patterns, $templates, $template_parts, $synced_patterns ) as $file ) { $file_content = file_get_contents( $file ); $file_content = str_replace( 'wp:block {"ref":' . $pattern->id . '}', 'wp:pattern {"slug":"' . $pattern->slug . '"}', $file_content ); file_put_contents( $file, $file_content ); } + // if we clear the template customizations for all templates then we have to SAVE all templates. CBT_Theme_Templates::clear_user_templates_customizations(); CBT_Theme_Templates::clear_user_template_parts_customizations(); } @@ -166,7 +155,7 @@ public static function add_patterns_to_theme( $options = null ) { public static function add_synced_pattern_to_theme($pattern) { - $patterns_dir = get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'synced-patterns' . DIRECTORY_SEPARATOR; + $patterns_dir = get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'patterns' . DIRECTORY_SEPARATOR; $pattern_file = $patterns_dir . $pattern->name . '.php'; // If there is no patterns folder, create it. @@ -181,8 +170,7 @@ public static function add_synced_pattern_to_theme($pattern) self::replace_local_synced_pattern_references($pattern); // Remove it from the database to ensure that these patterns are loaded from the theme. - // Don't remove it... we want to keep the synced patterns in the database too. - // wp_delete_post($pattern->id, true); + wp_delete_post($pattern->id, true); } public static function add_unsynced_pattern_to_theme($pattern) diff --git a/src/editor-sidebar/save-panel.js b/src/editor-sidebar/save-panel.js index 90c95abd..c540a340 100644 --- a/src/editor-sidebar/save-panel.js +++ b/src/editor-sidebar/save-panel.js @@ -157,9 +157,9 @@ export const SaveThemePanel = () => { /> Date: Thu, 21 Nov 2024 11:12:28 -0500 Subject: [PATCH 03/13] Tweaked how unsynced patterns are loaded --- .gitignore | 2 + .wp-env.json | 3 + includes/class-create-block-theme.php | 122 ++++++++++++++++---------- 3 files changed, 81 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index d644ee35..06578b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ build/ # phpunit *.result.* + +dev-env/ diff --git a/.wp-env.json b/.wp-env.json index f40abf34..811799d3 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -3,6 +3,9 @@ "port": 8988, "testsPort": 8989, "plugins": [ "." ], + "mappings": { + "wp-content/themes": "./dev-env/themes" + }, "config": { "WP_UPLOAD_MAX_FILESIZE": "128M", "WP_MEMORY_LIMIT": "256M" diff --git a/includes/class-create-block-theme.php b/includes/class-create-block-theme.php index b87520a1..5250f42b 100644 --- a/includes/class-create-block-theme.php +++ b/includes/class-create-block-theme.php @@ -74,6 +74,30 @@ public function run() } } +function CBT_register_theme_synced_block_patterns() { + + $patterns = CBT_get_theme_block_patterns(); + + // Just synced patterns + $patterns = array_filter($patterns, function ($pattern) { + return $pattern['synced'] === 'yes'; + }); + + foreach ($patterns as $pattern) { + + // $post_id = post_exists($pattern['slug'], '', '', 'wp_block'); + + // register_block_pattern($pattern['slug'], array( + // 'title' => $pattern['title']['raw'], + // 'content' => $pattern['content']['raw'], + // 'description' => $pattern['excerpt']['raw'], + // 'categories' => $pattern['wp_pattern_category'], + // 'keywords' => $pattern['title']['raw'], + // 'inserter' => false, + // )); + } +} + function CBT_render_pattern($pattern_file) { ob_start(); include $pattern_file; @@ -117,40 +141,41 @@ function CBT_get_theme_block_patterns() $pattern_data = get_file_data( $pattern_file, $default_headers ); - if ($registry->is_registered($pattern_data['slug'])) { - continue; - } - + $pattern_data['pattern_file'] = $pattern_file; $pattern_data['content'] = CBT_render_pattern($pattern_file); - $all_patterns[] = array( - 'id' => '8888' . crc32($pattern_data['slug']), - 'file_path' => $pattern_file, - 'slug' => $pattern_data['slug'] ?? null, - 'status' => 'publish', - 'type' => 'wp_block', - 'title' => array( - 'raw' => $pattern_data['title'] ?? null, - ), - 'content' => array( - 'raw' => $pattern_data['content'] ?? null, - 'protected' => false, - 'block_version' => null, - ), - 'excerpt' => array( - 'raw' => $pattern_data['description'] ?? null, - 'rendered' => null, - 'protected' => false, - ), - 'wp_pattern_category' => array(), - 'wp_pattern_sync_status' => $pattern_data['synced'] === 'yes' ? "" : "unsynced", - );; + $all_patterns[] = $pattern_data; } } return $all_patterns; } +function format_pattern_for_response( $pattern_data ) { + return array( + 'id' => 'CBT_' . $pattern_data['slug'], + 'file_path' => $pattern_data['pattern_file'], + 'slug' => $pattern_data['slug'] ?? null, + 'status' => 'publish', + 'type' => 'wp_block', + 'title' => array( + 'raw' => $pattern_data['title'] ?? null, + ), + 'content' => array( + 'raw' => $pattern_data['content'] ?? null, + 'protected' => false, + 'block_version' => null, + ), + 'excerpt' => array( + 'raw' => $pattern_data['description'] ?? null, + 'rendered' => null, + 'protected' => false, + ), + 'wp_pattern_category' => array(), + 'wp_pattern_sync_status' => $pattern_data['synced'] === 'yes' ? "" : "unsynced", + ); +} + // Add in the block patterns from the theme to the collection of blocks function CBT_filter_blocks_api_response($response, $server, $request) { @@ -161,6 +186,13 @@ function CBT_filter_blocks_api_response($response, $server, $request) $data = $response->get_data(); $patterns = CBT_get_theme_block_patterns(); + // filter out the synced patterns + $patterns = array_filter($patterns, function ($pattern) { + return $pattern['synced'] !== 'yes'; + }); + + $patterns = array_map( 'format_pattern_for_response', $patterns); + $response->set_data(array_merge($data, $patterns)); return $response; @@ -176,53 +208,49 @@ function CBT_filter_block_update($result, $server, $request) } - if ( ! str_contains($route, '8888')) { + if ( ! str_contains($route, 'CBT_')) { return $result; } - $encoded_pattern_slug = (int)ltrim(strstr($route, '8888'), '8888'); + $pattern_slug = ltrim(strstr($route, 'CBT_'), 'CBT_'); $theme_patterns = CBT_get_theme_block_patterns(); - - // if a pattern with a matching slug exists in the theme, do work on it foreach ($theme_patterns as $pattern) { - if (crc32($pattern['slug']) === $encoded_pattern_slug) { - - $pattern_slug = $pattern['slug']; + if ($pattern['slug'] === $pattern_slug) { // if the request is a GET, return the pattern content if ($request->get_method() === 'GET') { - return rest_ensure_response($pattern); + return rest_ensure_response(format_pattern_for_response($pattern)); } + // if the request is a PUT or POST, create/update the pattern content file if ($request->get_method() === 'PUT' || $request->get_method() === 'POST') { - // die('trying' . var_dump($pattern)); $block_content = $request->get_param('content'); - $categories = implode(', ',$pattern['wp_pattern_category']); - $synced_status = $pattern['wp_pattern_sync_status'] === '' ? 'yes' : 'no'; + $synced_status = $pattern['synced'] === 'yes' ? 'Synced: yes' : ''; $file_content = << {$block_content} PHP; - file_put_contents($pattern['file_path'], $file_content); + file_put_contents($pattern['pattern_file'], $file_content); - $pattern['content']['raw'] = $block_content; - return rest_ensure_response($pattern); + $pattern['content'] = $block_content; + + return rest_ensure_response(format_pattern_for_response($pattern)); } // if the request is a DELETE, delete the pattern content file if ($request->get_method() === 'DELETE') { unlink($pattern['file_path']); - return rest_ensure_response($pattern); + return rest_ensure_response(format_pattern_for_response($pattern)); } } @@ -235,6 +263,8 @@ function CBT_filter_block_update($result, $server, $request) // don't register the theme block patterns remove_action('init', '_register_theme_block_patterns'); +add_action('init', 'CBT_register_theme_synced_block_patterns'); + // add the theme block patterns to the block collection add_filter('rest_post_dispatch', 'CBT_filter_blocks_api_response', 10, 3); add_filter( 'rest_pre_dispatch', 'CBT_filter_block_update', 10, 3 ); From 4fb16af98a2ee1cd1d685481244450f391c0a6db Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Fri, 22 Nov 2024 07:08:52 -0500 Subject: [PATCH 04/13] Added dev mode env settings to wp-env config --- .wp-env.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.wp-env.json b/.wp-env.json index 811799d3..780ff72b 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -8,6 +8,8 @@ }, "config": { "WP_UPLOAD_MAX_FILESIZE": "128M", - "WP_MEMORY_LIMIT": "256M" + "WP_MEMORY_LIMIT": "256M", + "WP_ENVIRONMENT_TYPE": "development", + "WP_DEVELOPMENT_MODE": "all" } } From 79d0fc022cffd3fc84aab646cef94979cb181275 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Fri, 22 Nov 2024 07:10:57 -0500 Subject: [PATCH 05/13] Update patterns copied from USER space to THEME space to use the assigned slug --- includes/create-theme/theme-patterns.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/includes/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php index d465692c..b4f674f6 100644 --- a/includes/create-theme/theme-patterns.php +++ b/includes/create-theme/theme-patterns.php @@ -170,7 +170,15 @@ public static function add_synced_pattern_to_theme($pattern) self::replace_local_synced_pattern_references($pattern); // Remove it from the database to ensure that these patterns are loaded from the theme. - wp_delete_post($pattern->id, true); + //wp_delete_post($pattern->id, true); + // leave the pattern in the database so that existing references to it remain valid + // update the post_name value to match the pattern slug + wp_update_post( + array( + 'ID' => $pattern->id, + 'post_name' => sanitize_title($pattern->slug), + ) + ); } public static function add_unsynced_pattern_to_theme($pattern) From 507d0b60a19222e87ed68a23a3922f7079b73b84 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Fri, 22 Nov 2024 07:11:48 -0500 Subject: [PATCH 06/13] register ALL patterns (hidden). Plus do work on synced pattern files AND database. --- includes/class-create-block-theme.php | 106 ++++++++++++++++++-------- 1 file changed, 76 insertions(+), 30 deletions(-) diff --git a/includes/class-create-block-theme.php b/includes/class-create-block-theme.php index 5250f42b..0a349077 100644 --- a/includes/class-create-block-theme.php +++ b/includes/class-create-block-theme.php @@ -78,23 +78,57 @@ function CBT_register_theme_synced_block_patterns() { $patterns = CBT_get_theme_block_patterns(); - // Just synced patterns - $patterns = array_filter($patterns, function ($pattern) { - return $pattern['synced'] === 'yes'; - }); - foreach ($patterns as $pattern) { - // $post_id = post_exists($pattern['slug'], '', '', 'wp_block'); + // if it is a synced pattern manage the post + if ($pattern['synced'] === 'yes') { + + //search for post by slug + $pattern_post = get_page_by_path(sanitize_title($pattern['slug']), OBJECT, 'wp_block'); + + if ($pattern_post) { + $post_id = $pattern_post->ID; + // the synced pattern already exists + // should we update it? + // That's the question of the day! + // update the post with the content + wp_update_post(array( + 'ID' => $post_id, + 'post_content' => $pattern['content'], + )); + } else { + $post_id = wp_insert_post(array( + 'post_title' => $pattern['title'], + 'post_name' => $pattern['slug'], + 'post_content' => $pattern['content'], + 'post_type' => 'wp_block', + 'post_status' => 'publish', + 'ping_status' => 'closed', + 'comment_status' => 'closed', + )); + } - // register_block_pattern($pattern['slug'], array( - // 'title' => $pattern['title']['raw'], - // 'content' => $pattern['content']['raw'], - // 'description' => $pattern['excerpt']['raw'], - // 'categories' => $pattern['wp_pattern_category'], - // 'keywords' => $pattern['title']['raw'], - // 'inserter' => false, - // )); + // add the pattern as an UNsynced pattern TOO so that it can be used in templates. + // this pattern injects a synced pattern block as the content. + register_block_pattern( + $pattern['slug'], + array( + 'title' => $pattern['title'], + 'inserter' => false, + 'content' => '', + ) + ); + } else { + // register the pattern and hide from the inserter + register_block_pattern( + $pattern['slug'], + array( + 'title' => $pattern['title'], + 'inserter' => false, + 'content' => $pattern['content'], + ) + ); + } } } @@ -107,9 +141,6 @@ function CBT_render_pattern($pattern_file) { function CBT_get_theme_block_patterns() { - - $registry = WP_Block_Patterns_Registry::get_instance(); - $default_headers = array( 'title' => 'Title', 'slug' => 'Slug', @@ -153,7 +184,7 @@ function CBT_get_theme_block_patterns() function format_pattern_for_response( $pattern_data ) { return array( - 'id' => 'CBT_' . $pattern_data['slug'], + 'id' => $pattern_data['id'] ?? 'CBT_' . $pattern_data['slug'], 'file_path' => $pattern_data['pattern_file'], 'slug' => $pattern_data['slug'] ?? null, 'status' => 'publish', @@ -187,10 +218,13 @@ function CBT_filter_blocks_api_response($response, $server, $request) $patterns = CBT_get_theme_block_patterns(); // filter out the synced patterns + // filter out the patterns marked hidden $patterns = array_filter($patterns, function ($pattern) { - return $pattern['synced'] !== 'yes'; + return $pattern['synced'] !== 'yes' && $pattern['inserter'] !== 'no'; }); + + $patterns = array_map( 'format_pattern_for_response', $patterns); $response->set_data(array_merge($data, $patterns)); @@ -207,22 +241,29 @@ function CBT_filter_block_update($result, $server, $request) return $result; } - - if ( ! str_contains($route, 'CBT_')) { - return $result; + if ( str_contains($route, 'CBT_')) { + $pattern_slug = ltrim(strstr($route, 'CBT_'), 'CBT_'); + } + else { + //get the slug for the post with the pattern id + $pattern_id = $request->get_param('id'); + if ( ! $pattern_id ) { + //get the ID from the route + $pattern_id = str_replace('/wp/v2/blocks/', '', $route); + } + $pattern_slug = get_post_field('post_name', $pattern_id); } - - $pattern_slug = ltrim(strstr($route, 'CBT_'), 'CBT_'); $theme_patterns = CBT_get_theme_block_patterns(); // if a pattern with a matching slug exists in the theme, do work on it foreach ($theme_patterns as $pattern) { - if ($pattern['slug'] === $pattern_slug) { + + if (sanitize_title($pattern['slug']) === sanitize_title($pattern_slug)) { // if the request is a GET, return the pattern content if ($request->get_method() === 'GET') { - return rest_ensure_response(format_pattern_for_response($pattern)); + // which we do below } // if the request is a PUT or POST, create/update the pattern content file @@ -244,15 +285,20 @@ function CBT_filter_block_update($result, $server, $request) file_put_contents($pattern['pattern_file'], $file_content); $pattern['content'] = $block_content; - - return rest_ensure_response(format_pattern_for_response($pattern)); } + // if the request is a DELETE, delete the pattern content file if ($request->get_method() === 'DELETE') { - unlink($pattern['file_path']); - return rest_ensure_response(format_pattern_for_response($pattern)); + unlink($pattern['pattern_file']); + } + + // if we pulled the real ID then we also want to do work on the database; + // return null to allow the natural action to happen too. + if ( $pattern_id ) { + return null; } + return rest_ensure_response(format_pattern_for_response($pattern)); } } From b299d79880e5a6204b82724ab1aee882d092c528 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Fri, 22 Nov 2024 08:37:05 -0500 Subject: [PATCH 07/13] Got replacing all synced patterns working as exected --- includes/class-create-block-theme.php | 9 +++++++++ includes/create-theme/theme-patterns.php | 20 +++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/includes/class-create-block-theme.php b/includes/class-create-block-theme.php index 0a349077..868bd30e 100644 --- a/includes/class-create-block-theme.php +++ b/includes/class-create-block-theme.php @@ -175,6 +175,15 @@ function CBT_get_theme_block_patterns() $pattern_data['pattern_file'] = $pattern_file; $pattern_data['content'] = CBT_render_pattern($pattern_file); + // if the pattern is synced add the ID + + if ( $pattern_data['synced'] === 'yes' ) { + $pattern_post = get_page_by_path(sanitize_title($pattern_data['slug']), OBJECT, 'wp_block'); + if ($pattern_post) { + $pattern_data['id'] = $pattern_post->ID; + } + } + $all_patterns[] = $pattern_data; } } diff --git a/includes/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php index b4f674f6..d49b9c75 100644 --- a/includes/create-theme/theme-patterns.php +++ b/includes/create-theme/theme-patterns.php @@ -76,7 +76,8 @@ public static function create_pattern_link( $attributes ) { public static function replace_local_synced_pattern_references( $pattern ) { // If we save patterns we have to update the templates (or none of the templates). - CBT_Theme_Templates::add_templates_to_local( 'all', null, null, null ); + // However, we can't save it here because it will overwrite changes we make to the templates RE: Patterns. + // CBT_Theme_Templates::add_templates_to_local( 'all', null, null, null ); // List all template and pattern files in the theme $base_dir = get_stylesheet_directory(); @@ -85,10 +86,14 @@ public static function replace_local_synced_pattern_references( $pattern ) { $templates = glob( $base_dir . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . '*.html' ); $template_parts = glob( $base_dir . DIRECTORY_SEPARATOR . 'template-parts' . DIRECTORY_SEPARATOR . '*.html' ); + + + $needle = 'wp:block {"ref":' . $pattern['id']; + $replacement = 'wp:pattern {"slug":"' . $pattern['slug'] . '"'; // Replace references to the local patterns in the theme foreach ( array_merge( $patterns, $templates, $template_parts, $synced_patterns ) as $file ) { $file_content = file_get_contents( $file ); - $file_content = str_replace( 'wp:block {"ref":' . $pattern->id . '}', 'wp:pattern {"slug":"' . $pattern->slug . '"}', $file_content ); + $file_content = str_replace( $needle, $replacement, $file_content ); file_put_contents( $file, $file_content ); } @@ -151,6 +156,15 @@ public static function add_patterns_to_theme( $options = null ) { } } } + + // now replace all instances of synced blocks with pattern blocks + $patterns = CBT_get_theme_block_patterns(); + $patterns = array_filter($patterns, function ($pattern) { + return $pattern['synced'] === 'yes'; + }); + foreach ($patterns as $pattern) { + self::replace_local_synced_pattern_references($pattern); + } } public static function add_synced_pattern_to_theme($pattern) @@ -167,7 +181,7 @@ public static function add_synced_pattern_to_theme($pattern) file_put_contents( $pattern_file, $pattern->content); // Replace references in the templates - self::replace_local_synced_pattern_references($pattern); + // self::replace_local_synced_pattern_references($pattern); // Remove it from the database to ensure that these patterns are loaded from the theme. //wp_delete_post($pattern->id, true); From a71561b99b5dec43ed2aa3e2c18118b85961946e Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Fri, 22 Nov 2024 09:04:57 -0500 Subject: [PATCH 08/13] Moved synced pattern handling stuff to a class --- includes/class-create-block-theme.php | 252 +---------------------- includes/create-theme/theme-patterns.php | 6 - 2 files changed, 2 insertions(+), 256 deletions(-) diff --git a/includes/class-create-block-theme.php b/includes/class-create-block-theme.php index 868bd30e..1f4087bd 100644 --- a/includes/class-create-block-theme.php +++ b/includes/class-create-block-theme.php @@ -45,6 +45,7 @@ private function load_dependencies() require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-create-block-theme-api.php'; require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-create-block-theme-editor-tools.php'; require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-create-block-theme-admin-landing.php'; + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-create-block-theme-synced-pattern-loader.php'; $this->loader = new CBT_Plugin_Loader(); } @@ -61,6 +62,7 @@ private function define_admin_hooks() $plugin_api = new CBT_Theme_API(); $editor_tools = new CBT_Editor_Tools(); $admin_landing = new CBT_Admin_Landing(); + $synced_loader = new CBT_Synced_Pattern_Loader(); } /** @@ -73,253 +75,3 @@ public function run() $this->loader->run(); } } - -function CBT_register_theme_synced_block_patterns() { - - $patterns = CBT_get_theme_block_patterns(); - - foreach ($patterns as $pattern) { - - // if it is a synced pattern manage the post - if ($pattern['synced'] === 'yes') { - - //search for post by slug - $pattern_post = get_page_by_path(sanitize_title($pattern['slug']), OBJECT, 'wp_block'); - - if ($pattern_post) { - $post_id = $pattern_post->ID; - // the synced pattern already exists - // should we update it? - // That's the question of the day! - // update the post with the content - wp_update_post(array( - 'ID' => $post_id, - 'post_content' => $pattern['content'], - )); - } else { - $post_id = wp_insert_post(array( - 'post_title' => $pattern['title'], - 'post_name' => $pattern['slug'], - 'post_content' => $pattern['content'], - 'post_type' => 'wp_block', - 'post_status' => 'publish', - 'ping_status' => 'closed', - 'comment_status' => 'closed', - )); - } - - // add the pattern as an UNsynced pattern TOO so that it can be used in templates. - // this pattern injects a synced pattern block as the content. - register_block_pattern( - $pattern['slug'], - array( - 'title' => $pattern['title'], - 'inserter' => false, - 'content' => '', - ) - ); - } else { - // register the pattern and hide from the inserter - register_block_pattern( - $pattern['slug'], - array( - 'title' => $pattern['title'], - 'inserter' => false, - 'content' => $pattern['content'], - ) - ); - } - } -} - -function CBT_render_pattern($pattern_file) { - ob_start(); - include $pattern_file; - return ob_get_clean(); -} - - -function CBT_get_theme_block_patterns() -{ - $default_headers = array( - 'title' => 'Title', - 'slug' => 'Slug', - 'description' => 'Description', - 'viewportWidth' => 'Viewport Width', - 'inserter' => 'Inserter', - 'categories' => 'Categories', - 'keywords' => 'Keywords', - 'blockTypes' => 'Block Types', - 'postTypes' => 'Post Types', - 'templateTypes' => 'Template Types', - 'synced' => 'Synced', - ); - - $all_patterns = array(); - $themes = array(); - $theme = wp_get_theme(); - $themes[] = $theme; - - if ($theme->parent()) { - $themes[] = $theme->parent(); - } - - foreach ($themes as $theme) { - - $pattern_files = glob($theme->get_stylesheet_directory() . '/patterns/*.php'); - - foreach ($pattern_files as $pattern_file) { - - $pattern_data = get_file_data( $pattern_file, $default_headers ); - - $pattern_data['pattern_file'] = $pattern_file; - $pattern_data['content'] = CBT_render_pattern($pattern_file); - - // if the pattern is synced add the ID - - if ( $pattern_data['synced'] === 'yes' ) { - $pattern_post = get_page_by_path(sanitize_title($pattern_data['slug']), OBJECT, 'wp_block'); - if ($pattern_post) { - $pattern_data['id'] = $pattern_post->ID; - } - } - - $all_patterns[] = $pattern_data; - } - } - - return $all_patterns; -} - -function format_pattern_for_response( $pattern_data ) { - return array( - 'id' => $pattern_data['id'] ?? 'CBT_' . $pattern_data['slug'], - 'file_path' => $pattern_data['pattern_file'], - 'slug' => $pattern_data['slug'] ?? null, - 'status' => 'publish', - 'type' => 'wp_block', - 'title' => array( - 'raw' => $pattern_data['title'] ?? null, - ), - 'content' => array( - 'raw' => $pattern_data['content'] ?? null, - 'protected' => false, - 'block_version' => null, - ), - 'excerpt' => array( - 'raw' => $pattern_data['description'] ?? null, - 'rendered' => null, - 'protected' => false, - ), - 'wp_pattern_category' => array(), - 'wp_pattern_sync_status' => $pattern_data['synced'] === 'yes' ? "" : "unsynced", - ); -} - -// Add in the block patterns from the theme to the collection of blocks -function CBT_filter_blocks_api_response($response, $server, $request) -{ - if ($request->get_route() !== '/wp/v2/blocks') { - return $response; - } - - $data = $response->get_data(); - $patterns = CBT_get_theme_block_patterns(); - - // filter out the synced patterns - // filter out the patterns marked hidden - $patterns = array_filter($patterns, function ($pattern) { - return $pattern['synced'] !== 'yes' && $pattern['inserter'] !== 'no'; - }); - - - - $patterns = array_map( 'format_pattern_for_response', $patterns); - - $response->set_data(array_merge($data, $patterns)); - - return $response; -} - -// Handle CBT block updates -function CBT_filter_block_update($result, $server, $request) -{ - $route = $request->get_route(); - - if ( strpos( $route, '/wp/v2/blocks/' ) !== 0 ) { - return $result; - } - - if ( str_contains($route, 'CBT_')) { - $pattern_slug = ltrim(strstr($route, 'CBT_'), 'CBT_'); - } - else { - //get the slug for the post with the pattern id - $pattern_id = $request->get_param('id'); - if ( ! $pattern_id ) { - //get the ID from the route - $pattern_id = str_replace('/wp/v2/blocks/', '', $route); - } - $pattern_slug = get_post_field('post_name', $pattern_id); - } - - $theme_patterns = CBT_get_theme_block_patterns(); - - // if a pattern with a matching slug exists in the theme, do work on it - foreach ($theme_patterns as $pattern) { - - if (sanitize_title($pattern['slug']) === sanitize_title($pattern_slug)) { - - // if the request is a GET, return the pattern content - if ($request->get_method() === 'GET') { - // which we do below - } - - // if the request is a PUT or POST, create/update the pattern content file - if ($request->get_method() === 'PUT' || $request->get_method() === 'POST') { - $block_content = $request->get_param('content'); - $synced_status = $pattern['synced'] === 'yes' ? 'Synced: yes' : ''; - $file_content = << - {$block_content} - PHP; - - file_put_contents($pattern['pattern_file'], $file_content); - - $pattern['content'] = $block_content; - } - - // if the request is a DELETE, delete the pattern content file - if ($request->get_method() === 'DELETE') { - unlink($pattern['pattern_file']); - } - - // if we pulled the real ID then we also want to do work on the database; - // return null to allow the natural action to happen too. - if ( $pattern_id ) { - return null; - } - - return rest_ensure_response(format_pattern_for_response($pattern)); - } - - } - - return $result; -} - - -// don't register the theme block patterns -remove_action('init', '_register_theme_block_patterns'); -add_action('init', 'CBT_register_theme_synced_block_patterns'); - -// add the theme block patterns to the block collection -add_filter('rest_post_dispatch', 'CBT_filter_blocks_api_response', 10, 3); -add_filter( 'rest_pre_dispatch', 'CBT_filter_block_update', 10, 3 ); diff --git a/includes/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php index d49b9c75..0adf48c9 100644 --- a/includes/create-theme/theme-patterns.php +++ b/includes/create-theme/theme-patterns.php @@ -180,12 +180,6 @@ public static function add_synced_pattern_to_theme($pattern) // Create the pattern file. file_put_contents( $pattern_file, $pattern->content); - // Replace references in the templates - // self::replace_local_synced_pattern_references($pattern); - - // Remove it from the database to ensure that these patterns are loaded from the theme. - //wp_delete_post($pattern->id, true); - // leave the pattern in the database so that existing references to it remain valid // update the post_name value to match the pattern slug wp_update_post( array( From ed4825d79609ff50b77970d1fc568630c489fbcd Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Fri, 22 Nov 2024 09:42:30 -0500 Subject: [PATCH 09/13] Adding pattern loader class --- ...eate-block-theme-synced-pattern-loader.php | 266 ++++++++++++++++++ includes/create-theme/theme-patterns.php | 2 +- 2 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 includes/class-create-block-theme-synced-pattern-loader.php diff --git a/includes/class-create-block-theme-synced-pattern-loader.php b/includes/class-create-block-theme-synced-pattern-loader.php new file mode 100644 index 00000000..b173c952 --- /dev/null +++ b/includes/class-create-block-theme-synced-pattern-loader.php @@ -0,0 +1,266 @@ +ID; + // the synced pattern already exists + // should we update it? + // That's the question of the day! + // update the post with the content + wp_update_post(array( + 'ID' => $post_id, + 'post_content' => $pattern['content'], + )); + } else { + $post_id = wp_insert_post(array( + 'post_title' => $pattern['title'], + 'post_name' => $pattern['slug'], + 'post_content' => $pattern['content'], + 'post_type' => 'wp_block', + 'post_status' => 'publish', + 'ping_status' => 'closed', + 'comment_status' => 'closed', + )); + } + + // add the pattern as an UNsynced pattern TOO so that it can be used in templates. + // this pattern injects a synced pattern block as the content. + register_block_pattern( + $pattern['slug'], + array( + 'title' => $pattern['title'], + 'inserter' => false, + 'content' => '', + ) + ); + } else { + // register the pattern and hide from the inserter + register_block_pattern( + $pattern['slug'], + array( + 'title' => $pattern['title'], + 'inserter' => false, + 'content' => $pattern['content'], + ) + ); + } + } + } + + private static function CBT_render_pattern($pattern_file) + { + ob_start(); + include $pattern_file; + return ob_get_clean(); + } + + + public static function CBT_get_theme_block_patterns() + { + $default_headers = array( + 'title' => 'Title', + 'slug' => 'Slug', + 'description' => 'Description', + 'viewportWidth' => 'Viewport Width', + 'inserter' => 'Inserter', + 'categories' => 'Categories', + 'keywords' => 'Keywords', + 'blockTypes' => 'Block Types', + 'postTypes' => 'Post Types', + 'templateTypes' => 'Template Types', + 'synced' => 'Synced', + ); + + $all_patterns = array(); + $themes = array(); + $theme = wp_get_theme(); + $themes[] = $theme; + + if ($theme->parent()) { + $themes[] = $theme->parent(); + } + + foreach ($themes as $theme) { + + $pattern_files = glob($theme->get_stylesheet_directory() . '/patterns/*.php'); + + foreach ($pattern_files as $pattern_file) { + + $pattern_data = get_file_data($pattern_file, $default_headers); + + $pattern_data['pattern_file'] = $pattern_file; + $pattern_data['content'] = self::CBT_render_pattern($pattern_file); + + // if the pattern is synced add the ID + + if ($pattern_data['synced'] === 'yes') { + $pattern_post = get_page_by_path(sanitize_title($pattern_data['slug']), OBJECT, 'wp_block'); + if ($pattern_post) { + $pattern_data['id'] = $pattern_post->ID; + } + } + + $all_patterns[] = $pattern_data; + } + } + + return $all_patterns; + } + + private static function format_pattern_for_response($pattern_data) + { + return array( + 'id' => $pattern_data['id'] ?? 'CBT_' . $pattern_data['slug'], + 'file_path' => $pattern_data['pattern_file'], + 'slug' => $pattern_data['slug'] ?? null, + 'status' => 'publish', + 'type' => 'wp_block', + 'title' => array( + 'raw' => $pattern_data['title'] ?? null, + ), + 'content' => array( + 'raw' => $pattern_data['content'] ?? null, + 'protected' => false, + 'block_version' => null, + ), + 'excerpt' => array( + 'raw' => $pattern_data['description'] ?? null, + 'rendered' => null, + 'protected' => false, + ), + 'wp_pattern_category' => array(), + 'wp_pattern_sync_status' => $pattern_data['synced'] === 'yes' ? "" : "unsynced", + ); + } + + // Add in the block patterns from the theme to the collection of blocks + public function CBT_filter_blocks_api_response($response, $server, $request) + { + if ($request->get_route() !== '/wp/v2/blocks') { + return $response; + } + + $data = $response->get_data(); + $patterns = self::CBT_get_theme_block_patterns(); + + // filter out the synced patterns + // filter out the patterns marked hidden + $patterns = array_filter($patterns, function ($pattern) { + return $pattern['synced'] !== 'yes' && $pattern['inserter'] !== 'no'; + }); + + + + $patterns = array_map([self::class, 'format_pattern_for_response'], $patterns); + + $response->set_data(array_merge($data, $patterns)); + + return $response; + } + + // Handle CBT block updates + public function CBT_filter_block_update($result, $server, $request) + { + $route = $request->get_route(); + + if (strpos($route, '/wp/v2/blocks/') !== 0) { + return $result; + } + + if (str_contains($route, 'CBT_')) { + $pattern_slug = ltrim(strstr($route, 'CBT_'), 'CBT_'); + } else { + //get the slug for the post with the pattern id + $pattern_id = $request->get_param('id'); + if (! $pattern_id) { + //get the ID from the route + $pattern_id = str_replace('/wp/v2/blocks/', '', $route); + } + $pattern_slug = get_post_field('post_name', $pattern_id); + } + + $theme_patterns = self::CBT_get_theme_block_patterns(); + + // if a pattern with a matching slug exists in the theme, do work on it + foreach ($theme_patterns as $pattern) { + + if (sanitize_title($pattern['slug']) === sanitize_title($pattern_slug)) { + + // if the request is a GET, return the pattern content + if ($request->get_method() === 'GET') { + // which we do below + } + + // if the request is a PUT or POST, create/update the pattern content file + if ($request->get_method() === 'PUT' || $request->get_method() === 'POST') { + $block_content = $request->get_param('content'); + $synced_status = $pattern['synced'] === 'yes' ? 'Synced: yes' : ''; + $file_content = << + {$block_content} + PHP; + + file_put_contents($pattern['pattern_file'], $file_content); + + $pattern['content'] = $block_content; + } + + // if the request is a DELETE, delete the pattern content file + if ($request->get_method() === 'DELETE') { + unlink($pattern['pattern_file']); + } + + // if we pulled the real ID then we also want to do work on the database; + // return null to allow the natural action to happen too. + if ($pattern_id) { + return null; + } + + return rest_ensure_response(self::format_pattern_for_response($pattern)); + } + } + + return $result; + } +} diff --git a/includes/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php index 0adf48c9..632bfd53 100644 --- a/includes/create-theme/theme-patterns.php +++ b/includes/create-theme/theme-patterns.php @@ -158,7 +158,7 @@ public static function add_patterns_to_theme( $options = null ) { } // now replace all instances of synced blocks with pattern blocks - $patterns = CBT_get_theme_block_patterns(); + $patterns = CBT_Synced_Pattern_Loader::CBT_get_theme_block_patterns(); $patterns = array_filter($patterns, function ($pattern) { return $pattern['synced'] === 'yes'; }); From d15fa627c5e850e9b1bbd4ff2b66ca2831d03ce4 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Fri, 22 Nov 2024 16:25:07 -0500 Subject: [PATCH 10/13] Correct remove_action syntax --- includes/class-create-block-theme-synced-pattern-loader.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/includes/class-create-block-theme-synced-pattern-loader.php b/includes/class-create-block-theme-synced-pattern-loader.php index b173c952..a20188a1 100644 --- a/includes/class-create-block-theme-synced-pattern-loader.php +++ b/includes/class-create-block-theme-synced-pattern-loader.php @@ -14,7 +14,7 @@ class CBT_Synced_Pattern_Loader public function __construct() { // don't register the theme block patterns - remove_action('init', [$this, '_register_theme_block_patterns']); + remove_action('init', '_register_theme_block_patterns'); add_action('init', [$this, 'CBT_register_theme_synced_block_patterns']); // add the theme block patterns to the block collection @@ -38,9 +38,7 @@ public function CBT_register_theme_synced_block_patterns() if ($pattern_post) { $post_id = $pattern_post->ID; // the synced pattern already exists - // should we update it? - // That's the question of the day! - // update the post with the content + // update the post with the content in the file wp_update_post(array( 'ID' => $post_id, 'post_content' => $pattern['content'], From 294c5c69ac9f7e1379a161d55cad6fa4632c907e Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Mon, 25 Nov 2024 08:09:01 -0500 Subject: [PATCH 11/13] Added the 'load synced patterns' utility --- .../includes/load-synced-patterns.php | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 assets/boilerplate/includes/load-synced-patterns.php diff --git a/assets/boilerplate/includes/load-synced-patterns.php b/assets/boilerplate/includes/load-synced-patterns.php new file mode 100644 index 00000000..e0fffce4 --- /dev/null +++ b/assets/boilerplate/includes/load-synced-patterns.php @@ -0,0 +1,75 @@ +get_stylesheet_directory() . '/patterns/*.php'); + + foreach ($pattern_files as $pattern_file) { + + $pattern_data = get_file_data($pattern_file, array( + 'title' => 'Title', + 'slug' => 'Slug', + 'description' => 'Description', + 'inserter' => 'Inserter', + 'synced' => 'Synced', + )); + + // if the pattern is not synced do nothing + if ($pattern_data['synced'] !== 'yes') { + continue; + } + + $pattern_post = get_page_by_path(sanitize_title($pattern_data['slug']), OBJECT, 'wp_block'); + if ($pattern_post) { + // the post exists + $pattern_data['id'] = $pattern_post->ID; + // Note, we are NOT updating the post. If you want that behavior install the CBT plugin. + } + else { + // the post does not exist. create it. + $pattern_data['content'] = CBT_render_pattern($pattern_file); + $pattern_data['id'] = wp_insert_post(array( + 'post_title' => $pattern_data['title'], + 'post_name' => $pattern_data['slug'], + 'post_content' => $pattern_data['content'], + 'post_type' => 'wp_block', + 'post_status' => 'publish', + 'ping_status' => 'closed', + 'comment_status' => 'closed', + )); + } + + // UN register the unsynced pattern and RE register it with the reference to the synced pattern + // this pattern injects a synced pattern block as the content. + // and allows it to be used by anything that uses the wp:pattern (rather than the wp:block) + $pattern_registry = WP_Block_Patterns_Registry::get_instance(); + + if ( $pattern_registry->is_registered($pattern_data['slug'])){ + $pattern_registry->unregister($pattern_data['slug']); + } + + $pattern_registry->register( + $pattern_data['slug'], + array( + 'title' => $pattern_data['title'], + 'inserter' => false, + 'content' => '', + ) + ); + + } +}); From f557137630ec000dfb33cae10f2214f43c88d7d4 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Mon, 6 Jan 2025 16:06:37 -0500 Subject: [PATCH 12/13] Store both Synced and Unsynced patterns in the database AND files. Simplified pattern export logic. --- ...eate-block-theme-synced-pattern-loader.php | 250 ++++++------------ includes/create-theme/theme-patterns.php | 119 ++++----- 2 files changed, 129 insertions(+), 240 deletions(-) diff --git a/includes/class-create-block-theme-synced-pattern-loader.php b/includes/class-create-block-theme-synced-pattern-loader.php index a20188a1..8edaf314 100644 --- a/includes/class-create-block-theme-synced-pattern-loader.php +++ b/includes/class-create-block-theme-synced-pattern-loader.php @@ -13,49 +13,58 @@ class CBT_Synced_Pattern_Loader public function __construct() { - // don't register the theme block patterns remove_action('init', '_register_theme_block_patterns'); - add_action('init', [$this, 'CBT_register_theme_synced_block_patterns']); - - // add the theme block patterns to the block collection - add_filter('rest_post_dispatch', [$this, 'CBT_filter_blocks_api_response'], 10, 3); - add_filter('rest_pre_dispatch', [$this, 'CBT_filter_block_update'], 10, 3); + add_action('init', [$this, 'CBT_register_theme_block_patterns']); } - public function CBT_register_theme_synced_block_patterns() + public function CBT_register_theme_block_patterns() { - + $registry = WP_Block_Patterns_Registry::get_instance(); $patterns = self::CBT_get_theme_block_patterns(); foreach ($patterns as $pattern) { - // if it is a synced pattern manage the post - if ($pattern['synced'] === 'yes') { + if ( $registry->is_registered( $pattern['slug'] ) ) { + continue; + } - //search for post by slug - $pattern_post = get_page_by_path(sanitize_title($pattern['slug']), OBJECT, 'wp_block'); + // if the pattern is hidden from the inserter just register it, don't add as a post + if ($pattern['inserter'] === 'no') { + register_block_pattern( $pattern['slug'], $pattern ); + continue; + } + + //search for post by slug + $pattern_post = get_page_by_path(sanitize_title($pattern['slug']), OBJECT, 'wp_block'); + + if ($pattern_post) { + // the pattern already exists + $post_id = $pattern_post->ID; + } - if ($pattern_post) { - $post_id = $pattern_post->ID; - // the synced pattern already exists - // update the post with the content in the file - wp_update_post(array( - 'ID' => $post_id, - 'post_content' => $pattern['content'], - )); - } else { - $post_id = wp_insert_post(array( - 'post_title' => $pattern['title'], - 'post_name' => $pattern['slug'], - 'post_content' => $pattern['content'], - 'post_type' => 'wp_block', - 'post_status' => 'publish', - 'ping_status' => 'closed', - 'comment_status' => 'closed', - )); + else { + $post_id = wp_insert_post(array( + 'post_title' => $pattern['title'], + 'post_name' => $pattern['slug'], + 'post_content' => $pattern['content'], + 'post_type' => 'wp_block', + 'post_status' => 'publish', + 'ping_status' => 'closed', + 'comment_status' => 'closed', + 'meta_input' => array( + 'wp_pattern_sync_status' => $pattern['synced'] === 'yes' ? "" : "unsynced", + ), + )); + + $categories = self::get_pattern_categories( $pattern ); + + if ( ! empty( $categories ) ) { + wp_set_object_terms( $post_id, $categories, 'wp_pattern_category' ); } + } - // add the pattern as an UNsynced pattern TOO so that it can be used in templates. + if ( $pattern['synced'] === 'yes' ) { + // register as an unsynced pattern TOO so that it can be used as pattern blocks in templates // this pattern injects a synced pattern block as the content. register_block_pattern( $pattern['slug'], @@ -65,16 +74,6 @@ public function CBT_register_theme_synced_block_patterns() 'content' => '', ) ); - } else { - // register the pattern and hide from the inserter - register_block_pattern( - $pattern['slug'], - array( - 'title' => $pattern['title'], - 'inserter' => false, - 'content' => $pattern['content'], - ) - ); } } } @@ -86,6 +85,12 @@ private static function CBT_render_pattern($pattern_file) return ob_get_clean(); } + /** + * Get all the block patterns from the theme. + * Includes the non-standard 'Synced' key. + * + * @return array + */ public static function CBT_get_theme_block_patterns() { @@ -123,15 +128,6 @@ public static function CBT_get_theme_block_patterns() $pattern_data['pattern_file'] = $pattern_file; $pattern_data['content'] = self::CBT_render_pattern($pattern_file); - // if the pattern is synced add the ID - - if ($pattern_data['synced'] === 'yes') { - $pattern_post = get_page_by_path(sanitize_title($pattern_data['slug']), OBJECT, 'wp_block'); - if ($pattern_post) { - $pattern_data['id'] = $pattern_post->ID; - } - } - $all_patterns[] = $pattern_data; } } @@ -139,126 +135,48 @@ public static function CBT_get_theme_block_patterns() return $all_patterns; } - private static function format_pattern_for_response($pattern_data) - { - return array( - 'id' => $pattern_data['id'] ?? 'CBT_' . $pattern_data['slug'], - 'file_path' => $pattern_data['pattern_file'], - 'slug' => $pattern_data['slug'] ?? null, - 'status' => 'publish', - 'type' => 'wp_block', - 'title' => array( - 'raw' => $pattern_data['title'] ?? null, - ), - 'content' => array( - 'raw' => $pattern_data['content'] ?? null, - 'protected' => false, - 'block_version' => null, - ), - 'excerpt' => array( - 'raw' => $pattern_data['description'] ?? null, - 'rendered' => null, - 'protected' => false, - ), - 'wp_pattern_category' => array(), - 'wp_pattern_sync_status' => $pattern_data['synced'] === 'yes' ? "" : "unsynced", - ); - } - - // Add in the block patterns from the theme to the collection of blocks - public function CBT_filter_blocks_api_response($response, $server, $request) + private function get_pattern_categories($pattern_data) { - if ($request->get_route() !== '/wp/v2/blocks') { - return $response; - } - - $data = $response->get_data(); - $patterns = self::CBT_get_theme_block_patterns(); - - // filter out the synced patterns - // filter out the patterns marked hidden - $patterns = array_filter($patterns, function ($pattern) { - return $pattern['synced'] !== 'yes' && $pattern['inserter'] !== 'no'; - }); - - - - $patterns = array_map([self::class, 'format_pattern_for_response'], $patterns); - - $response->set_data(array_merge($data, $patterns)); - - return $response; - } - - // Handle CBT block updates - public function CBT_filter_block_update($result, $server, $request) - { - $route = $request->get_route(); - - if (strpos($route, '/wp/v2/blocks/') !== 0) { - return $result; - } - - if (str_contains($route, 'CBT_')) { - $pattern_slug = ltrim(strstr($route, 'CBT_'), 'CBT_'); - } else { - //get the slug for the post with the pattern id - $pattern_id = $request->get_param('id'); - if (! $pattern_id) { - //get the ID from the route - $pattern_id = str_replace('/wp/v2/blocks/', '', $route); - } - $pattern_slug = get_post_field('post_name', $pattern_id); - } - - $theme_patterns = self::CBT_get_theme_block_patterns(); - - // if a pattern with a matching slug exists in the theme, do work on it - foreach ($theme_patterns as $pattern) { - - if (sanitize_title($pattern['slug']) === sanitize_title($pattern_slug)) { - - // if the request is a GET, return the pattern content - if ($request->get_method() === 'GET') { - // which we do below + //get the default pattern categories + $registered_pattern_categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(); + + $category_ids = array(); + $categories = explode(',', $pattern_data['categories']); + $terms = get_terms(array( + 'taxonomy' => 'wp_pattern_category', + 'hide_empty' => false, + 'fields' => 'all', + + )); + foreach ($categories as $category) { + $category = sanitize_title($category); + $found = false; + foreach ($terms as $term) { + if (sanitize_title($term->name) === $category || sanitize_title($term->slug) === $category) { + $category_ids[] = $term->term_id; + $found = true; + break; } - - // if the request is a PUT or POST, create/update the pattern content file - if ($request->get_method() === 'PUT' || $request->get_method() === 'POST') { - $block_content = $request->get_param('content'); - $synced_status = $pattern['synced'] === 'yes' ? 'Synced: yes' : ''; - $file_content = << - {$block_content} - PHP; - - file_put_contents($pattern['pattern_file'], $file_content); - - $pattern['content'] = $block_content; - } - - // if the request is a DELETE, delete the pattern content file - if ($request->get_method() === 'DELETE') { - unlink($pattern['pattern_file']); - } - - // if we pulled the real ID then we also want to do work on the database; - // return null to allow the natural action to happen too. - if ($pattern_id) { - return null; + } + if ( ! $found ) { + // See if it's in the registered_pattern_categories + foreach ($registered_pattern_categories as $registered_category) { + if ( + ( isset($registered_category['slug']) && sanitize_title($registered_category['slug']) === $category ) || + ( isset($registered_category['name']) && sanitize_title($registered_category['name']) === $category)) { + $term = wp_insert_term($registered_category['name'], 'wp_pattern_category', array( + 'slug' => $registered_category['slug'], + 'description' => $registered_category['description'] ?? '', + )); + $terms[] = (object) $term; + $category_ids[] = $term['term_id']; + $found = true; + break; + } } - - return rest_ensure_response(self::format_pattern_for_response($pattern)); } + // if the term is still not found then I guess we're just out of luck. } - - return $result; + return $category_ids; } } diff --git a/includes/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php index 632bfd53..ae22acca 100644 --- a/includes/create-theme/theme-patterns.php +++ b/includes/create-theme/theme-patterns.php @@ -21,7 +21,16 @@ public static function pattern_from_template( $template, $new_slug = null ) { ); } - public static function pattern_from_wp_block( $pattern_post ) { + public static function pattern_from_wp_block( $pattern_post, $options ) { + + if ( ! $options ) { + $options = array( + 'localizeText' => false, + 'removeNavRefs' => true, + 'localizeImages' => true, + ); + } + $pattern = new stdClass(); $pattern->id = $pattern_post->ID; $pattern->title = $pattern_post->post_title; @@ -43,6 +52,21 @@ public static function pattern_from_wp_block( $pattern_post ) { {$pattern_post->post_content} PHP; + $pattern = CBT_Theme_Templates::eliminate_environment_specific_content( $pattern, $options ); + + if ( array_key_exists( 'localizeText', $options ) && $options['localizeText'] ) { + $pattern = CBT_Theme_Templates::escape_text_in_template( $pattern ); + } + + if ( array_key_exists( 'localizeImages', $options ) && $options['localizeImages'] ) { + $pattern = CBT_Theme_Media::make_template_images_local( $pattern ); + + // Write the media assets if there are any + if ( $pattern->media ) { + CBT_Theme_Media::add_media_to_local( $pattern->media ); + } + } + return $pattern; } @@ -75,23 +99,16 @@ public static function create_pattern_link( $attributes ) { public static function replace_local_synced_pattern_references( $pattern ) { - // If we save patterns we have to update the templates (or none of the templates). - // However, we can't save it here because it will overwrite changes we make to the templates RE: Patterns. - // CBT_Theme_Templates::add_templates_to_local( 'all', null, null, null ); - // List all template and pattern files in the theme $base_dir = get_stylesheet_directory(); $patterns = glob( $base_dir . DIRECTORY_SEPARATOR . 'patterns' . DIRECTORY_SEPARATOR . '*.php' ); - $synced_patterns = glob( $base_dir . DIRECTORY_SEPARATOR . 'synced-patterns' . DIRECTORY_SEPARATOR . '*.php' ); $templates = glob( $base_dir . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . '*.html' ); $template_parts = glob( $base_dir . DIRECTORY_SEPARATOR . 'template-parts' . DIRECTORY_SEPARATOR . '*.html' ); - - - $needle = 'wp:block {"ref":' . $pattern['id']; - $replacement = 'wp:pattern {"slug":"' . $pattern['slug'] . '"'; - // Replace references to the local patterns in the theme - foreach ( array_merge( $patterns, $templates, $template_parts, $synced_patterns ) as $file ) { + $needle = 'wp:block {"ref":' . $pattern->id; + $replacement = 'wp:pattern {"slug":"' . $pattern->slug . '"'; + // Replace references to the local unsynced patterns in the theme + foreach ( array_merge( $patterns, $templates, $template_parts ) as $file ) { $file_content = file_get_contents( $file ); $file_content = str_replace( $needle, $replacement, $file_content ); file_put_contents( $file, $file_content ); @@ -102,37 +119,11 @@ public static function replace_local_synced_pattern_references( $pattern ) { CBT_Theme_Templates::clear_user_template_parts_customizations(); } - public static function prepare_pattern_for_export( $pattern, $options = null ) { - if ( ! $options ) { - $options = array( - 'localizeText' => false, - 'removeNavRefs' => true, - 'localizeImages' => true, - ); - } - - $pattern = CBT_Theme_Templates::eliminate_environment_specific_content( $pattern, $options ); - - if ( array_key_exists( 'localizeText', $options ) && $options['localizeText'] ) { - $pattern = CBT_Theme_Templates::escape_text_in_template( $pattern ); - } - - if ( array_key_exists( 'localizeImages', $options ) && $options['localizeImages'] ) { - $pattern = CBT_Theme_Media::make_template_images_local( $pattern ); - - // Write the media assets if there are any - if ( $pattern->media ) { - CBT_Theme_Media::add_media_to_local( $pattern->media ); - } - } - - return $pattern; - } - /** * Copy the local patterns as well as any media to the theme filesystem. */ public static function add_patterns_to_theme( $options = null ) { + $pattern_query = new WP_Query( array( 'post_type' => 'wp_block', @@ -141,33 +132,30 @@ public static function add_patterns_to_theme( $options = null ) { ); if ( $pattern_query->have_posts() ) { - foreach ( $pattern_query->posts as $pattern ) { - $pattern = self::pattern_from_wp_block( $pattern ); - $pattern = self::prepare_pattern_for_export( $pattern, $options ); - // Check pattern is synced before adding to theme. + $synced_patterns = array(); - if ( 'unsynced' === $pattern->sync_status ) { - self::add_unsynced_pattern_to_theme( $pattern ); - } - else { - self::add_synced_pattern_to_theme( $pattern ); + foreach ( $pattern_query->posts as $pattern ) { + $pattern = self::pattern_from_wp_block( $pattern, $options ); + self::add_pattern_to_theme( $pattern ); + if ( $pattern->is_synced === 'yes' ) { + $synced_patterns[] = $pattern; } } - } - // now replace all instances of synced blocks with pattern blocks - $patterns = CBT_Synced_Pattern_Loader::CBT_get_theme_block_patterns(); - $patterns = array_filter($patterns, function ($pattern) { - return $pattern['synced'] === 'yes'; - }); - foreach ($patterns as $pattern) { - self::replace_local_synced_pattern_references($pattern); + // now replace all instances of synced blocks with pattern blocks + // we wait uttil all patterns are added to the theme before doing this + // because some patterns may reference other patterns + foreach ($synced_patterns as $pattern) { + self::replace_local_synced_pattern_references($pattern); + } } + + } - public static function add_synced_pattern_to_theme($pattern) + public static function add_pattern_to_theme($pattern) { $patterns_dir = get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'patterns' . DIRECTORY_SEPARATOR; $pattern_file = $patterns_dir . $pattern->name . '.php'; @@ -188,21 +176,4 @@ public static function add_synced_pattern_to_theme($pattern) ) ); } - - public static function add_unsynced_pattern_to_theme($pattern) - { - $patterns_dir = get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'patterns' . DIRECTORY_SEPARATOR; - $pattern_file = $patterns_dir . $pattern->name . '.php'; - - // If there is no patterns folder, create it. - if ( ! is_dir( $patterns_dir ) ) { - wp_mkdir_p( $patterns_dir ); - } - - // Create the pattern file. - file_put_contents( $pattern_file, $pattern->content); - - // Remove it from the database to ensure that these patterns are loaded from the theme. - wp_delete_post($pattern->id, true); - } } From ca8e4c3bd547eac50ba79f848cd197a15c8be986 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Tue, 7 Jan 2025 09:49:42 -0500 Subject: [PATCH 13/13] Inserter false vs inserter 'no' for block registration --- includes/class-create-block-theme-synced-pattern-loader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/class-create-block-theme-synced-pattern-loader.php b/includes/class-create-block-theme-synced-pattern-loader.php index 8edaf314..0addffcb 100644 --- a/includes/class-create-block-theme-synced-pattern-loader.php +++ b/includes/class-create-block-theme-synced-pattern-loader.php @@ -30,6 +30,7 @@ public function CBT_register_theme_block_patterns() // if the pattern is hidden from the inserter just register it, don't add as a post if ($pattern['inserter'] === 'no') { + $pattern['inserter'] = false; register_block_pattern( $pattern['slug'], $pattern ); continue; }