PluginProbe
ActivityPub / 2.0.0
ActivityPub v2.0.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 / class-mention.php

class-mention.php in ActivityPub 2.0.0, at includes/class-mention.php

175 lines 4.7 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\Webfinger;
6
7 /**
8 * ActivityPub Mention Class
9 *
10 * @author Alex Kirk
11 */
12 class Mention {
13 /**
14 * Initialize the class, registering WordPress hooks
15 */
16 public static function init() {
17 \add_filter( 'the_content', array( self::class, 'the_content' ), 99, 1 );
18 \add_filter( 'comment_text', array( self::class, 'the_content' ), 10, 1 );
19 \add_filter( 'activitypub_extract_mentions', array( self::class, 'extract_mentions' ), 99, 2 );
20 }
21
22 /**
23 * Filter to replace the mentions in the content with links
24 *
25 * @param string $the_content the post-content
26 *
27 * @return string the filtered post-content
28 */
29 public static function the_content( $the_content ) {
30 // small protection against execution timeouts: limit to 1 MB
31 if ( mb_strlen( $the_content ) > MB_IN_BYTES ) {
32 return $the_content;
33 }
34 $tag_stack = array();
35 $protected_tags = array(
36 'pre',
37 'code',
38 'textarea',
39 'style',
40 'a',
41 );
42 $content_with_links = '';
43 $in_protected_tag = false;
44 foreach ( wp_html_split( $the_content ) as $chunk ) {
45 if ( preg_match( '#^<!--[\s\S]*-->$#i', $chunk, $m ) ) {
46 $content_with_links .= $chunk;
47 continue;
48 }
49
50 if ( preg_match( '#^<(/)?([a-z-]+)\b[^>]*>$#i', $chunk, $m ) ) {
51 $tag = strtolower( $m[2] );
52 if ( '/' === $m[1] ) {
53 // Closing tag.
54 $i = array_search( $tag, $tag_stack );
55 // We can only remove the tag from the stack if it is in the stack.
56 if ( false !== $i ) {
57 $tag_stack = array_slice( $tag_stack, 0, $i );
58 }
59 } else {
60 // Opening tag, add it to the stack.
61 $tag_stack[] = $tag;
62 }
63
64 // If we're in a protected tag, the tag_stack contains at least one protected tag string.
65 // The protected tag state can only change when we encounter a start or end tag.
66 $in_protected_tag = array_intersect( $tag_stack, $protected_tags );
67
68 // Never inspect tags.
69 $content_with_links .= $chunk;
70 continue;
71 }
72
73 if ( $in_protected_tag ) {
74 // Don't inspect a chunk inside an inspected tag.
75 $content_with_links .= $chunk;
76 continue;
77 }
78
79 // Only reachable when there is no protected tag in the stack.
80 $content_with_links .= \preg_replace_callback( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/', array( self::class, 'replace_with_links' ), $chunk );
81 }
82
83 return $content_with_links;
84 }
85
86 /**
87 * A callback for preg_replace to build the user links
88 *
89 * @param array $result the preg_match results
90 *
91 * @return string the final string
92 */
93 public static function replace_with_links( $result ) {
94 $metadata = get_remote_metadata_by_actor( $result[0] );
95
96 if ( ! empty( $metadata ) && ! is_wp_error( $metadata ) && ! empty( $metadata['url'] ) ) {
97 $username = ltrim( $result[0], '@' );
98 if ( ! empty( $metadata['name'] ) ) {
99 $username = $metadata['name'];
100 }
101 if ( ! empty( $metadata['preferredUsername'] ) ) {
102 $username = $metadata['preferredUsername'];
103 }
104 return \sprintf( '<a rel="mention" class="u-url mention" href="%s">@<span>%s</span></a>', esc_url( $metadata['url'] ), esc_html( $username ) );
105 }
106
107 return $result[0];
108 }
109
110 /**
111 * Get the Inboxes for the mentioned Actors
112 *
113 * @param array $mentioned The list of Actors that were mentioned
114 *
115 * @return array The list of Inboxes
116 */
117 public static function get_inboxes( $mentioned ) {
118 $inboxes = array();
119
120 foreach ( $mentioned as $actor ) {
121 $inbox = self::get_inbox_by_mentioned_actor( $actor );
122
123 if ( ! is_wp_error( $inbox ) && $inbox ) {
124 $inboxes[] = $inbox;
125 }
126 }
127
128 return $inboxes;
129 }
130
131 /**
132 * Get the inbox from the Remote-Profile of a mentioned Actor
133 *
134 * @param string $actor The Actor-URL
135 *
136 * @return string The Inbox-URL
137 */
138 public static function get_inbox_by_mentioned_actor( $actor ) {
139 $metadata = get_remote_metadata_by_actor( $actor );
140
141 if ( \is_wp_error( $metadata ) ) {
142 return $metadata;
143 }
144
145 if ( isset( $metadata['endpoints'] ) && isset( $metadata['endpoints']['sharedInbox'] ) ) {
146 return $metadata['endpoints']['sharedInbox'];
147 }
148
149 if ( \array_key_exists( 'inbox', $metadata ) ) {
150 return $metadata['inbox'];
151 }
152
153 return new WP_Error( 'activitypub_no_inbox', \__( 'No "Inbox" found', 'activitypub' ), $metadata );
154 }
155
156 /**
157 * Extract the mentions from the post_content.
158 *
159 * @param array $mentions The already found mentions.
160 * @param string $post_content The post content.
161 *
162 * @return mixed The discovered mentions.
163 */
164 public static function extract_mentions( $mentions, $post_content ) {
165 \preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post_content, $matches );
166 foreach ( $matches[0] as $match ) {
167 $link = Webfinger::resolve( $match );
168 if ( ! is_wp_error( $link ) ) {
169 $mentions[ $match ] = $link;
170 }
171 }
172 return $mentions;
173 }
174 }
175