Skip to content

Commit

Permalink
Merge branch 'trunk' into add/save-fonts-in-variation-creation
Browse files Browse the repository at this point in the history
  • Loading branch information
mikachan authored Sep 17, 2024
2 parents 3c08b3c + 8993439 commit 6f83d5e
Show file tree
Hide file tree
Showing 18 changed files with 2,027 additions and 1,523 deletions.
634 changes: 339 additions & 295 deletions composer.lock

Large diffs are not rendered by default.

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 @@ -401,6 +401,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
57 changes: 45 additions & 12 deletions includes/create-theme/theme-fonts.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,42 +84,75 @@ public static function get_user_activated_fonts() {
return $user_settings['typography']['fontFamilies']['custom'] ?? null;
}

/**
* Make a pretty filename from a font face.
*
* The filename is based on the font family name, weight, style, unicode range and the source index.
* Example:
* $font_face = [ 'fontFamily' => 'Open Sans', 'fontWeight' => '400', 'fontStyle' => 'normal' ]
* $src = 'https://example.com/assets/fonts/open-sans-regular.ttf'
* $src_index = 0
* Returns: 'open-sans-400-normal.ttf'
*
* @param array $font_face
* @param string $src
* @param int $src_index
* @return string
*/
public static function make_filename_from_fontface( $font_face, $src, $src_index = 0 ) {
$font_extension = pathinfo( $src, PATHINFO_EXTENSION );
$font_filename = sanitize_title( $font_face['fontFamily'] )
. ( isset( $font_face['fontWeight'] ) ? '-' . sanitize_title( $font_face['fontWeight'] ) : '' )
. ( isset( $font_face['fontStyle'] ) ? '-' . sanitize_title( $font_face['fontStyle'] ) : '' )
. ( isset( $font_face['unicodeRange'] ) ? '-' . sanitize_title( $font_face['unicodeRange'] ) : '' )
. ( 0 !== $src_index ? '-' . $src_index : '' )
. '.'
. $font_extension;

return $font_filename;
}

/*
* Copy the font assets to the theme.
*
* @param array $font_families The font families to copy.
* @return array $font_families The font families with the font face src updated to the theme font asset location.
*/
public static function copy_font_assets_to_theme( $font_families ) {
$theme_font_asset_location = get_stylesheet_directory() . '/assets/fonts/';

require_once ABSPATH . 'wp-admin/includes/file.php';

if ( ! file_exists( $theme_font_asset_location ) ) {
mkdir( $theme_font_asset_location, 0777, true );
}
$theme_font_asset_location = path_join( get_stylesheet_directory(), 'assets/fonts/' );
// Create the font asset directory if it does not exist.
wp_mkdir_p( $theme_font_asset_location );

foreach ( $font_families as &$font_family ) {
if ( ! isset( $font_family['fontFace'] ) ) {
continue;
}

$font_family_dir_name = sanitize_title( $font_family['name'] );
$font_family_dir_path = path_join( $theme_font_asset_location, $font_family_dir_name );
// Crete a font family specific directory if it does not exist.
wp_mkdir_p( $font_family_dir_path );

foreach ( $font_family['fontFace'] as &$font_face ) {
// src can be a string or an array
// if it is a string, cast it to an array
$font_face['src'] = (array) $font_face['src'];
foreach ( $font_face['src'] as $font_src_index => &$font_src ) {
$font_filename = basename( $font_src );
$font_dir = wp_get_font_dir();
$font_filename = basename( $font_src );
$font_pretty_filename = self::make_filename_from_fontface( $font_face, $font_src, $font_src_index );
$font_face_path = path_join( $font_family_dir_path, $font_pretty_filename );
$font_dir = wp_get_font_dir();
if ( str_contains( $font_src, $font_dir['url'] ) ) {
// If the file is hosted on this server then copy it to the theme
copy( $font_dir['path'] . '/' . $font_filename, $theme_font_asset_location . '/' . $font_filename );
copy( path_join( $font_dir['path'], $font_filename ), $font_face_path );
} else {
// otherwise download it from wherever it is hosted
$tmp_file = download_url( $font_src );
copy( $tmp_file, $theme_font_asset_location . $font_filename );
copy( $tmp_file, $font_face_path );
unlink( $tmp_file );
}
$font_face['src'][ $font_src_index ] = 'file:./assets/fonts/' . $font_filename;
$font_face_family_path = path_join( $font_family_dir_name, $font_pretty_filename );
$font_face['src'][ $font_src_index ] = path_join( 'file:./assets/fonts/', $font_face_family_path );
}
}
}
Expand Down
40 changes: 36 additions & 4 deletions includes/create-theme/theme-locale.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,58 @@
class CBT_Theme_Locale {

/**
* Escape a string for localization.
* Escape text for localization.
*
* @param string $string The string to escape.
* @return string The escaped string.
*/
public static function escape_string( $string ) {
private static function escape_text_content( $string ) {
// Avoid escaping if the text is not a string.
if ( ! is_string( $string ) ) {
return $string;
}

// Check if string is empty.
if ( '' === $string ) {
return $string;
}

// Check if the text is already escaped.
if ( str_starts_with( $string, '<?php' ) ) {
return $string;
}

$string = addcslashes( $string, "'" );

return "<?php esc_html_e('" . $string . "', '" . wp_get_theme()->get( 'TextDomain' ) . "');?>";
}

/**
* Escape an html element attribute for localization.
*
* @param string $string The string to escape.
* @return string The escaped string.
*/
private static function escape_attribute( $string ) {
// Avoid escaping if the text is not a string.
if ( ! is_string( $string ) ) {
return $string;
}

// Check if string is empty.
if ( '' === $string ) {
return $string;
}

// Check if the text is already escaped.
if ( str_starts_with( $string, '<?php' ) ) {
return $string;
}

$string = addcslashes( $string, "'" );
return "<?php esc_attr_e('" . $string . "', '" . wp_get_theme()->get( 'TextDomain' ) . "');?>";
}

/**
* Get a replacement pattern for escaping the text from the html content of a block.
*
Expand Down Expand Up @@ -109,7 +141,7 @@ public static function escape_text_content_of_blocks( $blocks ) {
return preg_replace_callback(
$pattern,
function( $matches ) {
return $matches[1] . self::escape_string( $matches[2] ) . $matches[3];
return $matches[1] . self::escape_text_content( $matches[2] ) . $matches[3];
},
$content
);
Expand All @@ -125,7 +157,7 @@ function( $matches ) {
return preg_replace_callback(
$pattern,
function( $matches ) {
return 'alt="' . self::escape_string( $matches[1] ) . '"';
return 'alt="' . self::escape_attribute( $matches[1] ) . '"';
},
$content
);
Expand Down
176 changes: 164 additions & 12 deletions includes/create-theme/theme-patterns.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,46 @@ 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}
* 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 @@ -38,7 +61,7 @@ public static function escape_alt_for_pattern( $html ) {
public static function escape_text_for_pattern( $text ) {
if ( $text && trim( $text ) !== '' ) {
$escaped_text = addslashes( $text );
return "<?php echo esc_attr_e( '" . $escaped_text . "', '" . wp_get_theme()->get( 'Name' ) . "' ); ?>";
return "<?php esc_attr_e('" . $escaped_text . "', '" . wp_get_theme()->get( 'Name' ) . "');?>";
}
}

Expand All @@ -47,4 +70,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 );
}
}
}
}
}
Loading

0 comments on commit 6f83d5e

Please sign in to comment.