| 1 |
<?php |
| 2 |
/** |
| 3 |
* WP_Theme_JSON_Schema_Gutenberg class |
| 4 |
* |
| 5 |
* @package gutenberg |
| 6 |
* @since 5.9.0 |
| 7 |
*/ |
| 8 |
|
| 9 |
if ( class_exists( 'WP_Theme_JSON_Schema_Gutenberg' ) ) { |
| 10 |
return; |
| 11 |
} |
| 12 |
|
| 13 |
/** |
| 14 |
* Class that migrates a given theme.json structure to the latest schema. |
| 15 |
* |
| 16 |
* This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes). |
| 17 |
* This is a low-level API that may need to do breaking changes. Please, |
| 18 |
* use get_global_settings, get_global_styles, and get_global_stylesheet instead. |
| 19 |
* |
| 20 |
* @since 5.9.0 |
| 21 |
* @access private |
| 22 |
*/ |
| 23 |
#[AllowDynamicProperties] |
| 24 |
class WP_Theme_JSON_Schema_Gutenberg { |
| 25 |
|
| 26 |
/** |
| 27 |
* Maps old properties to their new location within the schema's settings. |
| 28 |
* This will be applied at both the defaults and individual block levels. |
| 29 |
*/ |
| 30 |
const V1_TO_V2_RENAMED_PATHS = array( |
| 31 |
'border.customRadius' => 'border.radius', |
| 32 |
'spacing.customMargin' => 'spacing.margin', |
| 33 |
'spacing.customPadding' => 'spacing.padding', |
| 34 |
'typography.customLineHeight' => 'typography.lineHeight', |
| 35 |
); |
| 36 |
|
| 37 |
/** |
| 38 |
* Function that migrates a given theme.json structure to the last version. |
| 39 |
* |
| 40 |
* @since 5.9.0 |
| 41 |
* @since 6.6.0 Migrate up to v3. |
| 42 |
* |
| 43 |
* @param array $theme_json The structure to migrate. |
| 44 |
* @param string $origin Optional. What source of data this object represents. |
| 45 |
* One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. |
| 46 |
* |
| 47 |
* @return array The structure in the last version. |
| 48 |
*/ |
| 49 |
public static function migrate( $theme_json, $origin = 'theme' ) { |
| 50 |
if ( ! isset( $theme_json['version'] ) ) { |
| 51 |
$theme_json = array( |
| 52 |
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, |
| 53 |
); |
| 54 |
} |
| 55 |
|
| 56 |
// Migrate each version in order starting with the current version. |
| 57 |
switch ( $theme_json['version'] ) { |
| 58 |
case 1: |
| 59 |
$theme_json = self::migrate_v1_to_v2( $theme_json ); |
| 60 |
// Deliberate fall through. Once migrated to v2, also migrate to v3. |
| 61 |
case 2: |
| 62 |
$theme_json = self::migrate_v2_to_v3( $theme_json, $origin ); |
| 63 |
} |
| 64 |
|
| 65 |
return $theme_json; |
| 66 |
} |
| 67 |
|
| 68 |
/** |
| 69 |
* Removes the custom prefixes for a few properties |
| 70 |
* that were part of v1: |
| 71 |
* |
| 72 |
* 'border.customRadius' => 'border.radius', |
| 73 |
* 'spacing.customMargin' => 'spacing.margin', |
| 74 |
* 'spacing.customPadding' => 'spacing.padding', |
| 75 |
* 'typography.customLineHeight' => 'typography.lineHeight', |
| 76 |
* |
| 77 |
* @since 5.9.0 |
| 78 |
* |
| 79 |
* @param array $old Data to migrate. |
| 80 |
* |
| 81 |
* @return array Data without the custom prefixes. |
| 82 |
*/ |
| 83 |
private static function migrate_v1_to_v2( $old ) { |
| 84 |
// Copy everything. |
| 85 |
$new = $old; |
| 86 |
|
| 87 |
// Overwrite the things that changed. |
| 88 |
if ( isset( $old['settings'] ) ) { |
| 89 |
$new['settings'] = self::rename_paths( $old['settings'], self::V1_TO_V2_RENAMED_PATHS ); |
| 90 |
} |
| 91 |
|
| 92 |
// Set the new version. |
| 93 |
$new['version'] = 2; |
| 94 |
|
| 95 |
return $new; |
| 96 |
} |
| 97 |
|
| 98 |
/** |
| 99 |
* Migrates from v2 to v3. |
| 100 |
* |
| 101 |
* - Sets settings.typography.defaultFontSizes to false if settings.typography.fontSizes are defined. |
| 102 |
* - Sets settings.spacing.defaultSpacingSizes to false if settings.spacing.spacingSizes are defined. |
| 103 |
* - Prevents settings.spacing.spacingSizes from merging with settings.spacing.spacingScale by |
| 104 |
* unsetting spacingScale when spacingSizes are defined. |
| 105 |
* |
| 106 |
* @since 6.6.0 |
| 107 |
* |
| 108 |
* @param array $old Data to migrate. |
| 109 |
* @param string $origin What source of data this object represents. |
| 110 |
* One of 'blocks', 'default', 'theme', or 'custom'. |
| 111 |
* @return array Data with defaultFontSizes set to false. |
| 112 |
*/ |
| 113 |
private static function migrate_v2_to_v3( $old, $origin ) { |
| 114 |
// Copy everything. |
| 115 |
$new = $old; |
| 116 |
|
| 117 |
// Set the new version. |
| 118 |
$new['version'] = 3; |
| 119 |
|
| 120 |
/* |
| 121 |
* Remaining changes do not need to be applied to the custom origin, |
| 122 |
* as they should take on the value of the theme origin. |
| 123 |
*/ |
| 124 |
if ( 'custom' === $origin ) { |
| 125 |
return $new; |
| 126 |
} |
| 127 |
|
| 128 |
/* |
| 129 |
* Even though defaultFontSizes is a new setting, we need to migrate |
| 130 |
* it as it controls the PRESETS_METADATA prevent_override which was |
| 131 |
* previously hardcoded to false. This only needs to happen when the |
| 132 |
* theme provided font sizes as they could match the default ones and |
| 133 |
* affect the generated CSS. And in v2 we provided default font sizes |
| 134 |
* when the theme did not provide any. |
| 135 |
*/ |
| 136 |
if ( isset( $old['settings']['typography']['fontSizes'] ) ) { |
| 137 |
$new['settings']['typography']['defaultFontSizes'] = false; |
| 138 |
} |
| 139 |
|
| 140 |
/* |
| 141 |
* Similarly to defaultFontSizes, we need to migrate defaultSpacingSizes |
| 142 |
* as it controls the PRESETS_METADATA prevent_override which was |
| 143 |
* previously hardcoded to false. This only needs to happen when the |
| 144 |
* theme provided spacing sizes via spacingSizes or spacingScale. |
| 145 |
*/ |
| 146 |
if ( |
| 147 |
isset( $old['settings']['spacing']['spacingSizes'] ) || |
| 148 |
isset( $old['settings']['spacing']['spacingScale'] ) |
| 149 |
) { |
| 150 |
$new['settings']['spacing']['defaultSpacingSizes'] = false; |
| 151 |
} |
| 152 |
|
| 153 |
/* |
| 154 |
* In v3 spacingSizes is merged with the generated spacingScale sizes |
| 155 |
* instead of completely replacing them. The v3 behavior is what was |
| 156 |
* documented for the v2 schema, but the code never actually did work |
| 157 |
* that way. Instead of surprising users with a behavior change two |
| 158 |
* years after the fact at the same time as a v3 update is introduced, |
| 159 |
* we'll continue using the "bugged" behavior for v2 themes. And treat |
| 160 |
* the "bug fix" as a breaking change for v3. |
| 161 |
*/ |
| 162 |
if ( isset( $old['settings']['spacing']['spacingSizes'] ) ) { |
| 163 |
unset( $new['settings']['spacing']['spacingScale'] ); |
| 164 |
} |
| 165 |
|
| 166 |
return $new; |
| 167 |
} |
| 168 |
|
| 169 |
/** |
| 170 |
* Processes the settings subtree. |
| 171 |
* |
| 172 |
* @since 5.9.0 |
| 173 |
* |
| 174 |
* @param array $settings Array to process. |
| 175 |
* @param array $paths_to_rename Paths to rename. |
| 176 |
* |
| 177 |
* @return array The settings in the new format. |
| 178 |
*/ |
| 179 |
private static function rename_paths( $settings, $paths_to_rename ) { |
| 180 |
$new_settings = $settings; |
| 181 |
|
| 182 |
// Process any renamed/moved paths within default settings. |
| 183 |
self::rename_settings( $new_settings, $paths_to_rename ); |
| 184 |
|
| 185 |
// Process individual block settings. |
| 186 |
if ( isset( $new_settings['blocks'] ) && is_array( $new_settings['blocks'] ) ) { |
| 187 |
foreach ( $new_settings['blocks'] as &$block_settings ) { |
| 188 |
self::rename_settings( $block_settings, $paths_to_rename ); |
| 189 |
} |
| 190 |
} |
| 191 |
|
| 192 |
return $new_settings; |
| 193 |
} |
| 194 |
|
| 195 |
/** |
| 196 |
* Processes a settings array, renaming or moving properties. |
| 197 |
* |
| 198 |
* @since 5.9.0 |
| 199 |
* |
| 200 |
* @param array $settings Reference to settings either defaults or an individual block's. |
| 201 |
* @param array $paths_to_rename Paths to rename. |
| 202 |
*/ |
| 203 |
private static function rename_settings( &$settings, $paths_to_rename ) { |
| 204 |
foreach ( $paths_to_rename as $original => $renamed ) { |
| 205 |
$original_path = explode( '.', $original ); |
| 206 |
$renamed_path = explode( '.', $renamed ); |
| 207 |
$current_value = _wp_array_get( $settings, $original_path, null ); |
| 208 |
|
| 209 |
if ( null !== $current_value ) { |
| 210 |
_wp_array_set( $settings, $renamed_path, $current_value ); |
| 211 |
self::unset_setting_by_path( $settings, $original_path ); |
| 212 |
} |
| 213 |
} |
| 214 |
} |
| 215 |
|
| 216 |
/** |
| 217 |
* Removes a property from within the provided settings by its path. |
| 218 |
* |
| 219 |
* @since 5.9.0 |
| 220 |
* |
| 221 |
* @param array $settings Reference to the current settings array. |
| 222 |
* @param array $path Path to the property to be removed. |
| 223 |
*/ |
| 224 |
private static function unset_setting_by_path( &$settings, $path ) { |
| 225 |
$tmp_settings = &$settings; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
| 226 |
$last_key = array_pop( $path ); |
| 227 |
foreach ( $path as $key ) { |
| 228 |
$tmp_settings = &$tmp_settings[ $key ]; |
| 229 |
} |
| 230 |
|
| 231 |
unset( $tmp_settings[ $last_key ] ); |
| 232 |
} |
| 233 |
} |
| 234 |
|