Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export patterns #693

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion create-block-theme.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions includes/create-theme/theme-create.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
130 changes: 119 additions & 11 deletions includes/create-theme/theme-patterns.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
'<?php
/**
* Title: ' . $template->slug . '
* Slug: ' . $pattern_slug . '
* Categories: hidden
* Inserter: no
*/
?>
' . $template->content
);
$pattern_content = <<<PHP
<?php
/**
* Title: {$pattern->title}
* Slug: {$pattern->slug}
* Categories: {$pattern->categories}
*/
?>
{$pattern_post->post_content}
Comment on lines +10 to +15
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Title: {$pattern->title}
* Slug: {$pattern->slug}
* Categories: {$pattern->categories}
*/
?>
{$pattern_post->post_content}
* Title: {$template->title}
* Slug: {$pattern_slug}
* Categories: {$template->categories}
*/
?>
{$template->post_content}

I think this should use $template rather than $pattern_post - I'm guessing this was accidentally copied over from pattern_from_wp_block. $template->categories might need to be created, too.

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 = <<<PHP
<?php
/**
* Title: {$pattern->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;
Expand All @@ -47,4 +68,91 @@ public static function create_pattern_link( $attributes ) {
$attributes_json = json_encode( $block_attributes, JSON_UNESCAPED_SLASHES );
return '<!-- wp:pattern ' . $attributes_json . ' /-->';
}

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 ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a neat way to check if the pattern is not synced. I already mentioned here, but I also found wp_pattern_sync_status in postmeta that we could use as well.

wp_delete_post( $pattern->id, true );
}
}
}

}
}
18 changes: 18 additions & 0 deletions includes/create-theme/theme-readme.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
18 changes: 17 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions tests/CbtThemeReadme/base.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

}
38 changes: 34 additions & 4 deletions tests/CbtThemeReadme/create.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand All @@ -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.' );
Expand Down Expand Up @@ -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.
);
}
Expand Down
26 changes: 21 additions & 5 deletions tests/CbtThemeReadme/update.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.' );
Expand All @@ -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.' );
}
}
Expand All @@ -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",
),
),
);
}
}