1<?php
2/**
3 * Block support flags.
4 *
5 * @package WordPress
6 *
7 * @since 5.6.0
8 */
9
10/**
11 * Class encapsulating and implementing Block Supports.
12 *
13 * @since 5.6.0
14 *
15 * @access private
16 */
17#[AllowDynamicProperties]
18class WP_Block_Supports {
19
20 /**
21 * Config.
22 *
23 * @since 5.6.0
24 * @var array
25 */
26 private $block_supports = array();
27
28 /**
29 * Tracks the current block to be rendered.
30 *
31 * @since 5.6.0
32 * @var array
33 */
34 public static $block_to_render = null;
35
36 /**
37 * Container for the main instance of the class.
38 *
39 * @since 5.6.0
40 * @var WP_Block_Supports|null
41 */
42 private static $instance = null;
43
44 /**
45 * Utility method to retrieve the main instance of the class.
46 *
47 * The instance will be created if it does not exist yet.
48 *
49 * @since 5.6.0
50 *
51 * @return WP_Block_Supports The main instance.
52 */
53 public static function get_instance() {
54 if ( null === self::$instance ) {
55 self::$instance = new self();
56 }
57
58 return self::$instance;
59 }
60
61 /**
62 * Initializes the block supports. It registers the block supports block attributes.
63 *
64 * @since 5.6.0
65 */
66 public static function init() {
67 $instance = self::get_instance();
68 $instance->register_attributes();
69 }
70
71 /**
72 * Registers a block support.
73 *
74 * @since 5.6.0
75 *
76 * @link https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/
77 *
78 * @param string $block_support_name Block support name.
79 * @param array $block_support_config Array containing the properties of the block support.
80 */
81 public function register( $block_support_name, $block_support_config ) {
82 $this->block_supports[ $block_support_name ] = array_merge(
83 $block_support_config,
84 array( 'name' => $block_support_name )
85 );
86 }
87
88 /**
89 * Generates an array of HTML attributes, such as classes, by applying to
90 * the given block all of the features that the block supports.
91 *
92 * @since 5.6.0
93 *
94 * @return string[] Array of HTML attribute values keyed by their name.
95 */
96 public function apply_block_supports() {
97 $block_type = WP_Block_Type_Registry::get_instance()->get_registered(
98 self::$block_to_render['blockName']
99 );
100
101 // If no render_callback, assume styles have been previously handled.
102 if ( ! $block_type || empty( $block_type ) ) {
103 return array();
104 }
105
106 $block_attributes = array_key_exists( 'attrs', self::$block_to_render ) && is_array( self::$block_to_render['attrs'] )
107 ? $block_type->prepare_attributes_for_render( self::$block_to_render['attrs'] )
108 : array();
109
110 $output = array();
111 foreach ( $this->block_supports as $block_support_config ) {
112 if ( ! isset( $block_support_config['apply'] ) ) {
113 continue;
114 }
115
116 $new_attributes = call_user_func(
117 $block_support_config['apply'],
118 $block_type,
119 $block_attributes
120 );
121
122 if ( ! empty( $new_attributes ) ) {
123 foreach ( $new_attributes as $attribute_name => $attribute_value ) {
124 if ( empty( $output[ $attribute_name ] ) ) {
125 $output[ $attribute_name ] = $attribute_value;
126 } else {
127 $output[ $attribute_name ] .= " $attribute_value";
128 }
129 }
130 }
131 }
132
133 return $output;
134 }
135
136 /**
137 * Registers the block attributes required by the different block supports.
138 *
139 * @since 5.6.0
140 */
141 private function register_attributes() {
142 $block_registry = WP_Block_Type_Registry::get_instance();
143 $registered_block_types = $block_registry->get_all_registered();
144 foreach ( $registered_block_types as $block_type ) {
145 if ( ! ( $block_type instanceof WP_Block_Type ) ) {
146 continue;
147 }
148 if ( ! $block_type->attributes ) {
149 $block_type->attributes = array();
150 }
151
152 foreach ( $this->block_supports as $block_support_config ) {
153 if ( ! isset( $block_support_config['register_attribute'] ) ) {
154 continue;
155 }
156
157 call_user_func(
158 $block_support_config['register_attribute'],
159 $block_type
160 );
161 }
162 }
163 }
164}
165
166/**
167 * Generates a string of attributes by applying to the current block being
168 * rendered all of the features that the block supports.
169 *
170 * @since 5.6.0
171 *
172 * @param string[] $extra_attributes Optional. Array of extra attributes to render on the block wrapper.
173 * @return string String of HTML attributes.
174 */
175function get_block_wrapper_attributes( $extra_attributes = array() ) {
176 $new_attributes = WP_Block_Supports::get_instance()->apply_block_supports();
177
178 if ( empty( $new_attributes ) && empty( $extra_attributes ) ) {
179 return '';
180 }
181
182 // This is hardcoded on purpose.
183 // We only support a fixed list of attributes.
184 $attributes_to_merge = array( 'style', 'class', 'id', 'aria-label' );
185 $attributes = array();
186 foreach ( $attributes_to_merge as $attribute_name ) {
187 if ( empty( $new_attributes[ $attribute_name ] ) && empty( $extra_attributes[ $attribute_name ] ) ) {
188 continue;
189 }
190
191 if ( empty( $new_attributes[ $attribute_name ] ) ) {
192 $attributes[ $attribute_name ] = $extra_attributes[ $attribute_name ];
193 continue;
194 }
195
196 if ( empty( $extra_attributes[ $attribute_name ] ) ) {
197 $attributes[ $attribute_name ] = $new_attributes[ $attribute_name ];
198 continue;
199 }
200
201 $attributes[ $attribute_name ] = $extra_attributes[ $attribute_name ] . ' ' . $new_attributes[ $attribute_name ];
202 }
203
204 foreach ( $extra_attributes as $attribute_name => $value ) {
205 if ( ! in_array( $attribute_name, $attributes_to_merge, true ) ) {
206 $attributes[ $attribute_name ] = $value;
207 }
208 }
209
210 if ( empty( $attributes ) ) {
211 return '';
212 }
213
214 $normalized_attributes = array();
215 foreach ( $attributes as $key => $value ) {
216 $normalized_attributes[] = $key . '="' . esc_attr( $value ) . '"';
217 }
218
219 return implode( ' ', $normalized_attributes );
220}
221