From 3c5561d6015545c3ba04311a7921d2ea82c817e8 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke <23417+roborourke@users.noreply.github.com> Date: Mon, 27 May 2024 15:34:41 +0100 Subject: [PATCH 1/8] Support has_published_posts Issue #145 --- inc/namespace.php | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/inc/namespace.php b/inc/namespace.php index 0d360d3..5a5b0d1 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -21,6 +21,7 @@ use WP_REST_Response; use WP_REST_Server; use WP_User; +use WP_User_Query; use function Asset_Loader\enqueue_asset; @@ -45,6 +46,7 @@ function bootstrap() : void { add_action( 'rest_api_init', __NAMESPACE__ . '\\register_rest_api_fields' ); add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\enqueue_assets' ); add_action( 'pre_get_posts', __NAMESPACE__ . '\\action_pre_get_posts', 9999 ); + add_action( 'pre_user_query', __NAMESPACE__ . '\\action_reference_pre_user_query', 9999 ); add_action( 'wp', __NAMESPACE__ . '\\action_wp' ); add_action( 'wp_insert_post', [ $insert_post_handler, 'action_wp_insert_post' ], 10, 3 ); @@ -686,6 +688,50 @@ function action_pre_get_posts( WP_Query $query ) : void { }, 999, 2 ); } +/** + * Handle has_published_posts where clause to include guest authors. + * + * @param WP_User_Query $query Current instance of WP_User_Query (passed by reference). + */ +function action_reference_pre_user_query( WP_User_Query $query ) : void { + global $wpdb; + + // Author sitemap queries check for this meta key. + if ( ! is_array( $query->get( 'has_published_posts' ) ) ) { + return; + } + + $blog_id = $query->get( 'blog_id' ) ?: 0; + $post_types = $query->get( 'has_published_posts' ); + foreach ( $post_types as &$post_type ) { + $post_type = $wpdb->prepare( '%s', $post_type ); + } + + $prefix = $wpdb->get_blog_prefix( $blog_id ); + $posts_table = $prefix . 'posts'; + $term_relationships_table = $prefix . 'term_relationships'; + $term_taxonomy_table = $prefix . 'term_taxonomy'; + $terms_table = $prefix . 'terms'; + + // Rebuild the has_published_posts part of the query. + $sql = " $wpdb->users.ID IN ( SELECT DISTINCT $posts_table.post_author FROM $posts_table WHERE $posts_table.post_status = 'publish' AND $posts_table.post_type IN ( " . implode( ', ', $post_types ) . ' ) )'; + + // Guest author query. + $guest_sql = " $wpdb->users.ID IN ( + SELECT DISTINCT CAST( t.slug AS SIGNED ) + FROM {$posts_table} p + LEFT JOIN {$term_relationships_table} tr ON p.ID = tr.object_id + LEFT JOIN {$term_taxonomy_table} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id + LEFT JOIN {$terms_table} t ON tt.term_id = t.term_id + WHERE p.post_type IN ( " . implode( ', ', $post_types ) . " ) + AND p.post_status = 'publish' + )"; + + $query_with_guests = " ( ( $sql ) OR ( $guest_sql ) )"; + + $query->query_where = str_replace( $sql, $query_with_guests, $query->query_where ); +} + /** * Filters the list of recipients for comment moderation emails. * From 6ecd56c111b99fe372e4e0f845d57ce447b540e8 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Mon, 27 May 2024 16:54:44 +0100 Subject: [PATCH 2/8] Use either attribution or regular lookup, not both --- inc/namespace.php | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/inc/namespace.php b/inc/namespace.php index 5a5b0d1..b79db73 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -703,9 +703,24 @@ function action_reference_pre_user_query( WP_User_Query $query ) : void { $blog_id = $query->get( 'blog_id' ) ?: 0; $post_types = $query->get( 'has_published_posts' ); + $supported_post_types = array_intersect( + get_supported_post_types(), + $post_types + ); + $unsupported_post_types = array_diff( + $supported_post_types, + $post_types + ); + foreach ( $post_types as &$post_type ) { $post_type = $wpdb->prepare( '%s', $post_type ); } + foreach ( $supported_post_types as &$post_type ) { + $post_type = $wpdb->prepare( '%s', $post_type ); + } + foreach ( $unsupported_post_types as &$post_type ) { + $post_type = $wpdb->prepare( '%s', $post_type ); + } $prefix = $wpdb->get_blog_prefix( $blog_id ); $posts_table = $prefix . 'posts'; @@ -716,18 +731,25 @@ function action_reference_pre_user_query( WP_User_Query $query ) : void { // Rebuild the has_published_posts part of the query. $sql = " $wpdb->users.ID IN ( SELECT DISTINCT $posts_table.post_author FROM $posts_table WHERE $posts_table.post_status = 'publish' AND $posts_table.post_type IN ( " . implode( ', ', $post_types ) . ' ) )'; - // Guest author query. - $guest_sql = " $wpdb->users.ID IN ( - SELECT DISTINCT CAST( t.slug AS SIGNED ) - FROM {$posts_table} p - LEFT JOIN {$term_relationships_table} tr ON p.ID = tr.object_id - LEFT JOIN {$term_taxonomy_table} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id - LEFT JOIN {$terms_table} t ON tt.term_id = t.term_id - WHERE p.post_type IN ( " . implode( ', ', $post_types ) . " ) - AND p.post_status = 'publish' - )"; - - $query_with_guests = " ( ( $sql ) OR ( $guest_sql ) )"; + // Non-authorship author query. + $unsupported_sql = empty( $unsupported_post_types ) + ? '1=0' + : str_replace( implode( ', ', $post_types ), implode( ', ', $unsupported_post_types ), $sql ); + + // Attributed author query. + $supported_sql = empty( $supported_post_types ) + ? '1=0' + : " $wpdb->users.ID IN ( + SELECT DISTINCT CAST( t.slug AS SIGNED ) + FROM {$posts_table} p + LEFT JOIN {$term_relationships_table} tr ON p.ID = tr.object_id + LEFT JOIN {$term_taxonomy_table} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id + LEFT JOIN {$terms_table} t ON tt.term_id = t.term_id + WHERE p.post_type IN ( " . implode( ', ', $supported_post_types ) . " ) + AND p.post_status = 'publish' + )"; + + $query_with_guests = " ( ( $unsupported_sql ) OR ( $supported_sql ) )"; $query->query_where = str_replace( $sql, $query_with_guests, $query->query_where ); } From 0c1fd320373e82d633fc3b05b4f7751be33177f9 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 28 May 2024 10:06:02 +0100 Subject: [PATCH 3/8] Add test case for WP_User_Query --- tests/phpunit/test-wp-user-query.php | 142 +++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/phpunit/test-wp-user-query.php diff --git a/tests/phpunit/test-wp-user-query.php b/tests/phpunit/test-wp-user-query.php new file mode 100644 index 0000000..0f4530e --- /dev/null +++ b/tests/phpunit/test-wp-user-query.php @@ -0,0 +1,142 @@ +post; + + // Attributed to Editor, owned by Admin. + $factory->create_and_get( [ + 'post_author' => self::$users['admin']->ID, + 'post_status' => 'publish', + POSTS_PARAM => [ + self::$users['editor']->ID, + ], + ] ); + + // Attributed to Editor, owned by nobody. + $factory->create_and_get( [ + 'post_status' => 'publish', + POSTS_PARAM => [ + self::$users['editor']->ID, + ], + ] ); + + // Attributed to Guest, owned by Author. + $factory->create_and_get( [ + 'post_author' => self::$users['author']->ID, + 'post_status' => 'publish', + POSTS_PARAM => [ + self::$users[ GUEST_ROLE ]->ID, + ], + ] ); + + // Attributed to Guest, owned by Admin. + $factory->create_and_get( [ + 'post_author' => self::$users['admin']->ID, + 'post_status' => 'publish', + POSTS_PARAM => [ + self::$users[ GUEST_ROLE ]->ID, + ], + ] ); + + // Owned by Author. + $factory->create_and_get( [ + 'post_author' => self::$users['author']->ID, + 'post_status' => 'publish', + 'post_type' => 'non_existent_type', + ] ); + + // Owned by Admin. + $factory->create_and_get( [ + 'post_author' => self::$users['admin']->ID, + 'post_status' => 'publish', + 'post_type' => 'non_existent_type', + ] ); + + $common_args = [ + 'fields' => 'ids', + 'orderby' => 'ID', + 'order' => 'ASC', + ]; + + // Queries for attributed published post types. + $test_args = [ + 'has_published_posts' => [ 'post' ], + ]; + + foreach ( $test_args as $test_key => $test_value ) { + $args = array_merge( $common_args, [ + $test_key => $test_value, + ] ); + + $query = new WP_User_Query(); + $users = $query->query( $args ); + + $this->assertSame( + [ self::$users['editor']->ID, self::$users[ GUEST_ROLE ]->ID ], + $users, + "User IDs for attributed {$test_key} query are incorrect." + ); + } + + // Queries for non attributed published post types. + $test_args = [ + 'has_published_posts' => [ 'non_existent_type' ], + ]; + + foreach ( $test_args as $test_key => $test_value ) { + $args = array_merge( $common_args, [ + $test_key => $test_value, + ] ); + + $query = new WP_User_Query(); + $users = $query->query( $args ); + + $this->assertSame( + [ self::$users['admin']->ID, self::$users['author']->ID ], + $users, + "User IDs for non attributed {$test_key} query are incorrect." + ); + } + + // Queries for non attributed published post types. + $test_args = [ + 'has_published_posts' => [ 'post', 'non_existent_type' ], + ]; + + foreach ( $test_args as $test_key => $test_value ) { + $args = array_merge( $common_args, [ + $test_key => $test_value, + ] ); + + $query = new WP_User_Query(); + $users = $query->query( $args ); + + $this->assertSame( + [ + self::$users['admin']->ID, + self::$users['editor']->ID, + self::$users['author']->ID, + self::$users[ GUEST_ROLE ]->ID + ], + $users, + "User IDs for combined attributed and non-attributed {$test_key} query are incorrect." + ); + } + } + +} From 6d2df28080034c59b4726ed5d381c22c4d008a59 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 28 May 2024 10:30:14 +0100 Subject: [PATCH 4/8] Correct the args for WP_User_Query --- tests/phpunit/test-wp-user-query.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/phpunit/test-wp-user-query.php b/tests/phpunit/test-wp-user-query.php index 0f4530e..5e98da5 100644 --- a/tests/phpunit/test-wp-user-query.php +++ b/tests/phpunit/test-wp-user-query.php @@ -68,7 +68,7 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : ] ); $common_args = [ - 'fields' => 'ids', + 'fields' => 'ID', 'orderby' => 'ID', 'order' => 'ASC', ]; @@ -83,8 +83,7 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : $test_key => $test_value, ] ); - $query = new WP_User_Query(); - $users = $query->query( $args ); + $users = new WP_User_Query( $args ); $this->assertSame( [ self::$users['editor']->ID, self::$users[ GUEST_ROLE ]->ID ], @@ -103,8 +102,7 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : $test_key => $test_value, ] ); - $query = new WP_User_Query(); - $users = $query->query( $args ); + $users = new WP_User_Query( $args ); $this->assertSame( [ self::$users['admin']->ID, self::$users['author']->ID ], @@ -123,8 +121,7 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : $test_key => $test_value, ] ); - $query = new WP_User_Query(); - $users = $query->query( $args ); + $users = new WP_User_Query( $args ); $this->assertSame( [ From f546204b4d0dc4a93e58a9122a006d90e9dcd72f Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 28 May 2024 10:34:59 +0100 Subject: [PATCH 5/8] Correct the args for WP_User_Query --- tests/phpunit/test-wp-user-query.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/test-wp-user-query.php b/tests/phpunit/test-wp-user-query.php index 5e98da5..3807e3c 100644 --- a/tests/phpunit/test-wp-user-query.php +++ b/tests/phpunit/test-wp-user-query.php @@ -83,7 +83,9 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : $test_key => $test_value, ] ); - $users = new WP_User_Query( $args ); + $query = new WP_User_Query( $args ); + $users = (array) $query->get_results(); + $users = array_map( 'absint', $users ); $this->assertSame( [ self::$users['editor']->ID, self::$users[ GUEST_ROLE ]->ID ], @@ -102,7 +104,9 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : $test_key => $test_value, ] ); - $users = new WP_User_Query( $args ); + $query = new WP_User_Query( $args ); + $users = (array) $query->get_results(); + $users = array_map( 'absint', $users ); $this->assertSame( [ self::$users['admin']->ID, self::$users['author']->ID ], @@ -121,14 +125,16 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : $test_key => $test_value, ] ); - $users = new WP_User_Query( $args ); + $query = new WP_User_Query( $args ); + $users = (array) $query->get_results(); + $users = array_map( 'absint', $users ); $this->assertSame( [ self::$users['admin']->ID, self::$users['editor']->ID, self::$users['author']->ID, - self::$users[ GUEST_ROLE ]->ID + self::$users[ GUEST_ROLE ]->ID, ], $users, "User IDs for combined attributed and non-attributed {$test_key} query are incorrect." From d7176037da680f442aca73772c3ff64479b762c2 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 28 May 2024 10:42:34 +0100 Subject: [PATCH 6/8] Use test CPT without author support --- tests/phpunit/test-wp-user-query.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/test-wp-user-query.php b/tests/phpunit/test-wp-user-query.php index 3807e3c..6dfe6a9 100644 --- a/tests/phpunit/test-wp-user-query.php +++ b/tests/phpunit/test-wp-user-query.php @@ -57,14 +57,14 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : $factory->create_and_get( [ 'post_author' => self::$users['author']->ID, 'post_status' => 'publish', - 'post_type' => 'non_existent_type', + 'post_type' => 'test_cpt_no_author', ] ); // Owned by Admin. $factory->create_and_get( [ 'post_author' => self::$users['admin']->ID, 'post_status' => 'publish', - 'post_type' => 'non_existent_type', + 'post_type' => 'test_cpt_no_author', ] ); $common_args = [ @@ -96,7 +96,7 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : // Queries for non attributed published post types. $test_args = [ - 'has_published_posts' => [ 'non_existent_type' ], + 'has_published_posts' => [ 'test_cpt_no_author' ], ]; foreach ( $test_args as $test_key => $test_value ) { @@ -117,7 +117,7 @@ public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : // Queries for non attributed published post types. $test_args = [ - 'has_published_posts' => [ 'post', 'non_existent_type' ], + 'has_published_posts' => [ 'post', 'test_cpt_no_author' ], ]; foreach ( $test_args as $test_key => $test_value ) { From abe692949fc73f0deff3fb55f9051dbd39cfed59 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 28 May 2024 10:42:43 +0100 Subject: [PATCH 7/8] Use test CPT without author support --- tests/phpunit/includes/bootstrap.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/phpunit/includes/bootstrap.php b/tests/phpunit/includes/bootstrap.php index e804f73..5c9bc5d 100644 --- a/tests/phpunit/includes/bootstrap.php +++ b/tests/phpunit/includes/bootstrap.php @@ -27,6 +27,13 @@ require_once $_plugin_dir . '/plugin.php'; } ); +tests_add_filter( 'init', function() : void { + register_post_type( 'test_cpt_no_author', [ + 'public' => true, + 'supports' => [ 'title', 'editor' ], + ] ); +} ); + require_once $_tests_dir . '/includes/bootstrap.php'; require_once __DIR__ . '/testcase.php'; require_once __DIR__ . '/email-testcase.php'; From 89d9e8f102c817bdc326d579107e77b9e013b684 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Tue, 28 May 2024 10:54:05 +0100 Subject: [PATCH 8/8] Fix supported/unsupported CPT splitting logic --- inc/namespace.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/namespace.php b/inc/namespace.php index b79db73..07ff5e6 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -708,8 +708,8 @@ function action_reference_pre_user_query( WP_User_Query $query ) : void { $post_types ); $unsupported_post_types = array_diff( - $supported_post_types, - $post_types + $post_types, + $supported_post_types ); foreach ( $post_types as &$post_type ) {