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

wp-graphql / wp-graphql / 13316763745

13 Feb 2025 08:45PM UTC coverage: 82.712% (-0.3%) from 83.023%
13316763745

push

github

web-flow
Merge pull request #3307 from wp-graphql/release/v2.0.0

release: v2.0.0

195 of 270 new or added lines in 20 files covered. (72.22%)

180 existing lines in 42 files now uncovered.

13836 of 16728 relevant lines covered (82.71%)

299.8 hits per line

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

95.83
/src/Registry/TypeRegistry.php
1
<?php
2

3
namespace WPGraphQL\Registry;
4

5
use GraphQL\Error\Error;
6
use GraphQL\Type\Definition\Type;
7
use WPGraphQL;
8
use WPGraphQL\Data\DataSource;
9
use WPGraphQL\Mutation\CommentCreate;
10
use WPGraphQL\Mutation\CommentDelete;
11
use WPGraphQL\Mutation\CommentRestore;
12
use WPGraphQL\Mutation\CommentUpdate;
13
use WPGraphQL\Mutation\MediaItemCreate;
14
use WPGraphQL\Mutation\MediaItemDelete;
15
use WPGraphQL\Mutation\MediaItemUpdate;
16
use WPGraphQL\Mutation\PostObjectCreate;
17
use WPGraphQL\Mutation\PostObjectDelete;
18
use WPGraphQL\Mutation\PostObjectUpdate;
19
use WPGraphQL\Mutation\ResetUserPassword;
20
use WPGraphQL\Mutation\SendPasswordResetEmail;
21
use WPGraphQL\Mutation\TermObjectCreate;
22
use WPGraphQL\Mutation\TermObjectDelete;
23
use WPGraphQL\Mutation\TermObjectUpdate;
24
use WPGraphQL\Mutation\UpdateSettings;
25
use WPGraphQL\Mutation\UserCreate;
26
use WPGraphQL\Mutation\UserDelete;
27
use WPGraphQL\Mutation\UserRegister;
28
use WPGraphQL\Mutation\UserUpdate;
29
use WPGraphQL\Registry\Utils\PostObject;
30
use WPGraphQL\Registry\Utils\TermObject;
31
use WPGraphQL\Type\Connection\Comments;
32
use WPGraphQL\Type\Connection\MenuItems;
33
use WPGraphQL\Type\Connection\PostObjects;
34
use WPGraphQL\Type\Connection\Taxonomies;
35
use WPGraphQL\Type\Connection\TermObjects;
36
use WPGraphQL\Type\Connection\Users;
37
use WPGraphQL\Type\Enum\AvatarRatingEnum;
38
use WPGraphQL\Type\Enum\CommentNodeIdTypeEnum;
39
use WPGraphQL\Type\Enum\CommentStatusEnum;
40
use WPGraphQL\Type\Enum\CommentsConnectionOrderbyEnum;
41
use WPGraphQL\Type\Enum\ContentNodeIdTypeEnum;
42
use WPGraphQL\Type\Enum\ContentTypeEnum;
43
use WPGraphQL\Type\Enum\ContentTypeIdTypeEnum;
44
use WPGraphQL\Type\Enum\MediaItemSizeEnum;
45
use WPGraphQL\Type\Enum\MediaItemStatusEnum;
46
use WPGraphQL\Type\Enum\MenuItemNodeIdTypeEnum;
47
use WPGraphQL\Type\Enum\MenuLocationEnum;
48
use WPGraphQL\Type\Enum\MenuNodeIdTypeEnum;
49
use WPGraphQL\Type\Enum\MimeTypeEnum;
50
use WPGraphQL\Type\Enum\OrderEnum;
51
use WPGraphQL\Type\Enum\PluginStatusEnum;
52
use WPGraphQL\Type\Enum\PostObjectFieldFormatEnum;
53
use WPGraphQL\Type\Enum\PostObjectsConnectionDateColumnEnum;
54
use WPGraphQL\Type\Enum\PostObjectsConnectionOrderbyEnum;
55
use WPGraphQL\Type\Enum\PostStatusEnum;
56
use WPGraphQL\Type\Enum\RelationEnum;
57
use WPGraphQL\Type\Enum\ScriptLoadingGroupLocationEnum;
58
use WPGraphQL\Type\Enum\ScriptLoadingStrategyEnum;
59
use WPGraphQL\Type\Enum\TaxonomyEnum;
60
use WPGraphQL\Type\Enum\TaxonomyIdTypeEnum;
61
use WPGraphQL\Type\Enum\TermNodeIdTypeEnum;
62
use WPGraphQL\Type\Enum\TermObjectsConnectionOrderbyEnum;
63
use WPGraphQL\Type\Enum\TimezoneEnum;
64
use WPGraphQL\Type\Enum\UserNodeIdTypeEnum;
65
use WPGraphQL\Type\Enum\UserRoleEnum;
66
use WPGraphQL\Type\Enum\UsersConnectionOrderbyEnum;
67
use WPGraphQL\Type\Enum\UsersConnectionSearchColumnEnum;
68
use WPGraphQL\Type\Input\DateInput;
69
use WPGraphQL\Type\Input\DateQueryInput;
70
use WPGraphQL\Type\Input\PostObjectsConnectionOrderbyInput;
71
use WPGraphQL\Type\Input\UsersConnectionOrderbyInput;
72
use WPGraphQL\Type\InterfaceType\Commenter;
73
use WPGraphQL\Type\InterfaceType\Connection;
74
use WPGraphQL\Type\InterfaceType\ContentNode;
75
use WPGraphQL\Type\InterfaceType\ContentTemplate;
76
use WPGraphQL\Type\InterfaceType\DatabaseIdentifier;
77
use WPGraphQL\Type\InterfaceType\Edge;
78
use WPGraphQL\Type\InterfaceType\EnqueuedAsset;
79
use WPGraphQL\Type\InterfaceType\HierarchicalContentNode;
80
use WPGraphQL\Type\InterfaceType\HierarchicalNode;
81
use WPGraphQL\Type\InterfaceType\HierarchicalTermNode;
82
use WPGraphQL\Type\InterfaceType\MenuItemLinkable;
83
use WPGraphQL\Type\InterfaceType\Node;
84
use WPGraphQL\Type\InterfaceType\NodeWithAuthor;
85
use WPGraphQL\Type\InterfaceType\NodeWithComments;
86
use WPGraphQL\Type\InterfaceType\NodeWithContentEditor;
87
use WPGraphQL\Type\InterfaceType\NodeWithExcerpt;
88
use WPGraphQL\Type\InterfaceType\NodeWithFeaturedImage;
89
use WPGraphQL\Type\InterfaceType\NodeWithPageAttributes;
90
use WPGraphQL\Type\InterfaceType\NodeWithRevisions;
91
use WPGraphQL\Type\InterfaceType\NodeWithTemplate;
92
use WPGraphQL\Type\InterfaceType\NodeWithTitle;
93
use WPGraphQL\Type\InterfaceType\NodeWithTrackbacks;
94
use WPGraphQL\Type\InterfaceType\OneToOneConnection;
95
use WPGraphQL\Type\InterfaceType\PageInfo;
96
use WPGraphQL\Type\InterfaceType\Previewable;
97
use WPGraphQL\Type\InterfaceType\TermNode;
98
use WPGraphQL\Type\InterfaceType\UniformResourceIdentifiable;
99
use WPGraphQL\Type\ObjectType\Avatar;
100
use WPGraphQL\Type\ObjectType\Comment;
101
use WPGraphQL\Type\ObjectType\CommentAuthor;
102
use WPGraphQL\Type\ObjectType\ContentType;
103
use WPGraphQL\Type\ObjectType\EnqueuedScript;
104
use WPGraphQL\Type\ObjectType\EnqueuedStylesheet;
105
use WPGraphQL\Type\ObjectType\MediaDetails;
106
use WPGraphQL\Type\ObjectType\MediaItemMeta;
107
use WPGraphQL\Type\ObjectType\MediaSize;
108
use WPGraphQL\Type\ObjectType\Menu;
109
use WPGraphQL\Type\ObjectType\MenuItem;
110
use WPGraphQL\Type\ObjectType\Plugin;
111
use WPGraphQL\Type\ObjectType\PostTypeLabelDetails;
112
use WPGraphQL\Type\ObjectType\RootMutation;
113
use WPGraphQL\Type\ObjectType\RootQuery;
114
use WPGraphQL\Type\ObjectType\SettingGroup;
115
use WPGraphQL\Type\ObjectType\Settings;
116
use WPGraphQL\Type\ObjectType\Taxonomy;
117
use WPGraphQL\Type\ObjectType\Theme;
118
use WPGraphQL\Type\ObjectType\User;
119
use WPGraphQL\Type\ObjectType\UserRole;
120
use WPGraphQL\Type\Union\MenuItemObjectUnion;
121
use WPGraphQL\Type\Union\PostObjectUnion;
122
use WPGraphQL\Type\Union\TermObjectUnion;
123
use WPGraphQL\Type\WPConnectionType;
124
use WPGraphQL\Type\WPEnumType;
125
use WPGraphQL\Type\WPInputObjectType;
126
use WPGraphQL\Type\WPInterfaceType;
127
use WPGraphQL\Type\WPMutationType;
128
use WPGraphQL\Type\WPObjectType;
129
use WPGraphQL\Type\WPScalar;
130
use WPGraphQL\Type\WPUnionType;
131
use WPGraphQL\Utils\Utils;
132

133
/**
134
 * Class TypeRegistry
135
 *
136
 * This class maintains the registry of Types used in the GraphQL Schema
137
 *
138
 * @phpstan-import-type InputObjectConfig from \GraphQL\Type\Definition\InputObjectType
139
 * @phpstan-import-type InterfaceConfig from \GraphQL\Type\Definition\InterfaceType
140
 * @phpstan-import-type ObjectConfig from \GraphQL\Type\Definition\ObjectType
141
 *
142
 * @package WPGraphQL\Registry
143
 */
144
class TypeRegistry {
145

146
        /**
147
         * The registered Types
148
         *
149
         * @var array<string,mixed>
150
         */
151
        protected $types;
152

153
        /**
154
         * The loaders needed to register types
155
         *
156
         * @var array<string,callable():(mixed|array<string,mixed>|\GraphQL\Type\Definition\Type|null)>
157
         */
158
        protected $type_loaders;
159

160
        /**
161
         * Stores a list of Types that need to be eagerly loaded instead of lazy loaded.
162
         *
163
         * Types that exist in the Schema but are only part of a Union/Interface ResolveType but not
164
         * referenced directly need to be eagerly loaded.
165
         *
166
         * @var array<string,string>
167
         */
168
        protected $eager_type_map;
169

170
        /**
171
         * Stores a list of Types that should be excluded from the schema.
172
         *
173
         * Type names are filtered by `graphql_excluded_types` and normalized using strtolower(), to avoid case sensitivity issues.
174
         *
175
         * @var string[]
176
         */
177
        protected $excluded_types = null;
178

179
        /**
180
         * Stores a list of mutation names that should be excluded from the schema, along with their generated input and payload types.
181
         *
182
         * Type names are filtered by `graphql_excluded_mutations` and normalized using strtolower(), to avoid case sensitivity issues.
183
         *
184
         * @var string[]
185
         */
186
        protected $excluded_mutations = null;
187

188
        /**
189
         * Stores a list of connection Type names that should be excluded from the schema, along with their generated types.
190
         *
191
         * Type names are filtered by `graphql_excluded_connections` and normalized using strtolower(), to avoid case sensitivity issues.
192
         *
193
         * Type name
194
         *
195
         * @var string[]
196
         */
197
        protected $excluded_connections = null;
198

199
        /**
200
         * TypeRegistry constructor.
201
         */
202
        public function __construct() {
595✔
203
                $this->types          = [];
595✔
204
                $this->type_loaders   = [];
595✔
205
                $this->eager_type_map = [];
595✔
206
        }
207

208
        /**
209
         * Formats the array key to a more friendly format
210
         *
211
         * @param string $key Name of the array key to format
212
         *
213
         * @return string
214
         */
215
        protected function format_key( string $key ) {
631✔
216
                return strtolower( $key );
631✔
217
        }
218

219
        /**
220
         * Returns the eager type map, an array of Type definitions for Types that
221
         * are not directly referenced in the schema.
222
         *
223
         * Types can add "eagerlyLoadType => true" when being registered to be included
224
         * in the eager_type_map.
225
         *
226
         * @return array<string,mixed>
227
         */
228
        protected function get_eager_type_map() {
593✔
229
                if ( ! empty( $this->eager_type_map ) ) {
593✔
230
                        return array_map(
69✔
231
                                function ( $type_name ) {
69✔
232
                                        return $this->get_type( $type_name );
69✔
233
                                },
69✔
234
                                $this->eager_type_map
69✔
235
                        );
69✔
236
                }
237

238
                return [];
524✔
239
        }
240

241
        /**
242
         * Initialize the TypeRegistry
243
         *
244
         * @throws \Exception
245
         *
246
         * @return void
247
         */
248
        public function init() {
593✔
249
                $this->register_type( 'Bool', Type::boolean() );
593✔
250
                $this->register_type( 'Boolean', Type::boolean() );
593✔
251
                $this->register_type( 'Float', Type::float() );
593✔
252
                $this->register_type( 'Number', Type::float() );
593✔
253
                $this->register_type( 'Id', Type::id() );
593✔
254
                $this->register_type( 'Int', Type::int() );
593✔
255
                $this->register_type( 'Integer', Type::int() );
593✔
256
                $this->register_type( 'String', Type::string() );
593✔
257

258
                /**
259
                 * When the Type Registry is initialized execute these files
260
                 */
261
                add_action( 'init_graphql_type_registry', [ $this, 'init_type_registry' ], 5, 1 );
593✔
262

263
                /**
264
                 * Fire an action as the Type registry is being initiated
265
                 *
266
                 * @param \WPGraphQL\Registry\TypeRegistry $registry Instance of the TypeRegistry
267
                 */
268
                do_action( 'init_graphql_type_registry', $this );
593✔
269
        }
270

271
        /**
272
         * Initialize the Type Registry
273
         *
274
         * @param \WPGraphQL\Registry\TypeRegistry $type_registry
275
         *
276
         * @return void
277
         * @throws \Exception
278
         */
279
        public function init_type_registry( self $type_registry ) {
593✔
280

281
                /**
282
                 * Fire an action as the type registry is initialized. This executes
283
                 * before the `graphql_register_types` action to allow for earlier hooking
284
                 *
285
                 * @param \WPGraphQL\Registry\TypeRegistry $registry Instance of the TypeRegistry
286
                 */
287
                do_action( 'graphql_register_initial_types', $type_registry );
593✔
288

289
                // Register Interfaces.
290
                Node::register_type();
593✔
291
                Commenter::register_type( $type_registry );
593✔
292
                Connection::register_type( $type_registry );
593✔
293
                ContentNode::register_type( $type_registry );
593✔
294
                ContentTemplate::register_type();
593✔
295
                DatabaseIdentifier::register_type();
593✔
296
                Edge::register_type( $type_registry );
593✔
297
                EnqueuedAsset::register_type( $type_registry );
593✔
298
                HierarchicalContentNode::register_type( $type_registry );
593✔
299
                HierarchicalNode::register_type( $type_registry );
593✔
300
                HierarchicalTermNode::register_type( $type_registry );
593✔
301
                MenuItemLinkable::register_type( $type_registry );
593✔
302
                NodeWithAuthor::register_type( $type_registry );
593✔
303
                NodeWithComments::register_type( $type_registry );
593✔
304
                NodeWithContentEditor::register_type( $type_registry );
593✔
305
                NodeWithExcerpt::register_type( $type_registry );
593✔
306
                NodeWithFeaturedImage::register_type( $type_registry );
593✔
307
                NodeWithRevisions::register_type( $type_registry );
593✔
308
                NodeWithTitle::register_type( $type_registry );
593✔
309
                NodeWithTemplate::register_type( $type_registry );
593✔
310
                NodeWithTrackbacks::register_type( $type_registry );
593✔
311
                NodeWithPageAttributes::register_type( $type_registry );
593✔
312
                PageInfo::register_type( $type_registry );
593✔
313
                Previewable::register_type( $type_registry );
593✔
314
                OneToOneConnection::register_type( $type_registry );
593✔
315
                TermNode::register_type( $type_registry );
593✔
316
                UniformResourceIdentifiable::register_type( $type_registry );
593✔
317

318
                // register types
319
                RootQuery::register_type();
593✔
320
                RootQuery::register_post_object_fields();
593✔
321
                RootQuery::register_term_object_fields();
593✔
322
                RootMutation::register_type();
593✔
323
                Avatar::register_type();
593✔
324
                Comment::register_type();
593✔
325
                CommentAuthor::register_type();
593✔
326
                ContentTemplate::register_content_template_types();
593✔
327
                EnqueuedStylesheet::register_type();
593✔
328
                EnqueuedScript::register_type();
593✔
329
                MediaDetails::register_type();
593✔
330
                MediaItemMeta::register_type();
593✔
331
                MediaSize::register_type();
593✔
332
                Menu::register_type();
593✔
333
                MenuItem::register_type();
593✔
334
                Plugin::register_type();
593✔
335
                ContentType::register_type();
593✔
336
                PostTypeLabelDetails::register_type();
593✔
337
                Settings::register_type( $this );
593✔
338
                Taxonomy::register_type();
593✔
339
                Theme::register_type();
593✔
340
                User::register_type();
593✔
341
                UserRole::register_type();
593✔
342

343
                AvatarRatingEnum::register_type();
593✔
344
                CommentNodeIdTypeEnum::register_type();
593✔
345
                CommentsConnectionOrderbyEnum::register_type();
593✔
346
                CommentStatusEnum::register_type();
593✔
347
                ContentNodeIdTypeEnum::register_type();
593✔
348
                ContentTypeEnum::register_type();
593✔
349
                ContentTypeIdTypeEnum::register_type();
593✔
350
                MediaItemSizeEnum::register_type();
593✔
351
                MediaItemStatusEnum::register_type();
593✔
352
                MenuLocationEnum::register_type();
593✔
353
                MenuItemNodeIdTypeEnum::register_type();
593✔
354
                MenuNodeIdTypeEnum::register_type();
593✔
355
                MimeTypeEnum::register_type();
593✔
356
                OrderEnum::register_type();
593✔
357
                PluginStatusEnum::register_type();
593✔
358
                PostObjectFieldFormatEnum::register_type();
593✔
359
                PostObjectsConnectionDateColumnEnum::register_type();
593✔
360
                PostObjectsConnectionOrderbyEnum::register_type();
593✔
361
                PostStatusEnum::register_type();
593✔
362
                RelationEnum::register_type();
593✔
363
                ScriptLoadingStrategyEnum::register_type();
593✔
364
                ScriptLoadingGroupLocationEnum::register_type();
593✔
365
                TaxonomyEnum::register_type();
593✔
366
                TaxonomyIdTypeEnum::register_type();
593✔
367
                TermNodeIdTypeEnum::register_type();
593✔
368
                TermObjectsConnectionOrderbyEnum::register_type();
593✔
369
                TimezoneEnum::register_type();
593✔
370
                UserNodeIdTypeEnum::register_type();
593✔
371
                UserRoleEnum::register_type();
593✔
372
                UsersConnectionOrderbyEnum::register_type();
593✔
373
                UsersConnectionSearchColumnEnum::register_type();
593✔
374

375
                DateInput::register_type();
593✔
376
                DateQueryInput::register_type();
593✔
377
                PostObjectsConnectionOrderbyInput::register_type();
593✔
378
                UsersConnectionOrderbyInput::register_type();
593✔
379

380
                // Deprecated types.
381
                MenuItemObjectUnion::register_type( $this ); /* @phpstan-ignore staticMethod.deprecatedClass */
593✔
382
                PostObjectUnion::register_type( $this ); /* @phpstan-ignore staticMethod.deprecatedClass */
593✔
383
                TermObjectUnion::register_type( $this ); /* @phpstan-ignore staticMethod.deprecatedClass */
593✔
384

385
                /**
386
                 * Register core connections
387
                 */
388
                Comments::register_connections();
593✔
389
                MenuItems::register_connections();
593✔
390
                PostObjects::register_connections();
593✔
391
                Taxonomies::register_connections();
593✔
392
                TermObjects::register_connections();
593✔
393
                Users::register_connections();
593✔
394

395
                /**
396
                 * Register core mutations
397
                 */
398
                CommentCreate::register_mutation();
593✔
399
                CommentDelete::register_mutation();
593✔
400
                CommentRestore::register_mutation();
593✔
401
                CommentUpdate::register_mutation();
593✔
402
                MediaItemCreate::register_mutation();
593✔
403
                MediaItemDelete::register_mutation();
593✔
404
                MediaItemUpdate::register_mutation();
593✔
405
                ResetUserPassword::register_mutation();
593✔
406
                SendPasswordResetEmail::register_mutation();
593✔
407
                UserCreate::register_mutation();
593✔
408
                UserDelete::register_mutation();
593✔
409
                UserUpdate::register_mutation();
593✔
410
                UserRegister::register_mutation();
593✔
411
                UpdateSettings::register_mutation( $this );
593✔
412

413
                /**
414
                 * Register PostObject types based on post_types configured to show_in_graphql
415
                 *
416
                 * @var \WP_Post_Type[] $allowed_post_types
417
                 */
418
                $allowed_post_types = WPGraphQL::get_allowed_post_types( 'objects' );
593✔
419

420
                /** @var \WP_Taxonomy[] $allowed_taxonomies */
421
                $allowed_taxonomies = WPGraphQL::get_allowed_taxonomies( 'objects' );
593✔
422

423
                foreach ( $allowed_post_types as $post_type_object ) {
593✔
424
                        PostObject::register_types( $post_type_object );
593✔
425

426
                        /**
427
                         * Mutations for attachments are handled differently
428
                         * because they require different inputs
429
                         */
430
                        if ( 'attachment' !== $post_type_object->name ) {
593✔
431

432
                                /**
433
                                 * Revisions are created behind the scenes as a side effect of post updates,
434
                                 * they aren't created manually.
435
                                 */
436
                                if ( 'revision' !== $post_type_object->name ) {
593✔
437
                                        if ( empty( $post_type_object->graphql_exclude_mutations ) || ! in_array( 'create', $post_type_object->graphql_exclude_mutations, true ) ) {
593✔
438
                                                PostObjectCreate::register_mutation( $post_type_object );
593✔
439
                                        }
440

441
                                        if ( empty( $post_type_object->graphql_exclude_mutations ) || ! in_array( 'update', $post_type_object->graphql_exclude_mutations, true ) ) {
593✔
442
                                                PostObjectUpdate::register_mutation( $post_type_object );
593✔
443
                                        }
444
                                }
445

446
                                if ( empty( $post_type_object->graphql_exclude_mutations ) || ! in_array( 'delete', $post_type_object->graphql_exclude_mutations, true ) ) {
593✔
447
                                        PostObjectDelete::register_mutation( $post_type_object );
593✔
448
                                }
449
                        }
450

451
                        foreach ( $allowed_taxonomies as $tax_object ) {
593✔
452
                                // If the taxonomy is in the array of taxonomies registered to the post_type
453
                                if ( in_array( $tax_object->name, get_object_taxonomies( $post_type_object->name ), true ) ) {
593✔
454
                                        register_graphql_input_type(
593✔
455
                                                $post_type_object->graphql_single_name . ucfirst( $tax_object->graphql_plural_name ) . 'NodeInput',
593✔
456
                                                [
593✔
457
                                                        'description' => sprintf(
593✔
458
                                                                // translators: %1$s is the GraphQL plural name of the taxonomy, %2$s is the GraphQL singular name of the post type.
459
                                                                __( 'List of %1$s to connect the %2$s to. If an ID is set, it will be used to create the connection. If not, it will look for a slug. If neither are valid existing terms, and the site is configured to allow terms to be created during post mutations, a term will be created using the Name if it exists in the input, then fallback to the slug if it exists.', 'wp-graphql' ),
593✔
460
                                                                $tax_object->graphql_plural_name,
593✔
461
                                                                $post_type_object->graphql_single_name
593✔
462
                                                        ),
593✔
463
                                                        'fields'      => [
593✔
464
                                                                'id'          => [
593✔
465
                                                                        'type'        => 'Id',
593✔
466
                                                                        'description' => sprintf(
593✔
467
                                                                                // translators: %1$s is the GraphQL name of the taxonomy, %2$s is the GraphQL name of the post type.
468
                                                                                __( 'The ID of the %1$s. If present, this will be used to connect to the %2$s. If no existing %1$s exists with this ID, no connection will be made.', 'wp-graphql' ),
593✔
469
                                                                                $tax_object->graphql_single_name,
593✔
470
                                                                                $post_type_object->graphql_single_name
593✔
471
                                                                        ),
593✔
472
                                                                ],
593✔
473
                                                                'slug'        => [
593✔
474
                                                                        'type'        => 'String',
593✔
475
                                                                        'description' => sprintf(
593✔
476
                                                                                // translators: %1$s is the GraphQL name of the taxonomy.
477
                                                                                __( 'The slug of the %1$s. If no ID is present, this field will be used to make a connection. If no existing term exists with this slug, this field will be used as a fallback to the Name field when creating a new term to connect to, if term creation is enabled as a nested mutation.', 'wp-graphql' ),
593✔
478
                                                                                $tax_object->graphql_single_name
593✔
479
                                                                        ),
593✔
480
                                                                ],
593✔
481
                                                                'description' => [
593✔
482
                                                                        'type'        => 'String',
593✔
483
                                                                        'description' => sprintf(
593✔
484
                                                                                // translators: %1$s is the GraphQL name of the taxonomy.
485
                                                                                __( 'The description of the %1$s. This field is used to set a description of the %1$s if a new one is created during the mutation.', 'wp-graphql' ),
593✔
486
                                                                                $tax_object->graphql_single_name
593✔
487
                                                                        ),
593✔
488
                                                                ],
593✔
489
                                                                'name'        => [
593✔
490
                                                                        'type'        => 'String',
593✔
491
                                                                        'description' => sprintf(
593✔
492
                                                                                        // translators: %1$s is the GraphQL name of the taxonomy.
493
                                                                                __( 'The name of the %1$s. This field is used to create a new term, if term creation is enabled in nested mutations, and if one does not already exist with the provided slug or ID or if a slug or ID is not provided. If no name is included and a term is created, the creation will fallback to the slug field.', 'wp-graphql' ),
593✔
494
                                                                                $tax_object->graphql_single_name
593✔
495
                                                                        ),
593✔
496
                                                                ],
593✔
497
                                                        ],
593✔
498
                                                ]
593✔
499
                                        );
593✔
500

501
                                        register_graphql_input_type(
593✔
502
                                                ucfirst( $post_type_object->graphql_single_name ) . ucfirst( $tax_object->graphql_plural_name ) . 'Input',
593✔
503
                                                [
593✔
504
                                                        'description' => sprintf(
593✔
505
                                                                // translators: %1$s is the GraphQL name of the post type, %2$s is the plural GraphQL name of the taxonomy.
506
                                                                __( 'Set relationships between the %1$s to %2$s', 'wp-graphql' ),
593✔
507
                                                                $post_type_object->graphql_single_name,
593✔
508
                                                                $tax_object->graphql_plural_name
593✔
509
                                                        ),
593✔
510
                                                        'fields'      => [
593✔
511
                                                                'append' => [
593✔
512
                                                                        'type'        => 'Boolean',
593✔
513
                                                                        'description' => sprintf(
593✔
514
                                                                                // translators: %1$s is the GraphQL name of the taxonomy, %2$s is the plural GraphQL name of the taxonomy.
515
                                                                                __( 'If true, this will append the %1$s to existing related %2$s. If false, this will replace existing relationships. Default true.', 'wp-graphql' ),
593✔
516
                                                                                $tax_object->graphql_single_name,
593✔
517
                                                                                $tax_object->graphql_plural_name
593✔
518
                                                                        ),
593✔
519
                                                                ],
593✔
520
                                                                'nodes'  => [
593✔
521
                                                                        'type'        => [
593✔
522
                                                                                'list_of' => $post_type_object->graphql_single_name . ucfirst( $tax_object->graphql_plural_name ) . 'NodeInput',
593✔
523
                                                                        ],
593✔
524
                                                                        'description' => __( 'The input list of items to set.', 'wp-graphql' ),
593✔
525
                                                                ],
593✔
526
                                                        ],
593✔
527
                                                ]
593✔
528
                                        );
593✔
529
                                }
530
                        }
531
                }
532

533
                /**
534
                 * Register TermObject types based on taxonomies configured to show_in_graphql
535
                 */
536
                foreach ( $allowed_taxonomies as $tax_object ) {
593✔
537
                        TermObject::register_types( $tax_object );
593✔
538

539
                        if ( empty( $tax_object->graphql_exclude_mutations ) || ! in_array( 'create', $tax_object->graphql_exclude_mutations, true ) ) {
593✔
540
                                TermObjectCreate::register_mutation( $tax_object );
593✔
541
                        }
542

543
                        if ( empty( $tax_object->graphql_exclude_mutations ) || ! in_array( 'update', $tax_object->graphql_exclude_mutations, true ) ) {
593✔
544
                                TermObjectUpdate::register_mutation( $tax_object );
593✔
545
                        }
546

547
                        if ( empty( $tax_object->graphql_exclude_mutations ) || ! in_array( 'delete', $tax_object->graphql_exclude_mutations, true ) ) {
593✔
548
                                TermObjectDelete::register_mutation( $tax_object );
593✔
549
                        }
550
                }
551

552
                /**
553
                 * Create the root query fields for any setting type in
554
                 * the $allowed_setting_types array.
555
                 */
556
                $allowed_setting_types = DataSource::get_allowed_settings_by_group( $this );
593✔
557

558
                /**
559
                 * The url is not a registered setting for multisite, so this is a polyfill
560
                 * to expose the URL to the Schema for multisite sites
561
                 */
562
                if ( is_multisite() ) {
593✔
563
                        $this->register_field(
593✔
564
                                'GeneralSettings',
593✔
565
                                'url',
593✔
566
                                [
593✔
567
                                        'type'        => 'String',
593✔
568
                                        'description' => __( 'Site URL.', 'wp-graphql' ),
593✔
569
                                        'resolve'     => static function () {
593✔
570
                                                return get_site_url();
1✔
571
                                        },
593✔
572
                                ]
593✔
573
                        );
593✔
574
                }
575

576
                if ( ! empty( $allowed_setting_types ) && is_array( $allowed_setting_types ) ) {
593✔
577
                        foreach ( $allowed_setting_types as $group_name => $setting_type ) {
593✔
578
                                $group_name = DataSource::format_group_name( $group_name );
593✔
579
                                $type_name  = SettingGroup::register_settings_group( $group_name, $group_name, $this );
593✔
580

581
                                if ( ! $type_name ) {
593✔
582
                                        continue;
×
583
                                }
584

585
                                register_graphql_field(
593✔
586
                                        'RootQuery',
593✔
587
                                        Utils::format_field_name( $type_name ),
593✔
588
                                        [
593✔
589
                                                'type'        => $type_name,
593✔
590
                                                'description' => sprintf(
593✔
591
                                                        // translators: %s is the GraphQL name of the settings group.
592
                                                        __( "Fields of the '%s' settings group", 'wp-graphql' ),
593✔
593
                                                        ucfirst( $group_name ) . 'Settings'
593✔
594
                                                ),
593✔
595
                                                'resolve'     => static function () use ( $setting_type ) {
593✔
596
                                                        return $setting_type;
8✔
597
                                                },
593✔
598
                                        ]
593✔
599
                                );
593✔
600
                        }
601
                }
602

603
                /**
604
                 * Fire an action as the type registry is initialized. This executes
605
                 * before the `graphql_register_types` action to allow for earlier hooking
606
                 *
607
                 * @param \WPGraphQL\Registry\TypeRegistry $registry Instance of the TypeRegistry
608
                 */
609
                do_action( 'graphql_register_types', $type_registry );
593✔
610

611
                /**
612
                 * Fire an action as the type registry is initialized. This executes
613
                 * during the `graphql_register_types` action to allow for earlier hooking
614
                 *
615
                 * @param \WPGraphQL\Registry\TypeRegistry $registry Instance of the TypeRegistry
616
                 */
617
                do_action( 'graphql_register_types_late', $type_registry );
593✔
618
        }
619

620
        /**
621
         * Given a config for a custom Scalar, this adds the Scalar for use in the Schema.
622
         *
623
         * @param string              $type_name The name of the Type to register
624
         * @param array<string,mixed> $config    The config for the scalar type to register
625
         *
626
         * @throws \Exception
627
         *
628
         * @return void
629
         */
630
        public function register_scalar( string $type_name, array $config ) {
1✔
631
                $config['kind'] = 'scalar';
1✔
632
                $this->register_type( $type_name, $config );
1✔
633
        }
634

635
        /**
636
         * Registers connections that were passed through the Type registration config
637
         *
638
         * @param array<string,mixed> $config Type config
639
         *
640
         * @return void
641
         *
642
         * @throws \Exception
643
         */
644
        protected function register_connections_from_config( array $config ) {
593✔
645
                $connections = $config['connections'] ?? null;
593✔
646

647
                if ( ! is_array( $connections ) ) {
593✔
648
                        return;
×
649
                }
650

651
                foreach ( $connections as $field_name => $connection_config ) {
593✔
652
                        if ( ! is_array( $connection_config ) ) {
593✔
653
                                continue;
×
654
                        }
655

656
                        $connection_config['fromType']      = $config['name'];
593✔
657
                        $connection_config['fromFieldName'] = $field_name;
593✔
658
                        register_graphql_connection( $connection_config );
593✔
659
                }
660
        }
661

662
        /**
663
         * Add a Type to the Registry
664
         *
665
         * @param string                                                  $type_name The name of the type to register
666
         * @param mixed|array<string,mixed>|\GraphQL\Type\Definition\Type $config The config for the type
667
         *
668
         * @throws \Exception
669
         */
670
        public function register_type( string $type_name, $config ): void {
606✔
671
                /**
672
                 * If the type should be excluded from the schema, skip it.
673
                 */
674
                if ( in_array( strtolower( $type_name ), $this->get_excluded_types(), true ) ) {
593✔
675
                        return;
5✔
676
                }
677
                /**
678
                 * If the Type Name starts with a number, skip it.
679
                 */
680
                if ( ! is_valid_graphql_name( $type_name ) ) {
593✔
681
                        graphql_debug(
×
682
                                sprintf(
×
683
                                        // translators: %s is the name of the type.
684
                                        __( 'The Type name \'%1$s\' is invalid and has not been added to the GraphQL Schema.', 'wp-graphql' ),
×
685
                                        $type_name
×
686
                                ),
×
687
                                [
×
688
                                        'type'      => 'INVALID_TYPE_NAME',
×
689
                                        'type_name' => $type_name,
×
690
                                ]
×
691
                        );
×
692
                        return;
×
693
                }
694

695
                /**
696
                 * If the Type Name is already registered, skip it.
697
                 */
698
                if ( isset( $this->types[ $this->format_key( $type_name ) ] ) || isset( $this->type_loaders[ $this->format_key( $type_name ) ] ) ) {
593✔
699
                        graphql_debug(
2✔
700
                                sprintf(
2✔
701
                                        // translators: %s is the name of the type.
702
                                        __( 'You cannot register duplicate Types to the Schema. The Type \'%1$s\' already exists in the Schema. Make sure to give new Types a unique name.', 'wp-graphql' ),
2✔
703
                                        $type_name
2✔
704
                                ),
2✔
705
                                [
2✔
706
                                        'type'      => 'DUPLICATE_TYPE',
2✔
707
                                        'type_name' => $type_name,
2✔
708
                                ]
2✔
709
                        );
2✔
710
                        return;
2✔
711
                }
712

713
                /**
714
                 * Register any connections that were passed through the Type config
715
                 */
716
                if ( is_array( $config ) && isset( $config['connections'] ) ) {
593✔
717
                        $config['name'] = ucfirst( $type_name );
593✔
718
                        $this->register_connections_from_config( $config );
593✔
719
                }
720

721
                $this->type_loaders[ $this->format_key( $type_name ) ] = function () use ( $type_name, $config ) {
606✔
722
                        return $this->prepare_type( $type_name, $config );
606✔
723
                };
606✔
724

725
                if ( WPGraphQL::is_introspection_query() && is_array( $config ) && isset( $config['eagerlyLoadType'] ) && true === $config['eagerlyLoadType'] && ! isset( $this->eager_type_map[ $this->format_key( $type_name ) ] ) ) {
593✔
726
                        $this->eager_type_map[ $this->format_key( $type_name ) ] = $this->format_key( $type_name );
69✔
727
                }
728
        }
729

730
        /**
731
         * Add an Object Type to the Registry
732
         *
733
         * @param string              $type_name The name of the type to register
734
         * @param array<string,mixed> $config The configuration of the type
735
         *
736
         * @throws \Exception
737
         */
738
        public function register_object_type( string $type_name, array $config ): void {
593✔
739
                $config['kind'] = 'object';
593✔
740
                $this->register_type( $type_name, $config );
593✔
741
        }
742

743
        /**
744
         * Add an Interface Type to the registry
745
         *
746
         * @param string                                                  $type_name The name of the type to register
747
         * @param mixed|array<string,mixed>|\GraphQL\Type\Definition\Type $config The configuration of the type
748
         *
749
         * @throws \Exception
750
         */
751
        public function register_interface_type( string $type_name, $config ): void {
593✔
752
                $config['kind'] = 'interface';
593✔
753
                $this->register_type( $type_name, $config );
593✔
754
        }
755

756
        /**
757
         * Add an Enum Type to the registry
758
         *
759
         * @param string              $type_name The name of the type to register
760
         * @param array<string,mixed> $config he configuration of the type
761
         *
762
         * @throws \Exception
763
         */
UNCOV
764
        public function register_enum_type( string $type_name, array $config ): void {
×
765
                $config['kind'] = 'enum';
×
766
                $this->register_type( $type_name, $config );
×
767
        }
768

769
        /**
770
         * Add an Input Type to the Registry
771
         *
772
         * @param string              $type_name The name of the type to register
773
         * @param array<string,mixed> $config he configuration of the type
774
         *
775
         * @throws \Exception
776
         */
777
        public function register_input_type( string $type_name, array $config ): void {
593✔
778
                $config['kind'] = 'input';
593✔
779
                $this->register_type( $type_name, $config );
593✔
780
        }
781

782
        /**
783
         * Add a Union Type to the Registry
784
         *
785
         * @param string              $type_name The name of the type to register
786
         * @param array<string,mixed> $config he configuration of the type
787
         *
788
         * @throws \Exception
789
         */
UNCOV
790
        public function register_union_type( string $type_name, array $config ): void {
×
791
                $config['kind'] = 'union';
×
792
                $this->register_type( $type_name, $config );
×
793
        }
794

795
        /**
796
         * Prepare the type for registration.
797
         *
798
         * @param string                                                  $type_name The name of the type to prepare
799
         * @param mixed|array<string,mixed>|\GraphQL\Type\Definition\Type $config    The config for the type
800
         *
801
         * @return mixed|\GraphQL\Type\Definition\Type|null The prepared type
802
         */
803
        protected function prepare_type( string $type_name, $config ) {
606✔
804
                if ( ! is_array( $config ) ) {
606✔
805
                        return $config;
594✔
806
                }
807

808
                $prepared_type = null;
606✔
809

810
                if ( ! empty( $config ) ) {
606✔
811
                        $kind           = isset( $config['kind'] ) ? $config['kind'] : null;
606✔
812
                        $config['name'] = ucfirst( $type_name );
606✔
813

814
                        switch ( $kind ) {
815
                                case 'enum':
606✔
816
                                        $prepared_type = new WPEnumType( $config );
408✔
817
                                        break;
408✔
818
                                case 'input':
605✔
819
                                        /** @var InputObjectConfig $config */
820
                                        $prepared_type = new WPInputObjectType( $config, $this );
425✔
821
                                        break;
425✔
822
                                case 'scalar':
604✔
823
                                        $prepared_type = new WPScalar( $config, $this );
1✔
824
                                        break;
1✔
825
                                case 'union':
604✔
826
                                        $prepared_type = new WPUnionType( $config, $this );
127✔
827
                                        break;
127✔
828
                                case 'interface':
604✔
829
                                        /** @var InterfaceConfig $config */
830
                                        $prepared_type = new WPInterfaceType( $config, $this );
562✔
831
                                        break;
562✔
832
                                case 'object':
604✔
833
                                default:
834
                                        /** @var ObjectConfig $config */
835
                                        $prepared_type = new WPObjectType( $config, $this );
604✔
836
                        }
837
                }
838

839
                return $prepared_type;
606✔
840
        }
841

842
        /**
843
         * Given a type name, returns the type or null if not found
844
         *
845
         * @param string $type_name The name of the Type to get from the registry
846
         *
847
         * @return mixed|array<string,mixed>|\GraphQL\Type\Definition\Type|null
848
         */
849
        public function get_type( string $type_name ) {
631✔
850
                $key = $this->format_key( $type_name );
631✔
851

852
                if ( isset( $this->type_loaders[ $key ] ) ) {
631✔
853
                        $type                = $this->type_loaders[ $key ]();
606✔
854
                        $this->types[ $key ] = apply_filters( 'graphql_get_type', $type, $type_name );
606✔
855
                        unset( $this->type_loaders[ $key ] );
606✔
856
                }
857

858
                return $this->types[ $key ] ?? null;
631✔
859
        }
860

861
        /**
862
         * Given a type name, determines if the type is already present in the Type Loader
863
         *
864
         * @param string $type_name The name of the type to check the registry for
865
         */
866
        public function has_type( string $type_name ): bool {
593✔
867
                return isset( $this->type_loaders[ $this->format_key( $type_name ) ] );
593✔
868
        }
869

870
        /**
871
         * Return the Types in the registry
872
         *
873
         * @return array<string,mixed>
874
         */
875
        public function get_types(): array {
593✔
876

877
                // The full map of types is merged with eager types to support the
878
                // rename_graphql_type API.
879
                //
880
                // All of the types are closures, but eager Types are the full
881
                // Type definitions up front
882
                return array_merge( $this->types, $this->get_eager_type_map() );
593✔
883
        }
884

885
        /**
886
         * Wrapper for prepare_field to prepare multiple fields for registration at once
887
         *
888
         * @param array<string,mixed> $fields    Array of fields and their settings to register on a Type
889
         * @param string              $type_name Name of the Type to register the fields to
890
         *
891
         * @return array<string,mixed>
892
         * @throws \Exception
893
         */
894
        public function prepare_fields( array $fields, string $type_name ): array {
605✔
895
                $prepared_fields = [];
605✔
896
                foreach ( $fields as $field_name => $field_config ) {
605✔
897
                        if ( is_array( $field_config ) && isset( $field_config['type'] ) ) {
605✔
898
                                $prepared_field = $this->prepare_field( $field_name, $field_config, $type_name );
605✔
899
                                if ( ! empty( $prepared_field ) ) {
605✔
900
                                        $prepared_fields[ $this->format_key( $field_name ) ] = $prepared_field;
605✔
901
                                }
902
                        }
903
                }
904

905
                return $prepared_fields;
605✔
906
        }
907

908
        /**
909
         * Prepare the field to be registered on the type
910
         *
911
         * @param string              $field_name   Friendly name of the field
912
         * @param array<string,mixed> $field_config Config data about the field to prepare
913
         * @param string              $type_name    Name of the type to prepare the field for
914
         *
915
         * @return ?array<string,mixed>
916
         * @throws \Exception
917
         */
918
        protected function prepare_field( string $field_name, array $field_config, string $type_name ): ?array {
612✔
919
                if ( ! isset( $field_config['name'] ) ) {
605✔
920
                        $field_config['name'] = lcfirst( $field_name );
602✔
921
                }
922

923
                if ( ! isset( $field_config['type'] ) ) {
605✔
924
                        graphql_debug(
1✔
925
                                sprintf(
1✔
926
                                        /* translators: %s is the Field name. */
927
                                        __( 'The registered field \'%s\' does not have a Type defined. Make sure to define a type for all fields.', 'wp-graphql' ),
1✔
928
                                        $field_name
1✔
929
                                ),
1✔
930
                                [
1✔
931
                                        'type'       => 'INVALID_FIELD_TYPE',
1✔
932
                                        'type_name'  => $type_name,
1✔
933
                                        'field_name' => $field_name,
1✔
934
                                ]
1✔
935
                        );
1✔
936
                        return null;
1✔
937
                }
938

939
                /**
940
                 * If the type is a string, create a callable wrapper to get the type from
941
                 * type registry. This preserves lazy-loading and prevents a bug where a type
942
                 * has the same name as a function in the global scope (e.g., `header()`) and
943
                 * is called since it passes `is_callable`.
944
                 */
945
                if ( is_string( $field_config['type'] ) ) {
605✔
946
                        // Bail if the type is excluded from the Schema.
947
                        if ( in_array( strtolower( $field_config['type'] ), $this->get_excluded_types(), true ) ) {
604✔
948
                                return null;
4✔
949
                        }
950

951
                        $field_config['type'] = function () use ( $field_config, $type_name ) {
612✔
952
                                $type = $this->get_type( $field_config['type'] );
557✔
953
                                if ( ! $type ) {
557✔
954
                                        $message = sprintf(
1✔
955
                                        /* translators: %1$s is the Field name, %2$s is the type name the field belongs to. %3$s is the non-existent type name being referenced. */
956
                                                __( 'The field \'%1$s\' on Type \'%2$s\' is configured to return \'%3$s\' which is a non-existent Type in the Schema. Make sure to define a valid type for all fields. This might occur if there was a typo with \'%3$s\', or it needs to be registered to the Schema.', 'wp-graphql' ),
1✔
957
                                                $field_config['name'],
1✔
958
                                                $type_name,
1✔
959
                                                $field_config['type']
1✔
960
                                        );
1✔
961
                                        // We throw an error here instead of graphql_debug message, as an error would already be thrown if a type didn't exist at this point,
962
                                        // but now it will have a more helpful error message.
963
                                        throw new Error( esc_html( $message ) );
1✔
964
                                }
965
                                return $type;
557✔
966
                        };
612✔
967
                }
968

969
                /**
970
                 * If the type is an array, it contains type modifiers (e.g., "non_null").
971
                 * Create a callable wrapper to preserve lazy-loading.
972
                 */
973
                if ( is_array( $field_config['type'] ) ) {
605✔
974
                        // Bail if the type is excluded from the Schema.
975
                        $unmodified_type_name = $this->get_unmodified_type_name( $field_config['type'] );
603✔
976

977
                        if ( empty( $unmodified_type_name ) || in_array( strtolower( $unmodified_type_name ), $this->get_excluded_types(), true ) ) {
603✔
978
                                return null;
1✔
979
                        }
980

981
                        $field_config['type'] = function () use ( $field_config ) {
608✔
982
                                return $this->setup_type_modifiers( $field_config['type'] );
522✔
983
                        };
608✔
984
                }
985

986
                /**
987
                 * If the field has arguments, each one must be prepared.
988
                 */
989
                if ( isset( $field_config['args'] ) && is_array( $field_config['args'] ) ) {
605✔
990
                        foreach ( $field_config['args'] as $arg_name => $arg_config ) {
598✔
991
                                $arg = $this->prepare_field( $arg_name, $arg_config, $type_name );
598✔
992

993
                                // Remove the arg if the field could not be prepared.
994
                                if ( empty( $arg ) ) {
598✔
995
                                        unset( $field_config['args'][ $arg_name ] );
1✔
996
                                        continue;
1✔
997
                                }
998

999
                                $field_config['args'][ $arg_name ] = $arg;
598✔
1000
                        }
1001
                }
1002

1003
                /**
1004
                 * If the field has no (remaining) valid arguments, unset the key.
1005
                 */
1006
                if ( empty( $field_config['args'] ) ) {
605✔
1007
                        unset( $field_config['args'] );
605✔
1008
                }
1009

1010
                return $field_config;
605✔
1011
        }
1012

1013
        /**
1014
         * Processes type modifiers (e.g., "non-null"). Loads types immediately, so do
1015
         * not call before types are ready to be loaded.
1016
         *
1017
         * @param \GraphQL\Type\Definition\Type|string|array<string,mixed> $type The type definition to process.
1018
         *
1019
         * @return \GraphQL\Type\Definition\Type|string|array<string,mixed>
1020
         * @throws \Exception
1021
         */
1022
        public function setup_type_modifiers( $type ) {
522✔
1023
                if ( ! is_array( $type ) ) {
522✔
1024
                        return $type;
522✔
1025
                }
1026

1027
                if ( isset( $type['non_null'] ) ) {
522✔
1028
                        /** @var \GraphQL\Type\Definition\Type $inner_type */
1029
                        $inner_type = $this->setup_type_modifiers( $type['non_null'] );
520✔
1030

1031
                        return $this->non_null( $inner_type );
520✔
1032
                }
1033

1034
                if ( isset( $type['list_of'] ) ) {
395✔
1035
                        /** @var \GraphQL\Type\Definition\Type $inner_type */
1036
                        $inner_type = $this->setup_type_modifiers( $type['list_of'] );
395✔
1037
                        return $this->list_of( $inner_type );
395✔
1038
                }
1039

1040
                return $type;
×
1041
        }
1042

1043
        /**
1044
         * Wrapper for the register_field method to register multiple fields at once
1045
         *
1046
         * @param string                            $type_name Name of the type in the Type Registry to add the fields to
1047
         * @param array<string,array<string,mixed>> $fields    Fields to register
1048
         *
1049
         * @throws \Exception
1050
         */
1051
        public function register_fields( string $type_name, array $fields = [] ): void {
592✔
1052
                if ( ! empty( $fields ) ) {
592✔
1053
                        foreach ( $fields as $field_name => $config ) {
592✔
1054
                                if ( is_string( $field_name ) && ! empty( $config ) && is_array( $config ) ) {
592✔
1055
                                        $this->register_field( $type_name, $field_name, $config );
592✔
1056
                                }
1057
                        }
1058
                }
1059
        }
1060

1061
        /**
1062
         * Add a field to a Type in the Type Registry
1063
         *
1064
         * @param string              $type_name  Name of the type in the Type Registry to add the fields to
1065
         * @param string              $field_name Name of the field to add to the type
1066
         * @param array<string,mixed> $config     Info about the field to register to the type
1067
         *
1068
         * @throws \Exception
1069
         */
1070
        public function register_field( string $type_name, string $field_name, array $config ): void {
593✔
1071
                add_filter(
593✔
1072
                        'graphql_' . $type_name . '_fields',
593✔
1073
                        function ( $fields ) use ( $type_name, $field_name, $config ) {
593✔
1074

1075
                                // Whether the field should be allowed to have underscores in the field name
1076
                                $allow_field_underscores = isset( $config['allowFieldUnderscores'] ) && true === $config['allowFieldUnderscores'];
593✔
1077

1078
                                $field_name = Utils::format_field_name( $field_name, $allow_field_underscores );
593✔
1079

1080
                                if ( preg_match( '/^\d/', $field_name ) ) {
593✔
1081
                                        graphql_debug(
1✔
1082
                                                sprintf(
1✔
1083
                                                        // translators: %1$s is the field name, %2$s is the type name.
1084
                                                        __( 'The field \'%1$s\' on Type \'%2$s\' is invalid. Field names cannot start with a number.', 'wp-graphql' ),
1✔
1085
                                                        $field_name,
1✔
1086
                                                        $type_name
1✔
1087
                                                ),
1✔
1088
                                                [
1✔
1089
                                                        'type'       => 'INVALID_FIELD_NAME',
1✔
1090
                                                        'field_name' => $field_name,
1✔
1091
                                                        'type_name'  => $type_name,
1✔
1092
                                                ]
1✔
1093
                                        );
1✔
1094
                                        return $fields;
1✔
1095
                                }
1096

1097
                                // if a field has already been registered with the same name output a debug message
1098
                                if ( isset( $fields[ $field_name ] ) ) {
593✔
1099

1100
                                        // if the existing field is a connection type
1101
                                        // and the new field is also a connection type
1102
                                        // and the toType is the same for both
1103
                                        // then we can allow the duplicate field
1104
                                        if (
1105
                                                isset(
1106
                                                        $fields[ $field_name ]['isConnectionField'],
1✔
1107
                                                        $config['isConnectionField'],
1✔
1108
                                                        $fields[ $field_name ]['toType'],
1✔
1109
                                                        $config['toType'],
1✔
1110
                                                        $fields[ $field_name ]['connectionTypeName'],
1✔
1111
                                                        $config['connectionTypeName']
1✔
1112
                                                ) &&
1113
                                                $fields[ $field_name ]['toType'] === $config['toType'] &&
1✔
1114
                                                $fields[ $field_name ]['connectionTypeName'] === $config['connectionTypeName']
1✔
1115
                                        ) {
1116
                                                return $fields;
×
1117
                                        }
1118

1119
                                        graphql_debug(
1✔
1120
                                                sprintf(
1✔
1121
                                                        // translators: %1$s is the field name, %2$s is the type name.
1122
                                                        __( 'You cannot register duplicate fields on the same Type. The field \'%1$s\' already exists on the type \'%2$s\'. Make sure to give the field a unique name.', 'wp-graphql' ),
1✔
1123
                                                        $field_name,
1✔
1124
                                                        $type_name
1✔
1125
                                                ),
1✔
1126
                                                [
1✔
1127
                                                        'type'            => 'DUPLICATE_FIELD',
1✔
1128
                                                        'field_name'      => $field_name,
1✔
1129
                                                        'type_name'       => $type_name,
1✔
1130
                                                        'existing_field'  => $fields[ $field_name ],
1✔
1131
                                                        'duplicate_field' => $config,
1✔
1132
                                                ]
1✔
1133
                                        );
1✔
1134
                                        return $fields;
1✔
1135
                                }
1136

1137
                                /**
1138
                                 * If the field returns a properly prepared field, add it the the field registry
1139
                                 */
1140
                                $field = $this->prepare_field( $field_name, $config, $type_name );
593✔
1141

1142
                                if ( ! empty( $field ) ) {
593✔
1143
                                        $fields[ $field_name ] = $field;
593✔
1144
                                }
1145

1146
                                return $fields;
593✔
1147
                        },
593✔
1148
                        10,
593✔
1149
                        1
593✔
1150
                );
593✔
1151
        }
1152

1153
        /**
1154
         * Remove a field from a type
1155
         *
1156
         * @param string $type_name  Name of the Type the field is registered to
1157
         * @param string $field_name Name of the field you would like to remove from the type
1158
         *
1159
         * @return void
1160
         */
1161
        public function deregister_field( string $type_name, string $field_name ) {
1✔
1162
                add_filter(
1✔
1163
                        'graphql_' . $type_name . '_fields',
1✔
1164
                        static function ( $fields ) use ( $field_name ) {
1✔
1165
                                if ( isset( $fields[ $field_name ] ) ) {
1✔
1166
                                        unset( $fields[ $field_name ] );
1✔
1167
                                }
1168

1169
                                return $fields;
1✔
1170
                        }
1✔
1171
                );
1✔
1172
        }
1173

1174
        /**
1175
         * Method to register a new connection in the Type registry
1176
         *
1177
         * @param array<string,mixed> $config The info about the connection being registered
1178
         *
1179
         * @throws \InvalidArgumentException
1180
         * @throws \Exception
1181
         */
1182
        public function register_connection( array $config ): void {
593✔
1183
                new WPConnectionType( $config, $this );
593✔
1184
        }
1185

1186
        /**
1187
         * Handles registration of a mutation to the Type registry
1188
         *
1189
         * @param string              $mutation_name Name of the mutation being registered
1190
         * @param array<string,mixed> $config        Info about the mutation being registered
1191
         *
1192
         * @throws \Exception
1193
         */
1194
        public function register_mutation( string $mutation_name, array $config ): void {
593✔
1195
                // Bail if the mutation has been excluded from the schema.
1196
                if ( in_array( strtolower( $mutation_name ), $this->get_excluded_mutations(), true ) ) {
593✔
1197
                        return;
2✔
1198
                }
1199

1200
                $config['name'] = $mutation_name;
593✔
1201
                new WPMutationType( $config, $this );
593✔
1202
        }
1203

1204
        /**
1205
         * Removes a GraphQL mutation from the schema.
1206
         *
1207
         * This works by preventing the mutation from being registered in the first place.
1208
         *
1209
         * @uses 'graphql_excluded_mutations' filter.
1210
         *
1211
         * @param string $mutation_name Name of the mutation to remove from the schema.
1212
         *
1213
         * @since 1.14.0
1214
         */
1215
        public function deregister_mutation( string $mutation_name ): void {
2✔
1216
                // Prevent the mutation from being registered to the scheme directly.
1217
                add_filter(
2✔
1218
                        'graphql_excluded_mutations',
2✔
1219
                        static function ( $excluded_mutations ) use ( $mutation_name ): array {
2✔
1220
                                // Normalize the types to prevent case sensitivity issues.
1221
                                $mutation_name = strtolower( $mutation_name );
2✔
1222
                                // If the type isn't already excluded, add it to the array.
1223
                                if ( ! in_array( $mutation_name, $excluded_mutations, true ) ) {
2✔
1224
                                        $excluded_mutations[] = $mutation_name;
2✔
1225
                                }
1226

1227
                                return $excluded_mutations;
2✔
1228
                        },
2✔
1229
                        10
2✔
1230
                );
2✔
1231
        }
1232

1233
        /**
1234
         * Removes a GraphQL connection from the schema.
1235
         *
1236
         * This works by preventing the connection from being registered in the first place.
1237
         *
1238
         * @uses 'graphql_excluded_connections' filter.
1239
         *
1240
         * @param string $connection_name The GraphQL connection name.
1241
         */
1242
        public function deregister_connection( string $connection_name ): void {
1✔
1243
                add_filter(
1✔
1244
                        'graphql_excluded_connections',
1✔
1245
                        static function ( $excluded_connections ) use ( $connection_name ) {
1✔
1246
                                $connection_name = strtolower( $connection_name );
1✔
1247

1248
                                if ( ! in_array( $connection_name, $excluded_connections, true ) ) {
1✔
1249
                                        $excluded_connections[] = $connection_name;
1✔
1250
                                }
1251

1252
                                return $excluded_connections;
1✔
1253
                        }
1✔
1254
                );
1✔
1255
        }
1256

1257
        /**
1258
         * Given a Type, this returns an instance of a NonNull of that type.
1259
         *
1260
         * @param \GraphQL\Type\Definition\Type|string $type The Type being wrapped.
1261
         *
1262
         * @phpstan-template T of \GraphQL\Type\Definition\NullableType&\GraphQL\Type\Definition\Type
1263
         * @phpstan-param T|string $type The Type being wrapped
1264
         */
1265
        public function non_null( $type ): \GraphQL\Type\Definition\NonNull {
520✔
1266
                if ( is_string( $type ) ) {
520✔
1267
                        $type_def = $this->get_type( $type );
520✔
1268
                        return Type::nonNull( $type_def );
520✔
1269
                }
1270

1271
                return Type::nonNull( $type );
372✔
1272
        }
1273

1274
        /**
1275
         * Given a Type, this returns an instance of a listOf of that type.
1276
         *
1277
         * @param \GraphQL\Type\Definition\Type|string $type The Type being wrapped.
1278
         *
1279
         * @phpstan-template T of \GraphQL\Type\Definition\Type
1280
         * @phpstan-param T|string $type The Type being wrapped.
1281
         * @phpstan-return \GraphQL\Type\Definition\ListOfType<T>
1282
         */
1283
        public function list_of( $type ): \GraphQL\Type\Definition\ListOfType {
396✔
1284
                if ( is_string( $type ) ) {
396✔
1285
                        $resolved_type = $this->get_type( $type );
214✔
1286

1287
                        if ( is_null( $resolved_type ) ) {
214✔
NEW
1288
                                $resolved_type = Type::string();
×
1289
                        }
1290

1291
                        /** @phpstan-var T $resolved_type */
1292
                        $type = $resolved_type;
214✔
1293
                }
1294

1295
                return Type::listOf( $type );
396✔
1296
        }
1297

1298
        /**
1299
         * Get the list of GraphQL type names to exclude from the schema.
1300
         *
1301
         * Type names are normalized using `strtolower()`, to avoid case sensitivity issues.
1302
         *
1303
         * @since 1.13.0
1304
         *
1305
         * @return string[]
1306
         */
1307
        public function get_excluded_types(): array {
605✔
1308
                if ( null === $this->excluded_types ) {
605✔
1309
                        /**
1310
                         * Filter the list of GraphQL types to exclude from the schema.
1311
                         *
1312
                         * Note: using this filter directly will NOT remove the type from being referenced as a possible interface or a union type.
1313
                         * To remove a GraphQL from the schema **entirely**, please use deregister_graphql_type();
1314
                         *
1315
                         * @param string[] $excluded_types The names of the GraphQL Types to exclude.
1316
                         *
1317
                         * @since 1.13.0
1318
                         */
1319
                        $excluded_types = apply_filters( 'graphql_excluded_types', [] );
593✔
1320

1321
                        // Normalize the types to be lowercase, to avoid case-sensitivity issue when comparing.
1322
                        $this->excluded_types = ! empty( $excluded_types ) ? array_map( 'strtolower', $excluded_types ) : [];
593✔
1323
                }
1324

1325
                return $this->excluded_types;
605✔
1326
        }
1327

1328
        /**
1329
         * Get the list of GraphQL connections to exclude from the schema.
1330
         *
1331
         * Type names are normalized using `strtolower()`, to avoid case sensitivity issues.
1332
         *
1333
         * @return string[]
1334
         *
1335
         * @since 1.14.0
1336
         */
1337
        public function get_excluded_connections(): array {
593✔
1338
                if ( null === $this->excluded_connections ) {
593✔
1339
                        /**
1340
                         * Filter the list of GraphQL connections to excluded from the registry.
1341
                         *
1342
                         * @param string[] $excluded_connections The names of the GraphQL connections to exclude.
1343
                         *
1344
                         * @since 1.14.0
1345
                         */
1346
                        $excluded_connections = apply_filters( 'graphql_excluded_connections', [] );
593✔
1347

1348
                        // Normalize the types to be lowercase, to avoid case-sensitivity issue when comparing.
1349
                        $this->excluded_connections = ! empty( $excluded_connections ) ? array_map( 'strtolower', $excluded_connections ) : [];
593✔
1350
                }
1351

1352
                return $this->excluded_connections;
593✔
1353
        }
1354

1355
        /**
1356
         * Get the list of GraphQL mutation names to exclude from the schema.
1357
         *
1358
         * Mutation names are normalized using `strtolower()`, to avoid case sensitivity issues.
1359
         *
1360
         * @return string[]
1361
         * @since 1.14.0
1362
         */
1363
        public function get_excluded_mutations(): array {
593✔
1364
                if ( null === $this->excluded_mutations ) {
593✔
1365
                        /**
1366
                         * Filter the list of GraphQL mutations to excluded from the registry.
1367
                         *
1368
                         * @param string[] $excluded_mutations The names of the GraphQL mutations to exclude.
1369
                         *
1370
                         * @since 1.14.0
1371
                         */
1372
                        $excluded_mutations = apply_filters( 'graphql_excluded_mutations', [] );
593✔
1373

1374
                        // Normalize the types to be lowercase, to avoid case-sensitivity issue when comparing.
1375
                        $this->excluded_mutations = ! empty( $excluded_mutations ) ? array_map( 'strtolower', $excluded_mutations ) : [];
593✔
1376
                }
1377

1378
                return $this->excluded_mutations;
593✔
1379
        }
1380

1381
        /**
1382
         * Gets the actual type name, stripped of possible NonNull and ListOf wrappers.
1383
         *
1384
         * Returns an empty string if the type modifiers are malformed.
1385
         *
1386
         * @param string|array<string|int,mixed> $type The (possibly-wrapped) type name.
1387
         */
1388
        protected function get_unmodified_type_name( $type ): string {
603✔
1389
                if ( ! is_array( $type ) ) {
603✔
1390
                        return $type;
603✔
1391
                }
1392

1393
                $type = array_values( $type )[0] ?? '';
603✔
1394

1395
                return $this->get_unmodified_type_name( $type );
603✔
1396
        }
1397
}
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