Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
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."
);
}
}

}