From 5421ccd9df35f7b7a66257cf09b0f89d36718abb Mon Sep 17 00:00:00 2001 From: Ta5r Date: Tue, 5 Nov 2024 15:50:57 +0530 Subject: [PATCH 1/2] dev : backport changes from CBR from block-model-david and SchemaFilters from model-compat branches --- .../GraphQL/Data/ContentBlocksResolver.php | 97 ++++++++++++++----- src/Modules/GraphQL/SchemaFilters.php | 27 +----- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/src/Modules/GraphQL/Data/ContentBlocksResolver.php b/src/Modules/GraphQL/Data/ContentBlocksResolver.php index b640518..7703e76 100644 --- a/src/Modules/GraphQL/Data/ContentBlocksResolver.php +++ b/src/Modules/GraphQL/Data/ContentBlocksResolver.php @@ -27,6 +27,20 @@ final class ContentBlocksResolver { public static function resolve_content_blocks( $node, $args, $allowed_block_names = [] ): array { global $post_id; + /** + * When this filter returns a non-null value, the content blocks resolver will use that value + * + * @param ?array $content_blocks The content blocks to parse. + * @param \WPGraphQL\Model\Model $node The node we are resolving. + * @param array $args GraphQL query args to pass to the connection resolver. + * @param array $allowed_block_names The list of allowed block names to filter. + */ + $pre_resolved_blocks = apply_filters( 'wpgraphql_content_blocks_pre_resolve_blocks', null, $node, $args, $allowed_block_names ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPGraphQL filter. + + if ( null !== $pre_resolved_blocks && is_array( $pre_resolved_blocks ) ) { + return $pre_resolved_blocks; + } + $content = null; if ( $node instanceof Post ) { @@ -66,15 +80,18 @@ public static function resolve_content_blocks( $node, $args, $allowed_block_name } // Final level of filtering out blocks not in the allowed list. - if ( ! empty( $allowed_block_names ) ) { - $parsed_blocks = array_filter( - $parsed_blocks, - static function ( $parsed_block ) use ( $allowed_block_names ) { - return in_array( $parsed_block['blockName'], $allowed_block_names, true ); - }, - ARRAY_FILTER_USE_BOTH - ); - } + $parsed_blocks = self::filter_allowed_blocks( $parsed_blocks, $allowed_block_names ); + + /** + * Filters the content blocks after they have been resolved. + * + * @param array $parsed_blocks The parsed blocks. + * @param \WPGraphQL\Model\Model $node The node we are resolving. + * @param array $args GraphQL query args to pass to the connection resolver. + * @param array $allowed_block_names The list of allowed block names to filter. + */ + $parsed_blocks = apply_filters( 'wpgraphql_content_blocks_resolve_blocks', $parsed_blocks, $node, $args, $allowed_block_names ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPGraphQL filter. + return $parsed_blocks; } @@ -101,13 +118,16 @@ private static function flatten_block_list( $blocks ): array { * @return array The flattened block. */ private static function flatten_inner_blocks( $block ): array { - $result = []; + $result = []; + + // Assign a unique clientId to the block if it doesn't already have one. $block['clientId'] = isset( $block['clientId'] ) ? $block['clientId'] : uniqid(); array_push( $result, $block ); foreach ( $block['innerBlocks'] as $child ) { $child['parentClientId'] = $block['clientId']; + // Flatten the child, and merge with the result. $result = array_merge( $result, self::flatten_inner_blocks( $child ) ); } @@ -131,6 +151,8 @@ private static function parse_blocks( $content ): array { /** * Recursively process blocks. * + * This mirrors the `do_blocks` function in WordPress which is responsible for hydrating certain block attributes and supports, but without the forced rendering. + * * @param array[] $blocks Blocks data. * * @return array[] The processed blocks. @@ -145,7 +167,7 @@ private static function handle_do_blocks( array $blocks ): array { } // Remove empty blocks. - return array_filter( $parsed ); + return array_values( array_filter( $parsed ) ); } /** @@ -177,6 +199,13 @@ private static function handle_do_block( array $block ): ?array { $block = self::populate_pattern_inner_blocks( $block ); + /** + * Filters the block data after it has been processed. + * + * @param array $block The block data. + */ + $block = apply_filters( 'wpgraphql_content_blocks_handle_do_block', $block ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPGraphQL filter. + // Prepare innerBlocks. if ( ! empty( $block['innerBlocks'] ) ) { $block['innerBlocks'] = self::handle_do_blocks( $block['innerBlocks'] ); @@ -196,19 +225,12 @@ private static function is_block_empty( array $block ): bool { return false; } - if ( ! empty( $block['innerBlocks'] ) || ! empty( trim( $block['innerHTML'] ) ) ) { - return false; + // If there is no innerHTML or innerContent, we can consider it empty. + if ( empty( $block['innerHTML'] ) && empty( $block['innerContent'] ) ) { + return true; } - // $block['innerContent'] can be an array, we need to check if it's empty, including empty strings. - if ( ! empty( $block['innerContent'] ) ) { - $inner_content = implode( '', $block['innerContent'] ); - if ( ! empty( trim( $inner_content ) ) ) { - return false; - } - } - - $stripped = preg_replace( '//Uis', '', render_block( $block ) ); + $stripped = preg_replace( '//Uis', '', $block['innerHTML'] ); return empty( trim( $stripped ?? '' ) ); } @@ -221,6 +243,11 @@ private static function is_block_empty( array $block ): bool { * @return array The populated block. */ private static function populate_template_part_inner_blocks( array $block ): array { + // Bail if not WP 5.8 or later. + if ( ! function_exists( 'get_block_templates' ) ) { + return $block; + } + if ( 'core/template-part' !== $block['blockName'] || ! isset( $block['attrs']['slug'] ) ) { return $block; } @@ -301,6 +328,11 @@ private static function populate_post_content_inner_blocks( array $block ): arra * @return array The populated block. */ private static function populate_pattern_inner_blocks( array $block ): array { + // Bail if not WP 6.6 or later. + if ( ! function_exists( 'resolve_pattern_blocks' ) ) { + return $block; + } + if ( 'core/pattern' !== $block['blockName'] || ! isset( $block['attrs']['slug'] ) ) { return $block; } @@ -315,4 +347,25 @@ private static function populate_pattern_inner_blocks( array $block ): array { return $block; } + + /** + * Filters out disallowed blocks from the list of blocks + * + * @param array $blocks A list of blocks to filter. + * @param string[] $allowed_block_names The list of allowed block names to filter. + * + * @return array The filtered list of blocks. + */ + private static function filter_allowed_blocks( array $blocks, array $allowed_block_names ): array { + if ( empty( $allowed_block_names ) ) { + return $blocks; + } + + return array_filter( + $blocks, + static function ( $block ) use ( $allowed_block_names ) { + return in_array( $block['blockName'], $allowed_block_names, true ); + } + ); + } } diff --git a/src/Modules/GraphQL/SchemaFilters.php b/src/Modules/GraphQL/SchemaFilters.php index 602f41e..8580197 100644 --- a/src/Modules/GraphQL/SchemaFilters.php +++ b/src/Modules/GraphQL/SchemaFilters.php @@ -37,11 +37,11 @@ final class SchemaFilters implements Registrable { */ public function register_hooks(): void { // No need to check for dependencies, since missing filters will just be ignored. + add_filter( 'wpgraphql_content_blocks_handle_do_block', [ ContentBlocksResolver::class, 'handle_do_block' ], 10, 1 ); add_filter( 'wpgraphql_content_blocks_resolver_content', [ $this, 'get_content_from_model' ], 10, 2 ); - add_filter( 'graphql_object_fields', [ $this, 'overload_content_blocks_resolver' ], 10 ); // Cache rendered blocks. - add_filter( 'pre_render_block', [ $this, 'get_cached_rendered_block' ], 10, 2 ); // @todo: this should be as early priority as possible + add_filter( 'pre_render_block', [ $this, 'get_cached_rendered_block' ], 11, 2 ); // @todo: this should be as early priority as possible // We want to cache the rendered block as late as possible to ensure we're caching the final output. add_filter( 'render_block', [ $this, 'cache_rendered_block' ], PHP_INT_MAX - 1, 2 ); } @@ -60,27 +60,6 @@ public function get_content_from_model( $content, $model ): string { return $content; } - /** - * Overloads the content blocks resolver to use ourn own resolver. - * - * @todo This is necessary because WPGraphQL Content Blocks' resolver is broken. - * - * @param array $fields The config for the interface type. - * - * @return array - */ - public function overload_content_blocks_resolver( array $fields ): array { - if ( ! isset( $fields['editorBlocks'] ) ) { - return $fields; - } - - $fields['editorBlocks']['resolve'] = static function ( $node, $args ) { - return ContentBlocksResolver::resolve_content_blocks( $node, $args ); - }; - - return $fields; - } - /** * Get the cached rendered block. * @@ -99,7 +78,7 @@ public function get_cached_rendered_block( $block_content, $parsed_block ) { } // Bail if block content is already set. - if ( null !== $block_content ) { + if ( null !== $block_content || empty( $parsed_block ) ) { return $block_content; } From b1495771c99d4f76b17bd0543c61ab2e4b5fd2dc Mon Sep 17 00:00:00 2001 From: Ta5r Date: Wed, 6 Nov 2024 11:28:24 +0530 Subject: [PATCH 2/2] fix : graphql_object_fields filter to overload the entire class. --- .../GraphQL/Data/ContentBlocksResolver.php | 1 - src/Modules/GraphQL/SchemaFilters.php | 23 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Modules/GraphQL/Data/ContentBlocksResolver.php b/src/Modules/GraphQL/Data/ContentBlocksResolver.php index 7703e76..7b95fa9 100644 --- a/src/Modules/GraphQL/Data/ContentBlocksResolver.php +++ b/src/Modules/GraphQL/Data/ContentBlocksResolver.php @@ -25,7 +25,6 @@ final class ContentBlocksResolver { * @return array The list of content blocks. */ public static function resolve_content_blocks( $node, $args, $allowed_block_names = [] ): array { - global $post_id; /** * When this filter returns a non-null value, the content blocks resolver will use that value diff --git a/src/Modules/GraphQL/SchemaFilters.php b/src/Modules/GraphQL/SchemaFilters.php index 8580197..7dcb213 100644 --- a/src/Modules/GraphQL/SchemaFilters.php +++ b/src/Modules/GraphQL/SchemaFilters.php @@ -37,7 +37,7 @@ final class SchemaFilters implements Registrable { */ public function register_hooks(): void { // No need to check for dependencies, since missing filters will just be ignored. - add_filter( 'wpgraphql_content_blocks_handle_do_block', [ ContentBlocksResolver::class, 'handle_do_block' ], 10, 1 ); + add_filter( 'graphql_object_fields', [ $this, 'overload_content_blocks_resolver' ], 10 ); add_filter( 'wpgraphql_content_blocks_resolver_content', [ $this, 'get_content_from_model' ], 10, 2 ); // Cache rendered blocks. @@ -60,6 +60,27 @@ public function get_content_from_model( $content, $model ): string { return $content; } + /** + * Overloads the content blocks resolver to use ourn own resolver. + * + * @todo This is necessary because WPGraphQL Content Blocks' resolver is broken. + * + * @param array $fields The config for the interface type. + * + * @return array + */ + public function overload_content_blocks_resolver( array $fields ): array { + if ( ! isset( $fields['editorBlocks'] ) ) { + return $fields; + } + + $fields['editorBlocks']['resolve'] = static function ( $node, $args ) { + return ContentBlocksResolver::resolve_content_blocks( $node, $args ); + }; + + return $fields; + } + /** * Get the cached rendered block. *