Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
9997fd5
feat: content distribution class
miguelpeixe Nov 28, 2024
7a5153d
feat: insert linked post
miguelpeixe Nov 29, 2024
4230692
refactor: use site url instead of ID
miguelpeixe Nov 29, 2024
0561d77
chore: lint
miguelpeixe Nov 29, 2024
e1aee3f
feat: return errors on post insert
miguelpeixe Nov 29, 2024
6d5e931
chore: lint
miguelpeixe Nov 29, 2024
34dcbf3
feat: support webhooks request priority
miguelpeixe Dec 2, 2024
5e47bbe
feat: unlink functionality and unit tests
miguelpeixe Dec 2, 2024
ca10658
chore: lint
miguelpeixe Dec 2, 2024
136ed6a
feat: introduce 'linked_post_inserted' hook and listener
miguelpeixe Dec 2, 2024
7738de3
chore: lint
miguelpeixe Dec 2, 2024
6095800
fix: listener hook name
miguelpeixe Dec 2, 2024
699228a
fix: typo
miguelpeixe Dec 2, 2024
0bf9c37
chore: better docblocks
miguelpeixe Dec 2, 2024
20d5dc0
chore: remove redundant code
miguelpeixe Dec 2, 2024
fb501d4
test: persist post hash
miguelpeixe Dec 2, 2024
548a571
test: remove unnecessary assertion
miguelpeixe Dec 2, 2024
b0c0a0d
refactor: distribute_post() to use handle_post_updated() method
miguelpeixe Dec 2, 2024
2d3f9ca
refactor: post hash is now network post id
miguelpeixe Dec 2, 2024
78c7f01
chore: update comment
miguelpeixe Dec 2, 2024
491bf9f
chore: Add CLI command for distribute
naxoc Dec 9, 2024
4f2641a
feat: content distribution class (#155)
miguelpeixe Dec 9, 2024
a397989
Merge branch 'epic/content-distribution' into feat/distribute-post-cli
naxoc Dec 9, 2024
cd4339d
feat(content-distribution): prevent older content updating linked pos…
miguelpeixe Dec 9, 2024
f2de08c
feat(content-distribution): handle post thumbnail (#157)
miguelpeixe Dec 9, 2024
db14c66
Add network util class
naxoc Dec 10, 2024
49936b4
Almost done except for a few todos
naxoc Dec 10, 2024
31d6744
refactor: OOP for distributed and linked posts (#160)
miguelpeixe Dec 10, 2024
568a1f2
Merge branch 'epic/content-distribution' into feat/distribute-post-cli
naxoc Dec 10, 2024
cf2a369
Merge branch 'epic/content-distribution' into feat/network-validate-urls
naxoc Dec 10, 2024
8e222e5
chore: Require php 8.1
naxoc Dec 10, 2024
36a8807
Merge pull request #161 from Automattic/feat/network-validate-urls
naxoc Dec 10, 2024
0724460
Merge branch 'epic/content-distribution' into feat/distribute-post-cli
naxoc Dec 10, 2024
43634ee
chore: Update to use util class
naxoc Dec 11, 2024
00be82b
fix: Include correct classname
naxoc Dec 11, 2024
cbd1704
chore: Add validation of outgoing post
naxoc Dec 11, 2024
7352967
chore: phpcs
naxoc Dec 11, 2024
0e3bec4
Merge pull request #164 from Automattic/fix/content-distribution-clas…
naxoc Dec 11, 2024
20b6996
Merge branch 'epic/content-distribution' into feat/distribute-post-cli
naxoc Dec 11, 2024
47bd6b6
fix(content-distribution): debug post update and remove deprecated co…
miguelpeixe Dec 11, 2024
9722f56
Merge branch 'epic/content-distribution' into feat/distribute-post-cli
naxoc Dec 11, 2024
aa12aea
fix: Don't return payload on all posts
naxoc Dec 11, 2024
745754f
fix: post handling
naxoc Dec 11, 2024
c64f15b
chore: move try-catch
naxoc Dec 11, 2024
b0c5f5d
chore: Move check for networked urls
naxoc Dec 11, 2024
6e33778
Move more
naxoc Dec 12, 2024
8427f77
Merge branch 'trunk' into feat/distribute-post-cli
naxoc Dec 12, 2024
7146907
Move things again
naxoc Dec 12, 2024
587e668
chore: Add a mock networked node
naxoc Dec 12, 2024
aeb190b
chore: add one more node to the mock nodes
naxoc Dec 12, 2024
17c4359
Add a test
naxoc Dec 12, 2024
d0b0743
Merge branch 'trunk' into feat/distribute-post-cli
naxoc Dec 12, 2024
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
19 changes: 12 additions & 7 deletions includes/class-content-distribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

namespace Newspack_Network;

use Newspack_Network\Content_Distribution\Outgoing_Post;
use Newspack_Network\Content_Distribution\Incoming_Post;
use Newspack\Data_Events;
use Newspack_Network\Content_Distribution\CLI;
use Newspack_Network\Content_Distribution\Incoming_Post;
use Newspack_Network\Content_Distribution\Outgoing_Post;
use WP_Post;
use WP_Error;

/**
* Main class for content distribution
Expand All @@ -26,6 +26,7 @@ public static function init() {
if ( ! defined( 'NEWPACK_NETWORK_CONTENT_DISTRIBUTION' ) || ! NEWPACK_NETWORK_CONTENT_DISTRIBUTION ) {
return;
}
CLI::init();
add_action( 'init', [ __CLASS__, 'register_listeners' ] );
add_filter( 'newspack_webhooks_request_priority', [ __CLASS__, 'webhooks_request_priority' ], 10, 2 );
}
Expand Down Expand Up @@ -73,6 +74,8 @@ public static function handle_post_updated( $post ) {
if ( $post ) {
return $post->get_payload();
}

return null;
}

/**
Expand Down Expand Up @@ -149,14 +152,16 @@ public static function get_reserved_post_meta_keys() {
*
* @param WP_Post|int $post The post object or ID.
*
* @return Outgoing_Post|null The distributed post or null if not found.
* @return Outgoing_Post|null The distributed post or null if not found, or we couldn't create one.
*/
public static function get_distributed_post( $post ) {
$outgoing_post = new Outgoing_Post( $post );
if ( ! $outgoing_post->is_distributed() ) {
try {
$outgoing_post = new Outgoing_Post( $post );
} catch ( \InvalidArgumentException ) {
return null;
}
return $outgoing_post;

return $outgoing_post->is_distributed() ? $outgoing_post : null;
}

/**
Expand Down
104 changes: 104 additions & 0 deletions includes/content-distribution/class-cli.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php
/**
* Network Content Distribution commands.
*
* @package Newspack
*/

namespace Newspack_Network\Content_Distribution;

use Newspack_Network\Content_Distribution;
use Newspack_Network\Utils\Network;
use WP_CLI;
use WP_CLI\ExitException;

/**
* Class Distribution.
*/
class CLI {
/**
* Initialize this class and register hooks
*
* @return void
*/
public static function init(): void {
if ( defined( 'WP_CLI' ) && WP_CLI ) {
add_action( 'init', [ __CLASS__, 'register_commands' ] );
}
}

/**
* Callback to register the WP-CLI commands.
*
* @return void
* @throws \Exception If something goes wrong.
*/
public static function register_commands(): void {
WP_CLI::add_command(
'newspack network distribute post',
[ __CLASS__, 'cmd_distribute_post' ],
[
'shortdesc' => __( 'Distribute a post to all the network or the specified sites' ),
'synopsis' => [
[
'type' => 'positional',
'name' => 'post-id',
'description' => sprintf(
'The ID of the post to distribute. Supported post types are: %s',
implode(
', ',
Content_Distribution::get_distributed_post_types()
)
),
'optional' => false,
'repeating' => false,
],
[
'type' => 'assoc',
'name' => 'sites',
'description' => __( "Networked site url(s) comma separated to distribute the post to – or 'all' to distribute to all sites in the network." ),
'optional' => false,
],
],
]
);
}

/**
* Callback for the `newspack-network distribute post` command.
*
* @param array $pos_args Positional arguments.
* @param array $assoc_args Associative arguments.
*
* @throws ExitException If something goes wrong.
*/
public function cmd_distribute_post( array $pos_args, array $assoc_args ): void {
$post_id = $pos_args[0];
if ( ! is_numeric( $post_id ) ) {
WP_CLI::error( 'Post ID must be a number.' );
}

if ( 'all' === $assoc_args['sites'] ) {
$sites = Network::get_networked_urls();
} else {
$sites = array_map(
fn( $site ) => untrailingslashit( trim( $site ) ),
explode( ',', $assoc_args['sites'] )
);
}

try {
$outgoing_post = Content_Distribution::get_distributed_post( $post_id ) ?? new Outgoing_Post( $post_id );
$config = $outgoing_post->set_config( $sites );
if ( is_wp_error( $config ) ) {
WP_CLI::error( $config->get_error_message() );
}

Content_Distribution::distribute_post( $outgoing_post );
WP_CLI::success( sprintf( 'Post with ID %d is distributed to %d sites: %s', $post_id, count( $config['site_urls'] ), implode( ', ', $config['site_urls'] ) ) );

} catch ( \Exception $e ) {
WP_CLI::error( $e->getMessage() );
}
}
}
42 changes: 38 additions & 4 deletions includes/content-distribution/class-outgoing-post.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
namespace Newspack_Network\Content_Distribution;

use Newspack_Network\Content_Distribution;
use WP_Post;
use Newspack_Network\Utils\Network;
use WP_Error;
use WP_Post;

/**
* Outgoing Post Class.
Expand Down Expand Up @@ -40,6 +41,11 @@ public function __construct( $post ) {
throw new \InvalidArgumentException( esc_html( __( 'Invalid post.', 'newspack-network' ) ) );
}

if ( ! in_array( $post->post_type, Content_Distribution::get_distributed_post_types() ) ) {
/* translators: unsupported post type for content distribution */
throw new \InvalidArgumentException( esc_html( sprintf( __( 'Post type %s is not supported as a distributed outgoing post.', 'newspack-network' ), $post->post_type ) ) );
}

Comment on lines +44 to +48
Copy link
Member

Choose a reason for hiding this comment

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

💯

$this->post = $post;
}

Expand All @@ -57,9 +63,27 @@ public function get_post() {
*
* @param int[] $site_urls Array of site URLs to distribute the post to.
*
* @return void|WP_Error Void on success, WP_Error on failure.
* @return array|WP_Error Config array on success, WP_Error on failure.
*/
public function set_config( $site_urls = [] ) {
public function set_config( $site_urls ) {
if ( empty( $site_urls ) ) {
return new WP_Error( 'config_no_site_urls', __( 'No site URLs provided.', 'newspack-network' ) );
}

$networked_urls = Network::get_networked_urls();
$urls_not_in_network = array_diff( $site_urls, $networked_urls );
if ( ! empty( $urls_not_in_network ) ) {
return new WP_Error(
'config_non_networked_urls',
sprintf(
/* translators: %s: list of non-networked URLs */
__( 'Non-networked URLs were passed to config: %s', 'newspack-network' ),
implode( ', ', $urls_not_in_network )
)
);
}


$config = get_post_meta( $this->post->ID, self::DISTRIBUTED_POST_META, true );
if ( ! is_array( $config ) ) {
$config = [];
Expand All @@ -68,8 +92,18 @@ public function set_config( $site_urls = [] ) {
if ( empty( $config['network_post_id'] ) ) {
$config['network_post_id'] = md5( $this->post->ID . get_bloginfo( 'url' ) );
}
$config['site_urls'] = $site_urls;

if ( empty( $config['site_urls'] ) ) {
$config['site_urls'] = [];
}

// If there are urls not already in the config, add them. Note that we don't support
// removing urls from the config.
$config['site_urls'] = array_unique( array_merge( $config['site_urls'], $site_urls ) );
Comment on lines +100 to +102
Copy link
Member

Choose a reason for hiding this comment

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

Can we unit test this new thing?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep! I added a test 👍


update_post_meta( $this->post->ID, self::DISTRIBUTED_POST_META, $config );

return $config;
}

/**
Expand Down
69 changes: 49 additions & 20 deletions tests/unit-tests/test-outgoing-post.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,31 @@
*/

use Newspack_Network\Content_Distribution\Outgoing_Post;
use Newspack_Network\Hub\Node as Hub_Node;

/**
* Test the Outgoing_Post class.
*/
class TestOutgoingPoist extends WP_UnitTestCase {
class TestOutgoingPost extends WP_UnitTestCase {


/**
* URL for node that receives posts.
* "Mocked" network nodes.
*
* @var string
* @var array
*/
protected $node_url = 'https://node.test';
protected $network = [
[
'id' => 1234,
'title' => 'Test Node',
'url' => 'https://node.test',
],
[
'id' => 5678,
'title' => 'Test Node 2',
'url' => 'https://other-node.test',
],
];

/**
* A distributed post.
Expand All @@ -31,16 +45,36 @@ class TestOutgoingPoist extends WP_UnitTestCase {
public function set_up() {
parent::set_up();

$post = $this->factory->post->create_and_get( [ 'post_type' => 'post' ] );
// "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' ] );
$this->outgoing_post = new Outgoing_Post( $post );
$this->outgoing_post->set_config( [ $this->node_url ] );
$this->outgoing_post->set_config( [ $this->network[0]['url'] ] );
}

/**
* Test adding a site URL to the config after already having added one.
*/
public function test_add_site_url() {
$config = $this->outgoing_post->get_config();
$this->assertTrue( in_array( $this->network[0]['url'], $config['site_urls'], true ) );
$this->assertEquals( 1, count( $config['site_urls'] ) );

// Now add one more site URL.
$this->outgoing_post->set_config( [ $this->network[1]['url'] ] );
$config = $this->outgoing_post->get_config();
// Check that both urls are there.
$this->assertTrue( in_array( $this->network[0]['url'], $config['site_urls'], true ) );
$this->assertTrue( in_array( $this->network[1]['url'], $config['site_urls'], true ) );
// But no more than that.
$this->assertEquals( 2, count( $config['site_urls'] ) );
}

/**
* Test set post distribution configuration.
*/
public function test_set_config() {
$result = $this->outgoing_post->set_config( [ $this->node_url ] );
$result = $this->outgoing_post->set_config( [ $this->network[0]['url'] ] );
$this->assertFalse( is_wp_error( $result ) );
}

Expand All @@ -49,17 +83,17 @@ public function test_set_config() {
*/
public function test_get_config() {
$config = $this->outgoing_post->get_config();
$this->assertSame( [ $this->node_url ], $config['site_urls'] );
$this->assertSame( [ $this->network[0]['url'] ], $config['site_urls'] );
$this->assertSame( 32, strlen( $config['network_post_id'] ) );
}

/**
* Test get config for non-distributed.
*/
public function test_get_config_for_non_distributed() {
$post = $this->factory->post->create_and_get( [ 'post_type' => 'post' ] );
$post = $this->factory->post->create_and_get( [ 'post_type' => 'post' ] );
$outgoing_post = new Outgoing_Post( $post );
$config = $outgoing_post->get_config();
$config = $outgoing_post->get_config();
$this->assertEmpty( $config['site_urls'] );
$this->assertEmpty( $config['network_post_id'] );
}
Expand All @@ -68,11 +102,12 @@ public function test_get_config_for_non_distributed() {
* Test set post distribution persists the network post ID.
*/
public function test_set_config_persists_network_post_id() {
$result = $this->outgoing_post->set_config( [ $this->node_url ] );
$horse = '';
$this->outgoing_post->set_config( [ $this->network[0]['url'] ] );
$config = $this->outgoing_post->get_config();

// Update the post distribution.
$result = $this->outgoing_post->set_config( [ 'https://other-node.test' ] );
// Update the post distribution with one more node.
$this->outgoing_post->set_config( [ $this->network[1]['url'] ] );
$new_config = $this->outgoing_post->get_config();

$this->assertSame( $config['network_post_id'], $new_config['network_post_id'] );
Expand All @@ -82,14 +117,8 @@ public function test_set_config_persists_network_post_id() {
* Test is distributed.
*/
public function test_is_distributed() {
$this->assertTrue( $this->outgoing_post->is_distributed() );

// Update the post distribution.
$result = $this->outgoing_post->set_config( [] );
$this->assertFalse( $this->outgoing_post->is_distributed() );

// Assert regular post.
$post = $this->factory->post->create_and_get( [ 'post_type' => 'post' ] );
$post = $this->factory->post->create_and_get( [ 'post_type' => 'post' ] );
$outgoing_post = new Outgoing_Post( $post );
$this->assertFalse( $outgoing_post->is_distributed() );
}
Expand Down
Loading