Skip to content

Move: Differentiate between internal and external Move #1413

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

Merged
merged 81 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
a67b890
init
pfefferle Mar 4, 2025
fdf7557
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 6, 2025
1c87343
Fix phpcs
pfefferle Mar 6, 2025
1cb3c89
Fix phpcs
pfefferle Mar 6, 2025
2cf6f55
support "Inherit"
pfefferle Mar 6, 2025
7a599d0
keep default
pfefferle Mar 6, 2025
d0d20e0
Add changelog
pfefferle Mar 6, 2025
d678025
differ between move externally and internally
pfefferle Mar 6, 2025
519f499
Fix tests
pfefferle Mar 6, 2025
e359f41
Implement internal move
pfefferle Mar 6, 2025
2f94b07
Add tests
pfefferle Mar 6, 2025
241bf22
fix phpcs
pfefferle Mar 6, 2025
3e7b023
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 6, 2025
97eb48b
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 10, 2025
7aba1c5
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 10, 2025
2d3c5dc
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 10, 2025
9219e98
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 14, 2025
b98fa15
added changelog
pfefferle Mar 14, 2025
39ea4dc
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 14, 2025
ac18538
Outbox: Add support for full activities
obenland Mar 18, 2025
adbc53e
iterate
obenland Mar 18, 2025
97ff76d
Fix remaining tests
obenland Mar 18, 2025
43af2cf
Merge branch 'trunk' into add/full-activity-support
obenland Mar 18, 2025
e681097
Simplify
obenland Mar 18, 2025
b6c8ec6
Use activity types
obenland Mar 18, 2025
b48dc85
Get title and id recursively
obenland Mar 18, 2025
00c5741
remove unused import
obenland Mar 18, 2025
b11eb10
Merge branch 'trunk' into add/full-activity-support
obenland Mar 18, 2025
d2a5e19
Buh-bye $activity_type!
obenland Mar 18, 2025
86d91d9
Further cleanup
obenland Mar 18, 2025
7daae92
cosmetics and update to latest Outbox::add changes
pfefferle Mar 19, 2025
bebbac7
check if `$activity` has to be transformed in an `Activity`
pfefferle Mar 19, 2025
eec265b
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 19, 2025
f8c15b3
Fix actor tests
obenland Mar 19, 2025
cdcffd4
Remove `Inherit` feature
pfefferle Mar 19, 2025
c174422
Fix Follow tests
obenland Mar 19, 2025
56cb618
Merge branch 'trunk' into add/full-activity-support
pfefferle Mar 19, 2025
ee40b63
Fix outbox content tests
obenland Mar 19, 2025
27edec9
Merge branch 'add/full-activity-support' of https://github.com/Automa…
obenland Mar 19, 2025
80469f7
Final fixes
obenland Mar 19, 2025
86b6eb4
Fix scheduler tests
obenland Mar 19, 2025
5dbdb79
set outbox ID after save
pfefferle Mar 19, 2025
0827123
use Activity functions and fix phpcs
pfefferle Mar 19, 2025
b021718
fix function
pfefferle Mar 19, 2025
378886f
fix phpcs
pfefferle Mar 19, 2025
dd11d46
simplify code
pfefferle Mar 19, 2025
900110a
some more progress
obenland Mar 19, 2025
1ea5ca6
Fixed tests
obenland Mar 19, 2025
56df01a
Better fix for Upgrade routine
obenland Mar 19, 2025
259e324
set type hint
pfefferle Mar 19, 2025
e7842a2
Merge branch 'add/full-activity-support' into add/inherit-activity
pfefferle Mar 19, 2025
f00b0c7
Fix tests when running them separately
obenland Mar 19, 2025
4371b61
Merge branch 'add/full-activity-support' of https://github.com/Automa…
obenland Mar 19, 2025
4759dc1
Add changelog
github-actions[bot] Mar 19, 2025
daf8621
Remove unused object types
obenland Mar 19, 2025
bfd5cb8
Merge branch 'add/full-activity-support' of https://github.com/Automa…
obenland Mar 19, 2025
bf5f298
Revert "Use activity types"
obenland Mar 19, 2025
f7b0b2e
Revert separating out activity types. It's unrelated to this PR
obenland Mar 19, 2025
d9392cc
Remove local variable
obenland Mar 19, 2025
e98dbcf
Update function docs
obenland Mar 19, 2025
e9b7086
Merge branch 'add/full-activity-support' into add/inherit-activity
pfefferle Mar 19, 2025
4f491c9
re-add `account`
pfefferle Mar 19, 2025
bf66d8e
revert CLI changes
pfefferle Mar 19, 2025
3f5c77e
test `Move` activity
pfefferle Mar 19, 2025
154ac94
there doe not have to be an `object`
pfefferle Mar 19, 2025
040c524
fix phpcs
pfefferle Mar 19, 2025
d097abe
Fix tests
pfefferle Mar 19, 2025
757f70e
Merge branch 'add/full-activity-support' into add/inherit-activity
pfefferle Mar 19, 2025
ede5fbc
remove object and content
pfefferle Mar 19, 2025
1725ba7
Make sure there's always an object_id
obenland Mar 19, 2025
794e231
Bail early
obenland Mar 19, 2025
5e2e577
Merge branch 'add/full-activity-support' into add/inherit-activity
pfefferle Mar 19, 2025
18af1b2
Test both ways of storing items
pfefferle Mar 19, 2025
ba8a55b
Remove fallback of the fallback. Let fatals guide us.
obenland Mar 20, 2025
038b7c0
Merge branch 'trunk' into add/full-activity-support
pfefferle Mar 20, 2025
811e22a
Merge branch 'trunk' into add/full-activity-support
obenland Mar 20, 2025
9624c81
fix id generator and re-add to outbox code as fallback
pfefferle Mar 20, 2025
b5d4d80
Merge branch 'add/full-activity-support' into add/inherit-activity
pfefferle Mar 20, 2025
438a951
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 20, 2025
8a4fb8d
fix is_same_domain check
pfefferle Mar 20, 2025
70a5d37
Merge branch 'trunk' into add/inherit-activity
pfefferle Mar 20, 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
4 changes: 4 additions & 0 deletions .github/changelog/add-inherit-activity
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: major
Type: added

Move: Differentiate between `internal` and 'external' Move.
94 changes: 83 additions & 11 deletions includes/class-move.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Activitypub;

use Activitypub\Activity\Actor;
use Activitypub\Activity\Activity;
use Activitypub\Collection\Actors;

/**
Expand All @@ -25,6 +26,26 @@ class Move {
* @return int|bool|\WP_Error The ID of the outbox item or false or WP_Error on failure.
*/
public static function account( $from, $to ) {
if ( is_same_domain( $from ) && is_same_domain( $to ) ) {
return self::internally( $from, $to );
}

return self::externally( $from, $to );
}

/**
* Move an ActivityPub Actor from one location (internal) to another (external).
*
* This helps migrating local profiles to a new external profile:
*
* `Move::externally( 'https://example.com/?author=123', 'https://mastodon.example/users/foo' );`
*
* @param string $from The current account URL.
* @param string $to The new account URL.
*
* @return int|bool|\WP_Error The ID of the outbox item or false or WP_Error on failure.
*/
public static function externally( $from, $to ) {
$user = Actors::get_by_various( $from );

if ( \is_wp_error( $user ) ) {
Expand All @@ -38,30 +59,81 @@ public static function account( $from, $to ) {
\update_option( 'activitypub_blog_user_moved_to', $to );
}

// Add the old account URL to alsoKnownAs.
if ( $user->get__id() > 0 ) {
self::update_user_also_known_as( $user->get__id(), $from );
} else {
self::update_blog_also_known_as( $from );
}

$response = Http::get_remote_object( $to );

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

$actor = new Actor();
$actor->from_array( $response );
$target_actor = new Actor();
$target_actor->from_array( $response );

// Check if the `Move` Activity is valid.
$also_known_as = $actor->get_also_known_as() ?? array();
$also_known_as = $target_actor->get_also_known_as() ?? array();
if ( ! in_array( $from, $also_known_as, true ) ) {
return new \WP_Error( 'invalid_target', __( 'Invalid target', 'activitypub' ) );
}

$activity = new Activity();
$activity->set_type( 'Move' );
$activity->set_actor( $user->get_id() );
$activity->set_origin( $user->get_id() );
$activity->set_object( $target_actor->get_id() );
$activity->set_target( $target_actor->get_id() );

// Add to outbox.
return add_to_outbox( $actor, 'Move', $user->get__id(), ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC );
return add_to_outbox( $activity, null, $user->get__id(), ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC );
}

/**
* Internal Move.
*
* Move an ActivityPub Actor from one location (internal) to another (internal).
*
* This helps migrating abandoned profiles to `Move` to other profiles:
*
* `Move::internally( 'https://example.com/?author=123', 'https://example.com/?author=321' );`
*
* ... or to change Actor-IDs like:
*
* `Move::internally( 'https://example.com/author/foo', 'https://example.com/?author=123' );`
*
* @param string $from The current account URL.
* @param string $to The new account URL.
*
* @return int|bool|\WP_Error The ID of the outbox item or false or WP_Error on failure.
*/
public static function internally( $from, $to ) {
$user = Actors::get_by_various( $from );

if ( \is_wp_error( $user ) ) {
return $user;
}

// Add the old account URL to alsoKnownAs.
if ( $user->get__id() > 0 ) {
self::update_user_also_known_as( $user->get__id(), $from );
\update_user_option( $user->get__id(), 'activitypub_moved_to', $to );
} else {
self::update_blog_also_known_as( $from );
\update_option( 'activitypub_blog_user_moved_to', $to );
}

// check if `$from` is a URL or an ID.
if ( \filter_var( $from, FILTER_VALIDATE_URL ) ) {
$actor = $from;
} else {
$actor = $user->get_id();
}

$activity = new Activity();
$activity->set_type( 'Move' );
$activity->set_actor( $actor );
$activity->set_origin( $actor );
$activity->set_object( $to );
$activity->set_target( $to );

return add_to_outbox( $activity, null, $user->get__id(), ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC );
}

/**
Expand Down
59 changes: 59 additions & 0 deletions tests/includes/class-test-handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
/**
* Handler Test Class
*
* @package Activitypub
*/

namespace Activitypub\Tests;

use Activitypub\Activity\Activity;
use Activitypub\Collection\Outbox;
use Activitypub\Handler;
use WP_UnitTestCase;

use function Activitypub\add_to_outbox;

/**
* Handler Test Class
*/
class Test_Handler extends WP_UnitTestCase {

/**
* The user ID.
*
* @var int
*/
protected $user_id;

/**
* Set up the test.
*/
public function set_up() {
parent::set_up();
$this->user_id = self::factory()->user->create(
array(
'role' => 'administrator',
)
);
}

/**
* Test the inherit functionality
*/
public function test_activity() {
// Create a mock inherit activity.
$activity = new Activity();
$activity->set_type( 'Move' );
$activity->set_id( 'https://example.com/activity/1' );
$activity->set_to( array( 'https://example.com/to' ) );
$activity->set_cc( array( 'https://example.com/cc' ) );

$id = add_to_outbox( $activity, null, $this->user_id );

$outbox_item = get_post( $id );
$outbox_activity = Outbox::get_activity( $outbox_item );

$this->assertEquals( 'Move', $outbox_activity->get_type() );
}
}
46 changes: 32 additions & 14 deletions tests/includes/class-test-move.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ public function test_account_with_valid_input() {
$from = Actors::get_by_id( self::$user_id )->get_id();
$to = 'https://newsite.com/user/1';

\Activitypub\Move::account( $from, $to );
\Activitypub\Move::externally( $from, $to );

$moved_to = Actors::get_by_id( self::$user_id )->get_moved_to();
$this->assertEquals( $to, $moved_to );

$also_known_as = Actors::get_by_id( self::$user_id )->get_also_known_as();
$this->assertContains( $from, $also_known_as );
$moved_to = Actors::get_by_id( self::$user_id )->get_moved_to();
$this->assertEquals( $to, $moved_to );
}

/**
Expand All @@ -62,7 +62,7 @@ public function test_account_with_valid_input() {
* @covers ::account
*/
public function test_account_with_invalid_user() {
$result = \Activitypub\Move::account(
$result = \Activitypub\Move::externally(
'https://example.com/nonexistent/user',
'https://newsite.com/user/999'
);
Expand All @@ -85,7 +85,7 @@ public function test_account_with_invalid_target() {
};
\add_filter( 'pre_http_request', $filter );

$result = \Activitypub\Move::account( $from, $to );
$result = \Activitypub\Move::externally( $from, $to );

$this->assertWPError( $result );
$this->assertEquals( 'http_request_failed', $result->get_error_code() );
Expand All @@ -112,12 +112,10 @@ public function test_account_with_duplicate_moves() {
};
\add_filter( 'pre_http_request', $filter );

\Activitypub\Move::account( $from, $to );
\Activitypub\Move::externally( $from, $to );

$also_known_as = Actors::get_by_id( self::$user_id )->get_also_known_as();
$this->assertCount( 3, $also_known_as );
$this->assertContains( $from, $also_known_as );
$this->assertContains( 'https://old.example.com/user/1', $also_known_as );
$moved_to = Actors::get_by_id( self::$user_id )->get_moved_to();
$this->assertEquals( $to, $moved_to );

\remove_filter( 'pre_http_request', $filter );
}
Expand All @@ -134,12 +132,32 @@ public function test_account_with_blog_author_as_actor() {
$from = Actors::get_by_id( Actors::BLOG_USER_ID )->get_id();
$to = 'https://newsite.com/user/0';

\Activitypub\Move::account( $from, $to );
\Activitypub\Move::externally( $from, $to );

$also_known_as = Actors::get_by_id( Actors::BLOG_USER_ID )->get_also_known_as();
$this->assertCount( 3, $also_known_as );
$this->assertContains( $from, $also_known_as );
$moved_to = Actors::get_by_id( Actors::BLOG_USER_ID )->get_moved_to();
$this->assertEquals( $to, $moved_to );

\delete_option( 'activitypub_actor_mode' );
}

/**
* Test the internally() method with valid input.
*
* @covers ::internally
*/
public function test_internally_with_valid_input() {
$from = get_author_posts_url( self::$user_id );
$to = Actors::get_by_id( self::$user_id )->get_id();

\Activitypub\Move::internally( $from, $to );

// Clear cache.
wp_cache_delete( self::$user_id, 'users' );

$moved_to = Actors::get_by_id( self::$user_id )->get_moved_to();
$this->assertEquals( $to, $moved_to );

$also_known_as = Actors::get_by_id( self::$user_id )->get_also_known_as();
$this->assertContains( $from, $also_known_as );
}
}
Loading