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

Block bindings: Add support for image caption #6838

Open
wants to merge 18 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 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
60 changes: 59 additions & 1 deletion src/wp-includes/class-wp-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ private function process_block_bindings() {
$supported_block_attributes = array(
'core/paragraph' => array( 'content' ),
'core/heading' => array( 'content' ),
'core/image' => array( 'id', 'url', 'title', 'alt' ),
'core/image' => array( 'id', 'url', 'title', 'alt', 'caption' ),
'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ),
);

Expand Down Expand Up @@ -333,6 +333,64 @@ private function replace_html( string $block_content, string $attribute_name, $s
switch ( $block_type->attributes[ $attribute_name ]['source'] ) {
case 'html':
case 'rich-text':
if ( 'core/image' === $this->name && 'caption' === $attribute_name ) {
// Create private anonymous class until the HTML API provides `set_inner_html` method.
$bindings_processor = new class( $block_content, WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE ) extends WP_HTML_Processor {
Copy link
Member

Choose a reason for hiding this comment

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

this is an interesting side-effect of that choice. I think we should rename that constant to something scarier, like what its value is.

I kind of like that it makes anonymous classes stand out more.

Copy link
Member

Choose a reason for hiding this comment

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

@sirreal I think this is a really good example of a part of the HTML Processor we're going to have to improve.

not only is the unlock code ugly here (ugly, because it shouts how this is work-around territory), but also by calling the constructor directly we bypass setting up the context node and the HTML node. in effect, this is starting as a full parser instead of a fragment parser.

I wonder how we can better allow subclassing without this hassle and risk.

SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved
/**
* Replace the inner content of a figcaption element with the passed content.
*
* DO NOT COPY THIS METHOD.
* THE HTML PROCESSOR WILL HAVE A PROPER METHOD.
* USE IT INSTEAD.
*
* @since 6.7.0
*
* @param string $new_content New content to insert in the figcaption element.
* @return bool Whether the inner content was properly replaced.
*/
public function set_figcaption_inner_html( $new_content ) {
// Check that the processor is paused on an opener tag.
if ( 'FIGCAPTION' !== $this->get_tag() || $this->is_tag_closer() ) {
return false;
}

// Set position of the opening tag.
$this->set_bookmark( 'opening' );

// Once this element closes the depth will be one shallower than it is now.
$depth = $this->get_current_depth();
while ( $this->next_token() && $this->get_current_depth() >= $depth ) {
// This is inside the FIGCAPTION element.
}

if ( null !== $this->get_last_error() || $this->paused_at_incomplete_token() ) {
return false;
}
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved

// Set position of the closing tag.
$this->set_bookmark( 'closing' );

$opening = $this->bookmarks['_opening'];
$closing = $this->bookmarks['_closing'];
$start = $opening->start + $opening->length;

$this->lexical_updates[] = new WP_HTML_Text_Replacement(
$start,
$closing->start - $start,
wp_kses_post( $new_content )
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved
);

return true;
}
};

$block_reader = $bindings_processor::create_fragment( $block_content );
Copy link
Member

Choose a reason for hiding this comment

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

whoah. this is unrelated to your changes @SantosGuillamot, but this line really confuses me, purely based on how PHP's anonymous classes work. @sirreal maybe what I wrote above is fine, and when we call new class( ...$args ) they don't matter?

yes, I just tested and this is odd. calling $bindings_processor->next_token() will crash because it never finds the context node. this means that it looks like an HTML Processor but isn't. it's better as a HTML Processor Builder.

@SantosGuillamot I wonder if we could take advantage of this in line 338 and instead of passing $block_content pass something like "do not use this, it will not work. it's only here to create a subclass and call the static creator method"

Copy link
Author

Choose a reason for hiding this comment

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

I am not 100% this is what you meant, but I made this change to address the feedback. Let me know if that's not what you were suggesting.

if ( $block_reader->next_tag( 'figcaption' ) ) {
$block_reader->set_figcaption_inner_html( $source_value );
}
return $block_reader->get_updated_html();
}

$block_reader = new WP_HTML_Tag_Processor( $block_content );

// TODO: Support for CSS selectors whenever they are ready in the HTML API.
Expand Down
37 changes: 37 additions & 0 deletions tests/phpunit/tests/block-bindings/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,41 @@ public function test_default_binding_for_pattern_overrides() {
'The `__default` attribute should be replaced with the real attribute prior to the callback.'
);
}

/**
* Tests that replacing inner text for bound attributes works as expected.
*
* @ticket 61466
*
* @covers WP_Block::process_block_bindings
*/
public function test_replacing_inner_text_with_block_bindings_value() {
$get_value_callback = function () {
return '$12.50';
};

register_block_bindings_source(
self::SOURCE_NAME,
array(
'label' => self::SOURCE_LABEL,
'get_value_callback' => $get_value_callback,
)
);

$block_content = <<<HTML
<!-- wp:image {"metadata":{"bindings":{"caption":{"source":"test/source"}}}} -->
<figure class="wp-block-image"><img src="https://example.com/image.jpg" /><figcaption class="wp-element-caption">Default value</figcaption></figure>
<!-- /wp:image -->
HTML;

$parsed_blocks = parse_blocks( $block_content );
$block = new WP_Block( $parsed_blocks[0] );
$result = $block->render();

$this->assertSame(
'<figure class="wp-block-image"><img src="https://example.com/image.jpg" /><figcaption class="wp-element-caption">$12.50</figcaption></figure>',
trim( $result ),
'The image caption should be updated with the value returned by the source.'
);
}
}
Loading