174 * @since 5.8.0 |
216 * @since 5.8.0 |
175 * @since 5.9.0 Added the `border-*`, `font-family`, `font-style`, `font-weight`, |
217 * @since 5.9.0 Added the `border-*`, `font-family`, `font-style`, `font-weight`, |
176 * `letter-spacing`, `margin-*`, `padding-*`, `--wp--style--block-gap`, |
218 * `letter-spacing`, `margin-*`, `padding-*`, `--wp--style--block-gap`, |
177 * `text-decoration`, `text-transform`, and `filter` properties, |
219 * `text-decoration`, `text-transform`, and `filter` properties, |
178 * simplified the metadata structure. |
220 * simplified the metadata structure. |
|
221 * @since 6.1.0 Added the `border-*-color`, `border-*-width`, `border-*-style`, |
|
222 * `--wp--style--root--padding-*`, and `box-shadow` properties, |
|
223 * removed the `--wp--style--block-gap` property. |
|
224 * @since 6.2.0 Added `outline-*`, and `min-height` properties. |
|
225 * @since 6.3.0 Added `column-count` property. |
|
226 * @since 6.4.0 Added `writing-mode` property. |
|
227 * @since 6.5.0 Added `aspect-ratio` property. |
|
228 * @since 6.6.0 Added `background-[image|position|repeat|size]` properties. |
|
229 * |
179 * @var array |
230 * @var array |
180 */ |
231 */ |
181 const PROPERTIES_METADATA = array( |
232 const PROPERTIES_METADATA = array( |
182 'background' => array( 'color', 'gradient' ), |
233 'aspect-ratio' => array( 'dimensions', 'aspectRatio' ), |
183 'background-color' => array( 'color', 'background' ), |
234 'background' => array( 'color', 'gradient' ), |
184 'border-radius' => array( 'border', 'radius' ), |
235 'background-color' => array( 'color', 'background' ), |
185 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), |
236 'background-image' => array( 'background', 'backgroundImage' ), |
186 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), |
237 'background-position' => array( 'background', 'backgroundPosition' ), |
187 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ), |
238 'background-repeat' => array( 'background', 'backgroundRepeat' ), |
188 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ), |
239 'background-size' => array( 'background', 'backgroundSize' ), |
189 'border-color' => array( 'border', 'color' ), |
240 'border-radius' => array( 'border', 'radius' ), |
190 'border-width' => array( 'border', 'width' ), |
241 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), |
191 'border-style' => array( 'border', 'style' ), |
242 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), |
192 'color' => array( 'color', 'text' ), |
243 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ), |
193 'font-family' => array( 'typography', 'fontFamily' ), |
244 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ), |
194 'font-size' => array( 'typography', 'fontSize' ), |
245 'border-color' => array( 'border', 'color' ), |
195 'font-style' => array( 'typography', 'fontStyle' ), |
246 'border-width' => array( 'border', 'width' ), |
196 'font-weight' => array( 'typography', 'fontWeight' ), |
247 'border-style' => array( 'border', 'style' ), |
197 'letter-spacing' => array( 'typography', 'letterSpacing' ), |
248 'border-top-color' => array( 'border', 'top', 'color' ), |
198 'line-height' => array( 'typography', 'lineHeight' ), |
249 'border-top-width' => array( 'border', 'top', 'width' ), |
199 'margin' => array( 'spacing', 'margin' ), |
250 'border-top-style' => array( 'border', 'top', 'style' ), |
200 'margin-top' => array( 'spacing', 'margin', 'top' ), |
251 'border-right-color' => array( 'border', 'right', 'color' ), |
201 'margin-right' => array( 'spacing', 'margin', 'right' ), |
252 'border-right-width' => array( 'border', 'right', 'width' ), |
202 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), |
253 'border-right-style' => array( 'border', 'right', 'style' ), |
203 'margin-left' => array( 'spacing', 'margin', 'left' ), |
254 'border-bottom-color' => array( 'border', 'bottom', 'color' ), |
204 'padding' => array( 'spacing', 'padding' ), |
255 'border-bottom-width' => array( 'border', 'bottom', 'width' ), |
205 'padding-top' => array( 'spacing', 'padding', 'top' ), |
256 'border-bottom-style' => array( 'border', 'bottom', 'style' ), |
206 'padding-right' => array( 'spacing', 'padding', 'right' ), |
257 'border-left-color' => array( 'border', 'left', 'color' ), |
207 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ), |
258 'border-left-width' => array( 'border', 'left', 'width' ), |
208 'padding-left' => array( 'spacing', 'padding', 'left' ), |
259 'border-left-style' => array( 'border', 'left', 'style' ), |
209 '--wp--style--block-gap' => array( 'spacing', 'blockGap' ), |
260 'color' => array( 'color', 'text' ), |
210 'text-decoration' => array( 'typography', 'textDecoration' ), |
261 'text-align' => array( 'typography', 'textAlign' ), |
211 'text-transform' => array( 'typography', 'textTransform' ), |
262 'column-count' => array( 'typography', 'textColumns' ), |
212 'filter' => array( 'filter', 'duotone' ), |
263 'font-family' => array( 'typography', 'fontFamily' ), |
|
264 'font-size' => array( 'typography', 'fontSize' ), |
|
265 'font-style' => array( 'typography', 'fontStyle' ), |
|
266 'font-weight' => array( 'typography', 'fontWeight' ), |
|
267 'letter-spacing' => array( 'typography', 'letterSpacing' ), |
|
268 'line-height' => array( 'typography', 'lineHeight' ), |
|
269 'margin' => array( 'spacing', 'margin' ), |
|
270 'margin-top' => array( 'spacing', 'margin', 'top' ), |
|
271 'margin-right' => array( 'spacing', 'margin', 'right' ), |
|
272 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ), |
|
273 'margin-left' => array( 'spacing', 'margin', 'left' ), |
|
274 'min-height' => array( 'dimensions', 'minHeight' ), |
|
275 'outline-color' => array( 'outline', 'color' ), |
|
276 'outline-offset' => array( 'outline', 'offset' ), |
|
277 'outline-style' => array( 'outline', 'style' ), |
|
278 'outline-width' => array( 'outline', 'width' ), |
|
279 'padding' => array( 'spacing', 'padding' ), |
|
280 'padding-top' => array( 'spacing', 'padding', 'top' ), |
|
281 'padding-right' => array( 'spacing', 'padding', 'right' ), |
|
282 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ), |
|
283 'padding-left' => array( 'spacing', 'padding', 'left' ), |
|
284 '--wp--style--root--padding' => array( 'spacing', 'padding' ), |
|
285 '--wp--style--root--padding-top' => array( 'spacing', 'padding', 'top' ), |
|
286 '--wp--style--root--padding-right' => array( 'spacing', 'padding', 'right' ), |
|
287 '--wp--style--root--padding-bottom' => array( 'spacing', 'padding', 'bottom' ), |
|
288 '--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ), |
|
289 'text-decoration' => array( 'typography', 'textDecoration' ), |
|
290 'text-transform' => array( 'typography', 'textTransform' ), |
|
291 'filter' => array( 'filter', 'duotone' ), |
|
292 'box-shadow' => array( 'shadow' ), |
|
293 'writing-mode' => array( 'typography', 'writingMode' ), |
|
294 ); |
|
295 |
|
296 /** |
|
297 * Indirect metadata for style properties that are not directly output. |
|
298 * |
|
299 * Each element maps from a CSS property name to an array of |
|
300 * paths to the value in theme.json & block attributes. |
|
301 * |
|
302 * Indirect properties are not output directly by `compute_style_properties`, |
|
303 * but are used elsewhere in the processing of global styles. The indirect |
|
304 * property is used to validate whether a style value is allowed. |
|
305 * |
|
306 * @since 6.2.0 |
|
307 * @since 6.6.0 Added background-image properties. |
|
308 * |
|
309 * @var array |
|
310 */ |
|
311 const INDIRECT_PROPERTIES_METADATA = array( |
|
312 'gap' => array( |
|
313 array( 'spacing', 'blockGap' ), |
|
314 ), |
|
315 'column-gap' => array( |
|
316 array( 'spacing', 'blockGap', 'left' ), |
|
317 ), |
|
318 'row-gap' => array( |
|
319 array( 'spacing', 'blockGap', 'top' ), |
|
320 ), |
|
321 'max-width' => array( |
|
322 array( 'layout', 'contentSize' ), |
|
323 array( 'layout', 'wideSize' ), |
|
324 ), |
|
325 'background-image' => array( |
|
326 array( 'background', 'backgroundImage', 'url' ), |
|
327 ), |
213 ); |
328 ); |
214 |
329 |
215 /** |
330 /** |
216 * Protected style properties. |
331 * Protected style properties. |
217 * |
332 * |
231 * The top-level keys a theme.json can have. |
346 * The top-level keys a theme.json can have. |
232 * |
347 * |
233 * @since 5.8.0 As `ALLOWED_TOP_LEVEL_KEYS`. |
348 * @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`, |
349 * @since 5.9.0 Renamed from `ALLOWED_TOP_LEVEL_KEYS` to `VALID_TOP_LEVEL_KEYS`, |
235 * added the `customTemplates` and `templateParts` values. |
350 * added the `customTemplates` and `templateParts` values. |
|
351 * @since 6.3.0 Added the `description` value. |
|
352 * @since 6.6.0 Added `blockTypes` to support block style variation theme.json partials. |
236 * @var string[] |
353 * @var string[] |
237 */ |
354 */ |
238 const VALID_TOP_LEVEL_KEYS = array( |
355 const VALID_TOP_LEVEL_KEYS = array( |
|
356 'blockTypes', |
239 'customTemplates', |
357 'customTemplates', |
|
358 'description', |
240 'patterns', |
359 'patterns', |
241 'settings', |
360 'settings', |
|
361 'slug', |
242 'styles', |
362 'styles', |
243 'templateParts', |
363 'templateParts', |
|
364 'title', |
244 'version', |
365 'version', |
245 'title', |
|
246 ); |
366 ); |
247 |
367 |
248 /** |
368 /** |
249 * The valid properties under the settings key. |
369 * The valid properties under the settings key. |
250 * |
370 * |
251 * @since 5.8.0 As `ALLOWED_SETTINGS`. |
371 * @since 5.8.0 As `ALLOWED_SETTINGS`. |
252 * @since 5.9.0 Renamed from `ALLOWED_SETTINGS` to `VALID_SETTINGS`, |
372 * @since 5.9.0 Renamed from `ALLOWED_SETTINGS` to `VALID_SETTINGS`, |
253 * added new properties for `border`, `color`, `spacing`, |
373 * added new properties for `border`, `color`, `spacing`, |
254 * and `typography`, and renamed others according to the new schema. |
374 * and `typography`, and renamed others according to the new schema. |
255 * @since 6.0.0 Added `color.defaultDuotone`. |
375 * @since 6.0.0 Added `color.defaultDuotone`. |
|
376 * @since 6.1.0 Added `layout.definitions` and `useRootPaddingAwareAlignments`. |
|
377 * @since 6.2.0 Added `dimensions.minHeight`, 'shadow.presets', 'shadow.defaultPresets', |
|
378 * `position.fixed` and `position.sticky`. |
|
379 * @since 6.3.0 Added support for `typography.textColumns`, removed `layout.definitions`. |
|
380 * @since 6.4.0 Added support for `layout.allowEditing`, `background.backgroundImage`, |
|
381 * `typography.writingMode`, `lightbox.enabled` and `lightbox.allowEditing`. |
|
382 * @since 6.5.0 Added support for `layout.allowCustomContentAndWideSize`, |
|
383 * `background.backgroundSize` and `dimensions.aspectRatio`. |
|
384 * @since 6.6.0 Added support for 'dimensions.aspectRatios', 'dimensions.defaultAspectRatios', |
|
385 * 'typography.defaultFontSizes', and 'spacing.defaultSpacingSizes'. |
256 * @var array |
386 * @var array |
257 */ |
387 */ |
258 const VALID_SETTINGS = array( |
388 const VALID_SETTINGS = array( |
259 'appearanceTools' => null, |
389 'appearanceTools' => null, |
260 'border' => array( |
390 'useRootPaddingAwareAlignments' => null, |
|
391 'background' => array( |
|
392 'backgroundImage' => null, |
|
393 'backgroundSize' => null, |
|
394 ), |
|
395 'border' => array( |
261 'color' => null, |
396 'color' => null, |
262 'radius' => null, |
397 'radius' => null, |
263 'style' => null, |
398 'style' => null, |
264 'width' => null, |
399 'width' => null, |
265 ), |
400 ), |
266 'color' => array( |
401 'color' => array( |
267 'background' => null, |
402 'background' => null, |
268 'custom' => null, |
403 'custom' => null, |
269 'customDuotone' => null, |
404 'customDuotone' => null, |
270 'customGradient' => null, |
405 'customGradient' => null, |
271 'defaultDuotone' => null, |
406 'defaultDuotone' => null, |
272 'defaultGradients' => null, |
407 'defaultGradients' => null, |
273 'defaultPalette' => null, |
408 'defaultPalette' => null, |
274 'duotone' => null, |
409 'duotone' => null, |
275 'gradients' => null, |
410 'gradients' => null, |
276 'link' => null, |
411 'link' => null, |
|
412 'heading' => null, |
|
413 'button' => null, |
|
414 'caption' => null, |
277 'palette' => null, |
415 'palette' => null, |
278 'text' => null, |
416 'text' => null, |
279 ), |
417 ), |
280 'custom' => null, |
418 'custom' => null, |
281 'layout' => array( |
419 'dimensions' => array( |
282 'contentSize' => null, |
420 'aspectRatio' => null, |
283 'wideSize' => null, |
421 'aspectRatios' => null, |
|
422 'defaultAspectRatios' => null, |
|
423 'minHeight' => null, |
284 ), |
424 ), |
285 'spacing' => array( |
425 'layout' => array( |
286 'blockGap' => null, |
426 'contentSize' => null, |
287 'margin' => null, |
427 'wideSize' => null, |
288 'padding' => null, |
428 'allowEditing' => null, |
289 'units' => null, |
429 'allowCustomContentAndWideSize' => null, |
290 ), |
430 ), |
291 'typography' => array( |
431 'lightbox' => array( |
292 'customFontSize' => null, |
432 'enabled' => null, |
293 'dropCap' => null, |
433 'allowEditing' => null, |
294 'fontFamilies' => null, |
434 ), |
295 'fontSizes' => null, |
435 'position' => array( |
296 'fontStyle' => null, |
436 'fixed' => null, |
297 'fontWeight' => null, |
437 'sticky' => null, |
298 'letterSpacing' => null, |
438 ), |
299 'lineHeight' => null, |
439 'spacing' => array( |
300 'textDecoration' => null, |
440 'customSpacingSize' => null, |
301 'textTransform' => null, |
441 'defaultSpacingSizes' => null, |
|
442 'spacingSizes' => null, |
|
443 'spacingScale' => null, |
|
444 'blockGap' => null, |
|
445 'margin' => null, |
|
446 'padding' => null, |
|
447 'units' => null, |
|
448 ), |
|
449 'shadow' => array( |
|
450 'presets' => null, |
|
451 'defaultPresets' => null, |
|
452 ), |
|
453 'typography' => array( |
|
454 'fluid' => null, |
|
455 'customFontSize' => null, |
|
456 'defaultFontSizes' => null, |
|
457 'dropCap' => null, |
|
458 'fontFamilies' => null, |
|
459 'fontSizes' => null, |
|
460 'fontStyle' => null, |
|
461 'fontWeight' => null, |
|
462 'letterSpacing' => null, |
|
463 'lineHeight' => null, |
|
464 'textAlign' => null, |
|
465 'textColumns' => null, |
|
466 'textDecoration' => null, |
|
467 'textTransform' => null, |
|
468 'writingMode' => null, |
|
469 ), |
|
470 ); |
|
471 |
|
472 /* |
|
473 * The valid properties for fontFamilies under settings key. |
|
474 * |
|
475 * @since 6.5.0 |
|
476 * |
|
477 * @var array |
|
478 */ |
|
479 const FONT_FAMILY_SCHEMA = array( |
|
480 array( |
|
481 'fontFamily' => null, |
|
482 'name' => null, |
|
483 'slug' => null, |
|
484 'fontFace' => array( |
|
485 array( |
|
486 'ascentOverride' => null, |
|
487 'descentOverride' => null, |
|
488 'fontDisplay' => null, |
|
489 'fontFamily' => null, |
|
490 'fontFeatureSettings' => null, |
|
491 'fontStyle' => null, |
|
492 'fontStretch' => null, |
|
493 'fontVariationSettings' => null, |
|
494 'fontWeight' => null, |
|
495 'lineGapOverride' => null, |
|
496 'sizeAdjust' => null, |
|
497 'src' => null, |
|
498 'unicodeRange' => null, |
|
499 ), |
|
500 ), |
302 ), |
501 ), |
303 ); |
502 ); |
304 |
503 |
305 /** |
504 /** |
306 * The valid properties under the styles key. |
505 * The valid properties under the styles key. |
307 * |
506 * |
308 * @since 5.8.0 As `ALLOWED_STYLES`. |
507 * @since 5.8.0 As `ALLOWED_STYLES`. |
309 * @since 5.9.0 Renamed from `ALLOWED_STYLES` to `VALID_STYLES`, |
508 * @since 5.9.0 Renamed from `ALLOWED_STYLES` to `VALID_STYLES`, |
310 * added new properties for `border`, `filter`, `spacing`, |
509 * added new properties for `border`, `filter`, `spacing`, |
311 * and `typography`. |
510 * and `typography`. |
|
511 * @since 6.1.0 Added new side properties for `border`, |
|
512 * added new property `shadow`, |
|
513 * updated `blockGap` to be allowed at any level. |
|
514 * @since 6.2.0 Added `outline`, and `minHeight` properties. |
|
515 * @since 6.3.0 Added support for `typography.textColumns`. |
|
516 * @since 6.5.0 Added support for `dimensions.aspectRatio`. |
|
517 * @since 6.6.0 Added `background` sub properties to top-level only. |
|
518 * |
312 * @var array |
519 * @var array |
313 */ |
520 */ |
314 const VALID_STYLES = array( |
521 const VALID_STYLES = array( |
|
522 'background' => array( |
|
523 'backgroundImage' => 'top', |
|
524 'backgroundPosition' => 'top', |
|
525 'backgroundRepeat' => 'top', |
|
526 'backgroundSize' => 'top', |
|
527 ), |
315 'border' => array( |
528 'border' => array( |
316 'color' => null, |
529 'color' => null, |
317 'radius' => null, |
530 'radius' => null, |
318 'style' => null, |
531 'style' => null, |
319 'width' => null, |
532 'width' => null, |
|
533 'top' => null, |
|
534 'right' => null, |
|
535 'bottom' => null, |
|
536 'left' => null, |
320 ), |
537 ), |
321 'color' => array( |
538 'color' => array( |
322 'background' => null, |
539 'background' => null, |
323 'gradient' => null, |
540 'gradient' => null, |
324 'text' => null, |
541 'text' => null, |
325 ), |
542 ), |
|
543 'dimensions' => array( |
|
544 'aspectRatio' => null, |
|
545 'minHeight' => null, |
|
546 ), |
326 'filter' => array( |
547 'filter' => array( |
327 'duotone' => null, |
548 'duotone' => null, |
328 ), |
549 ), |
|
550 'outline' => array( |
|
551 'color' => null, |
|
552 'offset' => null, |
|
553 'style' => null, |
|
554 'width' => null, |
|
555 ), |
|
556 'shadow' => null, |
329 'spacing' => array( |
557 'spacing' => array( |
330 'margin' => null, |
558 'margin' => null, |
331 'padding' => null, |
559 'padding' => null, |
332 'blockGap' => 'top', |
560 'blockGap' => null, |
333 ), |
561 ), |
334 'typography' => array( |
562 'typography' => array( |
335 'fontFamily' => null, |
563 'fontFamily' => null, |
336 'fontSize' => null, |
564 'fontSize' => null, |
337 'fontStyle' => null, |
565 'fontStyle' => null, |
338 'fontWeight' => null, |
566 'fontWeight' => null, |
339 'letterSpacing' => null, |
567 'letterSpacing' => null, |
340 'lineHeight' => null, |
568 'lineHeight' => null, |
|
569 'textAlign' => null, |
|
570 'textColumns' => null, |
341 'textDecoration' => null, |
571 'textDecoration' => null, |
342 'textTransform' => null, |
572 'textTransform' => null, |
|
573 'writingMode' => null, |
343 ), |
574 ), |
|
575 'css' => null, |
344 ); |
576 ); |
345 |
577 |
346 /** |
578 /** |
|
579 * Defines which pseudo selectors are enabled for which elements. |
|
580 * |
|
581 * The order of the selectors should be: link, any-link, visited, hover, focus, active. |
|
582 * This is to ensure the user action (hover, focus and active) styles have a higher |
|
583 * specificity than the visited styles, which in turn have a higher specificity than |
|
584 * the unvisited styles. |
|
585 * |
|
586 * See https://core.trac.wordpress.org/ticket/56928. |
|
587 * Note: this will affect both top-level and block-level elements. |
|
588 * |
|
589 * @since 6.1.0 |
|
590 * @since 6.2.0 Added support for ':link' and ':any-link'. |
|
591 */ |
|
592 const VALID_ELEMENT_PSEUDO_SELECTORS = array( |
|
593 'link' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':active' ), |
|
594 'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':active' ), |
|
595 ); |
|
596 |
|
597 /** |
347 * The valid elements that can be found under styles. |
598 * The valid elements that can be found under styles. |
348 * |
599 * |
349 * @since 5.8.0 |
600 * @since 5.8.0 |
|
601 * @since 6.1.0 Added `heading`, `button`, and `caption` elements. |
350 * @var string[] |
602 * @var string[] |
351 */ |
603 */ |
352 const ELEMENTS = array( |
604 const ELEMENTS = array( |
353 'link' => 'a', |
605 'link' => 'a:where(:not(.wp-element-button))', // The `where` is needed to lower the specificity. |
354 'h1' => 'h1', |
606 'heading' => 'h1, h2, h3, h4, h5, h6', |
355 'h2' => 'h2', |
607 'h1' => 'h1', |
356 'h3' => 'h3', |
608 'h2' => 'h2', |
357 'h4' => 'h4', |
609 'h3' => 'h3', |
358 'h5' => 'h5', |
610 'h4' => 'h4', |
359 'h6' => 'h6', |
611 'h5' => 'h5', |
|
612 'h6' => 'h6', |
|
613 // We have the .wp-block-button__link class so that this will target older buttons that have been serialized. |
|
614 'button' => '.wp-element-button, .wp-block-button__link', |
|
615 // The block classes are necessary to target older content that won't use the new class names. |
|
616 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption', |
|
617 'cite' => 'cite', |
360 ); |
618 ); |
361 |
619 |
|
620 const __EXPERIMENTAL_ELEMENT_CLASS_NAMES = array( |
|
621 'button' => 'wp-element-button', |
|
622 'caption' => 'wp-element-caption', |
|
623 ); |
|
624 |
|
625 /** |
|
626 * List of block support features that can have their related styles |
|
627 * generated under their own feature level selector rather than the block's. |
|
628 * |
|
629 * @since 6.1.0 |
|
630 * @var string[] |
|
631 */ |
|
632 const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array( |
|
633 '__experimentalBorder' => 'border', |
|
634 'color' => 'color', |
|
635 'spacing' => 'spacing', |
|
636 'typography' => 'typography', |
|
637 ); |
|
638 |
|
639 /** |
|
640 * Return the input schema at the root and per origin. |
|
641 * |
|
642 * @since 6.5.0 |
|
643 * |
|
644 * @param array $schema The base schema. |
|
645 * @return array The schema at the root and per origin. |
|
646 * |
|
647 * Example: |
|
648 * schema_in_root_and_per_origin( |
|
649 * array( |
|
650 * 'fontFamily' => null, |
|
651 * 'slug' => null, |
|
652 * ) |
|
653 * ) |
|
654 * |
|
655 * Returns: |
|
656 * array( |
|
657 * 'fontFamily' => null, |
|
658 * 'slug' => null, |
|
659 * 'default' => array( |
|
660 * 'fontFamily' => null, |
|
661 * 'slug' => null, |
|
662 * ), |
|
663 * 'blocks' => array( |
|
664 * 'fontFamily' => null, |
|
665 * 'slug' => null, |
|
666 * ), |
|
667 * 'theme' => array( |
|
668 * 'fontFamily' => null, |
|
669 * 'slug' => null, |
|
670 * ), |
|
671 * 'custom' => array( |
|
672 * 'fontFamily' => null, |
|
673 * 'slug' => null, |
|
674 * ), |
|
675 * ) |
|
676 */ |
|
677 protected static function schema_in_root_and_per_origin( $schema ) { |
|
678 $schema_in_root_and_per_origin = $schema; |
|
679 foreach ( static::VALID_ORIGINS as $origin ) { |
|
680 $schema_in_root_and_per_origin[ $origin ] = $schema; |
|
681 } |
|
682 return $schema_in_root_and_per_origin; |
|
683 } |
|
684 |
|
685 /** |
|
686 * Returns a class name by an element name. |
|
687 * |
|
688 * @since 6.1.0 |
|
689 * |
|
690 * @param string $element The name of the element. |
|
691 * @return string The name of the class. |
|
692 */ |
|
693 public static function get_element_class_name( $element ) { |
|
694 $class_name = ''; |
|
695 |
|
696 if ( isset( static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ] ) ) { |
|
697 $class_name = static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ]; |
|
698 } |
|
699 |
|
700 return $class_name; |
|
701 } |
|
702 |
362 /** |
703 /** |
363 * Options that settings.appearanceTools enables. |
704 * Options that settings.appearanceTools enables. |
364 * |
705 * |
365 * @since 6.0.0 |
706 * @since 6.0.0 |
|
707 * @since 6.2.0 Added `dimensions.minHeight` and `position.sticky`. |
|
708 * @since 6.4.0 Added `background.backgroundImage`. |
|
709 * @since 6.5.0 Added `background.backgroundSize` and `dimensions.aspectRatio`. |
366 * @var array |
710 * @var array |
367 */ |
711 */ |
368 const APPEARANCE_TOOLS_OPT_INS = array( |
712 const APPEARANCE_TOOLS_OPT_INS = array( |
|
713 array( 'background', 'backgroundImage' ), |
|
714 array( 'background', 'backgroundSize' ), |
369 array( 'border', 'color' ), |
715 array( 'border', 'color' ), |
370 array( 'border', 'radius' ), |
716 array( 'border', 'radius' ), |
371 array( 'border', 'style' ), |
717 array( 'border', 'style' ), |
372 array( 'border', 'width' ), |
718 array( 'border', 'width' ), |
373 array( 'color', 'link' ), |
719 array( 'color', 'link' ), |
|
720 array( 'color', 'heading' ), |
|
721 array( 'color', 'button' ), |
|
722 array( 'color', 'caption' ), |
|
723 array( 'dimensions', 'aspectRatio' ), |
|
724 array( 'dimensions', 'minHeight' ), |
|
725 array( 'position', 'sticky' ), |
374 array( 'spacing', 'blockGap' ), |
726 array( 'spacing', 'blockGap' ), |
375 array( 'spacing', 'margin' ), |
727 array( 'spacing', 'margin' ), |
376 array( 'spacing', 'padding' ), |
728 array( 'spacing', 'padding' ), |
377 array( 'typography', 'lineHeight' ), |
729 array( 'typography', 'lineHeight' ), |
378 ); |
730 ); |
380 /** |
732 /** |
381 * The latest version of the schema in use. |
733 * The latest version of the schema in use. |
382 * |
734 * |
383 * @since 5.8.0 |
735 * @since 5.8.0 |
384 * @since 5.9.0 Changed value from 1 to 2. |
736 * @since 5.9.0 Changed value from 1 to 2. |
|
737 * @since 6.6.0 Changed value from 2 to 3. |
385 * @var int |
738 * @var int |
386 */ |
739 */ |
387 const LATEST_SCHEMA = 2; |
740 const LATEST_SCHEMA = 3; |
388 |
741 |
389 /** |
742 /** |
390 * Constructor. |
743 * Constructor. |
391 * |
744 * |
392 * @since 5.8.0 |
745 * @since 5.8.0 |
|
746 * @since 6.6.0 Key spacingScale by origin, and Pre-generate the spacingSizes from spacingScale. |
|
747 * Added unwrapping of shared block style variations into block type variations if registered. |
393 * |
748 * |
394 * @param array $theme_json A structure that follows the theme.json schema. |
749 * @param array $theme_json A structure that follows the theme.json schema. |
395 * @param string $origin Optional. What source of data this object represents. |
750 * @param string $origin Optional. What source of data this object represents. |
396 * One of 'default', 'theme', or 'custom'. Default 'theme'. |
751 * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. |
397 */ |
752 */ |
398 public function __construct( $theme_json = array(), $origin = 'theme' ) { |
753 public function __construct( $theme_json = array( 'version' => self::LATEST_SCHEMA ), $origin = 'theme' ) { |
399 if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { |
754 if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { |
400 $origin = 'theme'; |
755 $origin = 'theme'; |
401 } |
756 } |
402 |
757 |
403 $this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); |
758 $this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json, $origin ); |
404 $valid_block_names = array_keys( static::get_blocks_metadata() ); |
759 $valid_block_names = array_keys( static::get_blocks_metadata() ); |
405 $valid_element_names = array_keys( static::ELEMENTS ); |
760 $valid_element_names = array_keys( static::ELEMENTS ); |
406 $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names ); |
761 $valid_variations = static::get_valid_block_style_variations(); |
407 $this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); |
762 $this->theme_json = static::unwrap_shared_block_style_variations( $this->theme_json, $valid_variations ); |
|
763 $this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); |
|
764 $this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json ); |
408 |
765 |
409 // Internally, presets are keyed by origin. |
766 // Internally, presets are keyed by origin. |
410 $nodes = static::get_setting_nodes( $this->theme_json ); |
767 $nodes = static::get_setting_nodes( $this->theme_json ); |
411 foreach ( $nodes as $node ) { |
768 foreach ( $nodes as $node ) { |
412 foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
769 foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
413 $path = array_merge( $node['path'], $preset_metadata['path'] ); |
770 $path = $node['path']; |
|
771 foreach ( $preset_metadata['path'] as $subpath ) { |
|
772 $path[] = $subpath; |
|
773 } |
414 $preset = _wp_array_get( $this->theme_json, $path, null ); |
774 $preset = _wp_array_get( $this->theme_json, $path, null ); |
415 if ( null !== $preset ) { |
775 if ( null !== $preset ) { |
416 // If the preset is not already keyed by origin. |
776 // If the preset is not already keyed by origin. |
417 if ( isset( $preset[0] ) || empty( $preset ) ) { |
777 if ( isset( $preset[0] ) || empty( $preset ) ) { |
418 _wp_array_set( $this->theme_json, $path, array( $origin => $preset ) ); |
778 _wp_array_set( $this->theme_json, $path, array( $origin => $preset ) ); |
419 } |
779 } |
420 } |
780 } |
421 } |
781 } |
422 } |
782 } |
|
783 |
|
784 // In addition to presets, spacingScale (which generates presets) is also keyed by origin. |
|
785 $scale_path = array( 'settings', 'spacing', 'spacingScale' ); |
|
786 $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null ); |
|
787 if ( null !== $spacing_scale ) { |
|
788 // If the spacingScale is not already keyed by origin. |
|
789 if ( empty( array_intersect( array_keys( $spacing_scale ), static::VALID_ORIGINS ) ) ) { |
|
790 _wp_array_set( $this->theme_json, $scale_path, array( $origin => $spacing_scale ) ); |
|
791 } |
|
792 } |
|
793 |
|
794 // Pre-generate the spacingSizes from spacingScale. |
|
795 $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin ); |
|
796 $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null ); |
|
797 if ( isset( $spacing_scale ) ) { |
|
798 $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin ); |
|
799 $spacing_sizes = _wp_array_get( $this->theme_json, $sizes_path, array() ); |
|
800 $spacing_scale_sizes = static::compute_spacing_sizes( $spacing_scale ); |
|
801 $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes ); |
|
802 _wp_array_set( $this->theme_json, $sizes_path, $merged_spacing_sizes ); |
|
803 } |
|
804 } |
|
805 |
|
806 /** |
|
807 * Unwraps shared block style variations. |
|
808 * |
|
809 * It takes the shared variations (styles.variations.variationName) and |
|
810 * applies them to all the blocks that have the given variation registered |
|
811 * (styles.blocks.blockType.variations.variationName). |
|
812 * |
|
813 * For example, given the `core/paragraph` and `core/group` blocks have |
|
814 * registered the `section-a` style variation, and given the following input: |
|
815 * |
|
816 * { |
|
817 * "styles": { |
|
818 * "variations": { |
|
819 * "section-a": { "color": { "background": "backgroundColor" } } |
|
820 * } |
|
821 * } |
|
822 * } |
|
823 * |
|
824 * It returns the following output: |
|
825 * |
|
826 * { |
|
827 * "styles": { |
|
828 * "blocks": { |
|
829 * "core/paragraph": { |
|
830 * "variations": { |
|
831 * "section-a": { "color": { "background": "backgroundColor" } } |
|
832 * }, |
|
833 * }, |
|
834 * "core/group": { |
|
835 * "variations": { |
|
836 * "section-a": { "color": { "background": "backgroundColor" } } |
|
837 * } |
|
838 * } |
|
839 * } |
|
840 * } |
|
841 * } |
|
842 * |
|
843 * @since 6.6.0 |
|
844 * |
|
845 * @param array $theme_json A structure that follows the theme.json schema. |
|
846 * @param array $valid_variations Valid block style variations. |
|
847 * @return array Theme json data with shared variation definitions unwrapped under appropriate block types. |
|
848 */ |
|
849 private static function unwrap_shared_block_style_variations( $theme_json, $valid_variations ) { |
|
850 if ( empty( $theme_json['styles']['variations'] ) || empty( $valid_variations ) ) { |
|
851 return $theme_json; |
|
852 } |
|
853 |
|
854 $new_theme_json = $theme_json; |
|
855 $variations = $new_theme_json['styles']['variations']; |
|
856 |
|
857 foreach ( $valid_variations as $block_type => $registered_variations ) { |
|
858 foreach ( $registered_variations as $variation_name ) { |
|
859 $block_level_data = $new_theme_json['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); |
|
860 $top_level_data = $variations[ $variation_name ] ?? array(); |
|
861 $merged_data = array_replace_recursive( $top_level_data, $block_level_data ); |
|
862 if ( ! empty( $merged_data ) ) { |
|
863 _wp_array_set( $new_theme_json, array( 'styles', 'blocks', $block_type, 'variations', $variation_name ), $merged_data ); |
|
864 } |
|
865 } |
|
866 } |
|
867 |
|
868 unset( $new_theme_json['styles']['variations'] ); |
|
869 |
|
870 return $new_theme_json; |
423 } |
871 } |
424 |
872 |
425 /** |
873 /** |
426 * Enables some opt-in settings if theme declared support. |
874 * Enables some opt-in settings if theme declared support. |
427 * |
875 * |
473 /** |
923 /** |
474 * Sanitizes the input according to the schemas. |
924 * Sanitizes the input according to the schemas. |
475 * |
925 * |
476 * @since 5.8.0 |
926 * @since 5.8.0 |
477 * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. |
927 * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. |
|
928 * @since 6.3.0 Added the `$valid_variations` parameter. |
|
929 * @since 6.6.0 Updated schema to allow extended block style variations. |
478 * |
930 * |
479 * @param array $input Structure to sanitize. |
931 * @param array $input Structure to sanitize. |
480 * @param array $valid_block_names List of valid block names. |
932 * @param array $valid_block_names List of valid block names. |
481 * @param array $valid_element_names List of valid element names. |
933 * @param array $valid_element_names List of valid element names. |
|
934 * @param array $valid_variations List of valid variations per block. |
482 * @return array The sanitized output. |
935 * @return array The sanitized output. |
483 */ |
936 */ |
484 protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { |
937 protected static function sanitize( $input, $valid_block_names, $valid_element_names, $valid_variations ) { |
|
938 |
485 $output = array(); |
939 $output = array(); |
486 |
940 |
487 if ( ! is_array( $input ) ) { |
941 if ( ! is_array( $input ) ) { |
488 return $output; |
942 return $output; |
489 } |
943 } |
490 |
944 |
|
945 // Preserve only the top most level keys. |
491 $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); |
946 $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); |
492 |
947 |
493 // Some styles are only meant to be available at the top-level (e.g.: blockGap), |
948 /* |
494 // hence, the schema for blocks & elements should not have them. |
949 * Remove any rules that are annotated as "top" in VALID_STYLES constant. |
|
950 * Some styles are only meant to be available at the top-level (e.g.: blockGap), |
|
951 * hence, the schema for blocks & elements should not have them. |
|
952 */ |
495 $styles_non_top_level = static::VALID_STYLES; |
953 $styles_non_top_level = static::VALID_STYLES; |
496 foreach ( array_keys( $styles_non_top_level ) as $section ) { |
954 foreach ( array_keys( $styles_non_top_level ) as $section ) { |
497 foreach ( array_keys( $styles_non_top_level[ $section ] ) as $prop ) { |
955 // array_key_exists() needs to be used instead of isset() because the value can be null. |
498 if ( 'top' === $styles_non_top_level[ $section ][ $prop ] ) { |
956 if ( array_key_exists( $section, $styles_non_top_level ) && is_array( $styles_non_top_level[ $section ] ) ) { |
499 unset( $styles_non_top_level[ $section ][ $prop ] ); |
957 foreach ( array_keys( $styles_non_top_level[ $section ] ) as $prop ) { |
|
958 if ( 'top' === $styles_non_top_level[ $section ][ $prop ] ) { |
|
959 unset( $styles_non_top_level[ $section ][ $prop ] ); |
|
960 } |
500 } |
961 } |
501 } |
962 } |
502 } |
963 } |
503 |
964 |
504 // Build the schema based on valid block & element names. |
965 // Build the schema based on valid block & element names. |
505 $schema = array(); |
966 $schema = array(); |
506 $schema_styles_elements = array(); |
967 $schema_styles_elements = array(); |
|
968 |
|
969 /* |
|
970 * Set allowed element pseudo selectors based on per element allow list. |
|
971 * Target data structure in schema: |
|
972 * e.g. |
|
973 * - top level elements: `$schema['styles']['elements']['link'][':hover']`. |
|
974 * - block level elements: `$schema['styles']['blocks']['core/button']['elements']['link'][':hover']`. |
|
975 */ |
507 foreach ( $valid_element_names as $element ) { |
976 foreach ( $valid_element_names as $element ) { |
508 $schema_styles_elements[ $element ] = $styles_non_top_level; |
977 $schema_styles_elements[ $element ] = $styles_non_top_level; |
509 } |
978 |
|
979 if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) { |
|
980 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { |
|
981 $schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level; |
|
982 } |
|
983 } |
|
984 } |
|
985 |
510 $schema_styles_blocks = array(); |
986 $schema_styles_blocks = array(); |
511 $schema_settings_blocks = array(); |
987 $schema_settings_blocks = array(); |
|
988 |
|
989 /* |
|
990 * Generate a schema for blocks. |
|
991 * - Block styles can contain `elements` & `variations` definitions. |
|
992 * - Variations definitions cannot be nested. |
|
993 * - Variations can contain styles for inner `blocks`. |
|
994 * - Variation inner `blocks` styles can contain `elements`. |
|
995 * |
|
996 * As each variation needs a `blocks` schema but further nested |
|
997 * inner `blocks`, the overall schema will be generated in multiple passes. |
|
998 */ |
512 foreach ( $valid_block_names as $block ) { |
999 foreach ( $valid_block_names as $block ) { |
513 $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; |
1000 $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; |
514 $schema_styles_blocks[ $block ] = $styles_non_top_level; |
1001 $schema_styles_blocks[ $block ] = $styles_non_top_level; |
515 $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; |
1002 $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; |
516 } |
1003 } |
517 $schema['styles'] = static::VALID_STYLES; |
1004 |
518 $schema['styles']['blocks'] = $schema_styles_blocks; |
1005 $block_style_variation_styles = static::VALID_STYLES; |
519 $schema['styles']['elements'] = $schema_styles_elements; |
1006 $block_style_variation_styles['blocks'] = $schema_styles_blocks; |
520 $schema['settings'] = static::VALID_SETTINGS; |
1007 $block_style_variation_styles['elements'] = $schema_styles_elements; |
521 $schema['settings']['blocks'] = $schema_settings_blocks; |
1008 |
|
1009 foreach ( $valid_block_names as $block ) { |
|
1010 // Build the schema for each block style variation. |
|
1011 $style_variation_names = array(); |
|
1012 if ( |
|
1013 ! empty( $input['styles']['blocks'][ $block ]['variations'] ) && |
|
1014 is_array( $input['styles']['blocks'][ $block ]['variations'] ) && |
|
1015 isset( $valid_variations[ $block ] ) |
|
1016 ) { |
|
1017 $style_variation_names = array_intersect( |
|
1018 array_keys( $input['styles']['blocks'][ $block ]['variations'] ), |
|
1019 $valid_variations[ $block ] |
|
1020 ); |
|
1021 } |
|
1022 |
|
1023 $schema_styles_variations = array(); |
|
1024 if ( ! empty( $style_variation_names ) ) { |
|
1025 $schema_styles_variations = array_fill_keys( $style_variation_names, $block_style_variation_styles ); |
|
1026 } |
|
1027 |
|
1028 $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; |
|
1029 } |
|
1030 |
|
1031 $schema['styles'] = static::VALID_STYLES; |
|
1032 $schema['styles']['blocks'] = $schema_styles_blocks; |
|
1033 $schema['styles']['elements'] = $schema_styles_elements; |
|
1034 $schema['settings'] = static::VALID_SETTINGS; |
|
1035 $schema['settings']['blocks'] = $schema_settings_blocks; |
|
1036 $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA ); |
522 |
1037 |
523 // Remove anything that's not present in the schema. |
1038 // Remove anything that's not present in the schema. |
524 foreach ( array( 'styles', 'settings' ) as $subtree ) { |
1039 foreach ( array( 'styles', 'settings' ) as $subtree ) { |
525 if ( ! isset( $input[ $subtree ] ) ) { |
1040 if ( ! isset( $input[ $subtree ] ) ) { |
526 continue; |
1041 continue; |
567 * } |
1134 * } |
568 * } |
1135 * } |
569 * |
1136 * |
570 * @since 5.8.0 |
1137 * @since 5.8.0 |
571 * @since 5.9.0 Added `duotone` key with CSS selector. |
1138 * @since 5.9.0 Added `duotone` key with CSS selector. |
|
1139 * @since 6.1.0 Added `features` key with block support feature level selectors. |
|
1140 * @since 6.3.0 Refactored and stabilized selectors API. |
|
1141 * @since 6.6.0 Updated to include block style variations from the block styles registry. |
572 * |
1142 * |
573 * @return array Block metadata. |
1143 * @return array Block metadata. |
574 */ |
1144 */ |
575 protected static function get_blocks_metadata() { |
1145 protected static function get_blocks_metadata() { |
576 if ( null !== static::$blocks_metadata ) { |
1146 $registry = WP_Block_Type_Registry::get_instance(); |
|
1147 $blocks = $registry->get_all_registered(); |
|
1148 $style_registry = WP_Block_Styles_Registry::get_instance(); |
|
1149 |
|
1150 // Is there metadata for all currently registered blocks? |
|
1151 $blocks = array_diff_key( $blocks, static::$blocks_metadata ); |
|
1152 if ( empty( $blocks ) ) { |
|
1153 /* |
|
1154 * New block styles may have been registered within WP_Block_Styles_Registry. |
|
1155 * Update block metadata for any new block style variations. |
|
1156 */ |
|
1157 $registered_styles = $style_registry->get_all_registered(); |
|
1158 foreach ( static::$blocks_metadata as $block_name => $block_metadata ) { |
|
1159 if ( ! empty( $registered_styles[ $block_name ] ) ) { |
|
1160 $style_selectors = $block_metadata['styleVariations'] ?? array(); |
|
1161 |
|
1162 foreach ( $registered_styles[ $block_name ] as $block_style ) { |
|
1163 if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { |
|
1164 $style_selectors[ $block_style['name'] ] = static::get_block_style_variation_selector( $block_style['name'], $block_metadata['selector'] ); |
|
1165 } |
|
1166 } |
|
1167 |
|
1168 static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; |
|
1169 } |
|
1170 } |
577 return static::$blocks_metadata; |
1171 return static::$blocks_metadata; |
578 } |
1172 } |
579 |
1173 |
580 static::$blocks_metadata = array(); |
|
581 |
|
582 $registry = WP_Block_Type_Registry::get_instance(); |
|
583 $blocks = $registry->get_all_registered(); |
|
584 foreach ( $blocks as $block_name => $block_type ) { |
1174 foreach ( $blocks as $block_name => $block_type ) { |
585 if ( |
1175 $root_selector = wp_get_block_css_selector( $block_type ); |
586 isset( $block_type->supports['__experimentalSelector'] ) && |
1176 |
587 is_string( $block_type->supports['__experimentalSelector'] ) |
1177 static::$blocks_metadata[ $block_name ]['selector'] = $root_selector; |
588 ) { |
1178 static::$blocks_metadata[ $block_name ]['selectors'] = static::get_block_selectors( $block_type, $root_selector ); |
589 static::$blocks_metadata[ $block_name ]['selector'] = $block_type->supports['__experimentalSelector']; |
1179 |
590 } else { |
1180 $elements = static::get_block_element_selectors( $root_selector ); |
591 static::$blocks_metadata[ $block_name ]['selector'] = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ); |
1181 if ( ! empty( $elements ) ) { |
592 } |
1182 static::$blocks_metadata[ $block_name ]['elements'] = $elements; |
593 |
1183 } |
594 if ( |
1184 |
595 isset( $block_type->supports['color']['__experimentalDuotone'] ) && |
1185 // The block may or may not have a duotone selector. |
596 is_string( $block_type->supports['color']['__experimentalDuotone'] ) |
1186 $duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); |
597 ) { |
1187 |
598 static::$blocks_metadata[ $block_name ]['duotone'] = $block_type->supports['color']['__experimentalDuotone']; |
1188 // Keep backwards compatibility for support.color.__experimentalDuotone. |
599 } |
1189 if ( null === $duotone_selector ) { |
600 |
1190 $duotone_support = isset( $block_type->supports['color']['__experimentalDuotone'] ) |
601 // Assign defaults, then overwrite those that the block sets by itself. |
1191 ? $block_type->supports['color']['__experimentalDuotone'] |
602 // If the block selector is compounded, will append the element to each |
1192 : null; |
603 // individual block selector. |
1193 |
604 $block_selectors = explode( ',', static::$blocks_metadata[ $block_name ]['selector'] ); |
1194 if ( $duotone_support ) { |
605 foreach ( static::ELEMENTS as $el_name => $el_selector ) { |
1195 $root_selector = wp_get_block_css_selector( $block_type ); |
606 $element_selector = array(); |
1196 $duotone_selector = static::scope_selector( $root_selector, $duotone_support ); |
607 foreach ( $block_selectors as $selector ) { |
1197 } |
608 $element_selector[] = $selector . ' ' . $el_selector; |
1198 } |
609 } |
1199 |
610 static::$blocks_metadata[ $block_name ]['elements'][ $el_name ] = implode( ',', $element_selector ); |
1200 if ( null !== $duotone_selector ) { |
|
1201 static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector; |
|
1202 } |
|
1203 |
|
1204 // If the block has style variations, append their selectors to the block metadata. |
|
1205 $style_selectors = array(); |
|
1206 if ( ! empty( $block_type->styles ) ) { |
|
1207 foreach ( $block_type->styles as $style ) { |
|
1208 $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); |
|
1209 } |
|
1210 } |
|
1211 |
|
1212 // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. |
|
1213 $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); |
|
1214 foreach ( $registered_styles as $style ) { |
|
1215 $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); |
|
1216 } |
|
1217 |
|
1218 if ( ! empty( $style_selectors ) ) { |
|
1219 static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; |
611 } |
1220 } |
612 } |
1221 } |
613 |
1222 |
614 return static::$blocks_metadata; |
1223 return static::$blocks_metadata; |
615 } |
1224 } |
707 $types = array( 'variables', 'styles', 'presets' ); |
1343 $types = array( 'variables', 'styles', 'presets' ); |
708 } |
1344 } |
709 } |
1345 } |
710 |
1346 |
711 $blocks_metadata = static::get_blocks_metadata(); |
1347 $blocks_metadata = static::get_blocks_metadata(); |
712 $style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata ); |
1348 $style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata, $options ); |
713 $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); |
1349 $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); |
|
1350 |
|
1351 $root_style_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $style_nodes, 'selector' ), true ); |
|
1352 $root_settings_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $setting_nodes, 'selector' ), true ); |
|
1353 |
|
1354 if ( ! empty( $options['scope'] ) ) { |
|
1355 foreach ( $setting_nodes as &$node ) { |
|
1356 $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] ); |
|
1357 } |
|
1358 foreach ( $style_nodes as &$node ) { |
|
1359 $node = static::scope_style_node_selectors( $options['scope'], $node ); |
|
1360 } |
|
1361 unset( $node ); |
|
1362 } |
|
1363 |
|
1364 if ( ! empty( $options['root_selector'] ) ) { |
|
1365 if ( false !== $root_settings_key ) { |
|
1366 $setting_nodes[ $root_settings_key ]['selector'] = $options['root_selector']; |
|
1367 } |
|
1368 if ( false !== $root_style_key ) { |
|
1369 $style_nodes[ $root_style_key ]['selector'] = $options['root_selector']; |
|
1370 } |
|
1371 } |
714 |
1372 |
715 $stylesheet = ''; |
1373 $stylesheet = ''; |
716 |
1374 |
717 if ( in_array( 'variables', $types, true ) ) { |
1375 if ( in_array( 'variables', $types, true ) ) { |
718 $stylesheet .= $this->get_css_variables( $setting_nodes, $origins ); |
1376 $stylesheet .= $this->get_css_variables( $setting_nodes, $origins ); |
719 } |
1377 } |
720 |
1378 |
721 if ( in_array( 'styles', $types, true ) ) { |
1379 if ( in_array( 'styles', $types, true ) ) { |
|
1380 if ( false !== $root_style_key && empty( $options['skip_root_layout_styles'] ) ) { |
|
1381 $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ] ); |
|
1382 } |
722 $stylesheet .= $this->get_block_classes( $style_nodes ); |
1383 $stylesheet .= $this->get_block_classes( $style_nodes ); |
|
1384 } elseif ( in_array( 'base-layout-styles', $types, true ) ) { |
|
1385 $root_selector = static::ROOT_BLOCK_SELECTOR; |
|
1386 $columns_selector = '.wp-block-columns'; |
|
1387 $post_template_selector = '.wp-block-post-template'; |
|
1388 if ( ! empty( $options['scope'] ) ) { |
|
1389 $root_selector = static::scope_selector( $options['scope'], $root_selector ); |
|
1390 $columns_selector = static::scope_selector( $options['scope'], $columns_selector ); |
|
1391 $post_template_selector = static::scope_selector( $options['scope'], $post_template_selector ); |
|
1392 } |
|
1393 if ( ! empty( $options['root_selector'] ) ) { |
|
1394 $root_selector = $options['root_selector']; |
|
1395 } |
|
1396 /* |
|
1397 * Base layout styles are provided as part of `styles`, so only output separately if explicitly requested. |
|
1398 * For backwards compatibility, the Columns block is explicitly included, to support a different default gap value. |
|
1399 */ |
|
1400 $base_styles_nodes = array( |
|
1401 array( |
|
1402 'path' => array( 'styles' ), |
|
1403 'selector' => $root_selector, |
|
1404 ), |
|
1405 array( |
|
1406 'path' => array( 'styles', 'blocks', 'core/columns' ), |
|
1407 'selector' => $columns_selector, |
|
1408 'name' => 'core/columns', |
|
1409 ), |
|
1410 array( |
|
1411 'path' => array( 'styles', 'blocks', 'core/post-template' ), |
|
1412 'selector' => $post_template_selector, |
|
1413 'name' => 'core/post-template', |
|
1414 ), |
|
1415 ); |
|
1416 |
|
1417 foreach ( $base_styles_nodes as $base_style_node ) { |
|
1418 $stylesheet .= $this->get_layout_styles( $base_style_node, $types ); |
|
1419 } |
723 } |
1420 } |
724 |
1421 |
725 if ( in_array( 'presets', $types, true ) ) { |
1422 if ( in_array( 'presets', $types, true ) ) { |
726 $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); |
1423 $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); |
|
1424 } |
|
1425 |
|
1426 return $stylesheet; |
|
1427 } |
|
1428 |
|
1429 /** |
|
1430 * Processes the CSS, to apply nesting. |
|
1431 * |
|
1432 * @since 6.2.0 |
|
1433 * @since 6.6.0 Enforced 0-1-0 specificity for block custom CSS selectors. |
|
1434 * |
|
1435 * @param string $css The CSS to process. |
|
1436 * @param string $selector The selector to nest. |
|
1437 * @return string The processed CSS. |
|
1438 */ |
|
1439 protected function process_blocks_custom_css( $css, $selector ) { |
|
1440 $processed_css = ''; |
|
1441 |
|
1442 if ( empty( $css ) ) { |
|
1443 return $processed_css; |
|
1444 } |
|
1445 |
|
1446 // Split CSS nested rules. |
|
1447 $parts = explode( '&', $css ); |
|
1448 foreach ( $parts as $part ) { |
|
1449 if ( empty( $part ) ) { |
|
1450 continue; |
|
1451 } |
|
1452 $is_root_css = ( ! str_contains( $part, '{' ) ); |
|
1453 if ( $is_root_css ) { |
|
1454 // If the part doesn't contain braces, it applies to the root level. |
|
1455 $processed_css .= ':root :where(' . trim( $selector ) . '){' . trim( $part ) . '}'; |
|
1456 } else { |
|
1457 // If the part contains braces, it's a nested CSS rule. |
|
1458 $part = explode( '{', str_replace( '}', '', $part ) ); |
|
1459 if ( count( $part ) !== 2 ) { |
|
1460 continue; |
|
1461 } |
|
1462 $nested_selector = $part[0]; |
|
1463 $css_value = $part[1]; |
|
1464 |
|
1465 /* |
|
1466 * Handle pseudo elements such as ::before, ::after etc. Regex will also |
|
1467 * capture any leading combinator such as >, +, or ~, as well as spaces. |
|
1468 * This allows pseudo elements as descendants e.g. `.parent ::before`. |
|
1469 */ |
|
1470 $matches = array(); |
|
1471 $has_pseudo_element = preg_match( '/([>+~\s]*::[a-zA-Z-]+)/', $nested_selector, $matches ); |
|
1472 $pseudo_part = $has_pseudo_element ? $matches[1] : ''; |
|
1473 $nested_selector = $has_pseudo_element ? str_replace( $pseudo_part, '', $nested_selector ) : $nested_selector; |
|
1474 |
|
1475 // Finalize selector and re-append pseudo element if required. |
|
1476 $part_selector = str_starts_with( $nested_selector, ' ' ) |
|
1477 ? static::scope_selector( $selector, $nested_selector ) |
|
1478 : static::append_to_selector( $selector, $nested_selector ); |
|
1479 $final_selector = ":root :where($part_selector)$pseudo_part"; |
|
1480 |
|
1481 $processed_css .= $final_selector . '{' . trim( $css_value ) . '}'; |
|
1482 } |
|
1483 } |
|
1484 return $processed_css; |
|
1485 } |
|
1486 |
|
1487 /** |
|
1488 * Returns the global styles custom CSS. |
|
1489 * |
|
1490 * @since 6.2.0 |
|
1491 * |
|
1492 * @return string The global styles custom CSS. |
|
1493 */ |
|
1494 public function get_custom_css() { |
|
1495 // Add the global styles root CSS. |
|
1496 $stylesheet = isset( $this->theme_json['styles']['css'] ) ? $this->theme_json['styles']['css'] : ''; |
|
1497 |
|
1498 // Add the global styles block CSS. |
|
1499 if ( isset( $this->theme_json['styles']['blocks'] ) ) { |
|
1500 foreach ( $this->theme_json['styles']['blocks'] as $name => $node ) { |
|
1501 $custom_block_css = isset( $this->theme_json['styles']['blocks'][ $name ]['css'] ) |
|
1502 ? $this->theme_json['styles']['blocks'][ $name ]['css'] |
|
1503 : null; |
|
1504 if ( $custom_block_css ) { |
|
1505 $selector = static::$blocks_metadata[ $name ]['selector']; |
|
1506 $stylesheet .= $this->process_blocks_custom_css( $custom_block_css, $selector ); |
|
1507 } |
|
1508 } |
727 } |
1509 } |
728 |
1510 |
729 return $stylesheet; |
1511 return $stylesheet; |
730 } |
1512 } |
731 |
1513 |
802 |
1585 |
803 foreach ( $style_nodes as $metadata ) { |
1586 foreach ( $style_nodes as $metadata ) { |
804 if ( null === $metadata['selector'] ) { |
1587 if ( null === $metadata['selector'] ) { |
805 continue; |
1588 continue; |
806 } |
1589 } |
807 |
1590 $block_rules .= static::get_styles_for_block( $metadata ); |
808 $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); |
1591 } |
809 $selector = $metadata['selector']; |
1592 |
810 $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); |
1593 return $block_rules; |
811 $declarations = static::compute_style_properties( $node, $settings ); |
1594 } |
812 |
1595 |
813 // 1. Separate the ones who use the general selector |
1596 /** |
814 // and the ones who use the duotone selector. |
1597 * Gets the CSS layout rules for a particular block from theme.json layout definitions. |
815 $declarations_duotone = array(); |
1598 * |
816 foreach ( $declarations as $index => $declaration ) { |
1599 * @since 6.1.0 |
817 if ( 'filter' === $declaration['name'] ) { |
1600 * @since 6.3.0 Reduced specificity for layout margin rules. |
818 unset( $declarations[ $index ] ); |
1601 * @since 6.5.1 Only output rules referencing content and wide sizes when values exist. |
819 $declarations_duotone[] = $declaration; |
1602 * @since 6.5.3 Add types parameter to check if only base layout styles are needed. |
820 } |
1603 * @since 6.6.0 Updated layout style specificity to be compatible with overall 0-1-0 specificity in global styles. |
821 } |
1604 * |
822 |
1605 * @param array $block_metadata Metadata about the block to get styles for. |
823 /* |
1606 * @param array $types Optional. Types of styles to output. If empty, all styles will be output. |
824 * Reset default browser margin on the root body element. |
1607 * @return string Layout styles for the block. |
825 * This is set on the root selector **before** generating the ruleset |
1608 */ |
826 * from the `theme.json`. This is to ensure that if the `theme.json` declares |
1609 protected function get_layout_styles( $block_metadata, $types = array() ) { |
827 * `margin` in its `spacing` declaration for the `body` element then these |
1610 $block_rules = ''; |
828 * user-generated values take precedence in the CSS cascade. |
1611 $block_type = null; |
829 * @link https://github.com/WordPress/gutenberg/issues/36147. |
1612 |
830 */ |
1613 // Skip outputting layout styles if explicitly disabled. |
831 if ( static::ROOT_BLOCK_SELECTOR === $selector ) { |
1614 if ( current_theme_supports( 'disable-layout-styles' ) ) { |
832 $block_rules .= 'body { margin: 0; }'; |
1615 return $block_rules; |
833 } |
1616 } |
834 |
1617 |
835 // 2. Generate the rules that use the general selector. |
1618 if ( isset( $block_metadata['name'] ) ) { |
836 $block_rules .= static::to_ruleset( $selector, $declarations ); |
1619 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] ); |
837 |
1620 if ( ! block_has_support( $block_type, 'layout', false ) && ! block_has_support( $block_type, '__experimentalLayout', false ) ) { |
838 // 3. Generate the rules that use the duotone selector. |
1621 return $block_rules; |
839 if ( isset( $metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { |
1622 } |
840 $selector_duotone = static::scope_selector( $metadata['selector'], $metadata['duotone'] ); |
1623 } |
841 $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); |
1624 |
842 } |
1625 $selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : ''; |
843 |
1626 $has_block_gap_support = isset( $this->theme_json['settings']['spacing']['blockGap'] ); |
844 if ( static::ROOT_BLOCK_SELECTOR === $selector ) { |
1627 $has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support. |
845 $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; |
1628 $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); |
846 $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; |
1629 $layout_definitions = wp_get_layout_definitions(); |
847 $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; |
1630 $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\,\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors. |
848 |
1631 |
849 $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; |
1632 /* |
850 if ( $has_block_gap_support ) { |
1633 * Gap styles will only be output if the theme has block gap support, or supports a fallback gap. |
851 $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; |
1634 * Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value. |
852 $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; |
1635 */ |
853 } |
1636 if ( $has_block_gap_support || $has_fallback_gap_support ) { |
854 } |
1637 $block_gap_value = null; |
855 } |
1638 // Use a fallback gap value if block gap support is not available. |
856 |
1639 if ( ! $has_block_gap_support ) { |
|
1640 $block_gap_value = static::ROOT_BLOCK_SELECTOR === $selector ? '0.5em' : null; |
|
1641 if ( ! empty( $block_type ) ) { |
|
1642 $block_gap_value = isset( $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ) |
|
1643 ? $block_type->supports['spacing']['blockGap']['__experimentalDefault'] |
|
1644 : null; |
|
1645 } |
|
1646 } else { |
|
1647 $block_gap_value = static::get_property_value( $node, array( 'spacing', 'blockGap' ) ); |
|
1648 } |
|
1649 |
|
1650 // Support split row / column values and concatenate to a shorthand value. |
|
1651 if ( is_array( $block_gap_value ) ) { |
|
1652 if ( isset( $block_gap_value['top'] ) && isset( $block_gap_value['left'] ) ) { |
|
1653 $gap_row = static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) ); |
|
1654 $gap_column = static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) ); |
|
1655 $block_gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column; |
|
1656 } else { |
|
1657 // Skip outputting gap value if not all sides are provided. |
|
1658 $block_gap_value = null; |
|
1659 } |
|
1660 } |
|
1661 |
|
1662 // If the block should have custom gap, add the gap styles. |
|
1663 if ( null !== $block_gap_value && false !== $block_gap_value && '' !== $block_gap_value ) { |
|
1664 foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { |
|
1665 // Allow outputting fallback gap styles for flex and grid layout types when block gap support isn't available. |
|
1666 if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key && 'grid' !== $layout_definition_key ) { |
|
1667 continue; |
|
1668 } |
|
1669 |
|
1670 $class_name = isset( $layout_definition['className'] ) ? $layout_definition['className'] : false; |
|
1671 $spacing_rules = isset( $layout_definition['spacingStyles'] ) ? $layout_definition['spacingStyles'] : array(); |
|
1672 |
|
1673 if ( |
|
1674 ! empty( $class_name ) && |
|
1675 ! empty( $spacing_rules ) |
|
1676 ) { |
|
1677 foreach ( $spacing_rules as $spacing_rule ) { |
|
1678 $declarations = array(); |
|
1679 if ( |
|
1680 isset( $spacing_rule['selector'] ) && |
|
1681 preg_match( $layout_selector_pattern, $spacing_rule['selector'] ) && |
|
1682 ! empty( $spacing_rule['rules'] ) |
|
1683 ) { |
|
1684 // Iterate over each of the styling rules and substitute non-string values such as `null` with the real `blockGap` value. |
|
1685 foreach ( $spacing_rule['rules'] as $css_property => $css_value ) { |
|
1686 $current_css_value = is_string( $css_value ) ? $css_value : $block_gap_value; |
|
1687 if ( static::is_safe_css_declaration( $css_property, $current_css_value ) ) { |
|
1688 $declarations[] = array( |
|
1689 'name' => $css_property, |
|
1690 'value' => $current_css_value, |
|
1691 ); |
|
1692 } |
|
1693 } |
|
1694 |
|
1695 if ( ! $has_block_gap_support ) { |
|
1696 // For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles. |
|
1697 $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s%3$s)' : ':where(%1$s.%2$s%3$s)'; |
|
1698 $layout_selector = sprintf( |
|
1699 $format, |
|
1700 $selector, |
|
1701 $class_name, |
|
1702 $spacing_rule['selector'] |
|
1703 ); |
|
1704 } else { |
|
1705 $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':root :where(.%2$s)%3$s' : ':root :where(%1$s-%2$s)%3$s'; |
|
1706 $layout_selector = sprintf( |
|
1707 $format, |
|
1708 $selector, |
|
1709 $class_name, |
|
1710 $spacing_rule['selector'] |
|
1711 ); |
|
1712 } |
|
1713 $block_rules .= static::to_ruleset( $layout_selector, $declarations ); |
|
1714 } |
|
1715 } |
|
1716 } |
|
1717 } |
|
1718 } |
|
1719 } |
|
1720 |
|
1721 // Output base styles. |
|
1722 if ( |
|
1723 static::ROOT_BLOCK_SELECTOR === $selector |
|
1724 ) { |
|
1725 $valid_display_modes = array( 'block', 'flex', 'grid' ); |
|
1726 foreach ( $layout_definitions as $layout_definition ) { |
|
1727 $class_name = isset( $layout_definition['className'] ) ? $layout_definition['className'] : false; |
|
1728 $base_style_rules = isset( $layout_definition['baseStyles'] ) ? $layout_definition['baseStyles'] : array(); |
|
1729 |
|
1730 if ( |
|
1731 ! empty( $class_name ) && |
|
1732 is_array( $base_style_rules ) |
|
1733 ) { |
|
1734 // Output display mode. This requires special handling as `display` is not exposed in `safe_style_css_filter`. |
|
1735 if ( |
|
1736 ! empty( $layout_definition['displayMode'] ) && |
|
1737 is_string( $layout_definition['displayMode'] ) && |
|
1738 in_array( $layout_definition['displayMode'], $valid_display_modes, true ) |
|
1739 ) { |
|
1740 $layout_selector = sprintf( |
|
1741 '%s .%s', |
|
1742 $selector, |
|
1743 $class_name |
|
1744 ); |
|
1745 $block_rules .= static::to_ruleset( |
|
1746 $layout_selector, |
|
1747 array( |
|
1748 array( |
|
1749 'name' => 'display', |
|
1750 'value' => $layout_definition['displayMode'], |
|
1751 ), |
|
1752 ) |
|
1753 ); |
|
1754 } |
|
1755 |
|
1756 foreach ( $base_style_rules as $base_style_rule ) { |
|
1757 $declarations = array(); |
|
1758 |
|
1759 // Skip outputting base styles for flow and constrained layout types if theme doesn't support theme.json. The 'base-layout-styles' type flags this. |
|
1760 if ( in_array( 'base-layout-styles', $types, true ) && ( 'default' === $layout_definition['name'] || 'constrained' === $layout_definition['name'] ) ) { |
|
1761 continue; |
|
1762 } |
|
1763 |
|
1764 if ( |
|
1765 isset( $base_style_rule['selector'] ) && |
|
1766 preg_match( $layout_selector_pattern, $base_style_rule['selector'] ) && |
|
1767 ! empty( $base_style_rule['rules'] ) |
|
1768 ) { |
|
1769 foreach ( $base_style_rule['rules'] as $css_property => $css_value ) { |
|
1770 // Skip rules that reference content size or wide size if they are not defined in the theme.json. |
|
1771 if ( |
|
1772 is_string( $css_value ) && |
|
1773 ( str_contains( $css_value, '--global--content-size' ) || str_contains( $css_value, '--global--wide-size' ) ) && |
|
1774 ! isset( $this->theme_json['settings']['layout']['contentSize'] ) && |
|
1775 ! isset( $this->theme_json['settings']['layout']['wideSize'] ) |
|
1776 ) { |
|
1777 continue; |
|
1778 } |
|
1779 |
|
1780 if ( static::is_safe_css_declaration( $css_property, $css_value ) ) { |
|
1781 $declarations[] = array( |
|
1782 'name' => $css_property, |
|
1783 'value' => $css_value, |
|
1784 ); |
|
1785 } |
|
1786 } |
|
1787 |
|
1788 $layout_selector = sprintf( |
|
1789 '.%s%s', |
|
1790 $class_name, |
|
1791 $base_style_rule['selector'] |
|
1792 ); |
|
1793 $block_rules .= static::to_ruleset( $layout_selector, $declarations ); |
|
1794 } |
|
1795 } |
|
1796 } |
|
1797 } |
|
1798 } |
857 return $block_rules; |
1799 return $block_rules; |
858 } |
1800 } |
859 |
1801 |
860 /** |
1802 /** |
861 * Creates new rulesets as classes for each preset value such as: |
1803 * Creates new rulesets as classes for each preset value such as: |
965 |
1911 |
966 return $selector . '{' . $declaration_block . '}'; |
1912 return $selector . '{' . $declaration_block . '}'; |
967 } |
1913 } |
968 |
1914 |
969 /** |
1915 /** |
970 * Function that appends a sub-selector to a existing one. |
1916 * Given a settings array, returns the generated rulesets |
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 */ |
|
982 protected static function append_to_selector( $selector, $to_append ) { |
|
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. |
1917 * for the preset classes. |
995 * |
1918 * |
996 * @since 5.8.0 |
1919 * @since 5.8.0 |
997 * @since 5.9.0 Added the `$origins` parameter. |
1920 * @since 5.9.0 Added the `$origins` parameter. |
998 * |
1921 * @since 6.6.0 Added check for root CSS properties selector. |
999 * @param array $settings Settings to process. |
1922 * |
1000 * @param string $selector Selector wrapping the classes. |
1923 * @param array $settings Settings to process. |
1001 * @param array $origins List of origins to process. |
1924 * @param string $selector Selector wrapping the classes. |
|
1925 * @param string[] $origins List of origins to process. |
1002 * @return string The result of processing the presets. |
1926 * @return string The result of processing the presets. |
1003 */ |
1927 */ |
1004 protected static function compute_preset_classes( $settings, $selector, $origins ) { |
1928 protected static function compute_preset_classes( $settings, $selector, $origins ) { |
1005 if ( static::ROOT_BLOCK_SELECTOR === $selector ) { |
1929 if ( static::ROOT_BLOCK_SELECTOR === $selector || static::ROOT_CSS_PROPERTIES_SELECTOR === $selector ) { |
1006 // Classes at the global level do not need any CSS prefixed, |
1930 /* |
1007 // and we don't want to increase its specificity. |
1931 * Classes at the global level do not need any CSS prefixed, |
|
1932 * and we don't want to increase its specificity. |
|
1933 */ |
1008 $selector = ''; |
1934 $selector = ''; |
1009 } |
1935 } |
1010 |
1936 |
1011 $stylesheet = ''; |
1937 $stylesheet = ''; |
1012 foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
1938 foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
|
1939 if ( empty( $preset_metadata['classes'] ) ) { |
|
1940 continue; |
|
1941 } |
1013 $slugs = static::get_settings_slugs( $settings, $preset_metadata, $origins ); |
1942 $slugs = static::get_settings_slugs( $settings, $preset_metadata, $origins ); |
1014 foreach ( $preset_metadata['classes'] as $class => $property ) { |
1943 foreach ( $preset_metadata['classes'] as $class => $property ) { |
1015 foreach ( $slugs as $slug ) { |
1944 foreach ( $slugs as $slug ) { |
1016 $css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ); |
1945 $css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ); |
1017 $class_name = static::replace_slug_in_string( $class, $slug ); |
1946 $class_name = static::replace_slug_in_string( $class, $slug ); |
1018 $stylesheet .= static::to_ruleset( |
1947 |
1019 static::append_to_selector( $selector, $class_name ), |
1948 // $selector is often empty, so we can save ourselves the `append_to_selector()` call then. |
|
1949 $new_selector = '' === $selector ? $class_name : static::append_to_selector( $selector, $class_name ); |
|
1950 $stylesheet .= static::to_ruleset( |
|
1951 $new_selector, |
1020 array( |
1952 array( |
1021 array( |
1953 array( |
1022 'name' => $property, |
1954 'name' => $property, |
1023 'value' => 'var(' . $css_var . ') !important', |
1955 'value' => 'var(' . $css_var . ') !important', |
1024 ), |
1956 ), |
1304 * 'value' => 'property_value, |
2287 * 'value' => 'property_value, |
1305 * ) |
2288 * ) |
1306 * |
2289 * |
1307 * @since 5.8.0 |
2290 * @since 5.8.0 |
1308 * @since 5.9.0 Added the `$settings` and `$properties` parameters. |
2291 * @since 5.9.0 Added the `$settings` and `$properties` parameters. |
1309 * |
2292 * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters. |
1310 * @param array $styles Styles to process. |
2293 * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set. |
1311 * @param array $settings Theme settings. |
2294 * @since 6.6.0 Pass current theme JSON settings to wp_get_typography_font_size_value(), and process background properties. |
1312 * @param array $properties Properties metadata. |
2295 * |
1313 * @return array Returns the modified $declarations. |
2296 * @param array $styles Styles to process. |
1314 */ |
2297 * @param array $settings Theme settings. |
1315 protected static function compute_style_properties( $styles, $settings = array(), $properties = null ) { |
2298 * @param array $properties Properties metadata. |
|
2299 * @param array $theme_json Theme JSON array. |
|
2300 * @param string $selector The style block selector. |
|
2301 * @param boolean $use_root_padding Whether to add custom properties at root level. |
|
2302 * @return array Returns the modified $declarations. |
|
2303 */ |
|
2304 protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) { |
1316 if ( null === $properties ) { |
2305 if ( null === $properties ) { |
1317 $properties = static::PROPERTIES_METADATA; |
2306 $properties = static::PROPERTIES_METADATA; |
1318 } |
2307 } |
1319 |
2308 |
1320 $declarations = array(); |
2309 $declarations = array(); |
1321 if ( empty( $styles ) ) { |
2310 if ( empty( $styles ) ) { |
1322 return $declarations; |
2311 return $declarations; |
1323 } |
2312 } |
1324 |
2313 |
|
2314 $root_variable_duplicates = array(); |
|
2315 |
1325 foreach ( $properties as $css_property => $value_path ) { |
2316 foreach ( $properties as $css_property => $value_path ) { |
1326 $value = static::get_property_value( $styles, $value_path ); |
2317 $value = static::get_property_value( $styles, $value_path, $theme_json ); |
1327 |
2318 |
1328 // Look up protected properties, keyed by value path. |
2319 if ( str_starts_with( $css_property, '--wp--style--root--' ) && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) { |
1329 // Skip protected properties that are explicitly set to `null`. |
2320 continue; |
|
2321 } |
|
2322 /* |
|
2323 * Root-level padding styles don't currently support strings with CSS shorthand values. |
|
2324 * This may change: https://github.com/WordPress/gutenberg/issues/40132. |
|
2325 */ |
|
2326 if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) { |
|
2327 continue; |
|
2328 } |
|
2329 |
|
2330 if ( str_starts_with( $css_property, '--wp--style--root--' ) && $use_root_padding ) { |
|
2331 $root_variable_duplicates[] = substr( $css_property, strlen( '--wp--style--root--' ) ); |
|
2332 } |
|
2333 |
|
2334 /* |
|
2335 * Look up protected properties, keyed by value path. |
|
2336 * Skip protected properties that are explicitly set to `null`. |
|
2337 */ |
1330 if ( is_array( $value_path ) ) { |
2338 if ( is_array( $value_path ) ) { |
1331 $path_string = implode( '.', $value_path ); |
2339 $path_string = implode( '.', $value_path ); |
1332 if ( |
2340 if ( |
1333 array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) && |
2341 isset( static::PROTECTED_PROPERTIES[ $path_string ] ) && |
1334 _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null |
2342 _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null |
1335 ) { |
2343 ) { |
1336 continue; |
2344 continue; |
1337 } |
2345 } |
|
2346 } |
|
2347 |
|
2348 // Processes background styles. |
|
2349 if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) { |
|
2350 $background_styles = wp_style_engine_get_styles( array( 'background' => $styles['background'] ) ); |
|
2351 $value = isset( $background_styles['declarations'][ $css_property ] ) ? $background_styles['declarations'][ $css_property ] : $value; |
1338 } |
2352 } |
1339 |
2353 |
1340 // Skip if empty and not "0" or value represents array of longhand values. |
2354 // Skip if empty and not "0" or value represents array of longhand values. |
1341 $has_missing_value = empty( $value ) && ! is_numeric( $value ); |
2355 $has_missing_value = empty( $value ) && ! is_numeric( $value ); |
1342 if ( $has_missing_value || is_array( $value ) ) { |
2356 if ( $has_missing_value || is_array( $value ) ) { |
1343 continue; |
2357 continue; |
1344 } |
2358 } |
1345 |
2359 |
|
2360 // Calculates fluid typography rules where available. |
|
2361 if ( 'font-size' === $css_property ) { |
|
2362 /* |
|
2363 * wp_get_typography_font_size_value() will check |
|
2364 * if fluid typography has been activated and also |
|
2365 * whether the incoming value can be converted to a fluid value. |
|
2366 * Values that already have a clamp() function will not pass the test, |
|
2367 * and therefore the original $value will be returned. |
|
2368 * Pass the current theme_json settings to override any global settings. |
|
2369 */ |
|
2370 $value = wp_get_typography_font_size_value( array( 'size' => $value ), $settings ); |
|
2371 } |
|
2372 |
|
2373 if ( 'aspect-ratio' === $css_property ) { |
|
2374 // For aspect ratio to work, other dimensions rules must be unset. |
|
2375 // This ensures that a fixed height does not override the aspect ratio. |
|
2376 $declarations[] = array( |
|
2377 'name' => 'min-height', |
|
2378 'value' => 'unset', |
|
2379 ); |
|
2380 } |
|
2381 |
1346 $declarations[] = array( |
2382 $declarations[] = array( |
1347 'name' => $css_property, |
2383 'name' => $css_property, |
1348 'value' => $value, |
2384 'value' => $value, |
1349 ); |
2385 ); |
1350 } |
2386 } |
1351 |
2387 |
|
2388 // If a variable value is added to the root, the corresponding property should be removed. |
|
2389 foreach ( $root_variable_duplicates as $duplicate ) { |
|
2390 $discard = array_search( $duplicate, array_column( $declarations, 'name' ), true ); |
|
2391 if ( is_numeric( $discard ) ) { |
|
2392 array_splice( $declarations, $discard, 1 ); |
|
2393 } |
|
2394 } |
|
2395 |
1352 return $declarations; |
2396 return $declarations; |
1353 } |
2397 } |
1354 |
2398 |
1355 /** |
2399 /** |
1356 * Returns the style property for the given path. |
2400 * Returns the style property for the given path. |
1357 * |
2401 * |
1358 * It also converts CSS Custom Property stored as |
2402 * It also converts references to a path to the value |
1359 * "var:preset|color|secondary" to the form |
2403 * stored at that location, e.g. |
1360 * "--wp--preset--color--secondary". |
2404 * { "ref": "style.color.background" } => "#fff". |
1361 * |
2405 * |
1362 * @since 5.8.0 |
2406 * @since 5.8.0 |
1363 * @since 5.9.0 Added support for values of array type, which are returned as is. |
2407 * @since 5.9.0 Added support for values of array type, which are returned as is. |
|
2408 * @since 6.1.0 Added the `$theme_json` parameter. |
|
2409 * @since 6.3.0 It no longer converts the internal format "var:preset|color|secondary" |
|
2410 * to the standard form "--wp--preset--color--secondary". |
|
2411 * This is already done by the sanitize method, |
|
2412 * so every property will be in the standard form. |
1364 * |
2413 * |
1365 * @param array $styles Styles subtree. |
2414 * @param array $styles Styles subtree. |
1366 * @param array $path Which property to process. |
2415 * @param array $path Which property to process. |
|
2416 * @param array $theme_json Theme JSON array. |
1367 * @return string|array Style property value. |
2417 * @return string|array Style property value. |
1368 */ |
2418 */ |
1369 protected static function get_property_value( $styles, $path ) { |
2419 protected static function get_property_value( $styles, $path, $theme_json = null ) { |
1370 $value = _wp_array_get( $styles, $path, '' ); |
2420 $value = _wp_array_get( $styles, $path, '' ); |
1371 |
2421 |
1372 if ( '' === $value || is_array( $value ) ) { |
2422 if ( '' === $value || null === $value ) { |
|
2423 // No need to process the value further. |
|
2424 return ''; |
|
2425 } |
|
2426 |
|
2427 /* |
|
2428 * This converts references to a path to the value at that path |
|
2429 * where the values is an array with a "ref" key, pointing to a path. |
|
2430 * For example: { "ref": "style.color.background" } => "#fff". |
|
2431 */ |
|
2432 if ( is_array( $value ) && isset( $value['ref'] ) ) { |
|
2433 $value_path = explode( '.', $value['ref'] ); |
|
2434 $ref_value = _wp_array_get( $theme_json, $value_path ); |
|
2435 // Only use the ref value if we find anything. |
|
2436 if ( ! empty( $ref_value ) && is_string( $ref_value ) ) { |
|
2437 $value = $ref_value; |
|
2438 } |
|
2439 |
|
2440 if ( is_array( $ref_value ) && isset( $ref_value['ref'] ) ) { |
|
2441 $path_string = json_encode( $path ); |
|
2442 $ref_value_string = json_encode( $ref_value ); |
|
2443 _doing_it_wrong( |
|
2444 'get_property_value', |
|
2445 sprintf( |
|
2446 /* translators: 1: theme.json, 2: Value name, 3: Value path, 4: Another value name. */ |
|
2447 __( 'Your %1$s file uses a dynamic value (%2$s) for the path at %3$s. However, the value at %3$s is also a dynamic value (pointing to %4$s) and pointing to another dynamic value is not supported. Please update %3$s to point directly to %4$s.' ), |
|
2448 'theme.json', |
|
2449 $ref_value_string, |
|
2450 $path_string, |
|
2451 $ref_value['ref'] |
|
2452 ), |
|
2453 '6.1.0' |
|
2454 ); |
|
2455 } |
|
2456 } |
|
2457 |
|
2458 if ( is_array( $value ) ) { |
1373 return $value; |
2459 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 } |
2460 } |
1388 |
2461 |
1389 return $value; |
2462 return $value; |
1390 } |
2463 } |
1391 |
2464 |
1474 'path' => array( 'styles' ), |
2553 'path' => array( 'styles' ), |
1475 'selector' => static::ROOT_BLOCK_SELECTOR, |
2554 'selector' => static::ROOT_BLOCK_SELECTOR, |
1476 ); |
2555 ); |
1477 |
2556 |
1478 if ( isset( $theme_json['styles']['elements'] ) ) { |
2557 if ( isset( $theme_json['styles']['elements'] ) ) { |
1479 foreach ( $theme_json['styles']['elements'] as $element => $node ) { |
2558 foreach ( self::ELEMENTS as $element => $selector ) { |
|
2559 if ( ! isset( $theme_json['styles']['elements'][ $element ] ) ) { |
|
2560 continue; |
|
2561 } |
1480 $nodes[] = array( |
2562 $nodes[] = array( |
1481 'path' => array( 'styles', 'elements', $element ), |
2563 'path' => array( 'styles', 'elements', $element ), |
1482 'selector' => static::ELEMENTS[ $element ], |
2564 'selector' => static::ELEMENTS[ $element ], |
1483 ); |
2565 ); |
1484 } |
2566 |
|
2567 // Handle any pseudo selectors for the element. |
|
2568 if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) { |
|
2569 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { |
|
2570 |
|
2571 if ( isset( $theme_json['styles']['elements'][ $element ][ $pseudo_selector ] ) ) { |
|
2572 $nodes[] = array( |
|
2573 'path' => array( 'styles', 'elements', $element ), |
|
2574 'selector' => static::append_to_selector( static::ELEMENTS[ $element ], $pseudo_selector ), |
|
2575 ); |
|
2576 } |
|
2577 } |
|
2578 } |
|
2579 } |
|
2580 } |
|
2581 |
|
2582 // Blocks. |
|
2583 if ( ! isset( $theme_json['styles']['blocks'] ) ) { |
|
2584 return $nodes; |
|
2585 } |
|
2586 |
|
2587 $block_nodes = static::get_block_nodes( $theme_json, $selectors, $options ); |
|
2588 foreach ( $block_nodes as $block_node ) { |
|
2589 $nodes[] = $block_node; |
|
2590 } |
|
2591 |
|
2592 /** |
|
2593 * Filters the list of style nodes with metadata. |
|
2594 * |
|
2595 * This allows for things like loading block CSS independently. |
|
2596 * |
|
2597 * @since 6.1.0 |
|
2598 * |
|
2599 * @param array $nodes Style nodes with metadata. |
|
2600 */ |
|
2601 return apply_filters( 'wp_theme_json_get_style_nodes', $nodes ); |
|
2602 } |
|
2603 |
|
2604 /** |
|
2605 * A public helper to get the block nodes from a theme.json file. |
|
2606 * |
|
2607 * @since 6.1.0 |
|
2608 * |
|
2609 * @return array The block nodes in theme.json. |
|
2610 */ |
|
2611 public function get_styles_block_nodes() { |
|
2612 return static::get_block_nodes( $this->theme_json ); |
|
2613 } |
|
2614 |
|
2615 /** |
|
2616 * Returns a filtered declarations array if there is a separator block with only a background |
|
2617 * style defined in theme.json by adding a color attribute to reflect the changes in the front. |
|
2618 * |
|
2619 * @since 6.1.1 |
|
2620 * |
|
2621 * @param array $declarations List of declarations. |
|
2622 * @return array $declarations List of declarations filtered. |
|
2623 */ |
|
2624 private static function update_separator_declarations( $declarations ) { |
|
2625 $background_color = ''; |
|
2626 $border_color_matches = false; |
|
2627 $text_color_matches = false; |
|
2628 |
|
2629 foreach ( $declarations as $declaration ) { |
|
2630 if ( 'background-color' === $declaration['name'] && ! $background_color && isset( $declaration['value'] ) ) { |
|
2631 $background_color = $declaration['value']; |
|
2632 } elseif ( 'border-color' === $declaration['name'] ) { |
|
2633 $border_color_matches = true; |
|
2634 } elseif ( 'color' === $declaration['name'] ) { |
|
2635 $text_color_matches = true; |
|
2636 } |
|
2637 |
|
2638 if ( $background_color && $border_color_matches && $text_color_matches ) { |
|
2639 break; |
|
2640 } |
|
2641 } |
|
2642 |
|
2643 if ( $background_color && ! $border_color_matches && ! $text_color_matches ) { |
|
2644 $declarations[] = array( |
|
2645 'name' => 'color', |
|
2646 'value' => $background_color, |
|
2647 ); |
|
2648 } |
|
2649 |
|
2650 return $declarations; |
|
2651 } |
|
2652 |
|
2653 /** |
|
2654 * An internal method to get the block nodes from a theme.json file. |
|
2655 * |
|
2656 * @since 6.1.0 |
|
2657 * @since 6.3.0 Refactored and stabilized selectors API. |
|
2658 * @since 6.6.0 Added optional selectors and options for generating block nodes. |
|
2659 * |
|
2660 * @param array $theme_json The theme.json converted to an array. |
|
2661 * @param array $selectors Optional list of selectors per block. |
|
2662 * @param array $options { |
|
2663 * Optional. An array of options for now used for internal purposes only (may change without notice). |
|
2664 * |
|
2665 * @type bool $include_block_style_variations Includes nodes for block style variations. Default false. |
|
2666 * } |
|
2667 * @return array The block nodes in theme.json. |
|
2668 */ |
|
2669 private static function get_block_nodes( $theme_json, $selectors = array(), $options = array() ) { |
|
2670 $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors; |
|
2671 $nodes = array(); |
|
2672 if ( ! isset( $theme_json['styles'] ) ) { |
|
2673 return $nodes; |
1485 } |
2674 } |
1486 |
2675 |
1487 // Blocks. |
2676 // Blocks. |
1488 if ( ! isset( $theme_json['styles']['blocks'] ) ) { |
2677 if ( ! isset( $theme_json['styles']['blocks'] ) ) { |
1489 return $nodes; |
2678 return $nodes; |
1498 $duotone_selector = null; |
2687 $duotone_selector = null; |
1499 if ( isset( $selectors[ $name ]['duotone'] ) ) { |
2688 if ( isset( $selectors[ $name ]['duotone'] ) ) { |
1500 $duotone_selector = $selectors[ $name ]['duotone']; |
2689 $duotone_selector = $selectors[ $name ]['duotone']; |
1501 } |
2690 } |
1502 |
2691 |
|
2692 $feature_selectors = null; |
|
2693 if ( isset( $selectors[ $name ]['selectors'] ) ) { |
|
2694 $feature_selectors = $selectors[ $name ]['selectors']; |
|
2695 } |
|
2696 |
|
2697 $variation_selectors = array(); |
|
2698 $include_variations = $options['include_block_style_variations'] ?? false; |
|
2699 if ( $include_variations && isset( $node['variations'] ) ) { |
|
2700 foreach ( $node['variations'] as $variation => $node ) { |
|
2701 $variation_selectors[] = array( |
|
2702 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), |
|
2703 'selector' => $selectors[ $name ]['styleVariations'][ $variation ], |
|
2704 ); |
|
2705 } |
|
2706 } |
|
2707 |
1503 $nodes[] = array( |
2708 $nodes[] = array( |
1504 'path' => array( 'styles', 'blocks', $name ), |
2709 'name' => $name, |
1505 'selector' => $selector, |
2710 'path' => array( 'styles', 'blocks', $name ), |
1506 'duotone' => $duotone_selector, |
2711 'selector' => $selector, |
|
2712 'selectors' => $feature_selectors, |
|
2713 'duotone' => $duotone_selector, |
|
2714 'features' => $feature_selectors, |
|
2715 'variations' => $variation_selectors, |
1507 ); |
2716 ); |
1508 |
2717 |
1509 if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { |
2718 if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { |
1510 foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { |
2719 foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { |
1511 $nodes[] = array( |
2720 $nodes[] = array( |
1512 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), |
2721 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), |
1513 'selector' => $selectors[ $name ]['elements'][ $element ], |
2722 'selector' => $selectors[ $name ]['elements'][ $element ], |
1514 ); |
2723 ); |
|
2724 |
|
2725 // Handle any pseudo selectors for the element. |
|
2726 if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) { |
|
2727 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { |
|
2728 if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) { |
|
2729 $nodes[] = array( |
|
2730 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), |
|
2731 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ), |
|
2732 ); |
|
2733 } |
|
2734 } |
|
2735 } |
1515 } |
2736 } |
1516 } |
2737 } |
1517 } |
2738 } |
1518 |
2739 |
1519 return $nodes; |
2740 return $nodes; |
1520 } |
2741 } |
1521 |
2742 |
1522 /** |
2743 /** |
|
2744 * Gets the CSS rules for a particular block from theme.json. |
|
2745 * |
|
2746 * @since 6.1.0 |
|
2747 * @since 6.6.0 Setting a min-height of HTML when root styles have a background gradient or image. |
|
2748 * Updated general global styles specificity to 0-1-0. |
|
2749 * Fixed custom CSS output in block style variations. |
|
2750 * |
|
2751 * @param array $block_metadata Metadata about the block to get styles for. |
|
2752 * |
|
2753 * @return string Styles for the block. |
|
2754 */ |
|
2755 public function get_styles_for_block( $block_metadata ) { |
|
2756 $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); |
|
2757 $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; |
|
2758 $selector = $block_metadata['selector']; |
|
2759 $settings = isset( $this->theme_json['settings'] ) ? $this->theme_json['settings'] : array(); |
|
2760 $feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node ); |
|
2761 $is_root_selector = static::ROOT_BLOCK_SELECTOR === $selector; |
|
2762 |
|
2763 // If there are style variations, generate the declarations for them, including any feature selectors the block may have. |
|
2764 $style_variation_declarations = array(); |
|
2765 $style_variation_custom_css = array(); |
|
2766 if ( ! empty( $block_metadata['variations'] ) ) { |
|
2767 foreach ( $block_metadata['variations'] as $style_variation ) { |
|
2768 $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() ); |
|
2769 $clean_style_variation_selector = trim( $style_variation['selector'] ); |
|
2770 |
|
2771 // Generate any feature/subfeature style declarations for the current style variation. |
|
2772 $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node ); |
|
2773 |
|
2774 // Combine selectors with style variation's selector and add to overall style variation declarations. |
|
2775 foreach ( $variation_declarations as $current_selector => $new_declarations ) { |
|
2776 // If current selector includes block classname, remove it but leave the whitespace in. |
|
2777 $shortened_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $current_selector ); |
|
2778 |
|
2779 // Prepend the variation selector to the current selector. |
|
2780 $split_selectors = explode( ',', $shortened_selector ); |
|
2781 $updated_selectors = array_map( |
|
2782 static function ( $split_selector ) use ( $clean_style_variation_selector ) { |
|
2783 return $clean_style_variation_selector . $split_selector; |
|
2784 }, |
|
2785 $split_selectors |
|
2786 ); |
|
2787 $combined_selectors = implode( ',', $updated_selectors ); |
|
2788 |
|
2789 // Add the new declarations to the overall results under the modified selector. |
|
2790 $style_variation_declarations[ $combined_selectors ] = $new_declarations; |
|
2791 } |
|
2792 |
|
2793 // Compute declarations for remaining styles not covered by feature level selectors. |
|
2794 $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); |
|
2795 // Store custom CSS for the style variation. |
|
2796 if ( isset( $style_variation_node['css'] ) ) { |
|
2797 $style_variation_custom_css[ $style_variation['selector'] ] = $this->process_blocks_custom_css( $style_variation_node['css'], $style_variation['selector'] ); |
|
2798 } |
|
2799 } |
|
2800 } |
|
2801 /* |
|
2802 * Get a reference to element name from path. |
|
2803 * $block_metadata['path'] = array( 'styles','elements','link' ); |
|
2804 * Make sure that $block_metadata['path'] describes an element node, like [ 'styles', 'element', 'link' ]. |
|
2805 * Skip non-element paths like just ['styles']. |
|
2806 */ |
|
2807 $is_processing_element = in_array( 'elements', $block_metadata['path'], true ); |
|
2808 |
|
2809 $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null; |
|
2810 |
|
2811 $element_pseudo_allowed = array(); |
|
2812 |
|
2813 if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) ) { |
|
2814 $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ]; |
|
2815 } |
|
2816 |
|
2817 /* |
|
2818 * Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover"). |
|
2819 * This also resets the array keys. |
|
2820 */ |
|
2821 $pseudo_matches = array_values( |
|
2822 array_filter( |
|
2823 $element_pseudo_allowed, |
|
2824 static function ( $pseudo_selector ) use ( $selector ) { |
|
2825 return str_contains( $selector, $pseudo_selector ); |
|
2826 } |
|
2827 ) |
|
2828 ); |
|
2829 |
|
2830 $pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null; |
|
2831 |
|
2832 /* |
|
2833 * If the current selector is a pseudo selector that's defined in the allow list for the current |
|
2834 * element then compute the style properties for it. |
|
2835 * Otherwise just compute the styles for the default selector as normal. |
|
2836 */ |
|
2837 if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && |
|
2838 isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) |
|
2839 && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) |
|
2840 ) { |
|
2841 $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding ); |
|
2842 } else { |
|
2843 $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding ); |
|
2844 } |
|
2845 |
|
2846 $block_rules = ''; |
|
2847 |
|
2848 /* |
|
2849 * 1. Bespoke declaration modifiers: |
|
2850 * - 'filter': Separate the declarations that use the general selector |
|
2851 * from the ones using the duotone selector. |
|
2852 * - 'background|background-image': set the html min-height to 100% |
|
2853 * to ensure the background covers the entire viewport. |
|
2854 */ |
|
2855 $declarations_duotone = array(); |
|
2856 $should_set_root_min_height = false; |
|
2857 |
|
2858 foreach ( $declarations as $index => $declaration ) { |
|
2859 if ( 'filter' === $declaration['name'] ) { |
|
2860 /* |
|
2861 * 'unset' filters happen when a filter is unset |
|
2862 * in the site-editor UI. Because the 'unset' value |
|
2863 * in the user origin overrides the value in the |
|
2864 * theme origin, we can skip rendering anything |
|
2865 * here as no filter needs to be applied anymore. |
|
2866 * So only add declarations to with values other |
|
2867 * than 'unset'. |
|
2868 */ |
|
2869 if ( 'unset' !== $declaration['value'] ) { |
|
2870 $declarations_duotone[] = $declaration; |
|
2871 } |
|
2872 unset( $declarations[ $index ] ); |
|
2873 } |
|
2874 |
|
2875 if ( $is_root_selector && ( 'background-image' === $declaration['name'] || 'background' === $declaration['name'] ) ) { |
|
2876 $should_set_root_min_height = true; |
|
2877 } |
|
2878 } |
|
2879 |
|
2880 /* |
|
2881 * If root styles has a background-image or a background (gradient) set, |
|
2882 * set the min-height to '100%'. Minus `--wp-admin--admin-bar--height` for logged-in view. |
|
2883 * Setting the CSS rule on the HTML tag ensures background gradients and images behave similarly, |
|
2884 * and matches the behavior of the site editor. |
|
2885 */ |
|
2886 if ( $should_set_root_min_height ) { |
|
2887 $block_rules .= static::to_ruleset( |
|
2888 'html', |
|
2889 array( |
|
2890 array( |
|
2891 'name' => 'min-height', |
|
2892 'value' => 'calc(100% - var(--wp-admin--admin-bar--height, 0px))', |
|
2893 ), |
|
2894 ) |
|
2895 ); |
|
2896 } |
|
2897 |
|
2898 // Update declarations if there are separators with only background color defined. |
|
2899 if ( '.wp-block-separator' === $selector ) { |
|
2900 $declarations = static::update_separator_declarations( $declarations ); |
|
2901 } |
|
2902 |
|
2903 /* |
|
2904 * Root selector (body) styles should not be wrapped in `:root where()` to keep |
|
2905 * specificity at (0,0,1) and maintain backwards compatibility. |
|
2906 * |
|
2907 * Top-level element styles using element-only specificity selectors should |
|
2908 * not get wrapped in `:root :where()` to maintain backwards compatibility. |
|
2909 * |
|
2910 * Pseudo classes, e.g. :hover, :focus etc., are a class-level selector so |
|
2911 * still need to be wrapped in `:root :where` to cap specificity for nested |
|
2912 * variations etc. Pseudo selectors won't match the ELEMENTS selector exactly. |
|
2913 */ |
|
2914 $element_only_selector = $is_root_selector || ( |
|
2915 $current_element && |
|
2916 isset( static::ELEMENTS[ $current_element ] ) && |
|
2917 // buttons, captions etc. still need `:root :where()` as they are class based selectors. |
|
2918 ! isset( static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $current_element ] ) && |
|
2919 static::ELEMENTS[ $current_element ] === $selector |
|
2920 ); |
|
2921 |
|
2922 // 2. Generate and append the rules that use the general selector. |
|
2923 $general_selector = $element_only_selector ? $selector : ":root :where($selector)"; |
|
2924 $block_rules .= static::to_ruleset( $general_selector, $declarations ); |
|
2925 |
|
2926 // 3. Generate and append the rules that use the duotone selector. |
|
2927 if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { |
|
2928 $block_rules .= static::to_ruleset( $block_metadata['duotone'], $declarations_duotone ); |
|
2929 } |
|
2930 |
|
2931 // 4. Generate Layout block gap styles. |
|
2932 if ( |
|
2933 ! $is_root_selector && |
|
2934 ! empty( $block_metadata['name'] ) |
|
2935 ) { |
|
2936 $block_rules .= $this->get_layout_styles( $block_metadata ); |
|
2937 } |
|
2938 |
|
2939 // 5. Generate and append the feature level rulesets. |
|
2940 foreach ( $feature_declarations as $feature_selector => $individual_feature_declarations ) { |
|
2941 $block_rules .= static::to_ruleset( ":root :where($feature_selector)", $individual_feature_declarations ); |
|
2942 } |
|
2943 |
|
2944 // 6. Generate and append the style variation rulesets. |
|
2945 foreach ( $style_variation_declarations as $style_variation_selector => $individual_style_variation_declarations ) { |
|
2946 $block_rules .= static::to_ruleset( ":root :where($style_variation_selector)", $individual_style_variation_declarations ); |
|
2947 if ( isset( $style_variation_custom_css[ $style_variation_selector ] ) ) { |
|
2948 $block_rules .= $style_variation_custom_css[ $style_variation_selector ]; |
|
2949 } |
|
2950 } |
|
2951 |
|
2952 // 7. Generate and append any custom CSS rules pertaining to nested block style variations. |
|
2953 if ( isset( $node['css'] ) && ! $is_root_selector ) { |
|
2954 $block_rules .= $this->process_blocks_custom_css( $node['css'], $selector ); |
|
2955 } |
|
2956 |
|
2957 return $block_rules; |
|
2958 } |
|
2959 |
|
2960 /** |
|
2961 * Outputs the CSS for layout rules on the root. |
|
2962 * |
|
2963 * @since 6.1.0 |
|
2964 * @since 6.6.0 Use `ROOT_CSS_PROPERTIES_SELECTOR` for CSS custom properties and improved consistency of root padding rules. |
|
2965 * Updated specificity of body margin reset and first/last child selectors. |
|
2966 * |
|
2967 * @param string $selector The root node selector. |
|
2968 * @param array $block_metadata The metadata for the root block. |
|
2969 * @return string The additional root rules CSS. |
|
2970 */ |
|
2971 public function get_root_layout_rules( $selector, $block_metadata ) { |
|
2972 $css = ''; |
|
2973 $settings = isset( $this->theme_json['settings'] ) ? $this->theme_json['settings'] : array(); |
|
2974 $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; |
|
2975 |
|
2976 /* |
|
2977 * If there are content and wide widths in theme.json, output them |
|
2978 * as custom properties on the body element so all blocks can use them. |
|
2979 */ |
|
2980 if ( isset( $settings['layout']['contentSize'] ) || isset( $settings['layout']['wideSize'] ) ) { |
|
2981 $content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize']; |
|
2982 $content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial'; |
|
2983 $wide_size = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize']; |
|
2984 $wide_size = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial'; |
|
2985 $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . ' { --wp--style--global--content-size: ' . $content_size . ';'; |
|
2986 $css .= '--wp--style--global--wide-size: ' . $wide_size . '; }'; |
|
2987 } |
|
2988 |
|
2989 /* |
|
2990 * Reset default browser margin on the body element. |
|
2991 * This is set on the body selector **before** generating the ruleset |
|
2992 * from the `theme.json`. This is to ensure that if the `theme.json` declares |
|
2993 * `margin` in its `spacing` declaration for the `body` element then these |
|
2994 * user-generated values take precedence in the CSS cascade. |
|
2995 * @link https://github.com/WordPress/gutenberg/issues/36147. |
|
2996 */ |
|
2997 $css .= ':where(body) { margin: 0; }'; |
|
2998 |
|
2999 if ( $use_root_padding ) { |
|
3000 // Top and bottom padding are applied to the outer block container. |
|
3001 $css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }'; |
|
3002 // Right and left padding are applied to the first container with `.has-global-padding` class. |
|
3003 $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; |
|
3004 // Alignfull children of the container with left and right padding have negative margins so they can still be full width. |
|
3005 $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; |
|
3006 // Nested children of the container with left and right padding that are not full aligned do not get padding, unless they are direct children of an alignfull flow container. |
|
3007 $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }'; |
|
3008 // Alignfull direct children of the containers that are targeted by the rule above do not need negative margins. |
|
3009 $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; }'; |
|
3010 } |
|
3011 |
|
3012 $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; |
|
3013 $css .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; |
|
3014 $css .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; |
|
3015 |
|
3016 // Block gap styles will be output unless explicitly set to `null`. See static::PROTECTED_PROPERTIES. |
|
3017 if ( isset( $this->theme_json['settings']['spacing']['blockGap'] ) ) { |
|
3018 $block_gap_value = static::get_property_value( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ) ); |
|
3019 $css .= ":where(.wp-site-blocks) > * { margin-block-start: $block_gap_value; margin-block-end: 0; }"; |
|
3020 $css .= ':where(.wp-site-blocks) > :first-child { margin-block-start: 0; }'; |
|
3021 $css .= ':where(.wp-site-blocks) > :last-child { margin-block-end: 0; }'; |
|
3022 |
|
3023 // For backwards compatibility, ensure the legacy block gap CSS variable is still available. |
|
3024 $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . " { --wp--style--block-gap: $block_gap_value; }"; |
|
3025 } |
|
3026 $css .= $this->get_layout_styles( $block_metadata ); |
|
3027 |
|
3028 return $css; |
|
3029 } |
|
3030 |
|
3031 /** |
1523 * For metadata values that can either be booleans or paths to booleans, gets the value. |
3032 * For metadata values that can either be booleans or paths to booleans, gets the value. |
1524 * |
3033 * |
1525 * ```php |
3034 * $data = array( |
1526 * $data = array( |
3035 * 'color' => array( |
1527 * 'color' => array( |
3036 * 'defaultPalette' => true |
1528 * 'defaultPalette' => true |
3037 * ) |
1529 * ) |
3038 * ); |
1530 * ); |
3039 * |
1531 * |
3040 * static::get_metadata_boolean( $data, false ); |
1532 * static::get_metadata_boolean( $data, false ); |
3041 * // => false |
1533 * // => false |
3042 * |
1534 * |
3043 * static::get_metadata_boolean( $data, array( 'color', 'defaultPalette' ) ); |
1535 * static::get_metadata_boolean( $data, array( 'color', 'defaultPalette' ) ); |
3044 * // => true |
1536 * // => true |
|
1537 * ``` |
|
1538 * |
3045 * |
1539 * @since 6.0.0 |
3046 * @since 6.0.0 |
1540 * |
3047 * |
1541 * @param array $data The data to inspect. |
3048 * @param array $data The data to inspect. |
1542 * @param bool|array $path Boolean or path to a boolean. |
3049 * @param bool|array $path Boolean or path to a boolean. |
1543 * @param bool $default Default value if the referenced path is missing. |
3050 * @param bool $default_value Default value if the referenced path is missing. |
1544 * Default false. |
3051 * Default false. |
1545 * @return bool Value of boolean metadata. |
3052 * @return bool Value of boolean metadata. |
1546 */ |
3053 */ |
1547 protected static function get_metadata_boolean( $data, $path, $default = false ) { |
3054 protected static function get_metadata_boolean( $data, $path, $default_value = false ) { |
1548 if ( is_bool( $path ) ) { |
3055 if ( is_bool( $path ) ) { |
1549 return $path; |
3056 return $path; |
1550 } |
3057 } |
1551 |
3058 |
1552 if ( is_array( $path ) ) { |
3059 if ( is_array( $path ) ) { |
1594 * we remove it from the theme presets. |
3135 * we remove it from the theme presets. |
1595 */ |
3136 */ |
1596 $nodes = static::get_setting_nodes( $incoming_data ); |
3137 $nodes = static::get_setting_nodes( $incoming_data ); |
1597 $slugs_global = static::get_default_slugs( $this->theme_json, array( 'settings' ) ); |
3138 $slugs_global = static::get_default_slugs( $this->theme_json, array( 'settings' ) ); |
1598 foreach ( $nodes as $node ) { |
3139 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. |
3140 // Replace the spacing.units. |
1603 $path = array_merge( $node['path'], array( 'spacing', 'units' ) ); |
3141 $path = $node['path']; |
|
3142 $path[] = 'spacing'; |
|
3143 $path[] = 'units'; |
|
3144 |
1604 $content = _wp_array_get( $incoming_data, $path, null ); |
3145 $content = _wp_array_get( $incoming_data, $path, null ); |
1605 if ( isset( $content ) ) { |
3146 if ( isset( $content ) ) { |
1606 _wp_array_set( $this->theme_json, $path, $content ); |
3147 _wp_array_set( $this->theme_json, $path, $content ); |
1607 } |
3148 } |
1608 |
3149 |
1609 // Replace the presets. |
3150 // Replace the presets. |
1610 foreach ( static::PRESETS_METADATA as $preset ) { |
3151 foreach ( static::PRESETS_METADATA as $preset_metadata ) { |
1611 $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true ); |
3152 $prevent_override = $preset_metadata['prevent_override']; |
|
3153 if ( is_array( $prevent_override ) ) { |
|
3154 $prevent_override = _wp_array_get( $this->theme_json['settings'], $preset_metadata['prevent_override'] ); |
|
3155 } |
1612 |
3156 |
1613 foreach ( static::VALID_ORIGINS as $origin ) { |
3157 foreach ( static::VALID_ORIGINS as $origin ) { |
1614 $base_path = array_merge( $node['path'], $preset['path'] ); |
3158 $base_path = $node['path']; |
1615 $path = array_merge( $base_path, array( $origin ) ); |
3159 foreach ( $preset_metadata['path'] as $leaf ) { |
1616 $content = _wp_array_get( $incoming_data, $path, null ); |
3160 $base_path[] = $leaf; |
|
3161 } |
|
3162 |
|
3163 $path = $base_path; |
|
3164 $path[] = $origin; |
|
3165 |
|
3166 $content = _wp_array_get( $incoming_data, $path, null ); |
1617 if ( ! isset( $content ) ) { |
3167 if ( ! isset( $content ) ) { |
1618 continue; |
3168 continue; |
1619 } |
3169 } |
1620 |
3170 |
1621 if ( 'theme' === $origin && $preset['use_default_names'] ) { |
3171 // Set names for theme presets based on the slug if they are not set and can use default names. |
1622 foreach ( $content as &$item ) { |
3172 if ( 'theme' === $origin && $preset_metadata['use_default_names'] ) { |
1623 if ( ! array_key_exists( 'name', $item ) ) { |
3173 foreach ( $content as $key => $item ) { |
|
3174 if ( ! isset( $item['name'] ) ) { |
1624 $name = static::get_name_from_defaults( $item['slug'], $base_path ); |
3175 $name = static::get_name_from_defaults( $item['slug'], $base_path ); |
1625 if ( null !== $name ) { |
3176 if ( null !== $name ) { |
1626 $item['name'] = $name; |
3177 $content[ $key ]['name'] = $name; |
1627 } |
3178 } |
1628 } |
3179 } |
1629 } |
3180 } |
1630 } |
3181 } |
1631 |
3182 |
1632 if ( |
3183 // Filter out default slugs from theme presets when defaults should not be overridden. |
1633 ( 'theme' !== $origin ) || |
3184 if ( 'theme' === $origin && $prevent_override ) { |
1634 ( 'theme' === $origin && $override_preset ) |
3185 $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); |
1635 ) { |
3186 $preset_global = _wp_array_get( $slugs_global, $preset_metadata['path'], array() ); |
1636 _wp_array_set( $this->theme_json, $path, $content ); |
3187 $preset_node = _wp_array_get( $slugs_node, $preset_metadata['path'], array() ); |
1637 } else { |
3188 $preset_slugs = array_merge_recursive( $preset_global, $preset_node ); |
1638 $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); |
3189 |
1639 $content = static::filter_slugs( $content, $slugs_for_preset ); |
3190 $content = static::filter_slugs( $content, $preset_slugs ); |
1640 _wp_array_set( $this->theme_json, $path, $content ); |
|
1641 } |
3191 } |
|
3192 |
|
3193 _wp_array_set( $this->theme_json, $path, $content ); |
1642 } |
3194 } |
1643 } |
3195 } |
1644 } |
3196 } |
1645 } |
3197 } |
1646 |
3198 |
1815 |
3373 |
1816 /** |
3374 /** |
1817 * Removes insecure data from theme.json. |
3375 * Removes insecure data from theme.json. |
1818 * |
3376 * |
1819 * @since 5.9.0 |
3377 * @since 5.9.0 |
1820 * |
3378 * @since 6.3.2 Preserves global styles block variations when securing styles. |
1821 * @param array $theme_json Structure to sanitize. |
3379 * @since 6.6.0 Updated to allow variation element styles and $origin parameter. |
|
3380 * |
|
3381 * @param array $theme_json Structure to sanitize. |
|
3382 * @param string $origin Optional. What source of data this object represents. |
|
3383 * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. |
1822 * @return array Sanitized structure. |
3384 * @return array Sanitized structure. |
1823 */ |
3385 */ |
1824 public static function remove_insecure_properties( $theme_json ) { |
3386 public static function remove_insecure_properties( $theme_json, $origin = 'theme' ) { |
|
3387 if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { |
|
3388 $origin = 'theme'; |
|
3389 } |
|
3390 |
1825 $sanitized = array(); |
3391 $sanitized = array(); |
1826 |
3392 |
1827 $theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); |
3393 $theme_json = WP_Theme_JSON_Schema::migrate( $theme_json, $origin ); |
1828 |
3394 |
1829 $valid_block_names = array_keys( static::get_blocks_metadata() ); |
3395 $valid_block_names = array_keys( static::get_blocks_metadata() ); |
1830 $valid_element_names = array_keys( static::ELEMENTS ); |
3396 $valid_element_names = array_keys( static::ELEMENTS ); |
1831 $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); |
3397 $valid_variations = static::get_valid_block_style_variations(); |
|
3398 |
|
3399 $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations ); |
1832 |
3400 |
1833 $blocks_metadata = static::get_blocks_metadata(); |
3401 $blocks_metadata = static::get_blocks_metadata(); |
1834 $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata ); |
3402 $style_options = array( 'include_block_style_variations' => true ); // Allow variations data. |
|
3403 $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata, $style_options ); |
|
3404 |
1835 foreach ( $style_nodes as $metadata ) { |
3405 foreach ( $style_nodes as $metadata ) { |
1836 $input = _wp_array_get( $theme_json, $metadata['path'], array() ); |
3406 $input = _wp_array_get( $theme_json, $metadata['path'], array() ); |
1837 if ( empty( $input ) ) { |
3407 if ( empty( $input ) ) { |
1838 continue; |
3408 continue; |
1839 } |
3409 } |
1840 |
3410 |
1841 $output = static::remove_insecure_styles( $input ); |
3411 // The global styles custom CSS is not sanitized, but can only be edited by users with 'edit_css' capability. |
|
3412 if ( isset( $input['css'] ) && current_user_can( 'edit_css' ) ) { |
|
3413 $output = $input; |
|
3414 } else { |
|
3415 $output = static::remove_insecure_styles( $input ); |
|
3416 } |
|
3417 |
|
3418 /* |
|
3419 * Get a reference to element name from path. |
|
3420 * $metadata['path'] = array( 'styles', 'elements', 'link' ); |
|
3421 */ |
|
3422 $current_element = $metadata['path'][ count( $metadata['path'] ) - 1 ]; |
|
3423 |
|
3424 /* |
|
3425 * $output is stripped of pseudo selectors. Re-add and process them |
|
3426 * or insecure styles here. |
|
3427 */ |
|
3428 if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) ) { |
|
3429 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] as $pseudo_selector ) { |
|
3430 if ( isset( $input[ $pseudo_selector ] ) ) { |
|
3431 $output[ $pseudo_selector ] = static::remove_insecure_styles( $input[ $pseudo_selector ] ); |
|
3432 } |
|
3433 } |
|
3434 } |
|
3435 |
1842 if ( ! empty( $output ) ) { |
3436 if ( ! empty( $output ) ) { |
1843 _wp_array_set( $sanitized, $metadata['path'], $output ); |
3437 _wp_array_set( $sanitized, $metadata['path'], $output ); |
|
3438 } |
|
3439 |
|
3440 if ( isset( $metadata['variations'] ) ) { |
|
3441 foreach ( $metadata['variations'] as $variation ) { |
|
3442 $variation_input = _wp_array_get( $theme_json, $variation['path'], array() ); |
|
3443 if ( empty( $variation_input ) ) { |
|
3444 continue; |
|
3445 } |
|
3446 |
|
3447 $variation_output = static::remove_insecure_styles( $variation_input ); |
|
3448 |
|
3449 // Process a variation's elements and element pseudo selector styles. |
|
3450 if ( isset( $variation_input['elements'] ) ) { |
|
3451 foreach ( $valid_element_names as $element_name ) { |
|
3452 $element_input = $variation_input['elements'][ $element_name ] ?? null; |
|
3453 if ( $element_input ) { |
|
3454 $element_output = static::remove_insecure_styles( $element_input ); |
|
3455 |
|
3456 if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { |
|
3457 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { |
|
3458 if ( isset( $element_input[ $pseudo_selector ] ) ) { |
|
3459 $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); |
|
3460 } |
|
3461 } |
|
3462 } |
|
3463 |
|
3464 if ( ! empty( $element_output ) ) { |
|
3465 _wp_array_set( $variation_output, array( 'elements', $element_name ), $element_output ); |
|
3466 } |
|
3467 } |
|
3468 } |
|
3469 } |
|
3470 |
|
3471 if ( ! empty( $variation_output ) ) { |
|
3472 _wp_array_set( $sanitized, $variation['path'], $variation_output ); |
|
3473 } |
|
3474 } |
1844 } |
3475 } |
1845 } |
3476 } |
1846 |
3477 |
1847 $setting_nodes = static::get_setting_nodes( $theme_json ); |
3478 $setting_nodes = static::get_setting_nodes( $theme_json ); |
1848 foreach ( $setting_nodes as $metadata ) { |
3479 foreach ( $setting_nodes as $metadata ) { |
2152 $items[ $slug ] = $item; |
3797 $items[ $slug ] = $item; |
2153 } |
3798 } |
2154 } |
3799 } |
2155 $flattened_preset = array(); |
3800 $flattened_preset = array(); |
2156 foreach ( $items as $slug => $value ) { |
3801 foreach ( $items as $slug => $value ) { |
2157 $flattened_preset[] = array_merge( array( 'slug' => $slug ), $value ); |
3802 $flattened_preset[] = array_merge( array( 'slug' => (string) $slug ), $value ); |
2158 } |
3803 } |
2159 _wp_array_set( $output, $path, $flattened_preset ); |
3804 _wp_array_set( $output, $path, $flattened_preset ); |
2160 } |
3805 } |
2161 } |
3806 } |
2162 |
3807 |
2163 // If all of the static::APPEARANCE_TOOLS_OPT_INS are true, |
3808 /* |
2164 // this code unsets them and sets 'appearanceTools' instead. |
3809 * If all of the static::APPEARANCE_TOOLS_OPT_INS are true, |
|
3810 * this code unsets them and sets 'appearanceTools' instead. |
|
3811 */ |
2165 foreach ( $nodes as $node ) { |
3812 foreach ( $nodes as $node ) { |
2166 $all_opt_ins_are_set = true; |
3813 $all_opt_ins_are_set = true; |
2167 foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { |
3814 foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { |
2168 $full_path = array_merge( $node['path'], $opt_in_path ); |
3815 $full_path = $node['path']; |
2169 // Use "unset prop" as a marker instead of "null" because |
3816 foreach ( $opt_in_path as $opt_in_path_item ) { |
2170 // "null" can be a valid value for some props (e.g. blockGap). |
3817 $full_path[] = $opt_in_path_item; |
|
3818 } |
|
3819 /* |
|
3820 * Use "unset prop" as a marker instead of "null" because |
|
3821 * "null" can be a valid value for some props (e.g. blockGap). |
|
3822 */ |
2171 $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); |
3823 $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); |
2172 if ( 'unset prop' === $opt_in_value ) { |
3824 if ( 'unset prop' === $opt_in_value ) { |
2173 $all_opt_ins_are_set = false; |
3825 $all_opt_ins_are_set = false; |
2174 break; |
3826 break; |
2175 } |
3827 } |
2176 } |
3828 } |
2177 |
3829 |
2178 if ( $all_opt_ins_are_set ) { |
3830 if ( $all_opt_ins_are_set ) { |
2179 _wp_array_set( $output, array_merge( $node['path'], array( 'appearanceTools' ) ), true ); |
3831 $node_path_with_appearance_tools = $node['path']; |
|
3832 $node_path_with_appearance_tools[] = 'appearanceTools'; |
|
3833 _wp_array_set( $output, $node_path_with_appearance_tools, true ); |
2180 foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { |
3834 foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { |
2181 $full_path = array_merge( $node['path'], $opt_in_path ); |
3835 $full_path = $node['path']; |
2182 // Use "unset prop" as a marker instead of "null" because |
3836 foreach ( $opt_in_path as $opt_in_path_item ) { |
2183 // "null" can be a valid value for some props (e.g. blockGap). |
3837 $full_path[] = $opt_in_path_item; |
|
3838 } |
|
3839 /* |
|
3840 * Use "unset prop" as a marker instead of "null" because |
|
3841 * "null" can be a valid value for some props (e.g. blockGap). |
|
3842 */ |
2184 $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); |
3843 $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); |
2185 if ( true !== $opt_in_value ) { |
3844 if ( true !== $opt_in_value ) { |
2186 continue; |
3845 continue; |
2187 } |
3846 } |
2188 |
3847 |
2189 // The following could be improved to be path independent. |
3848 /* |
2190 // At the moment it relies on a couple of assumptions: |
3849 * The following could be improved to be path independent. |
2191 // |
3850 * At the moment it relies on a couple of assumptions: |
2192 // - all opt-ins having a path of size 2. |
3851 * |
2193 // - there's two sources of settings: the top-level and the block-level. |
3852 * - all opt-ins having a path of size 2. |
|
3853 * - there's two sources of settings: the top-level and the block-level. |
|
3854 */ |
2194 if ( |
3855 if ( |
2195 ( 1 === count( $node['path'] ) ) && |
3856 ( 1 === count( $node['path'] ) ) && |
2196 ( 'settings' === $node['path'][0] ) |
3857 ( 'settings' === $node['path'][0] ) |
2197 ) { |
3858 ) { |
2198 // Top-level settings. |
3859 // Top-level settings. |
2219 wp_recursive_ksort( $output ); |
3880 wp_recursive_ksort( $output ); |
2220 |
3881 |
2221 return $output; |
3882 return $output; |
2222 } |
3883 } |
2223 |
3884 |
|
3885 /** |
|
3886 * Sets the spacingSizes array based on the spacingScale values from theme.json. |
|
3887 * |
|
3888 * @since 6.1.0 |
|
3889 * @deprecated 6.6.0 No longer used as the spacingSizes are automatically |
|
3890 * generated in the constructor and merge methods instead |
|
3891 * of manually after instantiation. |
|
3892 * |
|
3893 * @return null|void |
|
3894 */ |
|
3895 public function set_spacing_sizes() { |
|
3896 _deprecated_function( __METHOD__, '6.6.0' ); |
|
3897 |
|
3898 $spacing_scale = isset( $this->theme_json['settings']['spacing']['spacingScale'] ) |
|
3899 ? $this->theme_json['settings']['spacing']['spacingScale'] |
|
3900 : array(); |
|
3901 |
|
3902 if ( ! isset( $spacing_scale['steps'] ) |
|
3903 || ! is_numeric( $spacing_scale['steps'] ) |
|
3904 || ! isset( $spacing_scale['mediumStep'] ) |
|
3905 || ! isset( $spacing_scale['unit'] ) |
|
3906 || ! isset( $spacing_scale['operator'] ) |
|
3907 || ! isset( $spacing_scale['increment'] ) |
|
3908 || ! isset( $spacing_scale['steps'] ) |
|
3909 || ! is_numeric( $spacing_scale['increment'] ) |
|
3910 || ! is_numeric( $spacing_scale['mediumStep'] ) |
|
3911 || ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) ) { |
|
3912 if ( ! empty( $spacing_scale ) ) { |
|
3913 wp_trigger_error( |
|
3914 __METHOD__, |
|
3915 sprintf( |
|
3916 /* translators: 1: theme.json, 2: settings.spacing.spacingScale */ |
|
3917 __( 'Some of the %1$s %2$s values are invalid' ), |
|
3918 'theme.json', |
|
3919 'settings.spacing.spacingScale' |
|
3920 ), |
|
3921 E_USER_NOTICE |
|
3922 ); |
|
3923 } |
|
3924 return null; |
|
3925 } |
|
3926 |
|
3927 // If theme authors want to prevent the generation of the core spacing scale they can set their theme.json spacingScale.steps to 0. |
|
3928 if ( 0 === $spacing_scale['steps'] ) { |
|
3929 return null; |
|
3930 } |
|
3931 |
|
3932 $spacing_sizes = static::compute_spacing_sizes( $spacing_scale ); |
|
3933 |
|
3934 // If there are 7 or fewer steps in the scale revert to numbers for labels instead of t-shirt sizes. |
|
3935 if ( $spacing_scale['steps'] <= 7 ) { |
|
3936 for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) { |
|
3937 $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 ); |
|
3938 } |
|
3939 } |
|
3940 |
|
3941 _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); |
|
3942 } |
|
3943 |
|
3944 /** |
|
3945 * Merges two sets of spacing size presets. |
|
3946 * |
|
3947 * @since 6.6.0 |
|
3948 * |
|
3949 * @param array $base The base set of spacing sizes. |
|
3950 * @param array $incoming The set of spacing sizes to merge with the base. Duplicate slugs will override the base values. |
|
3951 * @return array The merged set of spacing sizes. |
|
3952 */ |
|
3953 private static function merge_spacing_sizes( $base, $incoming ) { |
|
3954 // Preserve the order if there are no base (spacingScale) values. |
|
3955 if ( empty( $base ) ) { |
|
3956 return $incoming; |
|
3957 } |
|
3958 $merged = array(); |
|
3959 foreach ( $base as $item ) { |
|
3960 $merged[ $item['slug'] ] = $item; |
|
3961 } |
|
3962 foreach ( $incoming as $item ) { |
|
3963 $merged[ $item['slug'] ] = $item; |
|
3964 } |
|
3965 ksort( $merged, SORT_NUMERIC ); |
|
3966 return array_values( $merged ); |
|
3967 } |
|
3968 |
|
3969 /** |
|
3970 * Generates a set of spacing sizes by starting with a medium size and |
|
3971 * applying an operator with an increment value to generate the rest of the |
|
3972 * sizes outward from the medium size. The medium slug is '50' with the rest |
|
3973 * of the slugs being 10 apart. The generated names use t-shirt sizing. |
|
3974 * |
|
3975 * Example: |
|
3976 * |
|
3977 * $spacing_scale = array( |
|
3978 * 'steps' => 4, |
|
3979 * 'mediumStep' => 16, |
|
3980 * 'unit' => 'px', |
|
3981 * 'operator' => '+', |
|
3982 * 'increment' => 2, |
|
3983 * ); |
|
3984 * $spacing_sizes = static::compute_spacing_sizes( $spacing_scale ); |
|
3985 * // -> array( |
|
3986 * // array( 'name' => 'Small', 'slug' => '40', 'size' => '14px' ), |
|
3987 * // array( 'name' => 'Medium', 'slug' => '50', 'size' => '16px' ), |
|
3988 * // array( 'name' => 'Large', 'slug' => '60', 'size' => '18px' ), |
|
3989 * // array( 'name' => 'X-Large', 'slug' => '70', 'size' => '20px' ), |
|
3990 * // ) |
|
3991 * |
|
3992 * @since 6.6.0 |
|
3993 * |
|
3994 * @param array $spacing_scale { |
|
3995 * The spacing scale values. All are required. |
|
3996 * |
|
3997 * @type int $steps The number of steps in the scale. (up to 10 steps are supported.) |
|
3998 * @type float $mediumStep The middle value that gets the slug '50'. (For even number of steps, this becomes the first middle value.) |
|
3999 * @type string $unit The CSS unit to use for the sizes. |
|
4000 * @type string $operator The mathematical operator to apply to generate the other sizes. Either '+' or '*'. |
|
4001 * @type float $increment The value used with the operator to generate the other sizes. |
|
4002 * } |
|
4003 * @return array The spacing sizes presets or an empty array if some spacing scale values are missing or invalid. |
|
4004 */ |
|
4005 private static function compute_spacing_sizes( $spacing_scale ) { |
|
4006 /* |
|
4007 * This condition is intentionally missing some checks on ranges for the values in order to |
|
4008 * keep backwards compatibility with the previous implementation. |
|
4009 */ |
|
4010 if ( |
|
4011 ! isset( $spacing_scale['steps'] ) || |
|
4012 ! is_numeric( $spacing_scale['steps'] ) || |
|
4013 0 === $spacing_scale['steps'] || |
|
4014 ! isset( $spacing_scale['mediumStep'] ) || |
|
4015 ! is_numeric( $spacing_scale['mediumStep'] ) || |
|
4016 ! isset( $spacing_scale['unit'] ) || |
|
4017 ! isset( $spacing_scale['operator'] ) || |
|
4018 ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) || |
|
4019 ! isset( $spacing_scale['increment'] ) || |
|
4020 ! is_numeric( $spacing_scale['increment'] ) |
|
4021 ) { |
|
4022 return array(); |
|
4023 } |
|
4024 |
|
4025 $unit = '%' === $spacing_scale['unit'] ? '%' : sanitize_title( $spacing_scale['unit'] ); |
|
4026 $current_step = $spacing_scale['mediumStep']; |
|
4027 $steps_mid_point = round( $spacing_scale['steps'] / 2, 0 ); |
|
4028 $x_small_count = null; |
|
4029 $below_sizes = array(); |
|
4030 $slug = 40; |
|
4031 $remainder = 0; |
|
4032 |
|
4033 for ( $below_midpoint_count = $steps_mid_point - 1; $spacing_scale['steps'] > 1 && $slug > 0 && $below_midpoint_count > 0; $below_midpoint_count-- ) { |
|
4034 if ( '+' === $spacing_scale['operator'] ) { |
|
4035 $current_step -= $spacing_scale['increment']; |
|
4036 } elseif ( $spacing_scale['increment'] > 1 ) { |
|
4037 $current_step /= $spacing_scale['increment']; |
|
4038 } else { |
|
4039 $current_step *= $spacing_scale['increment']; |
|
4040 } |
|
4041 |
|
4042 if ( $current_step <= 0 ) { |
|
4043 $remainder = $below_midpoint_count; |
|
4044 break; |
|
4045 } |
|
4046 |
|
4047 $below_sizes[] = array( |
|
4048 /* translators: %s: Digit to indicate multiple of sizing, eg. 2X-Small. */ |
|
4049 'name' => $below_midpoint_count === $steps_mid_point - 1 ? __( 'Small' ) : sprintf( __( '%sX-Small' ), (string) $x_small_count ), |
|
4050 'slug' => (string) $slug, |
|
4051 'size' => round( $current_step, 2 ) . $unit, |
|
4052 ); |
|
4053 |
|
4054 if ( $below_midpoint_count === $steps_mid_point - 2 ) { |
|
4055 $x_small_count = 2; |
|
4056 } |
|
4057 |
|
4058 if ( $below_midpoint_count < $steps_mid_point - 2 ) { |
|
4059 ++$x_small_count; |
|
4060 } |
|
4061 |
|
4062 $slug -= 10; |
|
4063 } |
|
4064 |
|
4065 $below_sizes = array_reverse( $below_sizes ); |
|
4066 |
|
4067 $below_sizes[] = array( |
|
4068 'name' => __( 'Medium' ), |
|
4069 'slug' => '50', |
|
4070 'size' => $spacing_scale['mediumStep'] . $unit, |
|
4071 ); |
|
4072 |
|
4073 $current_step = $spacing_scale['mediumStep']; |
|
4074 $x_large_count = null; |
|
4075 $above_sizes = array(); |
|
4076 $slug = 60; |
|
4077 $steps_above = ( $spacing_scale['steps'] - $steps_mid_point ) + $remainder; |
|
4078 |
|
4079 for ( $above_midpoint_count = 0; $above_midpoint_count < $steps_above; $above_midpoint_count++ ) { |
|
4080 $current_step = '+' === $spacing_scale['operator'] |
|
4081 ? $current_step + $spacing_scale['increment'] |
|
4082 : ( $spacing_scale['increment'] >= 1 ? $current_step * $spacing_scale['increment'] : $current_step / $spacing_scale['increment'] ); |
|
4083 |
|
4084 $above_sizes[] = array( |
|
4085 /* translators: %s: Digit to indicate multiple of sizing, eg. 2X-Large. */ |
|
4086 'name' => 0 === $above_midpoint_count ? __( 'Large' ) : sprintf( __( '%sX-Large' ), (string) $x_large_count ), |
|
4087 'slug' => (string) $slug, |
|
4088 'size' => round( $current_step, 2 ) . $unit, |
|
4089 ); |
|
4090 |
|
4091 if ( 1 === $above_midpoint_count ) { |
|
4092 $x_large_count = 2; |
|
4093 } |
|
4094 |
|
4095 if ( $above_midpoint_count > 1 ) { |
|
4096 ++$x_large_count; |
|
4097 } |
|
4098 |
|
4099 $slug += 10; |
|
4100 } |
|
4101 |
|
4102 $spacing_sizes = $below_sizes; |
|
4103 foreach ( $above_sizes as $above_sizes_item ) { |
|
4104 $spacing_sizes[] = $above_sizes_item; |
|
4105 } |
|
4106 |
|
4107 return $spacing_sizes; |
|
4108 } |
|
4109 |
|
4110 /** |
|
4111 * This is used to convert the internal representation of variables to the CSS representation. |
|
4112 * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`. |
|
4113 * |
|
4114 * @since 6.3.0 |
|
4115 * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert. |
|
4116 * @return string The converted variable. |
|
4117 */ |
|
4118 private static function convert_custom_properties( $value ) { |
|
4119 $prefix = 'var:'; |
|
4120 $prefix_len = strlen( $prefix ); |
|
4121 $token_in = '|'; |
|
4122 $token_out = '--'; |
|
4123 if ( str_starts_with( $value, $prefix ) ) { |
|
4124 $unwrapped_name = str_replace( |
|
4125 $token_in, |
|
4126 $token_out, |
|
4127 substr( $value, $prefix_len ) |
|
4128 ); |
|
4129 $value = "var(--wp--$unwrapped_name)"; |
|
4130 } |
|
4131 |
|
4132 return $value; |
|
4133 } |
|
4134 |
|
4135 /** |
|
4136 * Given a tree, converts the internal representation of variables to the CSS representation. |
|
4137 * It is recursive and modifies the input in-place. |
|
4138 * |
|
4139 * @since 6.3.0 |
|
4140 * @param array $tree Input to process. |
|
4141 * @return array The modified $tree. |
|
4142 */ |
|
4143 private static function resolve_custom_css_format( $tree ) { |
|
4144 $prefix = 'var:'; |
|
4145 |
|
4146 foreach ( $tree as $key => $data ) { |
|
4147 if ( is_string( $data ) && str_starts_with( $data, $prefix ) ) { |
|
4148 $tree[ $key ] = self::convert_custom_properties( $data ); |
|
4149 } elseif ( is_array( $data ) ) { |
|
4150 $tree[ $key ] = self::resolve_custom_css_format( $data ); |
|
4151 } |
|
4152 } |
|
4153 |
|
4154 return $tree; |
|
4155 } |
|
4156 |
|
4157 /** |
|
4158 * Returns the selectors metadata for a block. |
|
4159 * |
|
4160 * @since 6.3.0 |
|
4161 * |
|
4162 * @param object $block_type The block type. |
|
4163 * @param string $root_selector The block's root selector. |
|
4164 * |
|
4165 * @return array The custom selectors set by the block. |
|
4166 */ |
|
4167 protected static function get_block_selectors( $block_type, $root_selector ) { |
|
4168 if ( ! empty( $block_type->selectors ) ) { |
|
4169 return $block_type->selectors; |
|
4170 } |
|
4171 |
|
4172 $selectors = array( 'root' => $root_selector ); |
|
4173 foreach ( static::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $key => $feature ) { |
|
4174 $feature_selector = wp_get_block_css_selector( $block_type, $key ); |
|
4175 if ( null !== $feature_selector ) { |
|
4176 $selectors[ $feature ] = array( 'root' => $feature_selector ); |
|
4177 } |
|
4178 } |
|
4179 |
|
4180 return $selectors; |
|
4181 } |
|
4182 |
|
4183 /** |
|
4184 * Generates all the element selectors for a block. |
|
4185 * |
|
4186 * @since 6.3.0 |
|
4187 * |
|
4188 * @param string $root_selector The block's root CSS selector. |
|
4189 * @return array The block's element selectors. |
|
4190 */ |
|
4191 protected static function get_block_element_selectors( $root_selector ) { |
|
4192 /* |
|
4193 * Assign defaults, then override those that the block sets by itself. |
|
4194 * If the block selector is compounded, will append the element to each |
|
4195 * individual block selector. |
|
4196 */ |
|
4197 $block_selectors = explode( ',', $root_selector ); |
|
4198 $element_selectors = array(); |
|
4199 foreach ( static::ELEMENTS as $el_name => $el_selector ) { |
|
4200 $element_selector = array(); |
|
4201 foreach ( $block_selectors as $selector ) { |
|
4202 if ( $selector === $el_selector ) { |
|
4203 $element_selector = array( $el_selector ); |
|
4204 break; |
|
4205 } |
|
4206 $element_selector[] = static::prepend_to_selector( $el_selector, $selector . ' ' ); |
|
4207 } |
|
4208 $element_selectors[ $el_name ] = implode( ',', $element_selector ); |
|
4209 } |
|
4210 |
|
4211 return $element_selectors; |
|
4212 } |
|
4213 |
|
4214 /** |
|
4215 * Generates style declarations for a node's features e.g., color, border, |
|
4216 * typography etc. that have custom selectors in their related block's |
|
4217 * metadata. |
|
4218 * |
|
4219 * @since 6.3.0 |
|
4220 * |
|
4221 * @param object $metadata The related block metadata containing selectors. |
|
4222 * @param object $node A merged theme.json node for block or variation. |
|
4223 * |
|
4224 * @return array The style declarations for the node's features with custom |
|
4225 * selectors. |
|
4226 */ |
|
4227 protected function get_feature_declarations_for_node( $metadata, &$node ) { |
|
4228 $declarations = array(); |
|
4229 |
|
4230 if ( ! isset( $metadata['selectors'] ) ) { |
|
4231 return $declarations; |
|
4232 } |
|
4233 |
|
4234 $settings = isset( $this->theme_json['settings'] ) |
|
4235 ? $this->theme_json['settings'] |
|
4236 : array(); |
|
4237 |
|
4238 foreach ( $metadata['selectors'] as $feature => $feature_selectors ) { |
|
4239 /* |
|
4240 * Skip if this is the block's root selector or the block doesn't |
|
4241 * have any styles for the feature. |
|
4242 */ |
|
4243 if ( 'root' === $feature || empty( $node[ $feature ] ) ) { |
|
4244 continue; |
|
4245 } |
|
4246 |
|
4247 if ( is_array( $feature_selectors ) ) { |
|
4248 foreach ( $feature_selectors as $subfeature => $subfeature_selector ) { |
|
4249 if ( 'root' === $subfeature || empty( $node[ $feature ][ $subfeature ] ) ) { |
|
4250 continue; |
|
4251 } |
|
4252 |
|
4253 /* |
|
4254 * Create temporary node containing only the subfeature data |
|
4255 * to leverage existing `compute_style_properties` function. |
|
4256 */ |
|
4257 $subfeature_node = array( |
|
4258 $feature => array( |
|
4259 $subfeature => $node[ $feature ][ $subfeature ], |
|
4260 ), |
|
4261 ); |
|
4262 |
|
4263 // Generate style declarations. |
|
4264 $new_declarations = static::compute_style_properties( $subfeature_node, $settings, null, $this->theme_json ); |
|
4265 |
|
4266 // Merge subfeature declarations into feature declarations. |
|
4267 if ( isset( $declarations[ $subfeature_selector ] ) ) { |
|
4268 foreach ( $new_declarations as $new_declaration ) { |
|
4269 $declarations[ $subfeature_selector ][] = $new_declaration; |
|
4270 } |
|
4271 } else { |
|
4272 $declarations[ $subfeature_selector ] = $new_declarations; |
|
4273 } |
|
4274 |
|
4275 /* |
|
4276 * Remove the subfeature from the block's node now its |
|
4277 * styles will be included under its own selector not the |
|
4278 * block's. |
|
4279 */ |
|
4280 unset( $node[ $feature ][ $subfeature ] ); |
|
4281 } |
|
4282 } |
|
4283 |
|
4284 /* |
|
4285 * Now subfeatures have been processed and removed we can process |
|
4286 * feature root selector or simple string selector. |
|
4287 */ |
|
4288 if ( |
|
4289 is_string( $feature_selectors ) || |
|
4290 ( isset( $feature_selectors['root'] ) && $feature_selectors['root'] ) |
|
4291 ) { |
|
4292 $feature_selector = is_string( $feature_selectors ) ? $feature_selectors : $feature_selectors['root']; |
|
4293 |
|
4294 /* |
|
4295 * Create temporary node containing only the feature data |
|
4296 * to leverage existing `compute_style_properties` function. |
|
4297 */ |
|
4298 $feature_node = array( $feature => $node[ $feature ] ); |
|
4299 |
|
4300 // Generate the style declarations. |
|
4301 $new_declarations = static::compute_style_properties( $feature_node, $settings, null, $this->theme_json ); |
|
4302 |
|
4303 /* |
|
4304 * Merge new declarations with any that already exist for |
|
4305 * the feature selector. This may occur when multiple block |
|
4306 * support features use the same custom selector. |
|
4307 */ |
|
4308 if ( isset( $declarations[ $feature_selector ] ) ) { |
|
4309 foreach ( $new_declarations as $new_declaration ) { |
|
4310 $declarations[ $feature_selector ][] = $new_declaration; |
|
4311 } |
|
4312 } else { |
|
4313 $declarations[ $feature_selector ] = $new_declarations; |
|
4314 } |
|
4315 |
|
4316 /* |
|
4317 * Remove the feature from the block's node now its styles |
|
4318 * will be included under its own selector not the block's. |
|
4319 */ |
|
4320 unset( $node[ $feature ] ); |
|
4321 } |
|
4322 } |
|
4323 |
|
4324 return $declarations; |
|
4325 } |
|
4326 |
|
4327 /** |
|
4328 * Replaces CSS variables with their values in place. |
|
4329 * |
|
4330 * @since 6.3.0 |
|
4331 * @since 6.5.0 Check for empty style before processing its value. |
|
4332 * |
|
4333 * @param array $styles CSS declarations to convert. |
|
4334 * @param array $values key => value pairs to use for replacement. |
|
4335 * @return array |
|
4336 */ |
|
4337 private static function convert_variables_to_value( $styles, $values ) { |
|
4338 foreach ( $styles as $key => $style ) { |
|
4339 if ( empty( $style ) ) { |
|
4340 continue; |
|
4341 } |
|
4342 |
|
4343 if ( is_array( $style ) ) { |
|
4344 $styles[ $key ] = self::convert_variables_to_value( $style, $values ); |
|
4345 continue; |
|
4346 } |
|
4347 |
|
4348 if ( 0 <= strpos( $style, 'var(' ) ) { |
|
4349 // find all the variables in the string in the form of var(--variable-name, fallback), with fallback in the second capture group. |
|
4350 |
|
4351 $has_matches = preg_match_all( '/var\(([^),]+)?,?\s?(\S+)?\)/', $style, $var_parts ); |
|
4352 |
|
4353 if ( $has_matches ) { |
|
4354 $resolved_style = $styles[ $key ]; |
|
4355 foreach ( $var_parts[1] as $index => $var_part ) { |
|
4356 $key_in_values = 'var(' . $var_part . ')'; |
|
4357 $rule_to_replace = $var_parts[0][ $index ]; // the css rule to replace e.g. var(--wp--preset--color--vivid-green-cyan). |
|
4358 $fallback = $var_parts[2][ $index ]; // the fallback value. |
|
4359 $resolved_style = str_replace( |
|
4360 array( |
|
4361 $rule_to_replace, |
|
4362 $fallback, |
|
4363 ), |
|
4364 array( |
|
4365 isset( $values[ $key_in_values ] ) ? $values[ $key_in_values ] : $rule_to_replace, |
|
4366 isset( $values[ $fallback ] ) ? $values[ $fallback ] : $fallback, |
|
4367 ), |
|
4368 $resolved_style |
|
4369 ); |
|
4370 } |
|
4371 $styles[ $key ] = $resolved_style; |
|
4372 } |
|
4373 } |
|
4374 } |
|
4375 |
|
4376 return $styles; |
|
4377 } |
|
4378 |
|
4379 /** |
|
4380 * Resolves the values of CSS variables in the given styles. |
|
4381 * |
|
4382 * @since 6.3.0 |
|
4383 * @param WP_Theme_JSON $theme_json The theme json resolver. |
|
4384 * |
|
4385 * @return WP_Theme_JSON The $theme_json with resolved variables. |
|
4386 */ |
|
4387 public static function resolve_variables( $theme_json ) { |
|
4388 $settings = $theme_json->get_settings(); |
|
4389 $styles = $theme_json->get_raw_data()['styles']; |
|
4390 $preset_vars = static::compute_preset_vars( $settings, static::VALID_ORIGINS ); |
|
4391 $theme_vars = static::compute_theme_vars( $settings ); |
|
4392 $vars = array_reduce( |
|
4393 array_merge( $preset_vars, $theme_vars ), |
|
4394 function ( $carry, $item ) { |
|
4395 $name = $item['name']; |
|
4396 $carry[ "var({$name})" ] = $item['value']; |
|
4397 return $carry; |
|
4398 }, |
|
4399 array() |
|
4400 ); |
|
4401 |
|
4402 $theme_json->theme_json['styles'] = self::convert_variables_to_value( $styles, $vars ); |
|
4403 return $theme_json; |
|
4404 } |
|
4405 |
|
4406 /** |
|
4407 * Generates a selector for a block style variation. |
|
4408 * |
|
4409 * @since 6.5.0 |
|
4410 * |
|
4411 * @param string $variation_name Name of the block style variation. |
|
4412 * @param string $block_selector CSS selector for the block. |
|
4413 * @return string Block selector with block style variation selector added to it. |
|
4414 */ |
|
4415 protected static function get_block_style_variation_selector( $variation_name, $block_selector ) { |
|
4416 $variation_class = ".is-style-$variation_name"; |
|
4417 |
|
4418 if ( ! $block_selector ) { |
|
4419 return $variation_class; |
|
4420 } |
|
4421 |
|
4422 $limit = 1; |
|
4423 $selector_parts = explode( ',', $block_selector ); |
|
4424 $result = array(); |
|
4425 |
|
4426 foreach ( $selector_parts as $part ) { |
|
4427 $result[] = preg_replace_callback( |
|
4428 '/((?::\([^)]+\))?\s*)([^\s:]+)/', |
|
4429 function ( $matches ) use ( $variation_class ) { |
|
4430 return $matches[1] . $matches[2] . $variation_class; |
|
4431 }, |
|
4432 $part, |
|
4433 $limit |
|
4434 ); |
|
4435 } |
|
4436 |
|
4437 return implode( ',', $result ); |
|
4438 } |
|
4439 |
|
4440 /** |
|
4441 * Collects valid block style variations keyed by block type. |
|
4442 * |
|
4443 * @since 6.6.0 |
|
4444 * |
|
4445 * @return array Valid block style variations by block type. |
|
4446 */ |
|
4447 protected static function get_valid_block_style_variations() { |
|
4448 $valid_variations = array(); |
|
4449 foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) { |
|
4450 if ( ! isset( $block_meta['styleVariations'] ) ) { |
|
4451 continue; |
|
4452 } |
|
4453 $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] ); |
|
4454 } |
|
4455 |
|
4456 return $valid_variations; |
|
4457 } |
2224 } |
4458 } |