Source: includes/classes/BlockTemplateUtils.php

<?php
/**
 * Block Template Utils class
 *
 * @since 4.7.0
 * @package elasticpress
 */

namespace ElasticPress;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Block Template Utils class
 */
class BlockTemplateUtils {
	const CACHE_KEY = 'ep_blocks';

	/**
	 * Hook cache cleanup calls
	 */
	public function setup() {
		add_action( 'save_post_wp_template', [ $this, 'regenerate_cache' ] );
		add_action( 'save_post_wp_template_part', [ $this, 'regenerate_cache' ] );
		add_action( 'switch_theme', [ $this, 'regenerate_cache' ] );
	}

	/**
	 * Delete and regenerate the cache
	 */
	public function regenerate_cache() {
		delete_transient( self::CACHE_KEY );

		// Simply calling it will reset the transient (if the `ep_blocks_pre_all_blocks` filter isn't in use.)
		$this->get_all_blocks_in_all_templates();
	}

	/**
	 * Given a block name, return all its instances across all block templates
	 *
	 * @param string $block_name The block name, e.g., `elasticpress/facet-meta`
	 * @return array
	 */
	public function get_specific_block_in_all_templates( string $block_name ) : array {
		$blocks = array_filter(
			$this->get_all_blocks_in_all_templates(),
			function ( $block ) use ( $block_name ) {
				return ( $block['blockName'] === $block_name );
			}
		);

		return $blocks;
	}

	/**
	 * Get all blocks in all block templates
	 *
	 * It returns a flat list of all blocks, including innerBlocks.
	 *
	 * @return array
	 */
	public function get_all_blocks_in_all_templates() : array {
		/**
		 * Short-circuits the process of getting all blocks of a template.
		 *
		 * Returning a non-null value will effectively short-circuit the function.
		 *
		 * @since 4.7.0
		 * @hook ep_blocks_pre_all_blocks
		 * @param {null}   $meta_keys Blocks array
		 * @return {null|array} Blocks array or `null` to keep default behavior
		 */
		$pre_all_blocks = apply_filters( 'ep_blocks_pre_all_blocks', null );
		if ( null !== $pre_all_blocks ) {
			return (array) $pre_all_blocks;
		}

		$cache = get_transient( self::CACHE_KEY );
		if ( is_array( $cache ) ) {
			return $cache;
		}

		$all_blocks = [];

		$template_types = [ 'wp_template', 'wp_template_part' ];

		foreach ( $template_types as $template_type ) {
			$block_templates = get_block_templates( [], $template_type );
			foreach ( $block_templates as $block_template ) {
				$template_blocks = parse_blocks( $block_template->content );

				foreach ( $template_blocks as $block ) {
					$all_blocks[] = [
						'blockName' => $block['blockName'],
						'attrs'     => $block['attrs'],
					];

					$all_blocks = $this->recursively_get_inner_blocks( $all_blocks, $block );
				}
			}
		}

		set_transient( self::CACHE_KEY, $all_blocks, MONTH_IN_SECONDS );

		return $all_blocks;
	}

	/**
	 * Get all inner blocks recursively
	 *
	 * @param array $all_blocks All blocks analyzed so far
	 * @param array $block      Block to be analyzed now
	 * @return array
	 */
	protected function recursively_get_inner_blocks( array $all_blocks, array $block ) : array {
		if ( empty( $block['innerBlocks'] ) ) {
			return $all_blocks;
		}

		foreach ( $block['innerBlocks'] as $inner_block ) {
			$all_blocks[] = [
				'blockName' => $inner_block['blockName'],
				'attrs'     => $inner_block['attrs'],
			];

			$all_blocks = $this->recursively_get_inner_blocks( $all_blocks, $inner_block );
		}

		return $all_blocks;
	}
}