Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita10658b

Browse files
feat: 🎸 ORN-1185 Add Filter AND/OR relation support, for nested taxonomy filtering (#19)
* feat: 🎸 Add flat filter behavior with AND/OR operatorThis is not PR-ready, and has MCs, Lint errs, etc✅ Closes: ORN-1185* test: add some tests for or / and behavior* test: update test names* refactor: return void for setupBeforeClass* feat: 🎸 Recursive filter and/or fn with limit depth of 10Recursive filter schema def & filter to recursive fn for wpQuerybuilding. Testsed successfully so far to a depth of 3 but yet to belinted or unit-tested* refactor: 💡 Refactor depth nesting limit for filter argsJust throwing a null when occurs now to break the recursive loop* refactor: 💡 Added catch for empty relation array & fix linting* refactor: 💡 Re-added like operator special behavior to filtersHad removed earlier as it appeared to be buggy but seems like my issueas everything looks fine now* refactor: 💡 Fixed/expanded broken nested filter unit tests* refactor: made sure to check if filter arg exists* refactor: 💡 Replace nulls at error-state filtering w/throwablesReplaced the return nulls at the following with throwable Exceptions,natively picked up by WPGrapqhQL response object* docs: ✏️ Prepend@throws Exception fn docs header comments w '\'* fix: 🐛 Rework problematic function for SQL Nested Limit RemovalAs per Dimi suggestion.* refactor: 💡 Fix for all PR Suggestion, except Page & Exceptions* test: update tests to include 20 more posts (#24)* test: add test for errors* refactor: 💡 Refactored filter err tests & Err/Exceptions & Lint* refactor: 💡 Fix comment typo* refactor: 💡 Removed leftover throws error annotation in commentCo-authored-by: Shane Daly <shane.daly@wpengine.com>
1 parentc683432 commita10658b

File tree

5 files changed

+615
-54
lines changed

5 files changed

+615
-54
lines changed

‎src/aggregate-query.php‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,10 @@ private function remove_sql_group_by( string $sql ): string {
142142
* @return string
143143
*/
144144
privatefunctionremove_sql_limit(string$sql ):string {
145-
$sql_order_by =$this->clause_to_be_modified($sql,'LIMIT',"\n\t\t" );
145+
preg_match("#LIMIT(.*?)$#s",$sql,$matches );
146+
$limit =$matches[0] ??'';
146147

147-
returnstr_replace($sql_order_by ."\n\t\t",'',$sql );
148+
returnstr_replace($limit,'',$sql );
148149
}
149150

150151
/**

‎src/filter-exception.php‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
/**
3+
* Filter Query extension for WP-GraphQL
4+
*
5+
* @package WPGraphqlFilterQuery
6+
*/
7+
8+
namespaceWPGraphQLFilterQuery;
9+
10+
use \GraphQL\Error\ClientAware;
11+
12+
/**
13+
* Custom error exception.
14+
*/
15+
class FilterExceptionextends \Exceptionimplements ClientAware {
16+
/**
17+
* Relay ClientAware fn.
18+
*
19+
* @return bool isSafe from ClientAware.
20+
*/
21+
publicfunctionisClientSafe() {
22+
returntrue;
23+
}
24+
25+
/**
26+
* Relay ClientAware fn.
27+
*
28+
* @return string category from ClientAware.
29+
*/
30+
publicfunctiongetCategory() {
31+
return'wp-graphql-filter-query plugin';
32+
}
33+
}

‎src/filter-query.php‎

Lines changed: 114 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function __construct() {
2828
add_action('graphql_register_types', [$this,'extend_wp_graphql_fields' ] );
2929

3030
add_filter('graphql_RootQuery_fields', [$this,'apply_filters_input' ],20 );
31-
add_filter('graphql_connection_query_args', [$this,'apply_filter_resolver' ],10,2 );
31+
add_filter('graphql_connection_query_args', [$this,'apply_recursive_filter_resolver' ],10,2 );
3232
}
3333

3434
/**
@@ -57,66 +57,127 @@ public function apply_filters_input( array $fields ): array {
5757
return$fields;
5858
}
5959

60+
/**
61+
* $operator_mappings.
62+
*
63+
* @var array
64+
*/
65+
public$operator_mappings =array(
66+
'in' =>'IN',
67+
'notIn' =>'NOT IN',
68+
'eq' =>'IN',
69+
'notEq' =>'NOT IN',
70+
'like' =>'IN',
71+
'notLike' =>'NOT IN',
72+
);
73+
74+
/**
75+
* $taxonomy_keys.
76+
*
77+
* @var array
78+
*/
79+
public$taxonomy_keys = ['tag','category' ];
80+
81+
/**
82+
* $relation_keys.
83+
*
84+
* @var array
85+
*/
86+
public$relation_keys = ['and','or' ];
87+
88+
/**
89+
* $max_nesting_depth.
90+
*
91+
* @var int
92+
*/
93+
public$max_nesting_depth =10;
94+
95+
/**
96+
* Check if operator is like or notLike
97+
*
98+
* @param array $filter_obj A Filter object, for wpQuery access, to build upon within each recursive call.
99+
* @param int $depth A depth-counter to track recusrive call depth.
100+
*
101+
* @throws FilterException Throws max nested filter depth exception, caught by wpgraphql response.
102+
* @throws FilterException Throws and/or not allowed as siblings exception, caught by wpgraphql response.
103+
* @throws FilterException Throws empty relation (and/or) exception, caught by wpgraphql response.
104+
* @return array
105+
*/
106+
privatefunctionresolve_taxonomy(array$filter_obj,int$depth ):array {
107+
if ($depth >$this->max_nesting_depth ) {
108+
thrownewFilterException('The Filter\'s relation allowable depth nesting has been exceeded. Please reduce to allowable (10) depth to proceed' );
109+
}elseif (array_key_exists('and',$filter_obj ) &&array_key_exists('or',$filter_obj ) ) {
110+
thrownewFilterException('A Filter can only accept one of an\'and\' or\'or\' child relation as an immediate child.' );
111+
}
112+
113+
$temp_query = [];
114+
foreach ($filter_objas$root_obj_key =>$value ) {
115+
if (in_array($root_obj_key,$this->taxonomy_keys,true ) ) {
116+
$attribute_array =$value;
117+
foreach ($attribute_arrayas$field_key =>$field_kvp ) {
118+
foreach ($field_kvpas$operator =>$terms ) {
119+
$mapped_operator =$this->operator_mappings[$operator ] ??'IN';
120+
$is_like_operator =$this->is_like_operator($operator );
121+
$taxonomy =$root_obj_key ==='tag' ?'post_tag' :'category';
122+
123+
$terms = !$is_like_operator ?$terms :get_terms(
124+
[
125+
'taxonomy' =>$taxonomy,
126+
'fields' =>'ids',
127+
'name__like' =>esc_attr($terms ),
128+
]
129+
);
130+
131+
$result = [
132+
'taxonomy' =>$taxonomy,
133+
'field' => ($field_key ==='id' ) ||$is_like_operator ?'term_id' :'name',
134+
'terms' =>$terms,
135+
'operator' =>$mapped_operator,
136+
];
137+
138+
$temp_query[] =$result;
139+
}
140+
}
141+
}elseif (in_array($root_obj_key,$this->relation_keys,true ) ) {
142+
$nested_obj_array =$value;
143+
$wp_query_array = [];
144+
145+
if (count($nested_obj_array ) ===0 ) {
146+
thrownewFilterException('The Filter relation array specified has no children. Please remove the relation key or add one or more appropriate objects to proceed.' );
147+
}
148+
foreach ($nested_obj_arrayas$nested_obj_index =>$nested_obj_value ) {
149+
$wp_query_array[$nested_obj_index ] =$this->resolve_taxonomy($nested_obj_value, ++$depth );
150+
$wp_query_array[$nested_obj_index ]['relation'] ='AND';
151+
}
152+
$wp_query_array['relation'] =strtoupper($root_obj_key );
153+
$temp_query[] =$wp_query_array;
154+
}
155+
}
156+
return$temp_query;
157+
}
158+
60159
/**
61160
* Apply facet filters using graphql_connection_query_args filter hook.
62161
*
63162
* @param array $query_args Arguments that come from previous filter and will be passed to WP_Query.
64163
* @param AbstractConnectionResolver $connection_resolver Connection resolver.
65164
*
66-
* @return array|mixed
165+
* @return array
67166
*/
68-
publicfunctionapply_filter_resolver(array$query_args,AbstractConnectionResolver$connection_resolver ):array {
167+
publicfunctionapply_recursive_filter_resolver(array$query_args,AbstractConnectionResolver$connection_resolver ):array {
69168
$args =$connection_resolver->getArgs();
70169

71170
if (empty($args['filter'] ) ) {
72171
return$query_args;
73172
}
74-
$operator_mappings =array(
75-
'in' =>'IN',
76-
'notIn' =>'NOT IN',
77-
'eq' =>'IN',
78-
'notEq' =>'NOT IN',
79-
'like' =>'IN',
80-
'notLike' =>'NOT IN',
81-
);
82-
$c =0;
83-
foreach ($args['filter']as$taxonomy_input =>$data ) {
84-
foreach ($dataas$field_name =>$field_data ) {
85-
foreach ($field_dataas$operator =>$terms ) {
86-
$mapped_operator =$operator_mappings[$operator ] ??'IN';
87-
$is_like_operator =$this->is_like_operator($operator );
88-
$taxonomy =$taxonomy_input ==='tag' ?'post_tag' :'category';
89-
90-
$terms = !$is_like_operator ?$terms :get_terms(
91-
array(
92-
'name__like' =>esc_attr($terms ),
93-
'fields' =>'ids',
94-
'taxonomy' =>$taxonomy,
95-
)
96-
);
97-
98-
$result =array(
99-
'terms' =>$terms,
100-
'taxonomy' =>$taxonomy,
101-
'operator' =>$mapped_operator,
102-
'field' => ($field_name ==='id' ||$is_like_operator ) ?'term_id' :'name',
103-
);
104-
105-
$query_args['tax_query'][] =$result;
106-
$c++;
107-
}
108-
}
109-
}
110173

111-
if ($c >1 ) {
112-
$query_args['tax_query']['relation'] ='AND';
113-
}
174+
$filter_args_root =$args['filter'];
114175

115-
self::$query_args =$query_args;
176+
$query_args['tax_query'][] =$this->resolve_taxonomy($filter_args_root,0, [] );
116177

178+
self::$query_args =$query_args;
117179
return$query_args;
118180
}
119-
120181
/**
121182
* Return query args.
122183
*
@@ -220,13 +281,21 @@ public function extend_wp_graphql_fields() {
220281
'type' =>'TaxonomyFilterFields',
221282
'description' =>__('Category Object Fields Allowable For Filtering','wp-graphql-filter-query' ),
222283
],
284+
'and' => [
285+
'type' => ['list_of' =>'TaxonomyFilter' ],
286+
'description' =>__('\'AND\' Array of Taxonomy Objects Allowable For Filtering','wp-graphql-filter-query' ),
287+
],
288+
'or' => [
289+
'type' => ['list_of' =>'TaxonomyFilter' ],
290+
'description' =>__('\'OR\' Array of Taxonomy Objects Allowable For Filterin','wp-graphql-filter-query' ),
291+
],
223292
],
224293
]
225294
);
226295
}
227296

228297
/**
229-
* Check if operatorislike or notLike
298+
* Check if operator like or notLike
230299
*
231300
* @param string $operator Received operator - not mapped.
232301
*

‎tests/src/aggregate-query.test.php‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class AggregateQueryTest extends WP_UnitTestCase {
1717
privateconstTAG_BIG_ID_TO_BE_REPLACED ='{!#%_TAG_BIG_%#!}';
1818
privateconstTAG_SMALL_ID_TO_BE_REPLACED ='{!#%_TAG_SMALL_%#!}';
1919

20-
publicstaticfunctionsetUpBeforeClass() {
20+
publicstaticfunctionsetUpBeforeClass():void {
2121
$cat_post_id =wp_insert_post(
2222
array(
2323
'post_title' =>'cat',
@@ -44,6 +44,19 @@ public static function setUpBeforeClass() {
4444
wp_set_post_categories($dog_post_id,array(self::$category_animal_id,self::$category_canine_id ) );
4545
$ids =wp_add_post_tags($dog_post_id,array('black','big' ) );
4646
self::$tag_big_id =$ids[1];
47+
48+
$twenty_posts_category_id =wp_create_category('twenty-posts' );
49+
50+
foreach (range(1,20 )as$i ) {
51+
$id =wp_insert_post(
52+
array(
53+
'post_title' =>'post' .$i,
54+
'post_content' =>'this is post' .$i,
55+
'post_status' =>'publish',
56+
)
57+
);
58+
wp_set_post_categories($id,array($twenty_posts_category_id ) );
59+
}
4760
}
4861

4962
protectedfunctionsetUp():void {
@@ -223,6 +236,10 @@ public function test_get_tags_aggregations() {
223236
'key' =>'canine',
224237
'count' =>1,
225238
],
239+
[
240+
'key' =>'twenty-posts',
241+
'count' =>20,
242+
],
226243
];
227244

228245
$result =do_graphql_request($query );
@@ -232,7 +249,6 @@ public function test_get_tags_aggregations() {
232249
$this->assertEquals($expected_categories,$result['data']['posts']['aggregations']['categories'] );
233250
}
234251

235-
236252
/**
237253
* @dataProvider filter_aggregations_data_provider
238254
*
@@ -316,7 +332,7 @@ public function filter_aggregations_data_provider(): array {
316332
}
317333
}
318334
}',
319-
'{"data": { "posts": {"aggregations" : { "categories" : []}}}}',
335+
'{"data": { "posts": {"aggregations" : { "categories" : [{ "key" : "twenty-posts", "count" : "20"}]}}}}',
320336
],
321337
'posts_valid_filter_category_name_notIn' => [
322338
'query {
@@ -337,7 +353,7 @@ public function filter_aggregations_data_provider(): array {
337353
}
338354
}
339355
}',
340-
'{"data": { "posts": {"aggregations" : { "categories" : [{ "key" : "animal", "count" : "1"}, { "key" : "canine", "count" : "1"}]}}}}',
356+
'{"data": { "posts": {"aggregations" : { "categories" : [{ "key" : "animal", "count" : "1"}, { "key" : "canine", "count" : "1"}, { "key" : "twenty-posts", "count" : "20"}]}}}}',
341357
],
342358
'posts_valid_filter_category_name_eq_and_in' => [
343359
'query {
@@ -599,7 +615,7 @@ public function filter_aggregations_data_provider(): array {
599615
}
600616
}
601617
}',
602-
'{"data": { "posts": {"aggregations" : { "categories" : [ { "key" : "animal", "count" : "1"}, { "key" : "canine", "count" : "1"} ] }}}}',
618+
'{"data": { "posts": {"aggregations" : { "categories" : [ { "key" : "animal", "count" : "1"}, { "key" : "canine", "count" : "1"}, { "key" : "twenty-posts", "count" : "20"} ] }}}}',
603619
],
604620
'posts_valid_filter_category_id_in' => [
605621
'query {
@@ -641,7 +657,7 @@ public function filter_aggregations_data_provider(): array {
641657
}
642658
}
643659
}',
644-
'{"data": { "posts": {"aggregations" : { "categories" : [ { "key" : "animal", "count" : "1"}, { "key" : "feline", "count" : "1"} ] }}}}',
660+
'{"data": { "posts": {"aggregations" : { "categories" : [ { "key" : "animal", "count" : "1"}, { "key" : "feline", "count" : "1"}, { "key" : "twenty-posts", "count" : "20"} ] }}}}',
645661
],
646662
'posts_valid_filter_category_name_eq_id_notEq' => [
647663
'query {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp