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

rtCamp / snapwp-helper / 13606802535

01 Mar 2025 04:43PM UTC coverage: 82.359% (+0.07%) from 82.291%
13606802535

push

github

web-flow
chore: Remove unnecessary WP version checks (#92)

* chore: Remove unnecessary version checks

* feat: Add changelog entry

1564 of 1899 relevant lines covered (82.36%)

13.04 hits per line

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

77.19
/src/Modules/GraphQL/Data/ContentBlocksResolver.php
1
<?php
2
/**
3
 * Used to resolve content blocks from a node.
4
 *
5
 * Replaces WPGraphQL\ContentBlocks\Data\ContentBlocksResolver to avoid bugs.
6
 *
7
 * @package SnapWP\Helper\Modules\GraphQL\Data
8
 */
9

10
namespace SnapWP\Helper\Modules\GraphQL\Data;
11

12
use WPGraphQL\Model\Post;
13

14
/**
15
 * Class ContentBlocksResolver
16
 */
17
final class ContentBlocksResolver {
18
        /**
19
         * Retrieves a list of content blocks
20
         *
21
         * @param \WPGraphQL\Model\Model|array<string,mixed> $node The node we are resolving.
22
         * @param array<string,mixed>                        $args GraphQL query args to pass to the connection resolver.
23
         * @param string[]                                   $allowed_block_names The list of allowed block names to filter.
24
         *
25
         * @return array<string,mixed> The resolved parsed blocks.
26
         */
27
        public static function resolve_content_blocks( $node, $args, $allowed_block_names = [] ): array {
31✔
28
                /**
29
                 * When this filter returns a non-null value, the content blocks resolver will use that value
30
                 *
31
                 * @see WPGraphQL\ContentBlocks\Data\ContentBlocksResolver::resolve_content_blocks()
32
                 */
33
                $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.
31✔
34

35
                if ( null !== $pre_resolved_blocks && is_array( $pre_resolved_blocks ) ) {
31✔
36
                        return $pre_resolved_blocks;
×
37
                }
38

39
                $content = null;
31✔
40
                if ( $node instanceof Post ) {
31✔
41

42
                        // @todo: this is restricted intentionally.
43
                        // $content = $node->contentRaw;
44

45
                        // This is the unrestricted version, but we need to
46
                        // probably have a "Block" Model that handles
47
                        // determining what fields should/should not be
48
                        // allowed to be returned?
49
                        $post    = get_post( $node->databaseId );
×
50
                        $content = ! empty( $post->post_content ) ? $post->post_content : null;
×
51
                }
52

53
                /**
54
                 * Filters the content retrieved from the node used to parse the blocks.
55
                 *
56
                 * @see WPGraphQL\ContentBlocks\Data\ContentBlocksResolver::resolve_content_blocks()
57
                 */
58
                $content = apply_filters( 'wpgraphql_content_blocks_resolver_content', $content, $node, $args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPGraphQL filter.
31✔
59

60
                if ( empty( $content ) ) {
31✔
61
                        return [];
×
62
                }
63

64
                // Parse the blocks from HTML comments to an array of blocks.
65
                $parsed_blocks = self::parse_blocks( $content );
31✔
66
                if ( empty( $parsed_blocks ) ) {
31✔
67
                        return [];
×
68
                }
69

70
                // Flatten block list here if requested or if 'flat' value is not selected (default).
71
                if ( ! isset( $args['flat'] ) || 'true' == $args['flat'] ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
31✔
72
                        $parsed_blocks = self::flatten_block_list( $parsed_blocks );
×
73
                }
74

75
                // Final level of filtering out blocks not in the allowed list.
76
                $parsed_blocks = self::filter_allowed_blocks( $parsed_blocks, $allowed_block_names );
31✔
77

78
                /**
79
                 * Filters the content blocks after they have been resolved.
80
                 *
81
                 * @see WPGraphQL\ContentBlocks\Data\ContentBlocksResolver::resolve_content_blocks()
82
                 */
83
                $parsed_blocks = apply_filters( 'wpgraphql_content_blocks_resolve_blocks', $parsed_blocks, $node, $args, $allowed_block_names ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPGraphQL filter.
31✔
84

85
                return $parsed_blocks;
31✔
86
        }
87

88
        /**
89
         * Get blocks from html string.
90
         *
91
         * @param string $content Content to parse.
92
         *
93
         * @return array<string,mixed> List of blocks.
94
         */
95
        private static function parse_blocks( $content ): array {
31✔
96
                $blocks = parse_blocks( $content );
31✔
97

98
                return self::handle_do_blocks( $blocks );
31✔
99
        }
100

101
        /**
102
         * Recursively process blocks.
103
         *
104
         * This mirrors the `do_blocks` function in WordPress which is responsible for hydrating certain block attributes and supports, but without the forced rendering.
105
         *
106
         * @param array<string,mixed>[] $blocks Blocks data.
107
         *
108
         * @return array<string,mixed>[] The processed blocks.
109
         */
110
        private static function handle_do_blocks( array $blocks ): array {
31✔
111
                $parsed = [];
31✔
112
                foreach ( $blocks as $block ) {
31✔
113
                        $block_data = self::handle_do_block( $block );
31✔
114

115
                        if ( ! empty( $block_data ) ) {
31✔
116
                                $parsed[] = $block_data;
31✔
117
                        }
118
                }
119

120
                // Remove empty blocks.
121
                return $parsed;
31✔
122
        }
123

124
        /**
125
         * Process a block, getting all extra fields.
126
         *
127
         * @param array<string,mixed> $block Block data.
128
         *
129
         * @return ?array<string,mixed> The processed block.
130
         */
131
        private static function handle_do_block( array $block ): ?array {
31✔
132
                if ( self::is_block_empty( $block ) ) {
31✔
133
                        return null;
31✔
134
                }
135

136
                // Since Gutenberg assigns an empty blockName for Classic block, we define it here.
137
                if ( empty( trim( $block['blockName'] ) ) ) {
31✔
138
                        $block['blockName'] = 'core/freeform';
12✔
139
                }
140

141
                // Assign a unique clientId to the block.
142
                $block['clientId'] = uniqid();
31✔
143

144
                // Render the HTML once and store it for later use.
145
                $block['renderedHtml'] = render_block( $block );
31✔
146

147
                // @todo apply more hydrations.
148
                $block = self::populate_template_part_inner_blocks( $block );
31✔
149
                $block = self::populate_post_content_inner_blocks( $block );
31✔
150
                $block = self::populate_reusable_blocks( $block );
31✔
151
                $block = self::populate_pattern_inner_blocks( $block );
31✔
152

153
                // @todo evaluate populate_navigation_blocks().
154
                // Prepare innerBlocks.
155
                if ( ! empty( $block['innerBlocks'] ) ) {
31✔
156
                        $block['innerBlocks'] = self::handle_do_blocks( $block['innerBlocks'] );
31✔
157
                }
158

159
                return $block;
31✔
160
        }
161

162
        /**
163
         * Checks whether a block is really empty, and not just a `core/freeform`.
164
         *
165
         * @param array<string,mixed> $block The block to check.
166
         */
167
        private static function is_block_empty( array $block ): bool {
31✔
168
                // If we have a blockName, no need to check further.
169
                if ( ! empty( $block['blockName'] ) ) {
31✔
170
                        return false;
31✔
171
                }
172

173
                // If there is no innerHTML and no innerContent, we can consider it empty.
174
                if ( empty( $block['innerHTML'] ) && empty( $block['innerContent'] ) ) {
31✔
175
                        return true;
×
176
                }
177

178
                if ( ! array_key_exists( 'blockName', $block ) ) {
31✔
179
                        return true;
×
180
                }
181

182
                // Strip empty comments and spaces to see if `innerHTML` is truly empty.
183
                $stripped = preg_replace( '/<!--(.*)-->|[\s\n\r]+/Uis', '', $block['innerHTML'] );
31✔
184

185
                return empty( trim( $stripped ?? '' ) );
31✔
186
        }
187

188
        /**
189
         * Populates the innerBlocks of a template part block with the blocks from the template part.
190
         *
191
         * @param array<string,mixed> $block The block to populate.
192
         *
193
         * @return array<string,mixed> The populated block.
194
         */
195
        private static function populate_template_part_inner_blocks( array $block ): array {
31✔
196
                // Bail if not WP 5.8 or later.
197
                if ( ! function_exists( 'get_block_templates' ) ) {
31✔
198
                        return $block;
×
199
                }
200

201
                if ( 'core/template-part' !== $block['blockName'] || ! isset( $block['attrs']['slug'] ) ) {
31✔
202
                        return $block;
31✔
203
                }
204

205
                $matching_templates = get_block_templates( [ 'slug__in' => [ $block['attrs']['slug'] ] ], 'wp_template_part' );
31✔
206

207
                $template_blocks = ! empty( $matching_templates[0]->content ) ? self::parse_blocks( $matching_templates[0]->content ) : null;
31✔
208

209
                if ( empty( $template_blocks ) ) {
31✔
210
                        return $block;
×
211
                }
212

213
                $block['innerBlocks'] = $template_blocks;
31✔
214

215
                return $block;
31✔
216
        }
217

218
        /**
219
         * Populates the innerBlocks of a core/post-content block with the blocks from the post content.
220
         *
221
         * @param array<string,mixed> $block The block to populate.
222
         *
223
         * @return array<string,mixed> The populated block.
224
         */
225
        private static function populate_post_content_inner_blocks( array $block ): array {
31✔
226
                if ( 'core/post-content' !== $block['blockName'] ) {
31✔
227
                        return $block;
31✔
228
                }
229

230
                $post = get_post();
17✔
231

232
                if ( ! $post ) {
17✔
233
                        return $block;
×
234
                }
235

236
                $parsed_blocks = ! empty( $post->post_content ) ? self::parse_blocks( $post->post_content ) : null;
17✔
237

238
                if ( empty( $parsed_blocks ) ) {
17✔
239
                        return $block;
×
240
                }
241

242
                $block['innerBlocks'] = $parsed_blocks;
17✔
243

244
                return $block;
17✔
245
        }
246

247
        /**
248
         * Populates reusable blocks with the blocks from the reusable ref ID.
249
         *
250
         * @param array<string,mixed> $block The block to populate.
251
         *
252
         * @return array<string,mixed> The populated block.
253
         */
254
        private static function populate_reusable_blocks( array $block ): array {
31✔
255
                if ( 'core/block' !== $block['blockName'] || ! isset( $block['attrs']['ref'] ) ) {
31✔
256
                        return $block;
31✔
257
                }
258

259
                $reusable_block = get_post( $block['attrs']['ref'] );
×
260

261
                if ( ! $reusable_block ) {
×
262
                        return $block;
×
263
                }
264

265
                $parsed_blocks = ! empty( $reusable_block->post_content ) ? self::parse_blocks( $reusable_block->post_content ) : null;
×
266

267
                if ( empty( $parsed_blocks ) ) {
×
268
                        return $block;
×
269
                }
270

271
                return array_merge( ...$parsed_blocks );
×
272
        }
273

274
        /**
275
         * Populates the pattern innerBlocks with the blocks from the pattern.
276
         *
277
         * @param array<string,mixed> $block The block to populate.
278
         * @return array<string,mixed> The populated block.
279
         */
280
        private static function populate_pattern_inner_blocks( array $block ): array {
31✔
281
                if ( 'core/pattern' !== $block['blockName'] || ! isset( $block['attrs']['slug'] ) ) {
31✔
282
                        return $block;
31✔
283
                }
284

285
                $resolved_patterns = resolve_pattern_blocks( [ $block ] );
31✔
286

287
                if ( empty( $resolved_patterns ) ) {
31✔
288
                        return $block;
×
289
                }
290

291
                $block['innerBlocks'] = $resolved_patterns;
31✔
292
                return $block;
31✔
293
        }
294

295
        /**
296
         * Flattens a list blocks into a single array
297
         *
298
         * @param array<string,mixed> $blocks A list of blocks to flatten.
299
         *
300
         * @return array<string,mixed> The flattened list of blocks.
301
         */
302
        public static function flatten_block_list( $blocks ): array {
1✔
303
                $result = [];
1✔
304
                foreach ( $blocks as $block ) {
1✔
305
                        $result = array_merge( $result, self::flatten_inner_blocks( $block ) );
1✔
306
                }
307
                return $result;
1✔
308
        }
309

310
        /**
311
         * Flattens a block and its inner blocks into a single while attaching unique clientId's
312
         *
313
         * @param array<string,mixed> $block A parsed block.
314
         *
315
         * @return array<string,mixed> The flattened block.
316
         */
317
        private static function flatten_inner_blocks( $block ): array {
1✔
318
                $result = [];
1✔
319

320
                // Assign a unique clientId to the block if it doesn't already have one.
321
                $block['clientId'] = isset( $block['clientId'] ) ? $block['clientId'] : uniqid();
1✔
322
                array_push( $result, $block );
1✔
323

324
                foreach ( $block['innerBlocks'] as $child ) {
1✔
325
                        $child['parentClientId'] = $block['clientId'];
1✔
326

327
                        // Flatten the child, and merge with the result.
328
                        $result = array_merge( $result, self::flatten_inner_blocks( $child ) );
1✔
329
                }
330

331
                /** @var array<string,mixed> $result */
332
                return $result;
1✔
333
        }
334

335
        /**
336
         * Filters out disallowed blocks from the list of blocks
337
         *
338
         * @param array<string,mixed> $blocks A list of blocks to filter.
339
         * @param string[]            $allowed_block_names The list of allowed block names to filter.
340
         *
341
         * @return array<string,mixed> The filtered list of blocks.
342
         */
343
        private static function filter_allowed_blocks( array $blocks, array $allowed_block_names ): array {
31✔
344
                if ( empty( $allowed_block_names ) ) {
31✔
345
                        return $blocks;
31✔
346
                }
347

348
                return array_filter(
×
349
                        $blocks,
×
350
                        static function ( $block ) use ( $allowed_block_names ) {
×
351
                                return in_array( $block['blockName'], $allowed_block_names, true );
×
352
                        }
×
353
                );
×
354
        }
355
}
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