forked from WordPress/wordpress-develop
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclass-wp-block-bindings.php
More file actions
171 lines (159 loc) · 6.92 KB
/
class-wp-block-bindings.php
File metadata and controls
171 lines (159 loc) · 6.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<?php
/**
* Block Bindings API: WP_Block_Bindings class.
*
* Support for overriding content in blocks by connecting them to different sources.
*
* @package WordPress
* @subpackage Block Bindings
*/
/**
* Core class used to define supported blocks, register sources, and populate HTML with content from those sources.
*
* @since 6.5.0
*/
class WP_Block_Bindings {
/**
* Holds the registered block bindings sources, keyed by source identifier.
*
* @since 6.5.0
*
* @var array
*/
private $sources = array();
/**
* Function to register a new block binding source.
*
* Sources are used to override block's original attributes with a value
* coming from the source. Once a source is registered, it can be used by a
* block by setting its `metadata.bindings` attribute to a value that refers
* to the source.
*
* @since 6.5.0
*
* @param string $source_name The name of the source.
* @param array $source_args The array of arguments that are used to register a source. The array has two elements:
* 1. string $label The label of the source.
* 2. callback $apply A callback
* executed when the source is processed during
* block rendering. The callback should have the
* following signature:
*
* `function (object $source_attrs, object $block_instance, string $attribute_name): string`
* - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}.
* - @param object $block_instance: The block instance.
* - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context.
* The callback should return a string that will be used to override the block's original value.
*
* @return void
*/
public function register_source( string $source_name, array $source_args ) {
$this->sources[ $source_name ] = $source_args;
}
/**
* Depending on the block attributes, replace the HTML based on the value returned by the source.
*
* @since 6.5.0
*
* @param string $block_content Block content.
* @param string $block_name The name of the block to process.
* @param string $block_attr The attribute of the block we want to process.
* @param string $source_value The value used to replace the HTML.
*/
public function replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
if ( null === $block_type || ! isset( $block_type->attributes[ $block_attr ] ) ) {
return $block_content;
}
// Depending on the attribute source, the processing will be different.
switch ( $block_type->attributes[ $block_attr ]['source'] ) {
case 'html':
case 'rich-text':
$block_reader = new WP_HTML_Tag_Processor( $block_content );
// TODO: Support for CSS selectors whenever they are ready in the HTML API.
// In the meantime, support comma-separated selectors by exploding them into an array.
$selectors = explode( ',', $block_type->attributes[ $block_attr ]['selector'] );
// Add a bookmark to the first tag to be able to iterate over the selectors.
$block_reader->next_tag();
$block_reader->set_bookmark( 'iterate-selectors' );
// TODO: This shouldn't be needed when the `set_inner_html` function is ready.
// Store the parent tag and its attributes to be able to restore them later in the button.
// The button block has a wrapper while the paragraph and heading blocks don't.
if ( 'core/button' === $block_name ) {
$button_wrapper = $block_reader->get_tag();
$button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
$button_wrapper_attrs = array();
foreach ( $button_wrapper_attribute_names as $name ) {
$button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name );
}
}
foreach ( $selectors as $selector ) {
// If the parent tag, or any of its children, matches the selector, replace the HTML.
if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag(
array(
'tag_name' => $selector,
)
) ) {
$block_reader->release_bookmark( 'iterate-selectors' );
// TODO: Use `set_inner_html` method whenever it's ready in the HTML API.
// Until then, it is hardcoded for the paragraph, heading, and button blocks.
// Store the tag and its attributes to be able to restore them later.
$selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
$selector_attrs = array();
foreach ( $selector_attribute_names as $name ) {
$selector_attrs[ $name ] = $block_reader->get_attribute( $name );
}
$selector_markup = "<$selector>" . wp_kses_post( $source_value ) . "</$selector>";
$amended_content = new WP_HTML_Tag_Processor( $selector_markup );
$amended_content->next_tag();
foreach ( $selector_attrs as $attribute_key => $attribute_value ) {
$amended_content->set_attribute( $attribute_key, $attribute_value );
}
if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) {
return $amended_content->get_updated_html();
}
if ( 'core/button' === $block_name ) {
$button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}</$button_wrapper>";
$amended_button = new WP_HTML_Tag_Processor( $button_markup );
$amended_button->next_tag();
foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) {
$amended_button->set_attribute( $attribute_key, $attribute_value );
}
return $amended_button->get_updated_html();
}
} else {
$block_reader->seek( 'iterate-selectors' );
}
}
$block_reader->release_bookmark( 'iterate-selectors' );
return $block_content;
case 'attribute':
$amended_content = new WP_HTML_Tag_Processor( $block_content );
if ( ! $amended_content->next_tag(
array(
// TODO: build the query from CSS selector.
'tag_name' => $block_type->attributes[ $block_attr ]['selector'],
)
) ) {
return $block_content;
}
$amended_content->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) );
return $amended_content->get_updated_html();
break;
default:
return $block_content;
break;
}
return;
}
/**
* Retrieves the list of registered block sources.
*
* @since 6.5.0
*
* @return array The array of registered sources.
*/
public function get_sources() {
return $this->sources;
}
}