• 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

91.37
/src/Registry/Utils/TermObject.php
1
<?php
2

3
namespace WPGraphQL\Registry\Utils;
4

5
use GraphQL\Type\Definition\ResolveInfo;
6
use WPGraphQL;
7
use WPGraphQL\AppContext;
8
use WPGraphQL\Data\Connection\PostObjectConnectionResolver;
9
use WPGraphQL\Data\Connection\TaxonomyConnectionResolver;
10
use WPGraphQL\Data\Connection\TermObjectConnectionResolver;
11
use WPGraphQL\Model\Term;
12
use WPGraphQL\Type\Connection\PostObjects;
13
use WPGraphQL\Type\Connection\TermObjects;
14
use WP_Taxonomy;
15

16
/**
17
 * Class TermObjectType
18
 *
19
 * @package WPGraphQL\Data
20
 * @since   1.12.0
21
 */
22
class TermObject {
23

24
        /**
25
         * Registers a taxonomy type to the schema as either a GraphQL object, interface, or union.
26
         *
27
         * @param \WP_Taxonomy $tax_object Taxonomy.
28
         *
29
         * @return void
30
         * @throws \Exception
31
         */
32
        public static function register_types( WP_Taxonomy $tax_object ) {
593✔
33
                $single_name = $tax_object->graphql_single_name;
593✔
34

35
                $config = [
593✔
36
                        'description' => static function () use ( $tax_object, $single_name ) {
593✔
37
                                return ! empty( $tax_object->graphql_description )
20✔
38
                                        ? $tax_object->graphql_description
18✔
39
                                        : ( ! empty( $tax_object->description )
9✔
40
                                                ? $tax_object->description
×
41
                                                /* translators: taxonomy object singular name w/ description */
9✔
42
                                                : sprintf( __( 'The %s type', 'wp-graphql' ), $single_name )
20✔
43
                                        );
20✔
44
                        },
593✔
45
                        'connections' => static::get_connections( $tax_object ),
593✔
46
                        'interfaces'  => static::get_interfaces( $tax_object ),
593✔
47
                        'fields'      => static::get_fields( $tax_object ),
593✔
48
                        'model'       => Term::class,
593✔
49
                ];
593✔
50

51
                // Register as GraphQL objects.
52
                if ( 'object' === $tax_object->graphql_kind ) {
593✔
53
                        register_graphql_object_type( $single_name, $config );
593✔
54
                        return;
593✔
55
                }
56

57
                /**
58
                 * Register as GraphQL interfaces or unions.
59
                 *
60
                 * It's assumed that the types used in `resolveType` have already been registered to the schema.
61
                 */
62

63
                // Bail early if graphql_resolve_type isnt a vallable callback.
64
                if ( empty( $tax_object->graphql_resolve_type ) || ! is_callable( $tax_object->graphql_resolve_type ) ) {
3✔
65
                        graphql_debug(
1✔
66
                                sprintf(
1✔
67
                                        // translators: %1$s is the term object singular name, %2$s is the graphql kind.
68
                                        __( '%1$s is registered as a GraphQL %2$s, but has no way to resolve the type. Ensure "graphql_resolve_type" is a valid callback function', 'wp-graphql' ),
1✔
69
                                        $single_name,
1✔
70
                                        $tax_object->graphql_kind
1✔
71
                                ),
1✔
72
                                [ 'registered_taxonomy_object' => $tax_object ]
1✔
73
                        );
1✔
74

75
                        return;
1✔
76
                }
77

78
                $config['resolveType'] = $tax_object->graphql_resolve_type;
3✔
79

80
                if ( 'interface' === $tax_object->graphql_kind ) {
3✔
81
                        register_graphql_interface_type( $single_name, $config );
1✔
82

83
                        return;
1✔
84
                } elseif ( 'union' === $tax_object->graphql_kind ) {
2✔
85

86
                        // Bail early if graphql_union_types is not defined.
87
                        if ( empty( $tax_object->graphql_union_types ) || ! is_array( $tax_object->graphql_union_types ) ) {
2✔
88
                                graphql_debug(
1✔
89
                                        __( 'Registering a taxonomy with "graphql_kind" => "union" requires "graphql_union_types" to be a valid array of possible GraphQL type names.', 'wp-graphql' ),
1✔
90
                                        [ 'registered_taxonomy_object' => $tax_object ]
1✔
91
                                );
1✔
92

93
                                return;
1✔
94
                        }
95

96
                        // Set the possible types for the union.
97
                        $config['typeNames'] = $tax_object->graphql_union_types;
1✔
98

99
                        register_graphql_union_type( $single_name, $config );
1✔
100
                }
101
        }
102

103
        /**
104
         * Gets all the connections for the given post type.
105
         *
106
         * @param \WP_Taxonomy $tax_object
107
         *
108
         * @return array<string,array<string,mixed>>
109
         */
110
        protected static function get_connections( WP_Taxonomy $tax_object ) {
595✔
111
                $connections = [];
593✔
112

113
                // Taxonomy.
114
                // @todo connection move to TermNode (breaking).
115
                $connections['taxonomy'] = [
593✔
116
                        'toType'   => 'Taxonomy',
593✔
117
                        'oneToOne' => true,
593✔
118
                        'resolve'  => static function ( Term $source, $args, $context, $info ) {
593✔
119
                                if ( empty( $source->taxonomyName ) ) {
7✔
120
                                        return null;
×
121
                                }
122
                                $resolver = new TaxonomyConnectionResolver( $source, $args, $context, $info );
7✔
123
                                $resolver->set_query_arg( 'name', $source->taxonomyName );
7✔
124
                                return $resolver->one_to_one()->get_connection();
7✔
125
                        },
593✔
126
                ];
593✔
127

128
                if ( true === $tax_object->hierarchical ) {
593✔
129
                        // Children.
130
                        $connections['children'] = [
593✔
131
                                'toType'         => $tax_object->graphql_single_name,
593✔
132
                                'description'    => static function () use ( $tax_object ) {
593✔
133
                                        return sprintf(
18✔
134
                                                // translators: %1$s is the term object singular name, %2$s is the term object plural name.
135
                                                __( 'Connection between the %1$s type and its children %2$s.', 'wp-graphql' ),
18✔
136
                                                $tax_object->graphql_single_name,
18✔
137
                                                $tax_object->graphql_plural_name
18✔
138
                                        );
18✔
139
                                },
593✔
140
                                'connectionArgs' => TermObjects::get_connection_args(),
593✔
141
                                'queryClass'     => 'WP_Term_Query',
593✔
142
                                'resolve'        => static function ( Term $term, $args, AppContext $context, $info ) {
593✔
143
                                        $resolver = new TermObjectConnectionResolver( $term, $args, $context, $info );
2✔
144
                                        $resolver->set_query_arg( 'parent', $term->databaseId );
2✔
145

146
                                        return $resolver->get_connection();
2✔
147
                                },
593✔
148
                        ];
593✔
149

150
                        // Parent.
151
                        $connections['parent'] = [
594✔
152
                                'toType'             => $tax_object->graphql_single_name,
594✔
153
                                'description'        => static function () use ( $tax_object ) {
594✔
154
                                        return sprintf(
18✔
155
                                                // translators: %s is the term object singular name.
156
                                                __( 'Connection between the %1$s type and its parent %1$s.', 'wp-graphql' ),
18✔
157
                                                $tax_object->graphql_single_name
18✔
158
                                        );
18✔
159
                                },
594✔
160
                                'connectionTypeName' => ucfirst( $tax_object->graphql_single_name ) . 'ToParent' . ucfirst( $tax_object->graphql_single_name ) . 'Connection',
594✔
161
                                'oneToOne'           => true,
594✔
162
                                'resolve'            => static function ( Term $term, $args, AppContext $context, $info ) use ( $tax_object ) {
594✔
163
                                        if ( ! isset( $term->parentDatabaseId ) || empty( $term->parentDatabaseId ) ) {
2✔
164
                                                return null;
×
165
                                        }
166

167
                                        $resolver = new TermObjectConnectionResolver( $term, $args, $context, $info, $tax_object->name );
2✔
168
                                        $resolver->set_query_arg( 'include', $term->parentDatabaseId );
2✔
169

170
                                        return $resolver->one_to_one()->get_connection();
2✔
171
                                },
594✔
172
                        ];
594✔
173

174
                        // Ancestors.
175
                        $connections['ancestors'] = [
595✔
176
                                'toType'             => $tax_object->graphql_single_name,
595✔
177
                                'description'        => static function () {
595✔
178
                                        return __( 'The ancestors of the node. Default ordered as lowest (closest to the child) to highest (closest to the root).', 'wp-graphql' );
18✔
179
                                },
595✔
180
                                'connectionTypeName' => ucfirst( $tax_object->graphql_single_name ) . 'ToAncestors' . ucfirst( $tax_object->graphql_single_name ) . 'Connection',
595✔
181
                                'resolve'            => static function ( Term $term, $args, AppContext $context, $info ) use ( $tax_object ) {
595✔
182
                                        $ancestor_ids = isset( $term->databaseId ) ? get_ancestors( $term->databaseId, (string) $term->taxonomyName, 'taxonomy' ) : null;
2✔
183

184
                                        if ( empty( $ancestor_ids ) ) {
2✔
185
                                                return null;
×
186
                                        }
187

188
                                        $resolver = new TermObjectConnectionResolver( $term, $args, $context, $info, $tax_object->name );
2✔
189
                                        $resolver->set_query_arg( 'include', $ancestor_ids );
2✔
190
                                        $resolver->set_query_arg( 'orderby', 'include' );
2✔
191

192
                                        return $resolver->get_connection();
2✔
193
                                },
595✔
194
                        ];
595✔
195
                }
196

197
                // Used to ensure contentNodes connection doesn't get registered multiple times.
198
                $already_registered = false;
593✔
199
                $allowed_post_types = WPGraphQL::get_allowed_post_types( 'objects' );
593✔
200

201
                foreach ( $allowed_post_types as $post_type_object ) {
593✔
202
                        if ( ! in_array( $tax_object->name, get_object_taxonomies( $post_type_object->name ), true ) ) {
593✔
203
                                continue;
592✔
204
                        }
205

206
                        // ContentNodes.
207
                        if ( ! $already_registered ) {
593✔
208
                                $connections['contentNodes'] = PostObjects::get_connection_config(
593✔
209
                                        $tax_object,
593✔
210
                                        [
593✔
211
                                                'toType'  => 'ContentNode',
593✔
212
                                                'resolve' => static function ( Term $term, $args, $context, $info ) {
593✔
213
                                                        $resolver = new PostObjectConnectionResolver( $term, $args, $context, $info, 'any' );
×
214
                                                        $resolver->set_query_arg(
×
215
                                                                'tax_query',
×
216
                                                                [
×
217
                                                                        [
×
218
                                                                                'taxonomy'         => $term->taxonomyName,
×
219
                                                                                'terms'            => [ $term->databaseId ],
×
220
                                                                                'field'            => 'term_id',
×
221
                                                                                'include_children' => false,
×
222
                                                                        ],
×
223
                                                                ]
×
224
                                                        );
×
225

226
                                                        return $resolver->get_connection();
×
227
                                                },
593✔
228
                                        ]
593✔
229
                                );
593✔
230

231
                                // We won't need to register this connection again.
232
                                $already_registered = true;
593✔
233
                        }
234

235
                        // PostObjects.
236
                        $connections[ $post_type_object->graphql_plural_name ] = PostObjects::get_connection_config(
595✔
237
                                $post_type_object,
595✔
238
                                [
595✔
239
                                        'toType'     => $post_type_object->graphql_single_name,
595✔
240
                                        'queryClass' => 'WP_Query',
595✔
241
                                        'resolve'    => static function ( Term $term, $args, AppContext $context, ResolveInfo $info ) use ( $post_type_object ) {
595✔
242
                                                $resolver     = new PostObjectConnectionResolver( $term, $args, $context, $info, $post_type_object->name );
4✔
243
                                                $current_args = $resolver->get_query_args();
4✔
244
                                                $tax_query    = $current_args['tax_query'] ?? [];
4✔
245
                                                $tax_query[]  = [
4✔
246
                                                        'taxonomy'         => $term->taxonomyName,
4✔
247
                                                        'terms'            => [ $term->databaseId ],
4✔
248
                                                        'field'            => 'term_id',
4✔
249
                                                        'include_children' => false,
4✔
250
                                                ];
4✔
251
                                                $resolver->set_query_arg( 'tax_query', $tax_query );
4✔
252

253
                                                return $resolver->get_connection();
4✔
254
                                        },
595✔
255
                                ]
595✔
256
                        );
595✔
257
                }
258

259
                // Merge with connections set in register_taxonomy.
260
                if ( ! empty( $tax_object->graphql_connections ) ) {
593✔
261
                        $connections = array_merge( $connections, $tax_object->graphql_connections );
1✔
262
                }
263

264
                // Remove excluded connections.
265
                if ( ! empty( $tax_object->graphql_exclude_connections ) ) {
593✔
266
                        foreach ( $tax_object->graphql_exclude_connections as $connection_name ) {
1✔
267
                                unset( $connections[ lcfirst( $connection_name ) ] );
1✔
268
                        }
269
                }
270

271
                return $connections;
593✔
272
        }
273
        /**
274
         * Gets all the interfaces for the given Taxonomy.
275
         *
276
         * @param \WP_Taxonomy $tax_object Taxonomy.
277
         *
278
         * @return string[]
279
         */
280
        protected static function get_interfaces( WP_Taxonomy $tax_object ) {
593✔
281
                $interfaces = [ 'Node', 'TermNode', 'DatabaseIdentifier' ];
593✔
282

283
                if ( true === $tax_object->public ) {
593✔
284
                        $interfaces[] = 'UniformResourceIdentifiable';
593✔
285
                }
286

287
                if ( $tax_object->hierarchical ) {
593✔
288
                        $interfaces[] = 'HierarchicalTermNode';
593✔
289
                }
290

291
                if ( true === $tax_object->show_in_nav_menus ) {
593✔
292
                        $interfaces[] = 'MenuItemLinkable';
593✔
293
                }
294

295
                // Merge with interfaces set in register_taxonomy.
296
                if ( ! empty( $tax_object->graphql_interfaces ) ) {
593✔
297
                        $interfaces = array_merge( $interfaces, $tax_object->graphql_interfaces );
1✔
298
                }
299

300
                // Remove excluded interfaces.
301
                if ( ! empty( $tax_object->graphql_exclude_interfaces ) ) {
593✔
302
                        $interfaces = array_diff( $interfaces, $tax_object->graphql_exclude_interfaces );
1✔
303
                }
304

305
                return $interfaces;
593✔
306
        }
307

308
        /**
309
         * Registers common Taxonomy fields on schema type corresponding to provided Taxonomy object.
310
         *
311
         * @param \WP_Taxonomy $tax_object Taxonomy.
312
         *
313
         * @return array<string,array<string,mixed>>[]
314
         */
315
        protected static function get_fields( WP_Taxonomy $tax_object ) {
593✔
316
                $single_name = $tax_object->graphql_single_name;
593✔
317
                $fields      = [
593✔
318
                        $single_name . 'Id' => [
593✔
319
                                'type'              => 'Int',
593✔
320
                                'deprecationReason' => static function () {
593✔
321
                                        return __( 'Deprecated in favor of databaseId', 'wp-graphql' );
20✔
322
                                },
593✔
323
                                'description'       => static function () {
593✔
324
                                        return __( 'The id field matches the WP_Post->ID field.', 'wp-graphql' );
20✔
325
                                },
593✔
326
                                'resolve'           => static function ( Term $term ) {
593✔
327
                                        return absint( $term->databaseId );
28✔
328
                                },
593✔
329
                        ],
593✔
330
                ];
593✔
331

332
                // Merge with fields set in register_taxonomy.
333
                if ( ! empty( $tax_object->graphql_fields ) ) {
593✔
334
                        $fields = array_merge( $fields, $tax_object->graphql_fields );
2✔
335
                }
336

337
                // Remove excluded fields.
338
                if ( ! empty( $tax_object->graphql_exclude_fields ) ) {
593✔
339
                        foreach ( $tax_object->graphql_exclude_fields as $field_name ) {
1✔
340
                                unset( $fields[ $field_name ] );
1✔
341
                        }
342
                }
343

344
                return $fields;
593✔
345
        }
346
}
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

© 2025 Coveralls, Inc