PluginProbe
ActivityPub / 9.2.0
ActivityPub v9.2.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 / functions-federation.php

functions-federation.php in ActivityPub 9.2.0, at includes/functions-federation.php

276 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 * Federation functions.
4 *
5 * Functions for managing federation state, outbox, and follow/unfollow operations.
6 *
7 * @package Activitypub
8 */
9
10 namespace Activitypub;
11
12 use Activitypub\Activity\Activity;
13 use Activitypub\Collection\Actors;
14 use Activitypub\Collection\Following;
15 use Activitypub\Collection\Outbox;
16 use Activitypub\Collection\Remote_Actors;
17 use Activitypub\Transformer\Factory as Transformer_Factory;
18
19 /**
20 * Set the federation state of a WordPress object.
21 *
22 * @param \WP_Comment|\WP_Post $wp_object The WordPress object.
23 * @param string $state The state of the object.
24 */
25 function set_wp_object_state( $wp_object, $state ) {
26 if ( $wp_object instanceof \WP_Post ) {
27 $meta_type = 'post';
28 $object_id = $wp_object->ID;
29 } elseif ( $wp_object instanceof \WP_Comment ) {
30 $meta_type = 'comment';
31 $object_id = $wp_object->comment_ID;
32 } else {
33 /**
34 * Allow plugins to mark WordPress objects as federated.
35 *
36 * @param \WP_Comment|\WP_Post $wp_object The WordPress object.
37 */
38 \do_action( 'activitypub_mark_wp_object_as_federated', $wp_object );
39 return;
40 }
41
42 \update_metadata( $meta_type, $object_id, 'activitypub_status', $state );
43
44 if ( ACTIVITYPUB_OBJECT_STATE_DELETED === $state ) {
45 \update_metadata( $meta_type, $object_id, 'activitypub_deleted_at', \time() );
46 } else {
47 \delete_metadata( $meta_type, $object_id, 'activitypub_deleted_at' );
48 }
49 }
50
51 /**
52 * Get the federation state of a WordPress object.
53 *
54 * @param \WP_Comment|\WP_Post $wp_object The WordPress object.
55 *
56 * @return string|false The state of the object or false if not found.
57 */
58 function get_wp_object_state( $wp_object ) {
59 if ( $wp_object instanceof \WP_Post ) {
60 $meta_type = 'post';
61 $object_id = $wp_object->ID;
62 } elseif ( $wp_object instanceof \WP_Comment ) {
63 $meta_type = 'comment';
64 $object_id = $wp_object->comment_ID;
65 } else {
66 /**
67 * Allow plugins to get the federation state of a WordPress object.
68 *
69 * @param false $state The state of the object.
70 * @param \WP_Comment|\WP_Post $wp_object The WordPress object.
71 */
72 return \apply_filters( 'activitypub_get_wp_object_state', false, $wp_object );
73 }
74
75 return \get_metadata( $meta_type, $object_id, 'activitypub_status', true );
76 }
77
78 /**
79 * Check if an ID is from the same domain as the site.
80 *
81 * @param string $id The ID URI to check.
82 *
83 * @return boolean True if the ID is a self-ping, false otherwise.
84 */
85 function is_self_ping( $id ) {
86 $query_string = \wp_parse_url( $id, PHP_URL_QUERY );
87
88 if ( ! $query_string ) {
89 return false;
90 }
91
92 $query = array();
93 \parse_str( $query_string, $query );
94
95 if (
96 is_same_domain( $id ) &&
97 \in_array( 'c', \array_keys( $query ), true )
98 ) {
99 return true;
100 }
101
102 return false;
103 }
104
105 /**
106 * Add an object to the outbox.
107 *
108 * @param mixed $data The object to add to the outbox.
109 * @param string|null $activity_type Optional. The type of the Activity or null if `$data` is an Activity. Default null.
110 * @param integer $user_id Optional. The User-ID. Default 0.
111 * @param string $content_visibility Optional. The visibility of the content. See `constants.php` for possible values: `ACTIVITYPUB_CONTENT_VISIBILITY_*`. Default null.
112 *
113 * @return boolean|int The ID of the outbox item or false on failure.
114 */
115 function add_to_outbox( $data, $activity_type = null, $user_id = 0, $content_visibility = null ) {
116 // If the user is disabled, fall back to the blog user when available.
117 if ( ! user_can_activitypub( $user_id ) ) {
118 if ( user_can_activitypub( Actors::BLOG_USER_ID ) ) {
119 $user_id = Actors::BLOG_USER_ID;
120 } else {
121 return false;
122 }
123 }
124
125 /*
126 * Refuse to Update an object that is already deleted from the Fediverse. An
127 * explicit Update (e.g. `wp activitypub post update` or a third-party caller)
128 * on a soft-deleted post would only federate a Tombstone, and — worse — would
129 * reset the object's state to "federated" below, so a later re-publish would
130 * skip the Create that cancels the still-pending Delete and the post could be
131 * torn down after it is public again. Re-publishing is the supported way to
132 * bring a deleted object back, and that emits a Create, not an Update.
133 */
134 if ( 'Update' === $activity_type && ACTIVITYPUB_OBJECT_STATE_DELETED === get_wp_object_state( $data ) ) {
135 return new \WP_Error(
136 'activitypub_object_deleted',
137 \__( 'Cannot send an Update for an object that has been deleted from the Fediverse. Re-publish it instead.', 'activitypub' ),
138 array( 'status' => 409 )
139 );
140 }
141
142 $transformer = Transformer_Factory::get_transformer( $data );
143
144 if ( ! $transformer || \is_wp_error( $transformer ) ) {
145 return false;
146 }
147
148 if ( $content_visibility ) {
149 $transformer->set_content_visibility( $content_visibility );
150 } else {
151 $content_visibility = $transformer->get_content_visibility();
152 }
153
154 if ( $activity_type ) {
155 $activity = $transformer->to_activity( $activity_type );
156 $activity->set_actor( Actors::get_by_id( $user_id )->get_id() );
157 } else {
158 $activity = $transformer->to_object();
159 }
160
161 if ( ! $activity || \is_wp_error( $activity ) ) {
162 /**
163 * Action triggered when adding an object to the outbox fails.
164 *
165 * @param \WP_Error $activity The error object or false.
166 * @param mixed $data The object that failed to be added to the outbox.
167 * @param string|null $activity_type The type of the Activity or null if `$data` is an Activity.
168 * @param int $user_id The User ID.
169 * @param string $content_visibility The visibility of the content. See `constants.php` for possible values: `ACTIVITYPUB_CONTENT_VISIBILITY_*`.
170 */
171 \do_action( 'activitypub_add_to_outbox_failed', $activity, $data, $activity_type, $user_id, $content_visibility );
172
173 return false;
174 }
175
176 $outbox_activity_id = Outbox::add( $activity, $user_id, $content_visibility );
177
178 if ( ! $outbox_activity_id || \is_wp_error( $outbox_activity_id ) ) {
179 /**
180 * Action triggered when adding an object to the outbox fails.
181 *
182 * @param false|\WP_Error $outbox_activity_id The error object or false.
183 * @param mixed $data The object that failed to be added to the outbox.
184 * @param string|null $activity_type The type of the Activity or null if `$data` is an Activity.
185 * @param int $user_id The User ID.
186 * @param string $content_visibility The visibility of the content. See `constants.php` for possible values: `ACTIVITYPUB_CONTENT_VISIBILITY_*`.
187 */
188 \do_action( 'activitypub_add_to_outbox_failed', $outbox_activity_id, $data, $activity_type, $user_id, $content_visibility );
189
190 return false;
191 }
192
193 /**
194 * Action triggered after an object has been added to the outbox.
195 *
196 * @param int $outbox_activity_id The ID of the outbox item.
197 * @param Activity $activity The activity object.
198 * @param int $user_id The User-ID.
199 * @param string $content_visibility The visibility of the content. See `constants.php` for possible values: `ACTIVITYPUB_CONTENT_VISIBILITY_*`.
200 */
201 \do_action( 'post_activitypub_add_to_outbox', $outbox_activity_id, $activity, $user_id, $content_visibility );
202
203 // Update state based on activity.
204 $state_map = array(
205 'Create' => ACTIVITYPUB_OBJECT_STATE_FEDERATED,
206 'Update' => ACTIVITYPUB_OBJECT_STATE_FEDERATED,
207 'Delete' => ACTIVITYPUB_OBJECT_STATE_DELETED,
208 );
209
210 if ( $activity_type && isset( $state_map[ $activity_type ] ) ) {
211 set_wp_object_state( $data, $state_map[ $activity_type ] );
212 }
213
214 return $outbox_activity_id;
215 }
216
217 /**
218 * Follow a user.
219 *
220 * @param string|int $remote_actor The Actor URL, WebFinger Resource or Post-ID of the remote Actor.
221 * @param int $user_id The ID of the WordPress User.
222 *
223 * @return int|\WP_Error The Outbox ID on success or a WP_Error on failure.
224 */
225 function follow( $remote_actor, $user_id ) {
226 if ( \is_numeric( $remote_actor ) ) {
227 return Following::follow( $remote_actor, $user_id );
228 }
229
230 if ( ! \filter_var( $remote_actor, FILTER_VALIDATE_URL ) ) {
231 $remote_actor = Webfinger::resolve( $remote_actor );
232 }
233
234 if ( \is_wp_error( $remote_actor ) ) {
235 return $remote_actor;
236 }
237
238 $remote_actor_post = Remote_Actors::fetch_by_uri( $remote_actor );
239
240 if ( \is_wp_error( $remote_actor_post ) ) {
241 return $remote_actor_post;
242 }
243
244 return Following::follow( $remote_actor_post, $user_id );
245 }
246
247 /**
248 * Unfollow a user.
249 *
250 * @param string|int $remote_actor The Actor URL, WebFinger Resource or Post-ID of the remote Actor.
251 * @param int $user_id The ID of the WordPress User.
252 *
253 * @return int|\WP_Error The ID of the Undo outbox item, 0 if no matching Follow outbox was found, or WP_Error on failure.
254 */
255 function unfollow( $remote_actor, $user_id ) {
256 if ( \is_numeric( $remote_actor ) ) {
257 return Following::unfollow( $remote_actor, $user_id );
258 }
259
260 if ( ! \filter_var( $remote_actor, FILTER_VALIDATE_URL ) ) {
261 $remote_actor = Webfinger::resolve( $remote_actor );
262 }
263
264 if ( \is_wp_error( $remote_actor ) ) {
265 return $remote_actor;
266 }
267
268 $remote_actor_post = Remote_Actors::fetch_by_uri( $remote_actor );
269
270 if ( \is_wp_error( $remote_actor_post ) ) {
271 return $remote_actor_post;
272 }
273
274 return Following::unfollow( $remote_actor_post, $user_id );
275 }
276