-
Notifications
You must be signed in to change notification settings - Fork 442
/
TermObjectConnectionResolver.php
297 lines (257 loc) · 9.27 KB
/
TermObjectConnectionResolver.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
<?php
namespace WPGraphQL\Data\Connection;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL\AppContext;
use WPGraphQL\Utils\Utils;
/**
* Class TermObjectConnectionResolver
*
* @package WPGraphQL\Data\Connection
*/
class TermObjectConnectionResolver extends AbstractConnectionResolver {
/**
* {@inheritDoc}
*
* @var \WP_Term_Query
*/
protected $query;
/**
* The name of the Taxonomy the resolver is intended to be used for
*
* @var string
*/
protected $taxonomy;
/**
* {@inheritDoc}
*
* @param mixed|string|null $taxonomy The name of the Taxonomy the resolver is intended to be used for.
*/
public function __construct( $source, array $args, AppContext $context, ResolveInfo $info, $taxonomy = null ) {
$this->taxonomy = $taxonomy;
parent::__construct( $source, $args, $context, $info );
}
/**
* {@inheritDoc}
*/
public function get_query_args() {
$all_taxonomies = \WPGraphQL::get_allowed_taxonomies();
$taxonomy = ! empty( $this->taxonomy ) && in_array( $this->taxonomy, $all_taxonomies, true ) ? [ $this->taxonomy ] : $all_taxonomies;
if ( ! empty( $this->args['where']['taxonomies'] ) ) {
/**
* Set the taxonomy for the $args
*/
$requested_taxonomies = $this->args['where']['taxonomies'];
$taxonomy = array_intersect( $all_taxonomies, $requested_taxonomies );
}
$query_args = [
'taxonomy' => $taxonomy,
];
/**
* Prepare for later use
*/
$last = ! empty( $this->args['last'] ) ? $this->args['last'] : null;
/**
* Set hide_empty as false by default
*/
$query_args['hide_empty'] = false;
/**
* Set the number, ensuring it doesn't exceed the amount set as the $max_query_amount
*/
$query_args['number'] = $this->get_query_amount() + 1;
/**
* Don't calculate the total rows, it's not needed and can be expensive
*/
$query_args['count'] = false;
/**
* Take any of the $args that were part of the GraphQL query and map their
* GraphQL names to the WP_Term_Query names to be used in the WP_Term_Query
*
* @since 0.0.5
*/
$input_fields = [];
if ( ! empty( $this->args['where'] ) ) {
$input_fields = $this->sanitize_input_fields();
}
/**
* Merge the default $query_args with the $args that were entered
* in the query.
*
* @since 0.0.5
*/
if ( ! empty( $input_fields ) ) {
$query_args = array_merge( $query_args, $input_fields );
}
$query_args['graphql_cursor_compare'] = ( ! empty( $last ) ) ? '>' : '<';
$query_args['graphql_after_cursor'] = $this->get_after_offset();
$query_args['graphql_before_cursor'] = $this->get_before_offset();
/**
* Pass the graphql $args to the WP_Query
*/
$query_args['graphql_args'] = $this->args;
/**
* NOTE: We query for JUST the IDs here as deferred resolution of the nodes gets the full
* object from the cache or a follow-up request for the full object if it's not cached.
*/
$query_args['fields'] = 'ids';
/**
* If there's no orderby params in the inputArgs, default to ordering by name.
*/
if ( empty( $query_args['orderby'] ) ) {
$query_args['orderby'] = 'name';
}
/**
* If orderby params set but not order, default to ASC if going forward, DESC if going backward.
*/
if ( empty( $query_args['order'] ) && 'name' === $query_args['orderby'] ) {
$query_args['order'] = ! empty( $last ) ? 'DESC' : 'ASC';
} elseif ( empty( $query_args['order'] ) ) {
$query_args['order'] = ! empty( $last ) ? 'ASC' : 'DESC';
}
/**
* Filter the query_args that should be applied to the query. This filter is applied AFTER the input args from
* the GraphQL Query have been applied and has the potential to override the GraphQL Query Input Args.
*
* @param array<string,mixed> $query_args array of query_args being passed to the
* @param mixed $source source passed down from the resolve tree
* @param array<string,mixed> $args array of arguments input in the field as part of the GraphQL query
* @param \WPGraphQL\AppContext $context object passed down the resolve tree
* @param \GraphQL\Type\Definition\ResolveInfo $info info about fields passed down the resolve tree
*
* @since 0.0.6
*/
$query_args = apply_filters( 'graphql_term_object_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info );
return $query_args;
}
/**
* Return an instance of WP_Term_Query with the args mapped to the query
*
* @return \WP_Term_Query
* @throws \Exception
*/
public function get_query() {
return new \WP_Term_Query( $this->query_args );
}
/**
* {@inheritDoc}
*/
public function get_ids_from_query() {
/** @var string[] $ids */
$ids = ! empty( $this->query->get_terms() ) ? $this->query->get_terms() : [];
// If we're going backwards, we need to reverse the array.
if ( ! empty( $this->args['last'] ) ) {
$ids = array_reverse( $ids );
}
return $ids;
}
/**
* {@inheritDoc}
*/
protected function loader_name(): string {
return 'term';
}
/**
* This maps the GraphQL "friendly" args to get_terms $args.
* There's probably a cleaner/more dynamic way to approach this, but this was quick. I'd be down
* to explore more dynamic ways to map this, but for now this gets the job done.
*
* @since 0.0.5
* @return array<string,mixed>
*/
public function sanitize_input_fields() {
$arg_mapping = [
'objectIds' => 'object_ids',
'hideEmpty' => 'hide_empty',
'excludeTree' => 'exclude_tree',
'termTaxonomId' => 'term_taxonomy_id',
'termTaxonomyId' => 'term_taxonomy_id',
'nameLike' => 'name__like',
'descriptionLike' => 'description__like',
'padCounts' => 'pad_counts',
'childOf' => 'child_of',
'cacheDomain' => 'cache_domain',
'updateTermMetaCache' => 'update_term_meta_cache',
'taxonomies' => 'taxonomy',
];
$where_args = ! empty( $this->args['where'] ) ? $this->args['where'] : null;
// Deprecate usage of 'termTaxonomId'.
if ( ! empty( $where_args['termTaxonomId'] ) ) {
_deprecated_argument( 'where.termTaxonomId', '1.11.0', 'The `termTaxonomId` where arg is deprecated. Use `termTaxonomyId` instead.' );
// Only convert value if 'termTaxonomyId' isnt already set.
if ( empty( $where_args['termTaxonomyId'] ) ) {
$where_args['termTaxonomyId'] = $where_args['termTaxonomId'];
}
}
/**
* Map and sanitize the input args to the WP_Term_Query compatible args
*/
$query_args = Utils::map_input( $where_args, $arg_mapping );
/**
* Filter the input fields
* This allows plugins/themes to hook in and alter what $args should be allowed to be passed
* from a GraphQL Query to the get_terms query
*
* @param array<string,mixed> $query_args Array of mapped query args
* @param array<string,mixed> $where_args Array of query "where" args
* @param string $taxonomy The name of the taxonomy
* @param mixed $source The query results
* @param array<string,mixed> $all_args All of the query arguments (not just the "where" args)
* @param \WPGraphQL\AppContext $context The AppContext object
* @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo object
*
* @since 0.0.5
*/
$query_args = apply_filters( 'graphql_map_input_fields_to_get_terms', $query_args, $where_args, $this->taxonomy, $this->source, $this->args, $this->context, $this->info );
return ! empty( $query_args ) && is_array( $query_args ) ? $query_args : [];
}
/**
* {@inheritDoc}
*/
public function get_args(): array {
$args = $this->get_unfiltered_args();
if ( ! empty( $args['where'] ) ) {
// Ensure all IDs are converted to database IDs.
foreach ( $args['where'] as $input_key => $input_value ) {
if ( empty( $input_value ) ) {
continue;
}
switch ( $input_key ) {
case 'exclude':
case 'excludeTree':
case 'include':
case 'objectIds':
case 'termTaxonomId':
case 'termTaxonomyId':
if ( is_array( $input_value ) ) {
$args['where'][ $input_key ] = array_map(
static function ( $id ) {
return Utils::get_database_id_from_id( $id );
},
$input_value
);
break;
}
$args['where'][ $input_key ] = Utils::get_database_id_from_id( $input_value );
break;
}
}
}
/**
* Filters the GraphQL args before they are used in get_query_args().
*
* @param array<string,mixed> $args The GraphQL args passed to the resolver.
* @param \WPGraphQL\Data\Connection\TermObjectConnectionResolver $connection_resolver Instance of the ConnectionResolver
* @param array<string,mixed> $unfiltered_args Array of arguments input in the field as part of the GraphQL query.
*
* @since 1.11.0
*/
return apply_filters( 'graphql_term_object_connection_args', $args, $this, $this->get_unfiltered_args() );
}
/**
* {@inheritDoc}
*
* @param int $offset The ID of the node used in the cursor for offset.
*/
public function is_valid_offset( $offset ) {
return get_term( absint( $offset ) ) instanceof \WP_Term;
}
}