|
1 <?php |
|
2 /** |
|
3 * Block Bindings API: WP_Block_Bindings_Registry class. |
|
4 * |
|
5 * Supports overriding content in blocks by connecting them to different sources. |
|
6 * |
|
7 * @package WordPress |
|
8 * @subpackage Block Bindings |
|
9 * @since 6.5.0 |
|
10 */ |
|
11 |
|
12 /** |
|
13 * Core class used for interacting with block bindings sources. |
|
14 * |
|
15 * @since 6.5.0 |
|
16 */ |
|
17 final class WP_Block_Bindings_Registry { |
|
18 |
|
19 /** |
|
20 * Holds the registered block bindings sources, keyed by source identifier. |
|
21 * |
|
22 * @since 6.5.0 |
|
23 * @var WP_Block_Bindings_Source[] |
|
24 */ |
|
25 private $sources = array(); |
|
26 |
|
27 /** |
|
28 * Container for the main instance of the class. |
|
29 * |
|
30 * @since 6.5.0 |
|
31 * @var WP_Block_Bindings_Registry|null |
|
32 */ |
|
33 private static $instance = null; |
|
34 |
|
35 /** |
|
36 * Supported source properties that can be passed to the registered source. |
|
37 * |
|
38 * @since 6.5.0 |
|
39 * @var string[] |
|
40 */ |
|
41 private $allowed_source_properties = array( |
|
42 'label', |
|
43 'get_value_callback', |
|
44 'uses_context', |
|
45 ); |
|
46 |
|
47 /** |
|
48 * Supported blocks that can use the block bindings API. |
|
49 * |
|
50 * @since 6.5.0 |
|
51 * @var string[] |
|
52 */ |
|
53 private $supported_blocks = array( |
|
54 'core/paragraph', |
|
55 'core/heading', |
|
56 'core/image', |
|
57 'core/button', |
|
58 ); |
|
59 |
|
60 /** |
|
61 * Registers a new block bindings source. |
|
62 * |
|
63 * This is a low-level method. For most use cases, it is recommended to use |
|
64 * the `register_block_bindings_source()` function instead. |
|
65 * |
|
66 * @see register_block_bindings_source() |
|
67 * |
|
68 * Sources are used to override block's original attributes with a value |
|
69 * coming from the source. Once a source is registered, it can be used by a |
|
70 * block by setting its `metadata.bindings` attribute to a value that refers |
|
71 * to the source. |
|
72 * |
|
73 * @since 6.5.0 |
|
74 * |
|
75 * @param string $source_name The name of the source. It must be a string containing a namespace prefix, i.e. |
|
76 * `my-plugin/my-custom-source`. It must only contain lowercase alphanumeric |
|
77 * characters, the forward slash `/` and dashes. |
|
78 * @param array $source_properties { |
|
79 * The array of arguments that are used to register a source. |
|
80 * |
|
81 * @type string $label The label of the source. |
|
82 * @type callable $get_value_callback A callback executed when the source is processed during block rendering. |
|
83 * The callback should have the following signature: |
|
84 * |
|
85 * `function( $source_args, $block_instance, $attribute_name ): mixed` |
|
86 * - @param array $source_args Array containing source arguments |
|
87 * used to look up the override value, |
|
88 * i.e. {"key": "foo"}. |
|
89 * - @param WP_Block $block_instance The block instance. |
|
90 * - @param string $attribute_name The name of the target attribute. |
|
91 * The callback has a mixed return type; it may return a string to override |
|
92 * the block's original value, null, false to remove an attribute, etc. |
|
93 * @type string[] $uses_context Optional. Array of values to add to block `uses_context` needed by the source. |
|
94 * } |
|
95 * @return WP_Block_Bindings_Source|false Source when the registration was successful, or `false` on failure. |
|
96 */ |
|
97 public function register( string $source_name, array $source_properties ) { |
|
98 if ( ! is_string( $source_name ) ) { |
|
99 _doing_it_wrong( |
|
100 __METHOD__, |
|
101 __( 'Block bindings source name must be a string.' ), |
|
102 '6.5.0' |
|
103 ); |
|
104 return false; |
|
105 } |
|
106 |
|
107 if ( preg_match( '/[A-Z]+/', $source_name ) ) { |
|
108 _doing_it_wrong( |
|
109 __METHOD__, |
|
110 __( 'Block bindings source names must not contain uppercase characters.' ), |
|
111 '6.5.0' |
|
112 ); |
|
113 return false; |
|
114 } |
|
115 |
|
116 $name_matcher = '/^[a-z0-9-]+\/[a-z0-9-]+$/'; |
|
117 if ( ! preg_match( $name_matcher, $source_name ) ) { |
|
118 _doing_it_wrong( |
|
119 __METHOD__, |
|
120 __( 'Block bindings source names must contain a namespace prefix. Example: my-plugin/my-custom-source' ), |
|
121 '6.5.0' |
|
122 ); |
|
123 return false; |
|
124 } |
|
125 |
|
126 if ( $this->is_registered( $source_name ) ) { |
|
127 _doing_it_wrong( |
|
128 __METHOD__, |
|
129 /* translators: %s: Block bindings source name. */ |
|
130 sprintf( __( 'Block bindings source "%s" already registered.' ), $source_name ), |
|
131 '6.5.0' |
|
132 ); |
|
133 return false; |
|
134 } |
|
135 |
|
136 // Validates that the source properties contain the label. |
|
137 if ( ! isset( $source_properties['label'] ) ) { |
|
138 _doing_it_wrong( |
|
139 __METHOD__, |
|
140 __( 'The $source_properties must contain a "label".' ), |
|
141 '6.5.0' |
|
142 ); |
|
143 return false; |
|
144 } |
|
145 |
|
146 // Validates that the source properties contain the get_value_callback. |
|
147 if ( ! isset( $source_properties['get_value_callback'] ) ) { |
|
148 _doing_it_wrong( |
|
149 __METHOD__, |
|
150 __( 'The $source_properties must contain a "get_value_callback".' ), |
|
151 '6.5.0' |
|
152 ); |
|
153 return false; |
|
154 } |
|
155 |
|
156 // Validates that the get_value_callback is a valid callback. |
|
157 if ( ! is_callable( $source_properties['get_value_callback'] ) ) { |
|
158 _doing_it_wrong( |
|
159 __METHOD__, |
|
160 __( 'The "get_value_callback" parameter must be a valid callback.' ), |
|
161 '6.5.0' |
|
162 ); |
|
163 return false; |
|
164 } |
|
165 |
|
166 // Validates that the uses_context parameter is an array. |
|
167 if ( isset( $source_properties['uses_context'] ) && ! is_array( $source_properties['uses_context'] ) ) { |
|
168 _doing_it_wrong( |
|
169 __METHOD__, |
|
170 __( 'The "uses_context" parameter must be an array.' ), |
|
171 '6.5.0' |
|
172 ); |
|
173 return false; |
|
174 } |
|
175 |
|
176 if ( ! empty( array_diff( array_keys( $source_properties ), $this->allowed_source_properties ) ) ) { |
|
177 _doing_it_wrong( |
|
178 __METHOD__, |
|
179 __( 'The $source_properties array contains invalid properties.' ), |
|
180 '6.5.0' |
|
181 ); |
|
182 return false; |
|
183 } |
|
184 |
|
185 $source = new WP_Block_Bindings_Source( |
|
186 $source_name, |
|
187 $source_properties |
|
188 ); |
|
189 |
|
190 $this->sources[ $source_name ] = $source; |
|
191 |
|
192 // Adds `uses_context` defined by block bindings sources. |
|
193 add_filter( |
|
194 'get_block_type_uses_context', |
|
195 function ( $uses_context, $block_type ) use ( $source ) { |
|
196 if ( ! in_array( $block_type->name, $this->supported_blocks, true ) || empty( $source->uses_context ) ) { |
|
197 return $uses_context; |
|
198 } |
|
199 // Use array_values to reset the array keys. |
|
200 return array_values( array_unique( array_merge( $uses_context, $source->uses_context ) ) ); |
|
201 }, |
|
202 10, |
|
203 2 |
|
204 ); |
|
205 |
|
206 return $source; |
|
207 } |
|
208 |
|
209 /** |
|
210 * Unregisters a block bindings source. |
|
211 * |
|
212 * @since 6.5.0 |
|
213 * |
|
214 * @param string $source_name Block bindings source name including namespace. |
|
215 * @return WP_Block_Bindings_Source|false The unregistered block bindings source on success and `false` otherwise. |
|
216 */ |
|
217 public function unregister( string $source_name ) { |
|
218 if ( ! $this->is_registered( $source_name ) ) { |
|
219 _doing_it_wrong( |
|
220 __METHOD__, |
|
221 /* translators: %s: Block bindings source name. */ |
|
222 sprintf( __( 'Block binding "%s" not found.' ), $source_name ), |
|
223 '6.5.0' |
|
224 ); |
|
225 return false; |
|
226 } |
|
227 |
|
228 $unregistered_source = $this->sources[ $source_name ]; |
|
229 unset( $this->sources[ $source_name ] ); |
|
230 |
|
231 return $unregistered_source; |
|
232 } |
|
233 |
|
234 /** |
|
235 * Retrieves the list of all registered block bindings sources. |
|
236 * |
|
237 * @since 6.5.0 |
|
238 * |
|
239 * @return WP_Block_Bindings_Source[] The array of registered sources. |
|
240 */ |
|
241 public function get_all_registered() { |
|
242 return $this->sources; |
|
243 } |
|
244 |
|
245 /** |
|
246 * Retrieves a registered block bindings source. |
|
247 * |
|
248 * @since 6.5.0 |
|
249 * |
|
250 * @param string $source_name The name of the source. |
|
251 * @return WP_Block_Bindings_Source|null The registered block bindings source, or `null` if it is not registered. |
|
252 */ |
|
253 public function get_registered( string $source_name ) { |
|
254 if ( ! $this->is_registered( $source_name ) ) { |
|
255 return null; |
|
256 } |
|
257 |
|
258 return $this->sources[ $source_name ]; |
|
259 } |
|
260 |
|
261 /** |
|
262 * Checks if a block bindings source is registered. |
|
263 * |
|
264 * @since 6.5.0 |
|
265 * |
|
266 * @param string $source_name The name of the source. |
|
267 * @return bool `true` if the block bindings source is registered, `false` otherwise. |
|
268 */ |
|
269 public function is_registered( $source_name ) { |
|
270 return isset( $this->sources[ $source_name ] ); |
|
271 } |
|
272 |
|
273 /** |
|
274 * Wakeup magic method. |
|
275 * |
|
276 * @since 6.5.0 |
|
277 */ |
|
278 public function __wakeup() { |
|
279 if ( ! $this->sources ) { |
|
280 return; |
|
281 } |
|
282 if ( ! is_array( $this->sources ) ) { |
|
283 throw new UnexpectedValueException(); |
|
284 } |
|
285 foreach ( $this->sources as $value ) { |
|
286 if ( ! $value instanceof WP_Block_Bindings_Source ) { |
|
287 throw new UnexpectedValueException(); |
|
288 } |
|
289 } |
|
290 } |
|
291 |
|
292 /** |
|
293 * Utility method to retrieve the main instance of the class. |
|
294 * |
|
295 * The instance will be created if it does not exist yet. |
|
296 * |
|
297 * @since 6.5.0 |
|
298 * |
|
299 * @return WP_Block_Bindings_Registry The main instance. |
|
300 */ |
|
301 public static function get_instance() { |
|
302 if ( null === self::$instance ) { |
|
303 self::$instance = new self(); |
|
304 } |
|
305 |
|
306 return self::$instance; |
|
307 } |
|
308 } |