Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 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
397275f
Add filters for the payload
naxoc Jan 31, 2025
3c2cad4
Remove unused method
naxoc Jan 31, 2025
fa05a21
Remove unused post_data field
naxoc Jan 31, 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
10 changes: 6 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 @@ -63,6 +64,7 @@ public static function init() {
Editor::init();
Canonical_Url::init();
Distributor_Migrator::init();
Cap_Authors::init();
}

/**
Expand Down Expand Up @@ -239,7 +241,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
170 changes: 170 additions & 0 deletions includes/content-distribution/class-cap-authors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php
/**
* Newspack Network Co-Authors Plus authors.
*
* @package Newspack
*/

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_Post;

/**
* This class handles the Guest Contributors Newspack offers for CAP.
*
* For Co-Author Plus Guest Authors, see Cap_Guest_Authors.
*/
class Cap_Authors {

/**
* The key for the cap authors in the payload post data.
*
* @var string
*/
const PAYLOAD_POST_DATA_AUTHORS_KEY = 'cap_authors';

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

add_action( 'set_object_terms', [ __CLASS__, 'on_cap_authors_change' ], 20, 6 );
add_action( 'newspack_network_incoming_post_after_save', [ __CLASS__, 'ingest_incoming_for_post' ], 10, 3 );

add_filter( 'newspack_network_outgoing_payload_post_data', [ __CLASS__, 'add_co_authors_to_payload_post_data' ], 10, 2 );

if ( defined( 'NEWSPACK_ENABLE_CAP_GUEST_AUTHORS' ) && NEWSPACK_ENABLE_CAP_GUEST_AUTHORS ) {
// Support CAP Guest Authors.
Cap_Guest_Authors::init();
}
}

/**
* 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 CAP authors to the distribution queue when they change.
*
* @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 on_cap_authors_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;
}

if ( ! Content_Distribution_Class::is_post_distributed( $object_id ) ) {
return;
}

Content_Distribution_Class::queue_post_distribution( $object_id, self::PAYLOAD_POST_DATA_AUTHORS_KEY );
}

/**
* Filter callback: Add the Co-Authors Plus authors to the payload post data.
*
* @param array $payload_post_data The post data for the payload.
* @param WP_Post $post Post to get authors for.
*
* @return array The post data for the payload.
*/
public static function add_co_authors_to_payload_post_data( $payload_post_data, $post ) {
if ( ! self::is_co_authors_plus_active() ) {
return $payload_post_data;
}

$co_authors = get_coauthors( $post->ID );
if ( empty( $co_authors ) ) {
return $payload_post_data;
}

$payload_post_data[ self::PAYLOAD_POST_DATA_AUTHORS_KEY ] = [];

foreach ( $co_authors as $co_author ) {
if ( is_a( $co_author, 'WP_User' ) ) {
$payload_post_data[ self::PAYLOAD_POST_DATA_AUTHORS_KEY ][] = Outgoing_Post::get_outgoing_wp_user_author( $co_author );
continue;
}

$other_kind_of_author = apply_filters( 'newspack_network_outgoing_non_wp_user_author', false, $co_author );
if ( ! empty( $other_kind_of_author ) ) {
$payload_post_data[ self::PAYLOAD_POST_DATA_AUTHORS_KEY ][] = $other_kind_of_author;
}
}

return $payload_post_data;
}

/**
* Action callback: Ingest authors for a post distributed to this site
*
* @param array $post_data The post_data part of the payload.
* @param WP_post $post The post.
* @param string $remote_url The remote URL.
*
* @return void
*/
public static function ingest_incoming_for_post( $post_data, $post, $remote_url ): void {
if ( ! self::is_co_authors_plus_active() ) {
return;
}

$cap_authors_from_payload = $post_data[ self::PAYLOAD_POST_DATA_AUTHORS_KEY ] ?? [];

Debugger::log( 'Ingesting authors from networked post.' );
User_Update_Watcher::$enabled = false;

$guest_contributors = [];
$guest_authors = [];

foreach ( $cap_authors_from_payload as $author ) {
$author_type = $author['type'] ?? '';
switch ( $author_type ) {
case 'wp_user':
$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() );
}
$guest_contributors[] = $user->user_nicename;
break;
case 'guest_author':
$guest_authors[] = $author;
break;
}
}

do_action( 'newspack_network_incoming_cap_guest_authors', $post->ID, $guest_authors );

global $coauthors_plus;
$coauthors_plus->add_coauthors( $post->ID, $guest_contributors );
}
}
185 changes: 185 additions & 0 deletions includes/content-distribution/class-cap-guest-authors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php
/**
* Newspack Network filters for making guest authors work.
*
* @package Newspack
*/

namespace Newspack_Network\Content_Distribution;

use Newspack_Network\Debugger;

/**
* This class only deals with the "classic" Guest authors CAP has built in, not the Guest Contributors Newspack provides.
*/
class Cap_Guest_Authors {

/**
* Meta key for the "fake lightweight" Guest Authors we keep in post meta.
*/
const GUEST_AUTHORS_META_KEY = 'newspack_network_guest_authors_distributed';

/**
* Go!
*
* @return void
*/
public static function init(): void {
if ( ! Cap_Authors::is_co_authors_plus_active() ) {
return;
}

if ( ! defined( 'NEWSPACK_ENABLE_CAP_GUEST_AUTHORS' ) || empty( NEWSPACK_ENABLE_CAP_GUEST_AUTHORS ) ) {
return;
}

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_cap_guest_authors', [ __CLASS__, 'on_guest_authors_incoming' ], 10, 2 );


if ( ! is_admin() ) {
// These filters are for presentation - not ingesting or distributing.
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 outgoing author to include CAP's guest author if applicable.
*
* @param mixed $retval The return value.
* @param array $author The author.
*
* @return array The guest author or empty array.
*/
public static function filter_outgoing_non_wp_user_author( $retval, $author ): array {

if ( ! is_object( $author ) || ! isset( $author->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 .= '<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;
}

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