PluginProbe
ActivityPub / 1.1.0
ActivityPub v1.1.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 / rest / class-inbox.php

class-inbox.php in ActivityPub 1.1.0, at includes/rest/class-inbox.php

487 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Activitypub\Rest;
3
4 use WP_Error;
5 use WP_REST_Server;
6 use WP_REST_Response;
7 use Activitypub\Activity\Activity;
8 use Activitypub\Collection\Users as User_Collection;
9
10 use function Activitypub\get_context;
11 use function Activitypub\url_to_authorid;
12 use function Activitypub\get_rest_url_by_path;
13 use function Activitypub\get_remote_metadata_by_actor;
14
15 /**
16 * ActivityPub Inbox REST-Class
17 *
18 * @author Matthias Pfefferle
19 *
20 * @see https://www.w3.org/TR/activitypub/#inbox
21 */
22 class Inbox {
23 /**
24 * Initialize the class, registering WordPress hooks
25 */
26 public static function init() {
27 self::register_routes();
28
29 \add_action( 'activitypub_inbox_create', array( self::class, 'handle_create' ), 10, 2 );
30 }
31
32 /**
33 * Register routes
34 */
35 public static function register_routes() {
36 \register_rest_route(
37 ACTIVITYPUB_REST_NAMESPACE,
38 '/inbox',
39 array(
40 array(
41 'methods' => WP_REST_Server::CREATABLE,
42 'callback' => array( self::class, 'shared_inbox_post' ),
43 'args' => self::shared_inbox_post_parameters(),
44 'permission_callback' => '__return_true',
45 ),
46 )
47 );
48
49 \register_rest_route(
50 ACTIVITYPUB_REST_NAMESPACE,
51 '/users/(?P<user_id>[\w\-\.]+)/inbox',
52 array(
53 array(
54 'methods' => WP_REST_Server::CREATABLE,
55 'callback' => array( self::class, 'user_inbox_post' ),
56 'args' => self::user_inbox_post_parameters(),
57 'permission_callback' => '__return_true',
58 ),
59 array(
60 'methods' => WP_REST_Server::READABLE,
61 'callback' => array( self::class, 'user_inbox_get' ),
62 'args' => self::user_inbox_get_parameters(),
63 'permission_callback' => '__return_true',
64 ),
65 )
66 );
67 }
68
69 /**
70 * Renders the user-inbox
71 *
72 * @param WP_REST_Request $request
73 * @return WP_REST_Response
74 */
75 public static function user_inbox_get( $request ) {
76 $user_id = $request->get_param( 'user_id' );
77 $user = User_Collection::get_by_various( $user_id );
78
79 if ( is_wp_error( $user ) ) {
80 return $user;
81 }
82
83 $page = $request->get_param( 'page', 0 );
84
85 /*
86 * Action triggerd prior to the ActivityPub profile being created and sent to the client
87 */
88 \do_action( 'activitypub_rest_inbox_pre' );
89
90 $json = new \stdClass();
91
92 $json->{'@context'} = get_context();
93 $json->id = get_rest_url_by_path( sprintf( 'users/%d/inbox', $user->get__id() ) );
94 $json->generator = 'http://wordpress.org/?v=' . \get_bloginfo_rss( 'version' );
95 $json->type = 'OrderedCollectionPage';
96 $json->partOf = get_rest_url_by_path( sprintf( 'users/%d/inbox', $user->get__id() ) ); // phpcs:ignore
97
98 $json->totalItems = 0; // phpcs:ignore
99
100 $json->orderedItems = array(); // phpcs:ignore
101
102 $json->first = $json->partOf; // phpcs:ignore
103
104 // filter output
105 $json = \apply_filters( 'activitypub_rest_inbox_array', $json );
106
107 /*
108 * Action triggerd after the ActivityPub profile has been created and sent to the client
109 */
110 \do_action( 'activitypub_inbox_post' );
111
112 $rest_response = new WP_REST_Response( $json, 200 );
113 $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
114
115 return $rest_response;
116 }
117
118 /**
119 * Handles user-inbox requests
120 *
121 * @param WP_REST_Request $request
122 *
123 * @return WP_REST_Response
124 */
125 public static function user_inbox_post( $request ) {
126 $user_id = $request->get_param( 'user_id' );
127 $user = User_Collection::get_by_various( $user_id );
128
129 if ( is_wp_error( $user ) ) {
130 return $user;
131 }
132
133 $data = $request->get_json_params();
134 $type = $request->get_param( 'type' );
135 $type = \strtolower( $type );
136
137 \do_action( 'activitypub_inbox', $data, $user->get__id(), $type );
138 \do_action( "activitypub_inbox_{$type}", $data, $user->get__id() );
139
140 $rest_response = new WP_REST_Response( array(), 202 );
141 $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
142
143 return $rest_response;
144 }
145
146 /**
147 * The shared inbox
148 *
149 * @param WP_REST_Request $request
150 *
151 * @return WP_REST_Response
152 */
153 public static function shared_inbox_post( $request ) {
154 $data = $request->get_json_params();
155 $type = $request->get_param( 'type' );
156 $users = self::extract_recipients( $data );
157
158 if ( ! $users ) {
159 return new WP_Error(
160 'rest_invalid_param',
161 \__( 'No recipients found', 'activitypub' ),
162 array(
163 'status' => 400,
164 'params' => array(
165 'to' => \__( 'Please check/validate "to" field', 'activitypub' ),
166 'bto' => \__( 'Please check/validate "bto" field', 'activitypub' ),
167 'cc' => \__( 'Please check/validate "cc" field', 'activitypub' ),
168 'bcc' => \__( 'Please check/validate "bcc" field', 'activitypub' ),
169 'audience' => \__( 'Please check/validate "audience" field', 'activitypub' ),
170 ),
171 )
172 );
173 }
174
175 foreach ( $users as $user ) {
176 $user = User_Collection::get_by_various( $user );
177
178 if ( is_wp_error( $user ) ) {
179 continue;
180 }
181
182 $type = \strtolower( $type );
183
184 \do_action( 'activitypub_inbox', $data, $user->ID, $type );
185 \do_action( "activitypub_inbox_{$type}", $data, $user->ID );
186 }
187
188 $rest_response = new WP_REST_Response( array(), 202 );
189 $rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
190
191 return $rest_response;
192 }
193
194 /**
195 * The supported parameters
196 *
197 * @return array list of parameters
198 */
199 public static function user_inbox_get_parameters() {
200 $params = array();
201
202 $params['page'] = array(
203 'type' => 'integer',
204 );
205
206 $params['user_id'] = array(
207 'required' => true,
208 'type' => 'string',
209 );
210
211 return $params;
212 }
213
214 /**
215 * The supported parameters
216 *
217 * @return array list of parameters
218 */
219 public static function user_inbox_post_parameters() {
220 $params = array();
221
222 $params['page'] = array(
223 'type' => 'integer',
224 );
225
226 $params['user_id'] = array(
227 'required' => true,
228 'type' => 'string',
229 );
230
231 $params['id'] = array(
232 'required' => true,
233 'sanitize_callback' => 'esc_url_raw',
234 );
235
236 $params['actor'] = array(
237 'required' => true,
238 'sanitize_callback' => function( $param, $request, $key ) {
239 if ( ! \is_string( $param ) ) {
240 $param = $param['id'];
241 }
242 return \esc_url_raw( $param );
243 },
244 );
245
246 $params['type'] = array(
247 'required' => true,
248 //'type' => 'enum',
249 //'enum' => array( 'Create' ),
250 //'sanitize_callback' => function( $param, $request, $key ) {
251 // return \strtolower( $param );
252 //},
253 );
254
255 $params['object'] = array(
256 'required' => true,
257 );
258
259 return $params;
260 }
261
262 /**
263 * The supported parameters
264 *
265 * @return array list of parameters
266 */
267 public static function shared_inbox_post_parameters() {
268 $params = array();
269
270 $params['page'] = array(
271 'type' => 'integer',
272 );
273
274 $params['id'] = array(
275 'required' => true,
276 'type' => 'string',
277 'sanitize_callback' => 'esc_url_raw',
278 );
279
280 $params['actor'] = array(
281 'required' => true,
282 //'type' => array( 'object', 'string' ),
283 'sanitize_callback' => function( $param, $request, $key ) {
284 if ( ! \is_string( $param ) ) {
285 $param = $param['id'];
286 }
287 return \esc_url_raw( $param );
288 },
289 );
290
291 $params['type'] = array(
292 'required' => true,
293 //'type' => 'enum',
294 //'enum' => array( 'Create' ),
295 //'sanitize_callback' => function( $param, $request, $key ) {
296 // return \strtolower( $param );
297 //},
298 );
299
300 $params['object'] = array(
301 'required' => true,
302 //'type' => 'object',
303 );
304
305 $params['to'] = array(
306 'required' => false,
307 'sanitize_callback' => function( $param, $request, $key ) {
308 if ( \is_string( $param ) ) {
309 $param = array( $param );
310 }
311
312 return $param;
313 },
314 );
315
316 $params['cc'] = array(
317 'sanitize_callback' => function( $param, $request, $key ) {
318 if ( \is_string( $param ) ) {
319 $param = array( $param );
320 }
321
322 return $param;
323 },
324 );
325
326 $params['bcc'] = array(
327 'sanitize_callback' => function( $param, $request, $key ) {
328 if ( \is_string( $param ) ) {
329 $param = array( $param );
330 }
331
332 return $param;
333 },
334 );
335
336 return $params;
337 }
338
339 /**
340 * Handles "Create" requests
341 *
342 * @param array $object The activity-object
343 * @param int $user_id The id of the local blog-user
344 */
345 public static function handle_create( $object, $user_id ) {
346 $meta = get_remote_metadata_by_actor( $object['actor'] );
347
348 if ( ! isset( $object['object']['inReplyTo'] ) ) {
349 return;
350 }
351
352 // check if Activity is public or not
353 if ( ! self::is_activity_public( $object ) ) {
354 // @todo maybe send email
355 return;
356 }
357
358 $comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
359
360 // save only replys and reactions
361 if ( ! $comment_post_id ) {
362 return false;
363 }
364
365 $commentdata = array(
366 'comment_post_ID' => $comment_post_id,
367 'comment_author' => \esc_attr( $meta['name'] ),
368 'comment_author_url' => \esc_url_raw( $object['actor'] ),
369 'comment_content' => \wp_filter_kses( $object['object']['content'] ),
370 'comment_type' => 'comment',
371 'comment_author_email' => '',
372 'comment_parent' => 0,
373 'comment_meta' => array(
374 'source_url' => \esc_url_raw( $object['object']['url'] ),
375 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
376 'protocol' => 'activitypub',
377 ),
378 );
379
380 // disable flood control
381 \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
382
383 // do not require email for AP entries
384 \add_filter( 'pre_option_require_name_email', '__return_false' );
385
386 // No nonce possible for this submission route
387 \add_filter(
388 'akismet_comment_nonce',
389 function() {
390 return 'inactive';
391 }
392 );
393
394 $state = \wp_new_comment( $commentdata, true );
395
396 \remove_filter( 'pre_option_require_name_email', '__return_false' );
397
398 // re-add flood control
399 \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
400
401 do_action( 'activitypub_handled_create', $object, $user_id, $state, $commentdata );
402 }
403
404 /**
405 * Extract recipient URLs from Activity object
406 *
407 * @param array $data
408 *
409 * @return array The list of user URLs
410 */
411 public static function extract_recipients( $data ) {
412 $recipient_items = array();
413
414 foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
415 if ( array_key_exists( $i, $data ) ) {
416 if ( is_array( $data[ $i ] ) ) {
417 $recipient = $data[ $i ];
418 } else {
419 $recipient = array( $data[ $i ] );
420 }
421 $recipient_items = array_merge( $recipient_items, $recipient );
422 }
423
424 if ( is_array( $data['object'] ) && array_key_exists( $i, $data['object'] ) ) {
425 if ( is_array( $data['object'][ $i ] ) ) {
426 $recipient = $data['object'][ $i ];
427 } else {
428 $recipient = array( $data['object'][ $i ] );
429 }
430 $recipient_items = array_merge( $recipient_items, $recipient );
431 }
432 }
433
434 $recipients = array();
435
436 // flatten array
437 foreach ( $recipient_items as $recipient ) {
438 if ( is_array( $recipient ) ) {
439 // check if recipient is an object
440 if ( array_key_exists( 'id', $recipient ) ) {
441 $recipients[] = $recipient['id'];
442 }
443 } else {
444 $recipients[] = $recipient;
445 }
446 }
447
448 return array_unique( $recipients );
449 }
450
451 /**
452 * Get local user recipients
453 *
454 * @param array $data
455 *
456 * @return array The list of local users
457 */
458 public static function get_recipients( $data ) {
459 $recipients = self::extract_recipients( $data );
460 $users = array();
461
462 foreach ( $recipients as $recipient ) {
463 $user_id = url_to_authorid( $recipient );
464
465 $user = get_user_by( 'id', $user_id );
466
467 if ( $user ) {
468 $users[] = $user;
469 }
470 }
471
472 return $users;
473 }
474
475 /**
476 * Check if passed Activity is Public
477 *
478 * @param array $data
479 * @return boolean
480 */
481 public static function is_activity_public( $data ) {
482 $recipients = self::extract_recipients( $data );
483
484 return in_array( 'https://www.w3.org/ns/activitystreams#Public', $recipients, true );
485 }
486 }
487