Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theme Export: Restore appearanceTools when exporting a theme #39840

Merged
merged 14 commits into from
Apr 7, 2022
Merged
133 changes: 125 additions & 8 deletions lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 {
'title',
);

const APPEARANCE_TOOLS_OPT_INS = array(
array( 'border', 'color' ),
array( 'border', 'radius' ),
array( 'border', 'style' ),
array( 'border', 'width' ),
array( 'color', 'link' ),
array( 'spacing', 'blockGap' ),
array( 'spacing', 'margin' ),
array( 'spacing', 'padding' ),
array( 'typography', 'lineHeight' ),
);

/**
* The valid properties under the settings key.
*
Expand Down Expand Up @@ -426,18 +438,48 @@ protected static function get_metadata_boolean( $data, $path, $default = false )
}

/**
* Returns a valid theme.json for a theme.
* Essentially, it flattens the preset data.
* Returns a valid theme.json as provided by a theme.
*
* Unlike get_raw_data() this returns the presets flattened,
* as provided by a theme. This also uses appearanceTools
* instead of their opt-ins if all of them are true.
*
* @return array
*/
public function get_data() {
$flattened_theme_json = $this->theme_json;
$nodes = static::get_setting_nodes( $this->theme_json );
$output = $this->theme_json;
$nodes = static::get_setting_nodes( $output );

/**
* Flatten the theme & custom origins into a single one.
*
* For example, the following:
*
* {
* "settings": {
* "color": {
* "palette": {
* "theme": [ {} ],
* "custom": [ {} ]
* }
* }
* }
* }
*
* will be converted to:
*
* {
* "settings": {
* "color": {
* "palette": [ {} ]
* }
* }
* }
*/
foreach ( $nodes as $node ) {
foreach ( static::PRESETS_METADATA as $preset_metadata ) {
$path = array_merge( $node['path'], $preset_metadata['path'] );
$preset = _wp_array_get( $flattened_theme_json, $path, null );
$preset = _wp_array_get( $output, $path, null );
if ( null === $preset ) {
continue;
}
Expand All @@ -461,12 +503,87 @@ public function get_data() {
foreach ( $items as $slug => $value ) {
$flattened_preset[] = array_merge( array( 'slug' => $slug ), $value );
}
_wp_array_set( $flattened_theme_json, $path, $flattened_preset );
_wp_array_set( $output, $path, $flattened_preset );
}
}

wp_recursive_ksort( $flattened_theme_json );
// If all of the static::APPEARANCE_TOOLS_OPT_INS are true,
// this code unsets them and sets 'appearanceTools' instead.
foreach ( $nodes as $node ) {
$all_opt_ins_are_set = true;
foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) {
$full_path = array_merge( $node['path'], $opt_in_path );
// Use "unset prop" as a marker instead of "null" because
// "null" can be a valid value for some props (e.g. blockGap).
$opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' );
if ( 'unset prop' === $opt_in_value ) {
$all_opt_ins_are_set = false;
break;
}
}

if ( $all_opt_ins_are_set ) {
_wp_array_set( $output, array_merge( $node['path'], array( 'appearanceTools' ) ), true );
foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) {
$full_path = array_merge( $node['path'], $opt_in_path );
// Use "unset prop" as a marker instead of "null" because
// "null" can be a valid value for some props (e.g. blockGap).
$opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' );
if ( true !== $opt_in_value ) {
continue;
}

// The following could be improved to be path independent.
// At the moment it relies on a couple of assumptions:
//
// - all opt-ins having a path of size 2.
// - there's two sources of settings: the top-level and the block-level.
if (
( 1 === count( $node['path'] ) ) &&
( 'settings' === $node['path'][0] )
) {
// Top-level settings.
unset( $output['settings'][ $opt_in_path[0] ][ $opt_in_path[1] ] );
if ( empty( $output['settings'][ $opt_in_path[0] ] ) ) {
unset( $output['settings'][ $opt_in_path[0] ] );
}
} elseif (
( 3 === count( $node['path'] ) ) &&
( 'settings' === $node['path'][0] ) &&
( 'blocks' === $node['path'][1] )
) {
// Block-level settings.
$block_name = $node['path'][2];
unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ][ $opt_in_path[1] ] );
if ( empty( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ) ) {
unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] );
}
}
}
}
}

wp_recursive_ksort( $output );

return $output;
}

/**
* Enables some settings.
*
* @since 5.9.0
*
* @param array $context The context to which the settings belong.
*/
protected static function do_opt_in_into_settings( &$context ) {
foreach ( static::APPEARANCE_TOOLS_OPT_INS as $path ) {
// Use "unset prop" as a marker instead of "null" because
// "null" can be a valid value for some props (e.g. blockGap).
Copy link
Contributor

Choose a reason for hiding this comment

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

Can unset prop be, or ever become, a valid value? I know it's unlikely for, say, color – but still. How about an object? It should also have a unique identity.

Copy link
Member

Choose a reason for hiding this comment

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

All existing opt-ins are booleans (or null).

if ( 'unset prop' === _wp_array_get( $context, $path, 'unset prop' ) ) {
_wp_array_set( $context, $path, true );
}
}

return $flattened_theme_json;
unset( $context['appearanceTools'] );
}
}
31 changes: 31 additions & 0 deletions phpunit/class-wp-theme-json-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -2472,4 +2472,35 @@ function test_export_data_deals_with_empty_data() {
$expected_v1 = array( 'version' => 2 );
$this->assertEqualSetsWithIndex( $expected_v1, $actual_v1 );
}

function test_export_data_sets_appearance_tools() {
$theme = new WP_Theme_JSON_Gutenberg(
array(
'version' => 2,
'settings' => array(
'appearanceTools' => true,
'blocks' => array(
'core/paragraph' => array(
'appearanceTools' => true,
),
),
),
)
);

$actual = $theme->get_data();
$expected = array(
'version' => 2,
'settings' => array(
'appearanceTools' => true,
'blocks' => array(
'core/paragraph' => array(
'appearanceTools' => true,
),
),
),
);

$this->assertEqualSetsWithIndex( $expected, $actual );
}
}