Skip to content

Commit

Permalink
Try: Add synced patterns to theme on save (#675)
Browse files Browse the repository at this point in the history
* Add savePatterns option

* Add basic add_patterns_to_theme function

* Switch order of save options

* Add prepare_pattern_for_export function

* Copy media to theme filesystem

* Fix pattern slug

* Add replace_local_pattern_references

* Tidy up pattern_from_wp_block

* Add pattern categories

* Default to empty string for categories list

* Potentially save pattern sync status

* Refactor PHP content of pattern_from_template

Co-Authored-By: Elliott Richmond <[email protected]>

* Refactor PHP content of pattern_from_wp_block

Co-Authored-By: Elliott Richmond <[email protected]>

* Delete synced patterns after adding to theme

Co-Authored-By: Elliott Richmond <[email protected]>

* Refactor and add error handling

* Update save patterns option description

* Remove pattern- prefix

* Update option description

* Redirect to patterns page if editing a pattern

* Add check for preference.savePatterns

* Update templates that reference the pattern

---------

Co-authored-by: Elliott Richmond <[email protected]>
  • Loading branch information
mikachan and eirichmond authored Sep 9, 2024
1 parent 4911a06 commit 2ba5e3a
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 25 deletions.
8 changes: 8 additions & 0 deletions includes/class-create-block-theme-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,14 @@ function rest_save_theme( $request ) {
CBT_Theme_Styles::clear_user_styles_customizations();
}

if ( isset( $options['savePatterns'] ) && true === $options['savePatterns'] ) {
$response = CBT_Theme_Patterns::add_patterns_to_theme( $options );

if ( is_wp_error( $response ) ) {
return $response;
}
}

wp_get_theme()->cache_delete();

return new WP_REST_Response(
Expand Down
175 changes: 164 additions & 11 deletions includes/create-theme/theme-patterns.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,47 @@ 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: {$template->slug}
* Slug: {$pattern_slug}
* Categories: hidden
* Inserter: no
*/
?>
{$template->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->sync_status = get_post_meta( $pattern->id, 'wp_pattern_sync_status', true );
$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 +71,133 @@ 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 ) {
// 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 );

// 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 );
}

CBT_Theme_Templates::clear_user_templates_customizations();
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 ) {
$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_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 );
}
}
}
}
}
13 changes: 8 additions & 5 deletions includes/create-theme/theme-templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ class CBT_Theme_Templates {
* based on the given export_type.
*
* @param string $export_type The type of export to perform. 'all', 'current', or 'user'.
* @param array $templates_to_export List of specific templates to export.
* @return object An object containing the templates and parts that should be exported.
*/
public static function get_theme_templates( $export_type ) {
public static function get_theme_templates( $export_type, $templates_to_export = null ) {

$templates = get_block_templates();
$template_parts = get_block_templates( array(), 'wp_template_part' );
$templates = get_block_templates( array( 'slug__in' => $templates_to_export ) );
$template_parts = get_block_templates( array( 'slug__in' => $templates_to_export ), 'wp_template_part' );
$exported_templates = array();
$exported_parts = array();

Expand Down Expand Up @@ -195,10 +196,12 @@ public static function prepare_template_for_export( $template, $slug = null, $op
* @param string $export_type The type of export to perform. 'all', 'current', or 'user'.
* @param string $path The path to the theme folder. If null it is assumed to be the current theme.
* @param string $slug The slug of the theme. If null it is assumed to be the current theme.
* @param array $options An array of options to use when exporting the templates.
* @param array $templates_to_export List of specific templates to export. If null it will be fetched.
*/
public static function add_templates_to_local( $export_type, $path = null, $slug = null, $options = null ) {
public static function add_templates_to_local( $export_type, $path = null, $slug = null, $options = null, $templates_to_export = null ) {

$theme_templates = self::get_theme_templates( $export_type );
$theme_templates = self::get_theme_templates( $export_type, $templates_to_export );
$template_folders = get_block_theme_folders();

$base_dir = $path ? $path : get_stylesheet_directory();
Expand Down
55 changes: 46 additions & 9 deletions src/editor-sidebar/save-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const SaveThemePanel = () => {
saveTemplates: _preference?.saveTemplates ?? true,
processOnlySavedTemplates:
_preference?.processOnlySavedTemplates ?? true,
savePatterns: _preference?.savePatterns ?? true,
saveFonts: _preference?.saveFonts ?? true,
removeNavRefs: _preference?.removeNavRefs ?? false,
localizeText: _preference?.localizeText ?? false,
Expand Down Expand Up @@ -68,7 +69,22 @@ export const SaveThemePanel = () => {
'create-block-theme'
)
);
window.location.reload();

const searchParams = new URLSearchParams(
window?.location?.search
);
// If user is editing a pattern and savePatterns is true, redirect back to the patterns page.
if (
preference.savePatterns &&
searchParams.get( 'postType' ) === 'wp_block' &&
searchParams.get( 'postId' )
) {
window.location =
'/wp-admin/site-editor.php?postType=wp_block';
} else {
// If user is not editing a pattern, reload the editor.
window.location.reload();
}
} )
.catch( ( error ) => {
const errorMessage =
Expand Down Expand Up @@ -135,27 +151,44 @@ export const SaveThemePanel = () => {
handleTogglePreference( 'processOnlySavedTemplates' )
}
/>
<CheckboxControl
label={ __( 'Save Synced Patterns', 'create-block-theme' ) }
help={ __(
'Any synced patterns created in the Editor will be moved to the theme. Note that this will delete all synced patterns from the Editor and any references in templates will be made relative to the theme.',
'create-block-theme'
) }
checked={ preference.savePatterns }
onChange={ () => handleTogglePreference( 'savePatterns' ) }
/>
<CheckboxControl
label={ __( 'Localize Text', 'create-block-theme' ) }
help={ __(
'Any text in a template will be copied to a pattern and localized.',
'Any text in a template or pattern will be localized in a pattern.',
'create-block-theme'
) }
disabled={ ! preference.saveTemplates }
disabled={
! preference.saveTemplates && ! preference.savePatterns
}
checked={
preference.saveTemplates && preference.localizeText
( preference.saveTemplates ||
preference.savePatterns ) &&
preference.localizeText
}
onChange={ () => handleTogglePreference( 'localizeText' ) }
/>
<CheckboxControl
label={ __( 'Localize Images', 'create-block-theme' ) }
help={ __(
'Any images in a template will be copied to a local /assets folder and referenced from there via a pattern.',
'Any images in a template or pattern will be copied to a local /assets folder and referenced from there via a pattern.',
'create-block-theme'
) }
disabled={ ! preference.saveTemplates }
disabled={
! preference.saveTemplates && ! preference.savePatterns
}
checked={
preference.saveTemplates && preference.localizeImages
( preference.saveTemplates ||
preference.savePatterns ) &&
preference.localizeImages
}
onChange={ () =>
handleTogglePreference( 'localizeImages' )
Expand All @@ -170,9 +203,13 @@ export const SaveThemePanel = () => {
'Remove Navigation Refs from the theme returning your navigation to the default state.',
'create-block-theme'
) }
disabled={ ! preference.saveTemplates }
disabled={
! preference.saveTemplates && ! preference.savePatterns
}
checked={
preference.saveTemplates && preference.removeNavRefs
( preference.saveTemplates ||
preference.savePatterns ) &&
preference.removeNavRefs
}
onChange={ () => handleTogglePreference( 'removeNavRefs' ) }
/>
Expand Down

0 comments on commit 2ba5e3a

Please sign in to comment.