• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rtCamp / snapwp-helper / 13504100917

24 Feb 2025 05:30PM UTC coverage: 83.585% (+0.2%) from 83.424%
13504100917

Pull #89

github

web-flow
Merge 83e7d05b8 into 8e49111f1
Pull Request #89: Fix : `templateByUri.editorBlocks` to respect `flat: false`

1548 of 1852 relevant lines covered (83.59%)

13.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.29
/src/Modules/GraphQL/SchemaFilters.php
1
<?php
2
/**
3
 * Filters on the existing WPGraphQL schema.
4
 *
5
 * @package SnapWP\Helper\Modules\GraphQL
6
 */
7

8
declare( strict_types = 1 );
9

10
namespace SnapWP\Helper\Modules\GraphQL;
11

12
use SnapWP\Helper\Interfaces\Registrable;
13
use SnapWP\Helper\Modules\GraphQL\Data\ContentBlocksResolver;
14
use SnapWP\Helper\Modules\GraphQL\Server\DisableIntrospectionRule;
15
use SnapWP\Helper\Modules\GraphQL\Type\WPObject\RenderedTemplate;
16

17
/**
18
 * Class - SchemaFilters
19
 */
20
final class SchemaFilters implements Registrable {
21
        /**
22
         * The cache key prefix for rendered blocks.
23
         *
24
         * @var string
25
         */
26
        protected const BLOCK_CACHE_KEY_PREFIX = 'rendered_block-';
27

28
        /**
29
         * The cache group for rendered blocks.
30
         *
31
         * @var string
32
         */
33
        protected const BLOCK_CACHE_GROUP = 'snapwp-helper';
34

35
        /**
36
         * {@inheritDoc}
37
         *
38
         * There's need to check for dependencies, since missing filters will just be ignored.
39
         */
40
        public function register_hooks(): void {
1✔
41
                // Register custom validation rule for introspection.
42
                add_filter( 'graphql_validation_rules', [ $this, 'add_custom_validation_rule' ] );
1✔
43

44
                // Use our own resolver for content blocks.
45
                add_filter( 'wpgraphql_content_blocks_resolver_content', [ $this, 'get_content_for_resolved_template' ], 10, 2 );
1✔
46
                add_filter( 'graphql_object_fields', [ $this, 'overload_content_blocks_resolver' ], 10, 2 );
1✔
47
                add_filter( 'graphql_interface_fields', [ $this, 'overload_rendered_html' ], 10, 2 );
1✔
48

49
                // Cache rendered blocks.
50
                add_filter( 'pre_render_block', [ $this, 'get_cached_rendered_block' ], 10, 2 );
1✔
51
                // We want to cache the rendered block as late as possible to ensure we're caching the final output.
52
                add_filter( 'render_block', [ $this, 'cache_rendered_block' ], PHP_INT_MAX - 1, 2 );
1✔
53
        }
54

55
        /**
56
         * Adds a custom validation rule for introspection.
57
         *
58
         * @param array<int|string,\GraphQL\Validator\Rules\ValidationRule> $rules The existing validation rules.
59
         *
60
         * @return array<int|string,\GraphQL\Validator\Rules\ValidationRule> The modified validation rules.
61
         */
62
        public function add_custom_validation_rule( array $rules ): array {
37✔
63
                // Replace the default DisableIntrospection rule with our own.
64
                $rules['disable_introspection'] = new DisableIntrospectionRule();
37✔
65
                return $rules;
37✔
66
        }
67

68
        /**
69
         * Gets the content from the model for parsing by WPGraphQL ContentBlocks.
70
         *
71
         * @param string                 $content The content to parse.
72
         * @param \WPGraphQL\Model\Model $model   The model to get content from.
73
         *
74
         * @return string The content to parse.
75
         */
76
        public function get_content_for_resolved_template( $content, $model ) {
31✔
77
                if ( is_array( $model ) && isset( $model['content'] ) ) {
31✔
78
                        return $model['content'] ?? '';
31✔
79
                }
80

81
                return $content;
×
82
        }
83

84
        /**
85
         * Overloads the content blocks resolver to use ourn own resolver.
86
         *
87
         * @todo This is necessary because WPGraphQL Content Blocks' resolver is broken.
88
         *
89
         * @param array<string,mixed> $fields The config for the interface type.
90
         * @param string              $typename The name of the interface type.
91
         *
92
         * @return array<string,mixed>
93
         */
94
        public function overload_content_blocks_resolver( array $fields, $typename ): array {
34✔
95
                if ( ! isset( $fields['editorBlocks'] ) ) {
34✔
96
                        return $fields;
34✔
97
                }
98

99
                // RenderedTemplate is a special case, as it already has the blocks resolved.
100
                if ( RenderedTemplate::get_type_name() === $typename ) {
31✔
101
                        $fields['editorBlocks']['resolve'] = static function ( $node, $args ) {
31✔
102
                                if ( ! empty( $args['flat'] ) ) {
1✔
103
                                        return ContentBlocksResolver::flatten_block_list( $node->parsed_blocks );
1✔
104
                                }
105

106
                                return $node->parsed_blocks;
1✔
107
                        };
31✔
108

109
                        return $fields;
31✔
110
                }
111

112
                // Use our own resolver for the rest of the types.
113
                $fields['editorBlocks']['resolve'] = static function ( $node, $args ) {
20✔
114
                        return ContentBlocksResolver::resolve_content_blocks( $node, $args );
×
115
                };
20✔
116

117
                return $fields;
20✔
118
        }
119

120
        /**
121
         * Overloads the EditorBlock renderedHtml to avoid using render_block() multiple times.
122
         *
123
         * @param array<string,mixed> $fields The config for the interface type.
124
         * @param string              $typename The name of the interface type.
125
         *
126
         * @return array<string,mixed>
127
         */
128
        public function overload_rendered_html( array $fields, $typename ): array {
32✔
129
                if ( 'EditorBlock' === $typename && isset( $fields['renderedHtml'] ) ) {
32✔
130
                        $fields['renderedHtml']['resolve'] = static function ( $block ) {
21✔
131
                                return $block['renderedHtml'] ?? null;
×
132
                        };
21✔
133
                }
134

135
                return $fields;
32✔
136
        }
137

138
        /**
139
         * Get the cached rendered block.
140
         *
141
         * Blocks are cached to stabilize the rendering of inline classes, no matter how many times the block is rendered.
142
         * This is necessary because WPGraphQL Content Blocks calls render_block multiple times for the same block, causing the unique ids appended to the injected classes to change.
143
         *
144
         * @param ?string             $block_content The prerendered block content. This will be null if the block has not been rendered yet.
145
         * @param array<string,mixed> $parsed_block  The block array.
146
         *
147
         * @return ?string The cached rendered block.
148
         */
149
        public function get_cached_rendered_block( $block_content, $parsed_block ) {
31✔
150
                // Bail if not a GraphQL request.
151
                if ( ! class_exists( 'WPGraphQL' ) || ! \WPGraphQL::is_graphql_request() ) {
31✔
152
                        return $block_content;
×
153
                }
154

155
                // Bail if block content is already set.
156
                if ( null !== $block_content || empty( $parsed_block ) ) {
31✔
157
                        return $block_content;
×
158
                }
159

160
                $cache_key = $this->get_cache_key( $parsed_block );
31✔
161

162
                if ( null === $cache_key ) {
31✔
163
                        return $block_content;
31✔
164
                }
165

166
                $rendered_block = wp_cache_get( $cache_key, self::BLOCK_CACHE_GROUP );
31✔
167

168
                // If we've cached the block, return it.
169
                return false !== $rendered_block ? $rendered_block : $block_content;
31✔
170
        }
171

172
        /**
173
         * Cache the rendered block.
174
         *
175
         * This filter is called as late as possible to ensure we're caching the final output.
176
         *
177
         * @param string              $block_content The rendered block content.
178
         * @param array<string,mixed> $parsed_block  The block array.
179
         *
180
         * @return string The rendered block content.
181
         */
182
        public function cache_rendered_block( $block_content, $parsed_block ) {
31✔
183
                // Bail if not a GraphQL request.
184
                if ( ! class_exists( 'WPGraphQL' ) || ! \WPGraphQL::is_graphql_request() ) {
31✔
185
                        return $block_content;
×
186
                }
187

188
                $cache_key = $this->get_cache_key( $parsed_block );
31✔
189

190
                // Bail if we couldn't get a cache key.
191
                if ( null === $cache_key ) {
31✔
192
                        return $block_content;
31✔
193
                }
194

195
                wp_cache_set( $cache_key, $block_content, self::BLOCK_CACHE_GROUP );
31✔
196

197
                return $block_content;
31✔
198
        }
199

200
        /**
201
         * Gets the cache key for a block.
202
         *
203
         * @param array<string,mixed> $parsed_block The block array.
204
         */
205
        protected function get_cache_key( array $parsed_block ): ?string {
31✔
206
                if ( empty( $parsed_block['clientId'] ) ) {
31✔
207
                        return null;
31✔
208
                }
209

210
                return self::BLOCK_CACHE_KEY_PREFIX . $parsed_block['clientId'];
31✔
211
        }
212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc