PluginProbe
WPGraphQL / trunk
WPGraphQL vtrunk
2.22.3 2.22.2 2.22.1 2.22.0 2.21.1 2.21.0 2.20.0 2.19.0 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.1 2.14.0 2.13.0 2.2.0 2.3.0 2.3.3 2.3.6 2.3.8 2.5.0 2.5.1 2.5.2 2.5.3 All 177 releases
wp-graphql / src / Data / Connection / TermObjectConnectionResolver.php

TermObjectConnectionResolver.php in WPGraphQL trunk, at src/Data/Connection/TermObjectConnectionResolver.php

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