diff --git a/includes/class-distributor-customizations.php b/includes/class-distributor-customizations.php index b38925cb..84215d0b 100644 --- a/includes/class-distributor-customizations.php +++ b/includes/class-distributor-customizations.php @@ -20,6 +20,8 @@ class Distributor_Customizations { public static function init() { require_once NEWSPACK_NETWORK_PLUGIN_DIR . '/includes/distributor-customizations/global.php'; Distributor_Customizations\Canonical_Url::init(); + Distributor_Customizations\Author_Distribution::init(); + Distributor_Customizations\Author_Ingestion::init(); } diff --git a/includes/class-user-update-watcher.php b/includes/class-user-update-watcher.php index f85626f6..07c99606 100644 --- a/includes/class-user-update-watcher.php +++ b/includes/class-user-update-watcher.php @@ -13,12 +13,12 @@ class User_Update_Watcher { /** - * Flag to indicate if the processing of the user updated event is in progress. - * If true, this class won't fire any events to avoid an infinite loop in author updates. + * Flag to indicate if the watcher should watch. + * If false, this class won't fire any events to avoid an infinite loop in author updates. * * @var boolean */ - public static $processing_user_updated_event = false; + public static $enabled = true; /** * Holds information about the users that were updated in this request, if any. @@ -73,7 +73,6 @@ class User_Update_Watcher { public static $user_props = [ 'display_name', 'user_email', - 'ID', 'user_url', ]; @@ -126,7 +125,7 @@ private static function add_change( $user_id, $type, $key, $value ) { * @return void */ public static function update_user_meta( $meta_id, $user_id, $meta_key, $meta_value ) { - if ( self::$processing_user_updated_event ) { + if ( ! self::$enabled ) { return; } @@ -144,7 +143,7 @@ public static function update_user_meta( $meta_id, $user_id, $meta_key, $meta_va * @return void */ public static function profile_update( $user_id, $old_user_data, $user_data ) { - if ( self::$processing_user_updated_event ) { + if ( ! self::$enabled ) { return; } @@ -161,7 +160,7 @@ public static function profile_update( $user_id, $old_user_data, $user_data ) { * @return void */ public static function maybe_trigger_event() { - if ( self::$processing_user_updated_event ) { + if ( ! self::$enabled ) { return; } foreach ( self::$updated_users as $author ) { diff --git a/includes/distributor-customizations/class-author-distribution.php b/includes/distributor-customizations/class-author-distribution.php new file mode 100644 index 00000000..9efe3539 --- /dev/null +++ b/includes/distributor-customizations/class-author-distribution.php @@ -0,0 +1,202 @@ +get_param( 'distributor_request' ) ) || + 'GET' !== $request->get_method() || + 'edit' !== $request->get_param( 'context' ) || + empty( $request->get_param( 'id' ) ) + ) { + return $response; + } + + $authors = self::get_authors_for_distribution( $post ); + + if ( ! empty( $authors ) ) { + $data = $response->get_data(); + $data['newspack_network_authors'] = $authors; + $response->set_data( $data ); + } + + return $response; + + } + + /** + * Get the authors of a post to be added to the distribution payload. + * + * @param WP_Post $post The post object. + * @return array An array of authors. + */ + private static function get_authors_for_distribution( $post ) { + $author = self::get_wp_user_for_distribution( $post->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 ) { + + 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 = [ + 'type' => 'guest_author', + 'id' => $guest_author->ID, + ]; + + foreach ( User_Update_Watcher::$user_props as $prop ) { + if ( isset( $guest_author->$prop ) ) { + $author[ $prop ] = $guest_author->$prop; + } + } + + // CoAuthors' guest authors have a 'website' property. + if ( isset( $guest_author->website ) ) { + $author['website'] = $guest_author->website; + } + + foreach ( User_Update_Watcher::$watched_meta as $meta_key ) { + if ( isset( $guest_author->$meta_key ) ) { + $author[ $meta_key ] = $guest_author->$meta_key; + } + } + + return $author; + } + +} diff --git a/includes/distributor-customizations/class-author-ingestion.php b/includes/distributor-customizations/class-author-ingestion.php new file mode 100644 index 00000000..dd131d2e --- /dev/null +++ b/includes/distributor-customizations/class-author-ingestion.php @@ -0,0 +1,178 @@ +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 ) ) { + continue; + } + + foreach ( User_Update_Watcher::$watched_meta as $meta_key ) { + if ( isset( $author[ $meta_key ] ) ) { + update_user_meta( $user->ID, $meta_key, $author[ $meta_key ] ); + } + } + + // If CoAuthors Plus is not present, just assign the first author as the post author. + if ( ! $coauthors_plus ) { + wp_update_post( + [ + 'ID' => $post_id, + 'post_author' => $user->ID, + ] + ); + break; + } + + $coauthors[] = $user->user_nicename; + } + + if ( $coauthors_plus ) { + $coauthors_plus->add_coauthors( $post_id, $coauthors ); + } + + } + + /** + * Captures and stores the authorship data for a post + * + * Distributor discards the additional data we send in the REST request, so we need to capture it here + * for later use in self::handle_pull + * + * @param WP_Post $post The post object being pulled. + * @param array $post_array The post array received from the REST api. + * @return WP_Post + */ + public static function capture_authorship( $post, $post_array ) { + Debugger::log( 'Trying to capture authorship for post ' . $post->ID ); + if ( empty( $post_array['newspack_network_authors'] ) ) { + return $post; + } + Debugger::log( 'Capturing authorship for post ' . $post->ID ); + self::$pulled_posts[ $post->ID ] = $post_array['newspack_network_authors']; + return $post; + } + + /** + * Triggered when a post is pulled from a remote site. + * + * @param int $post_id The pulled post ID. + * @return void + */ + public static function handle_pull( $post_id ) { + $remote_id = get_post_meta( $post_id, 'dt_original_post_id', true ); + if ( ! $remote_id || empty( self::$pulled_posts[ $remote_id ] ) ) { + return; + } + + $distributed_authors = self::$pulled_posts[ $remote_id ]; + + self::ingest_authors_for_post( $post_id, $distributed_authors ); + + } + +} diff --git a/includes/distributor-customizations/global.php b/includes/distributor-customizations/global.php index 01252541..aabe682e 100644 --- a/includes/distributor-customizations/global.php +++ b/includes/distributor-customizations/global.php @@ -26,7 +26,7 @@ function( $post_body, $post ) { /** * Keep the publication date on the new pulled post. * - * This filter is used to filter the arguments sent to the remote server during a pull. + * This filters the arguments passed into wp_insert_post during a pull. */ add_filter( 'dt_pull_post_args', diff --git a/includes/incoming-events/class-user-updated.php b/includes/incoming-events/class-user-updated.php index a0ed4614..85d70033 100644 --- a/includes/incoming-events/class-user-updated.php +++ b/includes/incoming-events/class-user-updated.php @@ -53,7 +53,7 @@ public function maybe_update_user() { return; } - User_Update_Watcher::$processing_user_updated_event = true; + User_Update_Watcher::$enabled = false; $data = $this->get_data(); diff --git a/includes/utils/class-users.php b/includes/utils/class-users.php index 274c3fd8..94da9045 100644 --- a/includes/utils/class-users.php +++ b/includes/utils/class-users.php @@ -17,11 +17,11 @@ class Users { * * @param string $email The email of the user to look for. If no user is found, a new one will be created with this email. * @param string $remote_site_url The URL of the remote site. Used only when a new user is created. - * @param string $remote_site_id The ID 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 */ - public static function get_or_create_user_by_email( $email, $remote_site_url, $remote_site_id, $insert_array = [] ) { + public static function get_or_create_user_by_email( $email, $remote_site_url, $remote_id, $insert_array = [] ) { $existing_user = get_user_by( 'email', $email ); @@ -47,7 +47,7 @@ public static function get_or_create_user_by_email( $email, $remote_site_url, $r } update_user_meta( $user_id, 'newspack_remote_site', $remote_site_url ); - update_user_meta( $user_id, 'newspack_remote_id', $remote_site_id ); + update_user_meta( $user_id, 'newspack_remote_id', $remote_id ); return get_user_by( 'id', $user_id );