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

Add ability to access block context from inside render_block filter #63626

Closed
2 tasks done
simom opened this issue Jul 16, 2024 · 15 comments
Closed
2 tasks done

Add ability to access block context from inside render_block filter #63626

simom opened this issue Jul 16, 2024 · 15 comments
Labels
[Feature] Block API API that allows to express the block paradigm. [Type] Bug An existing feature does not function as intended

Comments

@simom
Copy link

simom commented Jul 16, 2024

Description

I'm trying to extend the core block "list" and the child block "list-item" by adding a custom attribute. This attribute must also be defined as providesContext in the "list" block so it will be available to the child items.

I'm filtering blocks.registerBlockType by adding the attribute and the contexts, and in the editor, it works as expected. On the frontend, I'm filtering the list item render with render_block_core/list-item and I'm expecting to retrieve the context with $block->context['list/icon'], but the context is not present.

As suggested in another similar ticket, I've tried to add the use_context via the register_block_type_args filter, but without success. Is there a limitation in the core regarding the assignment of a providesContext?

Step-by-step reproduction instructions

JS file

function addAttributes( settings, name ) {
	if ( name !== 'core/list-item' && name !== 'core/list' ) {
		return settings;
	}

	let attrs = {
		'icon': {
			'type': 'object',
            "default": {
                "name": "",
                "svg" : "",
                "size": "",
            }
		}
	}

    let attributes = {...settings.attributes, ...attrs }

    const { usesContext, providesContext } = settings;

    if ( name === 'core/list' ) {
        if ( ! providesContext['list/icon'] ) {
            settings.providesContext = Object.assign( providesContext, {'list/icon': 'icon'} )
        }
    }
    else if ( name === 'core/list-item' ) {
        if ( usesContext.includes( 'list/icon' ) === false ) {
            settings.usesContext = [...usesContext, 'list/icon' ]
        }
    }

	return {
        ...settings,
        attributes,
    };
}
addFilter(
	'blocks.registerBlockType',
	'prefix/custom-list-attributes',
	addAttributes
);

PHP file

add_filter( 'register_block_type_args', function($args, $block_type) {
	if( $block_type !== 'core/list-item' ) return $args;
	
	$args['uses_context'][] = 'list/icon';

	return $args;
}, 10, 2 );

add_action( 'render_block_core/list-item', function( $block_content, $block ) {
    return $block_content;
}, 10, 3 );

Screenshots, screen recording, code snippet

No response

Environment info

  • WordPress 6.6RC4

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

  • Yes
@simom simom added the [Type] Bug An existing feature does not function as intended label Jul 16, 2024
@fabiankaegy
Copy link
Member

@simom I believe the issue might be that you are not defining the providesContext & usesContext properties on the server. You are only adding them to the block in JS via the blocks.registerBlockType filter.

In order for the server to be aware and therefore properly pass the context to the render_block / render_callbackss you would need to also do the same in PHP using the block_type_metadata function.

@simom
Copy link
Author

simom commented Jul 16, 2024

@fabiankaegy thanks for your quick reply!
I've done a couple of tests but probably there is something wrong in my code, for example:

function core_list_item_context( $metadata ) {
	if ( ! isset( $metadata['name'] ) ) {
		return $metadata;
	}

	if ( 'core/list-item' === $metadata['name'] ) {
		$metadata['uses_context'][] = 'list/icon';
	}

	if ( 'core/list' === $metadata['name'] ) {
		$metadata['provides_context']['list/icon'] = 'icon';
	}

    return $metadata;
}
add_filter( 'block_type_metadata', 'core_list_item_context' );

I'm not sure if I must define also the provides_context.

@ndiego
Copy link
Member

ndiego commented Jul 17, 2024

if ( ! isset( $metadata['name'] ) || 'core/list-item' !== $metadata['name'] ) {
return $metadata;
}

I have not actually tried to implement this code, but it looks like core/list is not getting the provides_context attribute set due to this first conditional in the filter.

@simom
Copy link
Author

simom commented Jul 17, 2024

Thanks for your feedback @ndiego and yes, you are right, I pasted the wrong code since I was experimenting with different things. By the way, even with the correct condition, it is not working

@ndiego
Copy link
Member

ndiego commented Jul 17, 2024

Thanks for your feedback @ndiego and yes, you are right, I pasted the wrong code since I was experimenting with different things. By the way, even with the correct condition, it is not working

Thanks for confirming. I'll need to put a complete example together to really test this. I'll follow up once I investigate this a bit more.

@fabiankaegy fabiankaegy changed the title ProvidesContext and UsesContext on a core block is not working as expected Add ability to access block context from inside render_block filter Jul 18, 2024
@fabiankaegy
Copy link
Member

Reopening with a new title.

After doing some more research it appears the issue is not that you cannot register new attributes / set the provides_context / uses_context values. But instead that it isn't possible to access block context from within the callback added to the render_block filter.

@fabiankaegy fabiankaegy reopened this Jul 18, 2024
@ndiego
Copy link
Member

ndiego commented Jul 18, 2024

But instead that it isn't possible to access block context from within the callback added to the render_block filter.

Hmm, I think you can. I was just testing with the Social Icons block. If you add a Social Icons block with a few icons to a page, then add the following, the context will print out.

add_action( 'render_block_core/social-link', function( $block_content, $block, $instance ) {
    echo print_r( $instance->context );
    return $block_content;
}, 10, 3 );

@simom
Copy link
Author

simom commented Jul 18, 2024

I performed a test and it returns an empty array when trying to access $instance->context. (I've added $instance as parameter to the function)

@fabiankaegy
Copy link
Member

Yeah same here. The context is always just an empty array. Even for blocks that already define other uses_context values before extending them.

@ndiego
Copy link
Member

ndiego commented Jul 18, 2024

I got it to work. The following is a bit messy, but you need to make sure you are setting both useContext and providesContext and then the final piece is you need to make sure the attributes match the values provided to both. Context is actually set by comparing the attribute name to the context name.

add_filter( 'block_type_metadata',  function( $metadata ) {
	
	if ( isset( $metadata['name'] ) && 'core/list-item' === $metadata['name'] ) {
		$metadata['usesContext']   ??= [];
		$metadata['usesContext'][] = 'useIcon';
		$metadata['usesContext'][] = 'icon';
	}

	if ( isset( $metadata['name'] ) && 'core/list' === $metadata['name'] ) {
		$metadata['attributes']['useIcon'] = array(
			'type'    => 'boolean',
			'default' => 'false'
		);
		$metadata['attributes']['icon'] = array(
			'type'    => 'string',
			'default' => 'test' // Just for testing.
		);

		$metadata['providesContext']            ??= [];
		$metadata['providesContext']['useIcon'] = 'useIcon';
		$metadata['providesContext']['icon']    = 'icon';
	}

	return $metadata;
} );

add_action( 'render_block_core/list-item', function( $block_content, $block, $instance ) {
    
    if ( ! isset( $instance->context ) ) {
        return $block_content;
    }

    $context = $instance->context;
    $useIcon = $context['useIcon'] ??= false;
    $icon    = $context['icon']    ??= '';

    if ( $useIcon && $icon ) {
        // Do whatever you want here...
        return $block_content . ' ' . $context['icon'];
    } 

    return $block_content;
}, 10, 3 );

Give it a try, and let me know if you run into any issues.

One thing to note is that the context array returns empty if the attributes don't have default values and they have not been set on the block. So, just because it's an empty array doesn't necessarily mean that it's not working. This tripped me up for a while 😅

@simom
Copy link
Author

simom commented Jul 18, 2024

Oh, I see. The only difference is that it must be called providesContext and usesContext, not provides_context and uses_context.

Then, you have to check it inside the $instance rather than $block.

Thank you so much, Nick. Really appreciated!

Can I ask you why this naming difference? and why on a custom block I can access to the context directly from the $block?

@fabiankaegy
Copy link
Member

Okay I'll try and also Digg in more :) thanks @ndiego ❤️ will close the issue for now 👍

@ndiego
Copy link
Member

ndiego commented Jul 18, 2024

Can I ask you why this naming difference? and why on a custom block I can access to the context directly from the $block?

So it depends on the filter you use. The block_type_metadata filter is essentially filtering the values in the block’s block.json file. These values are then converted to their underscore versions before the register_block_type_args filter is run. Quite confusing.

For the context, the $block parameter in a block’s render callback function or render.php is not the same $block parameter provided to the render_block filter, despite the same name. You actually want the WP_Block object, which is provided by $instance.

@gziolo
Copy link
Member

gziolo commented Jul 23, 2024

register_block_type_args is the most low-level PHP filter here to pick from, and it would work for every block registered on the server. All settings used on the server are propagated to the client with higher priority than those set on the client. In effect, you can treat in that regard the server as the source of truth.

@helgatheviking
Copy link
Contributor

I modified @ndiego's snippet to attempt to pass some context into blocks inside the post template and the context is not passed down. I think I see why so I created a new issue for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

No branches or pull requests

6 participants
@simom @helgatheviking @gziolo @ndiego @fabiankaegy and others