WP_Theme_JSON::get_block_nodes( array $theme_json, array $selectors = array(), array $options = array() ): array

In this article

This function’s access is marked private. This means it is not intended for use by plugin or theme developers, only by core. It is listed here for completeness.

An internal method to get the block nodes from a theme.json file.

Parameters

$theme_jsonarrayrequired
The theme.json converted to an array.
$selectorsarrayoptional
Optional list of selectors per block.

Default:array()

$optionsarrayoptional
An array of options for now used for internal purposes only (may change without notice).
  • include_block_style_variations bool
    Include nodes for block style variations. Default false.
  • include_node_paths_only bool
    Return only block nodes node paths. Default false.

Default:array()

Return

array The block nodes in theme.json.

Source

private static function get_block_nodes( $theme_json, $selectors = array(), $options = array() ) {
	$nodes = array();

	if ( ! isset( $theme_json['styles']['blocks'] ) ) {
		return $nodes;
	}

	$include_variations       = $options['include_block_style_variations'] ?? false;
	$include_node_paths_only  = $options['include_node_paths_only'] ?? false;
	$responsive_media_queries = static::get_viewport_media_queries( $theme_json['settings']['viewport'] ?? null );

	// If only node paths are to be returned, skip selector assignment.
	if ( ! $include_node_paths_only ) {
		$selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors;
	}

	foreach ( $theme_json['styles']['blocks'] as $name => $node ) {
		$node_path = array( 'styles', 'blocks', $name );
		if ( $include_node_paths_only ) {
			$variation_paths = array();
			if ( $include_variations && isset( $node['variations'] ) ) {
				foreach ( $node['variations'] as $variation => $variation_node ) {
					$variation_paths[] = array(
						'path' => array( 'styles', 'blocks', $name, 'variations', $variation ),
					);
				}
			}
			$node = array(
				'path' => $node_path,
			);
			if ( ! empty( $variation_paths ) ) {
				$node['variations'] = $variation_paths;
			}
			$nodes[] = $node;
		} else {
			$selector = null;
			if ( isset( $selectors[ $name ]['selector'] ) ) {
				$selector = $selectors[ $name ]['selector'];
			}

			$duotone_selector = null;
			if ( isset( $selectors[ $name ]['duotone'] ) ) {
				$duotone_selector = $selectors[ $name ]['duotone'];
			}

			$feature_selectors = null;
			if ( isset( $selectors[ $name ]['selectors'] ) ) {
				$feature_selectors = $selectors[ $name ]['selectors'];
			}

			$variation_selectors = array();

			if ( $include_variations && isset( $node['variations'] ) ) {
				foreach ( $node['variations'] as $variation => $node ) {
					$variation_selectors[] = array(
						'name'     => $variation,
						'path'     => array( 'styles', 'blocks', $name, 'variations', $variation ),
						'selector' => $selectors[ $name ]['styleVariations'][ $variation ],
					);
				}
			}

			$nodes[] = array(
				'name'       => $name,
				'path'       => $node_path,
				'selector'   => $selector,
				'selectors'  => $feature_selectors,
				'elements'   => $selectors[ $name ]['elements'] ?? array(),
				'duotone'    => $duotone_selector,
				'variations' => $variation_selectors,
				'css'        => $selector,
			);

			// Responsive block nodes: emit one node per breakpoint that has styles.
			// These are rendered immediately after the base block node so that
			// the cascade order is: .block{} → @media{.block{}}
			foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
				if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ) ) {
					$nodes[] = array(
						'name'        => $name,
						'path'        => array( 'styles', 'blocks', $name, $breakpoint ),
						'media_query' => $responsive_media_queries[ $breakpoint ],
						'selector'    => $selector,
						'selectors'   => $feature_selectors,
						'elements'    => $selectors[ $name ]['elements'] ?? array(),
						'variations'  => $variation_selectors,
						'css'         => $selector,
					);
				}
			}

			// Handle any pseudo selectors for the block.
			if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) {
				foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo_selector ) {
					$has_pseudo            = isset( $theme_json['styles']['blocks'][ $name ][ $pseudo_selector ] );
					$has_responsive_pseudo = false;
					foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
						if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ][ $pseudo_selector ] ) ) {
							$has_responsive_pseudo = true;
							break;
						}
					}

					if ( ! $has_pseudo && ! $has_responsive_pseudo ) {
						continue;
					}

					/*
					 * Append the pseudo-selector to each feature selector so that
					 * get_feature_declarations_for_node generates CSS scoped to the
					 * pseudo-state (e.g. '.wp-block-button:hover') rather than the
					 * default state (e.g. '.wp-block-button').
					 */
					$pseudo_feature_selectors = array();
					foreach ( $feature_selectors ?? array() as $feature => $feature_selector ) {
						if ( is_array( $feature_selector ) ) {
							$pseudo_feature_selectors[ $feature ] = array();
							foreach ( $feature_selector as $subfeature => $subfeature_selector ) {
								$pseudo_feature_selectors[ $feature ][ $subfeature ] = static::append_to_selector( $subfeature_selector, $pseudo_selector );
							}
						} else {
							$pseudo_feature_selectors[ $feature ] = static::append_to_selector( $feature_selector, $pseudo_selector );
						}
					}

					if ( $has_pseudo ) {
						$nodes[] = array(
							'name'       => $name,
							'path'       => array( 'styles', 'blocks', $name, $pseudo_selector ),
							'selector'   => static::append_to_selector( $selector, $pseudo_selector ),
							'selectors'  => $pseudo_feature_selectors,
							'elements'   => $selectors[ $name ]['elements'] ?? array(),
							'duotone'    => $duotone_selector,
							'variations' => $variation_selectors,
							'css'        => static::append_to_selector( $selector, $pseudo_selector ),
						);
					}

					// Responsive pseudo nodes: emit one node per breakpoint that has
					// this pseudo state, immediately after the default pseudo node.
					// Cascade order: .block:hover{} → @media{.block:hover{}}
					foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
						if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ][ $pseudo_selector ] ) ) {
							$nodes[] = array(
								'name'        => $name,
								'path'        => array( 'styles', 'blocks', $name, $breakpoint, $pseudo_selector ),
								'media_query' => $responsive_media_queries[ $breakpoint ],
								'selector'    => static::append_to_selector( $selector, $pseudo_selector ),
								'selectors'   => $pseudo_feature_selectors,
								'elements'    => $selectors[ $name ]['elements'] ?? array(),
								'variations'  => $variation_selectors,
								'css'         => static::append_to_selector( $selector, $pseudo_selector ),
							);
						}
					}
				}
			}

			// Handle custom states (e.g. '-current' for navigation).
			if ( isset( static::VALID_BLOCK_CUSTOM_STATES[ $name ] ) ) {
				foreach ( static::VALID_BLOCK_CUSTOM_STATES[ $name ] as $custom_state ) {
					if (
						isset( $theme_json['styles']['blocks'][ $name ][ $custom_state ] ) &&
						isset( $selectors[ $name ]['states'][ $custom_state ] )
					) {
						$custom_css_selector = $selectors[ $name ]['states'][ $custom_state ];
						$nodes[]             = array(
							'name'       => $name,
							'path'       => array( 'styles', 'blocks', $name, $custom_state ),
							'selector'   => $custom_css_selector,
							'selectors'  => $feature_selectors,
							'elements'   => $selectors[ $name ]['elements'] ?? array(),
							'duotone'    => $duotone_selector,
							'variations' => $variation_selectors,
							'css'        => $custom_css_selector,
						);

						// Sub-pseudo-selectors within the custom state.
						if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) {
							foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo ) {
								if ( isset( $theme_json['styles']['blocks'][ $name ][ $custom_state ][ $pseudo ] ) ) {
									$compound_css_selector = static::append_to_selector( $custom_css_selector, $pseudo );
									$nodes[]               = array(
										'name'       => $name,
										'path'       => array( 'styles', 'blocks', $name, $custom_state, $pseudo ),
										'selector'   => $compound_css_selector,
										'selectors'  => $feature_selectors,
										'elements'   => $selectors[ $name ]['elements'] ?? array(),
										'duotone'    => $duotone_selector,
										'variations' => $variation_selectors,
										'css'        => $compound_css_selector,
									);
								}
							}
						}
					}
				}
			}
		}
		/*
		 * Elements can be styled outside any breakpoint, inside one, or both,
		 * so collect the names from all of those places before looping. An
		 * element styled only inside a breakpoint still needs a node.
		 */
		$block_node    = $theme_json['styles']['blocks'][ $name ] ?? array();
		$element_names = array_keys( $block_node['elements'] ?? array() );
		foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
			$element_names = array_merge(
				$element_names,
				array_keys( $block_node[ $breakpoint ]['elements'] ?? array() )
			);
		}
		$element_names = array_unique( $element_names );

		if ( ! empty( $element_names ) ) {
			foreach ( $element_names as $element ) {
				$element_path = array( 'styles', 'blocks', $name, 'elements', $element );
				if ( $include_node_paths_only ) {
					if ( isset( $block_node['elements'][ $element ] ) ) {
						$nodes[] = array(
							'path' => $element_path,
						);
					}
					continue;
				}

				if ( ! isset( $selectors[ $name ]['elements'][ $element ] ) ) {
					continue;
				}

				$element_selector = $selectors[ $name ]['elements'][ $element ];

				if ( isset( $block_node['elements'][ $element ] ) ) {
					$nodes[] = array(
						'path'     => $element_path,
						'selector' => $element_selector,
					);
				}

				// Responsive element nodes: one node per breakpoint that has
				// styles for this element. Cascade: a{} → @media{a{}}
				foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
					if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ]['elements'][ $element ] ) ) {
						$nodes[] = array(
							'path'        => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
							'selector'    => $element_selector,
							'media_query' => $responsive_media_queries[ $breakpoint ],
						);
					}
				}

				// Handle any pseudo selectors for the element.
				if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
					foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
						// Emit the default pseudo node only when the default state styles
						// the pseudo. Otherwise get_styles_for_block() falls back to the
						// element's base styles, outputting a rule the theme never defined.
						if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) {
							$nodes[] = array(
								'path'     => array( 'styles', 'blocks', $name, 'elements', $element ),
								'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
							);
						}

						// Responsive element pseudo nodes: one node per breakpoint
						// that has this pseudo state for this element.
						// Cascade: a:hover{} → @media{a:hover{}}
						foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
							if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ]['elements'][ $element ][ $pseudo_selector ] ) ) {
								$nodes[] = array(
									'path'        => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
									'selector'    => static::append_to_selector( $element_selector, $pseudo_selector ),
									'media_query' => $responsive_media_queries[ $breakpoint ],
								);
							}
						}
					}
				}
			}
		}
	}

	return $nodes;
}

Changelog

VersionDescription
7.1.0Added responsive block nodes for breakpoint-based styles.
6.7.0Added $include_node_paths_only option.
6.6.0Added optional selectors and options for generating block nodes.
6.3.0Refactored and stabilized selectors API.
6.1.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.