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

Create Block Template from copied block content #33379

Closed
CreativeDive opened this issue Jul 13, 2021 · 33 comments
Closed

Create Block Template from copied block content #33379

CreativeDive opened this issue Jul 13, 2021 · 33 comments
Labels
[Feature] Parsing Related to efforts to improving the parsing of a string of data and converting it into a different f Needs Technical Feedback Needs testing from a developer perspective. [Package] Blocks /packages/blocks

Comments

@CreativeDive
Copy link
Contributor

Hello,

currently there is the $post_type_object->template way to provide a default block template in specific cases. For example for a specific post type.

We can use this way to do that:

function myplugin_register_template() {
    $post_type_object = get_post_type_object( 'post' );
    $post_type_object->template = array(
        array( 'core/paragraph', array(
            'content' => 'My Content',
        ) ),
    );
    $post_type_object->template_lock = 'all';
}
add_action( 'init', 'myplugin_register_template' );

This is working with a template array like:

array(
	array( 'core/paragraph', array(
		'content' => 'My Content',
	) ),
);

But in the real world, we creating block templates in the editor way. And if we have finalized a really nice template with the combination of different blocks, we can use the "Copy all Content" functionality.

The result of this functionality is the block content like this:

<!-- wp:group -->
<div class="wp-block-group"><div class="wp-block-group__inner-container"><!-- wp:separator {"className":"is-style-default"} -->
<hr class="wp-block-separator is-style-default"/>
<!-- /wp:separator -->
<!-- wp:image {"align":"center","id":553,"width":150,"height":150,"sizeSlug":"large","linkDestination":"none","className":"is-style-rounded"} -->
<div class="wp-block-image is-style-rounded"><figure class="aligncenter size-large is-resized"><img src="https://blockpatterndesigns.mystagingwebsite.com/wp-content/uploads/2021/02/StockSnap_HQR8BJFZID-1.jpg" alt="" class="wp-image-553" width="150" height="150"/></figure></div>
<!-- /wp:image -->
<!-- wp:quote {"align":"center","className":"is-style-large"} -->
<blockquote class="wp-block-quote has-text-align-center is-style-large"><p>"Contributing makes me feel like I'm being useful to the planet."</p><cite>— Anna Wong, <em>Volunteer</em></cite></blockquote>
<!-- /wp:quote -->
<!-- wp:separator {"className":"is-style-default"} -->
<hr class="wp-block-separator is-style-default"/>
<!-- /wp:separator --></div></div>
<!-- /wp:group -->

So I want to use this block content code from my created layout to create a default template in a specific case when adding a new page.

So I thought I can use the same method as the block template array like the following (this is just an example).

function myplugin_register_template() {
    $post_type_object = get_post_type_object( 'post' );
    $post_type_object->template = array(
        array( 'core/paragraph', array(
            'content' => '<!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator -->',
        ) ),
    );
}
add_action( 'init', 'myplugin_register_template' );

Also this is not working:

function myplugin_register_template() {
    $post_type_object = get_post_type_object( 'post' );
    $post_type_object->template = '<!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator -->';
}
add_action( 'init', 'myplugin_register_template' );

Is there a possibility at this time to create a default block template on creating a post by using the block content instead of a block template array?

Or is there a way to automatically convert block content to a block template array?

@CreativeDive
Copy link
Contributor Author

On the other hand, I can use the copied block content and paste it into a paragraph block.

The result is as expected, all the blocks from my copied block content are very well made.

But when I use the way from my description above it doesn't work.

@CreativeDive
Copy link
Contributor Author

CreativeDive commented Jul 13, 2021

To work directly with the block content (block html) currently works as expected for:

register_block_pattern(
	'block/name',
	array(
		...
		'content' => "<!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator -->",
	)
);

... or works as expected for:

add_filter( 'block_editor_settings_all', function( $settings ) {
     $settings['defaultBlockTemplate'] = "<!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator -->";

     return $settings;

});

But why it dosen't work with $post_type_object->template? ... for:

function myplugin_register_template() {
    $post_type_object = get_post_type_object( 'post' );
    $post_type_object->template = '<!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator -->';
}
add_action( 'init', 'myplugin_register_template' );

@getdave
Copy link
Contributor

getdave commented Jul 20, 2021

Hi @CreativeDive. You are correct, the format for the template is expected to be this array structure rather than raw block grammar.

Here is the code which parses the template property and syncs it with the post content

if ( isNewPost && template ) {
blocks = synchronizeBlocksWithTemplate( blocks, template );
}

As you can see it expects the array.

To work around this you can use the parse_blocks() PHP implementation to parse the blocks into the correct (array-based) format before you pass them to the template property of your $post_type_object.

To build on your example:

function myplugin_register_template() {
	$post_type_object = get_post_type_object( 'post' );
        // Parse the blocks
	$blocks           = parse_blocks( '<!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator -->' );

        // Convert to required format
	$block_template = array_map(
		function( $block ) {
			return array(
				$block['blockName'],
				array(
					'content' => $block['innerContent'],
				),
				$block['innerBlocks'],
			);
		},
		$blocks
	);


	$post_type_object->template = $block_template;

}
add_action( 'init', 'myplugin_register_template' );

You might need to tweak that a little bit and test with more complex templates but it should work.

Alternatively we could look to update setupEditor() in order that it tries to parse the block first and then falls back to attempting a parse from a template. Something like this?

if ( isNewPost && template ) {
    try {
        // Attempt to parse raw block grammar if provided
        blocks = parse( template );
    } catch ( error ) {
        // Fallback to current behaviour
        blocks = synchronizeBlocksWithTemplate( blocks, template );
    }
}

I wonder what @youknowriad thinks about that and the idea of aligning the API for block templates across all use cases.

@getdave getdave added [Package] Blocks /packages/blocks Needs Technical Feedback Needs testing from a developer perspective. labels Jul 20, 2021
@CreativeDive
Copy link
Contributor Author

@getdave thanks for helping. I've tested your code, but unfortunately it's not working. After adding your code to convert the block content the the expected array, the block editor get a white screen. In the browser console I get this JS error:

Unhandled Promise Rejection: TypeError: undefined is not a function (near '...[name, attributes, innerBlocksTemplate]...')

@getdave
Copy link
Contributor

getdave commented Jul 21, 2021

@creativecoder No problem. Shame it didn't work for you as it works well for me with the content you provided.

Can you advise on exactly what steps you are taking and what content you are using in the template?

I would also recommend doing some digging and looking into whether the properties you're trying to access in PHP on the $block variable are actually defined (try using empty() or isset()).

You could also add some breakpoints around the below in JS to see if you can determine the source of the error:

if ( isNewPost && template ) {
blocks = synchronizeBlocksWithTemplate( blocks, template );
}

@CreativeDive
Copy link
Contributor Author

@getdave basically it works if I use this content instead:

<!-- wp:paragraph {"fontSize":"medium"} --><p class="has-medium-font-size">Lorem ipsum dolor sit amet consectetur adipiscing elit vulputate vehicula montes tortor nunc, varius curae ac tristique parturient sociis etiam duis mauris eros mollis.</p><!-- /wp:paragraph --><!-- wp:spacer --><div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div><!-- /wp:spacer -->

and changing $block['innerContent'] to $block['innerHTML']

But with more complex block content it fails. It seems there are some more investigations necessary to get it working fine, like you say to work with isset().

Is this planned to add this possibility as a WordPress build in functionality or to do the same like in register_block_pattern() or block_editor_settings_all filter for $post_type_object->template?

I wonder, if I copy very complex block content from the "Copy all content" functionality and paste it directly inside an empty paragraph block ... everything works really fine. But I think this is managed with javascript, right?

For the PHP solution we have to use an other way? And there is no build in PHP function from WordPress which is doing the same?

@getdave
Copy link
Contributor

getdave commented Jul 21, 2021

Hi @CreativeDive. I've solved the problem and will post a solution shortly. 🤞 this will work for you.

@getdave
Copy link
Contributor

getdave commented Jul 21, 2021

@CreativeDive As this was become complex to fix in a GH issue, I've written up a quick blog post on the solution that I found.

TL;DR - we needed to recurse into the innerBlocks and ensure they were all converted to the same array-based format. My previous example only handeld the topmost blocks.

With this solution you shoudl be able to copy/paste directly from the editor and it will work perfectly. I also tested against patterns from the Pattern Directory.

Hopefully this will work for you?

I'd also encourage you to explore the Template Editor and Block Templates that's just landed in WP 5.8 as that might present an alternative solution for you.

@CreativeDive
Copy link
Contributor Author

@getdave really nice and thank you for creating this block post. Nice content for your website :-D

I will investigate this soon and will test different block contents with your solution. I will leave my feedback here.

To work with Template Editor and Block Templates are an other possibility.

But the work with the block_editor_settings_all filter requires to create custom templates, right? Or I can do the same for the default page template without creating additional templates? I can't find a solution in this way.

add_filter( 'block_editor_settings_all', function( $settings ) {
     $settings['defaultBlockTemplate'] = '<!-- wp:paragraph {"fontSize":"medium"} --><p class="has-medium-font-size">Lorem ipsum dolor sit amet consectetur adipiscing elit vulputate vehicula montes tortor nunc, varius curae ac tristique parturient sociis etiam duis mauris eros mollis.</p><!-- /wp:paragraph --><!-- wp:spacer --><div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div><!-- /wp:spacer --><!-- wp:heading {"fontSize":"huge"} --><h2 class="has-huge-font-size">Lorem ipsum dolor sit amet consectetur</h2><!-- /wp:heading -->';
     return $settings;
});

@CreativeDive
Copy link
Contributor Author

CreativeDive commented Jul 21, 2021

@getdave I've tested your solution from your great blog post and see basically it works also with more complex block content.

But it seems there is something wrong. Here is my example. I use a simple heading block code like this:

<!-- wp:heading {"level":1,"fontSize":"large"} -->
<h1 class="has-large-font-size">My Heading</h1>
<!-- /wp:heading -->

After using your awesome convert to block array function, I get this wrong result if I create a new post:

<!-- wp:heading {"level":1,"fontSize":"large"} -->
<h1 class="has-large-font-size"><h1 class="has-large-font-size">My Heading</h1></h1>
<!-- /wp:heading -->

The h1 tag existing multiple times. The same thing happens with other blocks. Maybe there is something wrong with the "innerHTML" key?

@CreativeDive
Copy link
Contributor Author

@getdave It's possible that $block['innerHTML'] and $block['innerContent'] should be inserted separately? But how we can do that?

@getdave
Copy link
Contributor

getdave commented Jul 21, 2021

@CreativeDive As it looks like the array format expects the content to be a plain string, how about trying to strip all the tags from the content?

function convertBlockToTemplate( $block ) {
	$template = array(
		$block['blockName'],
		array_merge(
			$block['attrs'],
			array(
				'content' => wp_strip_all_tags( $block['innerHTML']),
			),
		),
	);

	$template[] = ( ! empty( $block['innerBlocks'] ) ) ? array_map( 'convertBlockToTemplate', $block['innerBlocks'] ) : array();

	return $template;
}

@CreativeDive
Copy link
Contributor Author

@getdave very good idea. It seems this is working great and the block content is recreated like expected.

I have found one additional issue. Linked images, like in the "Media/text" block are missing after converting the content to the array. I'll take a look at this soon.

@getdave
Copy link
Contributor

getdave commented Jul 28, 2021

@getdave very good idea. It seems this is working great and the block content is recreated like expected.

I have found one additional issue. Linked images, like in the "Media/text" block are missing after converting the content to the array. I'll take a look at this soon.

@CreativeDive Any luck here or can we now close this one out?

@CreativeDive
Copy link
Contributor Author

@getdave sorry for the missing feedback. Basically your method to convert the block content to an array is great. But at the end it's not a fully working solution.

If I use wp_strip_all_tags( $block['innerHTML']) to remove duplicated HTML tags, this also removes inline tags, like text color. Therefore we can't use wp_strip_all_tags() to solve this issue. :-(

I don't know how we can solve this topic. I think it is better to leave this issue open to use as a feature request. I hope this will come to WordPress as a default part, like register_block_pattern() or block_editor_settings_all.

@getdave
Copy link
Contributor

getdave commented Jul 28, 2021

@CreativeDive Thanks for the feedback.

Given this then, it looks like it's not possible (or at least simple) to convert copied block content to the array format required by the $post->template argument. There appear to be too many edge cases.

Therefore as suggested the best option would be to simply update the template parsing to accept copied block content as well as the current array format.

Something along these lines could be added here:

if ( isNewPost && template ) {
    try {
        // Attempt to parse raw block grammar if provided
        blocks = parse( template );
    } catch ( error ) {
        // Fallback to current behaviour
        blocks = synchronizeBlocksWithTemplate( blocks, template );
    }
}

@youknowriad could we explore this approach? Any other routes you'd suggest?

@getdave getdave added the [Feature] Parsing Related to efforts to improving the parsing of a string of data and converting it into a different f label Jul 28, 2021
@youknowriad
Copy link
Contributor

I'd love more opinions on this cc @mcsf @mtias but I'm hesitant a bit:

  • I like the JSON format for the current template API, it's easier to dynamically inject templates that way from php.
  • The current template API suffers from a fundamental issue though, deprecations don't run on it, so there's a potential of breakage for templates over time.
  • I like that you can just copy/paste the template with the proposal above.

I guess I could be in favor of soft-replacing the template with an html string like patterns... which would also solve the deprecations issues but I don't know how much this would hurt the dynamic injections possibilities.

@getdave
Copy link
Contributor

getdave commented Jul 29, 2021

I like that you can just copy/paste the template with the proposal above.

I think that's a killer feature.

I'm not necessarily proposing we drop the existing array-based format in PHP, but if we could also allow the user to provide the copied block content this would make it a tonne easier for folks to create complex templates rather than have to manually author them by hand.

If not then we at least need to provide a mechanism in our PHP API that allows us to reliably and consistently convert copied block content into the array format required by the template arg without having to jump through a load of hoops.

I tried this in the thread above and in this blog post, but we kept hitting issues with HTML tags within the content of certain blocks. Perhaps I missed something?

@ethanclevenger91
Copy link

Adding onto this - as a theme developer, the divergent APIs between creating block patterns and creating default templates is jarring. Seems like one method should be supported and recommended, rather than one using PHP arrays and the other using an HTML string.

@Jaska
Copy link

Jaska commented Mar 11, 2022

Hi. I struggled with this issue for a while until I started using " wp.data.select( 'core/block-editor' ).getBlocks() " as a source for block editor data rather than the html comment block syntax.

  • I build the template I want in the editor
  • Open the console
  • JSON.stringify( wp.data.select( 'core/block-editor' ).getBlocks() );
  • Copy that into PHP as some variable etc. $json_template.

Then I convert that into a working block template:
new Va_Post_Type_Templates( 'my_cpt_slug', $json_template );

This seems to work very well : Core tables, lists, columns work. 🤞

Here's the code for my class. The methods are very similar to functions in this issue.
I hope it might help someone with similar problem.

<?php
/**
 * Converts block editor data (json) into block template for post type
 * @param $cpt_slug string Post type slug
 * @param $json string JSON string of block editor data
 *
 * 1. Build the template in block editor
 * 2. Open console and use following to get Json string of editor data:
 * JSON.stringify( wp.data.select( 'core/block-editor' ).getBlocks() );
 *
 * Example:
 * new Va_Post_Type_Templates( 'my_cpt_slug', $json_template );
 *
 * @author Jaakko Saarenketo <jaakko.saarenketo@verkkoasema.fi>
 */
class Va_Post_Type_Templates {
    public function __construct( $cpt_slug, $json )
    {
        $this->cpt_slug = $cpt_slug;
        $this->json = $json;
        
        if ( ! is_admin() ) {
		return false;
	}
	
        add_action( 'init', [ $this, 'register_template' ] );
    }
    public $cpt_slug;
    public $json;
    public function register_template()
    {
        $post_type_object = get_post_type_object( $this->cpt_slug );
        if ( ! $post_type_object ) {
            return false;
        }
        $json = $this->json;
        $template = $this->build_template_from_json( $json );
        if ( ! $template ) {
            return false;
        }
        $post_type_object->template = $template;
    }
    public function build_template_from_json( $json )
    {
        $blocks = json_decode( $json, true );
        if ( ! $blocks ) {
            return false;
        }
        $block_template = array_map(
            [ $this, 'convert_block_to_template' ],
            $blocks
        );
        return $block_template;
    }
    public function convert_block_to_template( $block )
    {
        $template = [
            $block['name'],
            $block['attributes'],
        ];
        // Optionally recurse into innerBlocks if found
        $template[] = ( ! empty( $block['innerBlocks'] ) ) ? array_map( [ $this, 'convert_block_to_template' ], $block['innerBlocks'] ) : [];
        return $template;
    }
}

@cr0ybot
Copy link
Contributor

cr0ybot commented Mar 11, 2022

Wow, my lucky day that I came here trying to figure out some kind of solution for getting a JS array for use in template code out of the block editor, and @Jaska was here just 6 hours ago with a one-liner!

I'd just like to add, at least for me in Firefox, that it was better for me to drop the JSON.stringify() and go straight for wp.data.select( 'core/block-editor' ).getBlocks(), and then navigate to and right-click on the innerBlocks: Array[{...}] of the group I'd build the block "template" within. This way I can build it on any page/post and pull it out to create templates.

Screen Shot 2022-03-11 at 11 38 12 AM

This is also useful for block variations, as they are defined via this template array notation too.

Note that there are some extra properties that come along for the ride than need to be removed per block, such as clientId, isValid, originalContent, and validationIssues.

Screen Shot 2022-03-11 at 11 44 25 AM

@Jaska
Copy link

Jaska commented Mar 13, 2022

Thanks @cr0ybot! Are you registering the block template with javascript rather than php? If so, I'd like to see how you do that 😀

@cr0ybot
Copy link
Contributor

cr0ybot commented Apr 11, 2022

@Jaska generally speaking, yes, mostly for assigning a block template in a custom block with InnerBlocks or, as I was looking for here, registering a block variation.

Here's an example of creating a block variation that I was experimenting with, though I can't say this will get used in production. I was mostly testing different ways of inserting block templates into the editor that were template-locked.

import { registerBlockVariation } from '@wordpress/blocks';
import domReady from '@wordpress/dom-ready';
import { __ } from '@wordpress/i18n';

domReady( () => {
	registerBlockVariation( 'core/group', {
		name: 'latest-case-studies',
		title: __( 'Latest Case Studies', 'blackbird' ),
		attributes: {
			templateLock: 'all',
		},
		innerBlocks: [
			{
				name: 'core/group',
				attributes: {
					tagName: 'div',
					className: 'v-latest-case-studies',
					layout: {
						inherit: true,
					},
				},
				innerBlocks: [
					{
						name: 'core/heading',
						isValid: true,
						attributes: {
							textAlign: 'center',
							content: 'Case Studies',
							level: 2,
							fontSize: 'h3',
							anchor: 'case-studies',
						},
						innerBlocks: [],
					},
					{
						name: 'core/query',
						isValid: true,
						attributes: {
							queryId: 0,
							query: {
								perPage: 4,
								pages: 0,
								offset: 0,
								postType: 'case_study',
								order: 'desc',
								orderBy: 'date',
								author: '',
								search: '',
								exclude: [],
								sticky: '',
								inherit: false,
							},
							tagName: 'div',
							displayLayout: {
								type: 'flex',
								columns: 2,
							},
							layout: {
								inherit: true,
							},
						},
						innerBlocks: [
							{
								name: 'core/post-template',
								attributes: {},
								innerBlocks: [
									{
										name: 'core/post-featured-image',
										attributes: {
											isLink: true,
											scale: 'cover',
											className: 'ar-4-3',
										},
										innerBlocks: [],
									},
								],
							},
						],
					},
				],
			},
		],
	} );
} );

@cr0ybot
Copy link
Contributor

cr0ybot commented May 2, 2022

This plugin might help: https://wordpress.org/plugins/block-xray-attributes/

@mkdgs
Copy link

mkdgs commented May 10, 2022

@getdave , @CreativeDive i've made a little improvement on convertBlockToTemplate()
Block image need an attachment id and url attribute.
The url attribute is not generated on parsing, so wee need to recreate one.

function convertBlockToTemplate($blocks) {
    $tpl = [];
    foreach ($blocks as $block) {
        if (!empty($block['blockName'])) {
            if( "core/image" === $block['blockName']) {                
                if ( !empty($block['attrs']['id']) ) {
                    $block['attrs']['url'] = wp_get_attachment_url($block['attrs']['id']);
                }
            }
            $template = array(
                $block['blockName'],                
                array_merge(
                    $block['attrs'],
                    array(
                        'content' => $block['innerHTML'],
                    ),
                ),
            );
            $template[] =  (!empty($block['innerBlocks'])) ?  convertBlockToTemplate($block['innerBlocks']) : array();
            $tpl[] = $template;
        }         
    }    
    return $tpl;
}

// ....
$template = parse_blocks(file_get_contents( '/my-dir-to/block-pattern/my-pattern-or-template.html'));
$template = convertBlockToTemplate($template);
    
register_post_type(
    //....,
    [
        'template' => $template, 
        //  'template_lock' => true,
    ]
);

This can load the "html template block" (from a file) directly to a post_type default template.
To go further
In place of the file, it will be possible to create a post_type named template, and getting the default template for other post_type with this one.
eg: parse_blocks(get_page_by_path( $my_post_type, OBJECT, 'template' )->post_content)
So the template can simply be created/updated from the editor.

@CreativeDive
Copy link
Contributor Author

@mkdgs thanks for the input. But things like ...

if( "core/image" === $block['blockName']) {                
                if ( !empty($block['attrs']['id']) ) {
                    $block['attrs']['url'] = wp_get_attachment_url($block['attrs']['id']);
                }
            }

... is one case of a lot. Hopefully there comes an out of the box solution. @getdave All what we need is a core implementation like:

add_filter( 'block_editor_settings_all', function( $settings ) {
	
     $settings['defaultBlockTemplate'] = "<!-- wp:acf/site-logo {\"id\":\"\",\"name\":\"acf/site-logo\",\"data\":{\"field_5e9eddee6103b\":\"default\",\"field_5e9f41da433d5\":\"115\",\"field_60b1396af9910\":{\"field_60b1396af9910_field_60b131089e785\":\"default\",\"field_60b1396af9910_field_60b133299e9e1\":\"default\",\"field_60b1396af9910_field_60daf2f7165b2\":\"default\"},\"field_60b490adcf814\":{\"field_60b490adcf814_field_60b490d982aa6\":\"default\"}},\"align\":\"\",\"mode\":\"preview\",\"align_text\":\"left\"} /-->\n\n<!-- wp:heading {\"level\":1,\"fontSize\":\"huge\"} -->\n<h1 class=\"has-huge-font-size\">Heading</h1>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>This is a new template. You can use this template instead of the default template.</p>\n<!-- /wp:paragraph -->";

     return $settings;

});

It would be very helpful for register block templates on post/page creation.

@mkdgs
Copy link

mkdgs commented May 11, 2022

@CreativeDive for sure a native solution would be great.
But, i need it now and that's works for me ;)
For the case of ""core/image",i think the odd is in parse_blocks() i don't know why
url attribute is not generated, since it's mandatory.

I'm not using acf, can you confirm this solution not works or not in this case ?

@CreativeDive
Copy link
Contributor Author

@mkdgs it's not only an ACF block issue, it's an issue with all kind of blocks. I have wrote in a comment above, this solution dosen't work because, inline tags will removed. At the end there is a ready to use method in WordPress core using the block_editor_settings_all filter for the default block template. Actually this method could work also for $post_type_object->template, but it requires the attention of a wordpress developer to implement this now.

@mkdgs
Copy link

mkdgs commented May 12, 2022

@CreativeDive i've taking some time to see what is under the hood.
What i understand:

  • It seem's 'defaultBlockTemplate' is parsed with js, not php.
  • '$post_type_object->template' wait a php array.
  • There is no simple way to get this working.
  • And there no existing way if you want to do that on php side.

There is a lack in parse_blocks, but we can try to fill all missing attribute without knowing them.
for fun, a dirty POC :

function convertBlockToTemplate($blocks)
{
    $tpl = [];

    // get specs of all registered blocks
    $registry = WP_Block_Type_Registry::get_instance();
    $blocks_reg   = $registry->get_all_registered();

    foreach ($blocks as $block) {
        if (!empty($block['blockName'])) {         

            // foreach blocks, set the attribute accordling to the source
            if (array_key_exists($block['blockName'], $blocks_reg)) {                
                $block_reg = $blocks_reg[$block['blockName']];
                $dom = null;
                foreach ($block_reg->attributes as $attr => $vattr) {
                    // no html content
                    if (empty($block['innerHTML'])) continue;
                    // no source attribute
                    if (empty($vattr['source'])) continue;

                    if ( in_array($vattr['source'], ['html', 'attribute']) ) {
                        if (empty($vattr['selector'])) {
                            $block['attrs'][$attr] = $block['innerHTML'];
                        } else {
                            // create a dom fragment, only one time per block
                            if (!is_object($dom)) {
                                libxml_use_internal_errors(true);
                                $dom = new \DomDocument('1.0', 'UTF-8');
                                $dom->recover = true;
                                $var = '<div>' . $block['innerHTML'] . '</div>';
                                try {
                                    $dom->loadHTML('<?xml encoding="UTF-8">' . $var);
                                    $dom->encoding = 'UTF-8';
                                } catch (\Exception $e) {
                                }
                            }

                            // create an expression to explore the dom
                            $xpath = new DOMXpath($dom);
                            // maybe interesting to convert css selector to xpath
                            // or find a better way to use css selector with php
                            // https://github.com/ThomasWeinert/PhpCss
                            $xpath_query = "//" . $vattr['selector'];
                            if (!empty($vattr['multiline'])) {
                                $xpath_query = "//" . $vattr['selector'] . '/' . $vattr['multiline'];
                            }                           
                            // get the content with xpath corresponding to the selector and set the attribute
                            $el = $xpath->query($xpath_query);
                            if (is_object($el) && !empty($el[0])) {
                                if ( !empty($vattr['attribute']) ) {
                                    $block['attrs'][$attr] = $el[0]->getAttribute($vattr['attribute']);
                                }
                                else {
                                    $block['attrs'][$attr] = simplexml_import_dom($el[0])->asXML();
                                }                            
                            }
                        }
                    }
                }
            }

            $innerBlocks =  (!empty($block['innerBlocks'])) ?  convertBlockToTemplate($block['innerBlocks']) : array();
            $tpl[] = array(
                $block['blockName'],
                $block['attrs'],
                $innerBlocks               
            ); 
        }
    }
    return $tpl;
}

I'ts a little bit crazy, but it's works with your initial exemple.
Not perfect, but more accurate.
There is may be a way to handle deprecations too.
Not work with blocks registered only through js api

@getdave
Copy link
Contributor

getdave commented Sep 20, 2022

@jorgefilipecosta I light of the fact it's going to be possible to register block patterns for any post type in WordPress 6.1 is there any need to pursue the option I suggested here to allow for block grammar to be used as the template property of a post as well as the current array-based syntax?

I think the patterns solution you've implemented is very similar to what @youknowriad theorised about in his response to my comment when he said:

I guess I could be in favor of soft-replacing the template with an html string like patterns

@jorgefilipecosta
Copy link
Member

@jorgefilipecosta I light of the fact it's going to be possible to register block patterns for any post type in WordPress 6.1 is there any need to pursue the option I suggested here to allow for block grammar to be used as the template property of a post as well as the current array-based syntax?

I think the patterns solution you've implemented is very similar to what @youknowriad theorised about in his response to my comment when he said:

I guess I could be in favor of soft-replacing the template with an html string like patterns

I guess the fact we can have patterns for any post type does not remove the usefulness of having a template CPT as an HTML string in the same format as patterns. So I think the option you suggested is still useful.

@ngearing
Copy link

I just thought I would share the solution I have found, and will use from now on. And this is to use the default_content filter instead. This way I can easily copy blocks from the editor and paste them into a html template, then load that as the default content for a post type.

/** Add Default content for post types. */
function cpt_content_template( $content, $post ) {

	if ( $post->post_type == 'organisation' ) {
		$content = file_get_contents(get_theme_file_path('templates/single-organisation.html'));
	}

	return $content;
}
add_filter('default_content', 'cpt_content_template', 10, 2);

@CreativeDive
Copy link
Contributor Author

@ngearing Thanks, a really good solution. It works in the same use case as using $post_type_object->template.

Since a practical solution add_filter('default_content', ... has now been found here, the issue would actually be solved.

With that I close the issue. Should there still be a need to expand the functionality for $post_type_object->template the issue can be reopened again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Parsing Related to efforts to improving the parsing of a string of data and converting it into a different f Needs Technical Feedback Needs testing from a developer perspective. [Package] Blocks /packages/blocks
Projects
None yet
Development

No branches or pull requests

9 participants