PluginProbe
ActivityPub / 5.9.0
ActivityPub v5.9.0
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / class-move.php

class-move.php in ActivityPub 5.9.0, at includes/class-move.php

314 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Move class file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Activity\Actor;
11 use Activitypub\Activity\Activity;
12 use Activitypub\Collection\Actors;
13 use Activitypub\Model\Blog;
14 use Activitypub\Model\User;
15
16 /**
17 * ActivityPub (Account) Move Class
18 *
19 * @author Matthias Pfefferle
20 */
21 class Move {
22
23 /**
24 * Initialize the Move class.
25 */
26 public static function init() {
27 /**
28 * Filter to enable automatically moving Fediverse accounts when the domain changes.
29 *
30 * @param bool $domain_moves_enabled Whether domain moves are enabled.
31 */
32 $domain_moves_enabled = apply_filters( 'activitypub_enable_primary_domain_moves', false );
33
34 if ( $domain_moves_enabled ) {
35 // Add the filter to change the domain.
36 \add_filter( 'update_option_home', array( self::class, 'change_domain' ), 10, 2 );
37
38 if ( get_option( 'activitypub_old_host' ) ) {
39 \add_action( 'activitypub_construct_model_actor', array( self::class, 'maybe_initiate_old_user' ) );
40 \add_action( 'activitypub_pre_send_to_inboxes', array( self::class, 'pre_send_to_inboxes' ) );
41
42 if ( ! is_user_type_disabled( 'blog' ) ) {
43 \add_filter( 'activitypub_pre_get_by_username', array( self::class, 'old_blog_username' ), 10, 2 );
44 }
45 }
46 }
47 }
48
49 /**
50 * Move an ActivityPub account from one location to another.
51 *
52 * @param string $from The current account URL.
53 * @param string $to The new account URL.
54 *
55 * @return int|bool|\WP_Error The ID of the outbox item or false or WP_Error on failure.
56 */
57 public static function account( $from, $to ) {
58 if ( is_same_domain( $from ) && is_same_domain( $to ) ) {
59 return self::internally( $from, $to );
60 }
61
62 return self::externally( $from, $to );
63 }
64
65 /**
66 * Move an ActivityPub Actor from one location (internal) to another (external).
67 *
68 * This helps with migrating local profiles to a new external profile:
69 *
70 * `Move::externally( 'https://example.com/?author=123', 'https://mastodon.example/users/foo' );`
71 *
72 * @param string $from The current account URL.
73 * @param string $to The new account URL.
74 *
75 * @return int|bool|\WP_Error The ID of the outbox item or false or WP_Error on failure.
76 */
77 public static function externally( $from, $to ) {
78 $user = Actors::get_by_various( $from );
79
80 if ( \is_wp_error( $user ) ) {
81 return $user;
82 }
83
84 // Update the movedTo property.
85 if ( $user->get__id() > 0 ) {
86 \update_user_option( $user->get__id(), 'activitypub_moved_to', $to );
87 } else {
88 \update_option( 'activitypub_blog_user_moved_to', $to );
89 }
90
91 $response = Http::get_remote_object( $to );
92
93 if ( \is_wp_error( $response ) ) {
94 return $response;
95 }
96
97 $target_actor = new Actor();
98 $target_actor->from_array( $response );
99
100 // Check if the `Move` Activity is valid.
101 $also_known_as = $target_actor->get_also_known_as() ?? array();
102 if ( ! in_array( $from, $also_known_as, true ) ) {
103 return new \WP_Error( 'invalid_target', __( 'Invalid target', 'activitypub' ) );
104 }
105
106 $activity = new Activity();
107 $activity->set_type( 'Move' );
108 $activity->set_actor( $user->get_id() );
109 $activity->set_origin( $user->get_id() );
110 $activity->set_object( $user->get_id() );
111 $activity->set_target( $target_actor->get_id() );
112
113 // Add to outbox.
114 return add_to_outbox( $activity, null, $user->get__id(), ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC );
115 }
116
117 /**
118 * Internal Move.
119 *
120 * Move an ActivityPub Actor from one location (internal) to another (internal).
121 *
122 * This helps with migrating abandoned profiles to `Move` to other profiles:
123 *
124 * `Move::internally( 'https://example.com/?author=123', 'https://example.com/?author=321' );`
125 *
126 * ... or to change Actor-IDs like:
127 *
128 * `Move::internally( 'https://example.com/author/foo', 'https://example.com/?author=123' );`
129 *
130 * @param string $from The current account URL.
131 * @param string $to The new account URL.
132 *
133 * @return int|bool|\WP_Error The ID of the outbox item or false or WP_Error on failure.
134 */
135 public static function internally( $from, $to ) {
136 $user = Actors::get_by_various( $from );
137
138 if ( \is_wp_error( $user ) ) {
139 return $user;
140 }
141
142 // Add the old account URL to alsoKnownAs.
143 if ( $user->get__id() > 0 ) {
144 self::update_user_also_known_as( $user->get__id(), $from );
145 \update_user_option( $user->get__id(), 'activitypub_moved_to', $to );
146 } else {
147 self::update_blog_also_known_as( $from );
148 \update_option( 'activitypub_blog_user_moved_to', $to );
149 }
150
151 // check if `$from` is a URL or an ID.
152 if ( \filter_var( $from, FILTER_VALIDATE_URL ) ) {
153 $actor = $from;
154 } else {
155 $actor = $user->get_id();
156 }
157
158 $activity = new Activity();
159 $activity->set_type( 'Move' );
160 $activity->set_actor( $actor );
161 $activity->set_origin( $actor );
162 $activity->set_object( $actor );
163 $activity->set_target( $to );
164
165 return add_to_outbox( $activity, null, $user->get__id(), ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC );
166 }
167
168 /**
169 * Update the alsoKnownAs property of a user.
170 *
171 * @param int $user_id The user ID.
172 * @param string $from The current account URL.
173 */
174 private static function update_user_also_known_as( $user_id, $from ) {
175 // phpcs:ignore Universal.Operators.DisallowShortTernary.Found
176 $also_known_as = \get_user_option( 'activitypub_also_known_as', $user_id ) ?: array();
177 $also_known_as[] = $from;
178
179 \update_user_option( $user_id, 'activitypub_also_known_as', $also_known_as );
180 }
181
182 /**
183 * Update the alsoKnownAs property of the blog.
184 *
185 * @param string $from The current account URL.
186 */
187 private static function update_blog_also_known_as( $from ) {
188 $also_known_as = \get_option( 'activitypub_blog_user_also_known_as', array() );
189 $also_known_as[] = $from;
190
191 \update_option( 'activitypub_blog_user_also_known_as', $also_known_as );
192 }
193
194 /**
195 * Change domain for all ActivityPub Actors.
196 *
197 * This method handles domain migration according to the ActivityPub Data Portability spec.
198 * It stores the old host and calls Move::internally for each available profile.
199 * It also caches the JSON representation of the old Actor for future lookups.
200 *
201 * @param string $from The old domain.
202 * @param string $to The new domain.
203 *
204 * @return array Array of results from Move::internally calls.
205 */
206 public static function change_domain( $from, $to ) {
207 // Get all actors that need to be migrated.
208 $actors = Actors::get_all();
209
210 $results = array();
211 $to_host = \wp_parse_url( $to, \PHP_URL_HOST );
212 $from_host = \wp_parse_url( $from, \PHP_URL_HOST );
213
214 // Store the old host for future reference.
215 \update_option( 'activitypub_old_host', $from_host );
216
217 // Process each actor.
218 foreach ( $actors as $actor ) {
219 $actor_id = $actor->get_id();
220
221 // Replace the new host with the old host in the actor ID.
222 $old_actor_id = str_replace( $to_host, $from_host, $actor_id );
223
224 // Call Move::internally for this actor.
225 $result = self::internally( $old_actor_id, $actor_id );
226
227 if ( \is_wp_error( $result ) ) {
228 // Log the error and continue with the next actor.
229 Debug::write_log( 'Error moving actor: ' . $actor_id . ' - ' . $result->get_error_message() );
230 continue;
231 }
232
233 $json = str_replace( $to_host, $from_host, $actor->to_json() );
234
235 // Save the current actor data after migration.
236 if ( $actor instanceof Blog ) {
237 \update_option( 'activitypub_blog_user_old_host_data', $json, false );
238 } else {
239 \update_user_option( $actor->get__id(), 'activitypub_old_host_data', $json );
240 }
241
242 $results[] = array(
243 'actor' => $actor_id,
244 'result' => $result,
245 );
246 }
247
248 return $results;
249 }
250
251 /**
252 * Maybe initiate old user.
253 *
254 * This method checks if the current request domain matches the old host.
255 * If it does, it retrieves the cached data for the user and populates the instance.
256 *
257 * @param Blog|User $instance The Blog or User instance to populate.
258 */
259 public static function maybe_initiate_old_user( $instance ) {
260 if ( ! Query::get_instance()->is_old_host_request() ) {
261 return;
262 }
263
264 if ( $instance instanceof Blog ) {
265 $cached_data = \get_option( 'activitypub_blog_user_old_host_data' );
266 } elseif ( $instance instanceof User ) {
267 $cached_data = \get_user_option( 'activitypub_old_host_data', $instance->get__id() );
268 }
269
270 if ( ! empty( $cached_data ) ) {
271 $instance->from_json( $cached_data );
272 }
273 }
274
275 /**
276 * Pre-send to inboxes.
277 *
278 * @param string $json The ActivityPub Activity JSON.
279 */
280 public static function pre_send_to_inboxes( $json ) {
281 $json = json_decode( $json, true );
282
283 if ( 'Move' !== $json['type'] ) {
284 return;
285 }
286
287 if ( is_same_domain( $json['object'] ) ) {
288 return;
289 }
290
291 Query::get_instance()->set_old_host_request();
292 }
293
294 /**
295 * Filter to return the old blog username.
296 *
297 * @param null $pre The pre-existing value.
298 * @param string $username The username to check.
299 *
300 * @return Blog|null The old blog instance or null.
301 */
302 public static function old_blog_username( $pre, $username ) {
303 $old_host = \get_option( 'activitypub_old_host' );
304
305 // Special case for Blog Actor on old host.
306 if ( $old_host === $username && Query::get_instance()->is_old_host_request() ) {
307 // Return a new Blog instance which will load the cached data in its constructor.
308 $pre = new Blog();
309 }
310
311 return $pre;
312 }
313 }
314