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

Try: Add synced patterns to theme on save #675

Open
wants to merge 25 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
30c683f
Add savePatterns option
mikachan Jun 27, 2024
06cbb20
Add basic add_patterns_to_theme function
mikachan Jun 27, 2024
7e13fed
Switch order of save options
mikachan Jun 27, 2024
4bdd3ed
Add prepare_pattern_for_export function
mikachan Jun 27, 2024
f12304f
Copy media to theme filesystem
mikachan Jun 27, 2024
da1b2aa
Fix pattern slug
mikachan Jun 27, 2024
4995876
Add replace_local_pattern_references
mikachan Jun 27, 2024
b538305
Tidy up pattern_from_wp_block
mikachan Jun 29, 2024
4dfb09e
Add pattern categories
mikachan Jul 4, 2024
003f275
Default to empty string for categories list
mikachan Jul 4, 2024
e74d5e8
Potentially save pattern sync status
mikachan Jul 10, 2024
fd2a550
Merge branch 'trunk' into try/export-patterns
mikachan Jul 31, 2024
2b812e2
Merge branch 'trunk' into try/export-patterns
mikachan Aug 1, 2024
b3e8b78
Merge branch 'trunk' into try/export-patterns
mikachan Aug 14, 2024
e6d6570
Refactor PHP content of pattern_from_template
mikachan Aug 14, 2024
6cf4438
Refactor PHP content of pattern_from_wp_block
mikachan Aug 14, 2024
e986584
Delete synced patterns after adding to theme
mikachan Aug 14, 2024
2c6a642
Refactor and add error handling
mikachan Aug 14, 2024
ea94e10
Update save patterns option description
mikachan Aug 15, 2024
f017b94
Remove pattern- prefix
mikachan Aug 15, 2024
6197656
Update option description
mikachan Aug 15, 2024
6de813f
Redirect to patterns page if editing a pattern
mikachan Aug 15, 2024
95c068a
Add check for preference.savePatterns
mikachan Aug 15, 2024
9961ced
Update templates that reference the pattern
mikachan Aug 16, 2024
c901be3
Merge branch 'trunk' into try/export-patterns
mikachan Aug 16, 2024
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
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 );
mikachan marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}
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
Loading