From 95ceb4dc53d2bf43883f3291af52b9e643408c5d Mon Sep 17 00:00:00 2001 From: Tanay Srivastava <62954323+Ta5r@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:20:07 +0530 Subject: [PATCH] Fix: Back-port compatibility fixes for wp-graphql-content-blocks to SnapWP Helper plugin (#28) * dev : backport changes from CBR from block-model-david and SchemaFilters from model-compat branches * fix : graphql_object_fields filter to overload the entire class. --- .../GraphQL/Data/ContentBlocksResolver.php | 98 ++++++++++++++----- src/Modules/GraphQL/SchemaFilters.php | 6 +- 2 files changed, 78 insertions(+), 26 deletions(-) diff --git a/src/Modules/GraphQL/Data/ContentBlocksResolver.php b/src/Modules/GraphQL/Data/ContentBlocksResolver.php index b640518..7b95fa9 100644 --- a/src/Modules/GraphQL/Data/ContentBlocksResolver.php +++ b/src/Modules/GraphQL/Data/ContentBlocksResolver.php @@ -25,7 +25,20 @@ 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 + * + * @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 +79,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 +117,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 +150,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 +166,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 +198,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 +224,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 +242,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 +327,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 +346,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..7dcb213 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_resolver_content', [ $this, 'get_content_from_model' ], 10, 2 ); 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. - 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 ); } @@ -99,7 +99,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; }