From 6d04c307a54037883d09d665573a8c18a3a4254b Mon Sep 17 00:00:00 2001 From: ramon Date: Fri, 10 May 2024 17:10:23 +1000 Subject: [PATCH 1/5] This ensures that any theme exports get the benefit of the latest changes to theme json and resolver. --- .../wordpress-6.6/block-template-utils.php | 108 ++++++++++++++++++ ...g-rest-edit-site-export-controller-6-6.php | 50 ++++++++ lib/compat/wordpress-6.6/rest-api.php | 10 ++ lib/load.php | 1 + 4 files changed, 169 insertions(+) create mode 100644 lib/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php diff --git a/lib/compat/wordpress-6.6/block-template-utils.php b/lib/compat/wordpress-6.6/block-template-utils.php index 953f6bf20c077e..f40c638c889ea5 100644 --- a/lib/compat/wordpress-6.6/block-template-utils.php +++ b/lib/compat/wordpress-6.6/block-template-utils.php @@ -360,3 +360,111 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t */ return apply_filters( 'get_block_templates', $query_result, $query, $template_type ); } + +/** + * Creates an export of the current templates and + * template parts from the site editor at the + * specified path in a ZIP file. + * + * @since 5.9.0 + * @since 6.0.0 Adds the whole theme to the export archive. + * + * @global string $wp_version The WordPress version string. + * + * @return WP_Error|string Path of the ZIP file or error on failure. + */ +function gutenberg_generate_block_templates_export_file() { + global $wp_version; + + if ( ! class_exists( 'ZipArchive' ) ) { + return new WP_Error( 'missing_zip_package', __( 'Zip Export not supported.' ) ); + } + + $obscura = wp_generate_password( 12, false, false ); + $theme_name = basename( get_stylesheet() ); + $filename = get_temp_dir() . $theme_name . $obscura . '.zip'; + + $zip = new ZipArchive(); + if ( true !== $zip->open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { + return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.' ) ); + } + + $zip->addEmptyDir( 'templates' ); + $zip->addEmptyDir( 'parts' ); + + // Get path of the theme. + $theme_path = wp_normalize_path( get_stylesheet_directory() ); + + // Create recursive directory iterator. + $theme_files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $theme_path ), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + // Make a copy of the current theme. + foreach ( $theme_files as $file ) { + // Skip directories as they are added automatically. + if ( ! $file->isDir() ) { + // Get real and relative path for current file. + $file_path = wp_normalize_path( $file ); + $relative_path = substr( $file_path, strlen( $theme_path ) + 1 ); + + if ( ! wp_is_theme_directory_ignored( $relative_path ) ) { + $zip->addFile( $file_path, $relative_path ); + } + } + } + + // Load templates into the zip file. + $templates = gutenberg_get_block_templates(); + foreach ( $templates as $template ) { + $template->content = traverse_and_serialize_blocks( + parse_blocks( $template->content ), + '_remove_theme_attribute_from_template_part_block' + ); + + $zip->addFromString( + 'templates/' . $template->slug . '.html', + $template->content + ); + } + + // Load template parts into the zip file. + $template_parts = gutenberg_get_block_templates( array(), 'wp_template_part' ); + foreach ( $template_parts as $template_part ) { + $zip->addFromString( + 'parts/' . $template_part->slug . '.html', + $template_part->content + ); + } + + // Load theme.json into the zip file. + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) ); + // Merge with user data. + $tree->merge( WP_Theme_JSON_Resolver_Gutenberg::get_user_data() ); + + $theme_json_raw = $tree->get_data(); + // If a version is defined, add a schema. + if ( $theme_json_raw['version'] ) { + $theme_json_version = 'wp/' . substr( $wp_version, 0, 3 ); + $schema = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' ); + $theme_json_raw = array_merge( $schema, $theme_json_raw ); + } + + // Convert to a string. + $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); + + // Replace 4 spaces with a tab. + $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded ); + + // Add the theme.json file to the zip. + $zip->addFromString( + 'theme.json', + $theme_json_tabbed + ); + + // Save changes to the zip file. + $zip->close(); + + return $filename; +} diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php new file mode 100644 index 00000000000000..4ee8e15576190e --- /dev/null +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php @@ -0,0 +1,50 @@ +add_data( array( 'status' => 500 ) ); + + return $filename; + } + + $theme_name = basename( get_stylesheet() ); + header( 'Content-Type: application/zip' ); + header( 'Content-Disposition: attachment; filename=' . $theme_name . '.zip' ); + header( 'Content-Length: ' . filesize( $filename ) ); + flush(); + readfile( $filename ); + unlink( $filename ); + exit; + } +} diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index 54796685f45ab8..d7e711236b912d 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -87,3 +87,13 @@ function gutenberg_register_global_styles_revisions_endpoints() { } add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); + +/** + * Registers the Edit Site Export REST API routes. + */ +function gutenberg_register_edit_site_export_controller_endpoints() { + $edit_site_export_controller = new Gutenberg_REST_Edit_Site_Export_Controller_6_6(); + $edit_site_export_controller->register_routes(); +} + +add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_controller_endpoints' ); diff --git a/lib/load.php b/lib/load.php index b00c024778b5ff..b5d90cb436bda3 100644 --- a/lib/load.php +++ b/lib/load.php @@ -48,6 +48,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.6 compat. require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php'; + require_once __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php'; require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php'; require __DIR__ . '/compat/wordpress-6.6/rest-api.php'; From e23beb37608628f372e8b6582c3468f6e4ccea1e Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 11 May 2024 08:27:07 +1000 Subject: [PATCH 2/5] check for function exists. --- lib/compat/wordpress-6.6/rest-api.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index d7e711236b912d..c304c9aa4273bb 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -88,12 +88,14 @@ function gutenberg_register_global_styles_revisions_endpoints() { add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); -/** - * Registers the Edit Site Export REST API routes. - */ -function gutenberg_register_edit_site_export_controller_endpoints() { - $edit_site_export_controller = new Gutenberg_REST_Edit_Site_Export_Controller_6_6(); - $edit_site_export_controller->register_routes(); +if ( ! function_exists( 'gutenberg_register_edit_site_export_controller_endpoints' ) ) { + /** + * Registers the Edit Site Export REST API routes. + */ + function gutenberg_register_edit_site_export_controller_endpoints() { + $edit_site_export_controller = new Gutenberg_REST_Edit_Site_Export_Controller_6_6(); + $edit_site_export_controller->register_routes(); + } } add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_controller_endpoints' ); From b62e6f26fa0bf47bb85d363d2b536f1a5834750b Mon Sep 17 00:00:00 2001 From: ramon Date: Mon, 13 May 2024 15:22:42 +1000 Subject: [PATCH 3/5] Moving changes to `/lib` folder because none of the changes are backwards compat specific. Having this extension permanently in Gutenberg means that theme.json exporting will always use the latest version of the Theme JSON family of classes. --- ...edit-site-export-controller-gutenberg.php} | 28 ++--- .../wordpress-6.6/block-template-utils.php | 108 ------------------ lib/compat/wordpress-6.6/rest-api.php | 12 -- lib/load.php | 3 +- lib/rest-api.php | 12 ++ 5 files changed, 26 insertions(+), 137 deletions(-) rename lib/{compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php => class-wp-rest-edit-site-export-controller-gutenberg.php} (59%) diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php b/lib/class-wp-rest-edit-site-export-controller-gutenberg.php similarity index 59% rename from lib/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php rename to lib/class-wp-rest-edit-site-export-controller-gutenberg.php index 4ee8e15576190e..c1a37e032e6ad3 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php +++ b/lib/class-wp-rest-edit-site-export-controller-gutenberg.php @@ -1,25 +1,21 @@ open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { - return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.' ) ); - } - - $zip->addEmptyDir( 'templates' ); - $zip->addEmptyDir( 'parts' ); - - // Get path of the theme. - $theme_path = wp_normalize_path( get_stylesheet_directory() ); - - // Create recursive directory iterator. - $theme_files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( $theme_path ), - RecursiveIteratorIterator::LEAVES_ONLY - ); - - // Make a copy of the current theme. - foreach ( $theme_files as $file ) { - // Skip directories as they are added automatically. - if ( ! $file->isDir() ) { - // Get real and relative path for current file. - $file_path = wp_normalize_path( $file ); - $relative_path = substr( $file_path, strlen( $theme_path ) + 1 ); - - if ( ! wp_is_theme_directory_ignored( $relative_path ) ) { - $zip->addFile( $file_path, $relative_path ); - } - } - } - - // Load templates into the zip file. - $templates = gutenberg_get_block_templates(); - foreach ( $templates as $template ) { - $template->content = traverse_and_serialize_blocks( - parse_blocks( $template->content ), - '_remove_theme_attribute_from_template_part_block' - ); - - $zip->addFromString( - 'templates/' . $template->slug . '.html', - $template->content - ); - } - - // Load template parts into the zip file. - $template_parts = gutenberg_get_block_templates( array(), 'wp_template_part' ); - foreach ( $template_parts as $template_part ) { - $zip->addFromString( - 'parts/' . $template_part->slug . '.html', - $template_part->content - ); - } - - // Load theme.json into the zip file. - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) ); - // Merge with user data. - $tree->merge( WP_Theme_JSON_Resolver_Gutenberg::get_user_data() ); - - $theme_json_raw = $tree->get_data(); - // If a version is defined, add a schema. - if ( $theme_json_raw['version'] ) { - $theme_json_version = 'wp/' . substr( $wp_version, 0, 3 ); - $schema = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' ); - $theme_json_raw = array_merge( $schema, $theme_json_raw ); - } - - // Convert to a string. - $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); - - // Replace 4 spaces with a tab. - $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded ); - - // Add the theme.json file to the zip. - $zip->addFromString( - 'theme.json', - $theme_json_tabbed - ); - - // Save changes to the zip file. - $zip->close(); - - return $filename; -} diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index c304c9aa4273bb..54796685f45ab8 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -87,15 +87,3 @@ function gutenberg_register_global_styles_revisions_endpoints() { } add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); - -if ( ! function_exists( 'gutenberg_register_edit_site_export_controller_endpoints' ) ) { - /** - * Registers the Edit Site Export REST API routes. - */ - function gutenberg_register_edit_site_export_controller_endpoints() { - $edit_site_export_controller = new Gutenberg_REST_Edit_Site_Export_Controller_6_6(); - $edit_site_export_controller->register_routes(); - } -} - -add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_controller_endpoints' ); diff --git a/lib/load.php b/lib/load.php index b5d90cb436bda3..f30f0900558611 100644 --- a/lib/load.php +++ b/lib/load.php @@ -48,12 +48,12 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.6 compat. require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php'; - require_once __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-edit-site-export-controller-6-6.php'; require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php'; require __DIR__ . '/compat/wordpress-6.6/rest-api.php'; // Plugin specific code. require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php'; + require_once __DIR__ . '/class-wp-rest-edit-site-export-controller-gutenberg.php'; require_once __DIR__ . '/rest-api.php'; // Experimental. @@ -205,6 +205,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/demo.php'; require __DIR__ . '/experiments-page.php'; require __DIR__ . '/interactivity-api.php'; +require __DIR__ . '/block-template-utils.php'; if ( gutenberg_is_experiment_enabled( 'gutenberg-full-page-client-side-navigation' ) ) { require __DIR__ . '/experimental/full-page-client-side-navigation.php'; } diff --git a/lib/rest-api.php b/lib/rest-api.php index 04f521d132c461..fedd75151584d5 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -18,3 +18,15 @@ function gutenberg_register_global_styles_endpoints() { $global_styles_controller->register_routes(); } add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' ); + +if ( ! function_exists( 'gutenberg_register_edit_site_export_controller_endpoints' ) ) { + /** + * Registers the Edit Site Export REST API routes. + */ + function gutenberg_register_edit_site_export_controller_endpoints() { + $edit_site_export_controller = new WP_REST_Edit_Site_Export_Controller_Gutenberg(); + $edit_site_export_controller->register_routes(); + } +} + +add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_controller_endpoints' ); From c8a9ca0f042b5c1dfdc18cfde97b5394e4258c6c Mon Sep 17 00:00:00 2001 From: ramon Date: Mon, 13 May 2024 15:27:46 +1000 Subject: [PATCH 4/5] Add to version control would help --- lib/block-template-utils.php | 114 ++++++++++++++++++ ...-edit-site-export-controller-gutenberg.php | 2 +- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 lib/block-template-utils.php diff --git a/lib/block-template-utils.php b/lib/block-template-utils.php new file mode 100644 index 00000000000000..32d8f89923f2a6 --- /dev/null +++ b/lib/block-template-utils.php @@ -0,0 +1,114 @@ +open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { + return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.' ) ); + } + + $zip->addEmptyDir( 'templates' ); + $zip->addEmptyDir( 'parts' ); + + // Get path of the theme. + $theme_path = wp_normalize_path( get_stylesheet_directory() ); + + // Create recursive directory iterator. + $theme_files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $theme_path ), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + // Make a copy of the current theme. + foreach ( $theme_files as $file ) { + // Skip directories as they are added automatically. + if ( ! $file->isDir() ) { + // Get real and relative path for current file. + $file_path = wp_normalize_path( $file ); + $relative_path = substr( $file_path, strlen( $theme_path ) + 1 ); + + if ( ! wp_is_theme_directory_ignored( $relative_path ) ) { + $zip->addFile( $file_path, $relative_path ); + } + } + } + + // Load templates into the zip file. + $templates = gutenberg_get_block_templates(); + foreach ( $templates as $template ) { + $template->content = traverse_and_serialize_blocks( + parse_blocks( $template->content ), + '_remove_theme_attribute_from_template_part_block' + ); + + $zip->addFromString( + 'templates/' . $template->slug . '.html', + $template->content + ); + } + + // Load template parts into the zip file. + $template_parts = gutenberg_get_block_templates( array(), 'wp_template_part' ); + foreach ( $template_parts as $template_part ) { + $zip->addFromString( + 'parts/' . $template_part->slug . '.html', + $template_part->content + ); + } + + // Load theme.json into the zip file. + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) ); + // Merge with user data. + $tree->merge( WP_Theme_JSON_Resolver_Gutenberg::get_user_data() ); + + $theme_json_raw = $tree->get_data(); + // If a version is defined, add a schema. + if ( $theme_json_raw['version'] ) { + $theme_json_version = 'wp/' . substr( $wp_version, 0, 3 ); + $schema = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' ); + $theme_json_raw = array_merge( $schema, $theme_json_raw ); + } + + // Convert to a string. + $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); + + // Replace 4 spaces with a tab. + $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded ); + + // Add the theme.json file to the zip. + $zip->addFromString( + 'theme.json', + $theme_json_tabbed + ); + + // Save changes to the zip file. + $zip->close(); + + return $filename; +} diff --git a/lib/class-wp-rest-edit-site-export-controller-gutenberg.php b/lib/class-wp-rest-edit-site-export-controller-gutenberg.php index c1a37e032e6ad3..b05de230dd0ccd 100644 --- a/lib/class-wp-rest-edit-site-export-controller-gutenberg.php +++ b/lib/class-wp-rest-edit-site-export-controller-gutenberg.php @@ -4,7 +4,7 @@ * and template parts. * * This class extension exists so that theme exporting takes into account any updates/changes to - * WP_Theme_JSON_Gutenberg or WP_Theme_JSON_Resolver_Gutenberg or related. + * WP_Theme_JSON_Gutenberg, WP_Theme_JSON_Resolver_Gutenberg or related classes. * * @package gutenberg * @subpackage REST_API From ccd2ac40454a365c36ae7a15777973d6441d02c1 Mon Sep 17 00:00:00 2001 From: ramon Date: Mon, 13 May 2024 15:32:07 +1000 Subject: [PATCH 5/5] Added i18n domain --- lib/block-template-utils.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/block-template-utils.php b/lib/block-template-utils.php index 32d8f89923f2a6..a644047d3cfdc1 100644 --- a/lib/block-template-utils.php +++ b/lib/block-template-utils.php @@ -21,7 +21,7 @@ function gutenberg_generate_block_templates_export_file() { global $wp_version; if ( ! class_exists( 'ZipArchive' ) ) { - return new WP_Error( 'missing_zip_package', __( 'Zip Export not supported.' ) ); + return new WP_Error( 'missing_zip_package', __( 'Zip Export not supported.', 'gutenberg' ) ); } $obscura = wp_generate_password( 12, false, false ); @@ -30,7 +30,7 @@ function gutenberg_generate_block_templates_export_file() { $zip = new ZipArchive(); if ( true !== $zip->open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { - return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.' ) ); + return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.', 'gutenberg' ) ); } $zip->addEmptyDir( 'templates' );