Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support has_published_posts arg in WP_User_Query #146

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
68 changes: 68 additions & 0 deletions inc/namespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 );

Expand Down Expand Up @@ -687,6 +689,72 @@ 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' );
$supported_post_types = array_intersect(
get_supported_post_types(),
$post_types
);
$unsupported_post_types = array_diff(
$post_types,
$supported_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';
$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 ) . ' ) )';

// 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 );
}

/**
* Filters the list of recipients for comment moderation emails.
*
Expand Down
7 changes: 7 additions & 0 deletions tests/phpunit/includes/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
145 changes: 145 additions & 0 deletions tests/phpunit/test-wp-user-query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php
/**
* WP_Query tests.
*
* @package authorship
*/

declare( strict_types=1 );

namespace Authorship\Tests;

use const Authorship\GUEST_ROLE;
use const Authorship\POSTS_PARAM;

use WP_User_Query;

class TestWPUserQuery extends TestCase {
public function testQueryForAuthorWithPublishedPostsReturnsAttributedAuthors() : void {
$factory = self::factory()->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' => 'test_cpt_no_author',
] );

// Owned by Admin.
$factory->create_and_get( [
'post_author' => self::$users['admin']->ID,
'post_status' => 'publish',
'post_type' => 'test_cpt_no_author',
] );

$common_args = [
'fields' => 'ID',
'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( $args );
$users = (array) $query->get_results();
$users = array_map( 'absint', $users );

$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' => [ 'test_cpt_no_author' ],
];

foreach ( $test_args as $test_key => $test_value ) {
$args = array_merge( $common_args, [
$test_key => $test_value,
] );

$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 ],
$users,
"User IDs for non attributed {$test_key} query are incorrect."
);
}

// Queries for non attributed published post types.
$test_args = [
'has_published_posts' => [ 'post', 'test_cpt_no_author' ],
];

foreach ( $test_args as $test_key => $test_value ) {
$args = array_merge( $common_args, [
$test_key => $test_value,
] );

$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,
],
$users,
"User IDs for combined attributed and non-attributed {$test_key} query are incorrect."
);
}
}

}