From 18cb10c935ef8b45b4e127672d0845db13aee5eb Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Wed, 18 Dec 2024 11:55:36 +0100 Subject: [PATCH 01/25] work in progress --- .../class-author-distribution.php | 251 ++++++++++++++++++ .../class-author-ingestion.php | 168 ++++++++++++ .../class-incoming-post.php | 12 +- .../class-outgoing-post.php | 1 + .../class-author-distribution.php | 5 +- includes/utils/class-users.php | 2 +- tests/unit-tests/test-incoming-post.php | 17 ++ tests/unit-tests/test-outgoing-post.php | 16 +- 8 files changed, 460 insertions(+), 12 deletions(-) create mode 100644 includes/content-distribution/class-author-distribution.php create mode 100644 includes/content-distribution/class-author-ingestion.php diff --git a/includes/content-distribution/class-author-distribution.php b/includes/content-distribution/class-author-distribution.php new file mode 100644 index 00000000..8c059237 --- /dev/null +++ b/includes/content-distribution/class-author-distribution.php @@ -0,0 +1,251 @@ +post_author ); + + if ( ! function_exists( 'get_coauthors' ) ) { + if ( is_wp_error( $author ) ) { + Debugger::log( 'Error getting author ' . $post->post_author . ' for distribution on post ' . $post->ID . ': ' . $author->get_error_message() ); + + return []; + } + + return [ $author ]; + } + + $co_authors = get_coauthors( $post->ID ); + if ( empty( $co_authors ) ) { + if ( is_wp_error( $author ) ) { + Debugger::log( 'Error getting author ' . $post->post_author . ' for distribution on post ' . $post->ID . ': ' . $author->get_error_message() ); + + return []; + } + + return [ $author ]; + } + + $authors = []; + + foreach ( $co_authors as $co_author ) { + if ( is_a( $co_author, 'WP_User' ) ) { + // This will never return an error because we are checking for is_a() first. + $authors[] = self::get_wp_user_for_distribution( $co_author ); + continue; + } + + $guest_author = self::get_guest_author_for_distribution( $co_author ); + if ( is_wp_error( $guest_author ) ) { + Debugger::log( 'Error getting guest author for distribution on post ' . $post->ID . ': ' . $guest_author->get_error_message() ); + Debugger::log( $co_author ); + continue; + } + $authors[] = $guest_author; + } + + return $authors; + } + + /** + * Gets the user data of a WP user to be distributed along with the post. + * + * @param int|WP_Post $user The user ID or object. + * + * @return WP_Error|array + */ + private static function get_wp_user_for_distribution( $user ) { + if ( ! is_a( $user, 'WP_User' ) ) { + $user = get_user_by( 'ID', $user ); + } + + if ( ! $user ) { + return new WP_Error( 'Error getting WP User details for distribution. Invalid User' ); + } + + $author = [ + 'type' => 'wp_user', + 'ID' => $user->ID, + ]; + + + foreach ( User_Update_Watcher::$user_props as $prop ) { + if ( isset( $user->$prop ) ) { + $author[ $prop ] = $user->$prop; + } + } + + // CoAuthors' guest authors have a 'website' property. + if ( isset( $user->website ) ) { + $author['website'] = $user->website; + } + + foreach ( User_Update_Watcher::$watched_meta as $meta_key ) { + $author[ $meta_key ] = get_user_meta( $user->ID, $meta_key, true ); + } + + return $author; + } + + /** + * Get the guest author data to be distributed along with the post. + * + * @param object $guest_author The Guest Author object. + * + * @return WP_Error|array + */ + private static function get_guest_author_for_distribution( $guest_author ) { + + // CoAuthors plugin existence was checked in get_authors_for_distribution(). + global $coauthors_plus; + + if ( ! is_object( $guest_author ) || ! isset( $guest_author->type ) || 'guest-author' !== $guest_author->type ) { + return new WP_Error( 'Error getting guest author details for distribution. Invalid Guest Author' ); + } + + $author = (array) $guest_author; + $author['type'] = 'guest_author'; + + // Gets the guest author avatar. + // We only want to send an actual uploaded avatar, we don't want to send the fallback avatar, like gravatar. + // If no avatar was set, let it default to the fallback set in the target site. + $author_avatar = $coauthors_plus->guest_authors->get_guest_author_thumbnail( $guest_author, 80 ); + if ( $author_avatar ) { + $author['avatar_img_tag'] = $author_avatar; + } + + return $author; + } + + /** + * Sends an extra notification to subscribers when the authors of a post are updated. + * + * CoAuthors Plus updates the authors through an additional ajax request in the editor after the post is updated, + * therefore when we change the authors and update the post in the Editor, the notification sent to the subscribers still + * has the old authors + * + * We don't filter the response here, we just send the notification. + * + * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client. + * @param array $handler Route handler used for the request. + * @param WP_REST_Request $request Request used to generate the response. + * + * @return WP_REST_Response|WP_HTTP_Response|WP_Error|mixed + */ + public static function after_coauthors_update( $response, $handler, $request ) { + + if ( ! class_exists( 'CoAuthors\API\Endpoints' ) || ! function_exists( 'Distributor\Subscriptions\send_notifications' ) ) { + return $response; + } + + $coauthors_endpoint_base = \CoAuthors\API\Endpoints::NS . '/' . \CoAuthors\API\Endpoints::AUTHORS_ROUTE; + + if ( false === strpos( $request->get_route(), $coauthors_endpoint_base ) ) { + return $response; + } + + if ( 'POST' !== $request->get_method() ) { + return $response; + } + + \Distributor\Subscriptions\send_notifications( (int) $request->get_param( 'post_id' ) ); + + return $response; + } +} diff --git a/includes/content-distribution/class-author-ingestion.php b/includes/content-distribution/class-author-ingestion.php new file mode 100644 index 00000000..8924a4ed --- /dev/null +++ b/includes/content-distribution/class-author-ingestion.php @@ -0,0 +1,168 @@ +get_param( 'newspack_network_authors' ); + if ( empty( $distributed_authors ) ) { + return; + } + + self::ingest_authors_for_post( $post->ID, $distributed_authors ); + } + + /** + * Ingest authors for a post distributed to this site + * + * @param int $post_id The post ID. + * @param array $distributed_authors The distributed authors array. + * + * @return void + */ + public static function ingest_authors_for_post( $post_id, $distributed_authors ) { + + Debugger::log( 'Ingesting authors from distributed post.' ); + + User_Update_Watcher::$enabled = false; + + update_post_meta( $post_id, 'newspack_network_authors', $distributed_authors ); + + $coauthors_plus = self::get_coauthors_plus(); + $coauthors = []; + + foreach ( $distributed_authors as $author ) { + // We only ingest WP Users. Guest authors are only stored in the newspack_network_authors post meta. + if ( empty( $author['type'] ) || 'wp_user' != $author['type'] ) { + continue; + } + + Debugger::log( 'Ingesting author: ' . $author['user_email'] ); + + $insert_array = [ + 'role' => 'author', + ]; + + foreach ( User_Update_Watcher::$user_props as $prop ) { + if ( isset( $author[ $prop ] ) ) { + $insert_array[ $prop ] = $author[ $prop ]; + } + } + + $user = User_Utils::get_or_create_user_by_email( $author['user_email'], get_post_meta( $post_id, 'dt_original_site_url', true ), $author['ID'], $insert_array ); + + if ( is_wp_error( $user ) ) { + Debugger::log( 'Error creating user: ' . $user->get_error_message() ); + continue; + } + + foreach ( User_Update_Watcher::get_writable_meta() as $meta_key ) { + if ( isset( $author[ $meta_key ] ) ) { + update_user_meta( $user->ID, $meta_key, $author[ $meta_key ] ); + } + } + + User_Utils::maybe_sideload_avatar( $user->ID, $author, false ); + + // If CoAuthors Plus is not present, just assign the first author as the post author. + if ( ! $coauthors_plus ) { + Debugger::log( 'CoAuthors Plus not present, assigning first author as post author.' ); + wp_update_post( + [ + 'ID' => $post_id, + 'post_author' => $user->ID, + ] + ); + break; + } + + $coauthors[] = $user->user_nicename; + } + + if ( $coauthors_plus ) { + Debugger::log( 'CoAuthors Plus present, assigning coauthors:' ); + Debugger::log( $coauthors ); + $coauthors_plus->add_coauthors( $post_id, $coauthors ); + } + } + + /** + * Triggered when a post is pulled from a remote site. + * + * @param int $new_post_id The new post ID that was pulled. + * @param \Distributor\ExternalConnections\ExternalConnection $connection The Distributor connection pulling the post. + * @param array $post_array The original post data retrieved via the connection. + * + * @return void + */ + public static function handle_pull( $new_post_id, $connection, $post_array ) { + + if ( empty( $post_array['newspack_network_authors'] ) ) { + return; + } + + $distributed_authors = $post_array['newspack_network_authors']; + + self::ingest_authors_for_post( $new_post_id, $distributed_authors ); + } +} diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index dac553e4..cc2d73b4 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -7,10 +7,10 @@ namespace Newspack_Network\Content_Distribution; -use Newspack_Network\Debugger; use Newspack_Network\Content_Distribution; -use WP_Post; +use Newspack_Network\Debugger; use WP_Error; +use WP_Post; /** * Incoming Post Class. @@ -353,11 +353,7 @@ public function insert( $payload = [] ) { /** * Do not insert if payload is older than the linked post's stored payload. */ - $current_payload = $this->get_post_payload(); - if ( - ! empty( $current_payload ) && - $current_payload['post_data']['modified_gmt'] > $post_data['modified_gmt'] - ) { + if ( ( $this->get_post_payload()['post_data']['modified_gmt'] ?? 0 ) > $post_data['modified_gmt'] ) { return new WP_Error( 'old_modified_date', __( 'Linked post content is newer than the post payload.', 'newspack-network' ) ); } @@ -398,6 +394,8 @@ public function insert( $payload = [] ) { $this->ID = $post_id; $this->post = get_post( $this->ID ); + Author_Ingestion::ingest_authors_for_post( $this->ID, $post_data['authors'] ); + // Handle post meta. $this->update_post_meta(); diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index 40c6e76c..9e2976a4 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -181,6 +181,7 @@ public function get_payload() { 'modified_gmt' => $this->post->post_modified_gmt, 'slug' => $this->post->post_name, 'post_type' => $this->post->post_type, + 'authors' => Author_Distribution::get_authors_for_distribution( $this->post ), 'raw_content' => $this->post->post_content, 'content' => $this->get_processed_post_content(), 'excerpt' => $this->post->post_excerpt, diff --git a/includes/distributor-customizations/class-author-distribution.php b/includes/distributor-customizations/class-author-distribution.php index 60415a98..8b31b701 100644 --- a/includes/distributor-customizations/class-author-distribution.php +++ b/includes/distributor-customizations/class-author-distribution.php @@ -7,7 +7,6 @@ namespace Newspack_Network\Distributor_Customizations; -use Newspack\Data_Events; use Newspack_Network\Debugger; use Newspack_Network\User_Update_Watcher; use WP_Error; @@ -92,10 +91,10 @@ public static function add_author_data_to_pull( $post_array ) { /** * Get the authors of a post to be added to the distribution payload. * - * @param WP_Post $post The post object. + * @param \WP_Post $post The post object. * @return array An array of authors. */ - private static function get_authors_for_distribution( $post ) { + public static function get_authors_for_distribution( $post ) { $author = self::get_wp_user_for_distribution( $post->post_author ); if ( ! function_exists( 'get_coauthors' ) ) { diff --git a/includes/utils/class-users.php b/includes/utils/class-users.php index 5c310692..4687063d 100644 --- a/includes/utils/class-users.php +++ b/includes/utils/class-users.php @@ -86,7 +86,7 @@ public static function get_or_create_user_by_email( $email, $remote_site_url, $r */ public static function generate_user_nicename( $name ) { $name = self::strip_email_domain( $name ); // If an email address, strip the domain. - + // TODO. Could really benefit from the unique thing from NMT. return substr( \sanitize_title( \sanitize_user( $name, true ) ), 0, 50 ); } diff --git a/tests/unit-tests/test-incoming-post.php b/tests/unit-tests/test-incoming-post.php index 13e253fb..c540a4a6 100644 --- a/tests/unit-tests/test-incoming-post.php +++ b/tests/unit-tests/test-incoming-post.php @@ -51,6 +51,13 @@ private function get_sample_payload() { 'content' => '

Content

', 'excerpt' => 'Excerpt', 'thumbnail_url' => 'https://picsum.photos/id/1/300/300.jpg', + 'authors' => [ + [ + 'type' => 'wp_user', + 'user_email' => 'something-tapir@example.com', + 'ID' => 12345, + ] + ], 'taxonomy' => [ 'category' => [ [ @@ -330,4 +337,14 @@ public function test_post_meta_sync() { // Assert that the custom post meta was removed on relink. $this->assertEmpty( get_post_meta( $post_id, 'custom', true ) ); } + + /** + * Test that a post author is set. + */ + public function test_authors(): void { + $post_id = $this->incoming_post->insert( $this->get_sample_payload() ); + + $post = get_post( $post_id ); + $this->assertNotEmpty( $post->post_author ); + } } diff --git a/tests/unit-tests/test-outgoing-post.php b/tests/unit-tests/test-outgoing-post.php index 59362fe9..6979a8cb 100644 --- a/tests/unit-tests/test-outgoing-post.php +++ b/tests/unit-tests/test-outgoing-post.php @@ -37,15 +37,19 @@ class TestOutgoingPost extends WP_UnitTestCase { */ protected $outgoing_post; + private $some_editor; + /** * Set up. */ public function set_up() { parent::set_up(); + $this->some_editor = $this->factory->user->create_and_get( [ 'role' => 'editor' ] ); + // "Mock" the network node(s). update_option( Hub_Node::HUB_NODES_SYNCED_OPTION, $this->network ); - $post = $this->factory->post->create_and_get( [ 'post_type' => 'post' ] ); + $post = $this->factory->post->create_and_get( [ 'post_type' => 'post', 'post_author' => $this->some_editor->ID ] ); $this->outgoing_post = new Outgoing_Post( $post ); $this->outgoing_post->set_distribution( [ $this->network[0]['url'] ] ); } @@ -131,11 +135,21 @@ public function test_get_payload() { 'thumbnail_url', 'taxonomy', 'post_meta', + 'authors', ]; $this->assertEmpty( array_diff( $post_data_keys, array_keys( $payload['post_data'] ) ) ); $this->assertEmpty( array_diff( array_keys( $payload['post_data'] ), $post_data_keys ) ); } + /** + * Test that the author(s) are included in the payload. + */ + public function test_authors_data(): void { + $payload = $this->outgoing_post->get_payload(); + $this->assertNotEmpty( $payload['post_data']['authors'][0] ); + $this->assertEquals( $this->some_editor->ID, $payload['post_data']['authors'][0]['ID'] ); + } + /** * Test post meta. */ From 25511b4618970b71c94173107371a3127bdda0db Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Mon, 20 Jan 2025 15:00:23 +0100 Subject: [PATCH 02/25] Add to outgoing --- includes/content-distribution/class-outgoing-post.php | 1 + package.json | 5 +++-- tests/unit-tests/content-distribution/util.php | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index c66b95c0..50eecd7c 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -191,6 +191,7 @@ public function get_payload() { 'sites' => $this->get_distribution(), 'post_data' => [ 'title' => html_entity_decode( get_the_title( $this->post->ID ), ENT_QUOTES, get_bloginfo( 'charset' ) ), + 'authors' => Author_Distribution::get_authors_for_distribution( $this->post ), 'post_status' => $this->post->post_status, 'date_gmt' => $this->post->post_date_gmt, 'modified_gmt' => $this->post->post_modified_gmt, diff --git a/package.json b/package.json index 7ce52a91..63d7333f 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,9 @@ "format:php": "./vendor/bin/phpcbf .", "lint:php:staged": "./vendor/bin/phpcs", "release": "npm run semantic-release", - "release:archive": "rm -rf release && mkdir -p release && rsync -r . ./release/newspack-network --exclude-from='./.distignore' && cd release && zip -r newspack-network.zip newspack-network" - }, + "release:archive": "rm -rf release && mkdir -p release && rsync -r . ./release/newspack-network --exclude-from='./.distignore' && cd release && zip -r newspack-network.zip newspack-network", + "unittest": "./vendor/bin/phpunit" + }, "lint-staged": { "*.php": "npm run lint:php:staged" }, diff --git a/tests/unit-tests/content-distribution/util.php b/tests/unit-tests/content-distribution/util.php index 5b2806b5..8890dbed 100644 --- a/tests/unit-tests/content-distribution/util.php +++ b/tests/unit-tests/content-distribution/util.php @@ -30,6 +30,7 @@ function get_sample_payload( $origin = '', $destination = '' ) { 'sites' => [ $destination ], 'post_data' => [ 'title' => 'Title', + 'authors' => [], 'post_status' => 'publish', 'date_gmt' => '2021-01-01 00:00:00', 'modified_gmt' => '2021-01-01 00:00:00', From 2a7185f0ea7f1e36c2969c418490c16923abf746 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Mon, 20 Jan 2025 15:38:40 +0100 Subject: [PATCH 03/25] Clean up --- .../class-author-distribution.php | 112 +----------------- .../class-author-ingestion.php | 70 +---------- .../class-outgoing-post.php | 2 +- 3 files changed, 7 insertions(+), 177 deletions(-) diff --git a/includes/content-distribution/class-author-distribution.php b/includes/content-distribution/class-author-distribution.php index 8c059237..9f911e84 100644 --- a/includes/content-distribution/class-author-distribution.php +++ b/includes/content-distribution/class-author-distribution.php @@ -10,6 +10,7 @@ use Newspack_Network\Debugger; use Newspack_Network\User_Update_Watcher; use WP_Error; +use WP_Post; /** * Class to handle author distribution. @@ -19,78 +20,6 @@ */ class Author_Distribution { - /** - * Initializes the class - * - * @return void - */ - public static function init() { -// add_filter( 'dt_push_post_args', [ __CLASS__, 'add_author_data_to_push' ], 10, 2 ); -// add_filter( 'dt_subscription_post_args', [ __CLASS__, 'add_author_data_to_push' ], 10, 2 ); -// add_filter( 'dt_post_to_pull', [ __CLASS__, 'add_author_data_to_pull' ] ); -// add_filter( 'dt_syncable_taxonomies', [ __CLASS__, 'filter_syncable_taxonomies' ] ); -// -// add_filter( 'rest_request_after_callbacks', [ __CLASS__, 'after_coauthors_update' ], 10, 3 ); - } - - /** - * Removes CoAuthors Plus' author taxonomy from the list of taxonomies to be synced. - * - * This is crucial for out integration with CAP, as we are handling author syncing ourselves and we don't want - * Distributor to try to sync the author taxonomy. - * - * @param array $taxonomies An array of taxonomies slugs. - * - * @return array - */ - public static function filter_syncable_taxonomies( $taxonomies ) { - return array_filter( - $taxonomies, - function ( $taxonomy ) { - return 'author' !== $taxonomy; - } - ); - } - - /** - * Filters the post data sent on a push to add the author data. - * - * This callback is also used to add data to the post sent to the subscription endpoint. - * (sends an update to the linked posts in other sites) - * - * @param array $post_body The post data. - * @param WP_Post $post The post object. - * - * @return array - */ - public static function add_author_data_to_push( $post_body, $post ) { - $authors = self::get_authors_for_distribution( $post ); - if ( ! empty( $authors ) ) { - $post_body['newspack_network_authors'] = $authors; - } - - return $post_body; - } - - /** - * Filters the post data for a REST API response. - * - * This acts on requests made to pull a post from this site. - * - * @param array $post_array The post data. - */ - public static function add_author_data_to_pull( $post_array ) { - - $authors = self::get_authors_for_distribution( (object) $post_array ); - - if ( ! empty( $authors ) ) { - Debugger::log( 'Adding authors to pull' ); - $post_array['newspack_network_authors'] = $authors; - } - - return $post_array; - } - /** * Get the authors of a post to be added to the distribution payload. * @@ -98,7 +27,7 @@ public static function add_author_data_to_pull( $post_array ) { * * @return array An array of authors. */ - public static function get_authors_for_distribution( $post ) { + public static function get_authors_for_distribution( $post ): array { $author = self::get_wp_user_for_distribution( $post->post_author ); if ( ! function_exists( 'get_coauthors' ) ) { @@ -164,7 +93,6 @@ private static function get_wp_user_for_distribution( $user ) { 'ID' => $user->ID, ]; - foreach ( User_Update_Watcher::$user_props as $prop ) { if ( isset( $user->$prop ) ) { $author[ $prop ] = $user->$prop; @@ -212,40 +140,4 @@ private static function get_guest_author_for_distribution( $guest_author ) { return $author; } - - /** - * Sends an extra notification to subscribers when the authors of a post are updated. - * - * CoAuthors Plus updates the authors through an additional ajax request in the editor after the post is updated, - * therefore when we change the authors and update the post in the Editor, the notification sent to the subscribers still - * has the old authors - * - * We don't filter the response here, we just send the notification. - * - * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client. - * @param array $handler Route handler used for the request. - * @param WP_REST_Request $request Request used to generate the response. - * - * @return WP_REST_Response|WP_HTTP_Response|WP_Error|mixed - */ - public static function after_coauthors_update( $response, $handler, $request ) { - - if ( ! class_exists( 'CoAuthors\API\Endpoints' ) || ! function_exists( 'Distributor\Subscriptions\send_notifications' ) ) { - return $response; - } - - $coauthors_endpoint_base = \CoAuthors\API\Endpoints::NS . '/' . \CoAuthors\API\Endpoints::AUTHORS_ROUTE; - - if ( false === strpos( $request->get_route(), $coauthors_endpoint_base ) ) { - return $response; - } - - if ( 'POST' !== $request->get_method() ) { - return $response; - } - - \Distributor\Subscriptions\send_notifications( (int) $request->get_param( 'post_id' ) ); - - return $response; - } } diff --git a/includes/content-distribution/class-author-ingestion.php b/includes/content-distribution/class-author-ingestion.php index 8924a4ed..311b112d 100644 --- a/includes/content-distribution/class-author-ingestion.php +++ b/includes/content-distribution/class-author-ingestion.php @@ -1,6 +1,6 @@ get_param( 'newspack_network_authors' ); - if ( empty( $distributed_authors ) ) { - return; - } - - self::ingest_authors_for_post( $post->ID, $distributed_authors ); - } - /** * Ingest authors for a post distributed to this site * - * @param int $post_id The post ID. + * @param int $post_id The post ID. * @param array $distributed_authors The distributed authors array. * * @return void @@ -145,24 +103,4 @@ public static function ingest_authors_for_post( $post_id, $distributed_authors ) $coauthors_plus->add_coauthors( $post_id, $coauthors ); } } - - /** - * Triggered when a post is pulled from a remote site. - * - * @param int $new_post_id The new post ID that was pulled. - * @param \Distributor\ExternalConnections\ExternalConnection $connection The Distributor connection pulling the post. - * @param array $post_array The original post data retrieved via the connection. - * - * @return void - */ - public static function handle_pull( $new_post_id, $connection, $post_array ) { - - if ( empty( $post_array['newspack_network_authors'] ) ) { - return; - } - - $distributed_authors = $post_array['newspack_network_authors']; - - self::ingest_authors_for_post( $new_post_id, $distributed_authors ); - } } diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index 50eecd7c..8d4eadc3 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -191,7 +191,7 @@ public function get_payload() { 'sites' => $this->get_distribution(), 'post_data' => [ 'title' => html_entity_decode( get_the_title( $this->post->ID ), ENT_QUOTES, get_bloginfo( 'charset' ) ), - 'authors' => Author_Distribution::get_authors_for_distribution( $this->post ), + 'authors' => Author_Distribution::get_authors_for_distribution( $this->post ), 'post_status' => $this->post->post_status, 'date_gmt' => $this->post->post_date_gmt, 'modified_gmt' => $this->post->post_modified_gmt, From 6e13d27431e2f67e4ae959f12cafaa7600cd7cef Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Tue, 21 Jan 2025 14:11:06 +0100 Subject: [PATCH 04/25] Liniting --- .../content-distribution/test-outgoing-post.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/unit-tests/content-distribution/test-outgoing-post.php b/tests/unit-tests/content-distribution/test-outgoing-post.php index 36c1511e..ef925985 100644 --- a/tests/unit-tests/content-distribution/test-outgoing-post.php +++ b/tests/unit-tests/content-distribution/test-outgoing-post.php @@ -9,6 +9,7 @@ use Newspack_Network\Content_Distribution\Outgoing_Post; use Newspack_Network\Hub\Node as Hub_Node; +use WP_User; /** * Test the Outgoing_Post class. @@ -39,7 +40,12 @@ class TestOutgoingPost extends \WP_UnitTestCase { */ protected $outgoing_post; - private $some_editor; + /** + * An editor user. + * + * @var WP_User + */ + private WP_User $some_editor; /** * Set up. @@ -51,7 +57,12 @@ public function set_up() { // "Mock" the network node(s). update_option( Hub_Node::HUB_NODES_SYNCED_OPTION, $this->network ); - $post = $this->factory->post->create_and_get( [ 'post_type' => 'post', 'post_author' => $this->some_editor->ID ] ); + $post = $this->factory->post->create_and_get( + [ + 'post_type' => 'post', + 'post_author' => $this->some_editor->ID, + ] + ); $this->outgoing_post = new Outgoing_Post( $post ); $this->outgoing_post->set_distribution( [ $this->network[0]['url'] ] ); } From c4581cd8962c64558d71977b932f4b176d30a893 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Tue, 21 Jan 2025 19:44:55 +0100 Subject: [PATCH 05/25] Remane to avoid confusion --- includes/class-content-distribution.php | 2 +- ...{class-author-ingestion.php => class-incoming-authors.php} | 4 ++-- includes/content-distribution/class-incoming-post.php | 2 +- ...ass-author-distribution.php => class-outgoing-authors.php} | 2 +- includes/content-distribution/class-outgoing-post.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename includes/content-distribution/{class-author-ingestion.php => class-incoming-authors.php} (98%) rename includes/content-distribution/{class-author-distribution.php => class-outgoing-authors.php} (99%) diff --git a/includes/class-content-distribution.php b/includes/class-content-distribution.php index 1043369e..787a6f38 100644 --- a/includes/class-content-distribution.php +++ b/includes/class-content-distribution.php @@ -188,7 +188,7 @@ public static function handle_post_updated( $post ) { * * @param int $post_id The post ID. * - * @return @void + * @return void */ public static function handle_post_deleted( $post_id ) { if ( ! class_exists( 'Newspack\Data_Events' ) ) { diff --git a/includes/content-distribution/class-author-ingestion.php b/includes/content-distribution/class-incoming-authors.php similarity index 98% rename from includes/content-distribution/class-author-ingestion.php rename to includes/content-distribution/class-incoming-authors.php index 311b112d..97572c50 100644 --- a/includes/content-distribution/class-author-ingestion.php +++ b/includes/content-distribution/class-incoming-authors.php @@ -14,7 +14,7 @@ /** * Class to handle author ingestion for content distribution. */ -class Author_Ingestion { +class Incoming_Authors { /** * Gets the CoAuthors Plus main object, if present @@ -38,7 +38,7 @@ public static function get_coauthors_plus() { * * @return void */ - public static function ingest_authors_for_post( $post_id, $distributed_authors ) { + public static function ingest_authors_for_post( $post_id, $distributed_authors ): void { Debugger::log( 'Ingesting authors from distributed post.' ); diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index 15c07c2d..78f7410c 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -496,7 +496,7 @@ public function insert( $payload = [] ) { $this->ID = $post_id; $this->post = get_post( $this->ID ); - Author_Ingestion::ingest_authors_for_post( $this->ID, $post_data['authors'] ); + Incoming_Authors::ingest_authors_for_post( $this->ID, $post_data['authors'] ); // Handle post meta. $this->update_post_meta(); diff --git a/includes/content-distribution/class-author-distribution.php b/includes/content-distribution/class-outgoing-authors.php similarity index 99% rename from includes/content-distribution/class-author-distribution.php rename to includes/content-distribution/class-outgoing-authors.php index 9f911e84..3404d469 100644 --- a/includes/content-distribution/class-author-distribution.php +++ b/includes/content-distribution/class-outgoing-authors.php @@ -18,7 +18,7 @@ * Every time a post is distributed, we also send all the information about the author (or authors if CAP is enabled) * On the target site, the plugin will create the authors if they don't exist, and override the byline */ -class Author_Distribution { +class Outgoing_Authors { /** * Get the authors of a post to be added to the distribution payload. diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index b48898f8..8febc914 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -194,7 +194,7 @@ public function get_payload( $status_on_create = 'draft' ) { 'status_on_create' => $status_on_create, 'post_data' => [ 'title' => html_entity_decode( get_the_title( $this->post->ID ), ENT_QUOTES, get_bloginfo( 'charset' ) ), - 'authors' => Author_Distribution::get_authors_for_distribution( $this->post ), + 'authors' => Outgoing_Authors::get_authors_for_distribution( $this->post ), 'post_status' => $this->post->post_status, 'date_gmt' => $this->post->post_date_gmt, 'modified_gmt' => $this->post->post_modified_gmt, From cd2276c54e7e3c9f0709470cf97b210cebf11d41 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 23 Jan 2025 20:12:26 +0100 Subject: [PATCH 06/25] Add some comments on a way forward --- .../class-incoming-post.php | 2 +- .../class-outgoing-authors.php | 36 ++++++++++--------- .../class-outgoing-post.php | 4 ++- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index fa73e030..356eea3c 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -511,7 +511,7 @@ public function insert( $payload = [] ) { $this->ID = $post_id; $this->post = get_post( $this->ID ); - Incoming_Authors::ingest_authors_for_post( $this->ID, $post_data['authors'] ); + Incoming_Authors::ingest_authors_for_post( $this->ID, $post_data['author_list'] ); // Handle post meta. $this->update_meta(); diff --git a/includes/content-distribution/class-outgoing-authors.php b/includes/content-distribution/class-outgoing-authors.php index 3404d469..a50c1e7c 100644 --- a/includes/content-distribution/class-outgoing-authors.php +++ b/includes/content-distribution/class-outgoing-authors.php @@ -23,32 +23,19 @@ class Outgoing_Authors { /** * Get the authors of a post to be added to the distribution payload. * - * @param \WP_Post $post The post object. + * @param WP_Post $post The post object. * * @return array An array of authors. */ - public static function get_authors_for_distribution( $post ): array { - $author = self::get_wp_user_for_distribution( $post->post_author ); + public static function get_authors_for_distribution( WP_Post $post ): array { if ( ! function_exists( 'get_coauthors' ) ) { - if ( is_wp_error( $author ) ) { - Debugger::log( 'Error getting author ' . $post->post_author . ' for distribution on post ' . $post->ID . ': ' . $author->get_error_message() ); - - return []; - } - - return [ $author ]; + return self::get_wp_user_from_post_author( $post ); } $co_authors = get_coauthors( $post->ID ); if ( empty( $co_authors ) ) { - if ( is_wp_error( $author ) ) { - Debugger::log( 'Error getting author ' . $post->post_author . ' for distribution on post ' . $post->ID . ': ' . $author->get_error_message() ); - - return []; - } - - return [ $author ]; + return self::get_wp_user_from_post_author( $post ); } $authors = []; @@ -72,6 +59,21 @@ public static function get_authors_for_distribution( $post ): array { return $authors; } + private static function get_wp_user_from_post_author( $post ) { + if ( ! $post->post_author ) { + return []; + } + + $author = self::get_wp_user_for_distribution( $post->post_author ); + if ( is_wp_error( $author ) ) { + Debugger::log( 'Error getting author with id:' . $post->post_author . ' for distribution on post ' . $post->ID . ': ' . $author->get_error_message() ); + + return []; + } + + return [ $author ]; + } + /** * Gets the user data of a WP user to be distributed along with the post. * diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index 5474a0d6..1e754610 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -211,7 +211,9 @@ public function get_payload( $status_on_create = 'draft' ) { 'status_on_create' => $status_on_create, 'post_data' => [ 'title' => html_entity_decode( get_the_title( $this->post->ID ), ENT_QUOTES, get_bloginfo( 'charset' ) ), - 'authors' => Outgoing_Authors::get_authors_for_distribution( $this->post ), + // TODO: Refactor the get_authors_for_distribution function to live in this one to avoid confusion. + 'author' => Outgoing_Authors::get_authors_for_distribution( $this->post ), // TODO: name it clearly for ONE author. Not CAP. But alwaays set the "vanilla" author. + // Listen to set_post_terms, add a postmeta to the post with the distributable authors. (and ingest cleverly later) 'post_status' => $this->post->post_status, 'date_gmt' => $this->post->post_date_gmt, 'modified_gmt' => $this->post->post_modified_gmt, From caa506d99cb025bd2389715d38a43acf007be95b Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Fri, 24 Jan 2025 15:59:59 +0100 Subject: [PATCH 07/25] Move stuff around --- includes/class-content-distribution.php | 22 +++ ...-authors.php => class-incoming-author.php} | 2 +- .../class-incoming-cap.php | 96 ++++++++++++ .../class-incoming-post.php | 5 +- .../class-outgoing-author.php | 62 ++++++++ .../class-outgoing-authors.php | 145 ------------------ .../class-outgoing-cap.php | 109 +++++++++++++ .../class-outgoing-post.php | 8 +- 8 files changed, 299 insertions(+), 150 deletions(-) rename includes/content-distribution/{class-incoming-authors.php => class-incoming-author.php} (99%) create mode 100644 includes/content-distribution/class-incoming-cap.php create mode 100644 includes/content-distribution/class-outgoing-author.php delete mode 100644 includes/content-distribution/class-outgoing-authors.php create mode 100644 includes/content-distribution/class-outgoing-cap.php diff --git a/includes/class-content-distribution.php b/includes/class-content-distribution.php index 1038ecdb..7d77f729 100644 --- a/includes/class-content-distribution.php +++ b/includes/class-content-distribution.php @@ -61,6 +61,16 @@ public static function init() { Editor::init(); Canonical_Url::init(); Distributor_Migrator::init(); + + if ( Site_Role::is_hub() ) { + if ( self::is_co_authors_plus_active() ) { + Outgoing_Cap::init(); + } + } elseif ( Site_Role::is_node() ) { + if ( self::is_co_authors_plus_active() ) { + Outgoing_Cap::init(); + } + } } /** @@ -382,4 +392,16 @@ public static function distribute_post( $post, $status_on_create = 'draft' ) { update_post_meta( $post->ID, self::PAYLOAD_HASH_META, $payload_hash ); } } + + /** + * Helper to check if Co-Authors Plus is active. + * + * @return bool Whether Co-Authors Plus is active. + */ + public static function is_co_authors_plus_active(): bool { + global $coauthors_plus; + + return $coauthors_plus instanceof \CoAuthors_Plus; + } + } diff --git a/includes/content-distribution/class-incoming-authors.php b/includes/content-distribution/class-incoming-author.php similarity index 99% rename from includes/content-distribution/class-incoming-authors.php rename to includes/content-distribution/class-incoming-author.php index 97572c50..bae68b81 100644 --- a/includes/content-distribution/class-incoming-authors.php +++ b/includes/content-distribution/class-incoming-author.php @@ -14,7 +14,7 @@ /** * Class to handle author ingestion for content distribution. */ -class Incoming_Authors { +class Incoming_Author { /** * Gets the CoAuthors Plus main object, if present diff --git a/includes/content-distribution/class-incoming-cap.php b/includes/content-distribution/class-incoming-cap.php new file mode 100644 index 00000000..5f270a21 --- /dev/null +++ b/includes/content-distribution/class-incoming-cap.php @@ -0,0 +1,96 @@ + 'author', + ]; + + foreach ( User_Update_Watcher::$user_props as $prop ) { + if ( isset( $author[ $prop ] ) ) { + $insert_array[ $prop ] = $author[ $prop ]; + } + } + + $user = User_Utils::get_or_create_user_by_email( $author['user_email'], get_post_meta( $post_id, 'dt_original_site_url', true ), $author['ID'], $insert_array ); + + if ( is_wp_error( $user ) ) { + Debugger::log( 'Error creating user: ' . $user->get_error_message() ); + continue; + } + + foreach ( User_Update_Watcher::get_writable_meta() as $meta_key ) { + if ( isset( $author[ $meta_key ] ) ) { + update_user_meta( $user->ID, $meta_key, $author[ $meta_key ] ); + } + } + + User_Utils::maybe_sideload_avatar( $user->ID, $author, false ); + + // If CoAuthors Plus is not present, just assign the first author as the post author. + if ( ! $coauthors_plus ) { + Debugger::log( 'CoAuthors Plus not present, assigning first author as post author.' ); + wp_update_post( + [ + 'ID' => $post_id, + 'post_author' => $user->ID, + ] + ); + break; + } + + $coauthors[] = $user->user_nicename; + } + + if ( $coauthors_plus ) { + Debugger::log( 'CoAuthors Plus present, assigning coauthors:' ); + Debugger::log( $coauthors ); + $coauthors_plus->add_coauthors( $post_id, $coauthors ); + } + } +} diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index 356eea3c..2615aa27 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -511,7 +511,10 @@ public function insert( $payload = [] ) { $this->ID = $post_id; $this->post = get_post( $this->ID ); - Incoming_Authors::ingest_authors_for_post( $this->ID, $post_data['author_list'] ); + Incoming_Author::ingest_authors_for_post( $this->ID, $post_data['author'] ); + if ( Content_Distribution::is_co_authors_plus_active() ) { + Incoming_Cap::ingest_cap_authors_for_post( $this->ID, $post_data['post_meta']['newspack_network_cap_authors'] ?? [] ); + } // Handle post meta. $this->update_meta(); diff --git a/includes/content-distribution/class-outgoing-author.php b/includes/content-distribution/class-outgoing-author.php new file mode 100644 index 00000000..d850164d --- /dev/null +++ b/includes/content-distribution/class-outgoing-author.php @@ -0,0 +1,62 @@ + 'wp_user', + 'ID' => $user->ID, + ]; + + foreach ( User_Update_Watcher::$user_props as $prop ) { + if ( isset( $user->$prop ) ) { + $author[ $prop ] = $user->$prop; + } + } + + // CoAuthors' guest authors have a 'website' property. + if ( isset( $user->website ) ) { + $author['website'] = $user->website; + } + + foreach ( User_Update_Watcher::$watched_meta as $meta_key ) { + $author[ $meta_key ] = get_user_meta( $user->ID, $meta_key, true ); + } + + return $author; + } + +} diff --git a/includes/content-distribution/class-outgoing-authors.php b/includes/content-distribution/class-outgoing-authors.php deleted file mode 100644 index a50c1e7c..00000000 --- a/includes/content-distribution/class-outgoing-authors.php +++ /dev/null @@ -1,145 +0,0 @@ -ID ); - if ( empty( $co_authors ) ) { - return self::get_wp_user_from_post_author( $post ); - } - - $authors = []; - - foreach ( $co_authors as $co_author ) { - if ( is_a( $co_author, 'WP_User' ) ) { - // This will never return an error because we are checking for is_a() first. - $authors[] = self::get_wp_user_for_distribution( $co_author ); - continue; - } - - $guest_author = self::get_guest_author_for_distribution( $co_author ); - if ( is_wp_error( $guest_author ) ) { - Debugger::log( 'Error getting guest author for distribution on post ' . $post->ID . ': ' . $guest_author->get_error_message() ); - Debugger::log( $co_author ); - continue; - } - $authors[] = $guest_author; - } - - return $authors; - } - - private static function get_wp_user_from_post_author( $post ) { - if ( ! $post->post_author ) { - return []; - } - - $author = self::get_wp_user_for_distribution( $post->post_author ); - if ( is_wp_error( $author ) ) { - Debugger::log( 'Error getting author with id:' . $post->post_author . ' for distribution on post ' . $post->ID . ': ' . $author->get_error_message() ); - - return []; - } - - return [ $author ]; - } - - /** - * Gets the user data of a WP user to be distributed along with the post. - * - * @param int|WP_Post $user The user ID or object. - * - * @return WP_Error|array - */ - private static function get_wp_user_for_distribution( $user ) { - if ( ! is_a( $user, 'WP_User' ) ) { - $user = get_user_by( 'ID', $user ); - } - - if ( ! $user ) { - return new WP_Error( 'Error getting WP User details for distribution. Invalid User' ); - } - - $author = [ - 'type' => 'wp_user', - 'ID' => $user->ID, - ]; - - foreach ( User_Update_Watcher::$user_props as $prop ) { - if ( isset( $user->$prop ) ) { - $author[ $prop ] = $user->$prop; - } - } - - // CoAuthors' guest authors have a 'website' property. - if ( isset( $user->website ) ) { - $author['website'] = $user->website; - } - - foreach ( User_Update_Watcher::$watched_meta as $meta_key ) { - $author[ $meta_key ] = get_user_meta( $user->ID, $meta_key, true ); - } - - return $author; - } - - /** - * Get the guest author data to be distributed along with the post. - * - * @param object $guest_author The Guest Author object. - * - * @return WP_Error|array - */ - private static function get_guest_author_for_distribution( $guest_author ) { - - // CoAuthors plugin existence was checked in get_authors_for_distribution(). - global $coauthors_plus; - - if ( ! is_object( $guest_author ) || ! isset( $guest_author->type ) || 'guest-author' !== $guest_author->type ) { - return new WP_Error( 'Error getting guest author details for distribution. Invalid Guest Author' ); - } - - $author = (array) $guest_author; - $author['type'] = 'guest_author'; - - // Gets the guest author avatar. - // We only want to send an actual uploaded avatar, we don't want to send the fallback avatar, like gravatar. - // If no avatar was set, let it default to the fallback set in the target site. - $author_avatar = $coauthors_plus->guest_authors->get_guest_author_thumbnail( $guest_author, 80 ); - if ( $author_avatar ) { - $author['avatar_img_tag'] = $author_avatar; - } - - return $author; - } -} diff --git a/includes/content-distribution/class-outgoing-cap.php b/includes/content-distribution/class-outgoing-cap.php new file mode 100644 index 00000000..afd923a3 --- /dev/null +++ b/includes/content-distribution/class-outgoing-cap.php @@ -0,0 +1,109 @@ +is_distributed() ) { + return; + } + + $cap_authors = self::get_cap_authors_for_distribution( $outgoing_post->get_post() ); + update_post_meta( $object_id, 'newspack_network_cap_authors', $cap_authors ); + + } catch ( \InvalidArgumentException ) { + return; + } + + } + + public static function get_cap_authors_for_distribution( WP_Post $post ): array { + if ( ! function_exists( 'get_coauthors' ) ) { + return []; + } + + $co_authors = get_coauthors( $post->ID ); + if ( empty( $co_authors ) ) { + return []; + } + + $authors = []; + + foreach ( $co_authors as $co_author ) { + if ( is_a( $co_author, 'WP_User' ) ) { + // This will never return an error because we are checking for is_a() first. + $authors[] = Outgoing_Author::get_wp_user_for_distribution( $co_author ); + continue; + } + + $guest_author = self::get_guest_author_for_distribution( $co_author ); + if ( is_wp_error( $guest_author ) ) { + Debugger::log( 'Error getting guest author for distribution on post ' . $post->ID . ': ' . $guest_author->get_error_message() ); + Debugger::log( $co_author ); + continue; + } + $authors[] = $guest_author; + } + + return $authors; + } + + + /** + * Get the guest author data to be distributed along with the post. + * + * @param object $guest_author The Guest Author object. + * + * @return WP_Error|array + */ + private static function get_guest_author_for_distribution( $guest_author ) { + + // CoAuthors plugin existence was checked in get_authors_for_distribution(). + global $coauthors_plus; + + if ( ! is_object( $guest_author ) || ! isset( $guest_author->type ) || 'guest-author' !== $guest_author->type ) { + return new WP_Error( 'Error getting guest author details for distribution. Invalid Guest Author' ); + } + + $author = (array) $guest_author; + $author['type'] = 'guest_author'; + + // Gets the guest author avatar. + // We only want to send an actual uploaded avatar, we don't want to send the fallback avatar, like gravatar. + // If no avatar was set, let it default to the fallback set in the target site. + $author_avatar = $coauthors_plus->guest_authors->get_guest_author_thumbnail( $guest_author, 80 ); + if ( $author_avatar ) { + $author['avatar_img_tag'] = $author_avatar; + } + + return $author; + } +} \ No newline at end of file diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index 1e754610..e243068b 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -202,6 +202,10 @@ public function get_payload_hash( $payload = null ) { * @return array|WP_Error The post payload or WP_Error if the post is invalid. */ public function get_payload( $status_on_create = 'draft' ) { + $post_author = 0; + if ( $this->post->post_author ) { + $post_author = Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ); + } return [ 'site_url' => get_bloginfo( 'url' ), 'post_id' => $this->post->ID, @@ -211,9 +215,7 @@ public function get_payload( $status_on_create = 'draft' ) { 'status_on_create' => $status_on_create, 'post_data' => [ 'title' => html_entity_decode( get_the_title( $this->post->ID ), ENT_QUOTES, get_bloginfo( 'charset' ) ), - // TODO: Refactor the get_authors_for_distribution function to live in this one to avoid confusion. - 'author' => Outgoing_Authors::get_authors_for_distribution( $this->post ), // TODO: name it clearly for ONE author. Not CAP. But alwaays set the "vanilla" author. - // Listen to set_post_terms, add a postmeta to the post with the distributable authors. (and ingest cleverly later) + 'author' => $post_author, 'post_status' => $this->post->post_status, 'date_gmt' => $this->post->post_date_gmt, 'modified_gmt' => $this->post->post_modified_gmt, From beed21f950862cc9810b78fde30f31b58c63df55 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Mon, 27 Jan 2025 12:08:52 +0100 Subject: [PATCH 08/25] most things work now --- includes/class-content-distribution.php | 12 +-- ...outgoing-cap.php => class-cap-authors.php} | 94 +++++++++++++--- .../class-incoming-author.php | 102 +++++++----------- .../class-incoming-cap.php | 96 ----------------- .../class-incoming-post.php | 10 +- .../class-outgoing-author.php | 2 - includes/utils/class-users.php | 2 +- 7 files changed, 128 insertions(+), 190 deletions(-) rename includes/content-distribution/{class-outgoing-cap.php => class-cap-authors.php} (52%) delete mode 100644 includes/content-distribution/class-incoming-cap.php diff --git a/includes/class-content-distribution.php b/includes/class-content-distribution.php index 7d77f729..40001052 100644 --- a/includes/class-content-distribution.php +++ b/includes/class-content-distribution.php @@ -8,6 +8,7 @@ namespace Newspack_Network; use Newspack\Data_Events; +use Newspack_Network\Content_Distribution\Cap_Authors; use Newspack_Network\Content_Distribution\CLI; use Newspack_Network\Content_Distribution\Admin; use Newspack_Network\Content_Distribution\API; @@ -61,16 +62,7 @@ public static function init() { Editor::init(); Canonical_Url::init(); Distributor_Migrator::init(); - - if ( Site_Role::is_hub() ) { - if ( self::is_co_authors_plus_active() ) { - Outgoing_Cap::init(); - } - } elseif ( Site_Role::is_node() ) { - if ( self::is_co_authors_plus_active() ) { - Outgoing_Cap::init(); - } - } + Cap_Authors::init(); } /** diff --git a/includes/content-distribution/class-outgoing-cap.php b/includes/content-distribution/class-cap-authors.php similarity index 52% rename from includes/content-distribution/class-outgoing-cap.php rename to includes/content-distribution/class-cap-authors.php index afd923a3..057e1855 100644 --- a/includes/content-distribution/class-outgoing-cap.php +++ b/includes/content-distribution/class-cap-authors.php @@ -1,6 +1,6 @@ get_post() ); - update_post_meta( $object_id, 'newspack_network_cap_authors', $cap_authors ); + update_post_meta( $object_id, self::CAP_AUTHORS_META_KEY, $cap_authors ); } catch ( \InvalidArgumentException ) { return; } - } - public static function get_cap_authors_for_distribution( WP_Post $post ): array { - if ( ! function_exists( 'get_coauthors' ) ) { - return []; - } + private static function get_cap_authors_for_distribution( WP_Post $post ): array { $co_authors = get_coauthors( $post->ID ); if ( empty( $co_authors ) ) { @@ -76,6 +104,45 @@ public static function get_cap_authors_for_distribution( WP_Post $post ): array return $authors; } + /** + * Ingest authors for a post distributed to this site + * + * @param int $post_id The post ID. + * @param string $site_url The site URL. + * @param array $cap_authors Array of distributed authors. + * + * @return void + */ + public static function ingest_cap_authors_for_post( int $post_id, string $site_url, array $cap_authors ): void { + if ( ! self::is_co_authors_plus_active() ) { + return; + } + + $cap_authors = reset( $cap_authors ); // It comes as a multiple. TODO + + Debugger::log( 'Ingesting authors from networked post.' ); + User_Update_Watcher::$enabled = false; + + $coauthors = []; + + foreach ( $cap_authors as $author ) { + if ( 'wp_user' === ( $author['type'] ?? '' ) ) { + $user = Incoming_Author::get_wp_user_author( $post_id, $author ); + if ( is_wp_error( $user ) ) { + Debugger::log( 'Error ingesting author: ' . $user->get_error_message() ); + continue; + } + $coauthors[] = $user->user_nicename; + continue; + } elseif ( 'guest-author' === ( $author['type'] ?? '' ) ) { + // TODO. How do I get actual guest users enabled? + } + } + + global $coauthors_plus; + // Do this even if the array is empty, to clear out any existing authors. + $coauthors_plus->add_coauthors( $post_id, $coauthors ); + } /** * Get the guest author data to be distributed along with the post. @@ -84,9 +151,8 @@ public static function get_cap_authors_for_distribution( WP_Post $post ): array * * @return WP_Error|array */ - private static function get_guest_author_for_distribution( $guest_author ) { + private static function get_guest_author_for_distribution( $guest_author ): array|WP_Error { - // CoAuthors plugin existence was checked in get_authors_for_distribution(). global $coauthors_plus; if ( ! is_object( $guest_author ) || ! isset( $guest_author->type ) || 'guest-author' !== $guest_author->type ) { @@ -106,4 +172,4 @@ private static function get_guest_author_for_distribution( $guest_author ) { return $author; } -} \ No newline at end of file +} diff --git a/includes/content-distribution/class-incoming-author.php b/includes/content-distribution/class-incoming-author.php index bae68b81..fe8519e3 100644 --- a/includes/content-distribution/class-incoming-author.php +++ b/includes/content-distribution/class-incoming-author.php @@ -17,90 +17,66 @@ class Incoming_Author { /** - * Gets the CoAuthors Plus main object, if present + * Ingest authors for a post distributed to this site + * + * @param int $post_id The post ID. + * @param array $author The distributed authors array. * - * @return false|\CoAuthors_Plus + * @return void */ - public static function get_coauthors_plus() { - global $coauthors_plus; - if ( ! $coauthors_plus instanceof \CoAuthors_Plus ) { - return false; - } - - return $coauthors_plus; + public static function ingest_author_for_post( int $post_id, array $author ): void { + $author = self::get_wp_user_author( $post_id, $author ); + wp_update_post( + [ + 'ID' => $post_id, + 'post_author' => $author->ID, + ] + ); } /** * Ingest authors for a post distributed to this site * * @param int $post_id The post ID. - * @param array $distributed_authors The distributed authors array. + * @param array $author The distributed authors array. * - * @return void + * @return \WP_User|\WP_Error The user object or false on failure. */ - public static function ingest_authors_for_post( $post_id, $distributed_authors ): void { - - Debugger::log( 'Ingesting authors from distributed post.' ); + public static function get_wp_user_author( int $post_id, array $author ) { User_Update_Watcher::$enabled = false; - update_post_meta( $post_id, 'newspack_network_authors', $distributed_authors ); - - $coauthors_plus = self::get_coauthors_plus(); - $coauthors = []; + $insert_array = [ + 'role' => 'author', + ]; - foreach ( $distributed_authors as $author ) { - // We only ingest WP Users. Guest authors are only stored in the newspack_network_authors post meta. - if ( empty( $author['type'] ) || 'wp_user' != $author['type'] ) { - continue; + foreach ( User_Update_Watcher::$user_props as $prop ) { + if ( isset( $author[ $prop ] ) ) { + $insert_array[ $prop ] = $author[ $prop ]; } + } - Debugger::log( 'Ingesting author: ' . $author['user_email'] ); - - $insert_array = [ - 'role' => 'author', - ]; - - foreach ( User_Update_Watcher::$user_props as $prop ) { - if ( isset( $author[ $prop ] ) ) { - $insert_array[ $prop ] = $author[ $prop ]; - } - } - - $user = User_Utils::get_or_create_user_by_email( $author['user_email'], get_post_meta( $post_id, 'dt_original_site_url', true ), $author['ID'], $insert_array ); - - if ( is_wp_error( $user ) ) { - Debugger::log( 'Error creating user: ' . $user->get_error_message() ); - continue; - } + $user = User_Utils::get_or_create_user_by_email( + $author['user_email'], + get_post_meta( $post_id, 'dt_original_site_url', true ), // TODO. + $author['ID'], + $insert_array + ); - foreach ( User_Update_Watcher::get_writable_meta() as $meta_key ) { - if ( isset( $author[ $meta_key ] ) ) { - update_user_meta( $user->ID, $meta_key, $author[ $meta_key ] ); - } - } + if ( is_wp_error( $user ) ) { + Debugger::log( 'Error creating user: ' . $user->get_error_message() ); - User_Utils::maybe_sideload_avatar( $user->ID, $author, false ); + return $user; + } - // If CoAuthors Plus is not present, just assign the first author as the post author. - if ( ! $coauthors_plus ) { - Debugger::log( 'CoAuthors Plus not present, assigning first author as post author.' ); - wp_update_post( - [ - 'ID' => $post_id, - 'post_author' => $user->ID, - ] - ); - break; + foreach ( User_Update_Watcher::get_writable_meta() as $meta_key ) { + if ( isset( $author[ $meta_key ] ) ) { + update_user_meta( $user->ID, $meta_key, $author[ $meta_key ] ); } - - $coauthors[] = $user->user_nicename; } - if ( $coauthors_plus ) { - Debugger::log( 'CoAuthors Plus present, assigning coauthors:' ); - Debugger::log( $coauthors ); - $coauthors_plus->add_coauthors( $post_id, $coauthors ); - } + User_Utils::maybe_sideload_avatar( $user->ID, $author, false ); + + return $user; } } diff --git a/includes/content-distribution/class-incoming-cap.php b/includes/content-distribution/class-incoming-cap.php deleted file mode 100644 index 5f270a21..00000000 --- a/includes/content-distribution/class-incoming-cap.php +++ /dev/null @@ -1,96 +0,0 @@ - 'author', - ]; - - foreach ( User_Update_Watcher::$user_props as $prop ) { - if ( isset( $author[ $prop ] ) ) { - $insert_array[ $prop ] = $author[ $prop ]; - } - } - - $user = User_Utils::get_or_create_user_by_email( $author['user_email'], get_post_meta( $post_id, 'dt_original_site_url', true ), $author['ID'], $insert_array ); - - if ( is_wp_error( $user ) ) { - Debugger::log( 'Error creating user: ' . $user->get_error_message() ); - continue; - } - - foreach ( User_Update_Watcher::get_writable_meta() as $meta_key ) { - if ( isset( $author[ $meta_key ] ) ) { - update_user_meta( $user->ID, $meta_key, $author[ $meta_key ] ); - } - } - - User_Utils::maybe_sideload_avatar( $user->ID, $author, false ); - - // If CoAuthors Plus is not present, just assign the first author as the post author. - if ( ! $coauthors_plus ) { - Debugger::log( 'CoAuthors Plus not present, assigning first author as post author.' ); - wp_update_post( - [ - 'ID' => $post_id, - 'post_author' => $user->ID, - ] - ); - break; - } - - $coauthors[] = $user->user_nicename; - } - - if ( $coauthors_plus ) { - Debugger::log( 'CoAuthors Plus present, assigning coauthors:' ); - Debugger::log( $coauthors ); - $coauthors_plus->add_coauthors( $post_id, $coauthors ); - } - } -} diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index add41bd7..38ff1792 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -511,10 +511,12 @@ public function insert( $payload = [] ) { $this->ID = $post_id; $this->post = get_post( $this->ID ); - Incoming_Author::ingest_authors_for_post( $this->ID, $post_data['author'] ); - if ( Content_Distribution_Class::is_co_authors_plus_active() ) { - Incoming_Cap::ingest_cap_authors_for_post( $this->ID, $post_data['post_meta']['newspack_network_cap_authors'] ?? [] ); - } + Incoming_Author::ingest_author_for_post( $this->ID, $post_data['author'] ); + Cap_Authors::ingest_cap_authors_for_post( + $this->ID, + $this->get_original_site_url(), + $this->payload['post_data']['post_meta'][ Cap_Authors::CAP_AUTHORS_META_KEY ] ?? [] + ); // Handle post meta. $this->update_meta(); diff --git a/includes/content-distribution/class-outgoing-author.php b/includes/content-distribution/class-outgoing-author.php index d850164d..b0293be8 100644 --- a/includes/content-distribution/class-outgoing-author.php +++ b/includes/content-distribution/class-outgoing-author.php @@ -7,7 +7,6 @@ namespace Newspack_Network\Content_Distribution; -use Newspack_Network\Debugger; use Newspack_Network\User_Update_Watcher; use WP_Error; use WP_Post; @@ -58,5 +57,4 @@ public static function get_wp_user_for_distribution( $user ) { return $author; } - } diff --git a/includes/utils/class-users.php b/includes/utils/class-users.php index 4687063d..b4d80b96 100644 --- a/includes/utils/class-users.php +++ b/includes/utils/class-users.php @@ -23,7 +23,7 @@ class Users { * @param string $remote_site_url The URL of the remote site. Used only when a new user is created. * @param string $remote_id The ID of the user in the remote site. Used only when a new user is created. * @param array $insert_array An array of additional fields to be passed to wp_insert_user() when creating a new user. Use this to set the user's role, the default is NEWSPACK_NETWORK_READER_ROLE. - * @return WP_User|WP_Error + * @return \WP_User|\WP_Error */ public static function get_or_create_user_by_email( $email, $remote_site_url, $remote_id, $insert_array = [] ) { $existing_user = get_user_by( 'email', $email ); From 8e770bb0e41272e91174bbbc1fb2fc13253e1dc5 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Mon, 27 Jan 2025 20:28:23 +0100 Subject: [PATCH 09/25] Fix tests --- .../class-cap-authors.php | 20 ++++++++++++++++--- .../class-incoming-author.php | 3 +++ .../class-outgoing-post.php | 2 +- .../test-incoming-post.php | 5 +++++ .../test-outgoing-post.php | 8 +++++--- .../unit-tests/content-distribution/util.php | 6 ++++-- 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 057e1855..5bfa991d 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -46,11 +46,19 @@ public static function is_co_authors_plus_active(): bool { return $coauthors_plus instanceof \CoAuthors_Plus && function_exists( 'get_coauthors' ); } - /** * Action callback. * * Add a postmeta entry with the Co-Authors Plus authors for outgoing posts. + * + * @param int $object_id The object ID. + * @param array $terms The terms. + * @param array $tt_ids The term taxonomy IDs. + * @param string $taxonomy The taxonomy. + * @param bool $append Whether to append. + * @param array $old_tt_ids The old term taxonomy IDs. + * + * @return void */ public static function handle_cap_author_change( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ): void { if ( 'author' !== $taxonomy ) { // Co-Authors Plus author taxonomy. @@ -76,6 +84,13 @@ public static function handle_cap_author_change( $object_id, $terms, $tt_ids, $t } } + /** + * Get the Co-Authors Plus authors for distribution. + * + * @param WP_Post $post Post to get authors for. + * + * @return array Array of authors in distributable format. + */ private static function get_cap_authors_for_distribution( WP_Post $post ): array { $co_authors = get_coauthors( $post->ID ); @@ -118,7 +133,7 @@ public static function ingest_cap_authors_for_post( int $post_id, string $site_u return; } - $cap_authors = reset( $cap_authors ); // It comes as a multiple. TODO + $cap_authors = reset( $cap_authors ); Debugger::log( 'Ingesting authors from networked post.' ); User_Update_Watcher::$enabled = false; @@ -133,7 +148,6 @@ public static function ingest_cap_authors_for_post( int $post_id, string $site_u continue; } $coauthors[] = $user->user_nicename; - continue; } elseif ( 'guest-author' === ( $author['type'] ?? '' ) ) { // TODO. How do I get actual guest users enabled? } diff --git a/includes/content-distribution/class-incoming-author.php b/includes/content-distribution/class-incoming-author.php index fe8519e3..7edcbfe6 100644 --- a/includes/content-distribution/class-incoming-author.php +++ b/includes/content-distribution/class-incoming-author.php @@ -25,6 +25,9 @@ class Incoming_Author { * @return void */ public static function ingest_author_for_post( int $post_id, array $author ): void { + if ( empty( $author ) ) { + return; + } $author = self::get_wp_user_author( $post_id, $author ); wp_update_post( [ diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index d60bb126..6e42db4e 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -202,7 +202,7 @@ public function get_payload_hash( $payload = null ) { * @return array|WP_Error The post payload or WP_Error if the post is invalid. */ public function get_payload( $status_on_create = 'draft' ) { - $post_author = 0; + $post_author = []; if ( $this->post->post_author ) { $post_author = Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ); } diff --git a/tests/unit-tests/content-distribution/test-incoming-post.php b/tests/unit-tests/content-distribution/test-incoming-post.php index a717e4c3..b8b98b42 100644 --- a/tests/unit-tests/content-distribution/test-incoming-post.php +++ b/tests/unit-tests/content-distribution/test-incoming-post.php @@ -8,6 +8,7 @@ namespace Test\Content_Distribution; use Newspack_Network\Content_Distribution\Incoming_Post; +use Newspack_Network\Content_Distribution\Outgoing_Author; /** * Test the Incoming_Post class. @@ -27,6 +28,8 @@ class TestIncomingPost extends \WP_UnitTestCase { */ protected $node_2 = 'https://node2.test'; + protected $some_editor; + /** * A linked post. * @@ -51,6 +54,8 @@ public function set_up() { update_option( 'siteurl', $this->node_2 ); update_option( 'home', $this->node_2 ); + $this->some_editor = $this->factory->user->create_and_get( [ 'role' => 'editor' ] ); + $this->incoming_post = new Incoming_Post( $this->get_sample_payload() ); } diff --git a/tests/unit-tests/content-distribution/test-outgoing-post.php b/tests/unit-tests/content-distribution/test-outgoing-post.php index ef925985..0d8bc070 100644 --- a/tests/unit-tests/content-distribution/test-outgoing-post.php +++ b/tests/unit-tests/content-distribution/test-outgoing-post.php @@ -7,6 +7,8 @@ namespace Test\Content_Distribution; +use Newspack_Network\Content_Distribution\Incoming_Author; +use Newspack_Network\Content_Distribution\Outgoing_Author; use Newspack_Network\Content_Distribution\Outgoing_Post; use Newspack_Network\Hub\Node as Hub_Node; use WP_User; @@ -163,7 +165,7 @@ public function test_get_payload() { 'thumbnail_url', 'taxonomy', 'post_meta', - 'authors', + 'author', ]; $this->assertEmpty( array_diff( $post_data_keys, array_keys( $payload['post_data'] ) ) ); $this->assertEmpty( array_diff( array_keys( $payload['post_data'] ), $post_data_keys ) ); @@ -174,8 +176,8 @@ public function test_get_payload() { */ public function test_authors_data(): void { $payload = $this->outgoing_post->get_payload(); - $this->assertNotEmpty( $payload['post_data']['authors'][0] ); - $this->assertEquals( $this->some_editor->ID, $payload['post_data']['authors'][0]['ID'] ); + $this->assertNotEmpty( $payload['post_data']['author'] ); + $this->assertEquals( $this->some_editor->user_email, $payload['post_data']['author']['user_email'] ); } /** diff --git a/tests/unit-tests/content-distribution/util.php b/tests/unit-tests/content-distribution/util.php index e02741f7..7eacd0ea 100644 --- a/tests/unit-tests/content-distribution/util.php +++ b/tests/unit-tests/content-distribution/util.php @@ -7,10 +7,11 @@ namespace Test\Content_Distribution; + /** * Get a sample distributed post payload for testing. * - * @param string $origin Origin site URL. + * @param string $origin Origin site URL. * @param string $destination Destination site URL. * * @return array @@ -22,6 +23,7 @@ function get_sample_payload( $origin = '', $destination = '' ) { if ( empty( $destination ) ) { $destination = 'https://destination.test'; } + return [ 'site_url' => $origin, 'post_id' => 1, @@ -31,7 +33,7 @@ function get_sample_payload( $origin = '', $destination = '' ) { 'status_on_create' => 'draft', 'post_data' => [ 'title' => 'Title', - 'authors' => [], + 'author' => [], 'post_status' => 'publish', 'date_gmt' => '2021-01-01 00:00:00', 'modified_gmt' => '2021-01-01 00:00:00', From 4150ad0b29fa830931409fb71ea5da589873ac42 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Tue, 28 Jan 2025 16:03:50 +0100 Subject: [PATCH 10/25] Work in progress --- includes/class-content-distribution.php | 10 +-- .../class-cap-authors.php | 64 +++++++++++++------ .../class-incoming-post.php | 2 +- .../class-outgoing-author.php | 9 ++- 4 files changed, 56 insertions(+), 29 deletions(-) diff --git a/includes/class-content-distribution.php b/includes/class-content-distribution.php index 40001052..12a0be3b 100644 --- a/includes/class-content-distribution.php +++ b/includes/class-content-distribution.php @@ -8,15 +8,16 @@ namespace Newspack_Network; use Newspack\Data_Events; -use Newspack_Network\Content_Distribution\Cap_Authors; -use Newspack_Network\Content_Distribution\CLI; use Newspack_Network\Content_Distribution\Admin; use Newspack_Network\Content_Distribution\API; -use Newspack_Network\Content_Distribution\Editor; use Newspack_Network\Content_Distribution\Canonical_Url; +use Newspack_Network\Content_Distribution\Cap_Authors; +use Newspack_Network\Content_Distribution\Cap_Authors_Filters; +use Newspack_Network\Content_Distribution\CLI; +use Newspack_Network\Content_Distribution\Distributor_Migrator; +use Newspack_Network\Content_Distribution\Editor; use Newspack_Network\Content_Distribution\Incoming_Post; use Newspack_Network\Content_Distribution\Outgoing_Post; -use Newspack_Network\Content_Distribution\Distributor_Migrator; use WP_Post; /** @@ -63,6 +64,7 @@ public static function init() { Canonical_Url::init(); Distributor_Migrator::init(); Cap_Authors::init(); + Cap_Authors_Filters::init(); } /** diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 5bfa991d..ff4163f4 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -20,7 +20,10 @@ class Cap_Authors { /** * Meta key for Co-Authors Plus authors for networked posts. */ - const CAP_AUTHORS_META_KEY = 'newspack_network_cap_authors'; + const CAP_AUTHORS_TRANSFER_META_KEY = 'newspack_network_cap_authors_transfer'; + + // TODO. Explain the difference here + const CAP_GUEST_AUTHORS_META_KEY = 'newspack_network_cap_guest_authors'; /** * Get things going. @@ -33,6 +36,14 @@ public static function init(): void { } add_action( 'set_object_terms', [ __CLASS__, 'handle_cap_author_change' ], 10, 6 ); + add_filter('newspack_network_content_distribution_reserved_post_meta_keys', [ __CLASS__, 'add_ignored_postmeta_keys' ], 10, 2 ); + } + + public static function add_ignored_postmeta_keys( $keys ) { + if (! in_array(self::CAP_GUEST_AUTHORS_META_KEY, $keys)) { + $keys[] = self::CAP_GUEST_AUTHORS_META_KEY; + } + return $keys; } /** @@ -51,12 +62,12 @@ public static function is_co_authors_plus_active(): bool { * * Add a postmeta entry with the Co-Authors Plus authors for outgoing posts. * - * @param int $object_id The object ID. - * @param array $terms The terms. - * @param array $tt_ids The term taxonomy IDs. + * @param int $object_id The object ID. + * @param array $terms The terms. + * @param array $tt_ids The term taxonomy IDs. * @param string $taxonomy The taxonomy. - * @param bool $append Whether to append. - * @param array $old_tt_ids The old term taxonomy IDs. + * @param bool $append Whether to append. + * @param array $old_tt_ids The old term taxonomy IDs. * * @return void */ @@ -77,7 +88,7 @@ public static function handle_cap_author_change( $object_id, $terms, $tt_ids, $t } $cap_authors = self::get_cap_authors_for_distribution( $outgoing_post->get_post() ); - update_post_meta( $object_id, self::CAP_AUTHORS_META_KEY, $cap_authors ); + update_post_meta( $object_id, self::CAP_AUTHORS_TRANSFER_META_KEY, $cap_authors ); } catch ( \InvalidArgumentException ) { return; @@ -122,9 +133,9 @@ private static function get_cap_authors_for_distribution( WP_Post $post ): array /** * Ingest authors for a post distributed to this site * - * @param int $post_id The post ID. + * @param int $post_id The post ID. * @param string $site_url The site URL. - * @param array $cap_authors Array of distributed authors. + * @param array $cap_authors Array of distributed authors. * * @return void */ @@ -138,21 +149,32 @@ public static function ingest_cap_authors_for_post( int $post_id, string $site_u Debugger::log( 'Ingesting authors from networked post.' ); User_Update_Watcher::$enabled = false; - $coauthors = []; + $coauthors = []; + $guest_authors = []; foreach ( $cap_authors as $author ) { - if ( 'wp_user' === ( $author['type'] ?? '' ) ) { - $user = Incoming_Author::get_wp_user_author( $post_id, $author ); - if ( is_wp_error( $user ) ) { - Debugger::log( 'Error ingesting author: ' . $user->get_error_message() ); - continue; - } - $coauthors[] = $user->user_nicename; - } elseif ( 'guest-author' === ( $author['type'] ?? '' ) ) { - // TODO. How do I get actual guest users enabled? + $author_type = $author['type'] ?? ''; + switch ( $author_type ) { + case 'wp_user': + $user = Incoming_Author::get_wp_user_author( $post_id, $author ); + if ( is_wp_error( $user ) ) { + Debugger::log( 'Error ingesting author: ' . $user->get_error_message() ); + } + $coauthors[] = $user->user_nicename; + break; + case 'guest_author': + break; + default: + Debugger::log( sprintf( 'Error ingesting author: Invalid author type "%s"', $author_type ) ); } } + if ( ! empty( $guest_authors ) ) { + update_post_meta( $post_id, self::CAP_GUEST_AUTHORS_META_KEY, $guest_authors ); + } else { + delete_post_meta( $post_id, self::CAP_GUEST_AUTHORS_META_KEY ); + } + global $coauthors_plus; // Do this even if the array is empty, to clear out any existing authors. $coauthors_plus->add_coauthors( $post_id, $coauthors ); @@ -166,7 +188,6 @@ public static function ingest_cap_authors_for_post( int $post_id, string $site_u * @return WP_Error|array */ private static function get_guest_author_for_distribution( $guest_author ): array|WP_Error { - global $coauthors_plus; if ( ! is_object( $guest_author ) || ! isset( $guest_author->type ) || 'guest-author' !== $guest_author->type ) { @@ -184,6 +205,7 @@ private static function get_guest_author_for_distribution( $guest_author ): arra $author['avatar_img_tag'] = $author_avatar; } - return $author; + return array_filter( $author ); } + } diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index 38ff1792..8ec21295 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -515,7 +515,7 @@ public function insert( $payload = [] ) { Cap_Authors::ingest_cap_authors_for_post( $this->ID, $this->get_original_site_url(), - $this->payload['post_data']['post_meta'][ Cap_Authors::CAP_AUTHORS_META_KEY ] ?? [] + $this->payload['post_data']['post_meta'][ Cap_Authors::CAP_AUTHORS_TRANSFER_META_KEY ] ?? [] ); // Handle post meta. diff --git a/includes/content-distribution/class-outgoing-author.php b/includes/content-distribution/class-outgoing-author.php index b0293be8..017373a1 100644 --- a/includes/content-distribution/class-outgoing-author.php +++ b/includes/content-distribution/class-outgoing-author.php @@ -41,18 +41,21 @@ public static function get_wp_user_for_distribution( $user ) { ]; foreach ( User_Update_Watcher::$user_props as $prop ) { - if ( isset( $user->$prop ) ) { + if ( ! empty( $user->$prop ) ) { $author[ $prop ] = $user->$prop; } } // CoAuthors' guest authors have a 'website' property. - if ( isset( $user->website ) ) { + if ( ! empty( $user->website ) ) { $author['website'] = $user->website; } foreach ( User_Update_Watcher::$watched_meta as $meta_key ) { - $author[ $meta_key ] = get_user_meta( $user->ID, $meta_key, true ); + $value = get_user_meta( $user->ID, $meta_key, true ); + if ( ! empty( $value ) ) { + $author[ $meta_key ] = $value; + } } return $author; From 9b6686338d431997a527f0233e0feecc42ddbd54 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Tue, 28 Jan 2025 17:40:46 +0100 Subject: [PATCH 11/25] PHPCS --- .../class-cap-authors-filters.php | 6 +++--- .../content-distribution/class-cap-authors.php | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/includes/content-distribution/class-cap-authors-filters.php b/includes/content-distribution/class-cap-authors-filters.php index 98a85c57..b0881367 100644 --- a/includes/content-distribution/class-cap-authors-filters.php +++ b/includes/content-distribution/class-cap-authors-filters.php @@ -33,7 +33,7 @@ public static function init(): void { * Filters the coauthors of a post to include the distributed authors and CAP's guest authors * * @param array $coauthors Array of coauthors. - * @param int $post_id Post ID. + * @param int $post_id Post ID. * * @return array */ @@ -69,7 +69,7 @@ public static function filter_get_coauthors( $coauthors, $post_id ) { * Add job title for guest authors in the author bio. * * @param string $author_name The author name. - * @param int $author_id The author ID. + * @param int $author_id The author ID. * @param object $author The author object. */ public static function filter_newspack_author_bio_name( $author_name, $author_id, $author = null ) { @@ -88,7 +88,7 @@ public static function filter_newspack_author_bio_name( $author_name, $author_id * Filter the author link for guest authors. * * @param string $link The author link. - * @param int $author_id The author ID. + * @param int $author_id The author ID. * @param string $author_nicename The author nicename. * * @return string diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 85b2bbd1..b9f94c94 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -52,12 +52,12 @@ public static function is_co_authors_plus_active(): bool { * * Add a postmeta entry with the Co-Authors Plus authors for outgoing posts. * - * @param int $object_id The object ID. - * @param array $terms The terms. - * @param array $tt_ids The term taxonomy IDs. + * @param int $object_id The object ID. + * @param array $terms The terms. + * @param array $tt_ids The term taxonomy IDs. * @param string $taxonomy The taxonomy. - * @param bool $append Whether to append. - * @param array $old_tt_ids The old term taxonomy IDs. + * @param bool $append Whether to append. + * @param array $old_tt_ids The old term taxonomy IDs. * * @return void */ @@ -124,9 +124,9 @@ private static function get_cap_authors_for_distribution( WP_Post $post ): array /** * Ingest authors for a post distributed to this site * - * @param int $post_id The post ID. + * @param int $post_id The post ID. * @param string $site_url The site URL. - * @param array $cap_authors Array of distributed authors. + * @param array $cap_authors Array of distributed authors. * * @return void */ @@ -193,5 +193,4 @@ private static function get_guest_author_for_distribution( $guest_author ): arra return $author; } - } From ffd011611b8f70f258fe8e95d23c9a10b96d795b Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Tue, 28 Jan 2025 18:09:49 +0100 Subject: [PATCH 12/25] Cleanup --- includes/class-content-distribution.php | 11 ------- .../class-cap-authors-filters.php | 6 ++-- .../class-cap-authors.php | 13 ++++---- .../class-incoming-author.php | 19 +++++------ .../class-incoming-post.php | 32 ++++++++++++++----- .../class-author-distribution.php | 5 +-- includes/utils/class-users.php | 1 - package.json | 5 ++- 8 files changed, 49 insertions(+), 43 deletions(-) diff --git a/includes/class-content-distribution.php b/includes/class-content-distribution.php index 0fad00e1..27542b3c 100644 --- a/includes/class-content-distribution.php +++ b/includes/class-content-distribution.php @@ -391,15 +391,4 @@ public static function distribute_post( $post, $status_on_create = 'draft' ) { } } - /** - * Helper to check if Co-Authors Plus is active. - * - * @return bool Whether Co-Authors Plus is active. - */ - public static function is_co_authors_plus_active(): bool { - global $coauthors_plus; - - return $coauthors_plus instanceof \CoAuthors_Plus; - } - } diff --git a/includes/content-distribution/class-cap-authors-filters.php b/includes/content-distribution/class-cap-authors-filters.php index b0881367..0d68e07b 100644 --- a/includes/content-distribution/class-cap-authors-filters.php +++ b/includes/content-distribution/class-cap-authors-filters.php @@ -8,8 +8,7 @@ namespace Newspack_Network\Content_Distribution; /** - * Class to handle authorship filters - * TODO + * Class to support distributed Guest Authors. */ class Cap_Authors_Filters { @@ -22,6 +21,7 @@ public static function init(): void { if ( ! Cap_Authors::is_co_authors_plus_active() ) { return; } + if ( ! is_admin() ) { add_filter( 'get_coauthors', [ __CLASS__, 'filter_get_coauthors' ], 10, 2 ); add_filter( 'newspack_author_bio_name', [ __CLASS__, 'filter_newspack_author_bio_name' ], 10, 3 ); @@ -30,7 +30,7 @@ public static function init(): void { } /** - * Filters the coauthors of a post to include the distributed authors and CAP's guest authors + * Filters the coauthors of a post to include CAP's guest authors * * @param array $coauthors Array of coauthors. * @param int $post_id Post ID. diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index b9f94c94..04457bdb 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -18,7 +18,9 @@ class Cap_Authors { /** - * TODO + * Meta key for the author list we transfer. + * + * Note that it can have both Guest Contributors (that are WP_User objects) and Guest Authors (that are Guest Author objects). */ const AUTHOR_LIST_META_KEY = 'newspack_network_author_list'; @@ -125,12 +127,12 @@ private static function get_cap_authors_for_distribution( WP_Post $post ): array * Ingest authors for a post distributed to this site * * @param int $post_id The post ID. - * @param string $site_url The site URL. + * @param string $remote_url The remote URL. * @param array $cap_authors Array of distributed authors. * * @return void */ - public static function ingest_cap_authors_for_post( int $post_id, string $site_url, array $cap_authors ): void { + public static function ingest_cap_authors_for_post( int $post_id, string $remote_url, array $cap_authors ): void { if ( ! self::is_co_authors_plus_active() ) { return; } @@ -140,14 +142,13 @@ public static function ingest_cap_authors_for_post( int $post_id, string $site_u Debugger::log( 'Ingesting authors from networked post.' ); User_Update_Watcher::$enabled = false; - $coauthors = []; - $guest_authors = []; + $coauthors = []; foreach ( $cap_authors as $author ) { $author_type = $author['type'] ?? ''; switch ( $author_type ) { case 'wp_user': - $user = Incoming_Author::get_wp_user_author( $post_id, $author ); + $user = Incoming_Author::get_wp_user_author( $remote_url, $author ); if ( is_wp_error( $user ) ) { Debugger::log( 'Error ingesting author: ' . $user->get_error_message() ); } diff --git a/includes/content-distribution/class-incoming-author.php b/includes/content-distribution/class-incoming-author.php index 7edcbfe6..32557a99 100644 --- a/includes/content-distribution/class-incoming-author.php +++ b/includes/content-distribution/class-incoming-author.php @@ -19,33 +19,34 @@ class Incoming_Author { /** * Ingest authors for a post distributed to this site * - * @param int $post_id The post ID. - * @param array $author The distributed authors array. + * @param int $post_id The post ID. + * @param string $remote_url The remote URL. + * @param array $author The distributed authors array. * * @return void */ - public static function ingest_author_for_post( int $post_id, array $author ): void { + public static function ingest_author_for_post( int $post_id, string $remote_url, array $author ): void { if ( empty( $author ) ) { return; } - $author = self::get_wp_user_author( $post_id, $author ); + $author = self::get_wp_user_author( $remote_url, $author ); wp_update_post( [ 'ID' => $post_id, 'post_author' => $author->ID, - ] + ] ); } /** * Ingest authors for a post distributed to this site * - * @param int $post_id The post ID. - * @param array $author The distributed authors array. + * @param string $remote_url The remote site URL. + * @param array $author The distributed authors array. * * @return \WP_User|\WP_Error The user object or false on failure. */ - public static function get_wp_user_author( int $post_id, array $author ) { + public static function get_wp_user_author( string $remote_url, array $author ) { User_Update_Watcher::$enabled = false; @@ -61,7 +62,7 @@ public static function get_wp_user_author( int $post_id, array $author ) { $user = User_Utils::get_or_create_user_by_email( $author['user_email'], - get_post_meta( $post_id, 'dt_original_site_url', true ), // TODO. + $remote_url, $author['ID'], $insert_array ); diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index ea9b3f94..f56727cd 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -95,8 +95,8 @@ public function __construct( $payload ) { } if ( $post ) { - $this->ID = $post->ID; - $this->post = $post; + $this->ID = $post->ID; + $this->post = $post; } } @@ -107,7 +107,7 @@ public function __construct( $payload ) { * the Network's debugger. * * @param string $message The message to log. - * @param string $type The log type. Either 'error' or 'debug'. + * @param string $type The log type. Either 'error' or 'debug'. * Default is 'error'. * * @return void @@ -170,6 +170,7 @@ protected function get_post_payload() { if ( ! $this->ID ) { return []; } + return get_post_meta( $this->ID, self::PAYLOAD_META, true ); } @@ -200,7 +201,16 @@ protected function query_post() { $posts = get_posts( [ 'post_type' => Content_Distribution_Class::get_distributed_post_types(), - 'post_status' => [ 'publish', 'pending', 'draft', 'auto-draft', 'future', 'private', 'inherit', 'trash' ], + 'post_status' => [ + 'publish', + 'pending', + 'draft', + 'auto-draft', + 'future', + 'private', + 'inherit', + 'trash', + ], 'posts_per_page' => 1, 'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query [ @@ -213,6 +223,7 @@ protected function query_post() { if ( empty( $posts ) ) { return null; } + return $posts[0]; } @@ -341,6 +352,7 @@ protected function upload_thumbnail() { $attachment_id = media_sideload_image( $thumbnail_url, $this->ID, '', 'id' ); if ( is_wp_error( $attachment_id ) ) { self::log( 'Failed to upload featured image for post ' . $this->ID . ' with message: ' . $attachment_id->get_error_message() ); + return; } @@ -440,6 +452,7 @@ public function insert( $payload = [] ) { $error = $this->update_payload( $payload ); if ( is_wp_error( $error ) ) { self::log( 'Failed to update payload: ' . $error->get_error_message() ); + return $error; } } @@ -461,6 +474,7 @@ public function insert( $payload = [] ) { $current_payload['post_data']['modified_gmt'] > $post_data['modified_gmt'] ) { self::log( 'Linked post content is newer than the post payload.' ); + return new WP_Error( 'old_modified_date', __( 'Linked post content is newer than the post payload.', 'newspack-network' ) ); } @@ -504,12 +518,14 @@ public function insert( $payload = [] ) { if ( is_wp_error( $post_id ) ) { self::log( 'Failed to insert post with message: ' . $post_id->get_error_message() ); + return $post_id; } // The wp_insert_post() function might return `0` on failure. if ( ! $post_id ) { self::log( 'Failed to insert post.' ); + return new WP_Error( 'insert_error', __( 'Error inserting post.', 'newspack-network' ) ); } @@ -517,7 +533,7 @@ public function insert( $payload = [] ) { $this->ID = $post_id; $this->post = get_post( $this->ID ); - Incoming_Author::ingest_author_for_post( $this->ID, $post_data['author'] ); + Incoming_Author::ingest_author_for_post( $this->ID, $this->get_original_site_url(), $post_data['author'] ); Cap_Authors::ingest_cap_authors_for_post( $this->ID, $this->get_original_site_url(), @@ -549,9 +565,9 @@ public function insert( $payload = [] ) { /** * Fires after an incoming post is inserted. * - * @param int $post_id The post ID. - * @param bool $is_linked Whether the post is linked. - * @param array $payload The post payload. + * @param int $post_id The post ID. + * @param bool $is_linked Whether the post is linked. + * @param array $payload The post payload. */ do_action( 'newspack_network_incoming_post_inserted', $this->ID, $this->is_linked(), $this->payload ); diff --git a/includes/distributor-customizations/class-author-distribution.php b/includes/distributor-customizations/class-author-distribution.php index 8b31b701..60415a98 100644 --- a/includes/distributor-customizations/class-author-distribution.php +++ b/includes/distributor-customizations/class-author-distribution.php @@ -7,6 +7,7 @@ namespace Newspack_Network\Distributor_Customizations; +use Newspack\Data_Events; use Newspack_Network\Debugger; use Newspack_Network\User_Update_Watcher; use WP_Error; @@ -91,10 +92,10 @@ public static function add_author_data_to_pull( $post_array ) { /** * Get the authors of a post to be added to the distribution payload. * - * @param \WP_Post $post The post object. + * @param WP_Post $post The post object. * @return array An array of authors. */ - public static function get_authors_for_distribution( $post ) { + private static function get_authors_for_distribution( $post ) { $author = self::get_wp_user_for_distribution( $post->post_author ); if ( ! function_exists( 'get_coauthors' ) ) { diff --git a/includes/utils/class-users.php b/includes/utils/class-users.php index b4d80b96..2c72eea7 100644 --- a/includes/utils/class-users.php +++ b/includes/utils/class-users.php @@ -86,7 +86,6 @@ public static function get_or_create_user_by_email( $email, $remote_site_url, $r */ public static function generate_user_nicename( $name ) { $name = self::strip_email_domain( $name ); // If an email address, strip the domain. - // TODO. Could really benefit from the unique thing from NMT. return substr( \sanitize_title( \sanitize_user( $name, true ) ), 0, 50 ); } diff --git a/package.json b/package.json index d71be4da..2fe2468b 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,8 @@ "format:php": "./vendor/bin/phpcbf .", "lint:php:staged": "./vendor/bin/phpcs", "release": "npm run semantic-release", - "release:archive": "rm -rf release && mkdir -p release && rsync -r . ./release/newspack-network --exclude-from='./.distignore' && cd release && zip -r newspack-network.zip newspack-network", - "unittest": "./vendor/bin/phpunit" - }, + "release:archive": "rm -rf release && mkdir -p release && rsync -r . ./release/newspack-network --exclude-from='./.distignore' && cd release && zip -r newspack-network.zip newspack-network" + }, "lint-staged": { "*.php": "npm run lint:php:staged" }, From f0e4459dbe2ef3ef68ae667a9a8c45f237f05c04 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Tue, 28 Jan 2025 18:18:03 +0100 Subject: [PATCH 13/25] Check for warning --- includes/content-distribution/class-cap-authors.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 04457bdb..0d53593e 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -138,6 +138,9 @@ public static function ingest_cap_authors_for_post( int $post_id, string $remote } $cap_authors = reset( $cap_authors ); + if (! is_array( $cap_authors ) || empty( $cap_authors ) ) { + return; + } Debugger::log( 'Ingesting authors from networked post.' ); User_Update_Watcher::$enabled = false; From 1700eb79f35b1186c469f7c4ccdc02b0b993c1cf Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Wed, 29 Jan 2025 12:41:55 +0100 Subject: [PATCH 14/25] Work in progress --- .../class-cap-authors.php | 28 ++++++++----------- .../class-incoming-post.php | 4 +-- .../class-outgoing-post.php | 10 +++---- .../unit-tests/content-distribution/util.php | 1 + 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 0d53593e..29f215e5 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -7,6 +7,7 @@ namespace Newspack_Network\Content_Distribution; +use Newspack_Network\Content_Distribution as Content_Distribution_Class; use Newspack_Network\Debugger; use Newspack_Network\User_Update_Watcher; use WP_Error; @@ -30,12 +31,13 @@ class Cap_Authors { * @return void */ public static function init(): void { - if ( ! self::is_co_authors_plus_active() ) { + if ( self::is_co_authors_plus_active() ) { return; } Cap_Authors_Filters::init(); add_action( 'set_object_terms', [ __CLASS__, 'handle_cap_author_change' ], 10, 6 ); + add_filter('newspack_network_multiple_authors_for_post', [ __CLASS__, 'get_cap_authors_for_distribution' ], 10, 2); } /** @@ -76,12 +78,10 @@ public static function handle_cap_author_change( $object_id, $terms, $tt_ids, $t try { $outgoing_post = new Outgoing_Post( $object_id ); if ( ! $outgoing_post->is_distributed() ) { - // TODO. This is problematic I think. return; } - $cap_authors = self::get_cap_authors_for_distribution( $outgoing_post->get_post() ); - update_post_meta( $object_id, self::AUTHOR_LIST_META_KEY, $cap_authors ); + Content_Distribution_Class::distribute_post($outgoing_post->get_post() ); } catch ( \InvalidArgumentException ) { return; @@ -95,15 +95,17 @@ public static function handle_cap_author_change( $object_id, $terms, $tt_ids, $t * * @return array Array of authors in distributable format. */ - private static function get_cap_authors_for_distribution( WP_Post $post ): array { + public static function get_cap_authors_for_distribution( $authors, WP_Post $post ): array { + + if (! self::is_co_authors_plus_active() ) { + return []; + } $co_authors = get_coauthors( $post->ID ); if ( empty( $co_authors ) ) { return []; } - $authors = []; - foreach ( $co_authors as $co_author ) { if ( is_a( $co_author, 'WP_User' ) ) { // This will never return an error because we are checking for is_a() first. @@ -126,22 +128,17 @@ private static function get_cap_authors_for_distribution( WP_Post $post ): array /** * Ingest authors for a post distributed to this site * - * @param int $post_id The post ID. + * @param WP_post $post The post. * @param string $remote_url The remote URL. * @param array $cap_authors Array of distributed authors. * * @return void */ - public static function ingest_cap_authors_for_post( int $post_id, string $remote_url, array $cap_authors ): void { + public static function ingest_cap_authors_for_post( $post, string $remote_url, array $cap_authors ): void { if ( ! self::is_co_authors_plus_active() ) { return; } - $cap_authors = reset( $cap_authors ); - if (! is_array( $cap_authors ) || empty( $cap_authors ) ) { - return; - } - Debugger::log( 'Ingesting authors from networked post.' ); User_Update_Watcher::$enabled = false; @@ -166,8 +163,7 @@ public static function ingest_cap_authors_for_post( int $post_id, string $remote } global $coauthors_plus; - // Do this even if the array is empty, to clear out any existing authors. - $coauthors_plus->add_coauthors( $post_id, $coauthors ); + $coauthors_plus->add_coauthors( $post->ID, $coauthors ); } /** diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index f56727cd..1cb01926 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -535,9 +535,9 @@ public function insert( $payload = [] ) { Incoming_Author::ingest_author_for_post( $this->ID, $this->get_original_site_url(), $post_data['author'] ); Cap_Authors::ingest_cap_authors_for_post( - $this->ID, + $this->post, $this->get_original_site_url(), - $this->payload['post_data']['post_meta'][ Cap_Authors::AUTHOR_LIST_META_KEY ] ?? [] + $this->payload['multiple_authors'] ?? [] ); // Handle post meta. diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index af0e7650..fab7f653 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -202,10 +202,9 @@ public function get_payload_hash( $payload = null ) { * @return array|WP_Error The post payload or WP_Error if the post is invalid. */ public function get_payload( $status_on_create = 'draft' ) { - $post_author = []; - if ( $this->post->post_author ) { - $post_author = Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ); - } + $post_author = $this->post->post_author ? Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ) : []; + $multiple_authors = apply_filters( 'newspack_network_multiple_authors_for_post', [], $this->post ); + return [ 'site_url' => get_bloginfo( 'url' ), 'post_id' => $this->post->ID, @@ -213,9 +212,10 @@ public function get_payload( $status_on_create = 'draft' ) { 'network_post_id' => $this->get_network_post_id(), 'sites' => $this->get_distribution(), 'status_on_create' => $status_on_create, + 'multiple_authors' => is_wp_error( $multiple_authors ) ? [] : $multiple_authors, 'post_data' => [ 'title' => html_entity_decode( get_the_title( $this->post->ID ), ENT_QUOTES, get_bloginfo( 'charset' ) ), - 'author' => $post_author, + 'author' => is_wp_error( $post_author ) ? [] : $post_author, 'post_status' => $this->post->post_status, 'date_gmt' => $this->post->post_date_gmt, 'modified_gmt' => $this->post->post_modified_gmt, diff --git a/tests/unit-tests/content-distribution/util.php b/tests/unit-tests/content-distribution/util.php index 7eacd0ea..d7153d24 100644 --- a/tests/unit-tests/content-distribution/util.php +++ b/tests/unit-tests/content-distribution/util.php @@ -31,6 +31,7 @@ function get_sample_payload( $origin = '', $destination = '' ) { 'network_post_id' => '1234567890abcdef1234567890abcdef', 'sites' => [ $destination ], 'status_on_create' => 'draft', + 'multiple_authors' => [], 'post_data' => [ 'title' => 'Title', 'author' => [], From 32a5d18013e6b8a6d2af8bc7fa32f6a3119398c5 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Wed, 29 Jan 2025 17:44:18 +0100 Subject: [PATCH 15/25] Use partial payloads and simplify --- includes/class-content-distribution.php | 3 +- .../class-cap-authors-filters.php | 103 ---------- .../class-cap-authors.php | 94 +++------ .../class-cap-guest-authors.php | 185 ++++++++++++++++++ .../class-incoming-post.php | 2 +- 5 files changed, 220 insertions(+), 167 deletions(-) delete mode 100644 includes/content-distribution/class-cap-authors-filters.php create mode 100644 includes/content-distribution/class-cap-guest-authors.php diff --git a/includes/class-content-distribution.php b/includes/class-content-distribution.php index f33dc393..c95013dd 100644 --- a/includes/class-content-distribution.php +++ b/includes/class-content-distribution.php @@ -12,6 +12,7 @@ use Newspack_Network\Content_Distribution\API; use Newspack_Network\Content_Distribution\Canonical_Url; use Newspack_Network\Content_Distribution\Cap_Authors; +use Newspack_Network\Content_Distribution\Cap_Guest_Authors; use Newspack_Network\Content_Distribution\CLI; use Newspack_Network\Content_Distribution\Distributor_Migrator; use Newspack_Network\Content_Distribution\Editor; @@ -48,7 +49,7 @@ public static function init() { } add_action( 'init', [ __CLASS__, 'register_data_event_actions' ] ); - add_action( 'shutdown', [ __CLASS__, 'distribute_queued_posts' ] ); + add_action( 'shutdown', [ __CLASS__, 'distribute_queued_posts' ], 20 ); add_filter( 'newspack_webhooks_request_priority', [ __CLASS__, 'webhooks_request_priority' ], 10, 2 ); add_filter( 'update_post_metadata', [ __CLASS__, 'maybe_short_circuit_distributed_meta' ], 10, 4 ); add_action( 'wp_after_insert_post', [ __CLASS__, 'handle_post_updated' ] ); diff --git a/includes/content-distribution/class-cap-authors-filters.php b/includes/content-distribution/class-cap-authors-filters.php deleted file mode 100644 index 0d68e07b..00000000 --- a/includes/content-distribution/class-cap-authors-filters.php +++ /dev/null @@ -1,103 +0,0 @@ -type ) || 'guest_author' !== $author->type ) { - return $author_name; - } - - if ( $author && ! empty( $author->newspack_job_title ) ) { - $author_name .= '' . $author->newspack_job_title . ''; - } - - return $author_name; - } - - /** - * Filter the author link for guest authors. - * - * @param string $link The author link. - * @param int $author_id The author ID. - * @param string $author_nicename The author nicename. - * - * @return string - */ - public static function filter_author_link( $link, $author_id, $author_nicename ) { - if ( - 2 === $author_id && empty( $author_nicename ) ) { - $link = '#'; - } - - return $link; - } -} diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 29f215e5..0e99416f 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -1,6 +1,6 @@ get_post() ); + Content_Distribution_Class::distribute_post_partial( $outgoing_post->get_post(), [ 'multiple_authors' ] ); } catch ( \InvalidArgumentException ) { return; @@ -91,13 +89,14 @@ public static function handle_cap_author_change( $object_id, $terms, $tt_ids, $t /** * Get the Co-Authors Plus authors for distribution. * + * @param array $authors Array of authors. * @param WP_Post $post Post to get authors for. * * @return array Array of authors in distributable format. */ - public static function get_cap_authors_for_distribution( $authors, WP_Post $post ): array { + public static function get_outgoing_for_post( $authors, $post ): array { - if (! self::is_co_authors_plus_active() ) { + if ( ! self::is_co_authors_plus_active() ) { return []; } @@ -108,18 +107,14 @@ public static function get_cap_authors_for_distribution( $authors, WP_Post $post foreach ( $co_authors as $co_author ) { if ( is_a( $co_author, 'WP_User' ) ) { - // This will never return an error because we are checking for is_a() first. $authors[] = Outgoing_Author::get_wp_user_for_distribution( $co_author ); continue; } - $guest_author = self::get_guest_author_for_distribution( $co_author ); - if ( is_wp_error( $guest_author ) ) { - Debugger::log( 'Error getting guest author for distribution on post ' . $post->ID . ': ' . $guest_author->get_error_message() ); - Debugger::log( $co_author ); - continue; - } - $authors[] = $guest_author; + $other_kind_of_author = apply_filters( 'newspack_network_outgoing_non_wp_user_author', false, $co_author ); + if ( ! empty( $other_kind_of_author ) ) { + $authors[] = $other_kind_of_author; + } } return $authors; @@ -128,13 +123,13 @@ public static function get_cap_authors_for_distribution( $authors, WP_Post $post /** * Ingest authors for a post distributed to this site * - * @param WP_post $post The post. - * @param string $remote_url The remote URL. - * @param array $cap_authors Array of distributed authors. + * @param WP_post $post The post. + * @param string $remote_url The remote URL. + * @param array $cap_authors Array of distributed authors. * * @return void */ - public static function ingest_cap_authors_for_post( $post, string $remote_url, array $cap_authors ): void { + public static function ingest_incoming_for_post( $post, string $remote_url, array $cap_authors ): void { if ( ! self::is_co_authors_plus_active() ) { return; } @@ -142,7 +137,8 @@ public static function ingest_cap_authors_for_post( $post, string $remote_url, a Debugger::log( 'Ingesting authors from networked post.' ); User_Update_Watcher::$enabled = false; - $coauthors = []; + $guest_contributors = []; + $guest_authors = []; foreach ( $cap_authors as $author ) { $author_type = $author['type'] ?? ''; @@ -150,47 +146,21 @@ public static function ingest_cap_authors_for_post( $post, string $remote_url, a case 'wp_user': $user = Incoming_Author::get_wp_user_author( $remote_url, $author ); if ( is_wp_error( $user ) ) { - Debugger::log( 'Error ingesting author: ' . $user->get_error_message() ); + Debugger::log( 'Error ingesting guest contributor: ' . $user->get_error_message() ); } - $coauthors[] = $user->user_nicename; + $guest_contributors[] = $user->user_nicename; break; case 'guest_author': - // Do nothing here. We get the guest authors from the meta data on post views. See Cap_Authors_Filters. + $guest_authors[] = $author; break; default: Debugger::log( sprintf( 'Error ingesting author: Invalid author type "%s"', $author_type ) ); } } - global $coauthors_plus; - $coauthors_plus->add_coauthors( $post->ID, $coauthors ); - } + do_action( 'newspack_network_incoming_guest_authors', $post->ID, $guest_authors ); - /** - * Get the guest author data to be distributed along with the post. - * - * @param object $guest_author The Guest Author object. - * - * @return WP_Error|array - */ - private static function get_guest_author_for_distribution( $guest_author ): array|WP_Error { global $coauthors_plus; - - if ( ! is_object( $guest_author ) || ! isset( $guest_author->type ) || 'guest-author' !== $guest_author->type ) { - return new WP_Error( 'Error getting guest author details for distribution. Invalid Guest Author' ); - } - - $author = (array) $guest_author; - $author['type'] = 'guest_author'; - - // Gets the guest author avatar. - // We only want to send an actual uploaded avatar, we don't want to send the fallback avatar, like gravatar. - // If no avatar was set, let it default to the fallback set in the target site. - $author_avatar = $coauthors_plus->guest_authors->get_guest_author_thumbnail( $guest_author, 80 ); - if ( $author_avatar ) { - $author['avatar_img_tag'] = $author_avatar; - } - - return $author; + $coauthors_plus->add_coauthors( $post->ID, $guest_contributors ); } } diff --git a/includes/content-distribution/class-cap-guest-authors.php b/includes/content-distribution/class-cap-guest-authors.php new file mode 100644 index 00000000..53ec9848 --- /dev/null +++ b/includes/content-distribution/class-cap-guest-authors.php @@ -0,0 +1,185 @@ +type ) || 'guest-author' !== $author->type ) { + Debugger::log( 'Failed adding guest author to outgoing post' ); + + return []; + } + + global $coauthors_plus; + + $author = (array) $author; + $author['type'] = 'guest_author'; + + // Gets the guest author avatar. + // We only want to send an actual uploaded avatar, we don't want to send the fallback avatar, like gravatar. + // If no avatar was set, let it default to the fallback set in the target site. + $author_avatar = $coauthors_plus->guest_authors->get_guest_author_thumbnail( $author, 80 ); + if ( $author_avatar ) { + $author['avatar_img_tag'] = $author_avatar; + } + + return $author; + } + + /** + * Filters the coauthors of a post to include CAP's guest authors + * + * @param array $coauthors Array of coauthors. + * @param int $post_id Post ID. + * + * @return array + */ + public static function filter_get_coauthors( $coauthors, $post_id ) { + if ( empty( get_post_meta( $post_id, Incoming_Post::NETWORK_POST_ID_META, true ) ) ) { + return $coauthors; + } + + $distributed_authors = get_post_meta( $post_id, self::GUEST_AUTHORS_META_KEY, true ); + + if ( ! $distributed_authors ) { + return $coauthors; + } + + $guest_authors = []; + + foreach ( $distributed_authors as $distributed_author ) { + + if ( 'guest_author' !== $distributed_author['type'] ) { + continue; + } + // This removes the author URL from the guest author. + $distributed_author['user_nicename'] = ''; + $distributed_author['ID'] = - 2; + + $guest_authors[] = (object) $distributed_author; + } + + return [ ...$coauthors, ...$guest_authors ]; + } + + /** + * Add job title for guest authors in the author bio. + * + * @param string $author_name The author name. + * @param int $author_id The author ID. + * @param object $author The author object. + */ + public static function filter_newspack_author_bio_name( $author_name, $author_id, $author = null ) { + if ( empty( $author->type ) || 'guest_author' !== $author->type ) { + return $author_name; + } + + if ( $author && ! empty( $author->newspack_job_title ) ) { + $author_name .= '' . $author->newspack_job_title . ''; + } + + return $author_name; + } + + /** + * Filter the author link for guest authors. + * + * @param string $link The author link. + * @param int $author_id The author ID. + * @param string $author_nicename The author nicename. + * + * @return string + */ + public static function filter_author_link( $link, $author_id, $author_nicename ) { + if ( - 2 === $author_id && empty( $author_nicename ) ) { + $link = '#'; + } + + return $link; + } + + /** + * Action callback for reacting to incoimng guest authors. + * + * @param int $post_id The post ID. + * @param array $guest_authors The guest authors. + * + * @return void + */ + public static function on_guest_authors_incoming( $post_id, $guest_authors ): void { + if ( empty( $guest_authors ) ) { + delete_post_meta( $post_id, self::GUEST_AUTHORS_META_KEY ); + + return; + } + + update_post_meta( $post_id, self::GUEST_AUTHORS_META_KEY, $guest_authors ); + } + + /** + * Filter callback. + * + * Allow the guest authors meta key to be ignored when distributing post meta. + * + * @param array $ignored_keys The ignored keys to filter. + * + * @return array $ignored_keys with one more added. + */ + public static function filter_ignored_post_meta_keys( array $ignored_keys ): array { + $ignored_keys[] = self::GUEST_AUTHORS_META_KEY; + + return $ignored_keys; + } +} diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index d52eeb9a..75c74e8f 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -576,7 +576,7 @@ public function insert( $payload = [] ) { $this->post = get_post( $this->ID ); Incoming_Author::ingest_author_for_post( $this->ID, $this->get_original_site_url(), $post_data['author'] ); - Cap_Authors::ingest_cap_authors_for_post( + Cap_Authors::ingest_incoming_for_post( $this->post, $this->get_original_site_url(), $this->payload['multiple_authors'] ?? [] From 71ac74818171a10fb449abdadacf3ee902146ade Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Wed, 29 Jan 2025 19:53:26 +0100 Subject: [PATCH 16/25] phpcs --- includes/class-content-distribution.php | 1 - tests/unit-tests/content-distribution/test-incoming-post.php | 4 ++++ tests/unit-tests/content-distribution/util.php | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/class-content-distribution.php b/includes/class-content-distribution.php index c95013dd..f23cd9d2 100644 --- a/includes/class-content-distribution.php +++ b/includes/class-content-distribution.php @@ -460,5 +460,4 @@ public static function distribute_post_partial( $post, $post_data_keys ) { Data_Events::dispatch( 'network_post_updated', $payload ); } } - } diff --git a/tests/unit-tests/content-distribution/test-incoming-post.php b/tests/unit-tests/content-distribution/test-incoming-post.php index d769893d..7c1fb612 100644 --- a/tests/unit-tests/content-distribution/test-incoming-post.php +++ b/tests/unit-tests/content-distribution/test-incoming-post.php @@ -28,6 +28,10 @@ class TestIncomingPost extends \WP_UnitTestCase { */ protected $node_2 = 'https://node2.test'; + /** + * A user with the editor role. + * @var \WP_User + */ protected $some_editor; /** diff --git a/tests/unit-tests/content-distribution/util.php b/tests/unit-tests/content-distribution/util.php index d7153d24..59ecb5dc 100644 --- a/tests/unit-tests/content-distribution/util.php +++ b/tests/unit-tests/content-distribution/util.php @@ -7,7 +7,6 @@ namespace Test\Content_Distribution; - /** * Get a sample distributed post payload for testing. * From 113d019d038b278cdd8043c7e4631f3e6b692f62 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Wed, 29 Jan 2025 20:08:22 +0100 Subject: [PATCH 17/25] More phpcs --- tests/unit-tests/content-distribution/test-incoming-post.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit-tests/content-distribution/test-incoming-post.php b/tests/unit-tests/content-distribution/test-incoming-post.php index 7c1fb612..bfa4b9bb 100644 --- a/tests/unit-tests/content-distribution/test-incoming-post.php +++ b/tests/unit-tests/content-distribution/test-incoming-post.php @@ -30,6 +30,7 @@ class TestIncomingPost extends \WP_UnitTestCase { /** * A user with the editor role. + * * @var \WP_User */ protected $some_editor; From 37c2c9bb51b4c426736e2292f175a16e2d5173cc Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 30 Jan 2025 12:01:02 +0100 Subject: [PATCH 18/25] Suggestion from review Co-authored-by: Miguel Peixe --- includes/content-distribution/class-cap-authors.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 0e99416f..14312862 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -79,7 +79,7 @@ public static function on_cap_authors_change( $object_id, $terms, $tt_ids, $taxo return; } - Content_Distribution_Class::distribute_post_partial( $outgoing_post->get_post(), [ 'multiple_authors' ] ); + Content_Distribution_Class::queue_post_distribution( $object_id, 'multiple_authors' ); } catch ( \InvalidArgumentException ) { return; From 33daeb0e2b9e5c26dc7ca9dcb1286898ac9e8ec3 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 30 Jan 2025 12:14:23 +0100 Subject: [PATCH 19/25] Review feedback --- includes/class-content-distribution.php | 2 +- includes/content-distribution/class-cap-authors.php | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/includes/class-content-distribution.php b/includes/class-content-distribution.php index f23cd9d2..ff6b446c 100644 --- a/includes/class-content-distribution.php +++ b/includes/class-content-distribution.php @@ -49,7 +49,7 @@ public static function init() { } add_action( 'init', [ __CLASS__, 'register_data_event_actions' ] ); - add_action( 'shutdown', [ __CLASS__, 'distribute_queued_posts' ], 20 ); + add_action( 'shutdown', [ __CLASS__, 'distribute_queued_posts' ] ); add_filter( 'newspack_webhooks_request_priority', [ __CLASS__, 'webhooks_request_priority' ], 10, 2 ); add_filter( 'update_post_metadata', [ __CLASS__, 'maybe_short_circuit_distributed_meta' ], 10, 4 ); add_action( 'wp_after_insert_post', [ __CLASS__, 'handle_post_updated' ] ); diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 14312862..1ca1b52d 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -73,17 +73,11 @@ public static function on_cap_authors_change( $object_id, $terms, $tt_ids, $taxo return; } - try { - $outgoing_post = new Outgoing_Post( $object_id ); - if ( ! $outgoing_post->is_distributed() ) { - return; - } - - Content_Distribution_Class::queue_post_distribution( $object_id, 'multiple_authors' ); - - } catch ( \InvalidArgumentException ) { + if ( ! Content_Distribution_Class::is_post_distributed( $object_id ) ) { return; } + + Content_Distribution_Class::queue_post_distribution( $object_id, 'multiple_authors' ); } /** From 12547b6eea14ecbcbe749335555f0ae1afa12a7a Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 30 Jan 2025 12:23:45 +0100 Subject: [PATCH 20/25] Move 'multiple_authors' to post data --- .../class-incoming-post.php | 2 +- .../class-outgoing-post.php | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index 75c74e8f..d4afd6d0 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -579,7 +579,7 @@ public function insert( $payload = [] ) { Cap_Authors::ingest_incoming_for_post( $this->post, $this->get_original_site_url(), - $this->payload['multiple_authors'] ?? [] + $this->payload['post_data']['multiple_authors'] ?? [] ); // Handle post meta. diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index c62c57db..7a9e9938 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -202,7 +202,7 @@ public function get_payload_hash( $payload = null ) { * @return array|WP_Error The post payload or WP_Error if the post is invalid. */ public function get_payload( $status_on_create = 'draft' ) { - $post_author = $this->post->post_author ? Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ) : []; + $post_author = $this->post->post_author ? Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ) : []; $multiple_authors = apply_filters( 'newspack_network_multiple_authors_for_post', [], $this->post ); return [ @@ -212,23 +212,23 @@ public function get_payload( $status_on_create = 'draft' ) { 'network_post_id' => $this->get_network_post_id(), 'sites' => $this->get_distribution(), 'status_on_create' => $status_on_create, - 'multiple_authors' => is_wp_error( $multiple_authors ) ? [] : $multiple_authors, 'post_data' => [ - 'title' => html_entity_decode( get_the_title( $this->post->ID ), ENT_QUOTES, get_bloginfo( 'charset' ) ), - 'author' => is_wp_error( $post_author ) ? [] : $post_author, - 'post_status' => $this->post->post_status, - 'date_gmt' => $this->post->post_date_gmt, - 'modified_gmt' => $this->post->post_modified_gmt, - 'slug' => $this->post->post_name, - 'post_type' => $this->post->post_type, - 'raw_content' => $this->post->post_content, - 'content' => $this->get_processed_post_content(), - 'excerpt' => $this->post->post_excerpt, - 'comment_status' => $this->post->comment_status, - 'ping_status' => $this->post->ping_status, - 'taxonomy' => $this->get_post_taxonomy_terms(), - 'thumbnail_url' => get_the_post_thumbnail_url( $this->post->ID, 'full' ), - 'post_meta' => $this->get_post_meta(), + 'title' => html_entity_decode( get_the_title( $this->post->ID ), ENT_QUOTES, get_bloginfo( 'charset' ) ), + 'multiple_authors' => is_wp_error( $multiple_authors ) ? [] : $multiple_authors, + 'author' => is_wp_error( $post_author ) ? [] : $post_author, + 'post_status' => $this->post->post_status, + 'date_gmt' => $this->post->post_date_gmt, + 'modified_gmt' => $this->post->post_modified_gmt, + 'slug' => $this->post->post_name, + 'post_type' => $this->post->post_type, + 'raw_content' => $this->post->post_content, + 'content' => $this->get_processed_post_content(), + 'excerpt' => $this->post->post_excerpt, + 'comment_status' => $this->post->comment_status, + 'ping_status' => $this->post->ping_status, + 'taxonomy' => $this->get_post_taxonomy_terms(), + 'thumbnail_url' => get_the_post_thumbnail_url( $this->post->ID, 'full' ), + 'post_meta' => $this->get_post_meta(), ], ]; } From 0736b2be6cfa17c78bd7083fba85bedc6a36a9d5 Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 30 Jan 2025 14:01:05 +0100 Subject: [PATCH 21/25] Use filter for both incoming and outgoing multiple_authors --- .../class-cap-authors.php | 10 ++++----- .../class-cap-guest-authors.php | 2 +- .../class-incoming-post.php | 22 +++++++++++-------- .../class-outgoing-post.php | 11 ++++++++-- .../test-outgoing-post.php | 1 + 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 1ca1b52d..b114a87d 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -30,7 +30,9 @@ public static function init(): void { } add_action( 'set_object_terms', [ __CLASS__, 'on_cap_authors_change' ], 20, 6 ); - add_filter( 'newspack_network_multiple_authors_for_post', [ __CLASS__, 'get_outgoing_for_post' ], 10, 2 ); + add_action( 'newspack_network_incoming_multiple_authors', [ __CLASS__, 'ingest_incoming_for_post' ], 10, 3 ); + + add_filter( 'newspack_network_outgoing_multiple_authors', [ __CLASS__, 'get_outgoing_for_post' ], 10, 2 ); if ( defined( 'NEWSPACK_ENABLE_CAP_GUEST_AUTHORS' ) && NEWSPACK_ENABLE_CAP_GUEST_AUTHORS ) { // Support CAP Guest Authors. @@ -115,7 +117,7 @@ public static function get_outgoing_for_post( $authors, $post ): array { } /** - * Ingest authors for a post distributed to this site + * Action callback: Ingest authors for a post distributed to this site * * @param WP_post $post The post. * @param string $remote_url The remote URL. @@ -147,12 +149,10 @@ public static function ingest_incoming_for_post( $post, string $remote_url, arra case 'guest_author': $guest_authors[] = $author; break; - default: - Debugger::log( sprintf( 'Error ingesting author: Invalid author type "%s"', $author_type ) ); } } - do_action( 'newspack_network_incoming_guest_authors', $post->ID, $guest_authors ); + do_action( 'newspack_network_incoming_cap_guest_authors', $post->ID, $guest_authors ); global $coauthors_plus; $coauthors_plus->add_coauthors( $post->ID, $guest_contributors ); diff --git a/includes/content-distribution/class-cap-guest-authors.php b/includes/content-distribution/class-cap-guest-authors.php index 53ec9848..12628780 100644 --- a/includes/content-distribution/class-cap-guest-authors.php +++ b/includes/content-distribution/class-cap-guest-authors.php @@ -35,7 +35,7 @@ public static function init(): void { add_filter( 'newspack_network_content_distribution_ignored_post_meta_keys', [ __CLASS__, 'filter_ignored_post_meta_keys' ], 10, 2 ); add_filter( 'newspack_network_outgoing_non_wp_user_author', [ __CLASS__, 'filter_outgoing_non_wp_user_author' ], 10, 2 ); - add_action( 'newspack_network_incoming_guest_authors', [ __CLASS__, 'on_guest_authors_incoming' ], 10, 2 ); + add_action( 'newspack_network_incoming_cap_guest_authors', [ __CLASS__, 'on_guest_authors_incoming' ], 10, 2 ); if ( ! is_admin() ) { diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index d4afd6d0..fb665b48 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -116,7 +116,7 @@ public function __construct( $payload ) { * the Network's debugger. * * @param string $message The message to log. - * @param string $type The log type. Either 'error' or 'debug'. + * @param string $type The log type. Either 'error' or 'debug'. * Default is 'error'. * * @return void @@ -576,11 +576,15 @@ public function insert( $payload = [] ) { $this->post = get_post( $this->ID ); Incoming_Author::ingest_author_for_post( $this->ID, $this->get_original_site_url(), $post_data['author'] ); - Cap_Authors::ingest_incoming_for_post( - $this->post, - $this->get_original_site_url(), - $this->payload['post_data']['multiple_authors'] ?? [] - ); + + /** + * Fires on incoming posts handing it the multiple authors part of the payload - even if it's empty. + * + * @param WP_Post $post The post object. + * @param string $original_site_url The original site URL. + * @param array $multiple_authors The multiple authors part of the payload. + */ + do_action( 'newspack_network_incoming_multiple_authors', $this->post, $this->get_original_site_url(), $this->payload['post_data']['multiple_authors'] ?? [] ); // Handle post meta. $this->update_meta(); @@ -607,9 +611,9 @@ public function insert( $payload = [] ) { /** * Fires after an incoming post is inserted. * - * @param int $post_id The post ID. - * @param bool $is_linked Whether the post is linked. - * @param array $payload The post payload. + * @param int $post_id The post ID. + * @param bool $is_linked Whether the post is linked. + * @param array $payload The post payload. */ do_action( 'newspack_network_incoming_post_inserted', $this->ID, $this->is_linked(), $this->payload ); diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index 7a9e9938..d88d8ce6 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -202,8 +202,15 @@ public function get_payload_hash( $payload = null ) { * @return array|WP_Error The post payload or WP_Error if the post is invalid. */ public function get_payload( $status_on_create = 'draft' ) { - $post_author = $this->post->post_author ? Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ) : []; - $multiple_authors = apply_filters( 'newspack_network_multiple_authors_for_post', [], $this->post ); + $post_author = $this->post->post_author ? Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ) : []; + + /** + * Filters the multiple authors part of the outgoing post payload. + * + * @param array $multiple_authors The multiple authors data (empty by default). + * @param WP_Post $post The post object for the outgoing post. + */ + $multiple_authors = apply_filters( 'newspack_network_outgoing_multiple_authors', [], $this->post ); return [ 'site_url' => get_bloginfo( 'url' ), diff --git a/tests/unit-tests/content-distribution/test-outgoing-post.php b/tests/unit-tests/content-distribution/test-outgoing-post.php index b19b00fa..963a842e 100644 --- a/tests/unit-tests/content-distribution/test-outgoing-post.php +++ b/tests/unit-tests/content-distribution/test-outgoing-post.php @@ -166,6 +166,7 @@ public function test_get_payload() { 'taxonomy', 'post_meta', 'author', + 'multiple_authors', ]; $this->assertEmpty( array_diff( $post_data_keys, array_keys( $payload['post_data'] ) ) ); $this->assertEmpty( array_diff( array_keys( $payload['post_data'] ), $post_data_keys ) ); From 7ae0574a97fc4b374d1afa758f315fdb9b28ac2b Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 30 Jan 2025 14:20:44 +0100 Subject: [PATCH 22/25] Don't hit the db more than necessary --- .../class-incoming-author.php | 25 +++++++++---------- .../class-incoming-post.php | 4 +-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/includes/content-distribution/class-incoming-author.php b/includes/content-distribution/class-incoming-author.php index 32557a99..411bcbf7 100644 --- a/includes/content-distribution/class-incoming-author.php +++ b/includes/content-distribution/class-incoming-author.php @@ -17,32 +17,31 @@ class Incoming_Author { /** - * Ingest authors for a post distributed to this site + * Get the post author for an incoming post. * - * @param int $post_id The post ID. + * @param int $post_id The post ID. * @param string $remote_url The remote URL. - * @param array $author The distributed authors array. + * @param array $author The distributed authors array. * - * @return void + * @return int The author ID - or zero if none was found. */ - public static function ingest_author_for_post( int $post_id, string $remote_url, array $author ): void { + public static function get_incoming_post_author( int $post_id, string $remote_url, array $author ): int { if ( empty( $author ) ) { - return; + return 0; } $author = self::get_wp_user_author( $remote_url, $author ); - wp_update_post( - [ - 'ID' => $post_id, - 'post_author' => $author->ID, - ] - ); + if ( is_wp_error( $author ) ) { + return 0; + } + + return $author->ID; } /** * Ingest authors for a post distributed to this site * * @param string $remote_url The remote site URL. - * @param array $author The distributed authors array. + * @param array $author The distributed authors array. * * @return \WP_User|\WP_Error The user object or false on failure. */ diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index fb665b48..c3075427 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -549,6 +549,8 @@ public function insert( $payload = [] ) { $postarr['post_status'] = $post_data['post_status']; } + $postarr['post_author'] = Incoming_Author::get_incoming_post_author( $this->ID, $this->get_original_site_url(), $post_data['author'] ); + // Remove filters that may alter content updates. remove_all_filters( 'content_save_pre' ); @@ -575,8 +577,6 @@ public function insert( $payload = [] ) { $this->ID = $post_id; $this->post = get_post( $this->ID ); - Incoming_Author::ingest_author_for_post( $this->ID, $this->get_original_site_url(), $post_data['author'] ); - /** * Fires on incoming posts handing it the multiple authors part of the payload - even if it's empty. * From 1c69f02bffaafaf958bc470356d502cf646eb40f Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 30 Jan 2025 14:29:16 +0100 Subject: [PATCH 23/25] Move outoging authors to outgoing post --- .../class-cap-authors.php | 2 +- .../class-outgoing-author.php | 63 ------------------- .../class-outgoing-post.php | 45 ++++++++++++- 3 files changed, 45 insertions(+), 65 deletions(-) delete mode 100644 includes/content-distribution/class-outgoing-author.php diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index b114a87d..bca46e88 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -103,7 +103,7 @@ public static function get_outgoing_for_post( $authors, $post ): array { foreach ( $co_authors as $co_author ) { if ( is_a( $co_author, 'WP_User' ) ) { - $authors[] = Outgoing_Author::get_wp_user_for_distribution( $co_author ); + $authors[] = Outgoing_Post::get_outgoing_wp_user_author( $co_author ); continue; } diff --git a/includes/content-distribution/class-outgoing-author.php b/includes/content-distribution/class-outgoing-author.php deleted file mode 100644 index 017373a1..00000000 --- a/includes/content-distribution/class-outgoing-author.php +++ /dev/null @@ -1,63 +0,0 @@ - 'wp_user', - 'ID' => $user->ID, - ]; - - foreach ( User_Update_Watcher::$user_props as $prop ) { - if ( ! empty( $user->$prop ) ) { - $author[ $prop ] = $user->$prop; - } - } - - // CoAuthors' guest authors have a 'website' property. - if ( ! empty( $user->website ) ) { - $author['website'] = $user->website; - } - - foreach ( User_Update_Watcher::$watched_meta as $meta_key ) { - $value = get_user_meta( $user->ID, $meta_key, true ); - if ( ! empty( $value ) ) { - $author[ $meta_key ] = $value; - } - } - - return $author; - } -} diff --git a/includes/content-distribution/class-outgoing-post.php b/includes/content-distribution/class-outgoing-post.php index d88d8ce6..db03ee48 100644 --- a/includes/content-distribution/class-outgoing-post.php +++ b/includes/content-distribution/class-outgoing-post.php @@ -8,6 +8,7 @@ namespace Newspack_Network\Content_Distribution; use Newspack_Network\Content_Distribution as Content_Distribution_Class; +use Newspack_Network\User_Update_Watcher; use Newspack_Network\Utils\Network; use WP_Error; use WP_Post; @@ -53,6 +54,48 @@ public function __construct( $post ) { $this->post = $post; } + /** + * Gets the user data of a WP user to be distributed along with the post. + * + * @param int|WP_Post $user The user ID or object. + * + * @return WP_Error|array + */ + public static function get_outgoing_wp_user_author( $user ) { + if ( ! is_a( $user, 'WP_User' ) ) { + $user = get_user_by( 'ID', $user ); + } + + if ( ! $user ) { + return new WP_Error( 'Error getting WP User details for distribution. Invalid User' ); + } + + $author = [ + 'type' => 'wp_user', + 'ID' => $user->ID, + ]; + + foreach ( User_Update_Watcher::$user_props as $prop ) { + if ( ! empty( $user->$prop ) ) { + $author[ $prop ] = $user->$prop; + } + } + + // CoAuthors' guest authors have a 'website' property. + if ( ! empty( $user->website ) ) { + $author['website'] = $user->website; + } + + foreach ( User_Update_Watcher::$watched_meta as $meta_key ) { + $value = get_user_meta( $user->ID, $meta_key, true ); + if ( ! empty( $value ) ) { + $author[ $meta_key ] = $value; + } + } + + return $author; + } + /** * Get the post object. * @@ -202,7 +245,7 @@ public function get_payload_hash( $payload = null ) { * @return array|WP_Error The post payload or WP_Error if the post is invalid. */ public function get_payload( $status_on_create = 'draft' ) { - $post_author = $this->post->post_author ? Outgoing_Author::get_wp_user_for_distribution( $this->post->post_author ) : []; + $post_author = $this->post->post_author ? self::get_outgoing_wp_user_author( $this->post->post_author ) : []; /** * Filters the multiple authors part of the outgoing post payload. From af741a4b662a2401a57c3c3241cc5bc74e7481ce Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 30 Jan 2025 15:19:11 +0100 Subject: [PATCH 24/25] Move incoming authors to incoming post --- .../class-cap-authors.php | 2 +- .../class-incoming-author.php | 85 ------------------- .../class-incoming-post.php | 62 +++++++++++++- .../test-incoming-post.php | 1 - 4 files changed, 62 insertions(+), 88 deletions(-) delete mode 100644 includes/content-distribution/class-incoming-author.php diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index bca46e88..0bb5e4f4 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -140,7 +140,7 @@ public static function ingest_incoming_for_post( $post, string $remote_url, arra $author_type = $author['type'] ?? ''; switch ( $author_type ) { case 'wp_user': - $user = Incoming_Author::get_wp_user_author( $remote_url, $author ); + $user = Incoming_Post::get_incoming_wp_user_author( $remote_url, $author ); if ( is_wp_error( $user ) ) { Debugger::log( 'Error ingesting guest contributor: ' . $user->get_error_message() ); } diff --git a/includes/content-distribution/class-incoming-author.php b/includes/content-distribution/class-incoming-author.php deleted file mode 100644 index 411bcbf7..00000000 --- a/includes/content-distribution/class-incoming-author.php +++ /dev/null @@ -1,85 +0,0 @@ -ID; - } - - /** - * Ingest authors for a post distributed to this site - * - * @param string $remote_url The remote site URL. - * @param array $author The distributed authors array. - * - * @return \WP_User|\WP_Error The user object or false on failure. - */ - public static function get_wp_user_author( string $remote_url, array $author ) { - - User_Update_Watcher::$enabled = false; - - $insert_array = [ - 'role' => 'author', - ]; - - foreach ( User_Update_Watcher::$user_props as $prop ) { - if ( isset( $author[ $prop ] ) ) { - $insert_array[ $prop ] = $author[ $prop ]; - } - } - - $user = User_Utils::get_or_create_user_by_email( - $author['user_email'], - $remote_url, - $author['ID'], - $insert_array - ); - - if ( is_wp_error( $user ) ) { - Debugger::log( 'Error creating user: ' . $user->get_error_message() ); - - return $user; - } - - foreach ( User_Update_Watcher::get_writable_meta() as $meta_key ) { - if ( isset( $author[ $meta_key ] ) ) { - update_user_meta( $user->ID, $meta_key, $author[ $meta_key ] ); - } - } - - User_Utils::maybe_sideload_avatar( $user->ID, $author, false ); - - return $user; - } -} diff --git a/includes/content-distribution/class-incoming-post.php b/includes/content-distribution/class-incoming-post.php index c3075427..27cc05f6 100644 --- a/includes/content-distribution/class-incoming-post.php +++ b/includes/content-distribution/class-incoming-post.php @@ -9,6 +9,8 @@ use Newspack_Network\Content_Distribution as Content_Distribution_Class; use Newspack_Network\Debugger; +use Newspack_Network\User_Update_Watcher; +use Newspack_Network\Utils\Users as User_Utils; use WP_Error; use WP_Post; @@ -109,6 +111,56 @@ public function __construct( $payload ) { } } + /** + * Transform a distributed author into a WP_User object. + * + * @param string $remote_url The remote site URL. + * @param array $author The distributed authors array. + * + * @return \WP_User|\WP_Error The user object or false on failure. + */ + public static function get_incoming_wp_user_author( string $remote_url, array $author ) { + + if ( empty( $author['user_email'] ) ) { + return new WP_Error( 'missing_email', __( 'Missing email on incoming author creation', 'newspack-network' ) ); + } + + User_Update_Watcher::$enabled = false; + + $insert_array = [ + 'role' => 'author', + ]; + + foreach ( User_Update_Watcher::$user_props as $prop ) { + if ( isset( $author[ $prop ] ) ) { + $insert_array[ $prop ] = $author[ $prop ]; + } + } + + $user = User_Utils::get_or_create_user_by_email( + $author['user_email'], + $remote_url, + $author['ID'], + $insert_array + ); + + if ( is_wp_error( $user ) ) { + Debugger::log( 'Error creating user: ' . $user->get_error_message() ); + + return $user; + } + + foreach ( User_Update_Watcher::get_writable_meta() as $meta_key ) { + if ( isset( $author[ $meta_key ] ) ) { + update_user_meta( $user->ID, $meta_key, $author[ $meta_key ] ); + } + } + + User_Utils::maybe_sideload_avatar( $user->ID, $author, false ); + + return $user; + } + /** * Log a message. * @@ -549,7 +601,15 @@ public function insert( $payload = [] ) { $postarr['post_status'] = $post_data['post_status']; } - $postarr['post_author'] = Incoming_Author::get_incoming_post_author( $this->ID, $this->get_original_site_url(), $post_data['author'] ); + $postarr['post_author'] = 0; + if ( ! empty( $post_data['author'] ) ) { + $post_author = self::get_incoming_wp_user_author( $this->get_original_site_url(), $post_data['author'] ); + if ( is_wp_error( $post_author ) ) { + self::log( 'Failed to get post author with message: ' . $post_author->get_error_message() ); + } else { + $postarr['post_author'] = $post_author->ID; + } + } // Remove filters that may alter content updates. remove_all_filters( 'content_save_pre' ); diff --git a/tests/unit-tests/content-distribution/test-incoming-post.php b/tests/unit-tests/content-distribution/test-incoming-post.php index bfa4b9bb..5ca39bb3 100644 --- a/tests/unit-tests/content-distribution/test-incoming-post.php +++ b/tests/unit-tests/content-distribution/test-incoming-post.php @@ -8,7 +8,6 @@ namespace Test\Content_Distribution; use Newspack_Network\Content_Distribution\Incoming_Post; -use Newspack_Network\Content_Distribution\Outgoing_Author; /** * Test the Incoming_Post class. From 17e4b8c42bf10e231777eefbb91fa937388f3bfb Mon Sep 17 00:00:00 2001 From: Camilla Krag Jensen Date: Thu, 30 Jan 2025 19:51:19 +0100 Subject: [PATCH 25/25] Fix docblock --- includes/content-distribution/class-cap-authors.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/content-distribution/class-cap-authors.php b/includes/content-distribution/class-cap-authors.php index 0bb5e4f4..65c61199 100644 --- a/includes/content-distribution/class-cap-authors.php +++ b/includes/content-distribution/class-cap-authors.php @@ -54,7 +54,7 @@ public static function is_co_authors_plus_active(): bool { /** * Action callback. * - * Add a postmeta entry with the Co-Authors Plus authors for outgoing posts. + * Add CAP authors to the distribution queue when they change. * * @param int $object_id The object ID. * @param array $terms The terms.