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): migrator #185

Merged
merged 40 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6400e65
feat(content-distribution): migrator
miguelpeixe Jan 14, 2025
07f8774
feat: link existing incoming post
miguelpeixe Jan 14, 2025
99b39d4
chore: fix docblock
miguelpeixe Jan 14, 2025
22015c4
fix(content-distribution): persist site hash
miguelpeixe Jan 15, 2025
ed4cf73
Merge branch 'fix/content-distribution-site-hash' into feat/content-d…
miguelpeixe Jan 15, 2025
243c6bf
feat: cli
miguelpeixe Jan 15, 2025
39185b9
chore: remove copilot hallucination
miguelpeixe Jan 15, 2025
b73b6b3
fix: connection map clear
miguelpeixe Jan 15, 2025
3751782
fix(cli): validate subscriptions before migrating
miguelpeixe Jan 15, 2025
daefaf1
Merge branch 'trunk' into feat/content-distribution-migrator
miguelpeixe Jan 15, 2025
65804e7
feat(cli): support deletion
miguelpeixe Jan 15, 2025
32baad4
chore: lint
miguelpeixe Jan 15, 2025
4cb530a
feat: link payload and support unlinked state
miguelpeixe Jan 16, 2025
46523d1
fix: optimize subscriptions query
miguelpeixe Jan 16, 2025
c57e511
fix(cli): edge cases and better messaging
miguelpeixe Jan 16, 2025
2b337d9
fix(cli): add optional strict mode and migrate via posts
miguelpeixe Jan 16, 2025
156a28a
chore: improve cli description
miguelpeixe Jan 16, 2025
c3f5c1f
chore: improve cli description
miguelpeixe Jan 16, 2025
1850afd
fix: migrate incoming posts via data events
miguelpeixe Jan 20, 2025
e52bba2
Merge branch 'trunk' into feat/content-distribution-migrator
miguelpeixe Jan 20, 2025
8853e8e
fix: log batch migrated
miguelpeixe Jan 20, 2025
a7d410e
Merge branch 'trunk' into feat/content-distribution-migrator
miguelpeixe Jan 20, 2025
0c63bee
chore: update docblock
miguelpeixe Jan 20, 2025
c2f2dcc
Merge branch 'trunk' into feat/content-distribution-migrator
miguelpeixe Jan 20, 2025
0bfa9a7
chore: remove unused imports
miguelpeixe Jan 21, 2025
ad8fdd5
fix: typo
miguelpeixe Jan 21, 2025
893ed4e
fix: rename function
miguelpeixe Jan 21, 2025
ecd3d70
fix: typo
miguelpeixe Jan 21, 2025
1237389
fix: add error
miguelpeixe Jan 21, 2025
9bb8a15
Merge branch 'trunk' into feat/content-distribution-migrator
miguelpeixe Jan 21, 2025
9d5a2b4
Merge branch 'trunk' into feat/content-distribution-migrator
miguelpeixe Jan 21, 2025
1d5f4d1
fix: incoming post migration
miguelpeixe Jan 21, 2025
6f7af2c
chore: improve log
miguelpeixe Jan 21, 2025
65a3b09
Merge branch 'trunk' into feat/content-distribution-migrator
miguelpeixe Jan 22, 2025
eef612a
fix: remove distributor meta from payload
miguelpeixe Jan 22, 2025
77ff94b
fix: network post id
miguelpeixe Jan 22, 2025
f6a439d
fix: use current post status as `status_on_create`
miguelpeixe Jan 22, 2025
ce95fa7
fix: ensure default post status
miguelpeixe Jan 22, 2025
0a731b8
fix: use `wp_update_post()` for existing posts
miguelpeixe Jan 22, 2025
5d991c1
fix: ensure the latest post data on insert
miguelpeixe Jan 22, 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
3 changes: 2 additions & 1 deletion includes/class-content-distribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Newspack_Network\Content_Distribution\Canonical_Url;
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 @@ -55,8 +56,8 @@ public static function init() {
CLI::init();
API::init();
Editor::init();

Canonical_Url::init();
Distributor_Migrator::init();
}

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

namespace Newspack_Network\Content_Distribution;

use Newspack_Network\Content_Distribution;
naxoc marked this conversation as resolved.
Show resolved Hide resolved
use Newspack_Network\Utils\Network;
use WP_Error;
use WP_Query;
use WP_Post;
use InvalidArgumentException;
use WP_REST_Request;
use WP_REST_Response;

/**
* Distributor Migrator Class.
*/
class Distributor_Migrator {
/**
* Initialize hooks.
*/
public static function init() {
add_action( 'rest_api_init', [ __CLASS__, 'register_rest_routes' ] );
}

/**
* Register REST routes.
*/
public static function register_rest_routes() {
register_rest_route(
'newspack-network/v1',
'/content-distribution/distributor-migrator/link/(?P<post_id>\d+)',
[
'methods' => 'POST',
'callback' => [ __CLASS__, 'api_link_incoming_post' ],
'args' => [
'subscription_signature' => [
'type' => 'string',
'required' => true,
],
'network_post_id' => [
'type' => 'string',
'required' => true,
],
],
'permission_callback' => '__return_true', // TODO: Check network signature.
miguelpeixe marked this conversation as resolved.
Show resolved Hide resolved
]
);
}

/**
* API callback to link a migrated incoming post.
*
* @param WP_REST_Request $request The REST request object.
*
* @return WP_REST_Response|WP_Error The REST response or error.
*/
public static function api_link_incoming_post( $request ) {
$post_id = $request->get_param( 'post_id' );
$subscription_signature = $request->get_param( 'subscription_signature' );
$network_post_id = $request->get_param( 'network_post_id' );

$response = self::link_incoming_post( $post_id, $subscription_signature, $network_post_id );

if ( is_wp_error( $response ) ) {
return $response;
}

return new WP_REST_Response( null, 200 );
}

/**
* Link an incoming post given its subscription signature.
*
* @param int $post_id The ID of the post to link.
* @param string $subscription_signature The signature of the subscription.
* @param string $network_post_id The network post ID.
*
* @return WP_Error|void WP_Error on failure, void on success.
*/
protected static function link_incoming_post( $post_id, $subscription_signature, $network_post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error( 'post_not_found', __( 'Post not found.', 'newspack-network' ) );
}

$post_signature = get_post_meta( $post_id, 'dt_subscription_signature', true );
if ( $post_signature !== $subscription_signature ) {
return new WP_Error( 'subscription_signature_mismatch', __( 'Subscription signature mismatch.', 'newspack-network' ) );
}

update_post_meta( $post_id, Incoming_Post::NETWORK_POST_ID_META, $network_post_id );
}

/**
* Get all Distributor subscriptions.
*
* @return WP_Post[] Array of WP_Post objects representing Distributor subscriptions.
*/
public static function get_distributor_subscriptions() {
$query = new WP_Query(
[
'post_type' => 'dt_subscription',
'posts_per_page' => '-1',
]
);
return $query->posts;
}

/**
* Migrate a post subscription from Distributor to Newspack Network Content Distribution.
*
* @param int $subscription_id The ID of the subscription to migrate.
* @param boolean $distribute Whether to distribute the post after migrating the subscription.
*
* @return Outgoing_Post|WP_Error Outgoing_Post on success, WP_Error on failure.
*/
public static function migrate_subscription( $subscription_id, $distribute = false ) {
$subscription = get_post( $subscription_id );
if ( ! $subscription ) {
return new WP_Error( 'subscription_not_found', __( 'Subscription not found.', 'newspack-network' ) );
}

$post_id = get_post_meta( $subscription_id, 'dt_subscription_post_id', true );
$post = get_post( $post_id );

if ( ! $post_id || ! $post || empty( $post->ID ) ) {
return new WP_Error( 'post_not_found', __( 'Post not found.', 'newspack-network' ) );
}

$target_url = get_post_meta( $subscription_id, 'dt_subscription_target_url', true );

if ( ! $target_url ) {
return new WP_Error( 'target_url_not_found', __( 'Target URL not found.', 'newspack-network' ) );
}

$network_urls = Network::get_networked_urls();
$network_url = array_filter(
$network_urls,
function( $url ) use ( $target_url ) {
return false !== strpos( $target_url, $url );
}
);
$network_url = array_shift( $network_url );

if ( empty( $network_url ) ) {
return new WP_Error(
'target_url_not_networked',
sprintf(
// translators: target URL.
__( 'Target URL "%s" is not networked.', 'newspack-network' ),
$target_url
)
);
}

// Configure distribution.
try {
$outgoing_post = new Outgoing_Post( $post );
} catch ( InvalidArgumentException $e ) {
return new WP_Error( 'outgoing_post_error', $e->getMessage() );
}
$distribution = $outgoing_post->set_distribution( [ $network_url ] );
if (
is_wp_error( $distribution ) &&
// Ignore error if the post is already distributed.
'update_failed' !== $distribution->get_error_code()
) {
return $distribution;
}

// Link the migrated post.
$link_result = self::link_migrated_subscription( $network_url, $subscription_id, $outgoing_post->get_network_post_id() );
if ( is_wp_error( $link_result ) ) {
return $link_result;
}

if ( $distribute ) {
Content_Distribution::distribute_post( $outgoing_post );
}

// Delete the post meta.
delete_post_meta( $post_id, 'dt_subscriptions' );
delete_post_meta( $post_id, 'dt_connection_map' );

// Delete the subscription.
wp_delete_post( $subscription_id );

return $outgoing_post;
}

/**
* Trigger the request to link the incoming post from a subscription being
* migrated.
*
* @param string $site_url The URL of the site where the post is being migrated to.
* @param int $subscription_id The ID of the subscription being migrated.
* @param string $network_post_id The network post ID for the migrated post.
*
* @return WP_Error|void WP_Error on failure, void on success.
*/
protected static function link_migrated_subscription( $site_url, $subscription_id, $network_post_id ) {
$remote_post_id = get_post_meta( $subscription_id, 'dt_subscription_remote_post_id', true );
$signature = get_post_meta( $subscription_id, 'dt_subscription_signature', true );
$url = trailingslashit( $site_url ) . 'wp-json/newspack-network/v1/content-distribution/distributor-migrator/link/' . $remote_post_id;
$response = wp_remote_post(
$url,
[
'body' => [
'subscription_signature' => $signature,
'network_post_id' => $network_post_id,
],
'timeout' => 20, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
]
);
if ( is_wp_error( $response ) ) {
return $response;
}

$link_response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $link_response_code ) {
$link_response_body = wp_remote_retrieve_body( $response );
$error_message = __( 'Error linking migrated post.', 'newspack-network' );
if ( $link_response_body ) {
$error_message .= ' ' . $link_response_body;
}
return new WP_Error( 'link_migrated_subscription_error', $error_message );
}
}
}