PluginProbe
ActivityPub / 5.1.0
ActivityPub v5.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
← All changes | includes/class-activitypub.php +369 -143 2.0.05.1.0 View file →
@@ -1,19 +1,21 @@
1 1 <?php
2 +/**
3 + * ActivityPub Class.
4 + *
5 + * @package Activitypub
6 + */
7 +
2 8 namespace Activitypub;
3 9
4 10 use Exception;
5 -use Activitypub\Signature;
6 -use Activitypub\Collection\Users;
11 +use Activitypub\Transformer\Factory;
12 +use Activitypub\Collection\Outbox;
7 13 use Activitypub\Collection\Followers;
14 +use Activitypub\Collection\Extra_Fields;
8 15
9 -use function Activitypub\sanitize_url;
10 -
11 -use function Activitypub\is_comment;
12 -use function Activitypub\is_activitypub_request;
13 -
14 16 /**
15 - * ActivityPub Class
17 + * ActivityPub Class.
16 18 *
17 19 * @author Matthias Pfefferle
18 20 */
19 21 class Activitypub {
@@ -20,16 +22,16 @@
20 22 /**
21 23 * Initialize the class, registering WordPress hooks.
22 24 */
23 25 public static function init() {
24 - \add_filter( 'template_include', array( self::class, 'render_json_template' ), 99 );
26 + \add_filter( 'template_include', array( self::class, 'render_activitypub_template' ), 99 );
25 27 \add_action( 'template_redirect', array( self::class, 'template_redirect' ) );
28 + \add_filter( 'redirect_canonical', array( self::class, 'redirect_canonical' ), 10, 2 );
26 29 \add_filter( 'query_vars', array( self::class, 'add_query_vars' ) );
27 30 \add_filter( 'pre_get_avatar_data', array( self::class, 'pre_get_avatar_data' ), 11, 2 );
28 - \add_filter( 'get_comment_link', array( self::class, 'remote_comment_link' ), 11, 3 );
29 31
30 - // Add support for ActivityPub to custom post types
31 - $post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) : array();
32 + // Add support for ActivityPub to custom post types.
33 + $post_types = \get_option( 'activitypub_support_post_types', array( 'post' ) );
32 34
33 35 foreach ( $post_types as $post_type ) {
34 36 \add_post_type_support( $post_type, 'activitypub' );
35 37 }
@@ -37,47 +39,82 @@
37 39 \add_action( 'wp_trash_post', array( self::class, 'trash_post' ), 1 );
38 40 \add_action( 'untrash_post', array( self::class, 'untrash_post' ), 1 );
39 41
40 42 \add_action( 'init', array( self::class, 'add_rewrite_rules' ), 11 );
43 + \add_action( 'init', array( self::class, 'theme_compat' ), 11 );
41 44
42 - \add_action( 'after_setup_theme', array( self::class, 'theme_compat' ), 99 );
45 + \add_action( 'user_register', array( self::class, 'user_register' ) );
43 46
44 47 \add_action( 'in_plugin_update_message-' . ACTIVITYPUB_PLUGIN_BASENAME, array( self::class, 'plugin_update_message' ) );
45 48
46 - \add_filter( 'comment_class', array( self::class, 'comment_class' ), 10, 3 );
49 + if ( site_supports_blocks() ) {
50 + \add_action( 'tool_box', array( self::class, 'tool_box' ) );
51 + }
47 52
48 - // register several post_types
53 + \add_filter( 'activitypub_get_actor_extra_fields', array( Extra_Fields::class, 'default_actor_extra_fields' ), 10, 2 );
54 +
55 + \add_action( 'updated_postmeta', array( self::class, 'updated_postmeta' ), 10, 4 );
56 +
57 + // Register several post_types.
49 58 self::register_post_types();
50 59 }
51 60
52 61 /**
53 - * Activation Hook
54 - *
55 - * @return void
62 + * Activation Hook.
56 63 */
57 64 public static function activate() {
58 65 self::flush_rewrite_rules();
66 + Scheduler::register_schedules();
59 67
60 - Scheduler::register_schedules();
68 + \add_filter( 'pre_wp_update_comment_count_now', array( Comment::class, 'pre_wp_update_comment_count_now' ), 10, 3 );
69 + Migration::update_comment_counts();
61 70 }
62 71
63 72 /**
64 - * Deactivation Hook
65 - *
66 - * @return void
73 + * Deactivation Hook.
67 74 */
68 75 public static function deactivate() {
69 76 self::flush_rewrite_rules();
70 77 Scheduler::deregister_schedules();
78 +
79 + \remove_filter( 'pre_wp_update_comment_count_now', array( Comment::class, 'pre_wp_update_comment_count_now' ) );
80 + Migration::update_comment_counts( 2000 );
71 81 }
72 82
73 83 /**
74 - * Uninstall Hook
75 - *
76 - * @return void
84 + * Uninstall Hook.
77 85 */
78 86 public static function uninstall() {
79 87 Scheduler::deregister_schedules();
88 +
89 + \remove_filter( 'pre_wp_update_comment_count_now', array( Comment::class, 'pre_wp_update_comment_count_now' ) );
90 + Migration::update_comment_counts( 2000 );
91 +
92 + delete_option( 'activitypub_actor_mode' );
93 + delete_option( 'activitypub_attribution_domains' );
94 + delete_option( 'activitypub_authorized_fetch' );
95 + delete_option( 'activitypub_application_user_private_key' );
96 + delete_option( 'activitypub_application_user_public_key' );
97 + delete_option( 'activitypub_blog_user_private_key' );
98 + delete_option( 'activitypub_blog_user_public_key' );
99 + delete_option( 'activitypub_blog_description' );
100 + delete_option( 'activitypub_blog_identifier' );
101 + delete_option( 'activitypub_custom_post_content' );
102 + delete_option( 'activitypub_db_version' );
103 + delete_option( 'activitypub_default_extra_fields' );
104 + delete_option( 'activitypub_enable_blog_user' );
105 + delete_option( 'activitypub_enable_users' );
106 + delete_option( 'activitypub_header_image' );
107 + delete_option( 'activitypub_last_post_with_permalink_as_id' );
108 + delete_option( 'activitypub_mailer_new_follower' );
109 + delete_option( 'activitypub_mailer_new_dm' );
110 + delete_option( 'activitypub_max_image_attachments' );
111 + delete_option( 'activitypub_migration_lock' );
112 + delete_option( 'activitypub_object_type' );
113 + delete_option( 'activitypub_support_post_types' );
114 + delete_option( 'activitypub_use_hashtags' );
115 + delete_option( 'activitypub_use_opengraph' );
116 + delete_option( 'activitypub_use_permalink_as_id_for_blog' );
80 117 }
81 118
82 119 /**
83 120 * Return a AS2 JSON version of an author, post or page.
@@ -85,46 +122,126 @@
85 122 * @param string $template The path to the template object.
86 123 *
87 124 * @return string The new path to the JSON template.
88 125 */
89 - public static function render_json_template( $template ) {
126 + public static function render_activitypub_template( $template ) {
90 127 if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
91 128 return $template;
92 129 }
93 130
131 + self::add_headers();
132 +
94 133 if ( ! is_activitypub_request() ) {
95 134 return $template;
96 135 }
97 136
98 - $json_template = false;
137 + $activitypub_template = false;
138 + $activitypub_object = Query::get_instance()->get_activitypub_object();
99 139
100 - // check if user can publish posts
101 - if ( \is_author() && is_wp_error( Users::get_by_id( \get_the_author_meta( 'ID' ) ) ) ) {
102 - return $template;
103 - }
140 + if ( $activitypub_object ) {
141 + if ( \get_query_var( 'preview' ) ) {
142 + \define( 'ACTIVITYPUB_PREVIEW', true );
104 143
105 - if ( \is_author() ) {
106 - $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php';
107 - } elseif ( is_comment() ) {
108 - $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/comment-json.php';
109 - } elseif ( \is_singular() ) {
110 - $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-json.php';
111 - } elseif ( \is_home() ) {
112 - $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/blog-json.php';
144 + /**
145 + * Filter the template used for the ActivityPub preview.
146 + *
147 + * @param string $activitypub_template Absolute path to the template file.
148 + */
149 + $activitypub_template = apply_filters( 'activitypub_preview_template', ACTIVITYPUB_PLUGIN_DIR . '/templates/post-preview.php' );
150 + } else {
151 + $activitypub_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/activitypub-json.php';
152 + }
113 153 }
114 154
115 - if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
155 + /*
156 + * Check if the request is authorized.
157 + *
158 + * @see https://www.w3.org/wiki/SocialCG/ActivityPub/Primer/Authentication_Authorization#Authorized_fetch
159 + * @see https://swicg.github.io/activitypub-http-signature/#authorized-fetch
160 + */
161 + if ( $activitypub_template && use_authorized_fetch() ) {
116 162 $verification = Signature::verify_http_signature( $_SERVER );
117 163 if ( \is_wp_error( $verification ) ) {
118 - // fallback as template_loader can't return http headers
164 + header( 'HTTP/1.1 401 Unauthorized' );
165 +
166 + // Fallback as template_loader can't return http headers.
119 167 return $template;
120 168 }
121 169 }
122 170
123 - return $json_template;
171 + if ( $activitypub_template ) {
172 + // Check if header already sent.
173 + if ( ! \headers_sent() && ACTIVITYPUB_SEND_VARY_HEADER ) {
174 + // Send Vary header for Accept header.
175 + \header( 'Vary: Accept' );
176 + }
177 +
178 + return $activitypub_template;
179 + }
180 +
181 + return $template;
124 182 }
125 183
126 184 /**
185 + * Add the 'self' link to the header.
186 + */
187 + public static function add_headers() {
188 + $id = Query::get_instance()->get_activitypub_object_id();
189 +
190 + if ( ! $id ) {
191 + return;
192 + }
193 +
194 + if ( ! headers_sent() ) {
195 + header( 'Link: <' . esc_url( $id ) . '>; title="ActivityPub (JSON)"; rel="alternate"; type="application/activity+json"', false );
196 + }
197 +
198 + add_action(
199 + 'wp_head',
200 + function () use ( $id ) {
201 + echo PHP_EOL . '<link rel="alternate" title="ActivityPub (JSON)" type="application/activity+json" href="' . esc_url( $id ) . '" />' . PHP_EOL;
202 + }
203 + );
204 + }
205 +
206 + /**
207 + * Add support for `p` and `author` query vars.
208 + *
209 + * @param string $redirect_url The URL to redirect to.
210 + * @param string $requested_url The requested URL.
211 + *
212 + * @return string $redirect_url
213 + */
214 + public static function redirect_canonical( $redirect_url, $requested_url ) {
215 + if ( ! is_activitypub_request() ) {
216 + return $redirect_url;
217 + }
218 +
219 + $query = \wp_parse_url( $requested_url, PHP_URL_QUERY );
220 +
221 + if ( ! $query ) {
222 + return $redirect_url;
223 + }
224 +
225 + $query_params = \wp_parse_args( $query );
226 + unset( $query_params['activitypub'] );
227 +
228 + if ( 1 !== count( $query_params ) ) {
229 + return $redirect_url;
230 + }
231 +
232 + if ( isset( $query_params['p'] ) ) {
233 + return null;
234 + }
235 +
236 + if ( isset( $query_params['author'] ) ) {
237 + return null;
238 + }
239 +
240 + return $requested_url;
241 + }
242 +
243 + /**
127 244 * Custom redirects for ActivityPub requests.
128 245 *
129 246 * @return void
130 247 */
@@ -130,9 +247,9 @@
130 247 */
131 248 public static function template_redirect() {
132 249 $comment_id = get_query_var( 'c', null );
133 250
134 - // check if it seems to be a comment
251 + // Check if it seems to be a comment.
135 252 if ( ! $comment_id ) {
136 253 return;
137 254 }
138 255
@@ -137,9 +254,9 @@
137 254 }
138 255
139 256 $comment = get_comment( $comment_id );
140 257
141 - // load a 404 page if `c` is set but not valid
258 + // Load a 404 page if `c` is set but not valid.
142 259 if ( ! $comment ) {
143 260 global $wp_query;
144 261 $wp_query->set_404();
145 262 return;
@@ -144,21 +261,28 @@
144 261 $wp_query->set_404();
145 262 return;
146 263 }
147 264
148 - // stop if it's not an ActivityPub comment
149 - if ( is_activitypub_request() && $comment->user_id ) {
265 + // Stop if it's not an ActivityPub comment.
266 + if ( is_activitypub_request() && ! is_local_comment( $comment ) ) {
150 267 return;
151 268 }
152 269
153 270 wp_safe_redirect( get_comment_link( $comment ) );
271 + exit;
154 272 }
155 273
156 274 /**
157 275 * Add the 'activitypub' query variable so WordPress won't mangle it.
276 + *
277 + * @param array $vars The query variables.
278 + *
279 + * @return array The query variables.
158 280 */
159 281 public static function add_query_vars( $vars ) {
160 282 $vars[] = 'activitypub';
283 + $vars[] = 'preview';
284 + $vars[] = 'author';
161 285 $vars[] = 'c';
162 286 $vars[] = 'p';
163 287
164 288 return $vars;
@@ -198,16 +322,18 @@
198 322 // Check if comment has an avatar.
199 323 $avatar = self::get_avatar_url( $id_or_email->comment_ID );
200 324
201 325 if ( $avatar ) {
202 - if ( ! isset( $args['class'] ) || ! \is_array( $args['class'] ) ) {
203 - $args['class'] = array( 'u-photo' );
204 - } else {
205 - $args['class'][] = 'u-photo';
206 - $args['class'] = \array_unique( $args['class'] );
326 + if ( empty( $args['class'] ) ) {
327 + $args['class'] = array();
328 + } elseif ( \is_string( $args['class'] ) ) {
329 + $args['class'] = \explode( ' ', $args['class'] );
207 330 }
331 +
208 332 $args['url'] = $avatar;
209 333 $args['class'][] = 'avatar-activitypub';
334 + $args['class'][] = 'u-photo';
335 + $args['class'] = \array_unique( $args['class'] );
210 336 }
211 337
212 338 return $args;
213 339 }
@@ -214,11 +340,11 @@
214 340
215 341 /**
216 342 * Function to retrieve Avatar URL if stored in meta.
217 343 *
218 - * @param int|WP_Comment $comment
344 + * @param int|\WP_Comment $comment The comment ID or object.
219 345 *
220 - * @return string $url
346 + * @return string The Avatar URL.
221 347 */
222 348 public static function get_avatar_url( $comment ) {
223 349 if ( \is_numeric( $comment ) ) {
224 350 $comment = \get_comment( $comment );
@@ -226,42 +352,16 @@
226 352 return \get_comment_meta( $comment->comment_ID, 'avatar_url', true );
227 353 }
228 354
229 355 /**
230 - * Link remote comments to source url.
231 - *
232 - * @param string $comment_link
233 - * @param object|WP_Comment $comment
234 - *
235 - * @return string $url
236 - */
237 - public static function remote_comment_link( $comment_link, $comment ) {
238 - if ( ! $comment || is_admin() ) {
239 - return $comment_link;
240 - }
241 -
242 - $comment_meta = \get_comment_meta( $comment->comment_ID );
243 -
244 - if ( ! empty( $comment_meta['source_url'][0] ) ) {
245 - return $comment_meta['source_url'][0];
246 - } elseif ( ! empty( $comment_meta['source_id'][0] ) ) {
247 - return $comment_meta['source_id'][0];
248 - }
249 -
250 - return $comment_link;
251 - }
252 -
253 - /**
254 356 * Store permalink in meta, to send delete Activity.
255 357 *
256 358 * @param string $post_id The Post ID.
257 - *
258 - * @return void
259 359 */
260 360 public static function trash_post( $post_id ) {
261 361 \add_post_meta(
262 362 $post_id,
263 - 'activitypub_canonical_url',
363 + '_activitypub_canonical_url',
264 364 \get_permalink( $post_id ),
265 365 true
266 366 );
267 367 }
@@ -266,24 +366,24 @@
266 366 );
267 367 }
268 368
269 369 /**
270 - * Delete permalink from meta
370 + * Delete permalink from meta.
271 371 *
272 - * @param string $post_id The Post ID
273 - *
274 - * @return void
372 + * @param string $post_id The Post ID.
275 373 */
276 374 public static function untrash_post( $post_id ) {
277 - \delete_post_meta( $post_id, 'activitypub_canonical_url' );
375 + \delete_post_meta( $post_id, '_activitypub_canonical_url' );
278 376 }
279 377
280 378 /**
281 - * Add rewrite rules
379 + * Add rewrite rules.
282 380 */
283 381 public static function add_rewrite_rules() {
284 - // If another system needs to take precedence over the ActivityPub rewrite rules,
285 - // they can define their own and will manually call the appropriate functions as required.
382 + /*
383 + * If another system needs to take precedence over the ActivityPub rewrite rules,
384 + * they can define their own and will manually call the appropriate functions as required.
385 + */
286 386 if ( ACTIVITYPUB_DISABLE_REWRITES ) {
287 387 return;
288 388 }
289 389
@@ -297,21 +397,16 @@
297 397
298 398 if ( ! \class_exists( 'Nodeinfo_Endpoint' ) && true === (bool) \get_option( 'blog_public', 1 ) ) {
299 399 \add_rewrite_rule(
300 400 '^.well-known/nodeinfo',
301 - 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/nodeinfo/discovery',
401 + 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/nodeinfo',
302 402 'top'
303 403 );
304 - \add_rewrite_rule(
305 - '^.well-known/x-nodeinfo2',
306 - 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/nodeinfo2',
307 - 'top'
308 - );
309 404 }
310 405
311 406 \add_rewrite_rule(
312 407 '^@([\w\-\.]+)',
313 - 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/users/$matches[1]',
408 + 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/actors/$matches[1]',
314 409 'top'
315 410 );
316 411
317 412 \add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
@@ -317,9 +412,9 @@
317 412 \add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
318 413 }
319 414
320 415 /**
321 - * Flush rewrite rules;
416 + * Flush rewrite rules.
322 417 */
323 418 public static function flush_rewrite_rules() {
324 419 self::add_rewrite_rules();
325 420 \flush_rewrite_rules();
@@ -325,45 +420,42 @@
325 420 \flush_rewrite_rules();
326 421 }
327 422
328 423 /**
329 - * Theme compatibility stuff
330 - *
331 - * @return void
424 + * Adds metabox on wp-admin/tools.php.
332 425 */
333 - public static function theme_compat() {
334 - $site_icon = get_theme_support( 'custom-logo' );
335 -
336 - if ( ! $site_icon ) {
337 - // custom logo support
338 - add_theme_support(
339 - 'custom-logo',
340 - array(
341 - 'height' => 80,
342 - 'width' => 80,
343 - )
344 - );
426 + public static function tool_box() {
427 + if ( \current_user_can( 'edit_posts' ) ) {
428 + \load_template( ACTIVITYPUB_PLUGIN_DIR . 'templates/toolbox.php' );
345 429 }
430 + }
346 431
347 - $custom_header = get_theme_support( 'custom-header' );
348 -
349 - if ( ! $custom_header ) {
350 - // This theme supports a custom header
351 - $custom_header_args = array(
352 - 'width' => 1250,
353 - 'height' => 600,
354 - 'header-text' => true,
355 - );
356 - add_theme_support( 'custom-header', $custom_header_args );
432 + /**
433 + * Theme compatibility stuff.
434 + */
435 + public static function theme_compat() {
436 + // We assume that you want to use Post-Formats when enabling the setting.
437 + if ( 'wordpress-post-format' === \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE ) ) {
438 + if ( ! get_theme_support( 'post-formats' ) ) {
439 + // Add support for the Aside, Gallery Post Formats...
440 + add_theme_support(
441 + 'post-formats',
442 + array(
443 + 'gallery',
444 + 'status',
445 + 'image',
446 + 'video',
447 + 'audio',
448 + )
449 + );
450 + }
357 451 }
358 452 }
359 453
360 454 /**
361 - * Display plugin upgrade notice to users
455 + * Display plugin upgrade notice to users.
362 456 *
363 - * @param array $data The plugin data
364 - *
365 - * @return void
457 + * @param array $data The plugin data.
366 458 */
367 459 public static function plugin_update_message( $data ) {
368 460 if ( ! isset( $data['upgrade_notice'] ) ) {
369 461 return;
@@ -383,11 +475,9 @@
383 475 );
384 476 }
385 477
386 478 /**
387 - * Register the "Followers" Taxonomy
388 - *
389 - * @return void
479 + * Register Custom Post Types.
390 480 */
391 481 private static function register_post_types() {
392 482 \register_post_type(
393 483 Followers::POST_TYPE,
@@ -407,9 +497,9 @@
407 497 );
408 498
409 499 \register_post_meta(
410 500 Followers::POST_TYPE,
411 - 'activitypub_inbox',
501 + '_activitypub_inbox',
412 502 array(
413 503 'type' => 'string',
414 504 'single' => true,
415 505 'sanitize_callback' => 'sanitize_url',
@@ -417,9 +507,9 @@
417 507 );
418 508
419 509 \register_post_meta(
420 510 Followers::POST_TYPE,
421 - 'activitypub_errors',
511 + '_activitypub_errors',
422 512 array(
423 513 'type' => 'string',
424 514 'single' => false,
425 515 'sanitize_callback' => function ( $value ) {
@@ -433,9 +523,9 @@
433 523 );
434 524
435 525 \register_post_meta(
436 526 Followers::POST_TYPE,
437 - 'activitypub_user_id',
527 + '_activitypub_user_id',
438 528 array(
439 529 'type' => 'string',
440 530 'single' => false,
441 531 'sanitize_callback' => function ( $value ) {
@@ -445,9 +535,9 @@
445 535 );
446 536
447 537 \register_post_meta(
448 538 Followers::POST_TYPE,
449 - 'activitypub_actor_json',
539 + '_activitypub_actor_json',
450 540 array(
451 541 'type' => 'string',
452 542 'single' => true,
453 543 'sanitize_callback' => function ( $value ) {
@@ -455,25 +545,161 @@
455 545 },
456 546 )
457 547 );
458 548
549 + // Register Outbox Post-Type.
550 + register_post_type(
551 + Outbox::POST_TYPE,
552 + array(
553 + 'labels' => array(
554 + 'name' => _x( 'Outbox', 'post_type plural name', 'activitypub' ),
555 + 'singular_name' => _x( 'Outbox Item', 'post_type single name', 'activitypub' ),
556 + ),
557 + 'capabilities' => array(
558 + 'create_posts' => false,
559 + ),
560 + 'map_meta_cap' => true,
561 + 'rewrite' => false,
562 + 'query_var' => false,
563 + 'delete_with_user' => true,
564 + 'can_export' => true,
565 + 'exclude_from_search' => true,
566 + )
567 + );
568 +
569 + /**
570 + * Register Activity Type meta for Outbox items.
571 + *
572 + * @see https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
573 + */
574 + \register_post_meta(
575 + Outbox::POST_TYPE,
576 + '_activitypub_activity_type',
577 + array(
578 + 'type' => 'string',
579 + 'description' => 'The type of the activity',
580 + 'single' => true,
581 + 'sanitize_callback' => function ( $value ) {
582 + $value = ucfirst( strtolower( $value ) );
583 + $schema = array(
584 + 'type' => 'string',
585 + 'enum' => array( 'Accept', 'Add', 'Announce', 'Arrive', 'Block', 'Create', 'Delete', 'Dislike', 'Flag', 'Follow', 'Ignore', 'Invite', 'Join', 'Leave', 'Like', 'Listen', 'Move', 'Offer', 'Question', 'Reject', 'Read', 'Remove', 'TentativeReject', 'TentativeAccept', 'Travel', 'Undo', 'Update', 'View' ),
586 + 'default' => 'Announce',
587 + );
588 +
589 + if ( is_wp_error( rest_validate_enum( $value, $schema, '' ) ) ) {
590 + return $schema['default'];
591 + }
592 +
593 + return $value;
594 + },
595 + )
596 + );
597 +
598 + \register_post_meta(
599 + Outbox::POST_TYPE,
600 + '_activitypub_activity_actor',
601 + array(
602 + 'type' => 'string',
603 + 'single' => true,
604 + 'sanitize_callback' => function ( $value ) {
605 + $schema = array(
606 + 'type' => 'string',
607 + 'enum' => array( 'application', 'blog', 'user' ),
608 + 'default' => 'user',
609 + );
610 +
611 + if ( is_wp_error( rest_validate_enum( $value, $schema, '' ) ) ) {
612 + return $schema['default'];
613 + }
614 +
615 + return $value;
616 + },
617 + )
618 + );
619 +
620 + \register_post_meta(
621 + Outbox::POST_TYPE,
622 + 'activitypub_content_visibility',
623 + array(
624 + 'show_in_rest' => true,
625 + 'single' => true,
626 + 'type' => 'string',
627 + 'sanitize_callback' => function ( $value ) {
628 + $schema = array(
629 + 'type' => 'string',
630 + 'enum' => array( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE, ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL ),
631 + 'default' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
632 + );
633 +
634 + if ( is_wp_error( rest_validate_enum( $value, $schema, '' ) ) ) {
635 + return $schema['default'];
636 + }
637 +
638 + return $value;
639 + },
640 + )
641 + );
642 +
643 + // Both User and Blog Extra Fields types have the same args.
644 + $args = array(
645 + 'labels' => array(
646 + 'name' => _x( 'Extra fields', 'post_type plural name', 'activitypub' ),
647 + 'singular_name' => _x( 'Extra field', 'post_type single name', 'activitypub' ),
648 + 'add_new' => __( 'Add new', 'activitypub' ),
649 + 'add_new_item' => __( 'Add new extra field', 'activitypub' ),
650 + 'new_item' => __( 'New extra field', 'activitypub' ),
651 + 'edit_item' => __( 'Edit extra field', 'activitypub' ),
652 + 'view_item' => __( 'View extra field', 'activitypub' ),
653 + 'all_items' => __( 'All extra fields', 'activitypub' ),
654 + ),
655 + 'public' => false,
656 + 'hierarchical' => false,
657 + 'query_var' => false,
658 + 'has_archive' => false,
659 + 'publicly_queryable' => false,
660 + 'show_in_menu' => false,
661 + 'delete_with_user' => true,
662 + 'can_export' => true,
663 + 'exclude_from_search' => true,
664 + 'show_in_rest' => true,
665 + 'map_meta_cap' => true,
666 + 'show_ui' => true,
667 + 'supports' => array( 'title', 'editor', 'page-attributes' ),
668 + );
669 +
670 + \register_post_type( Extra_Fields::USER_POST_TYPE, $args );
671 + \register_post_type( Extra_Fields::BLOG_POST_TYPE, $args );
672 +
673 + /**
674 + * Fires after ActivityPub custom post types have been registered.
675 + */
459 676 \do_action( 'activitypub_after_register_post_type' );
460 677 }
461 678
462 679 /**
463 - * Filters the CSS classes to add an ActivityPub class.
680 + * Add the 'activitypub' capability to users who can publish posts.
464 681 *
465 - * @param string[] $classes An array of comment classes.
466 - * @param string[] $css_class An array of additional classes added to the list.
467 - * @param string $comment_id The comment ID as a numeric string.
682 + * @param int $user_id User ID.
683 + */
684 + public static function user_register( $user_id ) {
685 + if ( \user_can( $user_id, 'publish_posts' ) ) {
686 + $user = \get_user_by( 'id', $user_id );
687 + $user->add_cap( 'activitypub' );
688 + }
689 + }
690 +
691 + /**
692 + * Delete `activitypub_content_visibility` when updated to an empty value.
468 693 *
469 - * @return string[] An array of classes.
694 + * @param int $meta_id ID of updated metadata entry.
695 + * @param int $object_id Post ID.
696 + * @param string $meta_key Metadata key.
697 + * @param mixed $meta_value Metadata value. This will be a PHP-serialized string representation of the value
698 + * if the value is an array, an object, or itself a PHP-serialized string.
470 699 */
471 - public static function comment_class( $classes, $css_class, $comment_id ) {
472 - // check if ActivityPub comment
473 - if ( 'activitypub' === get_comment_meta( $comment_id, 'protocol', true ) ) {
474 - $classes[] = 'activitypub-comment';
700 + public static function updated_postmeta( $meta_id, $object_id, $meta_key, $meta_value ) {
701 + if ( 'activitypub_content_visibility' === $meta_key && empty( $meta_value ) ) {
702 + \delete_post_meta( $object_id, 'activitypub_content_visibility' );
475 703 }
476 -
477 - return $classes;
478 704 }
479 705 }