PluginProbe
ActivityPub / 1.3.0
ActivityPub v1.3.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 1.3.0, at includes/class-mention.php

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