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

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

214 lines 5.7 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 \add_filter( 'activitypub_get_outbox_activity', array( self::class, 'outbox_activity' ) );
29 }
30
31 /**
32 * Handle Move requests.
33 *
34 * @param array $activity The JSON "Move" Activity.
35 */
36 public static function handle_move( $activity ) {
37 $target = self::extract_target( $activity );
38 $origin = self::extract_origin( $activity );
39
40 if ( ! $target || ! $origin ) {
41 return;
42 }
43
44 $target_object = Http::get_remote_object( $target );
45 $origin_object = Http::get_remote_object( $origin );
46
47 $verified = self::verify_move( $target_object, $origin_object );
48
49 if ( ! $verified ) {
50 return;
51 }
52
53 $target_follower = Followers::get_follower_by_actor( $target );
54 $origin_follower = Followers::get_follower_by_actor( $origin );
55
56 /*
57 * If the new target is followed, but the origin is not,
58 * everything is fine, so we can return.
59 */
60 if ( $target_follower && ! $origin_follower ) {
61 return;
62 }
63
64 /*
65 * If the new target is not followed, but the origin is,
66 * update the origin follower to the new target.
67 */
68 if ( ! $target_follower && $origin_follower ) {
69 $origin_follower->from_array( $target_object );
70 $origin_follower->set_id( $target );
71 $origin_id = $origin_follower->upsert();
72
73 global $wpdb;
74 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
75 $wpdb->update(
76 $wpdb->posts,
77 array( 'guid' => sanitize_url( $target ) ),
78 array( 'ID' => sanitize_key( $origin_id ) )
79 );
80
81 // Clear the cache.
82 wp_cache_delete( $origin_id, 'posts' );
83 return;
84 }
85
86 /*
87 * If the new target is followed, and the origin is followed,
88 * move users and delete the origin follower.
89 */
90 if ( $target_follower && $origin_follower ) {
91 $origin_users = \get_post_meta( $origin_follower->get__id(), '_activitypub_user_id', false );
92 $target_users = \get_post_meta( $target_follower->get__id(), '_activitypub_user_id', false );
93
94 // Get all user ids from $origin_users that are not in $target_users.
95 $users = \array_diff( $origin_users, $target_users );
96
97 foreach ( $users as $user_id ) {
98 \add_post_meta( $target_follower->get__id(), '_activitypub_user_id', $user_id );
99 }
100
101 $origin_follower->delete();
102 }
103 }
104
105 /**
106 * Convert the object and origin to the correct format.
107 *
108 * @param \Activitypub\Activity\Activity $activity The Activity object.
109 * @return \Activitypub\Activity\Activity The filtered Activity object.
110 */
111 public static function outbox_activity( $activity ) {
112 if ( 'Move' === $activity->get_type() ) {
113 $activity->set_object( object_to_uri( $activity->get_object() ) );
114 $activity->set_origin( $activity->get_actor() );
115 $activity->set_target( $activity->get_object() );
116 }
117
118 return $activity;
119 }
120
121 /**
122 * Extract the target from the activity.
123 *
124 * The ActivityStreams spec define the `target` attribute as the
125 * destination of the activity, but Mastodon uses the `object`
126 * attribute to move profiles.
127 *
128 * @param array $activity The JSON "Move" Activity.
129 *
130 * @return string|null The target URI or null if not found.
131 */
132 private static function extract_target( $activity ) {
133 if ( ! empty( $activity['target'] ) ) {
134 return object_to_uri( $activity['target'] );
135 }
136
137 if ( ! empty( $activity['object'] ) ) {
138 return object_to_uri( $activity['object'] );
139 }
140
141 return null;
142 }
143
144 /**
145 * Extract the origin from the activity.
146 *
147 * The ActivityStreams spec define the `origin` attribute as source
148 * of the activity, but Mastodon uses the `actor` attribute as source
149 * to move profiles.
150 *
151 * @param array $activity The JSON "Move" Activity.
152 *
153 * @return string|null The origin URI or null if not found.
154 */
155 private static function extract_origin( $activity ) {
156 if ( ! empty( $activity['origin'] ) ) {
157 return object_to_uri( $activity['origin'] );
158 }
159
160 if ( ! empty( $activity['actor'] ) ) {
161 return object_to_uri( $activity['actor'] );
162 }
163
164 return null;
165 }
166
167 /**
168 * Verify the move.
169 *
170 * @param array $target_object The target object.
171 * @param array $origin_object The origin object.
172 *
173 * @return bool True if the move is verified, false otherwise.
174 */
175 private static function verify_move( $target_object, $origin_object ) {
176 // Check if both objects are valid.
177 if ( \is_wp_error( $target_object ) || \is_wp_error( $origin_object ) ) {
178 return false;
179 }
180
181 // Check if both objects are persons.
182 if ( 'Person' !== $target_object['type'] || 'Person' !== $origin_object['type'] ) {
183 return false;
184 }
185
186 // Check if the target and origin are not the same.
187 if ( $target_object['id'] === $origin_object['id'] ) {
188 return false;
189 }
190
191 // Check if the target has an alsoKnownAs property.
192 if ( empty( $target_object['also_known_as'] ) ) {
193 return false;
194 }
195
196 // Check if the origin is in the alsoKnownAs property of the target.
197 if ( ! in_array( $origin_object['id'], $target_object['also_known_as'], true ) ) {
198 return false;
199 }
200
201 // Check if the origin has a movedTo property.
202 if ( empty( $origin_object['movedTo'] ) ) {
203 return false;
204 }
205
206 // Check if the movedTo property of the origin is the target.
207 if ( $origin_object['movedTo'] !== $target_object['id'] ) {
208 return false;
209 }
210
211 return true;
212 }
213 }
214