Skip to content

Commit

Permalink
Site Export: ensure that the export endpoint uses Gutenberg theme cla…
Browse files Browse the repository at this point in the history
…sses (#61561)

* This ensures that any theme exports get the benefit of the latest changes to theme json and resolver.

* check for function exists.

* 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.

* Add to version control would help

* Added i18n domain


Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
  • Loading branch information
3 people committed Jun 4, 2024
1 parent 9cee9c4 commit 6e2d9c5
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 0 deletions.
114 changes: 114 additions & 0 deletions lib/block-template-utils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php
/**
* Utilities used to fetch and create templates and template parts.
*
* @package gutenberg
*/

/**
* 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.', 'gutenberg' ) );
}

$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.', 'gutenberg' ) );
}

$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;
}
46 changes: 46 additions & 0 deletions lib/class-wp-rest-edit-site-export-controller-gutenberg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
/**
* Controller which provides REST endpoint for exporting current templates
* and template parts.
*
* This class extension exists so that theme exporting takes into account any updates/changes to
* WP_Theme_JSON_Gutenberg, WP_Theme_JSON_Resolver_Gutenberg or related classes.
*
* @package gutenberg
* @subpackage REST_API
* @since 5.9.0
*/

if ( class_exists( 'WP_REST_Edit_Site_Export_Controller_Gutenberg' ) ) {
return;
}

class WP_REST_Edit_Site_Export_Controller_Gutenberg extends WP_REST_Edit_Site_Export_Controller {
/**
* Output a ZIP file with an export of the current templates
* and template parts from the site editor, and close the connection.
*
* @since 5.9.0
*
* @return WP_Error|void
*/
public function export() {
// Generate the export file.
$filename = gutenberg_generate_block_templates_export_file();

if ( is_wp_error( $filename ) ) {
$filename->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;
}
}
2 changes: 2 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function gutenberg_is_experiment_enabled( $name ) {

// 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.
Expand Down Expand Up @@ -206,6 +207,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';
}
Expand Down
12 changes: 12 additions & 0 deletions lib/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' );

1 comment on commit 6e2d9c5

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 6e2d9c5.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/9358890040
📝 Reported issues:

Please sign in to comment.