Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow connecting "url" and "title" attributes of the Image block to custom fields. #53488

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 63 additions & 18 deletions lib/experimental/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,23 @@ function gutenberg_register_metadata_attribute( $args ) {

$gutenberg_experiments = get_option( 'gutenberg-experiments' );
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-connections', $gutenberg_experiments ) ) {

/**
* Renders the block meta attributes.
* Renders the block connections.
* Block connections allow to connect block attributes to custom fields or
* other data sources (in the future).
*
* Rendering block connections is a 3-step process:
* 1. Get the block attributes that have a connection. The connections are
* added in the editor and stored in the block attributes.
* 2. For each "connected" attribute, get the value from the connection
* source. For now, the only supported source is meta (custom fields).
* 3. Update the HTML of the block using the value from the connection. The
* HTML can be replaced in two ways:
* - If the attribute's "source" is `html`, replace the whole tag with the
* value from the connection.
* - If the attribute's "source" is `attribute`, replace the value of the
* corresponding HTML attribute with the value from the connection.
*
* @param string $block_content Block Content.
* @param array $block Block attributes.
Expand All @@ -142,7 +157,7 @@ function gutenberg_render_block_connections( $block_content, $block, $block_inst
// - Image: url.
$blocks_attributes_allowlist = array(
'core/paragraph' => array( 'content' ),
'core/image' => array( 'url' ),
'core/image' => array( 'url', 'title' ),
);

// Whitelist of the block types that support block connections.
Expand Down Expand Up @@ -191,22 +206,47 @@ function gutenberg_render_block_connections( $block_content, $block, $block_inst
}

// Get the content from the connection source.
$custom_value = $connection_sources[ $attribute_value['source'] ](
$connection_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;
};
$attribute_config = $block_type->attributes[ $attribute_name ];

// Generate the new block content with the custom value.
$block_content = gutenberg_render_block_with_connection_value( $block_content, $connection_value, $attribute_config );

}

return $block_content;
}
add_filter( 'render_block', 'gutenberg_render_block_connections', 10, 3 );


/**
* Renders the block with the custom value from a connection source.
*
* @param string $block_content The HTML of the block originally passed to the `render_block` filter.
* @param mixed $custom_value The value obtained from the connection source (e.g. custom field).
* @param array $attribute_config The configuration of the connected
* attribute. It should contain the following keys:
* - selector: The HTML selector of the element that should be updated.
* - source: The source of the connection. Currently, only "html" and "attribute" are supported.
* - attribute: The name of the attribute that should be updated. Only used if the `source` is "attribute".
*/
function gutenberg_render_block_with_connection_value( $block_content, $custom_value, $attribute_config ) {
$tags = new WP_HTML_Tag_Processor( $block_content );
$block_selector_tag = $tags->next_tag(
array(
'tag_name' => $attribute_config['selector'],
)
);
if ( ! $block_selector_tag ) {
return $block_content;
}

// If the source is "html", it means that we should replace the whole tag with the meta value.
if ( 'html' === $attribute_config['source'] ) {
$tag_name = $tags->get_tag();
$markup = "<$tag_name>$custom_value</$tag_name>";
$updated_tags = new WP_HTML_Tag_Processor( $markup );
Expand All @@ -217,11 +257,16 @@ function gutenberg_render_block_connections( $block_content, $block, $block_inst
foreach ( $names as $name ) {
$updated_tags->set_attribute( $name, $tags->get_attribute( $name ) );
}

return $updated_tags->get_updated_html();
}

return $block_content;
// If the source is an attribute, it means that we should replace the attribute value with the custom value.
} elseif ( 'attribute' === $attribute_config['source'] ) {
$tags->set_attribute( $attribute_config['attribute'], $custom_value );
return $tags->get_updated_html();

} else {
// We don't support other sources yet.
return $block_content;
}
}
add_filter( 'render_block', 'gutenberg_render_block_connections', 10, 3 );
}
4 changes: 3 additions & 1 deletion lib/experimental/connection-sources/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
return array(
'name' => 'meta',
'meta_fields' => function ( $block_instance, $meta_field ) {
global $post;

// 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 );
return get_post_meta( $post->ID, $meta_field, true );
},
);
99 changes: 58 additions & 41 deletions packages/block-editor/src/hooks/custom-fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@ function addAttribute( settings ) {
/**
* Override the default edit UI to include a new block inspector control for
* assigning a connection to blocks that has support for connections.
* Currently, only the `core/paragraph` block is supported and there is only a relation
* between paragraph content and a custom field.
* Currently, only the `core/paragraph` and `core/image` blocks are supported
* and only the `content` and `url` attributes of these blocks are supported respectively.
*
* @param {WPComponent} BlockEdit Original component.
*
* @return {WPComponent} Wrapped component.
*/
const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => {
const blocksAttributesAllowlist = {
'core/paragraph': [ 'content' ],
'core/image': [ 'url', 'title' ],
};

return ( props ) => {
const blockEditingMode = useBlockEditingMode();
const hasCustomFieldsSupport = hasBlockSupport(
Expand All @@ -62,9 +67,7 @@ const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => {
// If the block is a paragraph or image block, we need to know which
// attribute to use for the connection. Only the `content` attribute
// of the paragraph block and the `url` attribute of the image block are supported.
let attributeName;
if ( props.name === 'core/paragraph' ) attributeName = 'content';
if ( props.name === 'core/image' ) attributeName = 'url';
const attributeNames = blocksAttributesAllowlist[ props.name ];

if ( hasCustomFieldsSupport && props.isSelected ) {
return (
Expand All @@ -76,44 +79,58 @@ const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => {
title={ __( 'Connections' ) }
initialOpen={ true }
>
<TextControl
__nextHasNoMarginBottom
autoComplete="off"
label={ __( 'Custom field meta_key' ) }
value={
props.attributes?.connections
?.attributes?.[ attributeName ]
?.value || ''
}
onChange={ ( nextValue ) => {
if ( nextValue === '' ) {
props.setAttributes( {
connections: undefined,
[ attributeName ]: undefined,
placeholder: undefined,
} );
} else {
props.setAttributes( {
connections: {
attributes: {
// The attributeName will be either `content` or `url`.
[ attributeName ]: {
// Source will be variable, could be post_meta, user_meta, term_meta, etc.
// Could even be a custom source like a social media attribute.
source: 'meta_fields',
value: nextValue,
{ attributeNames.map( ( attributeName ) => (
<TextControl
key={ attributeName }
__nextHasNoMarginBottom
autoComplete="off"
placeholder={ attributeName }
label={ __( `Custom field meta_key` ) }
value={
props.attributes?.connections
?.attributes?.[ attributeName ]
?.value || ''
}
onChange={ ( nextValue ) => {
if ( nextValue === '' ) {
props.setAttributes( {
connections: {
attributes: {
...props.attributes
?.connections
?.attributes,
[ attributeName ]:
undefined,
},
},
},
[ attributeName ]: undefined,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting this to undefined will remove the block content in the editor in the case of the Image block because an <img> without src is invalid markup. However, setting the url to '' (empty string) works.

placeholder: sprintf(
'This content will be replaced on the frontend by the value of "%s" custom field.',
nextValue
),
} );
}
} }
/>
[ attributeName ]: '',
placeholder: undefined,
} );
} else {
props.setAttributes( {
connections: {
attributes: {
...props.attributes
?.connections
?.attributes,
[ attributeName ]: {
// Source will be variable in the future, could be post_meta, user_meta, term_meta, etc.
// Could even be a custom source like a social media attribute.
source: 'meta_fields',
value: nextValue,
},
},
},
[ attributeName ]: '',
placeholder: sprintf(
'This content will be replaced on the frontend by the value of "%s" custom field.',
nextValue
),
} );
}
} }
/>
) ) }
</PanelBody>
</InspectorControls>
) }
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/image/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"filter": {
"duotone": true
},
"__experimentalConnections": true,
"__experimentalBorder": {
"color": true,
"radius": true,
Expand Down
1 change: 0 additions & 1 deletion packages/block-library/src/paragraph/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"description": "Start with the basic building block of all narrative.",
"keywords": [ "text" ],
"textdomain": "default",
"usesContext": [ "postId" ],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed it per this comment We can get the id from $post->ID.

"attributes": {
"align": {
"type": "string"
Expand Down