PluginProbe
ActivityPub / 1.0.0
ActivityPub v1.0.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.0.0, at includes/rest/class-inbox.php

482 lines 11.3 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 \add_action( 'rest_api_init', array( self::class, '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::EDITABLE,
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::EDITABLE,
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 $response = new WP_REST_Response( $json, 200 );
113
114 $response->header( 'Content-Type', 'application/activity+json' );
115
116 return $response;
117 }
118
119 /**
120 * Handles user-inbox requests
121 *
122 * @param WP_REST_Request $request
123 *
124 * @return WP_REST_Response
125 */
126 public static function user_inbox_post( $request ) {
127 $user_id = $request->get_param( 'user_id' );
128 $user = User_Collection::get_by_various( $user_id );
129
130 if ( is_wp_error( $user ) ) {
131 return $user;
132 }
133
134 $data = $request->get_json_params();
135 $type = $request->get_param( 'type' );
136 $type = \strtolower( $type );
137
138 \do_action( 'activitypub_inbox', $data, $user->get__id(), $type );
139 \do_action( "activitypub_inbox_{$type}", $data, $user->get__id() );
140
141 return new WP_REST_Response( array(), 202 );
142 }
143
144 /**
145 * The shared inbox
146 *
147 * @param WP_REST_Request $request
148 *
149 * @return WP_REST_Response
150 */
151 public static function shared_inbox_post( $request ) {
152 $data = $request->get_json_params();
153 $type = $request->get_param( 'type' );
154 $users = self::extract_recipients( $data );
155
156 if ( ! $users ) {
157 return new WP_Error(
158 'rest_invalid_param',
159 \__( 'No recipients found', 'activitypub' ),
160 array(
161 'status' => 404,
162 'params' => array(
163 'to' => \__( 'Please check/validate "to" field', 'activitypub' ),
164 'bto' => \__( 'Please check/validate "bto" field', 'activitypub' ),
165 'cc' => \__( 'Please check/validate "cc" field', 'activitypub' ),
166 'bcc' => \__( 'Please check/validate "bcc" field', 'activitypub' ),
167 'audience' => \__( 'Please check/validate "audience" field', 'activitypub' ),
168 ),
169 )
170 );
171 }
172
173 foreach ( $users as $user ) {
174 $user = User_Collection::get_by_various( $user );
175
176 if ( is_wp_error( $user ) ) {
177 continue;
178 }
179
180 $type = \strtolower( $type );
181
182 \do_action( 'activitypub_inbox', $data, $user->ID, $type );
183 \do_action( "activitypub_inbox_{$type}", $data, $user->ID );
184 }
185
186 return new WP_REST_Response( array(), 202 );
187 }
188
189 /**
190 * The supported parameters
191 *
192 * @return array list of parameters
193 */
194 public static function user_inbox_get_parameters() {
195 $params = array();
196
197 $params['page'] = array(
198 'type' => 'integer',
199 );
200
201 $params['user_id'] = array(
202 'required' => true,
203 'type' => 'string',
204 );
205
206 return $params;
207 }
208
209 /**
210 * The supported parameters
211 *
212 * @return array list of parameters
213 */
214 public static function user_inbox_post_parameters() {
215 $params = array();
216
217 $params['page'] = array(
218 'type' => 'integer',
219 );
220
221 $params['user_id'] = array(
222 'required' => true,
223 'type' => 'string',
224 );
225
226 $params['id'] = array(
227 'required' => true,
228 'sanitize_callback' => 'esc_url_raw',
229 );
230
231 $params['actor'] = array(
232 'required' => true,
233 'sanitize_callback' => function( $param, $request, $key ) {
234 if ( ! \is_string( $param ) ) {
235 $param = $param['id'];
236 }
237 return \esc_url_raw( $param );
238 },
239 );
240
241 $params['type'] = array(
242 'required' => true,
243 //'type' => 'enum',
244 //'enum' => array( 'Create' ),
245 //'sanitize_callback' => function( $param, $request, $key ) {
246 // return \strtolower( $param );
247 //},
248 );
249
250 $params['object'] = array(
251 'required' => true,
252 );
253
254 return $params;
255 }
256
257 /**
258 * The supported parameters
259 *
260 * @return array list of parameters
261 */
262 public static function shared_inbox_post_parameters() {
263 $params = array();
264
265 $params['page'] = array(
266 'type' => 'integer',
267 );
268
269 $params['id'] = array(
270 'required' => true,
271 'type' => 'string',
272 'sanitize_callback' => 'esc_url_raw',
273 );
274
275 $params['actor'] = array(
276 'required' => true,
277 //'type' => array( 'object', 'string' ),
278 'sanitize_callback' => function( $param, $request, $key ) {
279 if ( ! \is_string( $param ) ) {
280 $param = $param['id'];
281 }
282 return \esc_url_raw( $param );
283 },
284 );
285
286 $params['type'] = array(
287 'required' => true,
288 //'type' => 'enum',
289 //'enum' => array( 'Create' ),
290 //'sanitize_callback' => function( $param, $request, $key ) {
291 // return \strtolower( $param );
292 //},
293 );
294
295 $params['object'] = array(
296 'required' => true,
297 //'type' => 'object',
298 );
299
300 $params['to'] = array(
301 'required' => false,
302 'sanitize_callback' => function( $param, $request, $key ) {
303 if ( \is_string( $param ) ) {
304 $param = array( $param );
305 }
306
307 return $param;
308 },
309 );
310
311 $params['cc'] = array(
312 'sanitize_callback' => function( $param, $request, $key ) {
313 if ( \is_string( $param ) ) {
314 $param = array( $param );
315 }
316
317 return $param;
318 },
319 );
320
321 $params['bcc'] = array(
322 'sanitize_callback' => function( $param, $request, $key ) {
323 if ( \is_string( $param ) ) {
324 $param = array( $param );
325 }
326
327 return $param;
328 },
329 );
330
331 return $params;
332 }
333
334 /**
335 * Handles "Create" requests
336 *
337 * @param array $object The activity-object
338 * @param int $user_id The id of the local blog-user
339 */
340 public static function handle_create( $object, $user_id ) {
341 $meta = get_remote_metadata_by_actor( $object['actor'] );
342
343 if ( ! isset( $object['object']['inReplyTo'] ) ) {
344 return;
345 }
346
347 // check if Activity is public or not
348 if ( ! self::is_activity_public( $object ) ) {
349 // @todo maybe send email
350 return;
351 }
352
353 $comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
354
355 // save only replys and reactions
356 if ( ! $comment_post_id ) {
357 return false;
358 }
359
360 $commentdata = array(
361 'comment_post_ID' => $comment_post_id,
362 'comment_author' => \esc_attr( $meta['name'] ),
363 'comment_author_url' => \esc_url_raw( $object['actor'] ),
364 'comment_content' => \wp_filter_kses( $object['object']['content'] ),
365 'comment_type' => 'comment',
366 'comment_author_email' => '',
367 'comment_parent' => 0,
368 'comment_meta' => array(
369 'source_url' => \esc_url_raw( $object['object']['url'] ),
370 'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
371 'protocol' => 'activitypub',
372 ),
373 );
374
375 // disable flood control
376 \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
377
378 // do not require email for AP entries
379 \add_filter( 'pre_option_require_name_email', '__return_false' );
380
381 // No nonce possible for this submission route
382 \add_filter(
383 'akismet_comment_nonce',
384 function() {
385 return 'inactive';
386 }
387 );
388
389 $state = \wp_new_comment( $commentdata, true );
390
391 \remove_filter( 'pre_option_require_name_email', '__return_false' );
392
393 // re-add flood control
394 \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
395
396 do_action( 'activitypub_handled_create', $object, $user_id, $state, $commentdata );
397 }
398
399 /**
400 * Extract recipient URLs from Activity object
401 *
402 * @param array $data
403 *
404 * @return array The list of user URLs
405 */
406 public static function extract_recipients( $data ) {
407 $recipient_items = array();
408
409 foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
410 if ( array_key_exists( $i, $data ) ) {
411 if ( is_array( $data[ $i ] ) ) {
412 $recipient = $data[ $i ];
413 } else {
414 $recipient = array( $data[ $i ] );
415 }
416 $recipient_items = array_merge( $recipient_items, $recipient );
417 }
418
419 if ( array_key_exists( $i, $data['object'] ) ) {
420 if ( is_array( $data['object'][ $i ] ) ) {
421 $recipient = $data['object'][ $i ];
422 } else {
423 $recipient = array( $data['object'][ $i ] );
424 }
425 $recipient_items = array_merge( $recipient_items, $recipient );
426 }
427 }
428
429 $recipients = array();
430
431 // flatten array
432 foreach ( $recipient_items as $recipient ) {
433 if ( is_array( $recipient ) ) {
434 // check if recipient is an object
435 if ( array_key_exists( 'id', $recipient ) ) {
436 $recipients[] = $recipient['id'];
437 }
438 } else {
439 $recipients[] = $recipient;
440 }
441 }
442
443 return array_unique( $recipients );
444 }
445
446 /**
447 * Get local user recipients
448 *
449 * @param array $data
450 *
451 * @return array The list of local users
452 */
453 public static function get_recipients( $data ) {
454 $recipients = self::extract_recipients( $data );
455 $users = array();
456
457 foreach ( $recipients as $recipient ) {
458 $user_id = url_to_authorid( $recipient );
459
460 $user = get_user_by( 'id', $user_id );
461
462 if ( $user ) {
463 $users[] = $user;
464 }
465 }
466
467 return $users;
468 }
469
470 /**
471 * Check if passed Activity is Public
472 *
473 * @param array $data
474 * @return boolean
475 */
476 public static function is_activity_public( $data ) {
477 $recipients = self::extract_recipients( $data );
478
479 return in_array( 'https://www.w3.org/ns/activitystreams#Public', $recipients, true );
480 }
481 }
482