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
41 changes: 4 additions & 37 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-save.php';

/**
* The api functionality of the plugin leveraged by the site editor UI.
Expand Down Expand Up @@ -346,46 +347,12 @@ function rest_update_theme( $request ) {
* Save the user changes to the theme and clear user changes.
*/
function rest_save_theme( $request ) {
$result = CBT_Theme_Save::run( $request->get_params() );

$options = $request->get_params();

if ( isset( $options['saveFonts'] ) && true === $options['saveFonts'] ) {
CBT_Theme_Fonts::persist_font_settings();
if ( is_wp_error( $result ) ) {
return $result;
}

if ( isset( $options['saveTemplates'] ) && true === $options['saveTemplates'] ) {
if ( true === $options['processOnlySavedTemplates'] ) {
CBT_Theme_Templates::add_templates_to_local( 'user', null, null, $options );
} else {
if ( is_child_theme() ) {
CBT_Theme_Templates::add_templates_to_local( 'current', null, null, $options );
} else {
CBT_Theme_Templates::add_templates_to_local( 'all', null, null, $options );
}
}
CBT_Theme_Templates::clear_user_templates_customizations();
CBT_Theme_Templates::clear_user_template_parts_customizations();
}

if ( isset( $options['saveStyle'] ) && true === $options['saveStyle'] ) {
if ( is_child_theme() ) {
CBT_Theme_JSON::add_theme_json_to_local( 'current', null, null, $options );
} else {
CBT_Theme_JSON::add_theme_json_to_local( 'all', null, null, $options );
}
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(
array(
'status' => 'SUCCESS',
Expand Down
100 changes: 100 additions & 0 deletions includes/create-theme/theme-save.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php
/**
* Theme Save
*
* Service that persists user changes from the Editor to the active theme on
* disk. Shared by the REST endpoint at `/create-block-theme/v1/save` and any
* future non-REST callers (e.g. WP-CLI). The service does not perform
* capability or input-sanitization checks on its own — callers are
* responsible for those before invoking `run()`.
*
* @package Create_Block_Theme
*/
class CBT_Theme_Save {

/**
* Persist user changes from the Editor to the active theme on disk.
*
* Orchestrates the four optional save steps — fonts, templates, styles,
* patterns — gated by truthy flags on the options array. Templates and
* styles select scope ('user' / 'current' / 'all') based on
* `processOnlySavedTemplates` and `is_child_theme()`. After all steps run,
* the theme cache is invalidated once.
*
* Callers must enforce their own capability checks (e.g.
* `current_user_can( 'edit_theme_options' )`) and sanitize any non-flag
* values they place in `$options` that flow into downstream services
* such as `CBT_Theme_Patterns::add_patterns_to_theme()` and
* `CBT_Theme_Templates::add_templates_to_local()`. The REST endpoint
* relies on the route's `permission_callback` and the framework's
* sanitize layer; CLI and other callers must replicate that.
*
* @param array $options Options array with keys:
* - saveFonts (bool)
* - saveTemplates (bool)
* - processOnlySavedTemplates (bool)
* - saveStyle (bool)
* - savePatterns (bool)
* Plus any nested options consumed by downstream services.
*
* @return true|WP_Error true on success, WP_Error if the patterns step fails.
*/
public static function run( array $options ) {
$flags = self::normalize_flags( $options );

if ( $flags['saveFonts'] ) {
CBT_Theme_Fonts::persist_font_settings();
}

if ( $flags['saveTemplates'] ) {
$scope = $flags['processOnlySavedTemplates']
? 'user'
: ( is_child_theme() ? 'current' : 'all' );
CBT_Theme_Templates::add_templates_to_local( $scope, null, null, $options );
CBT_Theme_Templates::clear_user_templates_customizations();
CBT_Theme_Templates::clear_user_template_parts_customizations();
}

if ( $flags['saveStyle'] ) {
$scope = is_child_theme() ? 'current' : 'all';
CBT_Theme_JSON::add_theme_json_to_local( $scope, null, null, $options );
CBT_Theme_Styles::clear_user_styles_customizations();
}

if ( $flags['savePatterns'] ) {
$result = CBT_Theme_Patterns::add_patterns_to_theme( $options );
if ( is_wp_error( $result ) ) {
return $result;
}
}

wp_get_theme()->cache_delete();

return true;
}

/**
* Normalize the save flags through wp_validate_boolean().
*
* Collapses two prior issues: undefined-index notices when a flag was
* read without isset() (notably processOnlySavedTemplates), and strict
* `true ===` checks that rejected `1` / `"true"` / `"1"` from non-JS
* callers (CLI, form-encoded REST clients).
*
* Uses wp_validate_boolean() rather than ! empty() so the string
* "false" (commonly sent by query-string and form-encoded callers) is
* treated as falsy, not truthy.
*
* @param array $options Raw options array.
* @return array<string,bool> Normalized boolean flags.
*/
private static function normalize_flags( array $options ) {
return array(
'saveFonts' => wp_validate_boolean( $options['saveFonts'] ?? false ),
'saveTemplates' => wp_validate_boolean( $options['saveTemplates'] ?? false ),
'processOnlySavedTemplates' => wp_validate_boolean( $options['processOnlySavedTemplates'] ?? false ),
'saveStyle' => wp_validate_boolean( $options['saveStyle'] ?? false ),
'savePatterns' => wp_validate_boolean( $options['savePatterns'] ?? false ),
);
}
}
Loading
Loading