File: /var/www/html/wp-content/plugins/wp-graphql/src/Registry/Utils/TermObject.php
<?php
namespace WPGraphQL\Registry\Utils;
use GraphQL\Type\Definition\ResolveInfo;
use WPGraphQL;
use WPGraphQL\AppContext;
use WPGraphQL\Data\Connection\PostObjectConnectionResolver;
use WPGraphQL\Data\Connection\TaxonomyConnectionResolver;
use WPGraphQL\Data\Connection\TermObjectConnectionResolver;
use WPGraphQL\Model\Term;
use WPGraphQL\Type\Connection\PostObjects;
use WPGraphQL\Type\Connection\TermObjects;
use WP_Taxonomy;
/**
* Class TermObjectType
*
* @package WPGraphQL\Data
* @since 1.12.0
*/
class TermObject {
/**
* Registers a taxonomy type to the schema as either a GraphQL object, interface, or union.
*
* @param \WP_Taxonomy $tax_object Taxonomy.
*
* @return void
* @throws \Exception
*/
public static function register_types( WP_Taxonomy $tax_object ) {
$single_name = $tax_object->graphql_single_name;
$config = [
'description' => static function () use ( $tax_object, $single_name ) {
return ! empty( $tax_object->graphql_description )
? $tax_object->graphql_description
: ( ! empty( $tax_object->description )
? $tax_object->description
/* translators: taxonomy object singular name w/ description */
: sprintf( __( 'The %s type', 'wp-graphql' ), $single_name )
);
},
'connections' => static::get_connections( $tax_object ),
'interfaces' => static::get_interfaces( $tax_object ),
'fields' => static::get_fields( $tax_object ),
'model' => Term::class,
];
// Register as GraphQL objects.
if ( 'object' === $tax_object->graphql_kind ) {
register_graphql_object_type( $single_name, $config );
return;
}
/**
* Register as GraphQL interfaces or unions.
*
* It's assumed that the types used in `resolveType` have already been registered to the schema.
*/
// Bail early if graphql_resolve_type isnt a vallable callback.
if ( empty( $tax_object->graphql_resolve_type ) || ! is_callable( $tax_object->graphql_resolve_type ) ) {
graphql_debug(
sprintf(
// translators: %1$s is the term object singular name, %2$s is the graphql kind.
__( '%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' ),
$single_name,
$tax_object->graphql_kind
),
[ 'registered_taxonomy_object' => $tax_object ]
);
return;
}
$config['resolveType'] = $tax_object->graphql_resolve_type;
if ( 'interface' === $tax_object->graphql_kind ) {
register_graphql_interface_type( $single_name, $config );
return;
} elseif ( 'union' === $tax_object->graphql_kind ) {
// Bail early if graphql_union_types is not defined.
if ( empty( $tax_object->graphql_union_types ) || ! is_array( $tax_object->graphql_union_types ) ) {
graphql_debug(
__( 'Registering a taxonomy with "graphql_kind" => "union" requires "graphql_union_types" to be a valid array of possible GraphQL type names.', 'wp-graphql' ),
[ 'registered_taxonomy_object' => $tax_object ]
);
return;
}
// Set the possible types for the union.
$config['typeNames'] = $tax_object->graphql_union_types;
register_graphql_union_type( $single_name, $config );
}
}
/**
* Gets all the connections for the given post type.
*
* @param \WP_Taxonomy $tax_object
*
* @return array<string,array<string,mixed>>
*/
protected static function get_connections( WP_Taxonomy $tax_object ) {
$connections = [];
// Taxonomy.
// @todo connection move to TermNode (breaking).
$connections['taxonomy'] = [
'toType' => 'Taxonomy',
'oneToOne' => true,
'resolve' => static function ( Term $source, $args, $context, $info ) {
if ( empty( $source->taxonomyName ) ) {
return null;
}
$resolver = new TaxonomyConnectionResolver( $source, $args, $context, $info );
$resolver->set_query_arg( 'name', $source->taxonomyName );
return $resolver->one_to_one()->get_connection();
},
];
if ( true === $tax_object->hierarchical ) {
// Children.
$connections['children'] = [
'toType' => $tax_object->graphql_single_name,
'description' => static function () use ( $tax_object ) {
return sprintf(
// translators: %1$s is the term object singular name, %2$s is the term object plural name.
__( 'Connection between the %1$s type and its children %2$s.', 'wp-graphql' ),
$tax_object->graphql_single_name,
$tax_object->graphql_plural_name
);
},
'connectionArgs' => TermObjects::get_connection_args(),
'queryClass' => 'WP_Term_Query',
'resolve' => static function ( Term $term, $args, AppContext $context, $info ) {
$resolver = new TermObjectConnectionResolver( $term, $args, $context, $info );
$resolver->set_query_arg( 'parent', $term->databaseId );
return $resolver->get_connection();
},
];
// Parent.
$connections['parent'] = [
'toType' => $tax_object->graphql_single_name,
'description' => static function () use ( $tax_object ) {
return sprintf(
// translators: %s is the term object singular name.
__( 'Connection between the %1$s type and its parent %1$s.', 'wp-graphql' ),
$tax_object->graphql_single_name
);
},
'connectionTypeName' => ucfirst( $tax_object->graphql_single_name ) . 'ToParent' . ucfirst( $tax_object->graphql_single_name ) . 'Connection',
'oneToOne' => true,
'resolve' => static function ( Term $term, $args, AppContext $context, $info ) use ( $tax_object ) {
if ( ! isset( $term->parentDatabaseId ) || empty( $term->parentDatabaseId ) ) {
return null;
}
$resolver = new TermObjectConnectionResolver( $term, $args, $context, $info, $tax_object->name );
$resolver->set_query_arg( 'include', $term->parentDatabaseId );
return $resolver->one_to_one()->get_connection();
},
];
// Ancestors.
$connections['ancestors'] = [
'toType' => $tax_object->graphql_single_name,
'description' => static function () {
return __( 'The ancestors of the node. Default ordered as lowest (closest to the child) to highest (closest to the root).', 'wp-graphql' );
},
'connectionTypeName' => ucfirst( $tax_object->graphql_single_name ) . 'ToAncestors' . ucfirst( $tax_object->graphql_single_name ) . 'Connection',
'resolve' => static function ( Term $term, $args, AppContext $context, $info ) use ( $tax_object ) {
$ancestor_ids = isset( $term->databaseId ) ? get_ancestors( $term->databaseId, (string) $term->taxonomyName, 'taxonomy' ) : null;
if ( empty( $ancestor_ids ) ) {
return null;
}
$resolver = new TermObjectConnectionResolver( $term, $args, $context, $info, $tax_object->name );
$resolver->set_query_arg( 'include', $ancestor_ids );
$resolver->set_query_arg( 'orderby', 'include' );
return $resolver->get_connection();
},
];
}
// Used to ensure contentNodes connection doesn't get registered multiple times.
$already_registered = false;
$allowed_post_types = WPGraphQL::get_allowed_post_types( 'objects' );
foreach ( $allowed_post_types as $post_type_object ) {
if ( ! in_array( $tax_object->name, get_object_taxonomies( $post_type_object->name ), true ) ) {
continue;
}
// ContentNodes.
if ( ! $already_registered ) {
$connections['contentNodes'] = PostObjects::get_connection_config(
$tax_object,
[
'toType' => 'ContentNode',
'resolve' => static function ( Term $term, $args, $context, $info ) {
$resolver = new PostObjectConnectionResolver( $term, $args, $context, $info, 'any' );
$current_args = $resolver->get_query_args();
$tax_query = $current_args['tax_query'] ?? [];
$tax_query[] = [
'taxonomy' => $term->taxonomyName,
'terms' => [ $term->databaseId ],
'field' => 'term_id',
'include_children' => false,
];
$resolver->set_query_arg( 'tax_query', $tax_query );
return $resolver->get_connection();
},
]
);
// We won't need to register this connection again.
$already_registered = true;
}
// PostObjects.
$connections[ $post_type_object->graphql_plural_name ] = PostObjects::get_connection_config(
$post_type_object,
[
'toType' => $post_type_object->graphql_single_name,
'queryClass' => 'WP_Query',
'resolve' => static function ( Term $term, $args, AppContext $context, ResolveInfo $info ) use ( $post_type_object ) {
$resolver = new PostObjectConnectionResolver( $term, $args, $context, $info, $post_type_object->name );
$current_args = $resolver->get_query_args();
$tax_query = $current_args['tax_query'] ?? [];
$tax_query[] = [
'taxonomy' => $term->taxonomyName,
'terms' => [ $term->databaseId ],
'field' => 'term_id',
'include_children' => false,
];
$resolver->set_query_arg( 'tax_query', $tax_query );
return $resolver->get_connection();
},
]
);
}
// Merge with connections set in register_taxonomy.
if ( ! empty( $tax_object->graphql_connections ) ) {
$connections = array_merge( $connections, $tax_object->graphql_connections );
}
// Remove excluded connections.
if ( ! empty( $tax_object->graphql_exclude_connections ) ) {
foreach ( $tax_object->graphql_exclude_connections as $connection_name ) {
unset( $connections[ lcfirst( $connection_name ) ] );
}
}
return $connections;
}
/**
* Gets all the interfaces for the given Taxonomy.
*
* @param \WP_Taxonomy $tax_object Taxonomy.
*
* @return string[]
*/
protected static function get_interfaces( WP_Taxonomy $tax_object ) {
$interfaces = [ 'Node', 'TermNode', 'DatabaseIdentifier' ];
if ( true === $tax_object->public ) {
$interfaces[] = 'UniformResourceIdentifiable';
}
if ( $tax_object->hierarchical ) {
$interfaces[] = 'HierarchicalTermNode';
}
if ( true === $tax_object->show_in_nav_menus ) {
$interfaces[] = 'MenuItemLinkable';
}
// Merge with interfaces set in register_taxonomy.
if ( ! empty( $tax_object->graphql_interfaces ) ) {
$interfaces = array_merge( $interfaces, $tax_object->graphql_interfaces );
}
// Remove excluded interfaces.
if ( ! empty( $tax_object->graphql_exclude_interfaces ) ) {
$interfaces = array_diff( $interfaces, $tax_object->graphql_exclude_interfaces );
}
return $interfaces;
}
/**
* Registers common Taxonomy fields on schema type corresponding to provided Taxonomy object.
*
* @param \WP_Taxonomy $tax_object Taxonomy.
*
* @return array<string,array<string,mixed>>[]
*/
protected static function get_fields( WP_Taxonomy $tax_object ) {
$single_name = $tax_object->graphql_single_name;
$fields = [
$single_name . 'Id' => [
'type' => 'Int',
'deprecationReason' => static function () {
return __( 'Deprecated in favor of databaseId', 'wp-graphql' );
},
'description' => static function () {
return __( 'The id field matches the WP_Post->ID field.', 'wp-graphql' );
},
'resolve' => static function ( Term $term ) {
return absint( $term->databaseId );
},
],
];
// Merge with fields set in register_taxonomy.
if ( ! empty( $tax_object->graphql_fields ) ) {
$fields = array_merge( $fields, $tax_object->graphql_fields );
}
// Remove excluded fields.
if ( ! empty( $tax_object->graphql_exclude_fields ) ) {
foreach ( $tax_object->graphql_exclude_fields as $field_name ) {
unset( $fields[ $field_name ] );
}
}
return $fields;
}
}