PluginProbe
ActivityPub / 5.3.1
ActivityPub v5.3.1
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 / collection / class-outbox.php

class-outbox.php in ActivityPub 5.3.1, at includes/collection/class-outbox.php

287 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Outbox collection file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Collection;
9
10 use Activitypub\Dispatcher;
11 use Activitypub\Scheduler;
12 use Activitypub\Activity\Activity;
13
14 use function Activitypub\is_activity;
15 use function Activitypub\add_to_outbox;
16
17 /**
18 * ActivityPub Outbox Collection
19 *
20 * @link https://www.w3.org/TR/activitypub/#outbox
21 */
22 class Outbox {
23 const POST_TYPE = 'ap_outbox';
24
25 /**
26 * Add an Item to the outbox.
27 *
28 * @param \Activitypub\Activity\Base_Object $activity_object The object of the activity that will be added to the outbox.
29 * @param string $activity_type The activity type.
30 * @param int $user_id The real or imaginary user ID of the actor that published the activity that will be added to the outbox.
31 * @param string $content_visibility Optional. The visibility of the content. Default: `ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC`. See `constants.php` for possible values: `ACTIVITYPUB_CONTENT_VISIBILITY_*`.
32 *
33 * @return false|int|\WP_Error The added item or an error.
34 */
35 public static function add( $activity_object, $activity_type, $user_id, $content_visibility = ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC ) { // phpcs:ignore
36 switch ( $user_id ) {
37 case Actors::APPLICATION_USER_ID:
38 $actor_type = 'application';
39 break;
40 case Actors::BLOG_USER_ID:
41 $actor_type = 'blog';
42 break;
43 default:
44 $actor_type = 'user';
45 break;
46 }
47
48 $title = $activity_object->get_name() ?? $activity_object->get_content();
49 $activitypub_object_id = $activity_object->get_id();
50
51 if ( ! $title && is_activity( $activity_object ) && $activity_object->get_object() instanceof \Activitypub\Activity\Base_Object ) {
52 $title = $activity_object->get_object()->get_name() ?? $activity_object->get_object()->get_content();
53 $activitypub_object_id = $activity_object->get_object()->get_id();
54 }
55
56 $outbox_item = array(
57 'post_type' => self::POST_TYPE,
58 'post_title' => sprintf(
59 /* translators: 1. Activity type, 2. Object type, 3. Object Title or Excerpt */
60 __( '[%1$s] %2$s: %3$s', 'activitypub' ),
61 $activity_type,
62 $activity_object->get_type(),
63 \wp_trim_words( $title, 5 )
64 ),
65 'post_content' => wp_slash( $activity_object->to_json() ),
66 // ensure that user ID is not below 0.
67 'post_author' => \max( $user_id, 0 ),
68 'post_status' => 'pending',
69 'meta_input' => array(
70 '_activitypub_object_id' => $activitypub_object_id,
71 '_activitypub_activity_type' => $activity_type,
72 '_activitypub_activity_actor' => $actor_type,
73 'activitypub_content_visibility' => $content_visibility,
74 ),
75 );
76
77 $has_kses = false !== \has_filter( 'content_save_pre', 'wp_filter_post_kses' );
78 if ( $has_kses ) {
79 // Prevent KSES from corrupting JSON in post_content.
80 \kses_remove_filters();
81 }
82
83 $id = \wp_insert_post( $outbox_item, true );
84
85 if ( $has_kses ) {
86 \kses_init_filters();
87 }
88
89 if ( \is_wp_error( $id ) ) {
90 return $id;
91 }
92
93 if ( ! $id ) {
94 return false;
95 }
96
97 self::invalidate_existing_items( $activitypub_object_id, $activity_type, $id );
98
99 return $id;
100 }
101
102 /**
103 * Invalidate existing outbox items with the same activity type and object ID
104 * by setting their status to 'publish'.
105 *
106 * @param string $object_id The ID of the activity object.
107 * @param string $activity_type The type of the activity.
108 * @param int $current_id The ID of the current outbox item to exclude.
109 *
110 * @return void
111 */
112 private static function invalidate_existing_items( $object_id, $activity_type, $current_id ) {
113 $meta_query = array(
114 array(
115 'key' => '_activitypub_object_id',
116 'value' => $object_id,
117 ),
118 );
119
120 // For non-Delete activities, only invalidate items of the same type.
121 if ( 'Delete' !== $activity_type ) {
122 $meta_query[] = array(
123 'key' => '_activitypub_activity_type',
124 'value' => $activity_type,
125 );
126 }
127
128 $existing_items = get_posts(
129 array(
130 'post_type' => self::POST_TYPE,
131 'post_status' => 'pending',
132 'exclude' => array( $current_id ),
133 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
134 'meta_query' => $meta_query,
135 'fields' => 'ids',
136 )
137 );
138
139 foreach ( $existing_items as $existing_item_id ) {
140 $event_args = array(
141 Dispatcher::$callback,
142 $existing_item_id,
143 Dispatcher::$batch_size,
144 \get_post_meta( $existing_item_id, '_activitypub_outbox_offset', true ) ?: 0, // phpcs:ignore
145 );
146
147 $timestamp = \wp_next_scheduled( 'activitypub_async_batch', $event_args );
148 \wp_unschedule_event( $timestamp, 'activitypub_async_batch', $event_args );
149
150 $timestamp = \wp_next_scheduled( 'activitypub_process_outbox', array( $existing_item_id ) );
151 \wp_unschedule_event( $timestamp, 'activitypub_process_outbox', array( $existing_item_id ) );
152
153 \wp_publish_post( $existing_item_id );
154 \delete_post_meta( $existing_item_id, '_activitypub_outbox_offset' );
155 }
156 }
157
158 /**
159 * Creates an Undo activity.
160 *
161 * @param int|\WP_Post $outbox_item The Outbox post or post ID.
162 *
163 * @return int|bool The ID of the outbox item or false on failure.
164 */
165 public static function undo( $outbox_item ) {
166 $outbox_item = get_post( $outbox_item );
167 $activity = self::get_activity( $outbox_item );
168
169 $type = 'Undo';
170 if ( 'Create' === $activity->get_type() ) {
171 $type = 'Delete';
172 } elseif ( 'Add' === $activity->get_type() ) {
173 $type = 'Remove';
174 }
175
176 return add_to_outbox( $activity, $type, $outbox_item->post_author );
177 }
178
179 /**
180 * Reschedule an activity.
181 *
182 * @param int|\WP_Post $outbox_item The Outbox post or post ID.
183 *
184 * @return bool True if the activity was rescheduled, false otherwise.
185 */
186 public static function reschedule( $outbox_item ) {
187 $outbox_item = get_post( $outbox_item );
188
189 $outbox_item->post_status = 'pending';
190 $outbox_item->post_date = current_time( 'mysql' );
191
192 wp_update_post( $outbox_item );
193
194 Scheduler::schedule_outbox_activity_for_federation( $outbox_item->ID );
195
196 return true;
197 }
198
199 /**
200 * Get the Activity object from the Outbox item.
201 *
202 * @param int|\WP_Post $outbox_item The Outbox post or post ID.
203 * @return Activity|\WP_Error The Activity object or WP_Error.
204 */
205 public static function get_activity( $outbox_item ) {
206 $outbox_item = get_post( $outbox_item );
207 $actor = self::get_actor( $outbox_item );
208 if ( is_wp_error( $actor ) ) {
209 return $actor;
210 }
211
212 $type = \get_post_meta( $outbox_item->ID, '_activitypub_activity_type', true );
213 $activity = new Activity();
214 $activity->set_type( $type );
215 $activity->set_id( $outbox_item->guid );
216 // Pre-fill the Activity with data (for example cc and to).
217 $activity->set_object( \json_decode( $outbox_item->post_content, true ) );
218 $activity->set_actor( $actor->get_id() );
219
220 // Use simple Object (only ID-URI) for Like and Announce.
221 if ( in_array( $type, array( 'Like', 'Delete' ), true ) ) {
222 $activity->set_object( $activity->get_object()->get_id() );
223 }
224
225 return $activity;
226 }
227
228 /**
229 * Get the Actor object from the Outbox item.
230 *
231 * @param \WP_Post $outbox_item The Outbox post.
232 *
233 * @return \Activitypub\Model\User|\Activitypub\Model\Blog|\WP_Error The Actor object or WP_Error.
234 */
235 public static function get_actor( $outbox_item ) {
236 $actor_type = \get_post_meta( $outbox_item->ID, '_activitypub_activity_actor', true );
237
238 switch ( $actor_type ) {
239 case 'blog':
240 $actor_id = Actors::BLOG_USER_ID;
241 break;
242 case 'application':
243 $actor_id = Actors::APPLICATION_USER_ID;
244 break;
245 case 'user':
246 default:
247 $actor_id = $outbox_item->post_author;
248 break;
249 }
250
251 return Actors::get_by_id( $actor_id );
252 }
253
254 /**
255 * Get the Activity object from the Outbox item.
256 *
257 * @param \WP_Post $outbox_item The Outbox post.
258 *
259 * @return \Activitypub\Activity\Activity|\WP_Error The Activity object or WP_Error.
260 */
261 public static function maybe_get_activity( $outbox_item ) {
262 if ( ! $outbox_item || ! $outbox_item instanceof \WP_Post ) {
263 return new \WP_Error( 'invalid_outbox_item', 'Invalid Outbox item.' );
264 }
265
266 if ( 'ap_outbox' !== $outbox_item->post_type ) {
267 return new \WP_Error( 'invalid_outbox_item', 'Invalid Outbox item.' );
268 }
269
270 // Check if Outbox Activity is public.
271 $visibility = \get_post_meta( $outbox_item->ID, 'activitypub_content_visibility', true );
272
273 if ( ! in_array( $visibility, array( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC ), true ) ) {
274 return new \WP_Error( 'private_outbox_item', 'Not a public Outbox item.' );
275 }
276
277 $activity_types = \apply_filters( 'rest_activitypub_outbox_activity_types', array( 'Announce', 'Create', 'Like', 'Update' ) );
278 $activity_type = \get_post_meta( $outbox_item->ID, '_activitypub_activity_type', true );
279
280 if ( ! in_array( $activity_type, $activity_types, true ) ) {
281 return new \WP_Error( 'private_outbox_item', 'Not public Outbox item type.' );
282 }
283
284 return self::get_activity( $outbox_item );
285 }
286 }
287