PluginProbe
ActivityPub / 3.2.2
ActivityPub v3.2.2
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/functions.php +510 -71 2.0.13.2.2 View file →
@@ -1,14 +1,16 @@
1 1 <?php
2 2 namespace Activitypub;
3 3
4 +use WP_Query;
4 5 use WP_Error;
5 -use WP_Comment_Query;
6 6 use Activitypub\Http;
7 +use Activitypub\Comment;
7 8 use Activitypub\Webfinger;
8 9 use Activitypub\Activity\Activity;
9 10 use Activitypub\Collection\Followers;
10 11 use Activitypub\Collection\Users;
12 +use Activitypub\Collection\Extra_Fields;
11 13
12 14 /**
13 15 * Returns the ActivityPub default JSON-context
14 16 *
@@ -14,9 +16,9 @@
14 16 *
15 17 * @return array the activitypub context
16 18 */
17 19 function get_context() {
18 - $context = Activity::CONTEXT;
20 + $context = Activity::JSON_LD_CONTEXT;
19 21
20 22 return \apply_filters( 'activitypub_json_context', $context );
21 23 }
22 24
@@ -51,14 +53,33 @@
51 53 $pre = apply_filters( 'pre_get_remote_metadata_by_actor', false, $actor );
52 54 if ( $pre ) {
53 55 return $pre;
54 56 }
57 +
58 + if ( is_array( $actor ) ) {
59 + if ( array_key_exists( 'id', $actor ) ) {
60 + $actor = $actor['id'];
61 + } elseif ( array_key_exists( 'url', $actor ) ) {
62 + $actor = $actor['url'];
63 + } else {
64 + return new WP_Error(
65 + 'activitypub_no_valid_actor_identifier',
66 + \__( 'The "actor" identifier is not valid', 'activitypub' ),
67 + array( 'status' => 404, 'actor' => $actor )
68 + );
69 + }
70 + }
71 +
55 72 if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $actor ) ) {
56 73 $actor = Webfinger::resolve( $actor );
57 74 }
58 75
59 76 if ( ! $actor ) {
60 - return new WP_Error( 'activitypub_no_valid_actor_identifier', \__( 'The "actor" identifier is not valid', 'activitypub' ), array( 'status' => 404, 'actor' => $actor ) );
77 + return new WP_Error(
78 + 'activitypub_no_valid_actor_identifier',
79 + \__( 'The "actor" identifier is not valid', 'activitypub' ),
80 + array( 'status' => 404, 'actor' => $actor )
81 + );
61 82 }
62 83
63 84 if ( is_wp_error( $actor ) ) {
64 85 return $actor;
@@ -75,9 +96,13 @@
75 96 }
76 97 }
77 98
78 99 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 ) );
100 + $metadata = new WP_Error(
101 + 'activitypub_no_valid_actor_url',
102 + \__( 'The "actor" is no valid URL', 'activitypub' ),
103 + array( 'status' => 400, 'actor' => $actor )
104 + );
80 105 return $metadata;
81 106 }
82 107
83 108 $response = Http::get( $actor );
@@ -89,9 +114,13 @@
89 114 $metadata = \wp_remote_retrieve_body( $response );
90 115 $metadata = \json_decode( $metadata, true );
91 116
92 117 if ( ! $metadata ) {
93 - $metadata = new WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), array( 'status' => 400, 'actor' => $actor ) );
118 + $metadata = new WP_Error(
119 + 'activitypub_invalid_json',
120 + \__( 'No valid JSON data', 'activitypub' ),
121 + array( 'status' => 400, 'actor' => $actor )
122 + );
94 123 return $metadata;
95 124 }
96 125
97 126 \set_transient( $transient_key, $metadata, WEEK_IN_SECONDS );
@@ -133,9 +162,9 @@
133 162 function url_to_authorid( $url ) {
134 163 global $wp_rewrite;
135 164
136 165 // check if url hase the same host
137 - if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) !== \wp_parse_url( $url, \PHP_URL_HOST ) ) {
166 + if ( \wp_parse_url( \home_url(), \PHP_URL_HOST ) !== \wp_parse_url( $url, \PHP_URL_HOST ) ) {
138 167 return 0;
139 168 }
140 169
141 170 // first, check to see if there is a 'author=N' to match against
@@ -311,8 +340,14 @@
311 340 return false;
312 341 }
313 342 }
314 343
344 + // Check if header already sent.
345 + if ( ! \headers_sent() && ACTIVITYPUB_SEND_VARY_HEADER ) {
346 + // Send Vary header for Accept header.
347 + \header( 'Vary: Accept' );
348 + }
349 +
315 350 // One can trigger an ActivityPub request by adding ?activitypub to the URL.
316 351 // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.VariableRedeclaration
317 352 global $wp_query;
318 353 if ( isset( $wp_query->query_vars['activitypub'] ) ) {
@@ -377,9 +412,9 @@
377 412 $return = true;
378 413 break;
379 414 }
380 415
381 - if ( ! \user_can( $user_id, 'publish_posts' ) ) {
416 + if ( ! \user_can( $user_id, 'activitypub' ) ) {
382 417 $return = true;
383 418 break;
384 419 }
385 420
@@ -442,9 +477,13 @@
442 477
443 478 $return = false;
444 479 break;
445 480 default:
446 - $return = new WP_Error( 'activitypub_wrong_user_type', __( 'Wrong user type', 'activitypub' ), array( 'status' => 400 ) );
481 + $return = new WP_Error(
482 + 'activitypub_wrong_user_type',
483 + __( 'Wrong user type', 'activitypub' ),
484 + array( 'status' => 400 )
485 + );
447 486 break;
448 487 }
449 488
450 489 return apply_filters( 'activitypub_is_user_type_disabled', $return, $type );
@@ -637,9 +676,9 @@
637 676 }
638 677
639 678 $users = \get_users(
640 679 array(
641 - 'capability__in' => array( 'publish_posts' ),
680 + 'capability__in' => array( 'activitypub' ),
642 681 )
643 682 );
644 683
645 684 if ( is_array( $users ) ) {
@@ -663,24 +702,9 @@
663 702 *
664 703 * @return int|boolean Comment ID, or false on failure.
665 704 */
666 705 function object_id_to_comment( $id ) {
667 - $comment_query = new WP_Comment_Query(
668 - array(
669 - 'meta_key' => 'source_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
670 - 'meta_value' => $id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
671 - )
672 - );
673 -
674 - if ( ! $comment_query->comments ) {
675 - return false;
676 - }
677 -
678 - if ( count( $comment_query->comments ) > 1 ) {
679 - return false;
680 - }
681 -
682 - return $comment_query->comments[0];
706 + return Comment::object_id_to_comment( $id );
683 707 }
684 708
685 709 /**
686 710 * Verify if URL is a local comment,
@@ -691,52 +715,9 @@
691 715 *
692 716 * @return int comment_ID or null if not found
693 717 */
694 718 function url_to_commentid( $url ) {
695 - if ( ! $url || ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
696 - return null;
697 - }
698 -
699 - // check for local comment
700 - if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) === \wp_parse_url( $url, \PHP_URL_HOST ) ) {
701 - $query = \wp_parse_url( $url, PHP_URL_QUERY );
702 -
703 - if ( $query ) {
704 - parse_str( $query, $params );
705 -
706 - if ( ! empty( $params['c'] ) ) {
707 - $comment = \get_comment( $params['c'] );
708 -
709 - if ( $comment ) {
710 - return $comment->comment_ID;
711 - }
712 - }
713 - }
714 - }
715 -
716 - $args = array(
717 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
718 - 'meta_query' => array(
719 - 'relation' => 'OR',
720 - array(
721 - 'key' => 'source_url',
722 - 'value' => $url,
723 - ),
724 - array(
725 - 'key' => 'source_id',
726 - 'value' => $url,
727 - ),
728 - ),
729 - );
730 -
731 - $query = new \WP_Comment_Query();
732 - $comments = $query->query( $args );
733 -
734 - if ( $comments && is_array( $comments ) ) {
735 - return $comments[0]->comment_ID;
736 - }
737 -
738 - return null;
719 + return Comment::url_to_commentid( $url );
739 720 }
740 721
741 722 /**
742 723 * Get the URI of an ActivityPub object
@@ -744,9 +725,9 @@
744 725 * @param array $object The ActivityPub object
745 726 *
746 727 * @return string The URI of the ActivityPub object
747 728 */
748 -function object_to_uri( $object ) {
729 +function object_to_uri( $object ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.objectFound
749 730 // check if it is already simple
750 731 if ( ! $object || is_string( $object ) ) {
751 732 return $object;
752 733 }
@@ -761,10 +742,15 @@
761 742 if ( is_string( $object ) ) {
762 743 return $object;
763 744 }
764 745
746 + $type = 'Object';
747 + if ( isset( $object['type'] ) ) {
748 + $type = $object['type'];
749 + }
750 +
765 751 // return part of Object that makes most sense
766 - switch ( $object['type'] ) {
752 + switch ( $type ) {
767 753 case 'Link':
768 754 $object = $object['href'];
769 755 break;
770 756 default:
@@ -772,5 +758,458 @@
772 758 break;
773 759 }
774 760
775 761 return $object;
762 +}
763 +
764 +/**
765 + * Check if a comment should be federated.
766 + *
767 + * We consider a comment should be federated if it is authored by a user that is
768 + * not disabled for federation and if it is a reply directly to the post or to a
769 + * federated comment.
770 + *
771 + * @param mixed $comment Comment object or ID.
772 + *
773 + * @return boolean True if the comment should be federated, false otherwise.
774 + */
775 +function should_comment_be_federated( $comment ) {
776 + return Comment::should_be_federated( $comment );
777 +}
778 +
779 +/**
780 + * Check if a comment was federated.
781 + *
782 + * This function checks if a comment was federated via ActivityPub.
783 + *
784 + * @param mixed $comment Comment object or ID.
785 + *
786 + * @return boolean True if the comment was federated, false otherwise.
787 + */
788 +function was_comment_sent( $comment ) {
789 + return Comment::was_sent( $comment );
790 +}
791 +
792 +/**
793 + * Check if a comment is federated.
794 + *
795 + * We consider a comment federated if comment was received via ActivityPub.
796 + *
797 + * Use this function to check if it is comment that was received via ActivityPub.
798 + *
799 + * @param mixed $comment Comment object or ID.
800 + *
801 + * @return boolean True if the comment is federated, false otherwise.
802 + */
803 +function was_comment_received( $comment ) {
804 + return Comment::was_received( $comment );
805 +}
806 +
807 +/**
808 + * Check if a comment is local only.
809 + *
810 + * This function checks if a comment is local only and was not sent or received via ActivityPub.
811 + *
812 + * @param mixed $comment Comment object or ID.
813 + *
814 + * @return boolean True if the comment is local only, false otherwise.
815 + */
816 +function is_local_comment( $comment ) {
817 + return Comment::is_local( $comment );
818 +}
819 +
820 +/**
821 + * Mark a WordPress object as federated.
822 + *
823 + * @param WP_Comment|WP_Post|mixed $wp_object
824 + *
825 + * @return void
826 + */
827 +function set_wp_object_state( $wp_object, $state ) {
828 + $meta_key = 'activitypub_status';
829 +
830 + if ( $wp_object instanceof \WP_Post ) {
831 + \update_post_meta( $wp_object->ID, $meta_key, $state );
832 + } elseif ( $wp_object instanceof \WP_Comment ) {
833 + \update_comment_meta( $wp_object->comment_ID, $meta_key, $state );
834 + } else {
835 + \apply_filters( 'activitypub_mark_wp_object_as_federated', $wp_object );
836 + }
837 +}
838 +
839 +/**
840 + * Get the federation state of a WordPress object.
841 + *
842 + * @param WP_Comment|WP_Post|mixed $wp_object
843 + *
844 + * @return string|false The state of the object or false if not found.
845 + */
846 +function get_wp_object_state( $wp_object ) {
847 + $meta_key = 'activitypub_status';
848 +
849 + if ( $wp_object instanceof \WP_Post ) {
850 + return \get_post_meta( $wp_object->ID, $meta_key, true );
851 + } elseif ( $wp_object instanceof \WP_Comment ) {
852 + return \get_comment_meta( $wp_object->comment_ID, $meta_key, true );
853 + } else {
854 + return \apply_filters( 'activitypub_get_wp_object_state', false, $wp_object );
855 + }
856 +}
857 +
858 +/**
859 + * Get the description of a post type.
860 + *
861 + * Set some default descriptions for the default post types.
862 + *
863 + * @param WP_Post_Type $post_type The post type object.
864 + *
865 + * @return string The description of the post type.
866 + */
867 +function get_post_type_description( $post_type ) {
868 + $description = '';
869 +
870 + switch ( $post_type->name ) {
871 + case 'post':
872 + $description = '';
873 + break;
874 + case 'page':
875 + $description = '';
876 + break;
877 + case 'attachment':
878 + $description = ' - ' . __( 'The attachments that you have uploaded to a post (images, videos, documents or other files).', 'activitypub' );
879 + break;
880 + default:
881 + if ( ! empty( $post_type->description ) ) {
882 + $description = ' - ' . $post_type->description;
883 + }
884 + }
885 +
886 + return apply_filters( 'activitypub_post_type_description', $description, $post_type->name, $post_type );
887 +}
888 +
889 +/**
890 + * Get the masked WordPress version to only show the major and minor version.
891 + *
892 + * @return string The masked version.
893 + */
894 +function get_masked_wp_version() {
895 + // only show the major and minor version
896 + $version = get_bloginfo( 'version' );
897 + // strip the RC or beta part
898 + $version = preg_replace( '/-.*$/', '', $version );
899 + $version = explode( '.', $version );
900 + $version = array_slice( $version, 0, 2 );
901 +
902 + return implode( '.', $version );
903 +}
904 +
905 +/**
906 + * Get the enclosures of a post.
907 + *
908 + * @param int $post_id The post ID.
909 + *
910 + * @return array The enclosures.
911 + */
912 +function get_enclosures( $post_id ) {
913 + $enclosures = get_post_meta( $post_id, 'enclosure' );
914 +
915 + if ( ! $enclosures ) {
916 + return array();
917 + }
918 +
919 + $enclosures = array_map(
920 + function ( $enclosure ) {
921 + $attributes = explode( "\n", $enclosure );
922 +
923 + if ( ! isset( $attributes[0] ) || ! \wp_http_validate_url( $attributes[0] ) ) {
924 + return false;
925 + }
926 +
927 + return array(
928 + 'url' => $attributes[0],
929 + 'length' => isset( $attributes[1] ) ? trim( $attributes[1] ) : null,
930 + 'mediaType' => isset( $attributes[2] ) ? trim( $attributes[2] ) : null,
931 + );
932 + },
933 + $enclosures
934 + );
935 +
936 + return array_filter( $enclosures );
937 +}
938 +
939 +/**
940 + * Retrieves the IDs of the ancestors of a comment.
941 + *
942 + * Adaption of `get_post_ancestors` from WordPress core.
943 + *
944 + * @see https://developer.wordpress.org/reference/functions/get_post_ancestors/
945 + *
946 + * @param int|WP_Comment $comment Comment ID or comment object.
947 + *
948 + * @return WP_Comment[] Array of ancestor comments or empty array if there are none.
949 + */
950 +function get_comment_ancestors( $comment ) {
951 + $comment = \get_comment( $comment );
952 +
953 + // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
954 + if ( ! $comment || empty( $comment->comment_parent ) || $comment->comment_parent == $comment->comment_ID ) {
955 + return array();
956 + }
957 +
958 + $ancestors = array();
959 +
960 + $id = (int) $comment->comment_parent;
961 + $ancestors[] = $id;
962 +
963 + // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
964 + while ( $id > 0 ) {
965 + $ancestor = \get_comment( $id );
966 + $parent_id = (int) $ancestor->comment_parent;
967 +
968 + // Loop detection: If the ancestor has been seen before, break.
969 + if ( empty( $parent_id ) || ( $parent_id === (int) $comment->comment_ID ) || in_array( $parent_id, $ancestors, true ) ) {
970 + break;
971 + }
972 +
973 + $id = $parent_id;
974 + $ancestors[] = $id;
975 + }
976 +
977 + return $ancestors;
978 +}
979 +
980 +/**
981 + * Change the display of large numbers on the site.
982 + *
983 + * @author Jeremy Herve
984 + *
985 + * @see https://wordpress.org/support/topic/abbreviate-numbers-with-k/
986 + *
987 + * @param string $formatted Converted number in string format.
988 + * @param float $number The number to convert based on locale.
989 + * @param int $decimals Precision of the number of decimal places.
990 + *
991 + * @return string Converted number in string format.
992 + */
993 +function custom_large_numbers( $formatted, $number, $decimals ) {
994 + global $wp_locale;
995 +
996 + $decimals = 0;
997 + $decimal_point = '.';
998 + $thousands_sep = ',';
999 +
1000 + if ( isset( $wp_locale ) ) {
1001 + $decimals = (int) $wp_locale->number_format['decimal_point'];
1002 + $decimal_point = $wp_locale->number_format['decimal_point'];
1003 + $thousands_sep = $wp_locale->number_format['thousands_sep'];
1004 + }
1005 +
1006 + if ( $number < 1000 ) { // any number less than a Thousand.
1007 + return \number_format( $number, $decimals, $decimal_point, $thousands_sep );
1008 + } elseif ( $number < 1000000 ) { // any number less than a million
1009 + return \number_format( $number / 1000, $decimals, $decimal_point, $thousands_sep ) . 'K';
1010 + } elseif ( $number < 1000000000 ) { // any number less than a billion
1011 + return \number_format( $number / 1000000, $decimals, $decimal_point, $thousands_sep ) . 'M';
1012 + } else { // at least a billion
1013 + return \number_format( $number / 1000000000, $decimals, $decimal_point, $thousands_sep ) . 'B';
1014 + }
1015 +
1016 + // Default fallback. We should not get here.
1017 + return $formatted;
1018 +}
1019 +
1020 +/**
1021 + * Registers a ActivityPub comment type.
1022 + *
1023 + *
1024 + * @param string $comment_type Key for comment type.
1025 + * @param array $args Arguments.
1026 + *
1027 + * @return array The registered Activitypub comment type.
1028 + */
1029 +function register_comment_type( $comment_type, $args = array() ) {
1030 + global $activitypub_comment_types;
1031 +
1032 + if ( ! is_array( $activitypub_comment_types ) ) {
1033 + $activitypub_comment_types = array();
1034 + }
1035 +
1036 + // Sanitize comment type name.
1037 + $comment_type = sanitize_key( $comment_type );
1038 +
1039 + $activitypub_comment_types[ $comment_type ] = $args;
1040 +
1041 + /**
1042 + * Fires after a ActivityPub comment type is registered.
1043 + *
1044 + *
1045 + * @param string $comment_type Comment type.
1046 + * @param array $args Arguments used to register the comment type.
1047 + */
1048 + do_action( 'activitypub_registered_comment_type', $comment_type, $args );
1049 +
1050 + return $args;
1051 +}
1052 +
1053 +/**
1054 + * Normalize a URL.
1055 + *
1056 + * @param string $url The URL.
1057 + *
1058 + * @return string The normalized URL.
1059 + */
1060 +function normalize_url( $url ) {
1061 + $url = \untrailingslashit( $url );
1062 + $url = \str_replace( 'https://', '', $url );
1063 + $url = \str_replace( 'http://', '', $url );
1064 + $url = \str_replace( 'www.', '', $url );
1065 +
1066 + return $url;
1067 +}
1068 +
1069 +/**
1070 + * Normalize a host.
1071 + *
1072 + * @param string $host The host.
1073 + *
1074 + * @return string The normalized host.
1075 + */
1076 +function normalize_host( $host ) {
1077 + return \str_replace( 'www.', '', $host );
1078 +}
1079 +
1080 +/**
1081 + * Get the reply intent URI.
1082 + *
1083 + * @return string The reply intent URI.
1084 + */
1085 +function get_reply_intent_uri() {
1086 + return sprintf(
1087 + 'javascript:(()=>{window.open(\'%s\'+encodeURIComponent(window.location.href));})();',
1088 + esc_url( \admin_url( 'post-new.php?in_reply_to=' ) )
1089 + );
1090 +}
1091 +
1092 +/**
1093 + * Replace content with links, mentions or hashtags by Regex callback and not affect protected tags.
1094 + *
1095 + * @param $content string The content that should be changed
1096 + * @param $regex string The regex to use
1097 + * @param $regex_callback callable Callback for replacement logic
1098 + *
1099 + * @return string The content with links, mentions, hashtags, etc.
1100 + */
1101 +function enrich_content_data( $content, $regex, $regex_callback ) {
1102 + // small protection against execution timeouts: limit to 1 MB
1103 + if ( mb_strlen( $content ) > MB_IN_BYTES ) {
1104 + return $content;
1105 + }
1106 + $tag_stack = array();
1107 + $protected_tags = array(
1108 + 'pre',
1109 + 'code',
1110 + 'textarea',
1111 + 'style',
1112 + 'a',
1113 + );
1114 + $content_with_links = '';
1115 + $in_protected_tag = false;
1116 + foreach ( wp_html_split( $content ) as $chunk ) {
1117 + if ( preg_match( '#^<!--[\s\S]*-->$#i', $chunk, $m ) ) {
1118 + $content_with_links .= $chunk;
1119 + continue;
1120 + }
1121 +
1122 + if ( preg_match( '#^<(/)?([a-z-]+)\b[^>]*>$#i', $chunk, $m ) ) {
1123 + $tag = strtolower( $m[2] );
1124 + if ( '/' === $m[1] ) {
1125 + // Closing tag.
1126 + $i = array_search( $tag, $tag_stack, true );
1127 + // We can only remove the tag from the stack if it is in the stack.
1128 + if ( false !== $i ) {
1129 + $tag_stack = array_slice( $tag_stack, 0, $i );
1130 + }
1131 + } else {
1132 + // Opening tag, add it to the stack.
1133 + $tag_stack[] = $tag;
1134 + }
1135 +
1136 + // If we're in a protected tag, the tag_stack contains at least one protected tag string.
1137 + // The protected tag state can only change when we encounter a start or end tag.
1138 + $in_protected_tag = array_intersect( $tag_stack, $protected_tags );
1139 +
1140 + // Never inspect tags.
1141 + $content_with_links .= $chunk;
1142 + continue;
1143 + }
1144 +
1145 + if ( $in_protected_tag ) {
1146 + // Don't inspect a chunk inside an inspected tag.
1147 + $content_with_links .= $chunk;
1148 + continue;
1149 + }
1150 +
1151 + // Only reachable when there is no protected tag in the stack.
1152 + $content_with_links .= \preg_replace_callback( $regex, $regex_callback, $chunk );
1153 + }
1154 +
1155 + return $content_with_links;
1156 +}
1157 +
1158 +/**
1159 + * Generate a summary of a post.
1160 + *
1161 + * This function generates a summary of a post by extracting:
1162 + *
1163 + * 1. The post excerpt if it exists.
1164 + * 2. The first part of the post content if it contains the <!--more--> tag.
1165 + * 3. An excerpt of the post content if it is longer than the specified length.
1166 + *
1167 + * @param int|WP_Post $post The post ID or post object.
1168 + * @param integer $length The maximum length of the summary.
1169 + * Default is 500. It will ne ignored if the post excerpt
1170 + * and the content above the <!--more--> tag.
1171 + *
1172 + * @return string The generated post summary.
1173 + */
1174 +function generate_post_summary( $post, $length = 500 ) {
1175 + $post = get_post( $post );
1176 +
1177 + if ( ! $post ) {
1178 + return '';
1179 + }
1180 +
1181 + $content = \sanitize_post_field( 'post_excerpt', $post->post_excerpt, $post->ID );
1182 +
1183 + if ( $content ) {
1184 + return \apply_filters( 'the_excerpt', $content );
1185 + }
1186 +
1187 + $content = \sanitize_post_field( 'post_content', $post->post_content, $post->ID );
1188 + $content_parts = \get_extended( $content );
1189 +
1190 + $excerpt_more = \apply_filters( 'activitypub_excerpt_more', '[…]' );
1191 + $length = $length - strlen( $excerpt_more );
1192 +
1193 + // Check for the <!--more--> tag.
1194 + if (
1195 + ! empty( $content_parts['extended'] ) &&
1196 + ! empty( $content_parts['main'] )
1197 + ) {
1198 + $content = $content_parts['main'] . ' ' . $excerpt_more;
1199 + $length = null;
1200 + }
1201 +
1202 + $content = \html_entity_decode( $content );
1203 + $content = \wp_strip_all_tags( $content );
1204 + $content = \trim( $content );
1205 + $content = \preg_replace( '/\R+/m', "\n\n", $content );
1206 + $content = \preg_replace( '/[\r\t]/', '', $content );
1207 +
1208 + if ( $length && \strlen( $content ) > $length ) {
1209 + $content = \wordwrap( $content, $length, '</activitypub-summary>' );
1210 + $content = \explode( '</activitypub-summary>', $content, 2 );
1211 + $content = $content[0] . ' ' . $excerpt_more;
1212 + }
1213 +
1214 + return \apply_filters( 'the_excerpt', $content );
776 1215 }