Changeset 57514
- Timestamp:
- 02/01/2024 12:52:54 PM (8 months ago)
- Location:
- trunk
- Files:
-
- 5 added
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/block-bindings.php
r57500 r57514 20 20 * @since 6.5.0 21 21 * 22 * @param string $source_name The name of the source. 22 * @param string $source_name The name of the source. It must be a string containing a namespace prefix, i.e. 23 * `my-plugin/my-custom-source`. It must only contain lowercase alphanumeric 24 * characters, the forward slash `/` and dashes. 23 25 * @param array $source_properties { 24 26 * The array of arguments that are used to register a source. -
trunk/src/wp-includes/class-wp-block-bindings-registry.php
r57500 r57514 43 43 * @since 6.5.0 44 44 * 45 * @param string $source_name The name of the source. 45 * @param string $source_name The name of the source. It must be a string containing a namespace prefix, i.e. 46 * `my-plugin/my-custom-source`. It must only contain lowercase alphanumeric 47 * characters, the forward slash `/` and dashes. 46 48 * @param array $source_properties { 47 49 * The array of arguments that are used to register a source. -
trunk/src/wp-includes/class-wp-block.php
r57493 r57514 193 193 194 194 /** 195 * Processes the block bindings in block's attributes. 196 * 197 * A block might contain bindings in its attributes. Bindings are mappings 198 * between an attribute of the block and a source. A "source" is a function 199 * registered with `register_block_bindings_source()` that defines how to 200 * retrieve a value from outside the block, e.g. from post meta. 201 * 202 * This function will process those bindings and replace the HTML with the value of the binding. 203 * The value is retrieved from the source of the binding. 204 * 205 * ### Example 206 * 207 * The "bindings" property for an Image block might look like this: 208 * 209 * ```json 210 * { 211 * "metadata": { 212 * "bindings": { 213 * "title": { 214 * "source": "post_meta", 215 * "args": { "key": "text_custom_field" } 216 * }, 217 * "url": { 218 * "source": "post_meta", 219 * "args": { "key": "url_custom_field" } 220 * } 221 * } 222 * } 223 * } 224 * ``` 225 * 226 * The above example will replace the `title` and `url` attributes of the Image 227 * block with the values of the `text_custom_field` and `url_custom_field` post meta. 228 * 229 * @access private 230 * @since 6.5.0 231 * 232 * @param string $block_content Block content. 233 * @param array $block The full block, including name and attributes. 234 */ 235 private function process_block_bindings( $block_content ) { 236 $block = $this->parsed_block; 237 238 // Allowed blocks that support block bindings. 239 // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? 240 $allowed_blocks = array( 241 'core/paragraph' => array( 'content' ), 242 'core/heading' => array( 'content' ), 243 'core/image' => array( 'url', 'title', 'alt' ), 244 'core/button' => array( 'url', 'text' ), 245 ); 246 247 // If the block doesn't have the bindings property, isn't one of the allowed 248 // block types, or the bindings property is not an array, return the block content. 249 if ( ! isset( $block['attrs']['metadata']['bindings'] ) || 250 ! is_array( $block['attrs']['metadata']['bindings'] ) || 251 ! isset( $allowed_blocks[ $this->name ] ) 252 ) { 253 return $block_content; 254 } 255 256 $block_bindings_sources = get_all_registered_block_bindings_sources(); 257 $modified_block_content = $block_content; 258 foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { 259 // If the attribute is not in the list, process next attribute. 260 if ( ! in_array( $binding_attribute, $allowed_blocks[ $this->name ], true ) ) { 261 continue; 262 } 263 // If no source is provided, or that source is not registered, process next attribute. 264 if ( ! isset( $binding_source['source'] ) || ! is_string( $binding_source['source'] ) || ! isset( $block_bindings_sources[ $binding_source['source'] ] ) ) { 265 continue; 266 } 267 268 $source_callback = $block_bindings_sources[ $binding_source['source'] ]['get_value_callback']; 269 // Get the value based on the source. 270 if ( ! isset( $binding_source['args'] ) ) { 271 $source_args = array(); 272 } else { 273 $source_args = $binding_source['args']; 274 } 275 $source_value = call_user_func_array( $source_callback, array( $source_args, $this, $binding_attribute ) ); 276 // If the value is null, process next attribute. 277 if ( is_null( $source_value ) ) { 278 continue; 279 } 280 281 // Process the HTML based on the block and the attribute. 282 $modified_block_content = $this->replace_html( $modified_block_content, $this->name, $binding_attribute, $source_value ); 283 } 284 return $modified_block_content; 285 } 286 287 /** 288 * Depending on the block attributes, replace the HTML based on the value returned by the source. 289 * 290 * @since 6.5.0 291 * 292 * @param string $block_content Block content. 293 * @param string $block_name The name of the block to process. 294 * @param string $block_attr The attribute of the block we want to process. 295 * @param string $source_value The value used to replace the HTML. 296 */ 297 private function replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { 298 $block_type = $this->block_type; 299 if ( null === $block_type || ! isset( $block_type->attributes[ $block_attr ] ) ) { 300 return $block_content; 301 } 302 303 // Depending on the attribute source, the processing will be different. 304 switch ( $block_type->attributes[ $block_attr ]['source'] ) { 305 case 'html': 306 case 'rich-text': 307 $block_reader = new WP_HTML_Tag_Processor( $block_content ); 308 309 // TODO: Support for CSS selectors whenever they are ready in the HTML API. 310 // In the meantime, support comma-separated selectors by exploding them into an array. 311 $selectors = explode( ',', $block_type->attributes[ $block_attr ]['selector'] ); 312 // Add a bookmark to the first tag to be able to iterate over the selectors. 313 $block_reader->next_tag(); 314 $block_reader->set_bookmark( 'iterate-selectors' ); 315 316 // TODO: This shouldn't be needed when the `set_inner_html` function is ready. 317 // Store the parent tag and its attributes to be able to restore them later in the button. 318 // The button block has a wrapper while the paragraph and heading blocks don't. 319 if ( 'core/button' === $block_name ) { 320 $button_wrapper = $block_reader->get_tag(); 321 $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); 322 $button_wrapper_attrs = array(); 323 foreach ( $button_wrapper_attribute_names as $name ) { 324 $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name ); 325 } 326 } 327 328 foreach ( $selectors as $selector ) { 329 // If the parent tag, or any of its children, matches the selector, replace the HTML. 330 if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag( 331 array( 332 'tag_name' => $selector, 333 ) 334 ) ) { 335 $block_reader->release_bookmark( 'iterate-selectors' ); 336 337 // TODO: Use `set_inner_html` method whenever it's ready in the HTML API. 338 // Until then, it is hardcoded for the paragraph, heading, and button blocks. 339 // Store the tag and its attributes to be able to restore them later. 340 $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); 341 $selector_attrs = array(); 342 foreach ( $selector_attribute_names as $name ) { 343 $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); 344 } 345 $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . "</$selector>"; 346 $amended_content = new WP_HTML_Tag_Processor( $selector_markup ); 347 $amended_content->next_tag(); 348 foreach ( $selector_attrs as $attribute_key => $attribute_value ) { 349 $amended_content->set_attribute( $attribute_key, $attribute_value ); 350 } 351 if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) { 352 return $amended_content->get_updated_html(); 353 } 354 if ( 'core/button' === $block_name ) { 355 $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}</$button_wrapper>"; 356 $amended_button = new WP_HTML_Tag_Processor( $button_markup ); 357 $amended_button->next_tag(); 358 foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) { 359 $amended_button->set_attribute( $attribute_key, $attribute_value ); 360 } 361 return $amended_button->get_updated_html(); 362 } 363 } else { 364 $block_reader->seek( 'iterate-selectors' ); 365 } 366 } 367 $block_reader->release_bookmark( 'iterate-selectors' ); 368 return $block_content; 369 370 case 'attribute': 371 $amended_content = new WP_HTML_Tag_Processor( $block_content ); 372 if ( ! $amended_content->next_tag( 373 array( 374 // TODO: build the query from CSS selector. 375 'tag_name' => $block_type->attributes[ $block_attr ]['selector'], 376 ) 377 ) ) { 378 return $block_content; 379 } 380 $amended_content->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) ); 381 return $amended_content->get_updated_html(); 382 break; 383 384 default: 385 return $block_content; 386 break; 387 } 388 return; 389 } 390 391 392 /** 195 393 * Generates the render output for the block. 196 394 * … … 286 484 } 287 485 } 486 487 // Process the block bindings for this block, if any are registered. This 488 // will replace the block content with the value from a registered binding source. 489 $block_content = $this->process_block_bindings( $block_content ); 288 490 289 491 /** -
trunk/src/wp-settings.php
r57503 r57514 377 377 require ABSPATH . WPINC . '/class-wp-script-modules.php'; 378 378 require ABSPATH . WPINC . '/script-modules.php'; 379 require ABSPATH . WPINC . '/block-bindings/sources/post-meta.php'; 380 require ABSPATH . WPINC . '/block-bindings/sources/pattern.php'; 379 381 require ABSPATH . WPINC . '/interactivity-api.php'; 380 382 -
trunk/tests/phpunit/includes/functions.php
r56472 r57514 340 340 */ 341 341 function _unhook_block_registration() { 342 // Block types. 342 343 require __DIR__ . '/unregister-blocks-hooks.php'; 343 344 remove_action( 'init', 'register_core_block_types_from_metadata' ); … … 345 346 remove_action( 'init', 'register_block_core_widget_group' ); 346 347 remove_action( 'init', 'register_core_block_types_from_metadata' ); 348 349 // Block binding sources. 350 remove_action( 'init', '_register_block_bindings_pattern_overrides_source' ); 351 remove_action( 'init', '_register_block_bindings_post_meta_source' ); 347 352 } 348 353 tests_add_filter( 'init', '_unhook_block_registration', 1000 ); -
trunk/tests/phpunit/tests/block-bindings/register.php
r57385 r57514 18 18 19 19 /** 20 * Set up before each test. 21 * 22 * @since 6.5.0 23 */ 24 public function set_up() { 25 foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { 26 unregister_block_bindings_source( $source_name ); 27 } 28 29 parent::set_up(); 30 } 31 32 /** 20 33 * Tear down after each test. 21 34 * … … 24 37 public function tear_down() { 25 38 foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { 26 if ( str_starts_with( $source_name, 'test/' ) ) { 27 unregister_block_bindings_source( $source_name ); 28 } 39 unregister_block_bindings_source( $source_name ); 29 40 } 30 41
Note: See TracChangeset
for help on using the changeset viewer.