Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(content-distribution): Sync authors #194

Open
wants to merge 34 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
18cb10c
work in progress
naxoc Dec 18, 2024
06d1397
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 2, 2025
0227d6c
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 6, 2025
d323ebb
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 20, 2025
25511b4
Add to outgoing
naxoc Jan 20, 2025
2a7185f
Clean up
naxoc Jan 20, 2025
36b30ed
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 21, 2025
6e13d27
Liniting
naxoc Jan 21, 2025
c4581cd
Remane to avoid confusion
naxoc Jan 21, 2025
b08b048
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 22, 2025
077d5ce
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 23, 2025
cd2276c
Add some comments on a way forward
naxoc Jan 23, 2025
caa506d
Move stuff around
naxoc Jan 24, 2025
ea7bb12
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 24, 2025
beed21f
most things work now
naxoc Jan 27, 2025
8e770bb
Fix tests
naxoc Jan 27, 2025
4150ad0
Work in progress
naxoc Jan 28, 2025
c718269
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 28, 2025
9b66863
PHPCS
naxoc Jan 28, 2025
ffd0116
Cleanup
naxoc Jan 28, 2025
f0e4459
Check for warning
naxoc Jan 28, 2025
1700eb7
Work in progress
naxoc Jan 29, 2025
7f20d29
Merge branch 'trunk' into feat/content-distribution-authors
naxoc Jan 29, 2025
32a5d18
Use partial payloads and simplify
naxoc Jan 29, 2025
71ac748
phpcs
naxoc Jan 29, 2025
113d019
More phpcs
naxoc Jan 29, 2025
37c2c9b
Suggestion from review
naxoc Jan 30, 2025
33daeb0
Review feedback
naxoc Jan 30, 2025
12547b6
Move 'multiple_authors' to post data
naxoc Jan 30, 2025
0736b2b
Use filter for both incoming and outgoing multiple_authors
naxoc Jan 30, 2025
7ae0574
Don't hit the db more than necessary
naxoc Jan 30, 2025
1c69f02
Move outoging authors to outgoing post
naxoc Jan 30, 2025
af741a4
Move incoming authors to incoming post
naxoc Jan 30, 2025
17e4b8c
Fix docblock
naxoc Jan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions includes/class-content-distribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
namespace Newspack_Network;

use Newspack\Data_Events;
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\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;

/**
Expand Down Expand Up @@ -61,6 +62,7 @@ public static function init() {
Editor::init();
Canonical_Url::init();
Distributor_Migrator::init();
Cap_Authors::init();
}

/**
Expand Down Expand Up @@ -199,7 +201,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' ) ) {
Expand Down Expand Up @@ -388,4 +390,5 @@ public static function distribute_post( $post, $status_on_create = 'draft' ) {
update_post_meta( $post->ID, self::PAYLOAD_HASH_META, $payload_hash );
}
}

}
103 changes: 103 additions & 0 deletions includes/content-distribution/class-cap-authors-filters.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php
/**
* Newspack Network filters for making guest authors work.
*
* @package Newspack
*/

namespace Newspack_Network\Content_Distribution;

/**
* Class to support distributed Guest Authors.
*/
class Cap_Authors_Filters {

/**
* Go!
*
* @return void
*/
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 );
add_filter( 'author_link', [ __CLASS__, 'filter_author_link' ], 20, 3 );
}
}

/**
* 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, Cap_Authors::AUTHOR_LIST_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 .= '<span class="author-job-title">' . $author->newspack_job_title . '</span>';
}

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;
}
}
200 changes: 200 additions & 0 deletions includes/content-distribution/class-cap-authors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
<?php
/**
* Newspack Network author ingestion for content distribution.
*
* @package Newspack
*/

namespace Newspack_Network\Content_Distribution;

use Newspack_Network\Debugger;
use Newspack_Network\User_Update_Watcher;
use WP_Error;
use WP_Post;

/**
* Class to handle author ingestion for content distribution.
*/
class Cap_Authors {

/**
* 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';

/**
* Get things going.
*
* @return void
*/
public static function init(): void {
if ( ! self::is_co_authors_plus_active() ) {
return;
}
Cap_Authors_Filters::init();

add_action( 'set_object_terms', [ __CLASS__, 'handle_cap_author_change' ], 10, 6 );
}

/**
* 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 && function_exists( 'get_coauthors' );
}

/**
* Action callback.
*
* Add a postmeta entry with the Co-Authors Plus authors for outgoing posts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docblock needs an update.

*
* @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.
return;
}
// If the terms are the same, we don't need to do anything. Note that one array has string values and one has
// int values, so we use array_map with the intval for the comparison.
if ( array_map( 'intval', $old_tt_ids ) === array_map( 'intval', $tt_ids ) ) {
return;
}

try {
$outgoing_post = new Outgoing_Post( $object_id );
if ( ! $outgoing_post->is_distributed() ) {
naxoc marked this conversation as resolved.
Show resolved Hide resolved
// 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 );

} catch ( \InvalidArgumentException ) {
return;
}
}

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

/**
* Ingest authors for a post distributed to this site
*
* @param int $post_id The post ID.
* @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 {
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;

$coauthors = [];

foreach ( $cap_authors as $author ) {
$author_type = $author['type'] ?? '';
switch ( $author_type ) {
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() );
}
$coauthors[] = $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.
break;
default:
Debugger::log( sprintf( 'Error ingesting author: Invalid author type "%s"', $author_type ) );
}
}

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.
*
* @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;
}
}
Loading