Skip to content

Commit 88f1bfb

Browse files
authored
Add support for ActivityPub Reader-Item post type (#2311)
1 parent c6d7282 commit 88f1bfb

File tree

12 files changed

+871
-40
lines changed

12 files changed

+871
-40
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Added a new ap_object post type and taxonomies for storing and managing incoming ActivityPub objects, with updated handlers

includes/class-post-types.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Activitypub\Collection\Followers;
1313
use Activitypub\Collection\Inbox;
1414
use Activitypub\Collection\Outbox;
15+
use Activitypub\Collection\Posts;
1516
use Activitypub\Collection\Remote_Actors;
1617

1718
/**
@@ -25,6 +26,7 @@ public static function init() {
2526
\add_action( 'init', array( self::class, 'register_remote_actors_post_type' ), 11 );
2627
\add_action( 'init', array( self::class, 'register_inbox_post_type' ), 11 );
2728
\add_action( 'init', array( self::class, 'register_outbox_post_type' ), 11 );
29+
\add_action( 'init', array( self::class, 'register_post_post_type' ), 11 );
2830
\add_action( 'init', array( self::class, 'register_extra_fields_post_types' ), 11 );
2931
\add_action( 'init', array( self::class, 'register_activitypub_post_meta' ), 11 );
3032

@@ -345,6 +347,65 @@ public static function register_outbox_post_type() {
345347
);
346348
}
347349

350+
/**
351+
* Register the Object post type.
352+
*/
353+
public static function register_post_post_type() {
354+
\register_post_type(
355+
Posts::POST_TYPE,
356+
array(
357+
'labels' => array(
358+
'name' => \_x( 'Posts', 'post_type plural name', 'activitypub' ),
359+
'singular_name' => \_x( 'Post', 'post_type single name', 'activitypub' ),
360+
),
361+
'capabilities' => array(
362+
'create_posts' => false,
363+
),
364+
'map_meta_cap' => true,
365+
'public' => false,
366+
'show_in_rest' => true,
367+
'rewrite' => false,
368+
'query_var' => false,
369+
'supports' => array( 'title', 'editor', 'author', 'custom-fields', 'excerpt', 'comments' ),
370+
'delete_with_user' => true,
371+
'can_export' => true,
372+
'exclude_from_search' => true,
373+
'taxonomies' => array( 'ap_tag', 'ap_object_type' ),
374+
)
375+
);
376+
377+
\register_taxonomy(
378+
'ap_tag',
379+
array( Posts::POST_TYPE ),
380+
array(
381+
'public' => false,
382+
'query_var' => true,
383+
'show_in_rest' => true,
384+
)
385+
);
386+
387+
\register_taxonomy(
388+
'ap_object_type',
389+
array( Posts::POST_TYPE ),
390+
array(
391+
'public' => false,
392+
'query_var' => true,
393+
'show_in_rest' => true,
394+
)
395+
);
396+
397+
\register_post_meta(
398+
Posts::POST_TYPE,
399+
'_activitypub_remote_actor_id',
400+
array(
401+
'type' => 'integer',
402+
'single' => true,
403+
'description' => 'The local ID of the remote actor that created the object.',
404+
'sanitize_callback' => 'absint',
405+
)
406+
);
407+
}
408+
348409
/**
349410
* Register the Extra Fields post types.
350411
*/

includes/class-sanitize.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,19 @@ public static function webfinger( $value ) {
182182

183183
return $value;
184184
}
185+
186+
/**
187+
* Sanitize content for ActivityPub.
188+
*
189+
* @param string $content The content to convert.
190+
*
191+
* @return string The converted content.
192+
*/
193+
public static function content( $content ) {
194+
$content = \make_clickable( $content );
195+
$content = \wpautop( $content );
196+
$content = \wp_kses_post( $content );
197+
198+
return $content;
199+
}
185200
}

includes/collection/class-inbox.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
* @link https://www.w3.org/TR/activitypub/#inbox
2121
*/
2222
class Inbox {
23+
/**
24+
* The post type for the objects.
25+
*
26+
* @var string
27+
*/
2328
const POST_TYPE = 'ap_inbox';
2429

2530
/**

includes/collection/class-outbox.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
* @link https://www.w3.org/TR/activitypub/#outbox
2121
*/
2222
class Outbox {
23+
/**
24+
* The post type for the objects.
25+
*
26+
* @var string
27+
*/
2328
const POST_TYPE = 'ap_outbox';
2429

2530
/**
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
/**
3+
* Posts collection file.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub\Collection;
9+
10+
use Activitypub\Sanitize;
11+
12+
use function Activitypub\object_to_uri;
13+
14+
/**
15+
* Posts collection.
16+
*
17+
* Provides methods to retrieve, create, update, and manage ActivityPub posts (articles, notes, media, etc.).
18+
*/
19+
class Posts {
20+
/**
21+
* The post type for the posts.
22+
*
23+
* @var string
24+
*/
25+
const POST_TYPE = 'ap_post';
26+
27+
/**
28+
* Add an object to the collection.
29+
*
30+
* @param array $activity The activity object data.
31+
*
32+
* @return \WP_Post|\WP_Error The object post or WP_Error on failure.
33+
*/
34+
public static function add( $activity ) {
35+
$activity_object = $activity['object'];
36+
$actor = Remote_Actors::fetch_by_uri( object_to_uri( $activity_object['attributedTo'] ) );
37+
38+
if ( \is_wp_error( $actor ) ) {
39+
return $actor;
40+
}
41+
42+
$post_array = self::activity_to_post( $activity_object );
43+
$post_id = \wp_insert_post( $post_array, true );
44+
45+
if ( \is_wp_error( $post_id ) ) {
46+
return $post_id;
47+
}
48+
49+
\add_post_meta( $post_id, '_activitypub_remote_actor_id', $actor->ID );
50+
51+
self::add_taxonomies( $post_id, $activity_object );
52+
53+
return \get_post( $post_id );
54+
}
55+
56+
/**
57+
* Get an object from the collection.
58+
*
59+
* @param int $id The object ID.
60+
*
61+
* @return \WP_Post|null The post object or null on failure.
62+
*/
63+
public static function get( $id ) {
64+
return \get_post( $id );
65+
}
66+
67+
/**
68+
* Get an object by its GUID.
69+
*
70+
* @param string $guid The object GUID.
71+
*
72+
* @return \WP_Post|\WP_Error The object post or WP_Error on failure.
73+
*/
74+
public static function get_by_guid( $guid ) {
75+
global $wpdb;
76+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
77+
$post_id = $wpdb->get_var(
78+
$wpdb->prepare(
79+
"SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type=%s",
80+
\esc_url( $guid ),
81+
self::POST_TYPE
82+
)
83+
);
84+
85+
if ( ! $post_id ) {
86+
return new \WP_Error(
87+
'activitypub_object_not_found',
88+
\__( 'Object not found', 'activitypub' ),
89+
array( 'status' => 404 )
90+
);
91+
}
92+
93+
return \get_post( $post_id );
94+
}
95+
96+
/**
97+
* Update an object in the collection.
98+
*
99+
* @param array $activity The activity object data.
100+
*
101+
* @return \WP_Post|\WP_Error The updated object post or WP_Error on failure.
102+
*/
103+
public static function update( $activity ) {
104+
$post = self::get_by_guid( $activity['object']['id'] );
105+
if ( \is_wp_error( $post ) ) {
106+
return $post;
107+
}
108+
109+
$post_array = self::activity_to_post( $activity['object'] );
110+
$post_array['ID'] = $post->ID;
111+
$post_id = \wp_update_post( $post_array, true );
112+
113+
if ( \is_wp_error( $post_id ) ) {
114+
return $post_id;
115+
}
116+
117+
self::add_taxonomies( $post_id, $activity['object'] );
118+
119+
return \get_post( $post_id );
120+
}
121+
122+
/**
123+
* Convert an activity to a post array.
124+
*
125+
* @param array $activity The activity array.
126+
*
127+
* @return array|\WP_Error The post array or WP_Error on failure.
128+
*/
129+
private static function activity_to_post( $activity ) {
130+
if ( ! is_array( $activity ) ) {
131+
return new \WP_Error( 'invalid_activity', __( 'Invalid activity format', 'activitypub' ) );
132+
}
133+
134+
return array(
135+
'post_title' => isset( $activity['name'] ) ? \wp_strip_all_tags( $activity['name'] ) : '',
136+
'post_content' => isset( $activity['content'] ) ? Sanitize::content( $activity['content'] ) : '',
137+
'post_excerpt' => isset( $activity['summary'] ) ? \wp_strip_all_tags( $activity['summary'] ) : '',
138+
'post_status' => 'publish',
139+
'post_type' => self::POST_TYPE,
140+
'guid' => isset( $activity['id'] ) ? \esc_url_raw( $activity['id'] ) : '',
141+
);
142+
}
143+
144+
/**
145+
* Add taxonomies to the object post.
146+
*
147+
* @param int $post_id The post ID.
148+
* @param array $activity_object The activity object data.
149+
*/
150+
private static function add_taxonomies( $post_id, $activity_object ) {
151+
// Save Object Type as Taxonomy item.
152+
\wp_set_post_terms( $post_id, array( $activity_object['type'] ), 'ap_object_type' );
153+
154+
$tags = array();
155+
156+
// Save the Hashtags as Taxonomy items.
157+
if ( ! empty( $activity_object['tag'] ) && \is_array( $activity_object['tag'] ) ) {
158+
foreach ( $activity_object['tag'] as $tag ) {
159+
if ( isset( $tag['type'] ) && 'Hashtag' === $tag['type'] && isset( $tag['name'] ) ) {
160+
$tags[] = \wp_strip_all_tags( ltrim( $tag['name'], '#' ) );
161+
}
162+
}
163+
}
164+
165+
\wp_set_post_terms( $post_id, $tags, 'ap_tag' );
166+
}
167+
}

includes/debug.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use Activitypub\Collection\Inbox;
1111
use Activitypub\Collection\Outbox;
12+
use Activitypub\Collection\Posts;
1213

1314
/**
1415
* Allow localhost URLs if WP_DEBUG is true.
@@ -32,8 +33,8 @@ function allow_localhost( $parsed_args ) {
3233
*
3334
* @return array The arguments for the post type.
3435
*/
35-
function debug_outbox_post_type( $args, $post_type ) {
36-
if ( ! \in_array( $post_type, array( Outbox::POST_TYPE, Inbox::POST_TYPE ), true ) ) {
36+
function debug_post_type( $args, $post_type ) {
37+
if ( ! \in_array( $post_type, array( Outbox::POST_TYPE, Inbox::POST_TYPE, Posts::POST_TYPE ), true ) ) {
3738
return $args;
3839
}
3940

@@ -43,11 +44,33 @@ function debug_outbox_post_type( $args, $post_type ) {
4344
$args['menu_icon'] = 'dashicons-upload';
4445
} elseif ( Inbox::POST_TYPE === $post_type ) {
4546
$args['menu_icon'] = 'dashicons-download';
47+
} elseif ( Posts::POST_TYPE === $post_type ) {
48+
$args['menu_icon'] = 'dashicons-media-document';
49+
}
50+
51+
return $args;
52+
}
53+
\add_filter( 'register_post_type_args', '\Activitypub\debug_post_type', 10, 2 );
54+
55+
/**
56+
* Debug the object type taxonomy.
57+
*
58+
* @param array $args The arguments for the taxonomy.
59+
* @param string $taxonomy The taxonomy.
60+
*
61+
* @return array The arguments for the taxonomy.
62+
*/
63+
function debug_taxonomy( $args, $taxonomy ) {
64+
if ( ! in_array( $taxonomy, array( 'ap_object_type', 'ap_tag' ), true ) ) {
65+
return $args;
4666
}
4767

68+
$args['show_ui'] = true;
69+
$args['show_in_menu'] = true;
70+
4871
return $args;
4972
}
50-
\add_filter( 'register_post_type_args', '\Activitypub\debug_outbox_post_type', 10, 2 );
73+
\add_filter( 'register_taxonomy_args', '\Activitypub\debug_taxonomy', 10, 2 );
5174

5275
/**
5376
* Debug the outbox post type column.

0 commit comments

Comments
 (0)