Recently I was assigned the task of building a template that would list a few posts of a specific type. It was a simple loop, with the difference being that instead of a featured image, each entry had to display a carousel, which was a custom Gutenberg block.

Luckily, a quick search brought me to the parse_blocks() function, which allows us to get the content of a post and return an array of the blocks that it contains. So, how can we write a simple function to get a specific Gutenberg block from a post and display it outside of the post content?

First, we need to get the blocks of the post as an array:

$blocks = parse_blocks( get_the_content( null, false, $post_id ) );

We could entirely omit the parameters of get_the_content(), which would fetch the blocks of the current WP_Post instance. Since we might want to use it outside the loop on some occasions, though, we will need to pass the $post_id as a variable.

This should give us an array of all the blocks of the post, with each entry containing various interesting information like the block name, its attributes, and its actual content. Here is an example of such an array:

array (size=5)
  0 => 
    array (size=5)
      'blockName' => string 'core/paragraph' (length=14)
      'attrs' => 
        array (size=1)
          'className' => string '' (length=0)
      'innerBlocks' => 
        array (size=0)
          empty
      'innerHTML' => string '
<p class="">This is a paragraph block</p>
' (length=43)
      'innerContent' => 
        array (size=1)
          0 => string '
<p class="">This is a paragraph block</p>
' (length=43)
  1 => 
    array (size=5)
      'blockName' => null
      'attrs' => 
        array (size=0)
          empty
      'innerBlocks' => 
        array (size=0)
          empty
      'innerHTML' => string '

' (length=2)
      'innerContent' => 
        array (size=1)
          0 => string '

' (length=2)
  2 => 
    array (size=5)
      'blockName' => string 'core/heading' (length=12)
      'attrs' => 
        array (size=1)
          'className' => string '' (length=0)
      'innerBlocks' => 
        array (size=0)
          empty
      'innerHTML' => string '
<h2 class="">This is a heading block</h2>
' (length=43)
      'innerContent' => 
        array (size=1)
          0 => string '
<h2 class="">This is a heading block</h2>
' (length=43)
  3 => 
    array (size=5)
      'blockName' => null
      'attrs' => 
        array (size=0)
          empty
      'innerBlocks' => 
        array (size=0)
          empty
      'innerHTML' => string '

' (length=2)
      'innerContent' => 
        array (size=1)
          0 => string '

' (length=2)
  4 => 
    array (size=5)
      'blockName' => string 'core/gallery' (length=12)
      'attrs' => 
        array (size=1)
          'ids' => 
            array (size=2)
              ...
      'innerBlocks' => 
        array (size=0)
          empty
      'innerHTML' => string '
<figure class="wp-block-gallery columns-2 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img src="#" alt="" data-id="1428" data-full-url="#" data-link="#'... (length=1026)
      'innerContent' => 
        array (size=1)
          0 => string '
<figure class="wp-block-gallery columns-2 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img src="#" alt="" data-id="1428" data-full-url="#" data-link="#'... (length=1026)

From there, things are pretty straightforward. All we need to do is loop through the blocks and find the one that we want to use as featured content. In its simplest form, assuming that we want to get the gallery block, something like this would do the job:

foreach ( $blocks as $block ) {
   if ( 'core/gallery' === $block['blockName'] ) {
     echo apply_filters( 'the_content', render_block( $block ) );
     break;
   }
}

The code above would loop through the blocks and as soon as it found the first instance of the gallery block would echo it and stop. For an one-off task, this might be enough. What if you had more than one galleries on the same post, though, and wanted to display all of them instead of only the first, or what if you wanted to search for more than one block? Since we came so far, we can take a some more time and make a more reusable and flexible function.

As a first step, we prepare our function, with the variables that we want it to accept:

function get_featured_block( $block_names = [], $selector = 'first', $post_id = null ) {
   $blocks = parse_blocks( get_the_content( null, false, $post_id ) );
}

The first parameter will accept the names of the blocks that we want to display. It could be a string, but I prefer an array, as it gives us more flexibility in case we need more than one block on some projects. The second variable will give us some flexibility when it comes to how many and which blocks we would like to show. Finally, the third parameter is the post ID, in case we need to call the function outside the loop.

The first parameter is the only absolutely necessary parameter in order for the function to work, and the rest can be omitted. Since we made this an array instead of a string, the condition if ( 'core/gallery' === $block['blockName'] ) that we mentioned earlier will not work anymore. Our code, instead, should use the in_array() function to check whether the block name is among the blocks that we care about. Also, we should prefer to return the output instead of echoing it, which would allow us to store it to a variable. This could be particularly useful if we wanted to display a fallback (eg. the post’s featured image) if the block that we seek doesn’t exist. So, we end up with something like this:

function get_featured_block( $block_names = [], $selector = 'first', $post_id = null ) {
    $blocks = parse_blocks( get_the_content( null, false, $post_id ) );
    $output = '';
    foreach ( $blocks as $block ) {
        if ( in_array( $block['blockName'], $block_names, true ) ) {
            $output = apply_filters( 'the_content', render_block( $block ) );
            break;
        }
    }

    return $output;
}

We achieve the same result as before, only this time our function is more flexible and reusable. It will still fetch only the first block that it finds, though, as we still don’t take advantage of the $selector  parameter. To do so, we will modify our loop to make a few checks depending on how many blocks we want to display, and which in particular. Here is the final code:

function get_featured_block( $block_names = [], $selector = 'first', $post_id = null ) {
    $blocks     = parse_blocks( get_the_content( null, false, $post_id ) );
    $blocks_arr = [];
    foreach ( $blocks as $block ) {
        if ( in_array( $block['blockName'], $block_names, true ) ) {
            $blocks_arr[] = $block;
        }
    }
    $output = '';
    foreach ( $blocks_arr as $block ) {
        if ( 'all' === $selector ) {
            $output .= render_block( $block );
        } elseif ( 'first' === $selector ) {
            $output = render_block( $blocks_arr[0] );
        } elseif ( 'last' === $selector ) {
            $output = render_block( end( $blocks_arr ) );
        } else {
            $int = intval( $selector );
            if ( isset( $blocks_arr[ $int ] ) ) {
                $output = render_block( $blocks_arr[ $int ] );
            }
        }
    }

    return apply_filters( 'the_content', $output );
}

What we do here is quite simple. First, we loop over the blocks and extract those that match our criteria in a new array. Then, we loop over that array, and depending on the value given on the $selector parameter, we output the corresponding content. If we pass “all”, then all corresponding blocks will be displayed. If we pass “first” or “last” it will display the first or the last block respectively. To make it more specific, we can pass an integer with the exact position of the block that we want to fetch. For example, on a post with multiple galleries, calling get_featured_block( [ 'core/gallery' ], 2 ) should get us the third gallery of the post (remember that count in arrays starts from 0). Passing no $selector  parameter at all, should display the first block.

Keep in mind that if your block requires a specific script or CSS file, you will need to enqueue the assets on the page that you call them.

Also, this function will not find blocks within inner (nested) blocks and it will only retrieve first-level blocks only. If you need the same functionality with support for Inner Blocks, you can take a look at the DareDev must-use plugin.

Leave a Reply

Your email address will not be published. Required fields are marked *