Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
158 changes: 136 additions & 22 deletions includes/class-content-distribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,62 @@
* Main class for content distribution
*/
class Content_Distribution {
/**
* Queued network post updates.
*
* @var array Post IDs to update.
*/
private static $queued_post_updates = [];

/**
* Initialize this class and register hooks
*
* @return void
*/
public static function init() {
if ( ! defined( 'NEWPACK_NETWORK_CONTENT_DISTRIBUTION' ) || ! NEWPACK_NETWORK_CONTENT_DISTRIBUTION ) {
// Place content distribution behind a constant but run under unit tests.
if (
! ( defined( 'IS_TEST_ENV' ) && IS_TEST_ENV ) &&
( ! defined( 'NEWPACK_NETWORK_CONTENT_DISTRIBUTION' ) || ! NEWPACK_NETWORK_CONTENT_DISTRIBUTION )
) {
return;
}
CLI::init();
add_action( 'init', [ __CLASS__, 'register_listeners' ] );

add_action( 'init', [ __CLASS__, 'register_data_event_actions' ] );
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( 'updated_postmeta', [ __CLASS__, 'handle_postmeta_update' ], 10, 3 );
add_action( 'newspack_network_incoming_post_inserted', [ __CLASS__, 'handle_incoming_post_inserted' ], 10, 3 );

CLI::init();
}

/**
* Register the listeners to the Newspack Data Events API
* Register the data event actions for content distribution.
*
* @return void
*/
public static function register_listeners() {
public static function register_data_event_actions() {
if ( ! class_exists( 'Newspack\Data_Events' ) ) {
return;
}
Data_Events::register_listener( 'wp_after_insert_post', 'network_post_updated', [ __CLASS__, 'handle_post_updated' ] );
Data_Events::register_listener( 'newspack_network_incoming_post_inserted', 'network_incoming_post_inserted', [ __CLASS__, 'handle_incoming_post_inserted' ] );
Data_Events::register_action( 'network_post_updated' );
Data_Events::register_action( 'network_incoming_post_inserted' );
}

/**
* Distribute queued posts.
*/
public static function distribute_queued_posts() {
$post_ids = array_unique( self::$queued_post_updates );
foreach ( $post_ids as $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
continue;
}
self::distribute_post( $post );
}
}

/**
Expand All @@ -61,21 +92,83 @@ public static function webhooks_request_priority( $priority, $action_name ) {
}

/**
* Post update listener callback.
* Validate whether an update to DISTRIBUTED_POST_META is allowed.
*
* @param Outgoing_Post|WP_Post|int $post The post object or ID.
* @param null|bool $check Whether to allow updating metadata for the given type. Default null.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Metadata value.
*/
public static function maybe_short_circuit_distributed_meta( $check, $object_id, $meta_key, $meta_value ) {
if ( Outgoing_Post::DISTRIBUTED_POST_META !== $meta_key ) {
return $check;
}

// Ensure the post type can be distributed.
$post_types = self::get_distributed_post_types();
if ( ! in_array( get_post_type( $object_id ), $post_types, true ) ) {
return false;
}

if ( is_wp_error( Outgoing_Post::validate_distribution( $meta_value ) ) ) {
return false;
}

// Prevent removing existing distributions.
$current_value = get_post_meta( $object_id, $meta_key, true );
if ( ! empty( array_diff( empty( $current_value ) ? [] : $current_value, $meta_value ) ) ) {
return false;
}

return $check;
}

/**
* Distribute post on postmeta update.
*
* @param int $meta_id Meta ID.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
*
* @return array|null The post payload or null if the post is not distributed.
* @return void
*/
public static function handle_post_updated( $post ) {
if ( ! $post instanceof Outgoing_Post ) {
$post = self::get_distributed_post( $post );
public static function handle_postmeta_update( $meta_id, $object_id, $meta_key ) {
if ( ! $object_id ) {
return;
}
if ( $post ) {
return $post->get_payload();
$post = get_post( $object_id );
if ( ! $post ) {
return;
}
if ( ! self::is_post_distributed( $post ) ) {
return;
}
// Ignore reserved keys but run if the meta is setting the distribution.
if (
Outgoing_Post::DISTRIBUTED_POST_META !== $meta_key &&
in_array( $meta_key, self::get_reserved_post_meta_keys(), true )
) {
return;
}
self::$queued_post_updates[] = $object_id;
}

return null;
/**
* Distribute post on post updated.
*
* @param WP_Post|int $post The post object or ID.
*
* @return void
*/
public static function handle_post_updated( $post ) {
$post = get_post( $post );
if ( ! $post ) {
return;
}
if ( ! self::is_post_distributed( $post ) ) {
return;
}
self::$queued_post_updates[] = $post->ID;
}

/**
Expand All @@ -86,7 +179,10 @@ public static function handle_post_updated( $post ) {
* @param array $post_payload The post payload.
*/
public static function handle_incoming_post_inserted( $post_id, $is_linked, $post_payload ) {
return [
if ( ! class_exists( 'Newspack\Data_Events' ) ) {
return;
}
$data = [
'origin' => [
'site_url' => $post_payload['site_url'],
'post_id' => $post_payload['post_id'],
Expand All @@ -97,6 +193,7 @@ public static function handle_incoming_post_inserted( $post_id, $is_linked, $pos
'is_linked' => $is_linked,
],
];
Data_Events::dispatch( 'network_incoming_post_inserted', $data );
}

/**
Expand Down Expand Up @@ -147,6 +244,17 @@ public static function get_reserved_post_meta_keys() {
);
}

/**
* Whether a given post is distributed.
*
* @param WP_Post|int $post The post object or ID.
*
* @return bool Whether the post is distributed.
*/
public static function is_post_distributed( $post ) {
return (bool) self::get_distributed_post( $post );
}

/**
* Get a distributed post.
*
Expand All @@ -160,21 +268,27 @@ public static function get_distributed_post( $post ) {
} catch ( \InvalidArgumentException ) {
return null;
}

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

/**
* Manually trigger post distribution.
* Trigger post distribution.
*
* @param WP_Post|Outgoing_Post|int $post The post object or ID.
*
* @return void
*/
public static function distribute_post( $post ) {
$data = self::handle_post_updated( $post );
if ( $data ) {
Data_Events::dispatch( 'network_post_updated', $data );
if ( ! class_exists( 'Newspack\Data_Events' ) ) {
return;
}
if ( $post instanceof Outgoing_Post ) {
$distributed_post = $post;
} else {
$distributed_post = self::get_distributed_post( $post );
}
if ( $distributed_post ) {
Data_Events::dispatch( 'network_post_updated', $distributed_post->get_payload() );
}
}
}
8 changes: 4 additions & 4 deletions includes/content-distribution/class-cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ public function cmd_distribute_post( array $pos_args, array $assoc_args ): void

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() );
$sites = $outgoing_post->set_distribution( $sites );
if ( is_wp_error( $sites ) ) {
WP_CLI::error( $sites->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'] ) ) );
WP_CLI::success( sprintf( 'Post with ID %d is distributed to %d sites: %s', $post_id, count( $sites ), implode( ', ', $sites ) ) );

} catch ( \Exception $e ) {
WP_CLI::error( $e->getMessage() );
Expand Down
32 changes: 19 additions & 13 deletions includes/content-distribution/class-incoming-post.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public function __construct( $payload ) {
}

$this->payload = $payload;
$this->network_post_id = $payload['config']['network_post_id'];
$this->network_post_id = $payload['network_post_id'];

$post = $this->query_post();
if ( $post ) {
Expand All @@ -105,20 +105,19 @@ public static function get_payload_error( $payload ) {
if (
! is_array( $payload ) ||
empty( $payload['post_id'] ) ||
empty( $payload['config'] ) ||
empty( $payload['network_post_id'] ) ||
empty( $payload['sites'] ) ||
empty( $payload['post_data'] )
) {
return new WP_Error( 'invalid_post', __( 'Invalid post payload.', 'newspack-network' ) );
}

$config = $payload['config'];

if ( empty( $config['network_post_id'] ) || empty( $config['site_urls'] ) ) {
if ( empty( $payload['sites'] ) ) {
return new WP_Error( 'not_distributed', __( 'Post is not configured for distribution.', 'newspack-network' ) );
}

$site_url = get_bloginfo( 'url' );
if ( ! in_array( $site_url, $config['site_urls'], true ) ) {
if ( ! in_array( $site_url, $payload['sites'], true ) ) {
return new WP_Error( 'not_distributed_to_site', __( 'Post is not configured for distribution on this site.', 'newspack-network' ) );
}
}
Expand Down Expand Up @@ -217,26 +216,33 @@ public function is_linked() {
* @return void
*/
protected function update_post_meta() {
$data = $this->payload['post_data']['post_meta'];

$reserved_keys = Content_Distribution::get_reserved_post_meta_keys();

// Clear existing post meta.
// Clear existing post meta that are not in the payload.
$post_meta = get_post_meta( $this->ID );
foreach ( $post_meta as $meta_key => $meta_value ) {
if ( ! in_array( $meta_key, $reserved_keys, true ) ) {
if (
! in_array( $meta_key, $reserved_keys, true ) &&
! array_key_exists( $meta_key, $data )
) {
delete_post_meta( $this->ID, $meta_key );
}
}

$data = $this->payload['post_data']['post_meta'];

if ( empty( $data ) ) {
return;
}

foreach ( $data as $meta_key => $meta_value ) {
if ( ! in_array( $meta_key, $reserved_keys, true ) ) {
foreach ( $meta_value as $value ) {
add_post_meta( $this->ID, $meta_key, $value );
if ( 1 === count( $meta_value ) ) {
update_post_meta( $this->ID, $meta_key, $meta_value[0] );
} else {
foreach ( $meta_value as $value ) {
add_post_meta( $this->ID, $meta_key, $value );
}
}
}
}
Expand Down Expand Up @@ -317,7 +323,7 @@ protected function update_payload( $payload ) {
}

// Do not update if network post ID mismatches.
if ( $this->network_post_id !== $payload['config']['network_post_id'] ) {
if ( $this->network_post_id !== $payload['network_post_id'] ) {
return new WP_Error( 'mismatched_post_id', __( 'Mismatched post ID.', 'newspack-network' ) );
}

Expand Down
Loading
Loading