Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG PHP_VERSION=7.4
ARG WORDPRESS_VERSION=5.9.3
ARG PHP_VERSION=8.0
ARG WORDPRESS_VERSION=6.2

FROM wordpress:${WORDPRESS_VERSION}-php${PHP_VERSION}-apache

Expand Down
126 changes: 75 additions & 51 deletions src/filter-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ class FilterQuery {
*/
public function add_hooks(): void {
add_action( 'graphql_register_types', [ $this, 'extend_wp_graphql_fields' ] );

add_filter( 'graphql_RootQuery_fields', [ $this, 'apply_filters_input' ], 20 );
add_filter( 'graphql_connection_query_args', [ $this, 'apply_recursive_filter_resolver' ], 10, 2 );
}
Expand All @@ -56,7 +55,7 @@ public function apply_filters_input( array $fields ): array {
$args = is_array( $fields[ $post_type['plural_name'] ]['args'] ) ? $fields[ $post_type['plural_name'] ]['args'] : [];

$args['filter'] = [
'type' => 'TaxonomyFilter',
'type' => ucfirst( $post_type['name'] ) . 'TaxonomyFilter',
'description' => __( 'Filtering Queried Results By Taxonomy Objects', 'wp-graphql-filter-query' ),
];

Expand All @@ -81,13 +80,6 @@ public function apply_filters_input( array $fields ): array {
'notLike' => 'NOT IN',
);

/**
* $taxonomy_keys.
*
* @var array
*/
public $taxonomy_keys = [ 'tag', 'category' ];

/**
* $relation_keys.
*
Expand All @@ -98,15 +90,16 @@ public function apply_filters_input( array $fields ): array {
/**
* Check if operator is like or notLike
*
* @param array $filter_obj A Filter object, for wpQuery access, to build upon within each recursive call.
* @param int $depth A depth-counter to track recusrive call depth.
* @param string $post_type the current post type.
* @param array $filter_obj A Filter object, for wpQuery access, to build upon within each recursive call.
* @param int $depth A depth-counter to track recursive call depth.
*
* @throws FilterException Throws max nested filter depth exception, caught by wpgraphql response.
* @throws FilterException Throws and/or not allowed as siblings exception, caught by wpgraphql response.
* @throws FilterException Throws empty relation (and/or) exception, caught by wpgraphql response.
* @throws FilterException Throws max nested filter depth exception, caught by WPGraphQL response.
* @throws FilterException Throws and/or not allowed as siblings exception, caught by WPGraphQL response.
* @throws FilterException Throws empty relation (and/or) exception, caught by WPGraphQL response.
* @return array
*/
private function resolve_taxonomy( array $filter_obj, int $depth ): array {
private function resolve_taxonomy( string $post_type, array $filter_obj, int $depth ): array {
if ( $depth > $this->max_nesting_depth ) {
throw new FilterException( 'The Filter\'s relation allowable depth nesting has been exceeded. Please reduce to allowable (' . $this->max_nesting_depth . ') depth to proceed' );
} elseif ( array_key_exists( 'and', $filter_obj ) && array_key_exists( 'or', $filter_obj ) ) {
Expand All @@ -115,24 +108,37 @@ private function resolve_taxonomy( array $filter_obj, int $depth ): array {

$temp_query = [];
foreach ( $filter_obj as $root_obj_key => $value ) {
if ( in_array( $root_obj_key, $this->taxonomy_keys, true ) ) {
if ( in_array( $root_obj_key, $this->relation_keys, true ) ) {
$nested_obj_array = $value;
$wp_query_array = [];

if ( count( $nested_obj_array ) === 0 ) {
throw new FilterException( 'The Filter relation array specified has no children. Please remove the relation key or add one or more appropriate objects to proceed.' );
}
foreach ( $nested_obj_array as $nested_obj_index => $nested_obj_value ) {
$wp_query_array[ $nested_obj_index ] = $this->resolve_taxonomy( $post_type, $nested_obj_value, ++$depth );
$wp_query_array[ $nested_obj_index ]['relation'] = 'AND';
}
$wp_query_array['relation'] = strtoupper( $root_obj_key );
$temp_query[] = $wp_query_array;
} else {
$attribute_array = $value;
foreach ( $attribute_array as $field_key => $field_kvp ) {
foreach ( $field_kvp as $operator => $terms ) {
$mapped_operator = $this->operator_mappings[ $operator ] ?? 'IN';
$is_like_operator = $this->is_like_operator( $operator );
$taxonomy = $root_obj_key === 'tag' ? 'post_tag' : 'category';
$taxonomy_slug = $this->get_taxonomy_slug( $post_type, $root_obj_key );

$terms = ! $is_like_operator ? $terms : get_terms(
[
'taxonomy' => $taxonomy,
'taxonomy' => $taxonomy_slug,
'fields' => 'ids',
'name__like' => esc_attr( $terms ),
]
);

$result = [
'taxonomy' => $taxonomy,
'taxonomy' => $taxonomy_slug,
'field' => ( $field_key === 'id' ) || $is_like_operator ? 'term_id' : 'name',
'terms' => $terms,
'operator' => $mapped_operator,
Expand All @@ -141,24 +147,31 @@ private function resolve_taxonomy( array $filter_obj, int $depth ): array {
$temp_query[] = $result;
}
}
} elseif ( in_array( $root_obj_key, $this->relation_keys, true ) ) {
$nested_obj_array = $value;
$wp_query_array = [];

if ( count( $nested_obj_array ) === 0 ) {
throw new FilterException( 'The Filter relation array specified has no children. Please remove the relation key or add one or more appropriate objects to proceed.' );
}
foreach ( $nested_obj_array as $nested_obj_index => $nested_obj_value ) {
$wp_query_array[ $nested_obj_index ] = $this->resolve_taxonomy( $nested_obj_value, ++$depth );
$wp_query_array[ $nested_obj_index ]['relation'] = 'AND';
}
$wp_query_array['relation'] = strtoupper( $root_obj_key );
$temp_query[] = $wp_query_array;
}
}
return $temp_query;
}

/**
* Retrieves the slug of the taxonomy associated with the specified post type and GraphQL singular name.
*
* @param string $post_type The post type for which to retrieve the taxonomy slug.
* @param string $graphql_singular_name The GraphQL singular name of the taxonomy.
* @return string|false The slug of the associated taxonomy, or false if no taxonomy is found.
*/
private function get_taxonomy_slug( string $post_type, string $graphql_singular_name ) {
$taxonomies = get_object_taxonomies(
$post_type,
'objects'
);

foreach ( $taxonomies as $taxonomy ) {
if ( strtolower( $taxonomy->graphql_single_name ) === strtolower( $graphql_singular_name ) ) {
return $taxonomy->name;
}
}
}

/**
* Apply facet filters using graphql_connection_query_args filter hook.
*
Expand All @@ -177,7 +190,8 @@ public function apply_recursive_filter_resolver( array $query_args, AbstractConn

$filter_args_root = $args['filter'];

$query_args['tax_query'][] = $this->resolve_taxonomy( $filter_args_root, 0, [] );
// TODO: handle multiple post types here.
$query_args['tax_query'][] = $this->resolve_taxonomy( $query_args['post_type'][0], $filter_args_root, 0, [] );

self::$query_args = $query_args;
return $query_args;
Expand Down Expand Up @@ -272,30 +286,40 @@ public function extend_wp_graphql_fields() {
]
);

register_graphql_input_type(
'TaxonomyFilter',
[
foreach ( filter_query_get_supported_post_types() as $post_type ) {
$filter_obj_name = ucfirst( $post_type['name'] ) . 'TaxonomyFilter';

$config = [
'description' => __( 'Taxonomies Where Filtering Supported', 'wp-graphql-filter-query' ),
'fields' => [
'tag' => [
'type' => 'TaxonomyFilterFields',
'description' => __( 'Tags Object Fields Allowable For Filtering', 'wp-graphql-filter-query' ),
],
'category' => [
'type' => 'TaxonomyFilterFields',
'description' => __( 'Category Object Fields Allowable For Filtering', 'wp-graphql-filter-query' ),
],
'and' => [
'type' => [ 'list_of' => 'TaxonomyFilter' ],
'and' => [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you h8 linting? 😛

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was the linter that complained :D

'type' => [ 'list_of' => $filter_obj_name ],
'description' => __( '\'AND\' Array of Taxonomy Objects Allowable For Filtering', 'wp-graphql-filter-query' ),
],
'or' => [
'type' => [ 'list_of' => 'TaxonomyFilter' ],
'description' => __( '\'OR\' Array of Taxonomy Objects Allowable For Filterin', 'wp-graphql-filter-query' ),
'or' => [
'type' => [ 'list_of' => $filter_obj_name ],
'description' => __( '\'OR\' Array of Taxonomy Objects Allowable For Filtering', 'wp-graphql-filter-query' ),
],
],
]
);
];

$taxonomies = get_object_taxonomies(
$post_type['name'],
'objects'
);

foreach ( $taxonomies as $taxonomy ) {
$config['fields'][ $taxonomy->graphql_single_name ] = [
'type' => 'TaxonomyFilterFields',
'description' => __( 'Object Fields Allowable For Filtering', 'wp-graphql-filter-query' ),
];
}

register_graphql_input_type(
$filter_obj_name,
$config
);
}
}

/**
Expand Down
102 changes: 34 additions & 68 deletions tests/src/aggregate-query.test.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,40 @@ protected function setUp(): void {
'graphql_plural_name' => 'zombies',
)
);

// Set up the labels for the custom taxonomy.
$labels = array(
'name' => __( 'Sports', 'textdomain' ),
'singular_name' => __( 'Sport', 'textdomain' ),
'search_items' => __( 'Search Sports', 'textdomain' ),
'all_items' => __( 'All Sports', 'textdomain' ),
'parent_item' => __( 'Parent Sport', 'textdomain' ),
'parent_item_colon' => __( 'Parent Sport:', 'textdomain' ),
'edit_item' => __( 'Edit Sport', 'textdomain' ),
'update_item' => __( 'Update Sport', 'textdomain' ),
'add_new_item' => __( 'Add New Sport', 'textdomain' ),
'new_item_name' => __( 'New Sport Name', 'textdomain' ),
'menu_name' => __( 'Sports', 'textdomain' ),
);

// Set up the arguments for the custom taxonomy.
$args = array(
'labels' => $labels,
'hierarchical' => true,
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'sport' ),
'show_in_graphql' => true,
'graphql_single_name' => 'sport',
'graphql_plural_name' => 'sports',

);

// Register the custom taxonomy.
register_taxonomy( 'sport', 'zombie', $args );
}

public function data_for_schema_exists_for_aggregations(): array {
Expand Down Expand Up @@ -1193,74 +1227,6 @@ public function filter_aggregations_data_provider(): array {
}',
'{"data": { "posts": {"aggregations" : { "tags" : [] }}}}',
],
'pages_accept_valid_tax_filter_args' => [
'query {
pages(
filter: {
category: {
id: {
eq: 10
},
name: {
eq: "foo"
}
},
tag: {
name: {
in: ["foo", "bar"],
like: "tst"
}
}
}
) {
aggregations {
tags {
key
count
},
categories {
key
count
}
}
}
}',
'{"data": { "pages": {"aggregations" : { "tags" : [], "categories" : [] }}}}',
],
'zombies_accept_valid_tax_filter_args' => [
'query {
zombies(
filter: {
category: {
id: {
eq: 10
},
name: {
eq: "foo"
}
},
tag: {
name: {
in: ["foo", "bar"],
like: "tst"
}
}
}
) {
aggregations {
tags {
key
count
},
categories {
key
count
}
}
}
}',
'{"data": { "zombies": {"aggregations" : { "tags" : [], "categories" : [] }}}}',
],
];
}
}
Loading