update( $wpdb->posts, array( 'guid' => sanitize_url( $target_uri ) ), array( 'ID' => sanitize_key( $origin_object->ID ) ) ); // Clear the cache. \wp_cache_delete( $origin_object->ID, 'posts' ); $success = true; $result = Remote_Actors::upsert( $target_json ); } // If both the target and origin are followed, merge them. if ( ! \is_wp_error( $target_object ) && ! \is_wp_error( $origin_object ) ) { $origin_users = \get_post_meta( $origin_object->ID, Followers::FOLLOWER_META_KEY, false ); $target_users = \get_post_meta( $target_object->ID, Followers::FOLLOWER_META_KEY, false ); // Get all user ids from $origin_users that are not in $target_users. $users = \array_diff( $origin_users, $target_users ); foreach ( $users as $follower_user_id ) { \add_post_meta( $target_object->ID, Followers::FOLLOWER_META_KEY, $follower_user_id ); } $success = true; $result = \wp_delete_post( $origin_object->ID ); } /** * Fires after an ActivityPub Move activity has been handled. * * @param array $activity The ActivityPub activity data. * @param int[] $user_ids The local user IDs. * @param bool $success True on success, false otherwise. * @param mixed $result The result of the operation (e.g., post ID, WP_Error, or status). */ \do_action( 'activitypub_handled_move', $activity, (array) $user_ids, $success, $result ); } /** * Convert the object and origin to the correct format. * * @param \Activitypub\Activity\Activity $activity The Activity object. * @return \Activitypub\Activity\Activity The filtered Activity object. */ public static function outbox_activity( $activity ) { if ( 'Move' === $activity->get_type() ) { $activity->set_object( object_to_uri( $activity->get_object() ) ); $activity->set_origin( $activity->get_actor() ); $activity->set_target( $activity->get_object() ); } return $activity; } /** * Extract the target from the activity. * * The ActivityStreams spec define the `target` attribute as the * destination of the activity, but Mastodon uses the `object` * attribute to move profiles. * * @param array $activity The JSON "Move" Activity. * * @return string|null The target URI or null if not found. */ private static function extract_target( $activity ) { if ( ! empty( $activity['target'] ) ) { return object_to_uri( $activity['target'] ); } if ( ! empty( $activity['object'] ) ) { return object_to_uri( $activity['object'] ); } return null; } /** * Extract the origin from the activity. * * The ActivityStreams spec define the `origin` attribute as source * of the activity, but Mastodon uses the `actor` attribute as source * to move profiles. * * @param array $activity The JSON "Move" Activity. * * @return string|null The origin URI or null if not found. */ private static function extract_origin( $activity ) { if ( ! empty( $activity['origin'] ) ) { return object_to_uri( $activity['origin'] ); } if ( ! empty( $activity['actor'] ) ) { return object_to_uri( $activity['actor'] ); } return null; } /** * Verify the move. * * @param array $target_object The target object. * @param array $origin_object The origin object. * * @return bool True if the move is verified, false otherwise. */ private static function verify_move( $target_object, $origin_object ) { // Check if both objects are valid. if ( \is_wp_error( $target_object ) || \is_wp_error( $origin_object ) ) { return false; } // Check if both objects are persons. if ( 'Person' !== $target_object['type'] || 'Person' !== $origin_object['type'] ) { return false; } // Check if the target and origin are not the same. if ( $target_object['id'] === $origin_object['id'] ) { return false; } // Check if the target has an alsoKnownAs property. if ( empty( $target_object['also_known_as'] ) ) { return false; } // Check if the origin is in the alsoKnownAs property of the target. if ( ! in_array( $origin_object['id'], $target_object['also_known_as'], true ) ) { return false; } // Check if the origin has a movedTo property. if ( empty( $origin_object['movedTo'] ) ) { return false; } // Check if the movedTo property of the origin is the target. if ( $origin_object['movedTo'] !== $target_object['id'] ) { return false; } return true; } }