diff --git a/create-block-theme.php b/create-block-theme.php index bf0b9486..a66514ad 100644 --- a/create-block-theme.php +++ b/create-block-theme.php @@ -5,7 +5,7 @@ * Plugin Name: Create Block Theme * Plugin URI: https://wordpress.org/plugins/create-block-theme * Description: Generates a block theme - * Version: 2.2.0 + * Version: 2.3.0 * Author: WordPress.org * Author URI: https://wordpress.org/ * License: GNU General Public License v2 or later diff --git a/includes/create-theme/theme-create.php b/includes/create-theme/theme-create.php index 2629d434..c32f1a8f 100644 --- a/includes/create-theme/theme-create.php +++ b/includes/create-theme/theme-create.php @@ -25,6 +25,9 @@ public static function clone_current_theme( $theme ) { wp_mkdir_p( $new_theme_path ); + // Persist font settings for cloned theme. + CBT_Theme_Fonts::persist_font_settings(); + // Copy theme files. $template_options = array( 'localizeText' => false, diff --git a/includes/create-theme/theme-patterns.php b/includes/create-theme/theme-patterns.php index e7de4890..b2848cd2 100644 --- a/includes/create-theme/theme-patterns.php +++ b/includes/create-theme/theme-patterns.php @@ -4,23 +4,44 @@ class CBT_Theme_Patterns { public static function pattern_from_template( $template, $new_slug = null ) { $theme_slug = $new_slug ? $new_slug : wp_get_theme()->get( 'TextDomain' ); $pattern_slug = $theme_slug . '/' . $template->slug; - $pattern_content = ( - 'slug . ' - * Slug: ' . $pattern_slug . ' - * Categories: hidden - * Inserter: no - */ -?> -' . $template->content - ); + $pattern_content = <<title} + * Slug: {$pattern->slug} + * Categories: {$pattern->categories} + */ + ?> + {$pattern_post->post_content} + PHP; return array( 'slug' => $pattern_slug, 'content' => $pattern_content, ); } + public static function pattern_from_wp_block( $pattern_post ) { + $pattern = new stdClass(); + $pattern->id = $pattern_post->ID; + $pattern->title = $pattern_post->post_title; + $pattern->name = sanitize_title_with_dashes( $pattern_post->post_title ); + $pattern->slug = wp_get_theme()->get( 'TextDomain' ) . '/' . $pattern->name; + $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->content = <<title} + * Slug: {$pattern->slug} + * Categories: {$pattern->categories} + */ + ?> + {$pattern_post->post_content} + PHP; + + return $pattern; + } + public static function escape_alt_for_pattern( $html ) { if ( empty( $html ) ) { return $html; @@ -47,4 +68,91 @@ public static function create_pattern_link( $attributes ) { $attributes_json = json_encode( $block_attributes, JSON_UNESCAPED_SLASHES ); return ''; } + + public static function replace_local_pattern_references( $pattern ) { + // List all template and pattern files in the theme + $base_dir = get_stylesheet_directory(); + $patterns = glob( $base_dir . DIRECTORY_SEPARATOR . '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 ) { + $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 ); + } + } + + 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 ) { + $base_dir = get_stylesheet_directory(); + $patterns_dir = $base_dir . DIRECTORY_SEPARATOR . 'patterns'; + + $pattern_query = new WP_Query( + array( + 'post_type' => 'wp_block', + 'posts_per_page' => -1, + ) + ); + + 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_file = $patterns_dir . 'pattern-' . $pattern->name . '.php'; + file_put_contents( + $patterns_dir . DIRECTORY_SEPARATOR . 'pattern-' . $pattern->name . '.php', + $pattern->content + ); + + self::replace_local_pattern_references( $pattern ); + + /** + * if the pattern is not synced then remove if from + * database so patterns are loaded from the theme + * and not the database + */ + + if ( stripos( $pattern->content, 'pattern-overrides' ) !== true ) { + wp_delete_post( $pattern->id, true ); + } + } + } + + } } diff --git a/includes/create-theme/theme-readme.php b/includes/create-theme/theme-readme.php index ae303bde..2da3b57c 100644 --- a/includes/create-theme/theme-readme.php +++ b/includes/create-theme/theme-readme.php @@ -97,6 +97,9 @@ public static function create( $theme ) { // Adds the Images section $readme_content = self::add_or_update_section( 'Images', $image_credits, $readme_content ); + // Sanitize the readme content + $readme_content = self::sanitize( $readme_content ); + return $readme_content; } @@ -229,6 +232,9 @@ public static function update( $theme, $readme_content = '' ) { // Update image credits section. $readme_content = self::add_or_update_section( 'Images', $image_credits, $readme_content ); + // Sanitize the readme content. + $readme_content = self::sanitize( $readme_content ); + return $readme_content; } @@ -339,4 +345,16 @@ public static function get_sections() { return $sections; } + /** + * Sanitize the readme content. + * + * @param string $readme_content The readme content. + * @return string The sanitized readme content. + */ + private static function sanitize( $readme_content ) { + // Replaces DOS line endings with Unix line endings + $readme_content = str_replace( "\r\n", '', $readme_content ); + return $readme_content; + } + } diff --git a/package-lock.json b/package-lock.json index bf74cb6b..b6b42973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-block-theme", - "version": "2.2.0", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-block-theme", - "version": "2.2.0", + "version": "2.3.0", "license": "GPL-2.0-or-later", "dependencies": { "@codemirror/lang-json": "^6.0.1", diff --git a/package.json b/package.json index 72c1c5e4..ebcb80be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "create-block-theme", - "version": "2.2.0", + "version": "2.3.0", "private": true, "description": "Create a block-based theme", "author": "The WordPress Contributors", diff --git a/readme.txt b/readme.txt index 77a8f1fb..4d100341 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: wordpressdotorg, mikachan, onemaggie, pbking, scruffian, mmaattiia Tags: themes, theme, block-theme Requires at least: 6.5 Tested up to: 6.5 -Stable tag: 2.2.0 +Stable tag: 2.3.0 Requires PHP: 7.4 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -123,6 +123,22 @@ If you are having problems, please try the following: == Changelog == += 2.3.0 = +* Persist font settings when cloning a theme (#678) +* Landing Page: Improve design (#673) +* Fix small readme typo (#674) +* A11y: Improve color contrast for help button (#672) +* Quality: Fix warning error when exporting theme (#671) +* Remove unused REST API endpoint (#670) +* Refactor theme fonts class for readability (#661) +* Check if theme fonts present before removing (#660) +* Add an about section in the editor (#667) +* Update escaping function (#665) +* Make external links translatable (#663) +* Update url for blueprint (#658) +* Add image credits edit capabilities to the edit theme modal (#662) +* Quality: Remove unused PHP classes (#664) + = 2.2.0 = * Update modal width to 65vw (#638) * Fixed font utilities to work with font sources as an (optional) array. (#645) diff --git a/tests/CbtThemeReadme/base.php b/tests/CbtThemeReadme/base.php index 2200c4dd..1acd9e97 100644 --- a/tests/CbtThemeReadme/base.php +++ b/tests/CbtThemeReadme/base.php @@ -61,4 +61,18 @@ public function tear_down() { // Restore the original active theme. switch_theme( $this->orig_active_theme_slug ); } + + /** + * Removes the newlines from a string. + * + * This is useful to make it easier to search for strings in the readme content. + * Removes both DOS and Unix newlines. + * + * @param string $string + * @return string + */ + public function remove_newlines( $string ) { + return str_replace( array( "\r\n", "\n" ), '', $string ); + } + } diff --git a/tests/CbtThemeReadme/create.php b/tests/CbtThemeReadme/create.php index 7f9b6798..6faff42c 100644 --- a/tests/CbtThemeReadme/create.php +++ b/tests/CbtThemeReadme/create.php @@ -17,8 +17,11 @@ class CBT_ThemeReadme_Create extends CBT_Theme_Readme_UnitTestCase { public function test_create( $data ) { $readme = CBT_Theme_Readme::create( $data ); + // Check sanitazion before altering the content. + $this->assertStringNotContainsString( "\r\n", $readme, 'The readme content contains DOS newlines.' ); + // Removes the newlines from the readme content to make it easier to search for strings. - $readme_without_newlines = str_replace( "\n", '', $readme ); + $readme_without_newlines = $this->remove_newlines( $readme ); $expected_name = '== ' . $data['name'] . ' =='; $expected_description = '== Description ==' . $data['description']; @@ -30,10 +33,16 @@ public function test_create( $data ) { $expected_license = 'License: ' . $data['license']; $expected_license_uri = 'License URI: ' . $data['license_uri']; $expected_font_credits = '== Fonts ==' . - ( isset( $data['font_credits'] ) ? $data['font_credits'] : '' ); + ( isset( $data['font_credits'] ) + ? $this->remove_newlines( $data['font_credits'] ) + : '' + ); $expected_image_credits = '== Images ==' . - ( isset( $data['image_credits'] ) ? $data['image_credits'] : '' ); - $expected_recommended_plugins = '== Recommended Plugins ==' . $data['recommended_plugins']; + ( isset( $data['image_credits'] ) + ? $this->remove_newlines( $data['image_credits'] ) + : '' + ); + $expected_recommended_plugins = '== Recommended Plugins ==' . $this->remove_newlines( $data['recommended_plugins'] ); $this->assertStringContainsString( $expected_name, $readme_without_newlines, 'The expected name is missing.' ); $this->assertStringContainsString( $expected_author, $readme_without_newlines, 'The expected author is missing.' ); @@ -161,6 +170,27 @@ public function data_test_create() { 'font_credits' => 'Font credit example text', ), ), + /* + * This string contains DOS newlines. + * It uses double quotes to make PHP interpret the newlines as newlines and not as string literals. + */ + 'With DOS newlines' => array( + 'data' => array( + 'name' => 'My Theme', + 'description' => 'New theme description', + 'uri' => 'https://example.com', + 'author' => 'New theme author', + 'author_uri' => 'https://example.com/author', + 'copyright_year' => '2077', + 'wp_version' => '12.12', + 'required_php_version' => '10.0', + 'license' => 'GPLv2 or later', + 'license_uri' => 'https://www.gnu.org/licenses/gpl-2.0.html', + 'image_credits' => "New image credits \r\n New image credits 2", + 'recommended_plugins' => "Plugin1 \r\n Plugin2 \r\n Plugin3", + 'font_credits' => "Font1 \r\n Font2 \r\n Font3", + ), + ), // TODO: Add more test cases. ); } diff --git a/tests/CbtThemeReadme/update.php b/tests/CbtThemeReadme/update.php index 158736fd..d6ca9012 100644 --- a/tests/CbtThemeReadme/update.php +++ b/tests/CbtThemeReadme/update.php @@ -18,13 +18,16 @@ public function test_update( $data ) { $readme_content = CBT_Theme_Readme::get_content(); $readme = CBT_Theme_Readme::update( $data, $readme_content ); + // Check sanitazion before altering the content. + $this->assertStringNotContainsString( "\r\n", $readme, 'The readme content contains DOS newlines.' ); + // Removes the newlines from the readme content to make it easier to search for strings. - $readme_without_newlines = str_replace( "\n", '', $readme ); + $readme_without_newlines = $this->remove_newlines( $readme ); $expected_author = 'Contributors: ' . $data['author']; $expected_wp_version = 'Tested up to: ' . $data['wp_version'] ?? CBT_Theme_Utils::get_current_wordpress_version(); - $expected_image_credits = '== Images ==' . $data['image_credits']; - $expected_recommended_plugins = '== Recommended Plugins ==' . $data['recommended_plugins']; + $expected_image_credits = '== Images ==' . $this->remove_newlines( $data['image_credits'] ); + $expected_recommended_plugins = '== Recommended Plugins ==' . $this->remove_newlines( $data['recommended_plugins'] ); $this->assertStringContainsString( $expected_author, $readme_without_newlines, 'The expected author is missing.' ); $this->assertStringContainsString( $expected_wp_version, $readme_without_newlines, 'The expected WP version is missing.' ); @@ -33,7 +36,7 @@ public function test_update( $data ) { // Assertion specific to font credits. if ( isset( $data['font_credits'] ) ) { - $expected_font_credits = '== Fonts ==' . $data['font_credits']; + $expected_font_credits = '== Fonts ==' . $this->remove_newlines( $data['font_credits'] ); $this->assertStringContainsString( $expected_font_credits, $readme_without_newlines, 'The expected font credits are missing.' ); } } @@ -59,7 +62,20 @@ public function data_test_update() { 'recommended_plugins' => 'New recommended plugins', ), ), - // TODO: Add more test cases. + /* + * This string contains DOS newlines. + * It uses double quotes to make PHP interpret the newlines as newlines and not as string literals. + */ + 'Remove DOS newlines' => array( + 'data' => array( + 'description' => 'New theme description', + 'author' => 'New theme author', + 'wp_version' => '12.12', + 'image_credits' => "New image credits \r\n New image credits 2", + 'recommended_plugins' => "Plugin1 \r\n Plugin2 \r\n Plugin3", + 'font_credits' => "Font1 \r\n Font2 \r\n Font3", + ), + ), ); } }