Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions includes/class-create-block-theme-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
require_once __DIR__ . '/create-theme/theme-readme.php';
require_once __DIR__ . '/create-theme/theme-fonts.php';
require_once __DIR__ . '/create-theme/theme-create.php';
require_once __DIR__ . '/create-theme/theme-settings-save.php';

/**
* The api functionality of the plugin leveraged by the site editor UI.
Expand Down Expand Up @@ -68,6 +69,17 @@ public function register_rest_routes() {
},
)
);
register_rest_route(
'create-block-theme/v1',
'/theme-settings',
array(
'methods' => 'POST',
'callback' => array( $this, 'rest_save_theme_settings' ),
'permission_callback' => function () {
return current_user_can( 'edit_theme_options' );
},
)
);
register_rest_route(
'create-block-theme/v1',
'/clone',
Expand Down Expand Up @@ -394,6 +406,28 @@ function rest_save_theme( $request ) {
);
}

/**
* Persist a partial theme.json payload from the Edit Theme Settings modal.
*
* Accepts the keys `settings`, `customTemplates`, `templateParts`, and
* `removedShadowDefaults`. Only keys present in the payload are written;
* missing keys leave the existing theme.json untouched.
*/
function rest_save_theme_settings( $request ) {
$result = CBT_Theme_Settings_Save::run( $request->get_json_params() );

if ( is_wp_error( $result ) ) {
return $result;
}

return new WP_REST_Response(
array(
'status' => 'SUCCESS',
'theme_json' => $result,
)
);
}

/**
* Get a list of all the font families used in the theme.
*
Expand Down
31 changes: 30 additions & 1 deletion includes/create-theme/resolver_additions.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,37 @@ public static function get_theme_file_contents() {

public static function write_theme_file_contents( $theme_json_data ) {
$theme_json = wp_json_encode( $theme_json_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
file_put_contents( static::get_file_path_from_theme( 'theme.json' ), $theme_json );
$target = static::get_file_path_from_theme( 'theme.json' );

// Atomic write with a request-unique temp file: write to a
// per-request sibling temp file, then rename into place. A
// per-request name prevents two concurrent saves from clobbering
// each other's staging payload (a shared `theme.json.tmp` is unsafe
// — request A could rename request B's truncated contents, or one
// could unlink the other's temp file mid-write). Each request
// cleans up only its own temp file on failure.
$tmp = $target . '.' . uniqid( '', true ) . '.tmp';
// Suppress warnings so a permission/disk error returns false cleanly
// rather than emitting a PHP warning that may be promoted to an
// exception. Callers must check the boolean return value.
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
$bytes = @file_put_contents( $tmp, $theme_json );
if ( false === $bytes ) {
return false;
}

// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
if ( ! @rename( $tmp, $target ) ) {
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
@unlink( $tmp );
return false;
}

static::clean_cached_data();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rest_save_theme also calls wp_get_theme()->cache_delete() after writing, should we do that here too?

// Bust the WP-level theme cache too — `clean_cached_data()` only
// clears the JSON resolver's caches, not `wp_get_theme()`.
wp_get_theme()->cache_delete();
return true;
}

public static function write_user_settings( $user_settings ) {
Expand Down
Loading
Loading