|
10 | 10 | use Activitypub\Activity\Actor;
|
11 | 11 | use Activitypub\Activity\Activity;
|
12 | 12 | use Activitypub\Collection\Actors;
|
| 13 | +use Activitypub\Model\Blog; |
| 14 | +use Activitypub\Model\User; |
13 | 15 |
|
14 | 16 | /**
|
15 | 17 | * ActivityPub (Account) Move Class
|
16 | 18 | *
|
17 | 19 | * @author Matthias Pfefferle
|
18 | 20 | */
|
19 | 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 | + |
20 | 49 | /**
|
21 | 50 | * Move an ActivityPub account from one location to another.
|
22 | 51 | *
|
@@ -161,4 +190,124 @@ private static function update_blog_also_known_as( $from ) {
|
161 | 190 |
|
162 | 191 | \update_option( 'activitypub_blog_user_also_known_as', $also_known_as );
|
163 | 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, false ); |
| 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 | + } |
164 | 313 | }
|
0 commit comments