diff --git a/lib/compat/wordpress-6.5/class-gutenberg-rest-edit-site-export-controller-6-5.php b/lib/compat/wordpress-6.5/class-gutenberg-rest-edit-site-export-controller-6-5.php new file mode 100644 index 0000000000000..788dc61d1d440 --- /dev/null +++ b/lib/compat/wordpress-6.5/class-gutenberg-rest-edit-site-export-controller-6-5.php @@ -0,0 +1,45 @@ +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.5/rest-api.php b/lib/compat/wordpress-6.5/rest-api.php index 12d789fb58b86..3f17c702fe940 100644 --- a/lib/compat/wordpress-6.5/rest-api.php +++ b/lib/compat/wordpress-6.5/rest-api.php @@ -10,6 +10,16 @@ die( 'Silence is golden.' ); } +/** + * Registers the Edit Site Export REST API routes. + */ +function gutenberg_register_edit_site_export_endpoints() { + $edit_site_export_controller = new Gutenberg_REST_Edit_Site_Export_Controller_6_5(); + $edit_site_export_controller->register_routes(); +} + +add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_endpoints' ); + /** * Registers the Global Styles Revisions REST API routes. */ diff --git a/lib/experimental/theme-export/class-wp-theme-export.php b/lib/experimental/theme-export/class-wp-theme-export.php new file mode 100644 index 0000000000000..1df86c6407d1f --- /dev/null +++ b/lib/experimental/theme-export/class-wp-theme-export.php @@ -0,0 +1,123 @@ +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 = 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 = 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::get_theme_data( array(), array( 'with_supports' => false ) ); + // Merge with user data. + $tree->merge( WP_Theme_JSON_Resolver::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/load.php b/lib/load.php index 1aa55f7581c27..3fdaa09844808 100644 --- a/lib/load.php +++ b/lib/load.php @@ -42,6 +42,7 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.4/theme-previews.php'; // WordPress 6.5 compat. + require_once __DIR__ . '/compat/wordpress-6.5/class-gutenberg-rest-edit-site-export-controller-6-5.php'; require_once __DIR__ . '/compat/wordpress-6.5/class-gutenberg-rest-global-styles-revisions-controller-6-5.php'; require_once __DIR__ . '/compat/wordpress-6.5/rest-api.php'; @@ -113,6 +114,9 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/l10n.php'; require __DIR__ . '/experimental/synchronization.php'; +// Theme export class. +require __DIR__ . '/experimental/theme-export/class-wp-theme-export.php'; + if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) { require __DIR__ . '/experimental/disable-tinymce.php'; }