PluginProbe
ActivityPub / 2.1.0
ActivityPub v2.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 / functions.php

functions.php in ActivityPub 2.1.0, at includes/functions.php

830 lines 20.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Activitypub;
3
4 use WP_Error;
5 use Activitypub\Http;
6 use Activitypub\Comment;
7 use Activitypub\Webfinger;
8 use Activitypub\Activity\Activity;
9 use Activitypub\Collection\Followers;
10 use Activitypub\Collection\Users;
11
12 /**
13 * Returns the ActivityPub default JSON-context
14 *
15 * @return array the activitypub context
16 */
17 function get_context() {
18 $context = Activity::JSON_LD_CONTEXT;
19
20 return \apply_filters( 'activitypub_json_context', $context );
21 }
22
23 function safe_remote_post( $url, $body, $user_id ) {
24 return Http::post( $url, $body, $user_id );
25 }
26
27 function safe_remote_get( $url ) {
28 return Http::get( $url );
29 }
30
31 /**
32 * Returns a users WebFinger "resource"
33 *
34 * @param int $user_id The User-ID.
35 *
36 * @return string The User-Resource.
37 */
38 function get_webfinger_resource( $user_id ) {
39 return Webfinger::get_user_resource( $user_id );
40 }
41
42 /**
43 * Requests the Meta-Data from the Actors profile
44 *
45 * @param string $actor The Actor URL.
46 * @param bool $cached If the result should be cached.
47 *
48 * @return array|WP_Error The Actor profile as array or WP_Error on failure.
49 */
50 function get_remote_metadata_by_actor( $actor, $cached = true ) {
51 $pre = apply_filters( 'pre_get_remote_metadata_by_actor', false, $actor );
52 if ( $pre ) {
53 return $pre;
54 }
55 if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $actor ) ) {
56 $actor = Webfinger::resolve( $actor );
57 }
58
59 if ( ! $actor ) {
60 return new WP_Error( 'activitypub_no_valid_actor_identifier', \__( 'The "actor" identifier is not valid', 'activitypub' ), array( 'status' => 404, 'actor' => $actor ) );
61 }
62
63 if ( is_wp_error( $actor ) ) {
64 return $actor;
65 }
66
67 $transient_key = 'activitypub_' . $actor;
68
69 // only check the cache if needed.
70 if ( $cached ) {
71 $metadata = \get_transient( $transient_key );
72
73 if ( $metadata ) {
74 return $metadata;
75 }
76 }
77
78 if ( ! \wp_http_validate_url( $actor ) ) {
79 $metadata = new WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), array( 'status' => 400, 'actor' => $actor ) );
80 return $metadata;
81 }
82
83 $response = Http::get( $actor );
84
85 if ( \is_wp_error( $response ) ) {
86 return $response;
87 }
88
89 $metadata = \wp_remote_retrieve_body( $response );
90 $metadata = \json_decode( $metadata, true );
91
92 if ( ! $metadata ) {
93 $metadata = new WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), array( 'status' => 400, 'actor' => $actor ) );
94 return $metadata;
95 }
96
97 \set_transient( $transient_key, $metadata, WEEK_IN_SECONDS );
98
99 return $metadata;
100 }
101
102 /**
103 * Returns the followers of a given user.
104 *
105 * @param int $user_id The User-ID.
106 *
107 * @return array The followers.
108 */
109 function get_followers( $user_id ) {
110 return Followers::get_followers( $user_id );
111 }
112
113 /**
114 * Count the number of followers for a given user.
115 *
116 * @param int $user_id The User-ID.
117 *
118 * @return int The number of followers.
119 */
120 function count_followers( $user_id ) {
121 return Followers::count_followers( $user_id );
122 }
123
124 /**
125 * Examine a url and try to determine the author ID it represents.
126 *
127 * Checks are supposedly from the hosted site blog.
128 *
129 * @param string $url Permalink to check.
130 *
131 * @return int User ID, or 0 on failure.
132 */
133 function url_to_authorid( $url ) {
134 global $wp_rewrite;
135
136 // check if url hase the same host
137 if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) !== \wp_parse_url( $url, \PHP_URL_HOST ) ) {
138 return 0;
139 }
140
141 // first, check to see if there is a 'author=N' to match against
142 if ( \preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) {
143 $id = \absint( $values[1] );
144 if ( $id ) {
145 return $id;
146 }
147 }
148
149 // check to see if we are using rewrite rules
150 $rewrite = $wp_rewrite->wp_rewrite_rules();
151
152 // not using rewrite rules, and 'author=N' method failed, so we're out of options
153 if ( empty( $rewrite ) ) {
154 return 0;
155 }
156
157 // generate rewrite rule for the author url
158 $author_rewrite = $wp_rewrite->get_author_permastruct();
159 $author_regexp = \str_replace( '%author%', '', $author_rewrite );
160
161 // match the rewrite rule with the passed url
162 if ( \preg_match( '/https?:\/\/(.+)' . \preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) {
163 $user = \get_user_by( 'slug', $match[2] );
164 if ( $user ) {
165 return $user->ID;
166 }
167 }
168
169 return 0;
170 }
171
172 /**
173 * Verify if url is a wp_ap_comment,
174 * Or if it is a previously received remote comment
175 *
176 * @return int comment_id
177 */
178 function is_comment() {
179 $comment_id = get_query_var( 'c', null );
180
181 if ( ! is_null( $comment_id ) ) {
182 $comment = \get_comment( $comment_id );
183
184 // Only return local origin comments
185 if ( $comment && $comment->user_id ) {
186 return $comment_id;
187 }
188 }
189
190 return false;
191 }
192
193 /**
194 * Check for Tombstone Objects
195 *
196 * @see https://www.w3.org/TR/activitypub/#delete-activity-outbox
197 *
198 * @param WP_Error $wp_error A WP_Error-Response of an HTTP-Request
199 *
200 * @return boolean true if HTTP-Code is 410 or 404
201 */
202 function is_tombstone( $wp_error ) {
203 if ( ! is_wp_error( $wp_error ) ) {
204 return false;
205 }
206
207 if ( in_array( (int) $wp_error->get_error_code(), array( 404, 410 ), true ) ) {
208 return true;
209 }
210
211 return false;
212 }
213
214 /**
215 * Get the REST URL relative to this plugin's namespace.
216 *
217 * @param string $path Optional. REST route path. Otherwise this plugin's namespaced root.
218 *
219 * @return string REST URL relative to this plugin's namespace.
220 */
221 function get_rest_url_by_path( $path = '' ) {
222 // we'll handle the leading slash.
223 $path = ltrim( $path, '/' );
224 $namespaced_path = sprintf( '/%s/%s', ACTIVITYPUB_REST_NAMESPACE, $path );
225 return \get_rest_url( null, $namespaced_path );
226 }
227
228 /**
229 * Convert a string from camelCase to snake_case.
230 *
231 * @param string $string The string to convert.
232 *
233 * @return string The converted string.
234 */
235 // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound
236 function camel_to_snake_case( $string ) {
237 return strtolower( preg_replace( '/(?<!^)[A-Z]/', '_$0', $string ) );
238 }
239
240 /**
241 * Convert a string from snake_case to camelCase.
242 *
243 * @param string $string The string to convert.
244 *
245 * @return string The converted string.
246 */
247 // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound
248 function snake_to_camel_case( $string ) {
249 return lcfirst( str_replace( '_', '', ucwords( $string, '_' ) ) );
250 }
251
252 /**
253 * Escapes a Tag, to be used as a hashtag.
254 *
255 * @param string $string The string to escape.
256 *
257 * @return string The escaped hastag.
258 */
259 function esc_hashtag( $string ) {
260
261 $hashtag = \wp_specialchars_decode( $string, ENT_QUOTES );
262 // Remove all characters that are not letters, numbers, or underscores.
263 $hashtag = \preg_replace( '/emoji-regex(*SKIP)(?!)|[^\p{L}\p{Nd}_]+/u', '_', $hashtag );
264
265 // Capitalize every letter that is preceded by an underscore.
266 $hashtag = preg_replace_callback(
267 '/_(.)/',
268 function ( $matches ) {
269 return '' . strtoupper( $matches[1] );
270 },
271 $hashtag
272 );
273
274 // Add a hashtag to the beginning of the string.
275 $hashtag = ltrim( $hashtag, '#' );
276 $hashtag = '#' . $hashtag;
277
278 /**
279 * Allow defining your own custom hashtag generation rules.
280 *
281 * @param string $hashtag The hashtag to be returned.
282 * @param string $string The original string.
283 */
284 $hashtag = apply_filters( 'activitypub_esc_hashtag', $hashtag, $string );
285
286 return esc_html( $hashtag );
287 }
288
289 /**
290 * Check if a request is for an ActivityPub request.
291 *
292 * @return bool False by default.
293 */
294 function is_activitypub_request() {
295 global $wp_query;
296
297 /*
298 * ActivityPub requests are currently only made for
299 * author archives, singular posts, and the homepage.
300 */
301 if ( ! \is_author() && ! \is_singular() && ! \is_home() && ! defined( '\REST_REQUEST' ) ) {
302 return false;
303 }
304
305 // Check if the current post type supports ActivityPub.
306 if ( \is_singular() ) {
307 $queried_object = \get_queried_object();
308 $post_type = \get_post_type( $queried_object );
309
310 if ( ! \post_type_supports( $post_type, 'activitypub' ) ) {
311 return false;
312 }
313 }
314
315 // Check if header already sent.
316 if ( ! \headers_sent() && ACTIVITYPUB_SEND_VARY_HEADER ) {
317 // Send Vary header for Accept header.
318 \header( 'Vary: Accept' );
319 }
320
321 // One can trigger an ActivityPub request by adding ?activitypub to the URL.
322 // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.VariableRedeclaration
323 global $wp_query;
324 if ( isset( $wp_query->query_vars['activitypub'] ) ) {
325 return true;
326 }
327
328 /*
329 * The other (more common) option to make an ActivityPub request
330 * is to send an Accept header.
331 */
332 if ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
333 $accept = sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT'] ) );
334
335 /*
336 * $accept can be a single value, or a comma separated list of values.
337 * We want to support both scenarios,
338 * and return true when the header includes at least one of the following:
339 * - application/activity+json
340 * - application/ld+json
341 * - application/json
342 */
343 if ( preg_match( '/(application\/(ld\+json|activity\+json|json))/i', $accept ) ) {
344 return true;
345 }
346 }
347
348 return false;
349 }
350
351 /**
352 * This function checks if a user is disabled for ActivityPub.
353 *
354 * @param int $user_id The User-ID.
355 *
356 * @return boolean True if the user is disabled, false otherwise.
357 */
358 function is_user_disabled( $user_id ) {
359 $return = false;
360
361 switch ( $user_id ) {
362 // if the user is the application user, it's always enabled.
363 case \Activitypub\Collection\Users::APPLICATION_USER_ID:
364 $return = false;
365 break;
366 // if the user is the blog user, it's only enabled in single-user mode.
367 case \Activitypub\Collection\Users::BLOG_USER_ID:
368 if ( is_user_type_disabled( 'blog' ) ) {
369 $return = true;
370 break;
371 }
372
373 $return = false;
374 break;
375 // if the user is any other user, it's enabled if it can publish posts.
376 default:
377 if ( ! \get_user_by( 'id', $user_id ) ) {
378 $return = true;
379 break;
380 }
381
382 if ( is_user_type_disabled( 'user' ) ) {
383 $return = true;
384 break;
385 }
386
387 if ( ! \user_can( $user_id, 'publish_posts' ) ) {
388 $return = true;
389 break;
390 }
391
392 $return = false;
393 break;
394 }
395
396 return apply_filters( 'activitypub_is_user_disabled', $return, $user_id );
397 }
398
399 /**
400 * Checks if a User-Type is disabled for ActivityPub.
401 *
402 * This function is used to check if the 'blog' or 'user'
403 * type is disabled for ActivityPub.
404 *
405 * @param enum $type Can be 'blog' or 'user'.
406 *
407 * @return boolean True if the user type is disabled, false otherwise.
408 */
409 function is_user_type_disabled( $type ) {
410 switch ( $type ) {
411 case 'blog':
412 if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) ) {
413 if ( ACTIVITYPUB_SINGLE_USER_MODE ) {
414 $return = false;
415 break;
416 }
417 }
418
419 if ( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) ) {
420 $return = ACTIVITYPUB_DISABLE_BLOG_USER;
421 break;
422 }
423
424 if ( '1' !== \get_option( 'activitypub_enable_blog_user', '0' ) ) {
425 $return = true;
426 break;
427 }
428
429 $return = false;
430 break;
431 case 'user':
432 if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) ) {
433 if ( ACTIVITYPUB_SINGLE_USER_MODE ) {
434 $return = true;
435 break;
436 }
437 }
438
439 if ( \defined( 'ACTIVITYPUB_DISABLE_USER' ) ) {
440 $return = ACTIVITYPUB_DISABLE_USER;
441 break;
442 }
443
444 if ( '1' !== \get_option( 'activitypub_enable_users', '1' ) ) {
445 $return = true;
446 break;
447 }
448
449 $return = false;
450 break;
451 default:
452 $return = new WP_Error( 'activitypub_wrong_user_type', __( 'Wrong user type', 'activitypub' ), array( 'status' => 400 ) );
453 break;
454 }
455
456 return apply_filters( 'activitypub_is_user_type_disabled', $return, $type );
457 }
458
459 /**
460 * Check if the blog is in single-user mode.
461 *
462 * @return boolean True if the blog is in single-user mode, false otherwise.
463 */
464 function is_single_user() {
465 if (
466 false === is_user_type_disabled( 'blog' ) &&
467 true === is_user_type_disabled( 'user' )
468 ) {
469 return true;
470 }
471
472 return false;
473 }
474
475 /**
476 * Check if a site supports the block editor.
477 *
478 * @return boolean True if the site supports the block editor, false otherwise.
479 */
480 function site_supports_blocks() {
481 if ( \version_compare( \get_bloginfo( 'version' ), '5.9', '<' ) ) {
482 return false;
483 }
484
485 if ( ! \function_exists( 'register_block_type_from_metadata' ) ) {
486 return false;
487 }
488
489 /**
490 * Allow plugins to disable block editor support,
491 * thus disabling blocks registered by the ActivityPub plugin.
492 *
493 * @param boolean $supports_blocks True if the site supports the block editor, false otherwise.
494 */
495 return apply_filters( 'activitypub_site_supports_blocks', true );
496 }
497
498 /**
499 * Check if data is valid JSON.
500 *
501 * @param string $data The data to check.
502 *
503 * @return boolean True if the data is JSON, false otherwise.
504 */
505 function is_json( $data ) {
506 return \is_array( \json_decode( $data, true ) ) ? true : false;
507 }
508
509 /**
510 * Check if a blog is public based on the `blog_public` option
511 *
512 * @return bollean True if public, false if not
513 */
514 function is_blog_public() {
515 return (bool) apply_filters( 'activitypub_is_blog_public', \get_option( 'blog_public', 1 ) );
516 }
517
518 /**
519 * Sanitize a URL
520 *
521 * @param string $value The URL to sanitize
522 *
523 * @return string|null The sanitized URL or null if invalid
524 */
525 function sanitize_url( $value ) {
526 if ( filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
527 return null;
528 }
529
530 return esc_url_raw( $value );
531 }
532
533 /**
534 * Extract recipient URLs from Activity object
535 *
536 * @param array $data
537 *
538 * @return array The list of user URLs
539 */
540 function extract_recipients_from_activity( $data ) {
541 $recipient_items = array();
542
543 foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
544 if ( array_key_exists( $i, $data ) ) {
545 if ( is_array( $data[ $i ] ) ) {
546 $recipient = $data[ $i ];
547 } else {
548 $recipient = array( $data[ $i ] );
549 }
550 $recipient_items = array_merge( $recipient_items, $recipient );
551 }
552
553 if ( is_array( $data['object'] ) && array_key_exists( $i, $data['object'] ) ) {
554 if ( is_array( $data['object'][ $i ] ) ) {
555 $recipient = $data['object'][ $i ];
556 } else {
557 $recipient = array( $data['object'][ $i ] );
558 }
559 $recipient_items = array_merge( $recipient_items, $recipient );
560 }
561 }
562
563 $recipients = array();
564
565 // flatten array
566 foreach ( $recipient_items as $recipient ) {
567 if ( is_array( $recipient ) ) {
568 // check if recipient is an object
569 if ( array_key_exists( 'id', $recipient ) ) {
570 $recipients[] = $recipient['id'];
571 }
572 } else {
573 $recipients[] = $recipient;
574 }
575 }
576
577 return array_unique( $recipients );
578 }
579
580 /**
581 * Check if passed Activity is Public
582 *
583 * @param array $data The Activity object as array
584 *
585 * @return boolean True if public, false if not
586 */
587 function is_activity_public( $data ) {
588 $recipients = extract_recipients_from_activity( $data );
589
590 return in_array( 'https://www.w3.org/ns/activitystreams#Public', $recipients, true );
591 }
592
593 /**
594 * Get active users based on a given duration
595 *
596 * @param int $duration The duration to check in month(s)
597 *
598 * @return int The number of active users
599 */
600 function get_active_users( $duration = 1 ) {
601
602 $duration = intval( $duration );
603 $transient_key = sprintf( 'monthly_active_users_%d', $duration );
604 $count = get_transient( $transient_key );
605
606 if ( false === $count ) {
607 global $wpdb;
608 $query = "SELECT COUNT( DISTINCT post_author ) FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish' AND post_date <= DATE_SUB( NOW(), INTERVAL %d MONTH )";
609 $query = $wpdb->prepare( $query, $duration );
610 $count = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
611
612 set_transient( $transient_key, $count, DAY_IN_SECONDS );
613 }
614
615 // if 0 authors where active
616 if ( 0 === $count ) {
617 return 0;
618 }
619
620 // if single user mode
621 if ( is_single_user() ) {
622 return 1;
623 }
624
625 // if blog user is disabled
626 if ( is_user_disabled( Users::BLOG_USER_ID ) ) {
627 return $count;
628 }
629
630 // also count blog user
631 return $count + 1;
632 }
633
634 /**
635 * Get the total number of users
636 *
637 * @return int The total number of users
638 */
639 function get_total_users() {
640 // if single user mode
641 if ( is_single_user() ) {
642 return 1;
643 }
644
645 $users = \get_users(
646 array(
647 'capability__in' => array( 'publish_posts' ),
648 )
649 );
650
651 if ( is_array( $users ) ) {
652 $users = count( $users );
653 } else {
654 $users = 1;
655 }
656
657 // if blog user is disabled
658 if ( is_user_disabled( Users::BLOG_USER_ID ) ) {
659 return $users;
660 }
661
662 return $users + 1;
663 }
664
665 /**
666 * Examine a comment ID and look up an existing comment it represents.
667 *
668 * @param string $id ActivityPub object ID (usually a URL) to check.
669 *
670 * @return int|boolean Comment ID, or false on failure.
671 */
672 function object_id_to_comment( $id ) {
673 return Comment::object_id_to_comment( $id );
674 }
675
676 /**
677 * Verify if URL is a local comment,
678 * Or if it is a previously received remote comment
679 * (For threading comments locally)
680 *
681 * @param string $url The URL to check.
682 *
683 * @return int comment_ID or null if not found
684 */
685 function url_to_commentid( $url ) {
686 return Comment::url_to_commentid( $url );
687 }
688
689 /**
690 * Get the URI of an ActivityPub object
691 *
692 * @param array $object The ActivityPub object
693 *
694 * @return string The URI of the ActivityPub object
695 */
696 function object_to_uri( $object ) {
697 // check if it is already simple
698 if ( ! $object || is_string( $object ) ) {
699 return $object;
700 }
701
702 // check if it is a list, then take first item
703 // this plugin does not support collections
704 if ( array_is_list( $object ) ) {
705 $object = $object[0];
706 }
707
708 // check if it is simplified now
709 if ( is_string( $object ) ) {
710 return $object;
711 }
712
713 // return part of Object that makes most sense
714 switch ( $object['type'] ) {
715 case 'Link':
716 $object = $object['href'];
717 break;
718 default:
719 $object = $object['id'];
720 break;
721 }
722
723 return $object;
724 }
725
726 /**
727 * Check if a comment should be federated.
728 *
729 * We consider a comment should be federated if it is authored by a user that is
730 * not disabled for federation and if it is a reply directly to the post or to a
731 * federated comment.
732 *
733 * @param mixed $comment Comment object or ID.
734 *
735 * @return boolean True if the comment should be federated, false otherwise.
736 */
737 function should_comment_be_federated( $comment ) {
738 return Comment::should_be_federated( $comment );
739 }
740
741 /**
742 * Check if a comment was federated.
743 *
744 * This function checks if a comment was federated via ActivityPub.
745 *
746 * @param mixed $comment Comment object or ID.
747 *
748 * @return boolean True if the comment was federated, false otherwise.
749 */
750 function was_comment_sent( $comment ) {
751 return Comment::was_sent( $comment );
752 }
753
754 /**
755 * Check if a comment is federated.
756 *
757 * We consider a comment federated if comment was received via ActivityPub.
758 *
759 * Use this function to check if it is comment that was received via ActivityPub.
760 *
761 * @param mixed $comment Comment object or ID.
762 *
763 * @return boolean True if the comment is federated, false otherwise.
764 */
765 function was_comment_received( $comment ) {
766 return Comment::was_received( $comment );
767 }
768
769 /**
770 * Check if a comment is local only.
771 *
772 * This function checks if a comment is local only and was not sent or received via ActivityPub.
773 *
774 * @param mixed $comment Comment object or ID.
775 *
776 * @return boolean True if the comment is local only, false otherwise.
777 */
778 function is_local_comment( $comment ) {
779 return Comment::is_local( $comment );
780 }
781
782 /**
783 * Mark a WordPress object as federated.
784 *
785 * @param WP_Comment|WP_Post|mixed $wp_object
786 * @return void
787 */
788 function set_wp_object_state( $wp_object, $state ) {
789 $meta_key = 'activitypub_status';
790
791 if ( $wp_object instanceof \WP_Post ) {
792 \update_post_meta( $wp_object->ID, $meta_key, $state );
793 } elseif ( $wp_object instanceof \WP_Comment ) {
794 \update_comment_meta( $wp_object->comment_ID, $meta_key, $state );
795 } else {
796 \apply_filters( 'activitypub_mark_wp_object_as_federated', $wp_object );
797 }
798 }
799
800 /**
801 * Get the description of a post type.
802 *
803 * Set some default descriptions for the default post types.
804 *
805 * @param WP_Post_Type $post_type The post type object.
806 *
807 * @return string The description of the post type.
808 */
809 function get_post_type_description( $post_type ) {
810 $description = '';
811
812 switch ( $post_type->name ) {
813 case 'post':
814 $description = '';
815 break;
816 case 'page':
817 $description = '';
818 break;
819 case 'attachment':
820 $description = ' - ' . __( 'The attachments that you have uploaded to a post (images, videos, documents or other files).', 'activitypub' );
821 break;
822 default:
823 if ( ! empty( $post_type->description ) ) {
824 $description = ' - ' . $post_type->description;
825 }
826 }
827
828 return apply_filters( 'activitypub_post_type_description', $description, $post_type->name, $post_type );
829 }
830