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

wp-graphql / wp-graphql / 14716683875

28 Apr 2025 07:58PM UTC coverage: 84.287% (+1.6%) from 82.648%
14716683875

push

github

actions-user
release: merge develop into master for v2.3.0

15905 of 18870 relevant lines covered (84.29%)

257.23 hits per line

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

96.1
/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 keys that are prepared for introspection.
155
         *
156
         * @var array<string>|null
157
         */
158
        protected static ?array $introspection_keys = null;
159

160
        /**
161
         * The loaders needed to register types
162
         *
163
         * @var array<string,callable():(mixed|array<string,mixed>|\GraphQL\Type\Definition\Type|null)>
164
         */
165
        protected $type_loaders;
166

167
        /**
168
         * Stores a list of Types that need to be eagerly loaded instead of lazy loaded.
169
         *
170
         * Types that exist in the Schema but are only part of a Union/Interface ResolveType but not
171
         * referenced directly need to be eagerly loaded.
172
         *
173
         * @var array<string,string>
174
         */
175
        protected $eager_type_map;
176

177
        /**
178
         * Stores a list of Types that should be excluded from the schema.
179
         *
180
         * Type names are filtered by `graphql_excluded_types` and normalized using strtolower(), to avoid case sensitivity issues.
181
         *
182
         * @var string[]
183
         */
184
        protected $excluded_types = null;
185

186
        /**
187
         * Stores a list of mutation names that should be excluded from the schema, along with their generated input and payload types.
188
         *
189
         * Type names are filtered by `graphql_excluded_mutations` and normalized using strtolower(), to avoid case sensitivity issues.
190
         *
191
         * @var string[]
192
         */
193
        protected $excluded_mutations = null;
194

195
        /**
196
         * Stores a list of connection Type names that should be excluded from the schema, along with their generated types.
197
         *
198
         * Type names are filtered by `graphql_excluded_connections` and normalized using strtolower(), to avoid case sensitivity issues.
199
         *
200
         * Type name
201
         *
202
         * @var string[]
203
         */
204
        protected $excluded_connections = null;
205

206
        /**
207
         * TypeRegistry constructor.
208
         */
209
        public function __construct() {
595✔
210
                $this->types          = [];
595✔
211
                $this->type_loaders   = [];
595✔
212
                $this->eager_type_map = [];
595✔
213
        }
214

215
        /**
216
         * Formats the array key to a more friendly format
217
         *
218
         * @param string $key Name of the array key to format
219
         *
220
         * @return string
221
         */
222
        protected function format_key( string $key ) {
631✔
223
                return strtolower( $key );
631✔
224
        }
225

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

245
                return [];
524✔
246
        }
247

248
        /**
249
         * Initialize the TypeRegistry
250
         *
251
         * @throws \Exception
252
         *
253
         * @return void
254
         */
255
        public function init() {
593✔
256
                $this->register_type( 'Bool', Type::boolean() );
593✔
257
                $this->register_type( 'Boolean', Type::boolean() );
593✔
258
                $this->register_type( 'Float', Type::float() );
593✔
259
                $this->register_type( 'Number', Type::float() );
593✔
260
                $this->register_type( 'Id', Type::id() );
593✔
261
                $this->register_type( 'Int', Type::int() );
593✔
262
                $this->register_type( 'Integer', Type::int() );
593✔
263
                $this->register_type( 'String', Type::string() );
593✔
264

265
                /**
266
                 * When the Type Registry is initialized execute these files
267
                 */
268
                add_action( 'init_graphql_type_registry', [ $this, 'init_type_registry' ], 5, 1 );
593✔
269

270
                /**
271
                 * Fire an action as the Type registry is being initiated
272
                 *
273
                 * @param \WPGraphQL\Registry\TypeRegistry $registry Instance of the TypeRegistry
274
                 */
275
                do_action( 'init_graphql_type_registry', $this );
593✔
276
        }
277

278
        /**
279
         * Initialize the Type Registry
280
         *
281
         * @param \WPGraphQL\Registry\TypeRegistry $type_registry
282
         *
283
         * @return void
284
         * @throws \Exception
285
         */
286
        public function init_type_registry( self $type_registry ) {
593✔
287

288
                /**
289
                 * Fire an action as the type registry is initialized. This executes
290
                 * before the `graphql_register_types` action to allow for earlier hooking
291
                 *
292
                 * @param \WPGraphQL\Registry\TypeRegistry $registry Instance of the TypeRegistry
293
                 */
294
                do_action( 'graphql_register_initial_types', $type_registry );
593✔
295

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

325
                // register types
326
                RootQuery::register_type();
593✔
327
                RootQuery::register_post_object_fields();
593✔
328
                RootQuery::register_term_object_fields();
593✔
329
                RootMutation::register_type();
593✔
330
                Avatar::register_type();
593✔
331
                Comment::register_type();
593✔
332
                CommentAuthor::register_type();
593✔
333
                ContentTemplate::register_content_template_types();
593✔
334
                EnqueuedStylesheet::register_type();
593✔
335
                EnqueuedScript::register_type();
593✔
336
                MediaDetails::register_type();
593✔
337
                MediaItemMeta::register_type();
593✔
338
                MediaSize::register_type();
593✔
339
                Menu::register_type();
593✔
340
                MenuItem::register_type();
593✔
341
                Plugin::register_type();
593✔
342
                ContentType::register_type();
593✔
343
                PostTypeLabelDetails::register_type();
593✔
344
                Settings::register_type( $this );
593✔
345
                Taxonomy::register_type();
593✔
346
                Theme::register_type();
593✔
347
                User::register_type();
593✔
348
                UserRole::register_type();
593✔
349

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

382
                DateInput::register_type();
593✔
383
                DateQueryInput::register_type();
593✔
384
                PostObjectsConnectionOrderbyInput::register_type();
593✔
385
                UsersConnectionOrderbyInput::register_type();
593✔
386

387
                // Deprecated types.
388
                MenuItemObjectUnion::register_type( $this ); /* @phpstan-ignore staticMethod.deprecatedClass */
593✔
389
                PostObjectUnion::register_type( $this ); /* @phpstan-ignore staticMethod.deprecatedClass */
593✔
390
                TermObjectUnion::register_type( $this ); /* @phpstan-ignore staticMethod.deprecatedClass */
593✔
391

392
                /**
393
                 * Register core connections
394
                 */
395
                Comments::register_connections();
593✔
396
                MenuItems::register_connections();
593✔
397
                PostObjects::register_connections();
593✔
398
                Taxonomies::register_connections();
593✔
399
                TermObjects::register_connections();
593✔
400
                Users::register_connections();
593✔
401

402
                /**
403
                 * Register core mutations
404
                 */
405
                CommentCreate::register_mutation();
593✔
406
                CommentDelete::register_mutation();
593✔
407
                CommentRestore::register_mutation();
593✔
408
                CommentUpdate::register_mutation();
593✔
409
                MediaItemCreate::register_mutation();
593✔
410
                MediaItemDelete::register_mutation();
593✔
411
                MediaItemUpdate::register_mutation();
593✔
412
                ResetUserPassword::register_mutation();
593✔
413
                SendPasswordResetEmail::register_mutation();
593✔
414
                UserCreate::register_mutation();
593✔
415
                UserDelete::register_mutation();
593✔
416
                UserUpdate::register_mutation();
593✔
417
                UserRegister::register_mutation();
593✔
418
                UpdateSettings::register_mutation( $this );
593✔
419

420
                /**
421
                 * Register PostObject types based on post_types configured to show_in_graphql.
422
                 */
423
                $allowed_post_types = WPGraphQL::get_allowed_post_types( 'objects' );
593✔
424
                $allowed_taxonomies = WPGraphQL::get_allowed_taxonomies( 'objects' );
593✔
425

426
                foreach ( $allowed_post_types as $post_type_object ) {
593✔
427
                        PostObject::register_types( $post_type_object );
593✔
428

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

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

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

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

454
                        foreach ( $allowed_taxonomies as $tax_object ) {
593✔
455
                                // If the taxonomy is in the array of taxonomies registered to the post_type
456
                                if ( in_array( $tax_object->name, get_object_taxonomies( $post_type_object->name ), true ) ) {
593✔
457
                                        register_graphql_input_type(
593✔
458
                                                $post_type_object->graphql_single_name . ucfirst( $tax_object->graphql_plural_name ) . 'NodeInput',
593✔
459
                                                [
593✔
460
                                                        'description' => static function () use ( $tax_object, $post_type_object ) {
593✔
461
                                                                return sprintf(
15✔
462
                                                                                // translators: %1$s is the GraphQL plural name of the taxonomy, %2$s is the GraphQL singular name of the post type.
463
                                                                        __( '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' ),
15✔
464
                                                                        $tax_object->graphql_plural_name,
15✔
465
                                                                        $post_type_object->graphql_single_name
15✔
466
                                                                );
15✔
467
                                                        },
593✔
468
                                                        'fields'      => [
593✔
469
                                                                'id'          => [
593✔
470
                                                                        'type'        => 'Id',
593✔
471
                                                                        'description' => static function () use ( $tax_object, $post_type_object ) {
593✔
472
                                                                                return sprintf(
15✔
473
                                                                                                // translators: %1$s is the GraphQL name of the taxonomy, %2$s is the GraphQL name of the post type.
474
                                                                                        __( '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' ),
15✔
475
                                                                                        $tax_object->graphql_single_name,
15✔
476
                                                                                        $post_type_object->graphql_single_name
15✔
477
                                                                                );
15✔
478
                                                                        },
593✔
479
                                                                ],
593✔
480
                                                                'slug'        => [
593✔
481
                                                                        'type'        => 'String',
593✔
482
                                                                        'description' => static function () use ( $tax_object ) {
593✔
483
                                                                                return sprintf(
15✔
484
                                                                                        // translators: %1$s is the GraphQL name of the taxonomy.
485
                                                                                        __( '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' ),
15✔
486
                                                                                        $tax_object->graphql_single_name
15✔
487
                                                                                );
15✔
488
                                                                        },
593✔
489
                                                                ],
593✔
490
                                                                'description' => [
593✔
491
                                                                        'type'        => 'String',
593✔
492
                                                                        'description' => static function () use ( $tax_object ) {
593✔
493
                                                                                return sprintf(
15✔
494
                                                                                        // translators: %1$s is the GraphQL name of the taxonomy.
495
                                                                                        __( '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' ),
15✔
496
                                                                                        $tax_object->graphql_single_name
15✔
497
                                                                                );
15✔
498
                                                                        },
593✔
499
                                                                ],
593✔
500
                                                                'name'        => [
593✔
501
                                                                        'type'        => 'String',
593✔
502
                                                                        'description' => static function () use ( $tax_object ) {
593✔
503
                                                                                return sprintf(
15✔
504
                                                                                        // translators: %1$s is the GraphQL name of the taxonomy.
505
                                                                                        __( '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' ),
15✔
506
                                                                                        $tax_object->graphql_single_name
15✔
507
                                                                                );
15✔
508
                                                                        },
593✔
509
                                                                ],
593✔
510
                                                        ],
593✔
511
                                                ]
593✔
512
                                        );
593✔
513

514
                                        register_graphql_input_type(
593✔
515
                                                ucfirst( $post_type_object->graphql_single_name ) . ucfirst( $tax_object->graphql_plural_name ) . 'Input',
593✔
516
                                                [
593✔
517
                                                        'description' => static function () use ( $tax_object, $post_type_object ) {
593✔
518
                                                                return sprintf(
15✔
519
                                                                        // translators: %1$s is the GraphQL name of the post type, %2$s is the plural GraphQL name of the taxonomy.
520
                                                                        __( 'Set relationships between the %1$s to %2$s', 'wp-graphql' ),
15✔
521
                                                                        $post_type_object->graphql_single_name,
15✔
522
                                                                        $tax_object->graphql_plural_name
15✔
523
                                                                );
15✔
524
                                                        },
593✔
525
                                                        'fields'      => [
593✔
526
                                                                'append' => [
593✔
527
                                                                        'type'        => 'Boolean',
593✔
528
                                                                        'description' => static function () use ( $tax_object ) {
593✔
529
                                                                                return sprintf(
15✔
530
                                                                                        // translators: %1$s is the GraphQL name of the taxonomy, %2$s is the plural GraphQL name of the taxonomy.
531
                                                                                        __( 'If true, this will append the %1$s to existing related %2$s. If false, this will replace existing relationships. Default true.', 'wp-graphql' ),
15✔
532
                                                                                        $tax_object->graphql_single_name,
15✔
533
                                                                                        $tax_object->graphql_plural_name
15✔
534
                                                                                );
15✔
535
                                                                        },
593✔
536
                                                                ],
593✔
537
                                                                'nodes'  => [
593✔
538
                                                                        'type'        => [
593✔
539
                                                                                'list_of' => $post_type_object->graphql_single_name . ucfirst( $tax_object->graphql_plural_name ) . 'NodeInput',
593✔
540
                                                                        ],
593✔
541
                                                                        'description' => static function () {
593✔
542
                                                                                return __( 'The input list of items to set.', 'wp-graphql' );
15✔
543
                                                                        },
593✔
544
                                                                ],
593✔
545
                                                        ],
593✔
546
                                                ]
593✔
547
                                        );
593✔
548
                                }
549
                        }
550
                }
551

552
                /**
553
                 * Register TermObject types based on taxonomies configured to show_in_graphql
554
                 */
555
                foreach ( $allowed_taxonomies as $tax_object ) {
593✔
556
                        TermObject::register_types( $tax_object );
593✔
557

558
                        if ( empty( $tax_object->graphql_exclude_mutations ) || ! in_array( 'create', $tax_object->graphql_exclude_mutations, true ) ) {
593✔
559
                                TermObjectCreate::register_mutation( $tax_object );
593✔
560
                        }
561

562
                        if ( empty( $tax_object->graphql_exclude_mutations ) || ! in_array( 'update', $tax_object->graphql_exclude_mutations, true ) ) {
593✔
563
                                TermObjectUpdate::register_mutation( $tax_object );
593✔
564
                        }
565

566
                        if ( empty( $tax_object->graphql_exclude_mutations ) || ! in_array( 'delete', $tax_object->graphql_exclude_mutations, true ) ) {
593✔
567
                                TermObjectDelete::register_mutation( $tax_object );
593✔
568
                        }
569
                }
570

571
                /**
572
                 * Create the root query fields for any setting type in
573
                 * the $allowed_setting_types array.
574
                 */
575
                $allowed_setting_types = DataSource::get_allowed_settings_by_group( $this );
593✔
576

577
                /**
578
                 * The url is not a registered setting for multisite, so this is a polyfill
579
                 * to expose the URL to the Schema for multisite sites
580
                 */
581
                if ( is_multisite() ) {
593✔
582
                        $this->register_field(
593✔
583
                                'GeneralSettings',
593✔
584
                                'url',
593✔
585
                                [
593✔
586
                                        'type'        => 'String',
593✔
587
                                        'description' => static function () {
593✔
588
                                                return __( 'Site URL.', 'wp-graphql' );
18✔
589
                                        },
593✔
590
                                        'resolve'     => static function () {
593✔
591
                                                return get_site_url();
1✔
592
                                        },
593✔
593
                                ]
593✔
594
                        );
593✔
595
                }
596

597
                if ( ! empty( $allowed_setting_types ) && is_array( $allowed_setting_types ) ) {
593✔
598
                        foreach ( $allowed_setting_types as $group_name => $setting_type ) {
593✔
599
                                $group_name = DataSource::format_group_name( $group_name );
593✔
600
                                $type_name  = SettingGroup::register_settings_group( $group_name, $group_name, $this );
593✔
601

602
                                if ( ! $type_name ) {
593✔
603
                                        continue;
×
604
                                }
605

606
                                register_graphql_field(
593✔
607
                                        'RootQuery',
593✔
608
                                        Utils::format_field_name( $type_name ),
593✔
609
                                        [
593✔
610
                                                'type'        => $type_name,
593✔
611
                                                'description' => static function () use ( $group_name ) {
593✔
612
                                                        return sprintf(
69✔
613
                                                                // translators: %s is the GraphQL name of the settings group.
614
                                                                __( "Fields of the '%s' settings group", 'wp-graphql' ),
69✔
615
                                                                ucfirst( $group_name ) . 'Settings'
69✔
616
                                                        );
69✔
617
                                                },
593✔
618
                                                'resolve'     => static function () use ( $setting_type ) {
593✔
619
                                                        return $setting_type;
8✔
620
                                                },
593✔
621
                                        ]
593✔
622
                                );
593✔
623
                        }
624
                }
625

626
                /**
627
                 * Fire an action as the type registry is initialized. This executes
628
                 * before the `graphql_register_types` action to allow for earlier hooking
629
                 *
630
                 * @param \WPGraphQL\Registry\TypeRegistry $registry Instance of the TypeRegistry
631
                 */
632
                do_action( 'graphql_register_types', $type_registry );
593✔
633

634
                /**
635
                 * Fire an action as the type registry is initialized. This executes
636
                 * during the `graphql_register_types` action to allow for earlier hooking
637
                 *
638
                 * @param \WPGraphQL\Registry\TypeRegistry $registry Instance of the TypeRegistry
639
                 */
640
                do_action( 'graphql_register_types_late', $type_registry );
593✔
641
        }
642

643
        /**
644
         * Given a config for a custom Scalar, this adds the Scalar for use in the Schema.
645
         *
646
         * @param string              $type_name The name of the Type to register
647
         * @param array<string,mixed> $config    The config for the scalar type to register
648
         *
649
         * @throws \Exception
650
         *
651
         * @return void
652
         */
653
        public function register_scalar( string $type_name, array $config ) {
1✔
654
                $config['kind'] = 'scalar';
1✔
655
                $this->register_type( $type_name, $config );
1✔
656
        }
657

658
        /**
659
         * Registers connections that were passed through the Type registration config
660
         *
661
         * @param array<string,mixed> $config Type config
662
         *
663
         * @return void
664
         *
665
         * @throws \Exception
666
         */
667
        protected function register_connections_from_config( array $config ) {
593✔
668
                $connections = $config['connections'] ?? null;
593✔
669

670
                if ( ! is_array( $connections ) ) {
593✔
671
                        return;
×
672
                }
673

674
                foreach ( $connections as $field_name => $connection_config ) {
593✔
675
                        if ( ! is_array( $connection_config ) ) {
593✔
676
                                continue;
×
677
                        }
678

679
                        $connection_config['fromType']      = $config['name'];
593✔
680
                        $connection_config['fromFieldName'] = $field_name;
593✔
681
                        register_graphql_connection( $connection_config );
593✔
682
                }
683
        }
684

685
        /**
686
         * Add a Type to the Registry
687
         *
688
         * @param string                                                  $type_name The name of the type to register
689
         * @param mixed|array<string,mixed>|\GraphQL\Type\Definition\Type $config The config for the type
690
         *
691
         * @throws \Exception
692
         */
693
        public function register_type( string $type_name, $config ): void {
606✔
694
                /**
695
                 * If the type should be excluded from the schema, skip it.
696
                 */
697
                if ( in_array( strtolower( $type_name ), $this->get_excluded_types(), true ) ) {
593✔
698
                        return;
5✔
699
                }
700
                /**
701
                 * If the Type Name starts with a number, skip it.
702
                 */
703
                if ( ! is_valid_graphql_name( $type_name ) ) {
593✔
704
                        graphql_debug(
×
705
                                sprintf(
×
706
                                        // translators: %s is the name of the type.
707
                                        __( 'The Type name \'%1$s\' is invalid and has not been added to the GraphQL Schema.', 'wp-graphql' ),
×
708
                                        $type_name
×
709
                                ),
×
710
                                [
×
711
                                        'type'      => 'INVALID_TYPE_NAME',
×
712
                                        'type_name' => $type_name,
×
713
                                ]
×
714
                        );
×
715
                        return;
×
716
                }
717

718
                /**
719
                 * If the Type Name is already registered, skip it.
720
                 */
721
                if ( isset( $this->types[ $this->format_key( $type_name ) ] ) || isset( $this->type_loaders[ $this->format_key( $type_name ) ] ) ) {
593✔
722
                        graphql_debug(
2✔
723
                                sprintf(
2✔
724
                                        // translators: %s is the name of the type.
725
                                        __( '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✔
726
                                        $type_name
2✔
727
                                ),
2✔
728
                                [
2✔
729
                                        'type'      => 'DUPLICATE_TYPE',
2✔
730
                                        'type_name' => $type_name,
2✔
731
                                ]
2✔
732
                        );
2✔
733
                        return;
2✔
734
                }
735

736
                /**
737
                 * Register any connections that were passed through the Type config
738
                 */
739
                if ( is_array( $config ) && isset( $config['connections'] ) ) {
593✔
740
                        $config['name'] = ucfirst( $type_name );
593✔
741
                        $this->register_connections_from_config( $config );
593✔
742
                }
743

744
                $this->type_loaders[ $this->format_key( $type_name ) ] = function () use ( $type_name, $config ) {
606✔
745
                        return $this->prepare_type( $type_name, $config );
606✔
746
                };
606✔
747

748
                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✔
749
                        $this->eager_type_map[ $this->format_key( $type_name ) ] = $this->format_key( $type_name );
69✔
750
                }
751
        }
752

753
        /**
754
         * Add an Object Type to the Registry
755
         *
756
         * @param string              $type_name The name of the type to register
757
         * @param array<string,mixed> $config The configuration of the type
758
         *
759
         * @throws \Exception
760
         */
761
        public function register_object_type( string $type_name, array $config ): void {
593✔
762
                $config['kind'] = 'object';
593✔
763
                $this->register_type( $type_name, $config );
593✔
764
        }
765

766
        /**
767
         * Add an Interface Type to the registry
768
         *
769
         * @param string                                                  $type_name The name of the type to register
770
         * @param mixed|array<string,mixed>|\GraphQL\Type\Definition\Type $config The configuration of the type
771
         *
772
         * @throws \Exception
773
         */
774
        public function register_interface_type( string $type_name, $config ): void {
593✔
775
                $config['kind'] = 'interface';
593✔
776
                $this->register_type( $type_name, $config );
593✔
777
        }
778

779
        /**
780
         * Add an Enum Type to the registry
781
         *
782
         * @param string              $type_name The name of the type to register
783
         * @param array<string,mixed> $config he configuration of the type
784
         *
785
         * @throws \Exception
786
         */
787
        public function register_enum_type( string $type_name, array $config ): void {
×
788
                $config['kind'] = 'enum';
×
789
                $this->register_type( $type_name, $config );
×
790
        }
791

792
        /**
793
         * Add an Input Type to the Registry
794
         *
795
         * @param string              $type_name The name of the type to register
796
         * @param array<string,mixed> $config he configuration of the type
797
         *
798
         * @throws \Exception
799
         */
800
        public function register_input_type( string $type_name, array $config ): void {
593✔
801
                $config['kind'] = 'input';
593✔
802
                $this->register_type( $type_name, $config );
593✔
803
        }
804

805
        /**
806
         * Add a Union Type to the Registry
807
         *
808
         * @param string              $type_name The name of the type to register
809
         * @param array<string,mixed> $config he configuration of the type
810
         *
811
         * @throws \Exception
812
         */
813
        public function register_union_type( string $type_name, array $config ): void {
×
814
                $config['kind'] = 'union';
×
815
                $this->register_type( $type_name, $config );
×
816
        }
817

818
        /**
819
         * Get the keys that are prepared for introspection.
820
         *
821
         * @return array<string>
822
         */
823
        protected static function get_introspection_keys(): array {
607✔
824

825
                if ( null === self::$introspection_keys ) {
607✔
826
                        /**
827
                         * Filter the keys that are prepared for introspection.
828
                         *
829
                         * @param array<string> $introspection_keys The keys to prepare for introspection.
830
                         */
831
                        $introspection_keys       = \apply_filters( 'graphql_introspection_keys', [ 'description', 'deprecationReason' ] );
1✔
832
                        self::$introspection_keys = $introspection_keys;
1✔
833
                }
834

835
                return self::$introspection_keys;
607✔
836
        }
837

838
        /**
839
         * Prepare the config for introspection. This is used to resolve callable values for description and deprecationReason for
840
         * introspection queries.
841
         *
842
         * @param array<string,mixed> $config The config to prepare.
843
         *
844
         * @return array<string,mixed> The prepared config.
845
         *
846
         * @internal
847
         */
848
        public static function prepare_config_for_introspection( array $config ): array {
607✔
849

850
                // Get the keys that are prepared for introspection.
851
                $introspection_keys = self::get_introspection_keys();
607✔
852

853
                foreach ( $introspection_keys as $key ) {
607✔
854
                        if ( ! isset( $config[ $key ] ) || ! is_callable( $config[ $key ] ) ) {
607✔
855
                                continue;
607✔
856
                        }
857

858
                        if ( ! WPGraphQL::is_introspection_query() ) {
606✔
859
                                // If not introspection, set to null.
860
                                $config[ $key ] = null;
539✔
861
                                continue;
539✔
862
                        }
863

864
                        $config[ $key ] = is_callable( $config[ $key ] ) ? $config[ $key ]() : '';
73✔
865
                }
866

867
                return $config;
607✔
868
        }
869

870
        /**
871
         * Prepare the type for registration.
872
         *
873
         * @param string                                                  $type_name The name of the type to prepare
874
         * @param mixed|array<string,mixed>|\GraphQL\Type\Definition\Type $config    The config for the type
875
         *
876
         * @return mixed|\GraphQL\Type\Definition\Type|null The prepared type
877
         */
878
        protected function prepare_type( string $type_name, $config ) {
606✔
879
                if ( ! is_array( $config ) ) {
606✔
880
                        return $config;
594✔
881
                }
882

883
                $prepared_type = null;
606✔
884

885
                if ( ! empty( $config ) ) {
606✔
886
                        $kind           = isset( $config['kind'] ) ? $config['kind'] : null;
606✔
887
                        $config['name'] = ucfirst( $type_name );
606✔
888

889
                        $config = self::prepare_config_for_introspection( $config );
606✔
890

891
                        switch ( $kind ) {
892
                                case 'enum':
606✔
893
                                        $prepared_type = new WPEnumType( $config );
408✔
894
                                        break;
408✔
895
                                case 'input':
605✔
896
                                        /** @var InputObjectConfig $config */
897
                                        $prepared_type = new WPInputObjectType( $config, $this );
425✔
898
                                        break;
425✔
899
                                case 'scalar':
604✔
900
                                        $prepared_type = new WPScalar( $config, $this );
1✔
901
                                        break;
1✔
902
                                case 'union':
604✔
903
                                        $prepared_type = new WPUnionType( $config, $this );
127✔
904
                                        break;
127✔
905
                                case 'interface':
604✔
906
                                        /** @var InterfaceConfig $config */
907
                                        $prepared_type = new WPInterfaceType( $config, $this );
562✔
908
                                        break;
562✔
909
                                case 'object':
604✔
910
                                default:
911
                                        /** @var ObjectConfig $config */
912
                                        $prepared_type = new WPObjectType( $config, $this );
604✔
913
                        }
914
                }
915

916
                return $prepared_type;
606✔
917
        }
918

919
        /**
920
         * Given a type name, returns the type or null if not found
921
         *
922
         * @param string $type_name The name of the Type to get from the registry
923
         *
924
         * @return mixed|array<string,mixed>|\GraphQL\Type\Definition\Type|null
925
         */
926
        public function get_type( string $type_name ) {
631✔
927
                $key = $this->format_key( $type_name );
631✔
928

929
                if ( isset( $this->type_loaders[ $key ] ) ) {
631✔
930
                        $type                = $this->type_loaders[ $key ]();
606✔
931
                        $this->types[ $key ] = apply_filters( 'graphql_get_type', $type, $type_name );
606✔
932
                        unset( $this->type_loaders[ $key ] );
606✔
933
                }
934

935
                return $this->types[ $key ] ?? null;
631✔
936
        }
937

938
        /**
939
         * Given a type name, determines if the type is already present in the Type Loader
940
         *
941
         * @param string $type_name The name of the type to check the registry for
942
         */
943
        public function has_type( string $type_name ): bool {
593✔
944
                return isset( $this->type_loaders[ $this->format_key( $type_name ) ] );
593✔
945
        }
946

947
        /**
948
         * Return the Types in the registry
949
         *
950
         * @return array<string,mixed>
951
         */
952
        public function get_types(): array {
593✔
953

954
                // The full map of types is merged with eager types to support the
955
                // rename_graphql_type API.
956
                //
957
                // All of the types are closures, but eager Types are the full
958
                // Type definitions up front
959
                return array_merge( $this->types, $this->get_eager_type_map() );
593✔
960
        }
961

962
        /**
963
         * Wrapper for prepare_field to prepare multiple fields for registration at once
964
         *
965
         * @param array<string,mixed> $fields    Array of fields and their settings to register on a Type
966
         * @param string              $type_name Name of the Type to register the fields to
967
         *
968
         * @return array<string,mixed>
969
         * @throws \Exception
970
         */
971
        public function prepare_fields( array $fields, string $type_name ): array {
605✔
972
                $prepared_fields = [];
605✔
973
                foreach ( $fields as $field_name => $field_config ) {
605✔
974
                        if ( is_array( $field_config ) && isset( $field_config['type'] ) ) {
605✔
975
                                $prepared_field = $this->prepare_field( $field_name, $field_config, $type_name );
605✔
976
                                if ( ! empty( $prepared_field ) ) {
605✔
977
                                        $prepared_fields[ $this->format_key( $field_name ) ] = $prepared_field;
605✔
978
                                }
979
                        }
980
                }
981

982
                return $prepared_fields;
605✔
983
        }
984

985
        /**
986
         * Prepare the field to be registered on the type
987
         *
988
         * @param string              $field_name   Friendly name of the field
989
         * @param array<string,mixed> $field_config Config data about the field to prepare
990
         * @param string              $type_name    Name of the type to prepare the field for
991
         *
992
         * @return ?array<string,mixed>
993
         * @throws \Exception
994
         */
995
        protected function prepare_field( string $field_name, array $field_config, string $type_name ): ?array {
612✔
996
                if ( ! isset( $field_config['name'] ) ) {
605✔
997
                        $field_config['name'] = lcfirst( $field_name );
602✔
998
                }
999

1000
                if ( ! isset( $field_config['type'] ) ) {
605✔
1001
                        graphql_debug(
1✔
1002
                                sprintf(
1✔
1003
                                        /* translators: %s is the Field name. */
1004
                                        __( 'The registered field \'%s\' does not have a Type defined. Make sure to define a type for all fields.', 'wp-graphql' ),
1✔
1005
                                        $field_name
1✔
1006
                                ),
1✔
1007
                                [
1✔
1008
                                        'type'       => 'INVALID_FIELD_TYPE',
1✔
1009
                                        'type_name'  => $type_name,
1✔
1010
                                        'field_name' => $field_name,
1✔
1011
                                ]
1✔
1012
                        );
1✔
1013
                        return null;
1✔
1014
                }
1015

1016
                /**
1017
                 * If the type is a string, create a callable wrapper to get the type from
1018
                 * type registry. This preserves lazy-loading and prevents a bug where a type
1019
                 * has the same name as a function in the global scope (e.g., `header()`) and
1020
                 * is called since it passes `is_callable`.
1021
                 */
1022
                if ( is_string( $field_config['type'] ) ) {
605✔
1023
                        // Bail if the type is excluded from the Schema.
1024
                        if ( in_array( strtolower( $field_config['type'] ), $this->get_excluded_types(), true ) ) {
604✔
1025
                                return null;
4✔
1026
                        }
1027

1028
                        $field_config['type'] = function () use ( $field_config, $type_name ) {
612✔
1029
                                $type = $this->get_type( $field_config['type'] );
557✔
1030
                                if ( ! $type ) {
557✔
1031
                                        $message = sprintf(
1✔
1032
                                        /* 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. */
1033
                                                __( '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✔
1034
                                                $field_config['name'],
1✔
1035
                                                $type_name,
1✔
1036
                                                $field_config['type']
1✔
1037
                                        );
1✔
1038
                                        // 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,
1039
                                        // but now it will have a more helpful error message.
1040
                                        throw new Error( esc_html( $message ) );
1✔
1041
                                }
1042
                                return $type;
557✔
1043
                        };
612✔
1044
                }
1045

1046
                /**
1047
                 * If the type is an array, it contains type modifiers (e.g., "non_null").
1048
                 * Create a callable wrapper to preserve lazy-loading.
1049
                 */
1050
                if ( is_array( $field_config['type'] ) ) {
605✔
1051
                        // Bail if the type is excluded from the Schema.
1052
                        $unmodified_type_name = $this->get_unmodified_type_name( $field_config['type'] );
603✔
1053

1054
                        if ( empty( $unmodified_type_name ) || in_array( strtolower( $unmodified_type_name ), $this->get_excluded_types(), true ) ) {
603✔
1055
                                return null;
1✔
1056
                        }
1057

1058
                        $field_config['type'] = function () use ( $field_config ) {
608✔
1059
                                return $this->setup_type_modifiers( $field_config['type'] );
522✔
1060
                        };
608✔
1061
                }
1062

1063
                /**
1064
                 * If the field has arguments, each one must be prepared.
1065
                 */
1066
                if ( isset( $field_config['args'] ) && is_array( $field_config['args'] ) ) {
605✔
1067
                        foreach ( $field_config['args'] as $arg_name => $arg_config ) {
598✔
1068
                                $arg = $this->prepare_field( $arg_name, $arg_config, $type_name );
598✔
1069

1070
                                // Remove the arg if the field could not be prepared.
1071
                                if ( empty( $arg ) ) {
598✔
1072
                                        unset( $field_config['args'][ $arg_name ] );
1✔
1073
                                        continue;
1✔
1074
                                }
1075

1076
                                $field_config['args'][ $arg_name ] = $arg;
598✔
1077
                        }
1078
                }
1079

1080
                /**
1081
                 * If the field has no (remaining) valid arguments, unset the key.
1082
                 */
1083
                if ( empty( $field_config['args'] ) ) {
605✔
1084
                        unset( $field_config['args'] );
605✔
1085
                }
1086

1087
                $field_config = self::prepare_config_for_introspection( $field_config );
605✔
1088

1089
                return $field_config;
605✔
1090
        }
1091

1092
        /**
1093
         * Processes type modifiers (e.g., "non-null"). Loads types immediately, so do
1094
         * not call before types are ready to be loaded.
1095
         *
1096
         * @param \GraphQL\Type\Definition\Type|string|array<string,mixed> $type The type definition to process.
1097
         *
1098
         * @return \GraphQL\Type\Definition\Type|string|array<string,mixed>
1099
         * @throws \Exception
1100
         */
1101
        public function setup_type_modifiers( $type ) {
522✔
1102
                if ( ! is_array( $type ) ) {
522✔
1103
                        return $type;
522✔
1104
                }
1105

1106
                if ( isset( $type['non_null'] ) ) {
522✔
1107
                        /** @var \GraphQL\Type\Definition\Type $inner_type */
1108
                        $inner_type = $this->setup_type_modifiers( $type['non_null'] );
520✔
1109

1110
                        return $this->non_null( $inner_type );
520✔
1111
                }
1112

1113
                if ( isset( $type['list_of'] ) ) {
395✔
1114
                        /** @var \GraphQL\Type\Definition\Type $inner_type */
1115
                        $inner_type = $this->setup_type_modifiers( $type['list_of'] );
395✔
1116
                        return $this->list_of( $inner_type );
395✔
1117
                }
1118

1119
                return $type;
×
1120
        }
1121

1122
        /**
1123
         * Wrapper for the register_field method to register multiple fields at once
1124
         *
1125
         * @param string                            $type_name Name of the type in the Type Registry to add the fields to
1126
         * @param array<string,array<string,mixed>> $fields    Fields to register
1127
         *
1128
         * @throws \Exception
1129
         */
1130
        public function register_fields( string $type_name, array $fields = [] ): void {
592✔
1131
                if ( ! empty( $fields ) ) {
592✔
1132
                        foreach ( $fields as $field_name => $config ) {
592✔
1133
                                if ( is_string( $field_name ) && ! empty( $config ) && is_array( $config ) ) {
592✔
1134
                                        $this->register_field( $type_name, $field_name, $config );
592✔
1135
                                }
1136
                        }
1137
                }
1138
        }
1139

1140
        /**
1141
         * Add a field to a Type in the Type Registry
1142
         *
1143
         * @param string              $type_name  Name of the type in the Type Registry to add the fields to
1144
         * @param string              $field_name Name of the field to add to the type
1145
         * @param array<string,mixed> $config     Info about the field to register to the type
1146
         *
1147
         * @throws \Exception
1148
         */
1149
        public function register_field( string $type_name, string $field_name, array $config ): void {
593✔
1150
                add_filter(
593✔
1151
                        'graphql_' . $type_name . '_fields',
593✔
1152
                        function ( $fields ) use ( $type_name, $field_name, $config ) {
593✔
1153

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

1157
                                $field_name = Utils::format_field_name( $field_name, $allow_field_underscores );
593✔
1158

1159
                                if ( preg_match( '/^\d/', $field_name ) ) {
593✔
1160
                                        graphql_debug(
1✔
1161
                                                sprintf(
1✔
1162
                                                        // translators: %1$s is the field name, %2$s is the type name.
1163
                                                        __( 'The field \'%1$s\' on Type \'%2$s\' is invalid. Field names cannot start with a number.', 'wp-graphql' ),
1✔
1164
                                                        $field_name,
1✔
1165
                                                        $type_name
1✔
1166
                                                ),
1✔
1167
                                                [
1✔
1168
                                                        'type'       => 'INVALID_FIELD_NAME',
1✔
1169
                                                        'field_name' => $field_name,
1✔
1170
                                                        'type_name'  => $type_name,
1✔
1171
                                                ]
1✔
1172
                                        );
1✔
1173
                                        return $fields;
1✔
1174
                                }
1175

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

1179
                                        // if the existing field is a connection type
1180
                                        // and the new field is also a connection type
1181
                                        // and the toType is the same for both
1182
                                        // then we can allow the duplicate field
1183
                                        if (
1184
                                                isset(
1185
                                                        $fields[ $field_name ]['isConnectionField'],
1✔
1186
                                                        $config['isConnectionField'],
1✔
1187
                                                        $fields[ $field_name ]['toType'],
1✔
1188
                                                        $config['toType'],
1✔
1189
                                                        $fields[ $field_name ]['connectionTypeName'],
1✔
1190
                                                        $config['connectionTypeName']
1✔
1191
                                                ) &&
1192
                                                $fields[ $field_name ]['toType'] === $config['toType'] &&
1✔
1193
                                                $fields[ $field_name ]['connectionTypeName'] === $config['connectionTypeName']
1✔
1194
                                        ) {
1195
                                                return $fields;
×
1196
                                        }
1197

1198
                                        graphql_debug(
1✔
1199
                                                sprintf(
1✔
1200
                                                        // translators: %1$s is the field name, %2$s is the type name.
1201
                                                        __( '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✔
1202
                                                        $field_name,
1✔
1203
                                                        $type_name
1✔
1204
                                                ),
1✔
1205
                                                [
1✔
1206
                                                        'type'            => 'DUPLICATE_FIELD',
1✔
1207
                                                        'field_name'      => $field_name,
1✔
1208
                                                        'type_name'       => $type_name,
1✔
1209
                                                        'existing_field'  => $fields[ $field_name ],
1✔
1210
                                                        'duplicate_field' => $config,
1✔
1211
                                                ]
1✔
1212
                                        );
1✔
1213
                                        return $fields;
1✔
1214
                                }
1215

1216
                                /**
1217
                                 * If the field returns a properly prepared field, add it the the field registry
1218
                                 */
1219
                                $field = $this->prepare_field( $field_name, $config, $type_name );
593✔
1220

1221
                                if ( ! empty( $field ) ) {
593✔
1222
                                        $fields[ $field_name ] = $field;
593✔
1223
                                }
1224

1225
                                return $fields;
593✔
1226
                        },
593✔
1227
                        10,
593✔
1228
                        1
593✔
1229
                );
593✔
1230
        }
1231

1232
        /**
1233
         * Remove a field from a type
1234
         *
1235
         * @param string $type_name  Name of the Type the field is registered to
1236
         * @param string $field_name Name of the field you would like to remove from the type
1237
         *
1238
         * @return void
1239
         */
1240
        public function deregister_field( string $type_name, string $field_name ) {
1✔
1241
                add_filter(
1✔
1242
                        'graphql_' . $type_name . '_fields',
1✔
1243
                        static function ( $fields ) use ( $field_name ) {
1✔
1244
                                if ( isset( $fields[ $field_name ] ) ) {
1✔
1245
                                        unset( $fields[ $field_name ] );
1✔
1246
                                }
1247

1248
                                return $fields;
1✔
1249
                        }
1✔
1250
                );
1✔
1251
        }
1252

1253
        /**
1254
         * Method to register a new connection in the Type registry
1255
         *
1256
         * @param array<string,mixed> $config The info about the connection being registered
1257
         *
1258
         * @throws \InvalidArgumentException
1259
         * @throws \Exception
1260
         */
1261
        public function register_connection( array $config ): void {
593✔
1262
                new WPConnectionType( $config, $this );
593✔
1263
        }
1264

1265
        /**
1266
         * Handles registration of a mutation to the Type registry
1267
         *
1268
         * @param string              $mutation_name Name of the mutation being registered
1269
         * @param array<string,mixed> $config        Info about the mutation being registered
1270
         *
1271
         * @throws \Exception
1272
         */
1273
        public function register_mutation( string $mutation_name, array $config ): void {
593✔
1274
                // Bail if the mutation has been excluded from the schema.
1275
                if ( in_array( strtolower( $mutation_name ), $this->get_excluded_mutations(), true ) ) {
593✔
1276
                        return;
2✔
1277
                }
1278

1279
                $config['name'] = $mutation_name;
593✔
1280
                new WPMutationType( $config, $this );
593✔
1281
        }
1282

1283
        /**
1284
         * Removes a GraphQL mutation from the schema.
1285
         *
1286
         * This works by preventing the mutation from being registered in the first place.
1287
         *
1288
         * @uses 'graphql_excluded_mutations' filter.
1289
         *
1290
         * @param string $mutation_name Name of the mutation to remove from the schema.
1291
         *
1292
         * @since 1.14.0
1293
         */
1294
        public function deregister_mutation( string $mutation_name ): void {
2✔
1295
                // Prevent the mutation from being registered to the scheme directly.
1296
                add_filter(
2✔
1297
                        'graphql_excluded_mutations',
2✔
1298
                        static function ( $excluded_mutations ) use ( $mutation_name ): array {
2✔
1299
                                // Normalize the types to prevent case sensitivity issues.
1300
                                $mutation_name = strtolower( $mutation_name );
2✔
1301
                                // If the type isn't already excluded, add it to the array.
1302
                                if ( ! in_array( $mutation_name, $excluded_mutations, true ) ) {
2✔
1303
                                        $excluded_mutations[] = $mutation_name;
2✔
1304
                                }
1305

1306
                                return $excluded_mutations;
2✔
1307
                        },
2✔
1308
                        10
2✔
1309
                );
2✔
1310
        }
1311

1312
        /**
1313
         * Removes a GraphQL connection from the schema.
1314
         *
1315
         * This works by preventing the connection from being registered in the first place.
1316
         *
1317
         * @uses 'graphql_excluded_connections' filter.
1318
         *
1319
         * @param string $connection_name The GraphQL connection name.
1320
         */
1321
        public function deregister_connection( string $connection_name ): void {
1✔
1322
                add_filter(
1✔
1323
                        'graphql_excluded_connections',
1✔
1324
                        static function ( $excluded_connections ) use ( $connection_name ) {
1✔
1325
                                $connection_name = strtolower( $connection_name );
1✔
1326

1327
                                if ( ! in_array( $connection_name, $excluded_connections, true ) ) {
1✔
1328
                                        $excluded_connections[] = $connection_name;
1✔
1329
                                }
1330

1331
                                return $excluded_connections;
1✔
1332
                        }
1✔
1333
                );
1✔
1334
        }
1335

1336
        /**
1337
         * Given a Type, this returns an instance of a NonNull of that type.
1338
         *
1339
         * @param \GraphQL\Type\Definition\Type|string $type The Type being wrapped.
1340
         *
1341
         * @phpstan-template T of \GraphQL\Type\Definition\NullableType&\GraphQL\Type\Definition\Type
1342
         * @phpstan-param T|string $type The Type being wrapped
1343
         */
1344
        public function non_null( $type ): \GraphQL\Type\Definition\NonNull {
520✔
1345
                if ( is_string( $type ) ) {
520✔
1346
                        $type_def = $this->get_type( $type );
520✔
1347
                        return Type::nonNull( $type_def );
520✔
1348
                }
1349

1350
                return Type::nonNull( $type );
372✔
1351
        }
1352

1353
        /**
1354
         * Given a Type, this returns an instance of a listOf of that type.
1355
         *
1356
         * @param \GraphQL\Type\Definition\Type|string $type The Type being wrapped.
1357
         *
1358
         * @phpstan-template T of \GraphQL\Type\Definition\Type
1359
         * @phpstan-param T|string $type The Type being wrapped.
1360
         * @phpstan-return \GraphQL\Type\Definition\ListOfType<T>
1361
         */
1362
        public function list_of( $type ): \GraphQL\Type\Definition\ListOfType {
396✔
1363
                if ( is_string( $type ) ) {
396✔
1364
                        $resolved_type = $this->get_type( $type );
214✔
1365

1366
                        if ( is_null( $resolved_type ) ) {
214✔
1367
                                $resolved_type = Type::string();
×
1368
                        }
1369

1370
                        /** @phpstan-var T $resolved_type */
1371
                        $type = $resolved_type;
214✔
1372
                }
1373

1374
                return Type::listOf( $type );
396✔
1375
        }
1376

1377
        /**
1378
         * Get the list of GraphQL type names to exclude from the schema.
1379
         *
1380
         * Type names are normalized using `strtolower()`, to avoid case sensitivity issues.
1381
         *
1382
         * @since 1.13.0
1383
         *
1384
         * @return string[]
1385
         */
1386
        public function get_excluded_types(): array {
605✔
1387
                if ( null === $this->excluded_types ) {
605✔
1388
                        /**
1389
                         * Filter the list of GraphQL types to exclude from the schema.
1390
                         *
1391
                         * Note: using this filter directly will NOT remove the type from being referenced as a possible interface or a union type.
1392
                         * To remove a GraphQL from the schema **entirely**, please use deregister_graphql_type();
1393
                         *
1394
                         * @param string[] $excluded_types The names of the GraphQL Types to exclude.
1395
                         *
1396
                         * @since 1.13.0
1397
                         */
1398
                        $excluded_types = apply_filters( 'graphql_excluded_types', [] );
593✔
1399

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

1404
                return $this->excluded_types;
605✔
1405
        }
1406

1407
        /**
1408
         * Get the list of GraphQL connections to exclude from the schema.
1409
         *
1410
         * Type names are normalized using `strtolower()`, to avoid case sensitivity issues.
1411
         *
1412
         * @return string[]
1413
         *
1414
         * @since 1.14.0
1415
         */
1416
        public function get_excluded_connections(): array {
593✔
1417
                if ( null === $this->excluded_connections ) {
593✔
1418
                        /**
1419
                         * Filter the list of GraphQL connections to excluded from the registry.
1420
                         *
1421
                         * @param string[] $excluded_connections The names of the GraphQL connections to exclude.
1422
                         *
1423
                         * @since 1.14.0
1424
                         */
1425
                        $excluded_connections = apply_filters( 'graphql_excluded_connections', [] );
593✔
1426

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

1431
                return $this->excluded_connections;
593✔
1432
        }
1433

1434
        /**
1435
         * Get the list of GraphQL mutation names to exclude from the schema.
1436
         *
1437
         * Mutation names are normalized using `strtolower()`, to avoid case sensitivity issues.
1438
         *
1439
         * @return string[]
1440
         * @since 1.14.0
1441
         */
1442
        public function get_excluded_mutations(): array {
593✔
1443
                if ( null === $this->excluded_mutations ) {
593✔
1444
                        /**
1445
                         * Filter the list of GraphQL mutations to excluded from the registry.
1446
                         *
1447
                         * @param string[] $excluded_mutations The names of the GraphQL mutations to exclude.
1448
                         *
1449
                         * @since 1.14.0
1450
                         */
1451
                        $excluded_mutations = apply_filters( 'graphql_excluded_mutations', [] );
593✔
1452

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

1457
                return $this->excluded_mutations;
593✔
1458
        }
1459

1460
        /**
1461
         * Gets the actual type name, stripped of possible NonNull and ListOf wrappers.
1462
         *
1463
         * Returns an empty string if the type modifiers are malformed.
1464
         *
1465
         * @param string|array<string|int,mixed> $type The (possibly-wrapped) type name.
1466
         */
1467
        protected function get_unmodified_type_name( $type ): string {
603✔
1468
                if ( ! is_array( $type ) ) {
603✔
1469
                        return $type;
603✔
1470
                }
1471

1472
                $type = array_values( $type )[0] ?? '';
603✔
1473

1474
                return $this->get_unmodified_type_name( $type );
603✔
1475
        }
1476
}
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