Make WordPress Core

source: trunk/src/wp-includes/class-wp-block-bindings-registry.php @ 57641

Last change on this file since 57641 was 57641, checked in by gziolo, 12 months ago

Editor: Merge uses_context defined by block bindings sources with block types

Adds logic that fixes the limitation for souces by allowing merging the uses_context defined by block bindings sources into supported block types. Each source defines the context it needs and it is added to the block types that are using the block bindings API.

Fixes #60525.
Props santosguillamot, gziolo, czapla, thekt12.

  • Property svn:eol-style set to native
File size: 8.8 KB
Line 
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 */
17final 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 array
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 array
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 callback $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 array    $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}
Note: See TracBrowser for help on using the repository browser.