PluginProbe
ActivityPub / 5.3.2
ActivityPub v5.3.2
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 / handler / class-move.php

class-move.php in ActivityPub 5.3.2, at includes/handler/class-move.php

197 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Move handler file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Handler;
9
10 use Activitypub\Http;
11 use Activitypub\Collection\Followers;
12
13 use function Activitypub\object_to_uri;
14
15 /**
16 * Handle Move requests.
17 *
18 * @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-move
19 * @see https://docs.joinmastodon.org/user/moving/
20 * @see https://docs.joinmastodon.org/spec/activitypub/#Move
21 */
22 class Move {
23 /**
24 * Initialize the class, registering WordPress hooks.
25 */
26 public static function init() {
27 \add_action( 'activitypub_inbox_move', array( self::class, 'handle_move' ) );
28 }
29
30 /**
31 * Handle Move requests.
32 *
33 * @param array $activity The JSON "Move" Activity.
34 */
35 public static function handle_move( $activity ) {
36 $target = self::extract_target( $activity );
37 $origin = self::extract_origin( $activity );
38
39 if ( ! $target || ! $origin ) {
40 return;
41 }
42
43 $target_object = Http::get_remote_object( $target );
44 $origin_object = Http::get_remote_object( $origin );
45
46 $verified = self::verify_move( $target_object, $origin_object );
47
48 if ( ! $verified ) {
49 return;
50 }
51
52 $target_follower = Followers::get_follower_by_actor( $target );
53 $origin_follower = Followers::get_follower_by_actor( $origin );
54
55 /*
56 * If the new target is followed, but the origin is not,
57 * everything is fine, so we can return.
58 */
59 if ( $target_follower && ! $origin_follower ) {
60 return;
61 }
62
63 /*
64 * If the new target is not followed, but the origin is,
65 * update the origin follower to the new target.
66 */
67 if ( ! $target_follower && $origin_follower ) {
68 $origin_follower->from_array( $target_object );
69 $origin_follower->set_id( $target );
70 $origin_id = $origin_follower->upsert();
71
72 global $wpdb;
73 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
74 $wpdb->update(
75 $wpdb->posts,
76 array( 'guid' => sanitize_url( $target ) ),
77 array( 'ID' => sanitize_key( $origin_id ) )
78 );
79
80 // Clear the cache.
81 wp_cache_delete( $origin_id, 'posts' );
82 return;
83 }
84
85 /*
86 * If the new target is followed, and the origin is followed,
87 * move users and delete the origin follower.
88 */
89 if ( $target_follower && $origin_follower ) {
90 $origin_users = \get_post_meta( $origin_follower->get__id(), '_activitypub_user_id', false );
91 $target_users = \get_post_meta( $target_follower->get__id(), '_activitypub_user_id', false );
92
93 // Get all user ids from $origin_users that are not in $target_users.
94 $users = \array_diff( $origin_users, $target_users );
95
96 foreach ( $users as $user_id ) {
97 \add_post_meta( $target_follower->get__id(), '_activitypub_user_id', $user_id );
98 }
99
100 $origin_follower->delete();
101 }
102 }
103
104 /**
105 * Extract the target from the activity.
106 *
107 * The ActivityStreams spec define the `target` attribute as the
108 * destination of the activity, but Mastodon uses the `object`
109 * attribute to move profiles.
110 *
111 * @param array $activity The JSON "Move" Activity.
112 *
113 * @return string|null The target URI or null if not found.
114 */
115 private static function extract_target( $activity ) {
116 if ( ! empty( $activity['target'] ) ) {
117 return object_to_uri( $activity['target'] );
118 }
119
120 if ( ! empty( $activity['object'] ) ) {
121 return object_to_uri( $activity['object'] );
122 }
123
124 return null;
125 }
126
127 /**
128 * Extract the origin from the activity.
129 *
130 * The ActivityStreams spec define the `origin` attribute as source
131 * of the activity, but Mastodon uses the `actor` attribute as source
132 * to move profiles.
133 *
134 * @param array $activity The JSON "Move" Activity.
135 *
136 * @return string|null The origin URI or null if not found.
137 */
138 private static function extract_origin( $activity ) {
139 if ( ! empty( $activity['origin'] ) ) {
140 return object_to_uri( $activity['origin'] );
141 }
142
143 if ( ! empty( $activity['actor'] ) ) {
144 return object_to_uri( $activity['actor'] );
145 }
146
147 return null;
148 }
149
150 /**
151 * Verify the move.
152 *
153 * @param array $target_object The target object.
154 * @param array $origin_object The origin object.
155 *
156 * @return bool True if the move is verified, false otherwise.
157 */
158 private static function verify_move( $target_object, $origin_object ) {
159 // Check if both objects are valid.
160 if ( \is_wp_error( $target_object ) || \is_wp_error( $origin_object ) ) {
161 return false;
162 }
163
164 // Check if both objects are persons.
165 if ( 'Person' !== $target_object['type'] || 'Person' !== $origin_object['type'] ) {
166 return false;
167 }
168
169 // Check if the target and origin are not the same.
170 if ( $target_object['id'] === $origin_object['id'] ) {
171 return false;
172 }
173
174 // Check if the target has an alsoKnownAs property.
175 if ( empty( $target_object['also_known_as'] ) ) {
176 return false;
177 }
178
179 // Check if the origin is in the alsoKnownAs property of the target.
180 if ( ! in_array( $origin_object['id'], $target_object['also_known_as'], true ) ) {
181 return false;
182 }
183
184 // Check if the origin has a movedTo property.
185 if ( empty( $origin_object['movedTo'] ) ) {
186 return false;
187 }
188
189 // Check if the movedTo property of the origin is the target.
190 if ( $origin_object['movedTo'] !== $target_object['id'] ) {
191 return false;
192 }
193
194 return true;
195 }
196 }
197