PluginProbe
ActivityPub / 2.5.0
ActivityPub v2.5.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.5.0, at includes/class-mention.php

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