390 lines
10 KiB
PHP
390 lines
10 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* Process of structures that adhere to the theme.json schema.
|
||
|
*
|
||
|
* @package WordPress
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Class that encapsulates the processing of
|
||
|
* structures that adhere to the theme.json spec.
|
||
|
*
|
||
|
* @access private
|
||
|
*/
|
||
|
class WP_Theme_JSON {
|
||
|
|
||
|
/**
|
||
|
* Container of data in theme.json format.
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
private $theme_json = null;
|
||
|
|
||
|
/**
|
||
|
* Holds the allowed block names extracted from block.json.
|
||
|
* Shared among all instances so we only process it once.
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
private static $allowed_block_names = null;
|
||
|
|
||
|
const ALLOWED_TOP_LEVEL_KEYS = array(
|
||
|
'version',
|
||
|
'settings',
|
||
|
);
|
||
|
|
||
|
const ALLOWED_SETTINGS = array(
|
||
|
'color' => array(
|
||
|
'custom' => null,
|
||
|
'customGradient' => null,
|
||
|
'duotone' => null,
|
||
|
'gradients' => null,
|
||
|
'link' => null,
|
||
|
'palette' => null,
|
||
|
),
|
||
|
'custom' => null,
|
||
|
'layout' => null,
|
||
|
'spacing' => array(
|
||
|
'customMargin' => null,
|
||
|
'customPadding' => null,
|
||
|
'units' => null,
|
||
|
),
|
||
|
'typography' => array(
|
||
|
'customFontSize' => null,
|
||
|
'customLineHeight' => null,
|
||
|
'dropCap' => null,
|
||
|
'fontSizes' => null,
|
||
|
),
|
||
|
);
|
||
|
|
||
|
const LATEST_SCHEMA = 1;
|
||
|
|
||
|
/**
|
||
|
* Constructor.
|
||
|
*
|
||
|
* @param array $theme_json A structure that follows the theme.json schema.
|
||
|
*/
|
||
|
public function __construct( $theme_json = array() ) {
|
||
|
if ( ! isset( $theme_json['version'] ) || self::LATEST_SCHEMA !== $theme_json['version'] ) {
|
||
|
$this->theme_json = array();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$this->theme_json = self::sanitize( $theme_json );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the allowed block names.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
private static function get_allowed_block_names() {
|
||
|
if ( null !== self::$allowed_block_names ) {
|
||
|
return self::$allowed_block_names;
|
||
|
}
|
||
|
|
||
|
self::$allowed_block_names = array_keys( WP_Block_Type_Registry::get_instance()->get_all_registered() );
|
||
|
|
||
|
return self::$allowed_block_names;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sanitizes the input according to the schemas.
|
||
|
*
|
||
|
* @param array $input Structure to sanitize.
|
||
|
*
|
||
|
* @return array The sanitized output.
|
||
|
*/
|
||
|
private static function sanitize( $input ) {
|
||
|
$output = array();
|
||
|
|
||
|
if ( ! is_array( $input ) ) {
|
||
|
return $output;
|
||
|
}
|
||
|
|
||
|
$allowed_blocks = self::get_allowed_block_names();
|
||
|
|
||
|
$output = array_intersect_key( $input, array_flip( self::ALLOWED_TOP_LEVEL_KEYS ) );
|
||
|
|
||
|
// Build the schema.
|
||
|
$schema = array();
|
||
|
$schema_settings_blocks = array();
|
||
|
foreach ( $allowed_blocks as $block ) {
|
||
|
$schema_settings_blocks[ $block ] = self::ALLOWED_SETTINGS;
|
||
|
}
|
||
|
$schema['settings'] = self::ALLOWED_SETTINGS;
|
||
|
$schema['settings']['blocks'] = $schema_settings_blocks;
|
||
|
|
||
|
// Remove anything that's not present in the schema.
|
||
|
foreach ( array( 'settings' ) as $subtree ) {
|
||
|
if ( ! isset( $input[ $subtree ] ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( ! is_array( $input[ $subtree ] ) ) {
|
||
|
unset( $output[ $subtree ] );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$result = self::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] );
|
||
|
|
||
|
if ( empty( $result ) ) {
|
||
|
unset( $output[ $subtree ] );
|
||
|
} else {
|
||
|
$output[ $subtree ] = $result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $output;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given a tree, removes the keys that are not present in the schema.
|
||
|
*
|
||
|
* It is recursive and modifies the input in-place.
|
||
|
*
|
||
|
* @param array $tree Input to process.
|
||
|
* @param array $schema Schema to adhere to.
|
||
|
*
|
||
|
* @return array Returns the modified $tree.
|
||
|
*/
|
||
|
private static function remove_keys_not_in_schema( $tree, $schema ) {
|
||
|
$tree = array_intersect_key( $tree, $schema );
|
||
|
|
||
|
foreach ( $schema as $key => $data ) {
|
||
|
if ( ! isset( $tree[ $key ] ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( is_array( $schema[ $key ] ) && is_array( $tree[ $key ] ) ) {
|
||
|
$tree[ $key ] = self::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] );
|
||
|
|
||
|
if ( empty( $tree[ $key ] ) ) {
|
||
|
unset( $tree[ $key ] );
|
||
|
}
|
||
|
} elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) {
|
||
|
unset( $tree[ $key ] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $tree;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the existing settings for each block.
|
||
|
*
|
||
|
* Example:
|
||
|
*
|
||
|
* {
|
||
|
* 'root': {
|
||
|
* 'color': {
|
||
|
* 'custom': true
|
||
|
* }
|
||
|
* },
|
||
|
* 'core/paragraph': {
|
||
|
* 'spacing': {
|
||
|
* 'customPadding': true
|
||
|
* }
|
||
|
* }
|
||
|
* }
|
||
|
*
|
||
|
* @return array Settings per block.
|
||
|
*/
|
||
|
public function get_settings() {
|
||
|
if ( ! isset( $this->theme_json['settings'] ) ) {
|
||
|
return array();
|
||
|
} else {
|
||
|
return $this->theme_json['settings'];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Builds metadata for the setting nodes, which returns in the form of:
|
||
|
*
|
||
|
* [
|
||
|
* [
|
||
|
* 'path' => ['path', 'to', 'some', 'node' ]
|
||
|
* ],
|
||
|
* [
|
||
|
* 'path' => [ 'path', 'to', 'other', 'node' ]
|
||
|
* ],
|
||
|
* ]
|
||
|
*
|
||
|
* @param array $theme_json The tree to extract setting nodes from.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
private static function get_setting_nodes( $theme_json ) {
|
||
|
$nodes = array();
|
||
|
if ( ! isset( $theme_json['settings'] ) ) {
|
||
|
return $nodes;
|
||
|
}
|
||
|
|
||
|
// Top-level.
|
||
|
$nodes[] = array(
|
||
|
'path' => array( 'settings' ),
|
||
|
);
|
||
|
|
||
|
// Calculate paths for blocks.
|
||
|
if ( ! isset( $theme_json['settings']['blocks'] ) ) {
|
||
|
return $nodes;
|
||
|
}
|
||
|
|
||
|
foreach ( $theme_json['settings']['blocks'] as $name => $node ) {
|
||
|
$nodes[] = array(
|
||
|
'path' => array( 'settings', 'blocks', $name ),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $nodes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Merge new incoming data.
|
||
|
*
|
||
|
* @param WP_Theme_JSON $incoming Data to merge.
|
||
|
*/
|
||
|
public function merge( $incoming ) {
|
||
|
$incoming_data = $incoming->get_raw_data();
|
||
|
$this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data );
|
||
|
|
||
|
// The array_replace_recursive algorithm merges at the leaf level.
|
||
|
// For leaf values that are arrays it will use the numeric indexes for replacement.
|
||
|
// In those cases, what we want is to use the incoming value, if it exists.
|
||
|
//
|
||
|
// These are the cases that have array values at the leaf levels.
|
||
|
$properties = array();
|
||
|
$properties[] = array( 'color', 'palette' );
|
||
|
$properties[] = array( 'color', 'gradients' );
|
||
|
$properties[] = array( 'custom' );
|
||
|
$properties[] = array( 'spacing', 'units' );
|
||
|
$properties[] = array( 'typography', 'fontSizes' );
|
||
|
$properties[] = array( 'typography', 'fontFamilies' );
|
||
|
|
||
|
$nodes = self::get_setting_nodes( $this->theme_json );
|
||
|
foreach ( $nodes as $metadata ) {
|
||
|
foreach ( $properties as $property_path ) {
|
||
|
$path = array_merge( $metadata['path'], $property_path );
|
||
|
$node = _wp_array_get( $incoming_data, $path, array() );
|
||
|
if ( ! empty( $node ) ) {
|
||
|
_wp_array_set( $this->theme_json, $path, $node );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the raw data.
|
||
|
*
|
||
|
* @return array Raw data.
|
||
|
*/
|
||
|
public function get_raw_data() {
|
||
|
return $this->theme_json;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* Transforms the given editor settings according the
|
||
|
* add_theme_support format to the theme.json format.
|
||
|
*
|
||
|
* @param array $settings Existing editor settings.
|
||
|
*
|
||
|
* @return array Config that adheres to the theme.json schema.
|
||
|
*/
|
||
|
public static function get_from_editor_settings( $settings ) {
|
||
|
$theme_settings = array(
|
||
|
'version' => self::LATEST_SCHEMA,
|
||
|
'settings' => array(),
|
||
|
);
|
||
|
|
||
|
// Deprecated theme supports.
|
||
|
if ( isset( $settings['disableCustomColors'] ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['color'] ) ) {
|
||
|
$theme_settings['settings']['color'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors'];
|
||
|
}
|
||
|
|
||
|
if ( isset( $settings['disableCustomGradients'] ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['color'] ) ) {
|
||
|
$theme_settings['settings']['color'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients'];
|
||
|
}
|
||
|
|
||
|
if ( isset( $settings['disableCustomFontSizes'] ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['typography'] ) ) {
|
||
|
$theme_settings['settings']['typography'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes'];
|
||
|
}
|
||
|
|
||
|
if ( isset( $settings['enableCustomLineHeight'] ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['typography'] ) ) {
|
||
|
$theme_settings['settings']['typography'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['typography']['customLineHeight'] = $settings['enableCustomLineHeight'];
|
||
|
}
|
||
|
|
||
|
if ( isset( $settings['enableCustomUnits'] ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['spacing'] ) ) {
|
||
|
$theme_settings['settings']['spacing'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ?
|
||
|
array( 'px', 'em', 'rem', 'vh', 'vw' ) :
|
||
|
$settings['enableCustomUnits'];
|
||
|
}
|
||
|
|
||
|
if ( isset( $settings['colors'] ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['color'] ) ) {
|
||
|
$theme_settings['settings']['color'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['color']['palette'] = $settings['colors'];
|
||
|
}
|
||
|
|
||
|
if ( isset( $settings['gradients'] ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['color'] ) ) {
|
||
|
$theme_settings['settings']['color'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['color']['gradients'] = $settings['gradients'];
|
||
|
}
|
||
|
|
||
|
if ( isset( $settings['fontSizes'] ) ) {
|
||
|
$font_sizes = $settings['fontSizes'];
|
||
|
// Back-compatibility for presets without units.
|
||
|
foreach ( $font_sizes as $key => $font_size ) {
|
||
|
if ( is_numeric( $font_size['size'] ) ) {
|
||
|
$font_sizes[ $key ]['size'] = $font_size['size'] . 'px';
|
||
|
}
|
||
|
}
|
||
|
if ( ! isset( $theme_settings['settings']['typography'] ) ) {
|
||
|
$theme_settings['settings']['typography'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['typography']['fontSizes'] = $font_sizes;
|
||
|
}
|
||
|
|
||
|
// This allows to make the plugin work with WordPress 5.7 beta
|
||
|
// as well as lower versions. The second check can be removed
|
||
|
// as soon as the minimum WordPress version for the plugin
|
||
|
// is bumped to 5.7.
|
||
|
if ( isset( $settings['enableCustomSpacing'] ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['spacing'] ) ) {
|
||
|
$theme_settings['settings']['spacing'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['spacing']['customPadding'] = $settings['enableCustomSpacing'];
|
||
|
}
|
||
|
|
||
|
// Things that didn't land in core yet, so didn't have a setting assigned.
|
||
|
if ( current( (array) get_theme_support( 'experimental-link-color' ) ) ) {
|
||
|
if ( ! isset( $theme_settings['settings']['color'] ) ) {
|
||
|
$theme_settings['settings']['color'] = array();
|
||
|
}
|
||
|
$theme_settings['settings']['color']['link'] = true;
|
||
|
}
|
||
|
|
||
|
return $theme_settings;
|
||
|
}
|
||
|
|
||
|
}
|