Gutenberg: How to: Access InnerBlocks.Content in PHP

Created on 10 Jun 2018  路  20Comments  路  Source: WordPress/gutenberg

An example request regarding this documentation page: https://github.com/WordPress/gutenberg/tree/master/editor/components/inner-blocks

wp.blocks.registerBlockType( 'example/hello-world', {
    title: 'Hello World',
    icon: 'universal-access-alt',
    category: 'layout',
    attributes: {},
    edit: function(){
        return wp.element.createElement( wp.editor.InnerBlocks );
    },
    save: function(){
        return null;
    },
});

// This function is wrong and needs documentation.
function php_render_block( $attributes, $content = '' ) {
    // #1 How do I access InnerBlocks.Content here?
    // #2 When accessed how do I typically parse all InnerBlocks.Content?
    return '<p>' . $attributes['foo'] . '</p>' . '<pre><code>' . htmlspecialchars( $content ) . '</code></pre>';
}

Similar example:
https://gist.github.com/mattheu/7cf235b4f932de891bc21cb5f3ff3de6

[Feature] Block API [Feature] Extensibility [Type] Enhancement

Most helpful comment

@Tofandel It is exposed as innerBlocks on the render_block_data and render_block filters $block argument:

https://developer.wordpress.org/reference/hooks/render_block_data/
https://developer.wordpress.org/reference/hooks/render_block/

Example:

<?php

add_filter( 'render_block', function( $block_content, $block ) {
    var_export( $block['innerBlocks'] );

    return $block_content;
}, 10, 2 );

All 20 comments

Hi @manake,

This file in one of my plugins may help you:
https://github.com/mwtsn/secure-blocks-for-gutenberg/blob/master/blocks/secure-block/php/class-secure-block.php

In particular these two blocks of code:

function register_dynamic_block() {
    // Only load if Gutenberg is available.
    if ( ! function_exists( 'register_block_type' ) ) {
        return;
    }
    // Hook server side rendering into render callback
    register_block_type( 'matt-watson/secure-block', [
        'render_callback' => 'matt_watson\secure_blocks_for_gutenberg\matt_watson_secure_blocks_for_gutenberg_render',
    ] );
}

Then you can grab the content in this function:

function matt_watson_secure_blocks_for_gutenberg_render( $attributes, $content ) {
    return $content;
}

Note that I use a namespace in the plugin which is needed when calling the function.

@mwtsn The issue is that the InnerBlocks aren't currently passed to the parent block's content -- or rendered even, it seems.

Related: #6751

This is a known issue, I talked to both @aduth and @dmsnell about it last week -- I'm just popping around and documenting on open issues right now.

@mattwatsoncodes $content is empty for me.

Another example, in one of my blocks I have multiple InnerBlocks areas and want to save them all:

el( wp.editor.RichText, {
    tagName: 'div',
    format: 'string',
    placeholder: 'Some text',
    value: someAttribute,
    onChange: function( content ) {
        props.setAttributes({ someAttribute: content }); // I can save this in "someAttribute" easily...
    }
} ),
el(wp.editor.InnerBlocks), // ...but how to save this? And then access and render?
el(wp.editor.InnerBlocks), // ...and this?
el(wp.editor.InnerBlocks), // ...and this?

Any workaround for accessing InnerBlocks.Content in php yet?

@rameezwp We implemented a small polyfill for that problem: Gutenberg hooks into "the_content" to parse the Block-Comments. We also hooked in with preference 0 so that our filter starts first. Then we have a look into the Gutenberg Block Registry, look for our tags with inner content and called the registered callback ourself. Since Gutenberg Block Meta Comments are very structured, the needed regular expressions to identify tags and inner content were found very easely.

We did that only for nested Gutenberg Blocks and left single tags to the Gutenberg filter.

The solution has the benefit that you can work as documented by gutenberg and just turn off your filter as soon as the Gutenberg team has solved that issue.

BR from Salzburg!

So how I can actually render innerblocks from php callback?

block.js
save: function(props) { return el( InnerBlocks.Content ); },
php file
function render_block($attrs, $content){ ob_start(); echo $content; return ob_get_clean(); }

Thanks for your help.

@dmsnell thank you for the reference. I can see where your solution will lead but wouldn't it be easier to first render the sub HTML content and then passing it's content to the upper levels?

Example:
World
-> House
--> Table

With your solution if you render World you would have to manually render the house and then make a recursive function to render table as in table could be another block an so on. What if the InnerBlocks had an onChange function which would return the content. This content is allready rendered and doesen't has to be checked for not rendered content as it is already done by it's predecessor (for example table).

So your js code for World could look like this:

 el(InnerBlocks,{
     onChange: (value) => {props.setAttributes({innerBlockValueAsString: value})}
})

@chrisvanpatten and @jorgefilipecosta #7785 #11271 #10745 #6751

@noelelias in #10108 I'm exploring a filtering API but you can see there that the inner blocks are already accessible to parent blocks in PHP as rendered HTML.

I think there are two distinct ways we might want to process a block in PHP: before it's turned into HTML - "structural filtering"; after it's turned into HTML. For example, maybe I want to filter out any inner blocks of the type conditional/on-sunday - then I want access to the structure and not just the rendered HTML. On the other hand, maybe I'm concatenating my inner blocks with the normal output but one of them is dynamic - I need to make sure that render/filter gets in before I use the serialized HTML values. Does that make any sense?

Anyway, I'd love to have you share your input in #10108, or at least the PHP-specific bits. There are some trivial things we can do like pass the array of rendered inner blocks to the render_callback but that trivial action also carries non-trivial implications.

@dmsnell I think this solution works for editing the innerBlock although registering a callback function inside register_block_type would do the job as well. I personally don't like filter functions but that's just my opinion.

This feature will be important but I still think, that having access to the rendered inner blocks inside render_callback is so much more important right now. Without it people will, for example, start hard coding their headings inside their blocks instead using the heading block provided.

Also with your solution, passing the rendered inner blocks to the render_callback won't be a problem anymore as you can edit them with the filter function before they get passed to the render_callback.

If I am following correctly, the update in https://core.trac.wordpress.org/ticket/45451 to add a filter to override block attributes solves the problems raised here (and noting that update should ship with WordPress 5.1). @manake, if that is not correct, would you mind please leaving me a comment to let me know?

And so how do you use it?...

@Tofandel It is exposed as innerBlocks on the render_block_data and render_block filters $block argument:

https://developer.wordpress.org/reference/hooks/render_block_data/
https://developer.wordpress.org/reference/hooks/render_block/

Example:

<?php

add_filter( 'render_block', function( $block_content, $block ) {
    var_export( $block['innerBlocks'] );

    return $block_content;
}, 10, 2 );

Where are $block_content and $block coming from in your example, @aduth ?

@RodrigoDAgostino Those are the arguments passed to the filter callback.

https://github.com/WordPress/WordPress/blob/28a8f31ffa9dc238b56eb2413a728e861207b5af/wp-includes/blocks.php#L267-L275

Make sure you include the fourth argument of add_filter, $accepted_args.

https://developer.wordpress.org/reference/functions/add_filter/

@aduth yes, I understand those are the arguments I need to supply, but where am I supposed to get those from inside my render function?

The inner blocks are not currently exposed on render_callback. I don't recall if this was intentional.

Instead, you can use the above filter and test whether $block['blockName'] is the name of the block you've implemented.

Thanks for your efforts, @aduth , I still do not really understand how to use that filter. I might take a look to it later.
Thanks for your time too.

Regardless of this perhaps being an old thread, it still came up high in google for a search I performed to find out how to output InnerBlocks in PHP, and there isn't much else in the way of info on how to do this. So I wanted to share how I got it working...

In your php file for the block, you will have a function register_block_type, and in there is your render_callback. Whatever function your sending your render_callback to, give that function two parameters - $attributes and $content. Your $content should be your InnerBlocks rendering out.

function your_render_callback($attributes, $content) {
  echo $content; // this should be your InnerBlock output.
}

Hope this helps anyone who is slightly lost.

@flywolfcreative or anyone else:
php:

register_block_type('jma-ghb/featued-block', array( 'attributes' => array( ), 'editor_script' => 'jma-ghb-featured-script', 'render_callback' => 'JMA_GHB_featured_callback', ));

and:

function JMA_GHB_featured_callback($x, $y) { ob_start(); echo $y; return ob_get_clean(); }

wordpress gives us this for block.js https://developer.wordpress.org/block-editor/tutorials/block-tutorial/nested-blocks-inner-blocks/:

( function( blocks, element, blockEditor ) { var el = element.createElement; var InnerBlocks = blockEditor.InnerBlocks; blocks.registerBlockType( 'jma-ghb/featued-block', { title: 'Example: Inner Blocks', category: 'layout', edit: function( props ) { return el( 'div', { className: props.className }, el( InnerBlocks ) ); }, save: function( props ) { return el( 'div', { className: props.className }, el( InnerBlocks.Content ) ); }, } ); } ( window.wp.blocks, window.wp.element, window.wp.blockEditor, ) );

how do I modify block.js (I assume this is where my problem is). I have tried various combinations of ServerSideRender and save : ... return null;
but I can't put it all together. In the block editor I see "block renders empty or undefined and I can't drag anything into it.

thanks

@flywolfcreative
And what do you write in JS? Does your save function return null or InnerBlocks.Content?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

maddisondesigns picture maddisondesigns  路  79Comments

jasmussen picture jasmussen  路  173Comments

DeveloperWil picture DeveloperWil  路  102Comments

azaozz picture azaozz  路  91Comments

afercia picture afercia  路  73Comments