*/ class TermObjectConnectionResolver extends AbstractConnectionResolver { /** * The name of the Taxonomy the resolver is intended to be used for * * @var array|string */ protected $taxonomy; /** * {@inheritDoc} * * @param mixed|array|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} */ protected function prepare_query_args( array $args ): array { $all_taxonomies = \WPGraphQL::get_allowed_taxonomies(); if ( ! is_array( $this->taxonomy ) ) { $taxonomy = ! empty( $this->taxonomy ) && in_array( $this->taxonomy, $all_taxonomies, true ) ? [ $this->taxonomy ] : $all_taxonomies; } else { $taxonomy = $this->taxonomy; } if ( ! empty( $args['where']['taxonomies'] ) ) { /** * Set the taxonomy for the $args */ $requested_taxonomies = $args['where']['taxonomies']; $taxonomy = array_intersect( $all_taxonomies, $requested_taxonomies ); } $query_args = [ 'taxonomy' => $taxonomy, ]; /** * Prepare for later use */ $last = ! empty( $args['last'] ) ? $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( $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'] = $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'; } /** * Filters the query args used by the connection. * * @param array $query_args array of query_args being passed to the * @param mixed $source source passed down from the resolve tree * @param array $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 * * @hookGroup connections * @since 0.0.6 */ $query_args = apply_filters( 'graphql_term_object_connection_query_args', $query_args, $this->source, $args, $this->context, $this->info ); return $query_args; } /** * {@inheritDoc} */ protected function query_class(): string { return \WP_Term_Query::class; } /** * {@inheritDoc} */ public function get_ids_from_query() { /** * @todo This is for b/c. We can just use $this->get_query(). */ $queried = isset( $this->query ) ? $this->query : $this->get_query(); /** @var array $ids */ $ids = ! empty( $queried->get_terms() ) ? $queried->get_terms() : []; // If we're going backwards, we need to reverse the array. $args = $this->get_args(); if ( ! empty( $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 */ public function sanitize_input_fields() { $arg_mapping = [ 'objectIds' => 'object_ids', 'hideEmpty' => 'hide_empty', 'excludeTree' => 'exclude_tree', // @todo remove in 3.0.0 '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', ]; $args = $this->get_args(); $where_args = ! empty( $args['where'] ) ? $args['where'] : null; // Only convert value if 'termTaxonomyId' isnt already set. // @todo Remove in 3.0.0 if ( ! empty( $where_args['termTaxonomId'] ) && 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 $query_args Array of mapped query args * @param array $where_args Array of query "where" args * @param array|string $taxonomy The name of the taxonomy * @param mixed $source The query results * @param array $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 * * @hookGroup connections * @since 0.0.5 */ $query_args = apply_filters( 'graphql_map_input_fields_to_get_terms', $query_args, $where_args, $this->taxonomy, $this->source, $args, $this->context, $this->info ); return ! empty( $query_args ) && is_array( $query_args ) ? $query_args : []; } /** * {@inheritDoc} */ protected function prepare_args( array $args ): array { 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 $args The GraphQL args passed to the resolver. * @param self $resolver Instance of the ConnectionResolver. * @param array $unfiltered_args Array of arguments input in the field as part of the GraphQL query. * * @hookGroup connections * @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; } }