18
|
1 |
<?php |
|
2 |
/** |
|
3 |
* WP_Theme_JSON class |
|
4 |
* |
|
5 |
* @package WordPress |
|
6 |
* @subpackage Theme |
|
7 |
* @since 5.8.0 |
|
8 |
*/ |
|
9 |
|
|
10 |
/** |
|
11 |
* Class that encapsulates the processing of structures that adhere to the theme.json spec. |
|
12 |
* |
19
|
13 |
* This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes). |
|
14 |
* This is a low-level API that may need to do breaking changes. Please, |
|
15 |
* use get_global_settings, get_global_styles, and get_global_stylesheet instead. |
|
16 |
* |
18
|
17 |
* @access private |
|
18 |
*/ |
|
19 |
class WP_Theme_JSON { |
|
20 |
|
|
21 |
/** |
|
22 |
* Container of data in theme.json format. |
|
23 |
* |
|
24 |
* @since 5.8.0 |
|
25 |
* @var array |
|
26 |
*/ |
19
|
27 |
protected $theme_json = null; |
18
|
28 |
|
|
29 |
/** |
|
30 |
* Holds block metadata extracted from block.json |
|
31 |
* to be shared among all instances so we don't |
|
32 |
* process it twice. |
|
33 |
* |
|
34 |
* @since 5.8.0 |
|
35 |
* @var array |
|
36 |
*/ |
19
|
37 |
protected static $blocks_metadata = null; |
18
|
38 |
|
|
39 |
/** |
|
40 |
* The CSS selector for the top-level styles. |
|
41 |
* |
|
42 |
* @since 5.8.0 |
|
43 |
* @var string |
|
44 |
*/ |
|
45 |
const ROOT_BLOCK_SELECTOR = 'body'; |
|
46 |
|
|
47 |
/** |
|
48 |
* The sources of data this object can represent. |
|
49 |
* |
|
50 |
* @since 5.8.0 |
19
|
51 |
* @var string[] |
18
|
52 |
*/ |
|
53 |
const VALID_ORIGINS = array( |
19
|
54 |
'default', |
18
|
55 |
'theme', |
19
|
56 |
'custom', |
18
|
57 |
); |
|
58 |
|
|
59 |
/** |
|
60 |
* Presets are a set of values that serve |
|
61 |
* to bootstrap some styles: colors, font sizes, etc. |
|
62 |
* |
|
63 |
* They are a unkeyed array of values such as: |
|
64 |
* |
|
65 |
* ```php |
|
66 |
* array( |
|
67 |
* array( |
|
68 |
* 'slug' => 'unique-name-within-the-set', |
|
69 |
* 'name' => 'Name for the UI', |
|
70 |
* <value_key> => 'value' |
|
71 |
* ), |
|
72 |
* ) |
|
73 |
* ``` |
|
74 |
* |
|
75 |
* This contains the necessary metadata to process them: |
|
76 |
* |
19
|
77 |
* - path => Where to find the preset within the settings section. |
|
78 |
* - prevent_override => Disables override of default presets by theme presets. |
|
79 |
* The relationship between whether to override the defaults |
|
80 |
* and whether the defaults are enabled is inverse: |
|
81 |
* - If defaults are enabled => theme presets should not be overriden |
|
82 |
* - If defaults are disabled => theme presets should be overriden |
|
83 |
* For example, a theme sets defaultPalette to false, |
|
84 |
* making the default palette hidden from the user. |
|
85 |
* In that case, we want all the theme presets to be present, |
|
86 |
* so they should override the defaults by setting this false. |
|
87 |
* - use_default_names => whether to use the default names |
|
88 |
* - value_key => the key that represents the value |
|
89 |
* - value_func => optionally, instead of value_key, a function to generate |
|
90 |
* the value that takes a preset as an argument |
|
91 |
* (either value_key or value_func should be present) |
|
92 |
* - css_vars => template string to use in generating the CSS Custom Property. |
|
93 |
* Example output: "--wp--preset--duotone--blue: <value>" will generate as many CSS Custom Properties as presets defined |
|
94 |
* substituting the $slug for the slug's value for each preset value. |
|
95 |
* - classes => array containing a structure with the classes to |
|
96 |
* generate for the presets, where for each array item |
|
97 |
* the key is the class name and the value the property name. |
|
98 |
* The "$slug" substring will be replaced by the slug of each preset. |
|
99 |
* For example: |
|
100 |
* 'classes' => array( |
|
101 |
* '.has-$slug-color' => 'color', |
|
102 |
* '.has-$slug-background-color' => 'background-color', |
|
103 |
* '.has-$slug-border-color' => 'border-color', |
|
104 |
* ) |
|
105 |
* - properties => array of CSS properties to be used by kses to |
|
106 |
* validate the content of each preset |
|
107 |
* by means of the remove_insecure_properties method. |
18
|
108 |
* |
|
109 |
* @since 5.8.0 |
19
|
110 |
* @since 5.9.0 Added the `color.duotone` and `typography.fontFamilies` presets, |
|
111 |
* `use_default_names` preset key, and simplified the metadata structure. |
|
112 |
* @since 6.0.0 Replaced `override` with `prevent_override` and updated the |
|
113 |
* `prevent_overried` value for `color.duotone` to use `color.defaultDuotone`. |
18
|
114 |
* @var array |
|
115 |
*/ |
|
116 |
const PRESETS_METADATA = array( |
|
117 |
array( |
19
|
118 |
'path' => array( 'color', 'palette' ), |
|
119 |
'prevent_override' => array( 'color', 'defaultPalette' ), |
|
120 |
'use_default_names' => false, |
|
121 |
'value_key' => 'color', |
|
122 |
'css_vars' => '--wp--preset--color--$slug', |
|
123 |
'classes' => array( |
|
124 |
'.has-$slug-color' => 'color', |
|
125 |
'.has-$slug-background-color' => 'background-color', |
|
126 |
'.has-$slug-border-color' => 'border-color', |
18
|
127 |
), |
19
|
128 |
'properties' => array( 'color', 'background-color', 'border-color' ), |
|
129 |
), |
|
130 |
array( |
|
131 |
'path' => array( 'color', 'gradients' ), |
|
132 |
'prevent_override' => array( 'color', 'defaultGradients' ), |
|
133 |
'use_default_names' => false, |
|
134 |
'value_key' => 'gradient', |
|
135 |
'css_vars' => '--wp--preset--gradient--$slug', |
|
136 |
'classes' => array( '.has-$slug-gradient-background' => 'background' ), |
|
137 |
'properties' => array( 'background' ), |
18
|
138 |
), |
|
139 |
array( |
19
|
140 |
'path' => array( 'color', 'duotone' ), |
|
141 |
'prevent_override' => array( 'color', 'defaultDuotone' ), |
|
142 |
'use_default_names' => false, |
|
143 |
'value_func' => 'wp_get_duotone_filter_property', |
|
144 |
'css_vars' => '--wp--preset--duotone--$slug', |
|
145 |
'classes' => array(), |
|
146 |
'properties' => array( 'filter' ), |
18
|
147 |
), |
|
148 |
array( |
19
|
149 |
'path' => array( 'typography', 'fontSizes' ), |
|
150 |
'prevent_override' => false, |
|
151 |
'use_default_names' => true, |
|
152 |
'value_key' => 'size', |
|
153 |
'css_vars' => '--wp--preset--font-size--$slug', |
|
154 |
'classes' => array( '.has-$slug-font-size' => 'font-size' ), |
|
155 |
'properties' => array( 'font-size' ), |
|
156 |
), |
|
157 |
array( |
|
158 |
'path' => array( 'typography', 'fontFamilies' ), |
|
159 |
'prevent_override' => false, |
|
160 |
'use_default_names' => false, |
|
161 |
'value_key' => 'fontFamily', |
|
162 |
'css_vars' => '--wp--preset--font-family--$slug', |
|
163 |
'classes' => array( '.has-$slug-font-family' => 'font-family' ), |
|
164 |
'properties' => array( 'font-family' ), |
18
|
165 |
), |
|
166 |
); |
|
167 |
|
|
168 |
/** |
|
169 |
* Metadata for style properties. |
|
170 |
* |
19
|
171 |
* Each element is a direct mapping from the CSS property name to the |
|
172 |
* path to the value in theme.json & block attributes. |
18
|
173 |
* |
|
174 |
* @since 5.8.0 |
19
|
175 |
* @since 5.9.0 Added the `border-*`, `font-family`, `font-style`, `font-weight`, |
|
176 |
* `letter-spacing`, `margin-*`, `padding-*`, `--wp--style--block-gap`, |
|
177 |
* `text-decoration`, `text-transform`, and `filter` properties, |
|
178 |
* simplified the metadata structure. |
18
|
179 |
* @var array |
|
180 |
*/ |
|
181 |
const PROPERTIES_METADATA = array( |
19
|
182 |
'background' => array( 'color', 'gradient' ), |
|
183 |
'background-color' => array( 'color', 'background' ), |
|
184 |
'border-radius' => array( 'border', 'radius' ), |
|
185 |
'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), |
|
186 |
'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), |
|
187 |
'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ), |
|
188 |
'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ), |
|
189 |
'border-color' => array( 'border', 'color' ), |
|
190 |
'border-width' => array( 'border', 'width' ), |
|
191 |
'border-style' => array( 'border', 'style' ), |
|
192 |
'color' => array( 'color', 'text' ), |
|
193 |
'font-family' => array( 'typography', 'fontFamily' ), |
|
194 |
'font-size' => array( 'typography', 'fontSize' ), |
|
195 |
'font-style' => array( 'typography', 'fontStyle' ), |
|
196 |
'font-weight' => array( 'typography', 'fontWeight' ), |
|
197 |
'letter-spacing' => array( 'typography', 'letterSpacing' ), |
|
198 |
'line-height' => array( 'typography', 'lineHeight' ), |
|
199 |
'margin' => array( 'spacing', 'margin' ), |
|
200 |
'margin-top' => array( 'spacing', 'margin', 'top' ), |
|
201 |
'margin-right' => array( 'spacing', 'margin', 'right' ), |
|
202 |
'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), |
|
203 |
'margin-left' => array( 'spacing', 'margin', 'left' ), |
|
204 |
'padding' => array( 'spacing', 'padding' ), |
|
205 |
'padding-top' => array( 'spacing', 'padding', 'top' ), |
|
206 |
'padding-right' => array( 'spacing', 'padding', 'right' ), |
|
207 |
'padding-bottom' => array( 'spacing', 'padding', 'bottom' ), |
|
208 |
'padding-left' => array( 'spacing', 'padding', 'left' ), |
|
209 |
'--wp--style--block-gap' => array( 'spacing', 'blockGap' ), |
|
210 |
'text-decoration' => array( 'typography', 'textDecoration' ), |
|
211 |
'text-transform' => array( 'typography', 'textTransform' ), |
|
212 |
'filter' => array( 'filter', 'duotone' ), |
|
213 |
); |
|
214 |
|
|
215 |
/** |
|
216 |
* Protected style properties. |
|
217 |
* |
|
218 |
* These style properties are only rendered if a setting enables it |
|
219 |
* via a value other than `null`. |
|
220 |
* |
|
221 |
* Each element maps the style property to the corresponding theme.json |
|
222 |
* setting key. |
|
223 |
* |
|
224 |
* @since 5.9.0 |
|
225 |
*/ |
|
226 |
const PROTECTED_PROPERTIES = array( |
|
227 |
'spacing.blockGap' => array( 'spacing', 'blockGap' ), |
|
228 |
); |
|
229 |
|
|
230 |
/** |
|
231 |
* The top-level keys a theme.json can have. |
|
232 |
* |
|
233 |
* @since 5.8.0 As `ALLOWED_TOP_LEVEL_KEYS`. |
|
234 |
* @since 5.9.0 Renamed from `ALLOWED_TOP_LEVEL_KEYS` to `VALID_TOP_LEVEL_KEYS`, |
|
235 |
* added the `customTemplates` and `templateParts` values. |
|
236 |
* @var string[] |
|
237 |
*/ |
|
238 |
const VALID_TOP_LEVEL_KEYS = array( |
|
239 |
'customTemplates', |
|
240 |
'patterns', |
|
241 |
'settings', |
|
242 |
'styles', |
|
243 |
'templateParts', |
|
244 |
'version', |
|
245 |
'title', |
|
246 |
); |
|
247 |
|
|
248 |
/** |
|
249 |
* The valid properties under the settings key. |
|
250 |
* |
|
251 |
* @since 5.8.0 As `ALLOWED_SETTINGS`. |
|
252 |
* @since 5.9.0 Renamed from `ALLOWED_SETTINGS` to `VALID_SETTINGS`, |
|
253 |
* added new properties for `border`, `color`, `spacing`, |
|
254 |
* and `typography`, and renamed others according to the new schema. |
|
255 |
* @since 6.0.0 Added `color.defaultDuotone`. |
|
256 |
* @var array |
|
257 |
*/ |
|
258 |
const VALID_SETTINGS = array( |
|
259 |
'appearanceTools' => null, |
|
260 |
'border' => array( |
|
261 |
'color' => null, |
|
262 |
'radius' => null, |
|
263 |
'style' => null, |
|
264 |
'width' => null, |
18
|
265 |
), |
19
|
266 |
'color' => array( |
|
267 |
'background' => null, |
|
268 |
'custom' => null, |
|
269 |
'customDuotone' => null, |
|
270 |
'customGradient' => null, |
|
271 |
'defaultDuotone' => null, |
|
272 |
'defaultGradients' => null, |
|
273 |
'defaultPalette' => null, |
|
274 |
'duotone' => null, |
|
275 |
'gradients' => null, |
|
276 |
'link' => null, |
|
277 |
'palette' => null, |
|
278 |
'text' => null, |
18
|
279 |
), |
19
|
280 |
'custom' => null, |
|
281 |
'layout' => array( |
|
282 |
'contentSize' => null, |
|
283 |
'wideSize' => null, |
|
284 |
), |
|
285 |
'spacing' => array( |
|
286 |
'blockGap' => null, |
|
287 |
'margin' => null, |
|
288 |
'padding' => null, |
|
289 |
'units' => null, |
18
|
290 |
), |
19
|
291 |
'typography' => array( |
|
292 |
'customFontSize' => null, |
|
293 |
'dropCap' => null, |
|
294 |
'fontFamilies' => null, |
|
295 |
'fontSizes' => null, |
|
296 |
'fontStyle' => null, |
|
297 |
'fontWeight' => null, |
|
298 |
'letterSpacing' => null, |
|
299 |
'lineHeight' => null, |
|
300 |
'textDecoration' => null, |
|
301 |
'textTransform' => null, |
18
|
302 |
), |
|
303 |
); |
|
304 |
|
|
305 |
/** |
19
|
306 |
* The valid properties under the styles key. |
|
307 |
* |
|
308 |
* @since 5.8.0 As `ALLOWED_STYLES`. |
|
309 |
* @since 5.9.0 Renamed from `ALLOWED_STYLES` to `VALID_STYLES`, |
|
310 |
* added new properties for `border`, `filter`, `spacing`, |
|
311 |
* and `typography`. |
18
|
312 |
* @var array |
|
313 |
*/ |
19
|
314 |
const VALID_STYLES = array( |
18
|
315 |
'border' => array( |
19
|
316 |
'color' => null, |
18
|
317 |
'radius' => null, |
19
|
318 |
'style' => null, |
|
319 |
'width' => null, |
18
|
320 |
), |
|
321 |
'color' => array( |
|
322 |
'background' => null, |
|
323 |
'gradient' => null, |
|
324 |
'text' => null, |
|
325 |
), |
19
|
326 |
'filter' => array( |
|
327 |
'duotone' => null, |
|
328 |
), |
18
|
329 |
'spacing' => array( |
19
|
330 |
'margin' => null, |
|
331 |
'padding' => null, |
|
332 |
'blockGap' => 'top', |
18
|
333 |
), |
|
334 |
'typography' => array( |
19
|
335 |
'fontFamily' => null, |
|
336 |
'fontSize' => null, |
|
337 |
'fontStyle' => null, |
|
338 |
'fontWeight' => null, |
|
339 |
'letterSpacing' => null, |
|
340 |
'lineHeight' => null, |
|
341 |
'textDecoration' => null, |
|
342 |
'textTransform' => null, |
18
|
343 |
), |
|
344 |
); |
|
345 |
|
|
346 |
/** |
19
|
347 |
* The valid elements that can be found under styles. |
|
348 |
* |
18
|
349 |
* @since 5.8.0 |
19
|
350 |
* @var string[] |
18
|
351 |
*/ |
|
352 |
const ELEMENTS = array( |
|
353 |
'link' => 'a', |
|
354 |
'h1' => 'h1', |
|
355 |
'h2' => 'h2', |
|
356 |
'h3' => 'h3', |
|
357 |
'h4' => 'h4', |
|
358 |
'h5' => 'h5', |
|
359 |
'h6' => 'h6', |
|
360 |
); |
|
361 |
|
|
362 |
/** |
19
|
363 |
* Options that settings.appearanceTools enables. |
|
364 |
* |
|
365 |
* @since 6.0.0 |
|
366 |
* @var array |
|
367 |
*/ |
|
368 |
const APPEARANCE_TOOLS_OPT_INS = array( |
|
369 |
array( 'border', 'color' ), |
|
370 |
array( 'border', 'radius' ), |
|
371 |
array( 'border', 'style' ), |
|
372 |
array( 'border', 'width' ), |
|
373 |
array( 'color', 'link' ), |
|
374 |
array( 'spacing', 'blockGap' ), |
|
375 |
array( 'spacing', 'margin' ), |
|
376 |
array( 'spacing', 'padding' ), |
|
377 |
array( 'typography', 'lineHeight' ), |
|
378 |
); |
|
379 |
|
|
380 |
/** |
|
381 |
* The latest version of the schema in use. |
|
382 |
* |
18
|
383 |
* @since 5.8.0 |
19
|
384 |
* @since 5.9.0 Changed value from 1 to 2. |
18
|
385 |
* @var int |
|
386 |
*/ |
19
|
387 |
const LATEST_SCHEMA = 2; |
18
|
388 |
|
|
389 |
/** |
|
390 |
* Constructor. |
|
391 |
* |
|
392 |
* @since 5.8.0 |
|
393 |
* |
19
|
394 |
* @param array $theme_json A structure that follows the theme.json schema. |
|
395 |
* @param string $origin Optional. What source of data this object represents. |
|
396 |
* One of 'default', 'theme', or 'custom'. Default 'theme'. |
18
|
397 |
*/ |
|
398 |
public function __construct( $theme_json = array(), $origin = 'theme' ) { |
19
|
399 |
if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { |
18
|
400 |
$origin = 'theme'; |
|
401 |
} |
|
402 |
|
19
|
403 |
$this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); |
|
404 |
$valid_block_names = array_keys( static::get_blocks_metadata() ); |
|
405 |
$valid_element_names = array_keys( static::ELEMENTS ); |
|
406 |
$theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names ); |
|
407 |
$this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); |
18
|
408 |
|
|
409 |
// Internally, presets are keyed by origin. |
19
|
410 |
$nodes = static::get_setting_nodes( $this->theme_json ); |
18
|
411 |
foreach ( $nodes as $node ) { |
19
|
412 |
foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
|
413 |
$path = array_merge( $node['path'], $preset_metadata['path'] ); |
18
|
414 |
$preset = _wp_array_get( $this->theme_json, $path, null ); |
|
415 |
if ( null !== $preset ) { |
19
|
416 |
// If the preset is not already keyed by origin. |
|
417 |
if ( isset( $preset[0] ) || empty( $preset ) ) { |
|
418 |
_wp_array_set( $this->theme_json, $path, array( $origin => $preset ) ); |
|
419 |
} |
18
|
420 |
} |
|
421 |
} |
|
422 |
} |
|
423 |
} |
|
424 |
|
|
425 |
/** |
19
|
426 |
* Enables some opt-in settings if theme declared support. |
|
427 |
* |
|
428 |
* @since 5.9.0 |
|
429 |
* |
|
430 |
* @param array $theme_json A theme.json structure to modify. |
|
431 |
* @return array The modified theme.json structure. |
|
432 |
*/ |
|
433 |
protected static function maybe_opt_in_into_settings( $theme_json ) { |
|
434 |
$new_theme_json = $theme_json; |
|
435 |
|
|
436 |
if ( |
|
437 |
isset( $new_theme_json['settings']['appearanceTools'] ) && |
|
438 |
true === $new_theme_json['settings']['appearanceTools'] |
|
439 |
) { |
|
440 |
static::do_opt_in_into_settings( $new_theme_json['settings'] ); |
|
441 |
} |
|
442 |
|
|
443 |
if ( isset( $new_theme_json['settings']['blocks'] ) && is_array( $new_theme_json['settings']['blocks'] ) ) { |
|
444 |
foreach ( $new_theme_json['settings']['blocks'] as &$block ) { |
|
445 |
if ( isset( $block['appearanceTools'] ) && ( true === $block['appearanceTools'] ) ) { |
|
446 |
static::do_opt_in_into_settings( $block ); |
|
447 |
} |
|
448 |
} |
|
449 |
} |
|
450 |
|
|
451 |
return $new_theme_json; |
|
452 |
} |
|
453 |
|
|
454 |
/** |
|
455 |
* Enables some settings. |
|
456 |
* |
|
457 |
* @since 5.9.0 |
|
458 |
* |
|
459 |
* @param array $context The context to which the settings belong. |
|
460 |
*/ |
|
461 |
protected static function do_opt_in_into_settings( &$context ) { |
|
462 |
foreach ( static::APPEARANCE_TOOLS_OPT_INS as $path ) { |
|
463 |
// Use "unset prop" as a marker instead of "null" because |
|
464 |
// "null" can be a valid value for some props (e.g. blockGap). |
|
465 |
if ( 'unset prop' === _wp_array_get( $context, $path, 'unset prop' ) ) { |
|
466 |
_wp_array_set( $context, $path, true ); |
|
467 |
} |
|
468 |
} |
|
469 |
|
|
470 |
unset( $context['appearanceTools'] ); |
|
471 |
} |
|
472 |
|
|
473 |
/** |
18
|
474 |
* Sanitizes the input according to the schemas. |
|
475 |
* |
|
476 |
* @since 5.8.0 |
19
|
477 |
* @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. |
18
|
478 |
* |
19
|
479 |
* @param array $input Structure to sanitize. |
|
480 |
* @param array $valid_block_names List of valid block names. |
|
481 |
* @param array $valid_element_names List of valid element names. |
18
|
482 |
* @return array The sanitized output. |
|
483 |
*/ |
19
|
484 |
protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { |
18
|
485 |
$output = array(); |
|
486 |
|
|
487 |
if ( ! is_array( $input ) ) { |
|
488 |
return $output; |
|
489 |
} |
|
490 |
|
19
|
491 |
$output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); |
18
|
492 |
|
19
|
493 |
// Some styles are only meant to be available at the top-level (e.g.: blockGap), |
|
494 |
// hence, the schema for blocks & elements should not have them. |
|
495 |
$styles_non_top_level = static::VALID_STYLES; |
|
496 |
foreach ( array_keys( $styles_non_top_level ) as $section ) { |
|
497 |
foreach ( array_keys( $styles_non_top_level[ $section ] ) as $prop ) { |
|
498 |
if ( 'top' === $styles_non_top_level[ $section ][ $prop ] ) { |
|
499 |
unset( $styles_non_top_level[ $section ][ $prop ] ); |
|
500 |
} |
|
501 |
} |
|
502 |
} |
18
|
503 |
|
19
|
504 |
// Build the schema based on valid block & element names. |
18
|
505 |
$schema = array(); |
|
506 |
$schema_styles_elements = array(); |
19
|
507 |
foreach ( $valid_element_names as $element ) { |
|
508 |
$schema_styles_elements[ $element ] = $styles_non_top_level; |
18
|
509 |
} |
|
510 |
$schema_styles_blocks = array(); |
|
511 |
$schema_settings_blocks = array(); |
19
|
512 |
foreach ( $valid_block_names as $block ) { |
|
513 |
$schema_settings_blocks[ $block ] = static::VALID_SETTINGS; |
|
514 |
$schema_styles_blocks[ $block ] = $styles_non_top_level; |
18
|
515 |
$schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; |
|
516 |
} |
19
|
517 |
$schema['styles'] = static::VALID_STYLES; |
18
|
518 |
$schema['styles']['blocks'] = $schema_styles_blocks; |
|
519 |
$schema['styles']['elements'] = $schema_styles_elements; |
19
|
520 |
$schema['settings'] = static::VALID_SETTINGS; |
18
|
521 |
$schema['settings']['blocks'] = $schema_settings_blocks; |
|
522 |
|
|
523 |
// Remove anything that's not present in the schema. |
|
524 |
foreach ( array( 'styles', 'settings' ) as $subtree ) { |
|
525 |
if ( ! isset( $input[ $subtree ] ) ) { |
|
526 |
continue; |
|
527 |
} |
|
528 |
|
|
529 |
if ( ! is_array( $input[ $subtree ] ) ) { |
|
530 |
unset( $output[ $subtree ] ); |
|
531 |
continue; |
|
532 |
} |
|
533 |
|
19
|
534 |
$result = static::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] ); |
18
|
535 |
|
|
536 |
if ( empty( $result ) ) { |
|
537 |
unset( $output[ $subtree ] ); |
|
538 |
} else { |
|
539 |
$output[ $subtree ] = $result; |
|
540 |
} |
|
541 |
} |
|
542 |
|
|
543 |
return $output; |
|
544 |
} |
|
545 |
|
|
546 |
/** |
|
547 |
* Returns the metadata for each block. |
|
548 |
* |
|
549 |
* Example: |
|
550 |
* |
|
551 |
* { |
|
552 |
* 'core/paragraph': { |
|
553 |
* 'selector': 'p', |
|
554 |
* 'elements': { |
|
555 |
* 'link' => 'link selector', |
|
556 |
* 'etc' => 'element selector' |
|
557 |
* } |
|
558 |
* }, |
|
559 |
* 'core/heading': { |
|
560 |
* 'selector': 'h1', |
|
561 |
* 'elements': {} |
19
|
562 |
* }, |
|
563 |
* 'core/image': { |
|
564 |
* 'selector': '.wp-block-image', |
|
565 |
* 'duotone': 'img', |
18
|
566 |
* 'elements': {} |
|
567 |
* } |
|
568 |
* } |
|
569 |
* |
|
570 |
* @since 5.8.0 |
19
|
571 |
* @since 5.9.0 Added `duotone` key with CSS selector. |
18
|
572 |
* |
|
573 |
* @return array Block metadata. |
|
574 |
*/ |
19
|
575 |
protected static function get_blocks_metadata() { |
|
576 |
if ( null !== static::$blocks_metadata ) { |
|
577 |
return static::$blocks_metadata; |
18
|
578 |
} |
|
579 |
|
19
|
580 |
static::$blocks_metadata = array(); |
18
|
581 |
|
|
582 |
$registry = WP_Block_Type_Registry::get_instance(); |
|
583 |
$blocks = $registry->get_all_registered(); |
|
584 |
foreach ( $blocks as $block_name => $block_type ) { |
|
585 |
if ( |
|
586 |
isset( $block_type->supports['__experimentalSelector'] ) && |
|
587 |
is_string( $block_type->supports['__experimentalSelector'] ) |
|
588 |
) { |
19
|
589 |
static::$blocks_metadata[ $block_name ]['selector'] = $block_type->supports['__experimentalSelector']; |
18
|
590 |
} else { |
19
|
591 |
static::$blocks_metadata[ $block_name ]['selector'] = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ); |
18
|
592 |
} |
|
593 |
|
19
|
594 |
if ( |
|
595 |
isset( $block_type->supports['color']['__experimentalDuotone'] ) && |
|
596 |
is_string( $block_type->supports['color']['__experimentalDuotone'] ) |
|
597 |
) { |
|
598 |
static::$blocks_metadata[ $block_name ]['duotone'] = $block_type->supports['color']['__experimentalDuotone']; |
|
599 |
} |
|
600 |
|
|
601 |
// Assign defaults, then overwrite those that the block sets by itself. |
|
602 |
// If the block selector is compounded, will append the element to each |
|
603 |
// individual block selector. |
|
604 |
$block_selectors = explode( ',', static::$blocks_metadata[ $block_name ]['selector'] ); |
|
605 |
foreach ( static::ELEMENTS as $el_name => $el_selector ) { |
18
|
606 |
$element_selector = array(); |
|
607 |
foreach ( $block_selectors as $selector ) { |
|
608 |
$element_selector[] = $selector . ' ' . $el_selector; |
|
609 |
} |
19
|
610 |
static::$blocks_metadata[ $block_name ]['elements'][ $el_name ] = implode( ',', $element_selector ); |
18
|
611 |
} |
|
612 |
} |
|
613 |
|
19
|
614 |
return static::$blocks_metadata; |
18
|
615 |
} |
|
616 |
|
|
617 |
/** |
|
618 |
* Given a tree, removes the keys that are not present in the schema. |
|
619 |
* |
|
620 |
* It is recursive and modifies the input in-place. |
|
621 |
* |
|
622 |
* @since 5.8.0 |
|
623 |
* |
|
624 |
* @param array $tree Input to process. |
|
625 |
* @param array $schema Schema to adhere to. |
|
626 |
* @return array Returns the modified $tree. |
|
627 |
*/ |
19
|
628 |
protected static function remove_keys_not_in_schema( $tree, $schema ) { |
18
|
629 |
$tree = array_intersect_key( $tree, $schema ); |
|
630 |
|
|
631 |
foreach ( $schema as $key => $data ) { |
|
632 |
if ( ! isset( $tree[ $key ] ) ) { |
|
633 |
continue; |
|
634 |
} |
|
635 |
|
|
636 |
if ( is_array( $schema[ $key ] ) && is_array( $tree[ $key ] ) ) { |
19
|
637 |
$tree[ $key ] = static::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] ); |
18
|
638 |
|
|
639 |
if ( empty( $tree[ $key ] ) ) { |
|
640 |
unset( $tree[ $key ] ); |
|
641 |
} |
|
642 |
} elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) { |
|
643 |
unset( $tree[ $key ] ); |
|
644 |
} |
|
645 |
} |
|
646 |
|
|
647 |
return $tree; |
|
648 |
} |
|
649 |
|
|
650 |
/** |
|
651 |
* Returns the existing settings for each block. |
|
652 |
* |
|
653 |
* Example: |
|
654 |
* |
|
655 |
* { |
|
656 |
* 'root': { |
|
657 |
* 'color': { |
|
658 |
* 'custom': true |
|
659 |
* } |
|
660 |
* }, |
|
661 |
* 'core/paragraph': { |
|
662 |
* 'spacing': { |
|
663 |
* 'customPadding': true |
|
664 |
* } |
|
665 |
* } |
|
666 |
* } |
|
667 |
* |
|
668 |
* @since 5.8.0 |
|
669 |
* |
|
670 |
* @return array Settings per block. |
|
671 |
*/ |
|
672 |
public function get_settings() { |
|
673 |
if ( ! isset( $this->theme_json['settings'] ) ) { |
|
674 |
return array(); |
|
675 |
} else { |
|
676 |
return $this->theme_json['settings']; |
|
677 |
} |
|
678 |
} |
|
679 |
|
|
680 |
/** |
|
681 |
* Returns the stylesheet that results of processing |
|
682 |
* the theme.json structure this object represents. |
|
683 |
* |
|
684 |
* @since 5.8.0 |
19
|
685 |
* @since 5.9.0 Removed the `$type` parameter`, added the `$types` and `$origins` parameters. |
18
|
686 |
* |
19
|
687 |
* @param array $types Types of styles to load. Will load all by default. It accepts: |
|
688 |
* - `variables`: only the CSS Custom Properties for presets & custom ones. |
|
689 |
* - `styles`: only the styles section in theme.json. |
|
690 |
* - `presets`: only the classes for the presets. |
|
691 |
* @param array $origins A list of origins to include. By default it includes VALID_ORIGINS. |
18
|
692 |
* @return string Stylesheet. |
|
693 |
*/ |
19
|
694 |
public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null ) { |
|
695 |
if ( null === $origins ) { |
|
696 |
$origins = static::VALID_ORIGINS; |
|
697 |
} |
|
698 |
|
|
699 |
if ( is_string( $types ) ) { |
|
700 |
// Dispatch error and map old arguments to new ones. |
|
701 |
_deprecated_argument( __FUNCTION__, '5.9.0' ); |
|
702 |
if ( 'block_styles' === $types ) { |
|
703 |
$types = array( 'styles', 'presets' ); |
|
704 |
} elseif ( 'css_variables' === $types ) { |
|
705 |
$types = array( 'variables' ); |
|
706 |
} else { |
|
707 |
$types = array( 'variables', 'styles', 'presets' ); |
|
708 |
} |
|
709 |
} |
18
|
710 |
|
19
|
711 |
$blocks_metadata = static::get_blocks_metadata(); |
|
712 |
$style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata ); |
|
713 |
$setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); |
|
714 |
|
|
715 |
$stylesheet = ''; |
|
716 |
|
|
717 |
if ( in_array( 'variables', $types, true ) ) { |
|
718 |
$stylesheet .= $this->get_css_variables( $setting_nodes, $origins ); |
|
719 |
} |
|
720 |
|
|
721 |
if ( in_array( 'styles', $types, true ) ) { |
|
722 |
$stylesheet .= $this->get_block_classes( $style_nodes ); |
|
723 |
} |
|
724 |
|
|
725 |
if ( in_array( 'presets', $types, true ) ) { |
|
726 |
$stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); |
18
|
727 |
} |
|
728 |
|
19
|
729 |
return $stylesheet; |
|
730 |
} |
|
731 |
|
|
732 |
/** |
|
733 |
* Returns the page templates of the active theme. |
|
734 |
* |
|
735 |
* @since 5.9.0 |
|
736 |
* |
|
737 |
* @return array |
|
738 |
*/ |
|
739 |
public function get_custom_templates() { |
|
740 |
$custom_templates = array(); |
|
741 |
if ( ! isset( $this->theme_json['customTemplates'] ) || ! is_array( $this->theme_json['customTemplates'] ) ) { |
|
742 |
return $custom_templates; |
|
743 |
} |
|
744 |
|
|
745 |
foreach ( $this->theme_json['customTemplates'] as $item ) { |
|
746 |
if ( isset( $item['name'] ) ) { |
|
747 |
$custom_templates[ $item['name'] ] = array( |
|
748 |
'title' => isset( $item['title'] ) ? $item['title'] : '', |
|
749 |
'postTypes' => isset( $item['postTypes'] ) ? $item['postTypes'] : array( 'page' ), |
|
750 |
); |
|
751 |
} |
|
752 |
} |
|
753 |
return $custom_templates; |
|
754 |
} |
|
755 |
|
|
756 |
/** |
|
757 |
* Returns the template part data of active theme. |
|
758 |
* |
|
759 |
* @since 5.9.0 |
|
760 |
* |
|
761 |
* @return array |
|
762 |
*/ |
|
763 |
public function get_template_parts() { |
|
764 |
$template_parts = array(); |
|
765 |
if ( ! isset( $this->theme_json['templateParts'] ) || ! is_array( $this->theme_json['templateParts'] ) ) { |
|
766 |
return $template_parts; |
|
767 |
} |
|
768 |
|
|
769 |
foreach ( $this->theme_json['templateParts'] as $item ) { |
|
770 |
if ( isset( $item['name'] ) ) { |
|
771 |
$template_parts[ $item['name'] ] = array( |
|
772 |
'title' => isset( $item['title'] ) ? $item['title'] : '', |
|
773 |
'area' => isset( $item['area'] ) ? $item['area'] : '', |
|
774 |
); |
|
775 |
} |
|
776 |
} |
|
777 |
return $template_parts; |
18
|
778 |
} |
|
779 |
|
|
780 |
/** |
|
781 |
* Converts each style section into a list of rulesets |
|
782 |
* containing the block styles to be appended to the stylesheet. |
|
783 |
* |
|
784 |
* See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax |
|
785 |
* |
|
786 |
* For each section this creates a new ruleset such as: |
|
787 |
* |
|
788 |
* block-selector { |
|
789 |
* style-property-one: value; |
|
790 |
* } |
|
791 |
* |
19
|
792 |
* @since 5.8.0 As `get_block_styles()`. |
|
793 |
* @since 5.9.0 Renamed from `get_block_styles()` to `get_block_classes()` |
|
794 |
* and no longer returns preset classes. |
|
795 |
* Removed the `$setting_nodes` parameter. |
18
|
796 |
* |
19
|
797 |
* @param array $style_nodes Nodes with styles. |
18
|
798 |
* @return string The new stylesheet. |
|
799 |
*/ |
19
|
800 |
protected function get_block_classes( $style_nodes ) { |
18
|
801 |
$block_rules = ''; |
19
|
802 |
|
18
|
803 |
foreach ( $style_nodes as $metadata ) { |
|
804 |
if ( null === $metadata['selector'] ) { |
|
805 |
continue; |
|
806 |
} |
|
807 |
|
|
808 |
$node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); |
|
809 |
$selector = $metadata['selector']; |
19
|
810 |
$settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); |
|
811 |
$declarations = static::compute_style_properties( $node, $settings ); |
|
812 |
|
|
813 |
// 1. Separate the ones who use the general selector |
|
814 |
// and the ones who use the duotone selector. |
|
815 |
$declarations_duotone = array(); |
|
816 |
foreach ( $declarations as $index => $declaration ) { |
|
817 |
if ( 'filter' === $declaration['name'] ) { |
|
818 |
unset( $declarations[ $index ] ); |
|
819 |
$declarations_duotone[] = $declaration; |
|
820 |
} |
|
821 |
} |
|
822 |
|
|
823 |
/* |
|
824 |
* Reset default browser margin on the root body element. |
|
825 |
* This is set on the root selector **before** generating the ruleset |
|
826 |
* from the `theme.json`. This is to ensure that if the `theme.json` declares |
|
827 |
* `margin` in its `spacing` declaration for the `body` element then these |
|
828 |
* user-generated values take precedence in the CSS cascade. |
|
829 |
* @link https://github.com/WordPress/gutenberg/issues/36147. |
|
830 |
*/ |
|
831 |
if ( static::ROOT_BLOCK_SELECTOR === $selector ) { |
|
832 |
$block_rules .= 'body { margin: 0; }'; |
|
833 |
} |
|
834 |
|
|
835 |
// 2. Generate the rules that use the general selector. |
|
836 |
$block_rules .= static::to_ruleset( $selector, $declarations ); |
|
837 |
|
|
838 |
// 3. Generate the rules that use the duotone selector. |
|
839 |
if ( isset( $metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { |
|
840 |
$selector_duotone = static::scope_selector( $metadata['selector'], $metadata['duotone'] ); |
|
841 |
$block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); |
|
842 |
} |
|
843 |
|
|
844 |
if ( static::ROOT_BLOCK_SELECTOR === $selector ) { |
|
845 |
$block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; |
|
846 |
$block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; |
|
847 |
$block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; |
|
848 |
|
|
849 |
$has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; |
|
850 |
if ( $has_block_gap_support ) { |
|
851 |
$block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; |
|
852 |
$block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; |
|
853 |
} |
|
854 |
} |
18
|
855 |
} |
|
856 |
|
19
|
857 |
return $block_rules; |
|
858 |
} |
|
859 |
|
|
860 |
/** |
|
861 |
* Creates new rulesets as classes for each preset value such as: |
|
862 |
* |
|
863 |
* .has-value-color { |
|
864 |
* color: value; |
|
865 |
* } |
|
866 |
* |
|
867 |
* .has-value-background-color { |
|
868 |
* background-color: value; |
|
869 |
* } |
|
870 |
* |
|
871 |
* .has-value-font-size { |
|
872 |
* font-size: value; |
|
873 |
* } |
|
874 |
* |
|
875 |
* .has-value-gradient-background { |
|
876 |
* background: value; |
|
877 |
* } |
|
878 |
* |
|
879 |
* p.has-value-gradient-background { |
|
880 |
* background: value; |
|
881 |
* } |
|
882 |
* |
|
883 |
* @since 5.9.0 |
|
884 |
* |
|
885 |
* @param array $setting_nodes Nodes with settings. |
|
886 |
* @param array $origins List of origins to process presets from. |
|
887 |
* @return string The new stylesheet. |
|
888 |
*/ |
|
889 |
protected function get_preset_classes( $setting_nodes, $origins ) { |
18
|
890 |
$preset_rules = ''; |
19
|
891 |
|
18
|
892 |
foreach ( $setting_nodes as $metadata ) { |
|
893 |
if ( null === $metadata['selector'] ) { |
|
894 |
continue; |
|
895 |
} |
|
896 |
|
|
897 |
$selector = $metadata['selector']; |
|
898 |
$node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); |
19
|
899 |
$preset_rules .= static::compute_preset_classes( $node, $selector, $origins ); |
18
|
900 |
} |
|
901 |
|
19
|
902 |
return $preset_rules; |
18
|
903 |
} |
|
904 |
|
|
905 |
/** |
|
906 |
* Converts each styles section into a list of rulesets |
|
907 |
* to be appended to the stylesheet. |
|
908 |
* These rulesets contain all the css variables (custom variables and preset variables). |
|
909 |
* |
|
910 |
* See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax |
|
911 |
* |
|
912 |
* For each section this creates a new ruleset such as: |
|
913 |
* |
|
914 |
* block-selector { |
|
915 |
* --wp--preset--category--slug: value; |
|
916 |
* --wp--custom--variable: value; |
|
917 |
* } |
|
918 |
* |
|
919 |
* @since 5.8.0 |
19
|
920 |
* @since 5.9.0 Added the `$origins` parameter. |
18
|
921 |
* |
19
|
922 |
* @param array $nodes Nodes with settings. |
|
923 |
* @param array $origins List of origins to process. |
18
|
924 |
* @return string The new stylesheet. |
|
925 |
*/ |
19
|
926 |
protected function get_css_variables( $nodes, $origins ) { |
18
|
927 |
$stylesheet = ''; |
|
928 |
foreach ( $nodes as $metadata ) { |
|
929 |
if ( null === $metadata['selector'] ) { |
|
930 |
continue; |
|
931 |
} |
|
932 |
|
|
933 |
$selector = $metadata['selector']; |
|
934 |
|
|
935 |
$node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); |
19
|
936 |
$declarations = array_merge( static::compute_preset_vars( $node, $origins ), static::compute_theme_vars( $node ) ); |
18
|
937 |
|
19
|
938 |
$stylesheet .= static::to_ruleset( $selector, $declarations ); |
18
|
939 |
} |
|
940 |
|
|
941 |
return $stylesheet; |
|
942 |
} |
|
943 |
|
|
944 |
/** |
|
945 |
* Given a selector and a declaration list, |
|
946 |
* creates the corresponding ruleset. |
|
947 |
* |
|
948 |
* @since 5.8.0 |
|
949 |
* |
|
950 |
* @param string $selector CSS selector. |
|
951 |
* @param array $declarations List of declarations. |
|
952 |
* @return string CSS ruleset. |
|
953 |
*/ |
19
|
954 |
protected static function to_ruleset( $selector, $declarations ) { |
18
|
955 |
if ( empty( $declarations ) ) { |
|
956 |
return ''; |
|
957 |
} |
|
958 |
|
|
959 |
$declaration_block = array_reduce( |
|
960 |
$declarations, |
19
|
961 |
static function ( $carry, $element ) { |
18
|
962 |
return $carry .= $element['name'] . ': ' . $element['value'] . ';'; }, |
|
963 |
'' |
|
964 |
); |
|
965 |
|
|
966 |
return $selector . '{' . $declaration_block . '}'; |
|
967 |
} |
|
968 |
|
|
969 |
/** |
|
970 |
* Function that appends a sub-selector to a existing one. |
|
971 |
* |
|
972 |
* Given the compounded $selector "h1, h2, h3" |
|
973 |
* and the $to_append selector ".some-class" the result will be |
|
974 |
* "h1.some-class, h2.some-class, h3.some-class". |
|
975 |
* |
|
976 |
* @since 5.8.0 |
|
977 |
* |
|
978 |
* @param string $selector Original selector. |
|
979 |
* @param string $to_append Selector to append. |
|
980 |
* @return string |
|
981 |
*/ |
19
|
982 |
protected static function append_to_selector( $selector, $to_append ) { |
18
|
983 |
$new_selectors = array(); |
|
984 |
$selectors = explode( ',', $selector ); |
|
985 |
foreach ( $selectors as $sel ) { |
|
986 |
$new_selectors[] = $sel . $to_append; |
|
987 |
} |
|
988 |
|
|
989 |
return implode( ',', $new_selectors ); |
|
990 |
} |
|
991 |
|
|
992 |
/** |
|
993 |
* Given a settings array, it returns the generated rulesets |
|
994 |
* for the preset classes. |
|
995 |
* |
|
996 |
* @since 5.8.0 |
19
|
997 |
* @since 5.9.0 Added the `$origins` parameter. |
18
|
998 |
* |
|
999 |
* @param array $settings Settings to process. |
|
1000 |
* @param string $selector Selector wrapping the classes. |
19
|
1001 |
* @param array $origins List of origins to process. |
18
|
1002 |
* @return string The result of processing the presets. |
|
1003 |
*/ |
19
|
1004 |
protected static function compute_preset_classes( $settings, $selector, $origins ) { |
|
1005 |
if ( static::ROOT_BLOCK_SELECTOR === $selector ) { |
18
|
1006 |
// Classes at the global level do not need any CSS prefixed, |
|
1007 |
// and we don't want to increase its specificity. |
|
1008 |
$selector = ''; |
|
1009 |
} |
|
1010 |
|
|
1011 |
$stylesheet = ''; |
19
|
1012 |
foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
|
1013 |
$slugs = static::get_settings_slugs( $settings, $preset_metadata, $origins ); |
|
1014 |
foreach ( $preset_metadata['classes'] as $class => $property ) { |
|
1015 |
foreach ( $slugs as $slug ) { |
|
1016 |
$css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ); |
|
1017 |
$class_name = static::replace_slug_in_string( $class, $slug ); |
|
1018 |
$stylesheet .= static::to_ruleset( |
|
1019 |
static::append_to_selector( $selector, $class_name ), |
18
|
1020 |
array( |
|
1021 |
array( |
19
|
1022 |
'name' => $property, |
|
1023 |
'value' => 'var(' . $css_var . ') !important', |
18
|
1024 |
), |
|
1025 |
) |
|
1026 |
); |
|
1027 |
} |
|
1028 |
} |
|
1029 |
} |
|
1030 |
|
|
1031 |
return $stylesheet; |
|
1032 |
} |
|
1033 |
|
|
1034 |
/** |
19
|
1035 |
* Function that scopes a selector with another one. This works a bit like |
|
1036 |
* SCSS nesting except the `&` operator isn't supported. |
|
1037 |
* |
|
1038 |
* <code> |
|
1039 |
* $scope = '.a, .b .c'; |
|
1040 |
* $selector = '> .x, .y'; |
|
1041 |
* $merged = scope_selector( $scope, $selector ); |
|
1042 |
* // $merged is '.a > .x, .a .y, .b .c > .x, .b .c .y' |
|
1043 |
* </code> |
|
1044 |
* |
|
1045 |
* @since 5.9.0 |
|
1046 |
* |
|
1047 |
* @param string $scope Selector to scope to. |
|
1048 |
* @param string $selector Original selector. |
|
1049 |
* @return string Scoped selector. |
|
1050 |
*/ |
|
1051 |
protected static function scope_selector( $scope, $selector ) { |
|
1052 |
$scopes = explode( ',', $scope ); |
|
1053 |
$selectors = explode( ',', $selector ); |
|
1054 |
|
|
1055 |
$selectors_scoped = array(); |
|
1056 |
foreach ( $scopes as $outer ) { |
|
1057 |
foreach ( $selectors as $inner ) { |
|
1058 |
$selectors_scoped[] = trim( $outer ) . ' ' . trim( $inner ); |
|
1059 |
} |
|
1060 |
} |
|
1061 |
|
|
1062 |
return implode( ', ', $selectors_scoped ); |
|
1063 |
} |
|
1064 |
|
|
1065 |
/** |
|
1066 |
* Gets preset values keyed by slugs based on settings and metadata. |
|
1067 |
* |
|
1068 |
* <code> |
|
1069 |
* $settings = array( |
|
1070 |
* 'typography' => array( |
|
1071 |
* 'fontFamilies' => array( |
|
1072 |
* array( |
|
1073 |
* 'slug' => 'sansSerif', |
|
1074 |
* 'fontFamily' => '"Helvetica Neue", sans-serif', |
|
1075 |
* ), |
|
1076 |
* array( |
|
1077 |
* 'slug' => 'serif', |
|
1078 |
* 'colors' => 'Georgia, serif', |
|
1079 |
* ) |
|
1080 |
* ), |
|
1081 |
* ), |
|
1082 |
* ); |
|
1083 |
* $meta = array( |
|
1084 |
* 'path' => array( 'typography', 'fontFamilies' ), |
|
1085 |
* 'value_key' => 'fontFamily', |
|
1086 |
* ); |
|
1087 |
* $values_by_slug = get_settings_values_by_slug(); |
|
1088 |
* // $values_by_slug === array( |
|
1089 |
* // 'sans-serif' => '"Helvetica Neue", sans-serif', |
|
1090 |
* // 'serif' => 'Georgia, serif', |
|
1091 |
* // ); |
|
1092 |
* </code> |
|
1093 |
* |
|
1094 |
* @since 5.9.0 |
|
1095 |
* |
|
1096 |
* @param array $settings Settings to process. |
|
1097 |
* @param array $preset_metadata One of the PRESETS_METADATA values. |
|
1098 |
* @param array $origins List of origins to process. |
|
1099 |
* @return array Array of presets where each key is a slug and each value is the preset value. |
|
1100 |
*/ |
|
1101 |
protected static function get_settings_values_by_slug( $settings, $preset_metadata, $origins ) { |
|
1102 |
$preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() ); |
|
1103 |
|
|
1104 |
$result = array(); |
|
1105 |
foreach ( $origins as $origin ) { |
|
1106 |
if ( ! isset( $preset_per_origin[ $origin ] ) ) { |
|
1107 |
continue; |
|
1108 |
} |
|
1109 |
foreach ( $preset_per_origin[ $origin ] as $preset ) { |
|
1110 |
$slug = _wp_to_kebab_case( $preset['slug'] ); |
|
1111 |
|
|
1112 |
$value = ''; |
|
1113 |
if ( isset( $preset_metadata['value_key'], $preset[ $preset_metadata['value_key'] ] ) ) { |
|
1114 |
$value_key = $preset_metadata['value_key']; |
|
1115 |
$value = $preset[ $value_key ]; |
|
1116 |
} elseif ( |
|
1117 |
isset( $preset_metadata['value_func'] ) && |
|
1118 |
is_callable( $preset_metadata['value_func'] ) |
|
1119 |
) { |
|
1120 |
$value_func = $preset_metadata['value_func']; |
|
1121 |
$value = call_user_func( $value_func, $preset ); |
|
1122 |
} else { |
|
1123 |
// If we don't have a value, then don't add it to the result. |
|
1124 |
continue; |
|
1125 |
} |
|
1126 |
|
|
1127 |
$result[ $slug ] = $value; |
|
1128 |
} |
|
1129 |
} |
|
1130 |
return $result; |
|
1131 |
} |
|
1132 |
|
|
1133 |
/** |
|
1134 |
* Similar to get_settings_values_by_slug, but doesn't compute the value. |
|
1135 |
* |
|
1136 |
* @since 5.9.0 |
|
1137 |
* |
|
1138 |
* @param array $settings Settings to process. |
|
1139 |
* @param array $preset_metadata One of the PRESETS_METADATA values. |
|
1140 |
* @param array $origins List of origins to process. |
|
1141 |
* @return array Array of presets where the key and value are both the slug. |
|
1142 |
*/ |
|
1143 |
protected static function get_settings_slugs( $settings, $preset_metadata, $origins = null ) { |
|
1144 |
if ( null === $origins ) { |
|
1145 |
$origins = static::VALID_ORIGINS; |
|
1146 |
} |
|
1147 |
|
|
1148 |
$preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() ); |
|
1149 |
|
|
1150 |
$result = array(); |
|
1151 |
foreach ( $origins as $origin ) { |
|
1152 |
if ( ! isset( $preset_per_origin[ $origin ] ) ) { |
|
1153 |
continue; |
|
1154 |
} |
|
1155 |
foreach ( $preset_per_origin[ $origin ] as $preset ) { |
|
1156 |
$slug = _wp_to_kebab_case( $preset['slug'] ); |
|
1157 |
|
|
1158 |
// Use the array as a set so we don't get duplicates. |
|
1159 |
$result[ $slug ] = $slug; |
|
1160 |
} |
|
1161 |
} |
|
1162 |
return $result; |
|
1163 |
} |
|
1164 |
|
|
1165 |
/** |
|
1166 |
* Transform a slug into a CSS Custom Property. |
|
1167 |
* |
|
1168 |
* @since 5.9.0 |
|
1169 |
* |
|
1170 |
* @param string $input String to replace. |
|
1171 |
* @param string $slug The slug value to use to generate the custom property. |
|
1172 |
* @return string The CSS Custom Property. Something along the lines of `--wp--preset--color--black`. |
|
1173 |
*/ |
|
1174 |
protected static function replace_slug_in_string( $input, $slug ) { |
|
1175 |
return strtr( $input, array( '$slug' => $slug ) ); |
|
1176 |
} |
|
1177 |
|
|
1178 |
/** |
18
|
1179 |
* Given the block settings, it extracts the CSS Custom Properties |
|
1180 |
* for the presets and adds them to the $declarations array |
|
1181 |
* following the format: |
|
1182 |
* |
|
1183 |
* array( |
|
1184 |
* 'name' => 'property_name', |
|
1185 |
* 'value' => 'property_value, |
|
1186 |
* ) |
|
1187 |
* |
|
1188 |
* @since 5.8.0 |
19
|
1189 |
* @since 5.9.0 Added the `$origins` parameter. |
18
|
1190 |
* |
|
1191 |
* @param array $settings Settings to process. |
19
|
1192 |
* @param array $origins List of origins to process. |
18
|
1193 |
* @return array Returns the modified $declarations. |
|
1194 |
*/ |
19
|
1195 |
protected static function compute_preset_vars( $settings, $origins ) { |
18
|
1196 |
$declarations = array(); |
19
|
1197 |
foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
|
1198 |
$values_by_slug = static::get_settings_values_by_slug( $settings, $preset_metadata, $origins ); |
|
1199 |
foreach ( $values_by_slug as $slug => $value ) { |
18
|
1200 |
$declarations[] = array( |
19
|
1201 |
'name' => static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ), |
18
|
1202 |
'value' => $value, |
|
1203 |
); |
|
1204 |
} |
|
1205 |
} |
|
1206 |
|
|
1207 |
return $declarations; |
|
1208 |
} |
|
1209 |
|
|
1210 |
/** |
|
1211 |
* Given an array of settings, it extracts the CSS Custom Properties |
|
1212 |
* for the custom values and adds them to the $declarations |
|
1213 |
* array following the format: |
|
1214 |
* |
|
1215 |
* array( |
|
1216 |
* 'name' => 'property_name', |
|
1217 |
* 'value' => 'property_value, |
|
1218 |
* ) |
|
1219 |
* |
|
1220 |
* @since 5.8.0 |
|
1221 |
* |
|
1222 |
* @param array $settings Settings to process. |
|
1223 |
* @return array Returns the modified $declarations. |
|
1224 |
*/ |
19
|
1225 |
protected static function compute_theme_vars( $settings ) { |
18
|
1226 |
$declarations = array(); |
|
1227 |
$custom_values = _wp_array_get( $settings, array( 'custom' ), array() ); |
19
|
1228 |
$css_vars = static::flatten_tree( $custom_values ); |
18
|
1229 |
foreach ( $css_vars as $key => $value ) { |
|
1230 |
$declarations[] = array( |
|
1231 |
'name' => '--wp--custom--' . $key, |
|
1232 |
'value' => $value, |
|
1233 |
); |
|
1234 |
} |
|
1235 |
|
|
1236 |
return $declarations; |
|
1237 |
} |
|
1238 |
|
|
1239 |
/** |
|
1240 |
* Given a tree, it creates a flattened one |
|
1241 |
* by merging the keys and binding the leaf values |
|
1242 |
* to the new keys. |
|
1243 |
* |
|
1244 |
* It also transforms camelCase names into kebab-case |
|
1245 |
* and substitutes '/' by '-'. |
|
1246 |
* |
|
1247 |
* This is thought to be useful to generate |
|
1248 |
* CSS Custom Properties from a tree, |
|
1249 |
* although there's nothing in the implementation |
|
1250 |
* of this function that requires that format. |
|
1251 |
* |
|
1252 |
* For example, assuming the given prefix is '--wp' |
|
1253 |
* and the token is '--', for this input tree: |
|
1254 |
* |
|
1255 |
* { |
|
1256 |
* 'some/property': 'value', |
|
1257 |
* 'nestedProperty': { |
|
1258 |
* 'sub-property': 'value' |
|
1259 |
* } |
|
1260 |
* } |
|
1261 |
* |
|
1262 |
* it'll return this output: |
|
1263 |
* |
|
1264 |
* { |
|
1265 |
* '--wp--some-property': 'value', |
|
1266 |
* '--wp--nested-property--sub-property': 'value' |
|
1267 |
* } |
|
1268 |
* |
|
1269 |
* @since 5.8.0 |
|
1270 |
* |
|
1271 |
* @param array $tree Input tree to process. |
|
1272 |
* @param string $prefix Optional. Prefix to prepend to each variable. Default empty string. |
|
1273 |
* @param string $token Optional. Token to use between levels. Default '--'. |
|
1274 |
* @return array The flattened tree. |
|
1275 |
*/ |
19
|
1276 |
protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { |
18
|
1277 |
$result = array(); |
|
1278 |
foreach ( $tree as $property => $value ) { |
|
1279 |
$new_key = $prefix . str_replace( |
|
1280 |
'/', |
|
1281 |
'-', |
19
|
1282 |
strtolower( _wp_to_kebab_case( $property ) ) |
18
|
1283 |
); |
|
1284 |
|
|
1285 |
if ( is_array( $value ) ) { |
|
1286 |
$new_prefix = $new_key . $token; |
|
1287 |
$result = array_merge( |
|
1288 |
$result, |
19
|
1289 |
static::flatten_tree( $value, $new_prefix, $token ) |
18
|
1290 |
); |
|
1291 |
} else { |
|
1292 |
$result[ $new_key ] = $value; |
|
1293 |
} |
|
1294 |
} |
|
1295 |
return $result; |
|
1296 |
} |
|
1297 |
|
|
1298 |
/** |
|
1299 |
* Given a styles array, it extracts the style properties |
|
1300 |
* and adds them to the $declarations array following the format: |
|
1301 |
* |
|
1302 |
* array( |
|
1303 |
* 'name' => 'property_name', |
|
1304 |
* 'value' => 'property_value, |
|
1305 |
* ) |
|
1306 |
* |
|
1307 |
* @since 5.8.0 |
19
|
1308 |
* @since 5.9.0 Added the `$settings` and `$properties` parameters. |
18
|
1309 |
* |
19
|
1310 |
* @param array $styles Styles to process. |
|
1311 |
* @param array $settings Theme settings. |
|
1312 |
* @param array $properties Properties metadata. |
18
|
1313 |
* @return array Returns the modified $declarations. |
|
1314 |
*/ |
19
|
1315 |
protected static function compute_style_properties( $styles, $settings = array(), $properties = null ) { |
|
1316 |
if ( null === $properties ) { |
|
1317 |
$properties = static::PROPERTIES_METADATA; |
|
1318 |
} |
|
1319 |
|
18
|
1320 |
$declarations = array(); |
|
1321 |
if ( empty( $styles ) ) { |
|
1322 |
return $declarations; |
|
1323 |
} |
|
1324 |
|
19
|
1325 |
foreach ( $properties as $css_property => $value_path ) { |
|
1326 |
$value = static::get_property_value( $styles, $value_path ); |
|
1327 |
|
|
1328 |
// Look up protected properties, keyed by value path. |
|
1329 |
// Skip protected properties that are explicitly set to `null`. |
|
1330 |
if ( is_array( $value_path ) ) { |
|
1331 |
$path_string = implode( '.', $value_path ); |
|
1332 |
if ( |
|
1333 |
array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) && |
|
1334 |
_wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null |
|
1335 |
) { |
|
1336 |
continue; |
18
|
1337 |
} |
|
1338 |
} |
|
1339 |
|
19
|
1340 |
// Skip if empty and not "0" or value represents array of longhand values. |
|
1341 |
$has_missing_value = empty( $value ) && ! is_numeric( $value ); |
|
1342 |
if ( $has_missing_value || is_array( $value ) ) { |
18
|
1343 |
continue; |
|
1344 |
} |
|
1345 |
|
|
1346 |
$declarations[] = array( |
19
|
1347 |
'name' => $css_property, |
18
|
1348 |
'value' => $value, |
|
1349 |
); |
|
1350 |
} |
|
1351 |
|
|
1352 |
return $declarations; |
|
1353 |
} |
|
1354 |
|
|
1355 |
/** |
|
1356 |
* Returns the style property for the given path. |
|
1357 |
* |
|
1358 |
* It also converts CSS Custom Property stored as |
|
1359 |
* "var:preset|color|secondary" to the form |
|
1360 |
* "--wp--preset--color--secondary". |
|
1361 |
* |
|
1362 |
* @since 5.8.0 |
19
|
1363 |
* @since 5.9.0 Added support for values of array type, which are returned as is. |
18
|
1364 |
* |
|
1365 |
* @param array $styles Styles subtree. |
|
1366 |
* @param array $path Which property to process. |
19
|
1367 |
* @return string|array Style property value. |
18
|
1368 |
*/ |
19
|
1369 |
protected static function get_property_value( $styles, $path ) { |
18
|
1370 |
$value = _wp_array_get( $styles, $path, '' ); |
|
1371 |
|
19
|
1372 |
if ( '' === $value || is_array( $value ) ) { |
18
|
1373 |
return $value; |
|
1374 |
} |
|
1375 |
|
|
1376 |
$prefix = 'var:'; |
|
1377 |
$prefix_len = strlen( $prefix ); |
|
1378 |
$token_in = '|'; |
|
1379 |
$token_out = '--'; |
|
1380 |
if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { |
|
1381 |
$unwrapped_name = str_replace( |
|
1382 |
$token_in, |
|
1383 |
$token_out, |
|
1384 |
substr( $value, $prefix_len ) |
|
1385 |
); |
|
1386 |
$value = "var(--wp--$unwrapped_name)"; |
|
1387 |
} |
|
1388 |
|
|
1389 |
return $value; |
|
1390 |
} |
|
1391 |
|
|
1392 |
/** |
|
1393 |
* Builds metadata for the setting nodes, which returns in the form of: |
|
1394 |
* |
|
1395 |
* [ |
|
1396 |
* [ |
|
1397 |
* 'path' => ['path', 'to', 'some', 'node' ], |
|
1398 |
* 'selector' => 'CSS selector for some node' |
|
1399 |
* ], |
|
1400 |
* [ |
|
1401 |
* 'path' => [ 'path', 'to', 'other', 'node' ], |
|
1402 |
* 'selector' => 'CSS selector for other node' |
|
1403 |
* ], |
|
1404 |
* ] |
|
1405 |
* |
|
1406 |
* @since 5.8.0 |
|
1407 |
* |
|
1408 |
* @param array $theme_json The tree to extract setting nodes from. |
|
1409 |
* @param array $selectors List of selectors per block. |
|
1410 |
* @return array |
|
1411 |
*/ |
19
|
1412 |
protected static function get_setting_nodes( $theme_json, $selectors = array() ) { |
18
|
1413 |
$nodes = array(); |
|
1414 |
if ( ! isset( $theme_json['settings'] ) ) { |
|
1415 |
return $nodes; |
|
1416 |
} |
|
1417 |
|
|
1418 |
// Top-level. |
|
1419 |
$nodes[] = array( |
|
1420 |
'path' => array( 'settings' ), |
19
|
1421 |
'selector' => static::ROOT_BLOCK_SELECTOR, |
18
|
1422 |
); |
|
1423 |
|
|
1424 |
// Calculate paths for blocks. |
|
1425 |
if ( ! isset( $theme_json['settings']['blocks'] ) ) { |
|
1426 |
return $nodes; |
|
1427 |
} |
|
1428 |
|
|
1429 |
foreach ( $theme_json['settings']['blocks'] as $name => $node ) { |
|
1430 |
$selector = null; |
|
1431 |
if ( isset( $selectors[ $name ]['selector'] ) ) { |
|
1432 |
$selector = $selectors[ $name ]['selector']; |
|
1433 |
} |
|
1434 |
|
|
1435 |
$nodes[] = array( |
|
1436 |
'path' => array( 'settings', 'blocks', $name ), |
|
1437 |
'selector' => $selector, |
|
1438 |
); |
|
1439 |
} |
|
1440 |
|
|
1441 |
return $nodes; |
|
1442 |
} |
|
1443 |
|
|
1444 |
/** |
|
1445 |
* Builds metadata for the style nodes, which returns in the form of: |
|
1446 |
* |
|
1447 |
* [ |
|
1448 |
* [ |
|
1449 |
* 'path' => [ 'path', 'to', 'some', 'node' ], |
19
|
1450 |
* 'selector' => 'CSS selector for some node', |
|
1451 |
* 'duotone' => 'CSS selector for duotone for some node' |
18
|
1452 |
* ], |
|
1453 |
* [ |
|
1454 |
* 'path' => ['path', 'to', 'other', 'node' ], |
19
|
1455 |
* 'selector' => 'CSS selector for other node', |
|
1456 |
* 'duotone' => null |
18
|
1457 |
* ], |
|
1458 |
* ] |
|
1459 |
* |
|
1460 |
* @since 5.8.0 |
|
1461 |
* |
|
1462 |
* @param array $theme_json The tree to extract style nodes from. |
|
1463 |
* @param array $selectors List of selectors per block. |
|
1464 |
* @return array |
|
1465 |
*/ |
19
|
1466 |
protected static function get_style_nodes( $theme_json, $selectors = array() ) { |
18
|
1467 |
$nodes = array(); |
|
1468 |
if ( ! isset( $theme_json['styles'] ) ) { |
|
1469 |
return $nodes; |
|
1470 |
} |
|
1471 |
|
|
1472 |
// Top-level. |
|
1473 |
$nodes[] = array( |
|
1474 |
'path' => array( 'styles' ), |
19
|
1475 |
'selector' => static::ROOT_BLOCK_SELECTOR, |
18
|
1476 |
); |
|
1477 |
|
|
1478 |
if ( isset( $theme_json['styles']['elements'] ) ) { |
|
1479 |
foreach ( $theme_json['styles']['elements'] as $element => $node ) { |
|
1480 |
$nodes[] = array( |
|
1481 |
'path' => array( 'styles', 'elements', $element ), |
19
|
1482 |
'selector' => static::ELEMENTS[ $element ], |
18
|
1483 |
); |
|
1484 |
} |
|
1485 |
} |
|
1486 |
|
|
1487 |
// Blocks. |
|
1488 |
if ( ! isset( $theme_json['styles']['blocks'] ) ) { |
|
1489 |
return $nodes; |
|
1490 |
} |
|
1491 |
|
|
1492 |
foreach ( $theme_json['styles']['blocks'] as $name => $node ) { |
|
1493 |
$selector = null; |
|
1494 |
if ( isset( $selectors[ $name ]['selector'] ) ) { |
|
1495 |
$selector = $selectors[ $name ]['selector']; |
|
1496 |
} |
|
1497 |
|
19
|
1498 |
$duotone_selector = null; |
|
1499 |
if ( isset( $selectors[ $name ]['duotone'] ) ) { |
|
1500 |
$duotone_selector = $selectors[ $name ]['duotone']; |
|
1501 |
} |
|
1502 |
|
18
|
1503 |
$nodes[] = array( |
|
1504 |
'path' => array( 'styles', 'blocks', $name ), |
|
1505 |
'selector' => $selector, |
19
|
1506 |
'duotone' => $duotone_selector, |
18
|
1507 |
); |
|
1508 |
|
|
1509 |
if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { |
|
1510 |
foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { |
|
1511 |
$nodes[] = array( |
|
1512 |
'path' => array( 'styles', 'blocks', $name, 'elements', $element ), |
|
1513 |
'selector' => $selectors[ $name ]['elements'][ $element ], |
|
1514 |
); |
|
1515 |
} |
|
1516 |
} |
|
1517 |
} |
|
1518 |
|
|
1519 |
return $nodes; |
|
1520 |
} |
|
1521 |
|
|
1522 |
/** |
19
|
1523 |
* For metadata values that can either be booleans or paths to booleans, gets the value. |
|
1524 |
* |
|
1525 |
* ```php |
|
1526 |
* $data = array( |
|
1527 |
* 'color' => array( |
|
1528 |
* 'defaultPalette' => true |
|
1529 |
* ) |
|
1530 |
* ); |
|
1531 |
* |
|
1532 |
* static::get_metadata_boolean( $data, false ); |
|
1533 |
* // => false |
|
1534 |
* |
|
1535 |
* static::get_metadata_boolean( $data, array( 'color', 'defaultPalette' ) ); |
|
1536 |
* // => true |
|
1537 |
* ``` |
|
1538 |
* |
|
1539 |
* @since 6.0.0 |
|
1540 |
* |
|
1541 |
* @param array $data The data to inspect. |
|
1542 |
* @param bool|array $path Boolean or path to a boolean. |
|
1543 |
* @param bool $default Default value if the referenced path is missing. |
|
1544 |
* Default false. |
|
1545 |
* @return bool Value of boolean metadata. |
|
1546 |
*/ |
|
1547 |
protected static function get_metadata_boolean( $data, $path, $default = false ) { |
|
1548 |
if ( is_bool( $path ) ) { |
|
1549 |
return $path; |
|
1550 |
} |
|
1551 |
|
|
1552 |
if ( is_array( $path ) ) { |
|
1553 |
$value = _wp_array_get( $data, $path ); |
|
1554 |
if ( null !== $value ) { |
|
1555 |
return $value; |
|
1556 |
} |
|
1557 |
} |
|
1558 |
|
|
1559 |
return $default; |
|
1560 |
} |
|
1561 |
|
|
1562 |
/** |
18
|
1563 |
* Merge new incoming data. |
|
1564 |
* |
|
1565 |
* @since 5.8.0 |
19
|
1566 |
* @since 5.9.0 Duotone preset also has origins. |
18
|
1567 |
* |
|
1568 |
* @param WP_Theme_JSON $incoming Data to merge. |
|
1569 |
*/ |
|
1570 |
public function merge( $incoming ) { |
|
1571 |
$incoming_data = $incoming->get_raw_data(); |
|
1572 |
$this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); |
|
1573 |
|
|
1574 |
/* |
19
|
1575 |
* The array_replace_recursive algorithm merges at the leaf level, |
|
1576 |
* but we don't want leaf arrays to be merged, so we overwrite it. |
|
1577 |
* |
|
1578 |
* For leaf values that are sequential arrays it will use the numeric indexes for replacement. |
|
1579 |
* We rather replace the existing with the incoming value, if it exists. |
|
1580 |
* This is the case of spacing.units. |
|
1581 |
* |
|
1582 |
* For leaf values that are associative arrays it will merge them as expected. |
|
1583 |
* This is also not the behavior we want for the current associative arrays (presets). |
|
1584 |
* We rather replace the existing with the incoming value, if it exists. |
|
1585 |
* This happens, for example, when we merge data from theme.json upon existing |
|
1586 |
* theme supports or when we merge anything coming from the same source twice. |
|
1587 |
* This is the case of color.palette, color.gradients, color.duotone, |
|
1588 |
* typography.fontSizes, or typography.fontFamilies. |
|
1589 |
* |
|
1590 |
* Additionally, for some preset types, we also want to make sure the |
|
1591 |
* values they introduce don't conflict with default values. We do so |
|
1592 |
* by checking the incoming slugs for theme presets and compare them |
|
1593 |
* with the equivalent default presets: if a slug is present as a default |
|
1594 |
* we remove it from the theme presets. |
18
|
1595 |
*/ |
19
|
1596 |
$nodes = static::get_setting_nodes( $incoming_data ); |
|
1597 |
$slugs_global = static::get_default_slugs( $this->theme_json, array( 'settings' ) ); |
|
1598 |
foreach ( $nodes as $node ) { |
|
1599 |
$slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); |
|
1600 |
$slugs = array_merge_recursive( $slugs_global, $slugs_node ); |
|
1601 |
|
|
1602 |
// Replace the spacing.units. |
|
1603 |
$path = array_merge( $node['path'], array( 'spacing', 'units' ) ); |
|
1604 |
$content = _wp_array_get( $incoming_data, $path, null ); |
|
1605 |
if ( isset( $content ) ) { |
|
1606 |
_wp_array_set( $this->theme_json, $path, $content ); |
|
1607 |
} |
|
1608 |
|
|
1609 |
// Replace the presets. |
|
1610 |
foreach ( static::PRESETS_METADATA as $preset ) { |
|
1611 |
$override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true ); |
|
1612 |
|
|
1613 |
foreach ( static::VALID_ORIGINS as $origin ) { |
|
1614 |
$base_path = array_merge( $node['path'], $preset['path'] ); |
|
1615 |
$path = array_merge( $base_path, array( $origin ) ); |
|
1616 |
$content = _wp_array_get( $incoming_data, $path, null ); |
|
1617 |
if ( ! isset( $content ) ) { |
|
1618 |
continue; |
|
1619 |
} |
|
1620 |
|
|
1621 |
if ( 'theme' === $origin && $preset['use_default_names'] ) { |
|
1622 |
foreach ( $content as &$item ) { |
|
1623 |
if ( ! array_key_exists( 'name', $item ) ) { |
|
1624 |
$name = static::get_name_from_defaults( $item['slug'], $base_path ); |
|
1625 |
if ( null !== $name ) { |
|
1626 |
$item['name'] = $name; |
|
1627 |
} |
|
1628 |
} |
|
1629 |
} |
|
1630 |
} |
|
1631 |
|
|
1632 |
if ( |
|
1633 |
( 'theme' !== $origin ) || |
|
1634 |
( 'theme' === $origin && $override_preset ) |
|
1635 |
) { |
|
1636 |
_wp_array_set( $this->theme_json, $path, $content ); |
|
1637 |
} else { |
|
1638 |
$slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); |
|
1639 |
$content = static::filter_slugs( $content, $slugs_for_preset ); |
|
1640 |
_wp_array_set( $this->theme_json, $path, $content ); |
|
1641 |
} |
|
1642 |
} |
|
1643 |
} |
18
|
1644 |
} |
19
|
1645 |
} |
18
|
1646 |
|
19
|
1647 |
/** |
|
1648 |
* Converts all filter (duotone) presets into SVGs. |
|
1649 |
* |
|
1650 |
* @since 5.9.1 |
|
1651 |
* |
|
1652 |
* @param array $origins List of origins to process. |
|
1653 |
* @return string SVG filters. |
|
1654 |
*/ |
|
1655 |
public function get_svg_filters( $origins ) { |
|
1656 |
$blocks_metadata = static::get_blocks_metadata(); |
|
1657 |
$setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); |
|
1658 |
|
|
1659 |
$filters = ''; |
|
1660 |
foreach ( $setting_nodes as $metadata ) { |
|
1661 |
$node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); |
|
1662 |
if ( empty( $node['color']['duotone'] ) ) { |
|
1663 |
continue; |
|
1664 |
} |
|
1665 |
|
|
1666 |
$duotone_presets = $node['color']['duotone']; |
|
1667 |
|
|
1668 |
foreach ( $origins as $origin ) { |
|
1669 |
if ( ! isset( $duotone_presets[ $origin ] ) ) { |
|
1670 |
continue; |
|
1671 |
} |
|
1672 |
foreach ( $duotone_presets[ $origin ] as $duotone_preset ) { |
|
1673 |
$filters .= wp_get_duotone_filter_svg( $duotone_preset ); |
18
|
1674 |
} |
|
1675 |
} |
|
1676 |
} |
19
|
1677 |
|
|
1678 |
return $filters; |
|
1679 |
} |
|
1680 |
|
|
1681 |
/** |
|
1682 |
* Returns whether a presets should be overridden or not. |
|
1683 |
* |
|
1684 |
* @since 5.9.0 |
|
1685 |
* @deprecated 6.0.0 Use {@see 'get_metadata_boolean'} instead. |
|
1686 |
* |
|
1687 |
* @param array $theme_json The theme.json like structure to inspect. |
|
1688 |
* @param array $path Path to inspect. |
|
1689 |
* @param bool|array $override Data to compute whether to override the preset. |
|
1690 |
* @return boolean |
|
1691 |
*/ |
|
1692 |
protected static function should_override_preset( $theme_json, $path, $override ) { |
|
1693 |
_deprecated_function( __METHOD__, '6.0.0', 'get_metadata_boolean' ); |
|
1694 |
|
|
1695 |
if ( is_bool( $override ) ) { |
|
1696 |
return $override; |
|
1697 |
} |
|
1698 |
|
|
1699 |
/* |
|
1700 |
* The relationship between whether to override the defaults |
|
1701 |
* and whether the defaults are enabled is inverse: |
|
1702 |
* |
|
1703 |
* - If defaults are enabled => theme presets should not be overridden |
|
1704 |
* - If defaults are disabled => theme presets should be overridden |
|
1705 |
* |
|
1706 |
* For example, a theme sets defaultPalette to false, |
|
1707 |
* making the default palette hidden from the user. |
|
1708 |
* In that case, we want all the theme presets to be present, |
|
1709 |
* so they should override the defaults. |
|
1710 |
*/ |
|
1711 |
if ( is_array( $override ) ) { |
|
1712 |
$value = _wp_array_get( $theme_json, array_merge( $path, $override ) ); |
|
1713 |
if ( isset( $value ) ) { |
|
1714 |
return ! $value; |
|
1715 |
} |
|
1716 |
|
|
1717 |
// Search the top-level key if none was found for this node. |
|
1718 |
$value = _wp_array_get( $theme_json, array_merge( array( 'settings' ), $override ) ); |
|
1719 |
if ( isset( $value ) ) { |
|
1720 |
return ! $value; |
|
1721 |
} |
|
1722 |
|
|
1723 |
return true; |
|
1724 |
} |
|
1725 |
} |
|
1726 |
|
|
1727 |
/** |
|
1728 |
* Returns the default slugs for all the presets in an associative array |
|
1729 |
* whose keys are the preset paths and the leafs is the list of slugs. |
|
1730 |
* |
|
1731 |
* For example: |
|
1732 |
* |
|
1733 |
* array( |
|
1734 |
* 'color' => array( |
|
1735 |
* 'palette' => array( 'slug-1', 'slug-2' ), |
|
1736 |
* 'gradients' => array( 'slug-3', 'slug-4' ), |
|
1737 |
* ), |
|
1738 |
* ) |
|
1739 |
* |
|
1740 |
* @since 5.9.0 |
|
1741 |
* |
|
1742 |
* @param array $data A theme.json like structure. |
|
1743 |
* @param array $node_path The path to inspect. It's 'settings' by default. |
|
1744 |
* @return array |
|
1745 |
*/ |
|
1746 |
protected static function get_default_slugs( $data, $node_path ) { |
|
1747 |
$slugs = array(); |
|
1748 |
|
|
1749 |
foreach ( static::PRESETS_METADATA as $metadata ) { |
|
1750 |
$path = array_merge( $node_path, $metadata['path'], array( 'default' ) ); |
|
1751 |
$preset = _wp_array_get( $data, $path, null ); |
|
1752 |
if ( ! isset( $preset ) ) { |
|
1753 |
continue; |
|
1754 |
} |
|
1755 |
|
|
1756 |
$slugs_for_preset = array(); |
|
1757 |
$slugs_for_preset = array_map( |
|
1758 |
static function( $value ) { |
|
1759 |
return isset( $value['slug'] ) ? $value['slug'] : null; |
|
1760 |
}, |
|
1761 |
$preset |
|
1762 |
); |
|
1763 |
_wp_array_set( $slugs, $metadata['path'], $slugs_for_preset ); |
|
1764 |
} |
|
1765 |
|
|
1766 |
return $slugs; |
|
1767 |
} |
|
1768 |
|
|
1769 |
/** |
|
1770 |
* Get a `default`'s preset name by a provided slug. |
|
1771 |
* |
|
1772 |
* @since 5.9.0 |
|
1773 |
* |
|
1774 |
* @param string $slug The slug we want to find a match from default presets. |
|
1775 |
* @param array $base_path The path to inspect. It's 'settings' by default. |
|
1776 |
* @return string|null |
|
1777 |
*/ |
|
1778 |
protected function get_name_from_defaults( $slug, $base_path ) { |
|
1779 |
$path = array_merge( $base_path, array( 'default' ) ); |
|
1780 |
$default_content = _wp_array_get( $this->theme_json, $path, null ); |
|
1781 |
if ( ! $default_content ) { |
|
1782 |
return null; |
|
1783 |
} |
|
1784 |
foreach ( $default_content as $item ) { |
|
1785 |
if ( $slug === $item['slug'] ) { |
|
1786 |
return $item['name']; |
|
1787 |
} |
|
1788 |
} |
|
1789 |
return null; |
|
1790 |
} |
|
1791 |
|
|
1792 |
/** |
|
1793 |
* Removes the preset values whose slug is equal to any of given slugs. |
|
1794 |
* |
|
1795 |
* @since 5.9.0 |
|
1796 |
* |
|
1797 |
* @param array $node The node with the presets to validate. |
|
1798 |
* @param array $slugs The slugs that should not be overridden. |
|
1799 |
* @return array The new node. |
|
1800 |
*/ |
|
1801 |
protected static function filter_slugs( $node, $slugs ) { |
|
1802 |
if ( empty( $slugs ) ) { |
|
1803 |
return $node; |
|
1804 |
} |
|
1805 |
|
|
1806 |
$new_node = array(); |
|
1807 |
foreach ( $node as $value ) { |
|
1808 |
if ( isset( $value['slug'] ) && ! in_array( $value['slug'], $slugs, true ) ) { |
|
1809 |
$new_node[] = $value; |
|
1810 |
} |
|
1811 |
} |
|
1812 |
|
|
1813 |
return $new_node; |
|
1814 |
} |
|
1815 |
|
|
1816 |
/** |
|
1817 |
* Removes insecure data from theme.json. |
|
1818 |
* |
|
1819 |
* @since 5.9.0 |
|
1820 |
* |
|
1821 |
* @param array $theme_json Structure to sanitize. |
|
1822 |
* @return array Sanitized structure. |
|
1823 |
*/ |
|
1824 |
public static function remove_insecure_properties( $theme_json ) { |
|
1825 |
$sanitized = array(); |
|
1826 |
|
|
1827 |
$theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); |
|
1828 |
|
|
1829 |
$valid_block_names = array_keys( static::get_blocks_metadata() ); |
|
1830 |
$valid_element_names = array_keys( static::ELEMENTS ); |
|
1831 |
$theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); |
|
1832 |
|
|
1833 |
$blocks_metadata = static::get_blocks_metadata(); |
|
1834 |
$style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata ); |
|
1835 |
foreach ( $style_nodes as $metadata ) { |
|
1836 |
$input = _wp_array_get( $theme_json, $metadata['path'], array() ); |
|
1837 |
if ( empty( $input ) ) { |
|
1838 |
continue; |
|
1839 |
} |
|
1840 |
|
|
1841 |
$output = static::remove_insecure_styles( $input ); |
|
1842 |
if ( ! empty( $output ) ) { |
|
1843 |
_wp_array_set( $sanitized, $metadata['path'], $output ); |
|
1844 |
} |
|
1845 |
} |
|
1846 |
|
|
1847 |
$setting_nodes = static::get_setting_nodes( $theme_json ); |
|
1848 |
foreach ( $setting_nodes as $metadata ) { |
|
1849 |
$input = _wp_array_get( $theme_json, $metadata['path'], array() ); |
|
1850 |
if ( empty( $input ) ) { |
|
1851 |
continue; |
|
1852 |
} |
|
1853 |
|
|
1854 |
$output = static::remove_insecure_settings( $input ); |
|
1855 |
if ( ! empty( $output ) ) { |
|
1856 |
_wp_array_set( $sanitized, $metadata['path'], $output ); |
|
1857 |
} |
|
1858 |
} |
|
1859 |
|
|
1860 |
if ( empty( $sanitized['styles'] ) ) { |
|
1861 |
unset( $theme_json['styles'] ); |
|
1862 |
} else { |
|
1863 |
$theme_json['styles'] = $sanitized['styles']; |
|
1864 |
} |
|
1865 |
|
|
1866 |
if ( empty( $sanitized['settings'] ) ) { |
|
1867 |
unset( $theme_json['settings'] ); |
|
1868 |
} else { |
|
1869 |
$theme_json['settings'] = $sanitized['settings']; |
|
1870 |
} |
|
1871 |
|
|
1872 |
return $theme_json; |
|
1873 |
} |
|
1874 |
|
|
1875 |
/** |
|
1876 |
* Processes a setting node and returns the same node |
|
1877 |
* without the insecure settings. |
|
1878 |
* |
|
1879 |
* @since 5.9.0 |
|
1880 |
* |
|
1881 |
* @param array $input Node to process. |
|
1882 |
* @return array |
|
1883 |
*/ |
|
1884 |
protected static function remove_insecure_settings( $input ) { |
|
1885 |
$output = array(); |
|
1886 |
foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
|
1887 |
foreach ( static::VALID_ORIGINS as $origin ) { |
|
1888 |
$path_with_origin = array_merge( $preset_metadata['path'], array( $origin ) ); |
|
1889 |
$presets = _wp_array_get( $input, $path_with_origin, null ); |
|
1890 |
if ( null === $presets ) { |
|
1891 |
continue; |
|
1892 |
} |
|
1893 |
|
|
1894 |
$escaped_preset = array(); |
|
1895 |
foreach ( $presets as $preset ) { |
|
1896 |
if ( |
|
1897 |
esc_attr( esc_html( $preset['name'] ) ) === $preset['name'] && |
|
1898 |
sanitize_html_class( $preset['slug'] ) === $preset['slug'] |
|
1899 |
) { |
|
1900 |
$value = null; |
|
1901 |
if ( isset( $preset_metadata['value_key'], $preset[ $preset_metadata['value_key'] ] ) ) { |
|
1902 |
$value = $preset[ $preset_metadata['value_key'] ]; |
|
1903 |
} elseif ( |
|
1904 |
isset( $preset_metadata['value_func'] ) && |
|
1905 |
is_callable( $preset_metadata['value_func'] ) |
|
1906 |
) { |
|
1907 |
$value = call_user_func( $preset_metadata['value_func'], $preset ); |
|
1908 |
} |
|
1909 |
|
|
1910 |
$preset_is_valid = true; |
|
1911 |
foreach ( $preset_metadata['properties'] as $property ) { |
|
1912 |
if ( ! static::is_safe_css_declaration( $property, $value ) ) { |
|
1913 |
$preset_is_valid = false; |
|
1914 |
break; |
|
1915 |
} |
|
1916 |
} |
|
1917 |
|
|
1918 |
if ( $preset_is_valid ) { |
|
1919 |
$escaped_preset[] = $preset; |
|
1920 |
} |
|
1921 |
} |
|
1922 |
} |
|
1923 |
|
|
1924 |
if ( ! empty( $escaped_preset ) ) { |
|
1925 |
_wp_array_set( $output, $path_with_origin, $escaped_preset ); |
|
1926 |
} |
|
1927 |
} |
|
1928 |
} |
|
1929 |
return $output; |
|
1930 |
} |
|
1931 |
|
|
1932 |
/** |
|
1933 |
* Processes a style node and returns the same node |
|
1934 |
* without the insecure styles. |
|
1935 |
* |
|
1936 |
* @since 5.9.0 |
|
1937 |
* |
|
1938 |
* @param array $input Node to process. |
|
1939 |
* @return array |
|
1940 |
*/ |
|
1941 |
protected static function remove_insecure_styles( $input ) { |
|
1942 |
$output = array(); |
|
1943 |
$declarations = static::compute_style_properties( $input ); |
|
1944 |
|
|
1945 |
foreach ( $declarations as $declaration ) { |
|
1946 |
if ( static::is_safe_css_declaration( $declaration['name'], $declaration['value'] ) ) { |
|
1947 |
$path = static::PROPERTIES_METADATA[ $declaration['name'] ]; |
|
1948 |
|
|
1949 |
// Check the value isn't an array before adding so as to not |
|
1950 |
// double up shorthand and longhand styles. |
|
1951 |
$value = _wp_array_get( $input, $path, array() ); |
|
1952 |
if ( ! is_array( $value ) ) { |
|
1953 |
_wp_array_set( $output, $path, $value ); |
|
1954 |
} |
|
1955 |
} |
|
1956 |
} |
|
1957 |
return $output; |
|
1958 |
} |
|
1959 |
|
|
1960 |
/** |
|
1961 |
* Checks that a declaration provided by the user is safe. |
|
1962 |
* |
|
1963 |
* @since 5.9.0 |
|
1964 |
* |
|
1965 |
* @param string $property_name Property name in a CSS declaration, i.e. the `color` in `color: red`. |
|
1966 |
* @param string $property_value Value in a CSS declaration, i.e. the `red` in `color: red`. |
|
1967 |
* @return bool |
|
1968 |
*/ |
|
1969 |
protected static function is_safe_css_declaration( $property_name, $property_value ) { |
|
1970 |
$style_to_validate = $property_name . ': ' . $property_value; |
|
1971 |
$filtered = esc_html( safecss_filter_attr( $style_to_validate ) ); |
|
1972 |
return ! empty( trim( $filtered ) ); |
18
|
1973 |
} |
|
1974 |
|
|
1975 |
/** |
|
1976 |
* Returns the raw data. |
|
1977 |
* |
|
1978 |
* @since 5.8.0 |
|
1979 |
* |
|
1980 |
* @return array Raw data. |
|
1981 |
*/ |
|
1982 |
public function get_raw_data() { |
|
1983 |
return $this->theme_json; |
|
1984 |
} |
|
1985 |
|
|
1986 |
/** |
|
1987 |
* Transforms the given editor settings according the |
|
1988 |
* add_theme_support format to the theme.json format. |
|
1989 |
* |
|
1990 |
* @since 5.8.0 |
|
1991 |
* |
|
1992 |
* @param array $settings Existing editor settings. |
|
1993 |
* @return array Config that adheres to the theme.json schema. |
|
1994 |
*/ |
|
1995 |
public static function get_from_editor_settings( $settings ) { |
|
1996 |
$theme_settings = array( |
19
|
1997 |
'version' => static::LATEST_SCHEMA, |
18
|
1998 |
'settings' => array(), |
|
1999 |
); |
|
2000 |
|
|
2001 |
// Deprecated theme supports. |
|
2002 |
if ( isset( $settings['disableCustomColors'] ) ) { |
|
2003 |
if ( ! isset( $theme_settings['settings']['color'] ) ) { |
|
2004 |
$theme_settings['settings']['color'] = array(); |
|
2005 |
} |
|
2006 |
$theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors']; |
|
2007 |
} |
|
2008 |
|
|
2009 |
if ( isset( $settings['disableCustomGradients'] ) ) { |
|
2010 |
if ( ! isset( $theme_settings['settings']['color'] ) ) { |
|
2011 |
$theme_settings['settings']['color'] = array(); |
|
2012 |
} |
|
2013 |
$theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; |
|
2014 |
} |
|
2015 |
|
|
2016 |
if ( isset( $settings['disableCustomFontSizes'] ) ) { |
|
2017 |
if ( ! isset( $theme_settings['settings']['typography'] ) ) { |
|
2018 |
$theme_settings['settings']['typography'] = array(); |
|
2019 |
} |
|
2020 |
$theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; |
|
2021 |
} |
|
2022 |
|
|
2023 |
if ( isset( $settings['enableCustomLineHeight'] ) ) { |
|
2024 |
if ( ! isset( $theme_settings['settings']['typography'] ) ) { |
|
2025 |
$theme_settings['settings']['typography'] = array(); |
|
2026 |
} |
19
|
2027 |
$theme_settings['settings']['typography']['lineHeight'] = $settings['enableCustomLineHeight']; |
18
|
2028 |
} |
|
2029 |
|
|
2030 |
if ( isset( $settings['enableCustomUnits'] ) ) { |
|
2031 |
if ( ! isset( $theme_settings['settings']['spacing'] ) ) { |
|
2032 |
$theme_settings['settings']['spacing'] = array(); |
|
2033 |
} |
|
2034 |
$theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? |
|
2035 |
array( 'px', 'em', 'rem', 'vh', 'vw', '%' ) : |
|
2036 |
$settings['enableCustomUnits']; |
|
2037 |
} |
|
2038 |
|
|
2039 |
if ( isset( $settings['colors'] ) ) { |
|
2040 |
if ( ! isset( $theme_settings['settings']['color'] ) ) { |
|
2041 |
$theme_settings['settings']['color'] = array(); |
|
2042 |
} |
|
2043 |
$theme_settings['settings']['color']['palette'] = $settings['colors']; |
|
2044 |
} |
|
2045 |
|
|
2046 |
if ( isset( $settings['gradients'] ) ) { |
|
2047 |
if ( ! isset( $theme_settings['settings']['color'] ) ) { |
|
2048 |
$theme_settings['settings']['color'] = array(); |
|
2049 |
} |
|
2050 |
$theme_settings['settings']['color']['gradients'] = $settings['gradients']; |
|
2051 |
} |
|
2052 |
|
|
2053 |
if ( isset( $settings['fontSizes'] ) ) { |
|
2054 |
$font_sizes = $settings['fontSizes']; |
|
2055 |
// Back-compatibility for presets without units. |
|
2056 |
foreach ( $font_sizes as $key => $font_size ) { |
|
2057 |
if ( is_numeric( $font_size['size'] ) ) { |
|
2058 |
$font_sizes[ $key ]['size'] = $font_size['size'] . 'px'; |
|
2059 |
} |
|
2060 |
} |
|
2061 |
if ( ! isset( $theme_settings['settings']['typography'] ) ) { |
|
2062 |
$theme_settings['settings']['typography'] = array(); |
|
2063 |
} |
|
2064 |
$theme_settings['settings']['typography']['fontSizes'] = $font_sizes; |
|
2065 |
} |
|
2066 |
|
|
2067 |
if ( isset( $settings['enableCustomSpacing'] ) ) { |
|
2068 |
if ( ! isset( $theme_settings['settings']['spacing'] ) ) { |
|
2069 |
$theme_settings['settings']['spacing'] = array(); |
|
2070 |
} |
19
|
2071 |
$theme_settings['settings']['spacing']['padding'] = $settings['enableCustomSpacing']; |
18
|
2072 |
} |
|
2073 |
|
|
2074 |
return $theme_settings; |
|
2075 |
} |
|
2076 |
|
19
|
2077 |
/** |
|
2078 |
* Returns the current theme's wanted patterns(slugs) to be |
|
2079 |
* registered from Pattern Directory. |
|
2080 |
* |
|
2081 |
* @since 6.0.0 |
|
2082 |
* |
|
2083 |
* @return string[] |
|
2084 |
*/ |
|
2085 |
public function get_patterns() { |
|
2086 |
if ( isset( $this->theme_json['patterns'] ) && is_array( $this->theme_json['patterns'] ) ) { |
|
2087 |
return $this->theme_json['patterns']; |
|
2088 |
} |
|
2089 |
return array(); |
|
2090 |
} |
|
2091 |
|
|
2092 |
/** |
|
2093 |
* Returns a valid theme.json as provided by a theme. |
|
2094 |
* |
|
2095 |
* Unlike get_raw_data() this returns the presets flattened, as provided by a theme. |
|
2096 |
* This also uses appearanceTools instead of their opt-ins if all of them are true. |
|
2097 |
* |
|
2098 |
* @since 6.0.0 |
|
2099 |
* |
|
2100 |
* @return array |
|
2101 |
*/ |
|
2102 |
public function get_data() { |
|
2103 |
$output = $this->theme_json; |
|
2104 |
$nodes = static::get_setting_nodes( $output ); |
|
2105 |
|
|
2106 |
/** |
|
2107 |
* Flatten the theme & custom origins into a single one. |
|
2108 |
* |
|
2109 |
* For example, the following: |
|
2110 |
* |
|
2111 |
* { |
|
2112 |
* "settings": { |
|
2113 |
* "color": { |
|
2114 |
* "palette": { |
|
2115 |
* "theme": [ {} ], |
|
2116 |
* "custom": [ {} ] |
|
2117 |
* } |
|
2118 |
* } |
|
2119 |
* } |
|
2120 |
* } |
|
2121 |
* |
|
2122 |
* will be converted to: |
|
2123 |
* |
|
2124 |
* { |
|
2125 |
* "settings": { |
|
2126 |
* "color": { |
|
2127 |
* "palette": [ {} ] |
|
2128 |
* } |
|
2129 |
* } |
|
2130 |
* } |
|
2131 |
*/ |
|
2132 |
foreach ( $nodes as $node ) { |
|
2133 |
foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
|
2134 |
$path = array_merge( $node['path'], $preset_metadata['path'] ); |
|
2135 |
$preset = _wp_array_get( $output, $path, null ); |
|
2136 |
if ( null === $preset ) { |
|
2137 |
continue; |
|
2138 |
} |
|
2139 |
|
|
2140 |
$items = array(); |
|
2141 |
if ( isset( $preset['theme'] ) ) { |
|
2142 |
foreach ( $preset['theme'] as $item ) { |
|
2143 |
$slug = $item['slug']; |
|
2144 |
unset( $item['slug'] ); |
|
2145 |
$items[ $slug ] = $item; |
|
2146 |
} |
|
2147 |
} |
|
2148 |
if ( isset( $preset['custom'] ) ) { |
|
2149 |
foreach ( $preset['custom'] as $item ) { |
|
2150 |
$slug = $item['slug']; |
|
2151 |
unset( $item['slug'] ); |
|
2152 |
$items[ $slug ] = $item; |
|
2153 |
} |
|
2154 |
} |
|
2155 |
$flattened_preset = array(); |
|
2156 |
foreach ( $items as $slug => $value ) { |
|
2157 |
$flattened_preset[] = array_merge( array( 'slug' => $slug ), $value ); |
|
2158 |
} |
|
2159 |
_wp_array_set( $output, $path, $flattened_preset ); |
|
2160 |
} |
|
2161 |
} |
|
2162 |
|
|
2163 |
// If all of the static::APPEARANCE_TOOLS_OPT_INS are true, |
|
2164 |
// this code unsets them and sets 'appearanceTools' instead. |
|
2165 |
foreach ( $nodes as $node ) { |
|
2166 |
$all_opt_ins_are_set = true; |
|
2167 |
foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { |
|
2168 |
$full_path = array_merge( $node['path'], $opt_in_path ); |
|
2169 |
// Use "unset prop" as a marker instead of "null" because |
|
2170 |
// "null" can be a valid value for some props (e.g. blockGap). |
|
2171 |
$opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); |
|
2172 |
if ( 'unset prop' === $opt_in_value ) { |
|
2173 |
$all_opt_ins_are_set = false; |
|
2174 |
break; |
|
2175 |
} |
|
2176 |
} |
|
2177 |
|
|
2178 |
if ( $all_opt_ins_are_set ) { |
|
2179 |
_wp_array_set( $output, array_merge( $node['path'], array( 'appearanceTools' ) ), true ); |
|
2180 |
foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { |
|
2181 |
$full_path = array_merge( $node['path'], $opt_in_path ); |
|
2182 |
// Use "unset prop" as a marker instead of "null" because |
|
2183 |
// "null" can be a valid value for some props (e.g. blockGap). |
|
2184 |
$opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); |
|
2185 |
if ( true !== $opt_in_value ) { |
|
2186 |
continue; |
|
2187 |
} |
|
2188 |
|
|
2189 |
// The following could be improved to be path independent. |
|
2190 |
// At the moment it relies on a couple of assumptions: |
|
2191 |
// |
|
2192 |
// - all opt-ins having a path of size 2. |
|
2193 |
// - there's two sources of settings: the top-level and the block-level. |
|
2194 |
if ( |
|
2195 |
( 1 === count( $node['path'] ) ) && |
|
2196 |
( 'settings' === $node['path'][0] ) |
|
2197 |
) { |
|
2198 |
// Top-level settings. |
|
2199 |
unset( $output['settings'][ $opt_in_path[0] ][ $opt_in_path[1] ] ); |
|
2200 |
if ( empty( $output['settings'][ $opt_in_path[0] ] ) ) { |
|
2201 |
unset( $output['settings'][ $opt_in_path[0] ] ); |
|
2202 |
} |
|
2203 |
} elseif ( |
|
2204 |
( 3 === count( $node['path'] ) ) && |
|
2205 |
( 'settings' === $node['path'][0] ) && |
|
2206 |
( 'blocks' === $node['path'][1] ) |
|
2207 |
) { |
|
2208 |
// Block-level settings. |
|
2209 |
$block_name = $node['path'][2]; |
|
2210 |
unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ][ $opt_in_path[1] ] ); |
|
2211 |
if ( empty( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ) ) { |
|
2212 |
unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ); |
|
2213 |
} |
|
2214 |
} |
|
2215 |
} |
|
2216 |
} |
|
2217 |
} |
|
2218 |
|
|
2219 |
wp_recursive_ksort( $output ); |
|
2220 |
|
|
2221 |
return $output; |
|
2222 |
} |
|
2223 |
|
18
|
2224 |
} |