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 / class-mention.php

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

182 lines 4.8 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
105 $url = isset( $metadata['url'] ) ? $metadata['url'] : $metadata['id'];
106
107 if ( \is_array( $url ) ) {
108 $url = $url[0];
109 }
110
111 return \sprintf( '<a rel="mention" class="u-url mention" href="%s">@<span>%s</span></a>', esc_url( $url ), esc_html( $username ) );
112 }
113
114 return $result[0];
115 }
116
117 /**
118 * Get the Inboxes for the mentioned Actors
119 *
120 * @param array $mentioned The list of Actors that were mentioned
121 *
122 * @return array The list of Inboxes
123 */
124 public static function get_inboxes( $mentioned ) {
125 $inboxes = array();
126
127 foreach ( $mentioned as $actor ) {
128 $inbox = self::get_inbox_by_mentioned_actor( $actor );
129
130 if ( ! is_wp_error( $inbox ) && $inbox ) {
131 $inboxes[] = $inbox;
132 }
133 }
134
135 return $inboxes;
136 }
137
138 /**
139 * Get the inbox from the Remote-Profile of a mentioned Actor
140 *
141 * @param string $actor The Actor-URL
142 *
143 * @return string The Inbox-URL
144 */
145 public static function get_inbox_by_mentioned_actor( $actor ) {
146 $metadata = get_remote_metadata_by_actor( $actor );
147
148 if ( \is_wp_error( $metadata ) ) {
149 return $metadata;
150 }
151
152 if ( isset( $metadata['endpoints'] ) && isset( $metadata['endpoints']['sharedInbox'] ) ) {
153 return $metadata['endpoints']['sharedInbox'];
154 }
155
156 if ( \array_key_exists( 'inbox', $metadata ) ) {
157 return $metadata['inbox'];
158 }
159
160 return new WP_Error( 'activitypub_no_inbox', \__( 'No "Inbox" found', 'activitypub' ), $metadata );
161 }
162
163 /**
164 * Extract the mentions from the post_content.
165 *
166 * @param array $mentions The already found mentions.
167 * @param string $post_content The post content.
168 *
169 * @return mixed The discovered mentions.
170 */
171 public static function extract_mentions( $mentions, $post_content ) {
172 \preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post_content, $matches );
173 foreach ( $matches[0] as $match ) {
174 $link = Webfinger::resolve( $match );
175 if ( ! is_wp_error( $link ) ) {
176 $mentions[ $match ] = $link;
177 }
178 }
179 return $mentions;
180 }
181 }
182