diff --git a/elasticpress.php b/elasticpress.php index 9a3bd39874..f510ba4660 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -206,6 +206,8 @@ function register_indexable_posts() { */ $query_logger = apply_filters( 'ep_query_logger', new \ElasticPress\QueryLogger() ); get_container()->set( '\ElasticPress\QueryLogger', $query_logger, true ); + + get_container()->set( '\ElasticPress\BlockTemplateUtils', new \ElasticPress\BlockTemplateUtils(), true ); } add_action( 'plugins_loaded', __NAMESPACE__ . '\register_indexable_posts' ); diff --git a/includes/classes/BlockTemplateUtils.php b/includes/classes/BlockTemplateUtils.php new file mode 100644 index 0000000000..fe1ae40b43 --- /dev/null +++ b/includes/classes/BlockTemplateUtils.php @@ -0,0 +1,133 @@ +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; + } +} diff --git a/includes/classes/Feature/Facets/FacetType.php b/includes/classes/Feature/Facets/FacetType.php index dad760d37e..c11dc819aa 100644 --- a/includes/classes/Feature/Facets/FacetType.php +++ b/includes/classes/Feature/Facets/FacetType.php @@ -89,4 +89,33 @@ public function add_query_params( array $query_params, array $filters ) : array return $query_params; } + + /** + * Get all facet fields selected across all blocks of the current facet type. + * + * Given a block name, e.g., `elasticpress/facet-meta` this method returns all meta fields + * selected in all blocks. + * + * @param string $block_name The block name + * @return array + */ + protected function block_template_meta_fields( string $block_name ) : array { + $block_template_utils = \ElasticPress\get_container()->get( '\ElasticPress\BlockTemplateUtils' ); + $ep_blocks = $block_template_utils->get_specific_block_in_all_templates( $block_name ); + + return array_filter( + array_reduce( + $ep_blocks, + function( $acc, $block ) use ( $block_name ) { + if ( 0 !== strpos( $block['blockName'], $block_name ) ) { + return $acc; + } + + $acc[] = $block['attrs']['facet']; + return $acc; + }, + [] + ) + ); + } } diff --git a/includes/classes/Feature/Facets/Types/Meta/FacetType.php b/includes/classes/Feature/Facets/Types/Meta/FacetType.php index ba53dd299a..9c59f22898 100644 --- a/includes/classes/Feature/Facets/Types/Meta/FacetType.php +++ b/includes/classes/Feature/Facets/Types/Meta/FacetType.php @@ -288,6 +288,13 @@ public function get_facets_meta_fields() { $facets_meta_fields = array_merge( $facets_meta_fields, $matches[1] ); } + if ( current_theme_supports( 'block-templates' ) ) { + $facets_meta_fields = array_merge( + $facets_meta_fields, + $this->block_template_meta_fields( 'elasticpress/facet-meta' ) + ); + } + /** * Filter meta fields to be used in aggregations. * diff --git a/includes/classes/Feature/Facets/Types/MetaRange/FacetType.php b/includes/classes/Feature/Facets/Types/MetaRange/FacetType.php index 9cab44d59c..aa6dce91a9 100644 --- a/includes/classes/Feature/Facets/Types/MetaRange/FacetType.php +++ b/includes/classes/Feature/Facets/Types/MetaRange/FacetType.php @@ -244,6 +244,13 @@ public function get_facets_meta_fields() { $facets_meta_fields = array_merge( $facets_meta_fields, $matches[1] ); } + if ( current_theme_supports( 'block-templates' ) ) { + $facets_meta_fields = array_merge( + $facets_meta_fields, + $this->block_template_meta_fields( 'elasticpress/facet-meta-range' ) + ); + } + /** * Filter meta fields to be used in aggregations related to meta range blocks. * diff --git a/tests/php/TestBlockTemplateUtils.php b/tests/php/TestBlockTemplateUtils.php new file mode 100644 index 0000000000..7320f7f269 --- /dev/null +++ b/tests/php/TestBlockTemplateUtils.php @@ -0,0 +1,124 @@ +regenerate_cache(); + + $this->assertIsArray( get_transient( $block_template_utils::CACHE_KEY ) ); + } + + /** + * Test the `get_specific_block_in_all_templates` method + * + * @group block_template_utils + */ + public function test_get_specific_block_in_all_templates() { + $block_template_utils = new BlockTemplateUtils(); + + $meta_block = [ + 'blockName' => 'elasticpress/facet-meta', + 'attrs' => [ 'facet' => '_price' ], + ]; + + $meta_range_block = [ + 'blockName' => 'elasticpress/facet-meta-range', + 'attrs' => [ 'facet' => '_sale_price' ], + ]; + + $blocks = [ + [ + 'blockName' => 'core/search', + 'attrs' => [ 'label' => 'Search' ], + ], + $meta_block, + $meta_range_block, + ]; + + $set_blocks = function () use ( $blocks ) { + return $blocks; + }; + add_filter( 'ep_blocks_pre_all_blocks', $set_blocks ); + + $this->assertEqualsCanonicalizing( + [ $meta_block ], + $block_template_utils->get_specific_block_in_all_templates( 'elasticpress/facet-meta' ) + ); + $this->assertEqualsCanonicalizing( + [ $meta_range_block ], + $block_template_utils->get_specific_block_in_all_templates( 'elasticpress/facet-meta-range' ) + ); + } + + /** + * Test the `get_all_blocks_in_all_templates` method + * + * @group block_template_utils + */ + public function test_get_all_blocks_in_all_templates() { + $this->markTestIncomplete( 'This test should also test the real returns from get_block_templates(), etc.' ); + } + + /** + * Test the `ep_blocks_pre_all_blocks` filter + * + * @group block_template_utils + */ + public function test_get_all_blocks_in_all_templates_ep_blocks_pre_all_blocks() { + $block_template_utils = new BlockTemplateUtils(); + + $times_called = did_filter( 'pre_transient_' . $block_template_utils::CACHE_KEY ); + + $set_blocks = function ( $pre_all_blocks ) { + $this->assertNull( $pre_all_blocks ); + return [ 'test' ]; + }; + add_filter( 'ep_blocks_pre_all_blocks', $set_blocks ); + + $this->assertSame( [ 'test' ], $block_template_utils->get_all_blocks_in_all_templates() ); + + // This filter should not have been called once more + $this->assertSame( $times_called, did_filter( 'pre_transient_' . $block_template_utils::CACHE_KEY ) ); + } + + /** + * Test the `get_all_blocks_in_all_templates` transient + * + * @group block_template_utils + */ + public function test_get_all_blocks_in_all_templates_transient() { + $block_template_utils = new BlockTemplateUtils(); + delete_transient( $block_template_utils::CACHE_KEY ); + + $times_called = did_filter( 'pre_set_transient_' . $block_template_utils::CACHE_KEY ); + + // This filter should be called once to set the transient + $block_template_utils->get_all_blocks_in_all_templates(); + $expected_times_called = $times_called + 1; + $this->assertSame( $expected_times_called, did_filter( 'pre_set_transient_' . $block_template_utils::CACHE_KEY ) ); + + // This filter should not be called again, because the transient was already set + $block_template_utils->get_all_blocks_in_all_templates(); + $this->assertSame( $expected_times_called, did_filter( 'pre_set_transient_' . $block_template_utils::CACHE_KEY ) ); + } +}