From fffaab7678c960e86eb745d281bb044a6ab4923e Mon Sep 17 00:00:00 2001 From: Michal Date: Wed, 9 Aug 2023 16:48:39 +0100 Subject: [PATCH] Allow contents of Paragraph to be "connected" to a meta custom field (#53247) * Add custom fields experimental setting * Add connections experimental setting, block inspector control and block attributes * Add "connections" block attribute and get the meta field * Get rid of a phpcs warning * Update attribute acces and source definition Changed the attribute access from `$block_type->attributes` to `$block['attrs']`. Also updated attribute 'source' value from 'meta' to 'meta_fields'. These changes have been applied in both `blocks.php` and block attributes declaration in `block.json`. * Updated the 'connections' property to include 'attributes' * Add `attributes` to TextControl value * Add `attributes` to TextControl value * Wrap the PHP code in experimental flag * Update parens * Return early if there is no support for connections * Updated block type checks Rearranged the block type checks. Now, the code first checks if a given block is in the accepted types list (Paragraph and Image) and then it checks if the block type is null. This is a slight revamp of the existing checks for block support and block type acceptance. * Updated experimental blocks logic - Removed unused test functions. - Update the comment above the tag name query in the `custom-sources` to clarify future improvements for supporting more block types. * Add a comment in meta.php * Update comment * Clear the paragraph if meta value is cleared * Use placeholders instead of content * Update names of HTML processor instances in meta.php * Add extra comments * Rephrase the placeholder comment * Check if current block is a paragraph or image * Abstract the attribute name to use for the connection * rename `connections` to `__experimentalConnections` * Do not export `addAttribute()` or `withInspectorControl()` * Renamed '__experimentalConnections' to 'connections' in block settings * Renamed '__experimentalConnections' to 'connections' in block settings * Remove `connections` attribute from block.json * Renamed '__experimentalConnections' to 'connections' in block settings * Remove `get_updated_html() . ''` in meta.php * Add an allowlist of blocks/attributes * Refactor blocks.php & custom sources - Separate the "connections" into own file. - Move the code that renders the HTML using the meta fields back into blocks.php * Remove trailing comma? * Update naming: - "custom sources" -> connection sources - use "block connections" consistently --------- Co-authored-by: Carlos Bravo --- lib/experimental/blocks.php | 104 ++++++++++++++++++ lib/experimental/connection-sources/index.php | 15 +++ .../block-library/src/paragraph/block.json | 1 + 3 files changed, 120 insertions(+) create mode 100644 lib/experimental/connection-sources/index.php diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index fafeba00e3b3f..febb404394a4f 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -121,3 +121,107 @@ function gutenberg_register_metadata_attribute( $args ) { return $args; } add_filter( 'register_block_type_args', 'gutenberg_register_metadata_attribute' ); + + +$gutenberg_experiments = get_option( 'gutenberg-experiments' ); +if ( $gutenberg_experiments && array_key_exists( 'gutenberg-connections', $gutenberg_experiments ) ) { + /** + * Renders the block meta attributes. + * + * @param string $block_content Block Content. + * @param array $block Block attributes. + * @param WP_Block $block_instance The block instance. + */ + function gutenberg_render_block_connections( $block_content, $block, $block_instance ) { + $connection_sources = require __DIR__ . '/connection-sources/index.php'; + $block_type = $block_instance->block_type; + + // Allowlist of blocks that support block connections. + // Currently, we only allow the following blocks and attributes: + // - Paragraph: content. + // - Image: url. + $blocks_attributes_allowlist = array( + 'core/paragraph' => array( 'content' ), + 'core/image' => array( 'url' ), + ); + + // Whitelist of the block types that support block connections. + // Currently, we only allow the Paragraph and Image blocks to use block connections. + if ( ! in_array( $block['blockName'], array_keys( $blocks_attributes_allowlist ), true ) ) { + return $block_content; + } + + // If for some reason, the block type is not found, skip it. + if ( null === $block_type ) { + return $block_content; + } + + // If the block does not have support for block connections, skip it. + if ( ! block_has_support( $block_type, array( '__experimentalConnections' ), false ) ) { + return $block_content; + } + + // Get all the attributes that have a connection. + $connected_attributes = _wp_array_get( $block['attrs'], array( 'connections', 'attributes' ), false ); + if ( ! $connected_attributes ) { + return $block_content; + } + + foreach ( $connected_attributes as $attribute_name => $attribute_value ) { + + // If the attribute is not in the allowlist, skip it. + if ( ! in_array( $attribute_name, $blocks_attributes_allowlist[ $block['blockName'] ], true ) ) { + continue; + } + + // If the source value is not "meta_fields", skip it because the only supported + // connection source is meta (custom fields) for now. + if ( 'meta_fields' !== $attribute_value['source'] ) { + continue; + } + + // If the attribute does not have a source, skip it. + if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) { + continue; + } + + // If the attribute does not specify the name of the custom field, skip it. + if ( ! isset( $attribute_value['value'] ) ) { + continue; + } + + // Get the content from the connection source. + $custom_value = $connection_sources[ $attribute_value['source'] ]( + $block_instance, + $attribute_value['value'] + ); + + $tags = new WP_HTML_Tag_Processor( $block_content ); + $found = $tags->next_tag( + array( + // TODO: In the future, when blocks other than Paragraph and Image are + // supported, we should build the full query from CSS selector. + 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'], + ) + ); + if ( ! $found ) { + return $block_content; + }; + $tag_name = $tags->get_tag(); + $markup = "<$tag_name>$custom_value"; + $updated_tags = new WP_HTML_Tag_Processor( $markup ); + $updated_tags->next_tag(); + + // Get all the attributes from the original block and add them to the new markup. + $names = $tags->get_attribute_names_with_prefix( '' ); + foreach ( $names as $name ) { + $updated_tags->set_attribute( $name, $tags->get_attribute( $name ) ); + } + + return $updated_tags->get_updated_html(); + } + + return $block_content; + } + add_filter( 'render_block', 'gutenberg_render_block_connections', 10, 3 ); +} diff --git a/lib/experimental/connection-sources/index.php b/lib/experimental/connection-sources/index.php new file mode 100644 index 0000000000000..b63abcad96f62 --- /dev/null +++ b/lib/experimental/connection-sources/index.php @@ -0,0 +1,15 @@ + 'meta', + 'meta_fields' => function ( $block_instance, $meta_field ) { + // We should probably also check if the meta field exists but for now it's okay because + // if it doesn't, `get_post_meta()` will just return an empty string. + return get_post_meta( $block_instance->context['postId'], $meta_field, true ); + }, +); diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json index db30d95db8475..85f56f4a838f5 100644 --- a/packages/block-library/src/paragraph/block.json +++ b/packages/block-library/src/paragraph/block.json @@ -7,6 +7,7 @@ "description": "Start with the basic building block of all narrative.", "keywords": [ "text" ], "textdomain": "default", + "usesContext": [ "postId" ], "attributes": { "align": { "type": "string"