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 API: Add block bindings "Post data" source #57260

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
2 changes: 1 addition & 1 deletion lib/block-supports/pattern.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @param WP_Block_Type $block_type Block Type.
*/
function gutenberg_register_pattern_support( $block_type ) {
$pattern_support = property_exists( $block_type, 'supports' ) ? _wp_array_get( $block_type->supports, array( '__experimentalConnections' ), false ) : false;
$pattern_support = property_exists( $block_type, 'supports' ) ? _wp_array_get( $block_type->supports, array( '__experimentalBlockBindings' ), false ) : false;

if ( $pattern_support ) {
if ( ! $block_type->uses_context ) {
Expand Down
112 changes: 112 additions & 0 deletions lib/experimental/block-bindings/html-processing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php
/**
* Define the mechanism to replpace the HTML depending on the block attributes.
*
* @package gutenberg
*/

if ( ! function_exists( 'block_bindings_replace_html' ) ) {
/**
* Depending on the block attributes, replace the proper HTML based on the value returned by the source.
*
* @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.
*/
function block_bindings_replace_html( $block_content, $block_name, $block_attr, $source_value ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
if ( null === $block_type ) {
return;
}

// Depending on the attribute source, the processing will be different.
// TODO: Get the type from the block attribute definition and modify/validate the value returned by the source if needed.
switch ( $block_type->attributes[ $block_attr ]['source'] ) {
case 'html':
case 'rich-text':
$p = 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.
$p->next_tag();
$p->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 ) {
$parent_tag = $p->get_tag();
$parent_tag_names = $p->get_attribute_names_with_prefix( '' );
$parent_tag_attrs = array();
foreach ( $parent_tag_names as $name ) {
$parent_tag_attrs[ $name ] = $p->get_attribute( $name );
}
}

foreach ( $selectors as $selector ) {
// If the parent tag, or any of its children, matches the selector, replace the HTML.
if ( strcasecmp( $p->get_tag( $selector ), $selector ) === 0 || $p->next_tag(
array(
'tag_name' => $selector,
)
) ) {
$p->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_tag_names = $p->get_attribute_names_with_prefix( '' );
$selector_tag_attrs = array();
foreach ( $selector_tag_names as $name ) {
$selector_tag_attrs[ $name ] = $p->get_attribute( $name );
}
$selector_markup = "<$selector>" . esc_html( $source_value ) . "</$selector>";
$p2 = new WP_HTML_Tag_Processor( $selector_markup );
$p2->next_tag();
foreach ( $selector_tag_attrs as $attribute_key => $attribute_value ) {
$p2->set_attribute( $attribute_key, $attribute_value );
}
$selector_updated_html = $p2->get_updated_html();
if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) {
return $selector_updated_html;
}
if ( 'core/button' === $block_name ) {
$markup = "<$parent_tag>$selector_updated_html</$parent_tag>";
$p3 = new WP_HTML_Tag_Processor( $markup );
$p3->next_tag();
foreach ( $parent_tag_attrs as $attribute_key => $attribute_value ) {
$p3->set_attribute( $attribute_key, $attribute_value );
}
return $p3->get_updated_html();
}
} else {
$p->seek( 'iterate-selectors' );
}
}
$p->release_bookmark( 'iterate-selectors' );
return $block_content;

case 'attribute':
$p = new WP_HTML_Tag_Processor( $block_content );
if ( ! $p->next_tag(
array(
// TODO: build the query from CSS selector.
'tag_name' => $block_type->attributes[ $block_attr ]['selector'],
)
) ) {
return $block_content;
}
$p->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) );
return $p->get_updated_html();
break;

default:
return $block_content;
break;
}
return;
}
}
21 changes: 21 additions & 0 deletions lib/experimental/block-bindings/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* Require the necessary files.
*
* @package gutenberg
*/

require_once __DIR__ . '/sources/index.php';
require_once __DIR__ . '/html-processing.php';

// Register the sources.
$gutenberg_experiments = get_option( 'gutenberg-experiments' );
if ( $gutenberg_experiments ) {
if ( array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments ) ) {
require_once __DIR__ . '/sources/pattern.php';
}
if ( array_key_exists( 'gutenberg-block-bindings', $gutenberg_experiments ) ) {
require_once __DIR__ . '/sources/post-meta.php';
require_once __DIR__ . '/sources/post-data.php';
}
}
24 changes: 24 additions & 0 deletions lib/experimental/block-bindings/sources/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
/**
* Define the mechanism to add new sources available in the block bindings API.
*
* @package gutenberg
*/

global $block_bindings_sources;
$block_bindings_sources = array();
if ( ! function_exists( 'register_block_bindings_source' ) ) {
/**
* Function to register a new source.
*
* @param string $source_name The name of the source.
* @param function $source_args List of arguments for the block bindings source:
* - label: The label of the source.
* - apply: The callback executed when the source is processed in the server.
* @return void
*/
function register_block_bindings_source( $source_name, $source_args ) {
global $block_bindings_sources;
$block_bindings_sources[ $source_name ] = $source_args;
}
}
23 changes: 23 additions & 0 deletions lib/experimental/block-bindings/sources/pattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* Add the metadata source to the block bindings API.
*
* @package gutenberg
*/

if ( function_exists( 'register_block_bindings_source' ) ) {
$pattern_source_callback = function ( $source_attrs, $block_content, $block, $block_instance ) {
if ( ! _wp_array_get( $block_instance->attributes, array( 'metadata', 'id' ), false ) ) {
return;
}
$block_id = $block_instance->attributes['metadata']['id'];
return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id ), false );
};
register_block_bindings_source(
'pattern_attributes',
array(
'label' => __( 'Pattern Attributes', 'gutenberg' ),
'apply' => $pattern_source_callback,
)
);
}
26 changes: 26 additions & 0 deletions lib/experimental/block-bindings/sources/post-data.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* Add the site_data source to the block bindings API.
*
* @package gutenberg
*/

if ( function_exists( 'register_block_bindings_source' ) ) {
$post_data_source_callback = function ( $source_attrs ) {
// Use the postId attribute if available, otherwise use the context.
if ( isset( $source_attrs['postId'] ) ) {
$post_id = $source_attrs['postId'];
} else {
// I tried using $block_instance->context['postId'] but it wasn't available in the image block.
$post_id = get_the_ID();
}
return get_post( $post_id )->{$source_attrs['value']};
};
register_block_bindings_source(
'post_data',
array(
'label' => __( 'Post Data', 'gutenberg' ),
'apply' => $post_data_source_callback,
)
);
}
27 changes: 27 additions & 0 deletions lib/experimental/block-bindings/sources/post-meta.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* Add the post_meta source to the block bindings API.
*
* @package gutenberg
*/

if ( function_exists( 'register_block_bindings_source' ) ) {
$post_meta_source_callback = function ( $source_attrs ) {
// Use the postId attribute if available, otherwise use the context.
if ( isset( $source_attrs['postId'] ) ) {
$post_id = $source_attrs['postId'];
} else {
// I tried using $block_instance->context['postId'] but it wasn't available in the image block.
$post_id = get_the_ID();
}

return get_post_meta( $post_id, $source_attrs['value'], true );
};
register_block_bindings_source(
'post_meta',
array(
'label' => __( 'Post Meta', 'gutenberg' ),
'apply' => $post_meta_source_callback,
)
);
}
Loading
Loading