PluginProbe
ActivityPub / 7.8.1
ActivityPub v7.8.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 / class-post-types.php

class-post-types.php in ActivityPub 7.8.1, at includes/class-post-types.php

877 lines 25.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Post Types class for consolidating all custom post type and related meta registrations.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Activity\Activity;
11 use Activitypub\Collection\Extra_Fields;
12 use Activitypub\Collection\Followers;
13 use Activitypub\Collection\Following;
14 use Activitypub\Collection\Inbox;
15 use Activitypub\Collection\Outbox;
16 use Activitypub\Collection\Posts;
17 use Activitypub\Collection\Remote_Actors;
18
19 /**
20 * Post Types class.
21 */
22 class Post_Types {
23 /**
24 * Initialize the class, registering all custom post types and post meta.
25 */
26 public static function init() {
27 \add_action( 'init', array( self::class, 'register_remote_actors_post_type' ), 11 );
28 \add_action( 'init', array( self::class, 'register_inbox_post_type' ), 11 );
29 \add_action( 'init', array( self::class, 'register_outbox_post_type' ), 11 );
30 \add_action( 'init', array( self::class, 'register_post_post_type' ), 11 );
31 \add_action( 'init', array( self::class, 'register_extra_fields_post_types' ), 11 );
32 \add_action( 'init', array( self::class, 'register_activitypub_post_meta' ), 11 );
33
34 \add_action( 'rest_api_init', array( self::class, 'register_ap_actor_rest_field' ) );
35 \add_action( 'rest_api_init', array( self::class, 'register_ap_post_actor_rest_field' ) );
36 \add_action( 'rest_api_init', array( self::class, 'register_ap_post_rest_params' ) );
37
38 \add_filter( 'rest_ap_post_query', array( self::class, 'filter_ap_post_by_user' ), 10, 2 );
39 \add_filter( 'rest_ap_object_type_query', array( self::class, 'filter_object_type_by_user' ), 10, 2 );
40 \add_filter( 'rest_ap_object_type_collection_params', array( self::class, 'register_object_type_user_param' ) );
41
42 \add_filter( 'activitypub_get_actor_extra_fields', array( Extra_Fields::class, 'default_actor_extra_fields' ), 10, 2 );
43
44 \add_filter( 'add_post_metadata', array( self::class, 'prevent_empty_post_meta' ), 10, 4 );
45 \add_filter( 'update_post_metadata', array( self::class, 'prevent_empty_post_meta' ), 10, 4 );
46
47 // Add support for ActivityPub to custom post types.
48 foreach ( \get_option( 'activitypub_support_post_types', array( 'post' ) ) as $post_type ) {
49 \add_post_type_support( $post_type, 'activitypub' );
50 }
51 }
52
53 /**
54 * Register the Remote Actors post type and its meta.
55 */
56 public static function register_remote_actors_post_type() {
57 \register_post_type(
58 Remote_Actors::POST_TYPE,
59 array(
60 'labels' => array(
61 'name' => \_x( 'Followers', 'post_type plural name', 'activitypub' ),
62 'singular_name' => \_x( 'Follower', 'post_type single name', 'activitypub' ),
63 ),
64 'public' => false,
65 'show_in_rest' => true,
66 'hierarchical' => false,
67 'rewrite' => false,
68 'query_var' => false,
69 'delete_with_user' => false,
70 'can_export' => true,
71 'supports' => array( 'custom-fields' ),
72 )
73 );
74
75 // Register meta for Remote Actors post type.
76 \register_post_meta(
77 Remote_Actors::POST_TYPE,
78 '_activitypub_inbox',
79 array(
80 'type' => 'string',
81 'single' => true,
82 'sanitize_callback' => 'sanitize_url',
83 )
84 );
85
86 \register_post_meta(
87 Remote_Actors::POST_TYPE,
88 '_activitypub_errors',
89 array(
90 'type' => 'string',
91 'single' => false,
92 'sanitize_callback' => 'sanitize_text_field',
93 )
94 );
95
96 \register_post_meta(
97 Remote_Actors::POST_TYPE,
98 Followers::FOLLOWER_META_KEY,
99 array(
100 'type' => 'string',
101 'single' => false,
102 'show_in_rest' => true,
103 'sanitize_callback' => 'sanitize_text_field',
104 )
105 );
106 }
107
108 /**
109 * Register the Inbox post type and its meta.
110 */
111 public static function register_inbox_post_type() {
112 \register_post_type(
113 Inbox::POST_TYPE,
114 array(
115 'labels' => array(
116 'name' => \_x( 'Inbox', 'post_type plural name', 'activitypub' ),
117 'singular_name' => \_x( 'Inbox Item', 'post_type single name', 'activitypub' ),
118 ),
119 'capabilities' => array(
120 'create_posts' => false,
121 ),
122 'map_meta_cap' => true,
123 'public' => false,
124 'show_in_rest' => false,
125 'rewrite' => false,
126 'query_var' => false,
127 'supports' => array( 'title', 'editor', 'author', 'custom-fields' ),
128 'delete_with_user' => true,
129 'can_export' => true,
130 'exclude_from_search' => true,
131 )
132 );
133
134 // Register meta for Inbox post type.
135 \register_post_meta(
136 Inbox::POST_TYPE,
137 '_activitypub_object_id',
138 array(
139 'type' => 'string',
140 'single' => true,
141 'description' => 'The ID (ActivityPub URI) of the object that the inbox item is about.',
142 'sanitize_callback' => 'sanitize_url',
143 )
144 );
145
146 \register_post_meta(
147 Inbox::POST_TYPE,
148 '_activitypub_activity_type',
149 array(
150 'type' => 'string',
151 'description' => 'The type of the activity',
152 'single' => true,
153 'show_in_rest' => true,
154 'sanitize_callback' => static function ( $value ) {
155 $schema = array(
156 'type' => 'string',
157 'enum' => Activity::TYPES,
158 'default' => 'Create',
159 );
160
161 if ( \is_wp_error( \rest_validate_enum( $value, $schema, '' ) ) ) {
162 return $schema['default'];
163 }
164
165 return $value;
166 },
167 )
168 );
169
170 \register_post_meta(
171 Inbox::POST_TYPE,
172 '_activitypub_activity_remote_actor',
173 array(
174 'type' => 'string',
175 'single' => true,
176 'description' => 'The ID (ActivityPub URI) of the remote actor that sent the activity.',
177 'sanitize_callback' => 'sanitize_url',
178 )
179 );
180
181 \register_post_meta(
182 Inbox::POST_TYPE,
183 'activitypub_content_visibility',
184 array(
185 'type' => 'string',
186 'single' => true,
187 'show_in_rest' => true,
188 'sanitize_callback' => static function ( $value ) {
189 $schema = array(
190 'type' => 'string',
191 'enum' => array( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE, ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ),
192 'default' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
193 );
194
195 if ( \is_wp_error( \rest_validate_enum( $value, $schema, '' ) ) ) {
196 return $schema['default'];
197 }
198
199 return $value;
200 },
201 )
202 );
203
204 \register_post_meta(
205 Inbox::POST_TYPE,
206 '_activitypub_user_id',
207 array(
208 'type' => 'integer',
209 'single' => false, // Allow multiple values - one per recipient.
210 'description' => 'User ID of a recipient of this activity. Multiple entries allowed.',
211 'sanitize_callback' => 'absint',
212 'show_in_rest' => true,
213 )
214 );
215 }
216
217 /**
218 * Register the Outbox post type and its meta.
219 */
220 public static function register_outbox_post_type() {
221 \register_post_type(
222 Outbox::POST_TYPE,
223 array(
224 'labels' => array(
225 'name' => \_x( 'Outbox', 'post_type plural name', 'activitypub' ),
226 'singular_name' => \_x( 'Outbox Item', 'post_type single name', 'activitypub' ),
227 ),
228 'capabilities' => array(
229 'create_posts' => false,
230 ),
231 'map_meta_cap' => true,
232 'public' => false,
233 'show_in_rest' => false,
234 'rewrite' => false,
235 'query_var' => false,
236 'supports' => array( 'title', 'editor', 'author', 'custom-fields' ),
237 'delete_with_user' => true,
238 'can_export' => true,
239 'exclude_from_search' => true,
240 )
241 );
242
243 // Register meta for Outbox post type.
244 /**
245 * Register Activity Type meta for Outbox items.
246 *
247 * @see https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
248 */
249 \register_post_meta(
250 Outbox::POST_TYPE,
251 '_activitypub_activity_type',
252 array(
253 'type' => 'string',
254 'description' => 'The type of the activity',
255 'single' => true,
256 'show_in_rest' => true,
257 'sanitize_callback' => static function ( $value ) {
258 $schema = array(
259 'type' => 'string',
260 'enum' => Activity::TYPES,
261 'default' => 'Announce',
262 );
263
264 if ( \is_wp_error( \rest_validate_enum( $value, $schema, '' ) ) ) {
265 return $schema['default'];
266 }
267
268 return $value;
269 },
270 )
271 );
272
273 \register_post_meta(
274 Outbox::POST_TYPE,
275 '_activitypub_activity_actor',
276 array(
277 'type' => 'string',
278 'single' => true,
279 'show_in_rest' => true,
280 'sanitize_callback' => static function ( $value ) {
281 $schema = array(
282 'type' => 'string',
283 'enum' => array( 'application', 'blog', 'user' ),
284 'default' => 'user',
285 );
286
287 if ( \is_wp_error( \rest_validate_enum( $value, $schema, '' ) ) ) {
288 return $schema['default'];
289 }
290
291 return $value;
292 },
293 )
294 );
295
296 \register_post_meta(
297 Outbox::POST_TYPE,
298 '_activitypub_outbox_offset',
299 array(
300 'type' => 'integer',
301 'single' => true,
302 'description' => 'Keeps track of the followers offset when processing outbox items.',
303 'sanitize_callback' => 'absint',
304 'default' => 0,
305 )
306 );
307
308 \register_post_meta(
309 Outbox::POST_TYPE,
310 '_activitypub_object_id',
311 array(
312 'type' => 'string',
313 'single' => true,
314 'description' => 'The ID (ActivityPub URI) of the object that the outbox item is about.',
315 'sanitize_callback' => 'sanitize_url',
316 )
317 );
318
319 \register_post_meta(
320 Outbox::POST_TYPE,
321 'activitypub_content_visibility',
322 array(
323 'type' => 'string',
324 'single' => true,
325 'show_in_rest' => true,
326 'sanitize_callback' => static function ( $value ) {
327 $schema = array(
328 'type' => 'string',
329 'enum' => array( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE, ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ),
330 'default' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
331 );
332
333 if ( \is_wp_error( \rest_validate_enum( $value, $schema, '' ) ) ) {
334 return $schema['default'];
335 }
336
337 return $value;
338 },
339 )
340 );
341 }
342
343 /**
344 * Register the Post post type.
345 */
346 public static function register_post_post_type() {
347 \register_post_type(
348 Posts::POST_TYPE,
349 array(
350 'labels' => array(
351 'name' => \_x( 'Posts', 'post_type plural name', 'activitypub' ),
352 'singular_name' => \_x( 'Post', 'post_type single name', 'activitypub' ),
353 ),
354 'capabilities' => array(
355 'activitypub' => true,
356 ),
357 'map_meta_cap' => true,
358 'public' => false,
359 'show_in_rest' => true,
360 'rewrite' => false,
361 'query_var' => false,
362 'supports' => array( 'title', 'editor', 'author', 'custom-fields', 'excerpt', 'comments' ),
363 'delete_with_user' => true,
364 'can_export' => true,
365 'exclude_from_search' => true,
366 'taxonomies' => array( 'ap_tag', 'ap_object_type' ),
367 )
368 );
369
370 \register_taxonomy(
371 'ap_tag',
372 array( Posts::POST_TYPE ),
373 array(
374 'public' => false,
375 'query_var' => true,
376 'show_in_rest' => true,
377 )
378 );
379
380 \register_taxonomy(
381 'ap_object_type',
382 array( Posts::POST_TYPE ),
383 array(
384 'public' => false,
385 'query_var' => true,
386 'show_in_rest' => true,
387 )
388 );
389
390 \register_post_meta(
391 Posts::POST_TYPE,
392 '_activitypub_remote_actor_id',
393 array(
394 'type' => 'integer',
395 'single' => true,
396 'description' => 'The local ID of the remote actor that created the object.',
397 'sanitize_callback' => 'absint',
398 )
399 );
400
401 \register_post_meta(
402 Posts::POST_TYPE,
403 '_activitypub_user_id',
404 array(
405 'type' => 'integer',
406 'single' => true,
407 'description' => 'The ID of the local user that received the activity.',
408 'sanitize_callback' => 'absint',
409 )
410 );
411 }
412
413 /**
414 * Register the Extra Fields post types.
415 */
416 public static function register_extra_fields_post_types() {
417 $extra_field_args = array(
418 'labels' => array(
419 'name' => \_x( 'Extra fields', 'post_type plural name', 'activitypub' ),
420 'singular_name' => \_x( 'Extra field', 'post_type single name', 'activitypub' ),
421 'add_new' => \__( 'Add new', 'activitypub' ),
422 'add_new_item' => \__( 'Add new extra field', 'activitypub' ),
423 'new_item' => \__( 'New extra field', 'activitypub' ),
424 'edit_item' => \__( 'Edit extra field', 'activitypub' ),
425 'view_item' => \__( 'View extra field', 'activitypub' ),
426 'all_items' => \__( 'All extra fields', 'activitypub' ),
427 ),
428 'public' => false,
429 'hierarchical' => false,
430 'query_var' => false,
431 'has_archive' => false,
432 'publicly_queryable' => false,
433 'show_in_menu' => false,
434 'delete_with_user' => true,
435 'can_export' => true,
436 'exclude_from_search' => true,
437 'show_in_rest' => true,
438 'map_meta_cap' => true,
439 'show_ui' => true,
440 'supports' => array( 'title', 'editor', 'page-attributes', 'author' ),
441 'capabilities' => array(
442 'create_posts' => 'activitypub', // Require activitypub capability to create extra fields.
443 'edit_others_posts' => 'do_not_allow', // Disallow editing others' Extra Fields (only own ones).
444 ),
445 );
446
447 \register_post_type( Extra_Fields::USER_POST_TYPE, $extra_field_args );
448
449 // Blog Extra Fields require manage_options capability.
450 $extra_field_args['capabilities'] = array( 'create_posts' => 'manage_options' );
451 \register_post_type( Extra_Fields::BLOG_POST_TYPE, $extra_field_args );
452
453 /**
454 * Fires after ActivityPub custom post types have been registered.
455 */
456 \do_action( 'activitypub_after_register_post_type' );
457 }
458
459 /**
460 * Register post meta for ActivityPub supported post types.
461 */
462 public static function register_activitypub_post_meta() {
463 $ap_post_types = \get_post_types_by_support( 'activitypub' );
464 foreach ( $ap_post_types as $post_type ) {
465 \register_post_meta(
466 $post_type,
467 'activitypub_content_warning',
468 array(
469 'show_in_rest' => true,
470 'single' => true,
471 'type' => 'string',
472 'sanitize_callback' => 'sanitize_text_field',
473 )
474 );
475
476 \register_post_meta(
477 $post_type,
478 'activitypub_content_visibility',
479 array(
480 'type' => 'string',
481 'single' => true,
482 'show_in_rest' => true,
483 'sanitize_callback' => static function ( $value ) {
484 $schema = array(
485 'type' => 'string',
486 'enum' => array( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE, ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ),
487 'default' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
488 );
489
490 if ( \is_wp_error( \rest_validate_enum( $value, $schema, '' ) ) ) {
491 return $schema['default'];
492 }
493
494 return $value;
495 },
496 )
497 );
498
499 \register_post_meta(
500 $post_type,
501 'activitypub_max_image_attachments',
502 array(
503 'type' => 'integer',
504 'single' => true,
505 'show_in_rest' => true,
506 'default' => \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ),
507 'sanitize_callback' => 'absint',
508 )
509 );
510
511 \register_post_meta(
512 $post_type,
513 'activitypub_interaction_policy_quote',
514 array(
515 'type' => 'string',
516 'single' => true,
517 'show_in_rest' => true,
518 'sanitize_callback' => static function ( $value ) {
519 $schema = array(
520 'type' => 'string',
521 'enum' => array( ACTIVITYPUB_INTERACTION_POLICY_ANYONE, ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS, ACTIVITYPUB_INTERACTION_POLICY_ME ),
522 'default' => ACTIVITYPUB_INTERACTION_POLICY_ANYONE,
523 );
524
525 if ( \is_wp_error( \rest_validate_enum( $value, $schema, '' ) ) ) {
526 return $schema['default'];
527 }
528
529 return $value;
530 },
531 )
532 );
533
534 \register_post_meta(
535 $post_type,
536 'activitypub_status',
537 array(
538 'type' => 'string',
539 'single' => true,
540 'show_in_rest' => true,
541 'sanitize_callback' => static function ( $value ) {
542 $schema = array(
543 'type' => 'string',
544 'enum' => array( 'pending', 'federated', 'failed' ),
545 'default' => 'pending',
546 );
547
548 if ( \is_wp_error( \rest_validate_enum( $value, $schema, '' ) ) ) {
549 return $schema['default'];
550 }
551
552 return $value;
553 },
554 )
555 );
556 }
557 }
558
559 /**
560 * Register REST field for ap_actor posts.
561 */
562 public static function register_ap_actor_rest_field() {
563 \register_rest_field(
564 Remote_Actors::POST_TYPE,
565 'activitypub_json',
566 array(
567 /**
568 * Get the raw post content without WordPress content filtering.
569 *
570 * @param array $response Prepared response array.
571 * @return string The raw post content.
572 */
573 'get_callback' => static function ( $response ) {
574 return \get_post_field( 'post_content', $response['id'] );
575 },
576 'schema' => array(
577 'description' => 'Raw ActivityPub JSON data without WordPress content filtering',
578 'type' => 'string',
579 'context' => array( 'view', 'edit' ),
580 ),
581 )
582 );
583
584 // Add formatted actor data field.
585 \register_rest_field(
586 Remote_Actors::POST_TYPE,
587 'actor_info',
588 array(
589 'get_callback' => function ( $response ) {
590 $actor = Remote_Actors::get_actor( $response['id'] );
591 if ( \is_wp_error( $actor ) ) {
592 return null;
593 }
594 return array(
595 'username' => $actor->get_preferred_username(),
596 'name' => $actor->get_name() ?? $actor->get_preferred_username(),
597 'icon' => object_to_uri( $actor->get_icon() ),
598 'url' => object_to_uri( $actor->get_url() ?? $actor->get_id() ),
599 'webfinger' => Remote_Actors::get_acct( $response['id'] ),
600 'identifier' => $actor->get_id(),
601 );
602 },
603 'schema' => array(
604 'description' => 'Parsed ActivityPub actor information',
605 'type' => 'object',
606 'context' => array( 'view', 'edit' ),
607 ),
608 )
609 );
610
611 // Add follow status field.
612 \register_rest_field(
613 Remote_Actors::POST_TYPE,
614 'follow_status',
615 array(
616 'get_callback' => function ( $response ) {
617 $current_user_id = \get_current_user_id();
618 if ( ! $current_user_id ) {
619 return array( 'follows_back' => false );
620 }
621 return array(
622 'follows_back' => Following::check_status( $current_user_id, $response['id'] ),
623 );
624 },
625 'schema' => array(
626 'description' => 'Follow relationship status',
627 'type' => 'object',
628 'context' => array( 'view', 'edit' ),
629 ),
630 )
631 );
632
633 // Add custom query parameter for filtering by follower relationships.
634 \add_filter( 'rest_ap_actor_query', array( self::class, 'filter_ap_actor_query_by_follower' ), 10, 2 );
635 }
636
637 /**
638 * Filter WP_Query args to support follower_of parameter.
639 *
640 * @param array $args Array of arguments for WP_Query.
641 * @param \WP_REST_Request $request The REST API request.
642 * @return array Modified query arguments.
643 */
644 public static function filter_ap_actor_query_by_follower( $args, $request ) {
645 if ( ! empty( $request['follower_of'] ) ) {
646 // Add meta_query to filter by _activitypub_following.
647 if ( ! isset( $args['meta_query'] ) ) {
648 $args['meta_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
649 }
650
651 $args['meta_query'][] = array(
652 'key' => Followers::FOLLOWER_META_KEY,
653 'value' => $request['follower_of'],
654 );
655 }
656
657 return $args;
658 }
659
660 /**
661 * Register a REST field for the ap_post post type to embed remote actor data.
662 */
663 public static function register_ap_post_actor_rest_field() {
664 \register_rest_field(
665 Posts::POST_TYPE,
666 'actor_info',
667 array(
668 /**
669 * Get the remote actor data for an ap_post.
670 *
671 * @param array $response Prepared response array.
672 * @return array|null The actor data or null if not found.
673 */
674 'get_callback' => function ( $response ) {
675 $id = \get_post_meta( $response['id'], '_activitypub_remote_actor_id', true );
676 $actor = Remote_Actors::get_actor( $id );
677
678 if ( \is_wp_error( $actor ) ) {
679 return null;
680 }
681
682 return array(
683 'username' => $actor->get_preferred_username(),
684 'name' => $actor->get_name() ?? $actor->get_preferred_username(),
685 'icon' => object_to_uri( $actor->get_icon() ),
686 'url' => object_to_uri( $actor->get_url() ?? $actor->get_id() ),
687 'webfinger' => Remote_Actors::get_acct( $id ),
688 'identifier' => $actor->get_id(),
689 );
690 },
691 'schema' => array(
692 'description' => 'Remote actor data',
693 'type' => 'object',
694 'context' => array( 'view', 'edit' ),
695 ),
696 )
697 );
698 }
699
700 /**
701 * Register custom REST API parameters for ap_post endpoint.
702 */
703 public static function register_ap_post_rest_params() {
704 \add_filter(
705 'rest_' . Posts::POST_TYPE . '_collection_params',
706 function ( $params ) {
707 $params['user_id'] = array(
708 'description' => __( 'Filter posts by user ID (0 for site/blog actor).', 'activitypub' ),
709 'type' => 'integer',
710 'sanitize_callback' => 'absint',
711 );
712
713 $params['ap_object_type'] = array(
714 'description' => 'Filter posts by ActivityPub object type.',
715 'type' => 'array',
716 'items' => array(
717 'type' => 'integer',
718 'minimum' => 0,
719 ),
720 );
721
722 $params['ap_tag'] = array(
723 'description' => 'Filter posts by ActivityPub tag (term IDs).',
724 'type' => 'array',
725 'items' => array(
726 'type' => 'integer',
727 'minimum' => 0,
728 ),
729 );
730
731 return $params;
732 }
733 );
734 }
735
736 /**
737 * Filter ap_post REST query to only show posts for the current user.
738 *
739 * @param array $args Query arguments.
740 * @param \WP_REST_Request $request The REST API request.
741 *
742 * @return array Modified query arguments.
743 */
744 public static function filter_ap_post_by_user( $args, $request ) {
745 $ap_tag = $request->get_param( 'ap_tag' );
746 if ( ! empty( $ap_tag ) ) {
747 if ( ! isset( $args['tax_query'] ) ) {
748 $args['tax_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
749 }
750
751 $args['tax_query'][] = array(
752 'taxonomy' => 'ap_tag',
753 'field' => 'term_id',
754 'terms' => $ap_tag,
755 );
756
757 return $args;
758 }
759
760 // Filter by user_id (defaults to current user, use 0 for site/blog actor).
761 $user_id = isset( $request['user_id'] ) ? (int) $request['user_id'] : \get_current_user_id();
762
763 if ( ! isset( $args['meta_query'] ) ) {
764 $args['meta_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
765 }
766
767 $args['meta_query'][] = array(
768 'key' => '_activitypub_user_id',
769 'value' => $user_id,
770 'compare' => '=',
771 );
772
773 // Filter by object type if provided.
774 if ( ! empty( $request['ap_object_type'] ) ) {
775 if ( ! isset( $args['tax_query'] ) ) {
776 $args['tax_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
777 }
778
779 $args['tax_query'][] = array(
780 'taxonomy' => 'ap_object_type',
781 'field' => 'term_id',
782 'terms' => $request['ap_object_type'],
783 );
784 }
785
786 return $args;
787 }
788
789 /**
790 * Register user_id parameter for ap_object_type taxonomy REST API.
791 *
792 * @param array $params Existing collection parameters.
793 *
794 * @return array Modified collection parameters.
795 */
796 public static function register_object_type_user_param( $params ) {
797 $params['user_id'] = array(
798 'description' => __( 'Filter terms to those with posts from this user ID.', 'activitypub' ),
799 'type' => 'integer',
800 'sanitize_callback' => 'absint',
801 );
802
803 return $params;
804 }
805
806 /**
807 * Filter ap_object_type REST query to only return terms that have posts for the given user.
808 *
809 * Uses a direct SQL query to efficiently get term IDs without loading all post IDs.
810 *
811 * @param array $args Query arguments.
812 * @param \WP_REST_Request $request The REST API request.
813 *
814 * @return array Modified query arguments.
815 */
816 public static function filter_object_type_by_user( $args, $request ) {
817 $user_id = $request->get_param( 'user_id' );
818 if ( null === $user_id ) {
819 return $args;
820 }
821
822 global $wpdb;
823
824 // Get term IDs that have at least one ap_post for this user.
825 $term_ids = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
826 $wpdb->prepare(
827 "SELECT DISTINCT tt.term_id
828 FROM {$wpdb->term_taxonomy} tt
829 INNER JOIN {$wpdb->term_relationships} tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
830 INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID
831 INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
832 WHERE tt.taxonomy = 'ap_object_type'
833 AND p.post_type = 'ap_post'
834 AND pm.meta_key = '_activitypub_user_id'
835 AND pm.meta_value = %s",
836 $user_id
837 )
838 );
839
840 if ( empty( $term_ids ) ) {
841 // Force empty result.
842 $term_ids = array( 0 );
843 }
844
845 $args['include'] = \array_map( 'intval', $term_ids );
846
847 return $args;
848 }
849
850 /**
851 * Prevent empty or default meta values.
852 *
853 * @param null|bool $check Whether to allow updating metadata for the given type.
854 * @param int $object_id ID of the object metadata is for.
855 * @param string $meta_key Metadata key.
856 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
857 */
858 public static function prevent_empty_post_meta( $check, $object_id, $meta_key, $meta_value ) {
859 $post_metas = array(
860 'activitypub_content_visibility' => '',
861 'activitypub_content_warning' => '',
862 'activitypub_interaction_policy_quote' => ACTIVITYPUB_INTERACTION_POLICY_ANYONE,
863 'activitypub_max_image_attachments' => (string) \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ),
864 );
865
866 if ( isset( $post_metas[ $meta_key ] ) && $post_metas[ $meta_key ] === (string) $meta_value ) {
867 if ( 'update_post_metadata' === current_action() ) {
868 \delete_post_meta( $object_id, $meta_key );
869 }
870
871 $check = true;
872 }
873
874 return $check;
875 }
876 }
877