PluginProbe
ActivityPub / 5.7.0
ActivityPub v5.7.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 / collection / class-interactions.php

class-interactions.php in ActivityPub 5.7.0, at includes/collection/class-interactions.php

350 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Interactions collection file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Collection;
9
10 use Activitypub\Webfinger;
11 use WP_Comment_Query;
12 use Activitypub\Comment;
13
14 use function Activitypub\object_to_uri;
15 use function Activitypub\is_post_disabled;
16 use function Activitypub\url_to_commentid;
17 use function Activitypub\object_id_to_comment;
18 use function Activitypub\get_remote_metadata_by_actor;
19
20 /**
21 * ActivityPub Interactions Collection.
22 */
23 class Interactions {
24 const INSERT = 'insert';
25 const UPDATE = 'update';
26
27 /**
28 * Add a comment to a post.
29 *
30 * @param array $activity The activity-object.
31 *
32 * @return int|false|\WP_Error The comment ID or false or WP_Error on failure.
33 */
34 public static function add_comment( $activity ) {
35 $commentdata = self::activity_to_comment( $activity );
36
37 if ( ! $commentdata || ! isset( $activity['object']['inReplyTo'] ) ) {
38 return false;
39 }
40
41 $in_reply_to = object_to_uri( $activity['object']['inReplyTo'] );
42 $in_reply_to = \esc_url_raw( $in_reply_to );
43 $comment_post_id = \url_to_postid( $in_reply_to );
44 $parent_comment_id = url_to_commentid( $in_reply_to );
45
46 // Save only replies and reactions.
47 if ( ! $comment_post_id && $parent_comment_id ) {
48 $parent_comment = get_comment( $parent_comment_id );
49 $comment_post_id = $parent_comment->comment_post_ID;
50 }
51
52 if ( is_post_disabled( $comment_post_id ) ) {
53 return false;
54 }
55
56 $commentdata['comment_post_ID'] = $comment_post_id;
57 $commentdata['comment_parent'] = $parent_comment_id ? $parent_comment_id : 0;
58
59 return self::persist( $commentdata, self::INSERT );
60 }
61
62 /**
63 * Update a comment.
64 *
65 * @param array $activity The activity object.
66 *
67 * @return array|string|int|\WP_Error|false The comment data or false on failure.
68 */
69 public static function update_comment( $activity ) {
70 $meta = get_remote_metadata_by_actor( $activity['actor'] );
71
72 // Determine comment_ID.
73 $comment = object_id_to_comment( \esc_url_raw( $activity['object']['id'] ) );
74 $commentdata = \get_comment( $comment, ARRAY_A );
75
76 if ( ! $commentdata ) {
77 return false;
78 }
79
80 // Found a local comment id.
81 $commentdata['comment_author'] = \esc_attr( $meta['name'] ? $meta['name'] : $meta['preferredUsername'] );
82 $commentdata['comment_content'] = \addslashes( $activity['object']['content'] );
83
84 return self::persist( $commentdata, self::UPDATE );
85 }
86
87 /**
88 * Adds an incoming Like, Announce, ... as a comment to a post.
89 *
90 * @param array $activity Activity array.
91 *
92 * @return array|false Comment data or `false` on failure.
93 */
94 public static function add_reaction( $activity ) {
95 $commentdata = self::activity_to_comment( $activity );
96
97 if ( ! $commentdata ) {
98 return false;
99 }
100
101 $url = object_to_uri( $activity['object'] );
102 $comment_post_id = \url_to_postid( $url );
103 $parent_comment_id = url_to_commentid( $url );
104
105 if ( ! $comment_post_id && $parent_comment_id ) {
106 $parent_comment = \get_comment( $parent_comment_id );
107 $comment_post_id = $parent_comment->comment_post_ID;
108 }
109
110 if ( ! $comment_post_id || is_post_disabled( $comment_post_id ) ) {
111 // Not a reply to a post or comment.
112 return false;
113 }
114
115 $comment_type = Comment::get_comment_type_by_activity_type( $activity['type'] );
116
117 if ( ! $comment_type ) {
118 // Not a valid comment type.
119 return false;
120 }
121
122 $comment_content = $comment_type['excerpt'];
123
124 $commentdata['comment_post_ID'] = $comment_post_id;
125 $commentdata['comment_content'] = \esc_html( $comment_content );
126 $commentdata['comment_type'] = \esc_attr( $comment_type['type'] );
127 $commentdata['comment_meta']['source_id'] = \esc_url_raw( $activity['id'] );
128
129 return self::persist( $commentdata, self::INSERT );
130 }
131
132 /**
133 * Get interaction(s) for a given URL/ID.
134 *
135 * @param string $url The URL/ID to get interactions for.
136 *
137 * @return array The interactions as WP_Comment objects.
138 */
139 public static function get_interaction_by_id( $url ) {
140 $args = array(
141 'nopaging' => true,
142 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
143 'meta_query' => array(
144 'relation' => 'AND',
145 array(
146 'key' => 'protocol',
147 'value' => 'activitypub',
148 ),
149 array(
150 'relation' => 'OR',
151 array(
152 'key' => 'source_url',
153 'value' => $url,
154 ),
155 array(
156 'key' => 'source_id',
157 'value' => $url,
158 ),
159 ),
160 ),
161 );
162
163 $query = new WP_Comment_Query( $args );
164 return $query->comments;
165 }
166
167 /**
168 * Get interaction(s) for a given actor.
169 *
170 * @param string $actor The Actor-URL.
171 *
172 * @return array The interactions as WP_Comment objects.
173 */
174 public static function get_interactions_by_actor( $actor ) {
175 $meta = get_remote_metadata_by_actor( $actor );
176
177 // Get URL, because $actor seems to be the ID.
178 if ( $meta && ! is_wp_error( $meta ) && isset( $meta['url'] ) ) {
179 $actor = object_to_uri( $meta['url'] );
180 }
181
182 $args = array(
183 'nopaging' => true,
184 'author_url' => $actor,
185 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
186 'meta_query' => array(
187 array(
188 'key' => 'protocol',
189 'value' => 'activitypub',
190 ),
191 ),
192 );
193
194 return get_comments( $args );
195 }
196
197 /**
198 * Adds line breaks to the list of allowed comment tags.
199 *
200 * @param array $allowed_tags Allowed HTML tags.
201 * @param string $context Optional. Context. Default empty.
202 *
203 * @return array Filtered tag list.
204 */
205 public static function allowed_comment_html( $allowed_tags, $context = '' ) {
206 if ( 'pre_comment_content' !== $context ) {
207 // Do nothing.
208 return $allowed_tags;
209 }
210
211 // Add `p` and `br` to the list of allowed tags.
212 if ( ! array_key_exists( 'br', $allowed_tags ) ) {
213 $allowed_tags['br'] = array();
214 }
215
216 if ( ! array_key_exists( 'p', $allowed_tags ) ) {
217 $allowed_tags['p'] = array();
218 }
219
220 return $allowed_tags;
221 }
222
223 /**
224 * Convert an Activity to a WP_Comment
225 *
226 * @param array $activity The Activity array.
227 *
228 * @return array|false The comment data or false on failure.
229 */
230 public static function activity_to_comment( $activity ) {
231 $comment_content = null;
232 $actor = object_to_uri( $activity['actor'] ?? null );
233 $actor = get_remote_metadata_by_actor( $actor );
234
235 // Check Actor-Meta.
236 if ( ! $actor || is_wp_error( $actor ) ) {
237 return false;
238 }
239
240 // Check Actor-Name.
241 if ( isset( $actor['name'] ) ) {
242 $comment_author = $actor['name'];
243 } elseif ( isset( $actor['preferredUsername'] ) ) {
244 $comment_author = $actor['preferredUsername'];
245 } else {
246 return false;
247 }
248
249 $url = object_to_uri( $actor['url'] ?? $actor['id'] );
250
251 if ( ! $url ) {
252 $url = object_to_uri( $actor['id'] );
253 }
254
255 if ( isset( $activity['object']['content'] ) ) {
256 $comment_content = \addslashes( $activity['object']['content'] );
257 }
258
259 $webfinger = Webfinger::uri_to_acct( $url );
260 if ( is_wp_error( $webfinger ) ) {
261 $webfinger = '';
262 } else {
263 $webfinger = str_replace( 'acct:', '', $webfinger );
264 }
265
266 $commentdata = array(
267 'comment_author' => \esc_attr( $comment_author ),
268 'comment_author_url' => \esc_url_raw( $url ),
269 'comment_content' => $comment_content,
270 'comment_type' => 'comment',
271 'comment_author_email' => $webfinger,
272 'comment_meta' => array(
273 'source_id' => \esc_url_raw( object_to_uri( $activity['object'] ) ),
274 'protocol' => 'activitypub',
275 ),
276 );
277
278 if ( isset( $actor['icon']['url'] ) ) {
279 $commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $actor['icon']['url'] );
280 }
281
282 if ( isset( $activity['object']['url'] ) ) {
283 $commentdata['comment_meta']['source_url'] = \esc_url_raw( object_to_uri( $activity['object']['url'] ) );
284 }
285
286 return $commentdata;
287 }
288
289 /**
290 * Persist a comment.
291 *
292 * @param array $commentdata The commentdata array.
293 * @param string $action Optional. Either 'insert' or 'update'. Default 'insert'.
294 *
295 * @return array|string|int|\WP_Error|false The comment data or false on failure
296 */
297 public static function persist( $commentdata, $action = self::INSERT ) {
298 // Disable flood control.
299 \remove_action( 'check_comment_flood', 'check_comment_flood_db' );
300 // Do not require email for AP entries.
301 \add_filter( 'pre_option_require_name_email', '__return_false' );
302 // No nonce possible for this submission route.
303 \add_filter(
304 'akismet_comment_nonce',
305 function () {
306 return 'inactive';
307 }
308 );
309 \add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
310
311 if ( self::INSERT === $action ) {
312 $state = \wp_new_comment( $commentdata, true );
313 } else {
314 $state = \wp_update_comment( $commentdata, true );
315 }
316
317 \remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ) );
318 \remove_filter( 'pre_option_require_name_email', '__return_false' );
319 // Restore flood control.
320 \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
321
322 if ( 1 === $state ) {
323 return $commentdata;
324 } else {
325 return $state; // Either WP_Comment, false, a WP_Error, 0, or 1!
326 }
327 }
328
329 /**
330 * Get the total number of interactions by type for a given ID.
331 *
332 * @param int $post_id The post ID.
333 * @param string $type The type of interaction to count.
334 *
335 * @return int The total number of interactions.
336 */
337 public static function count_by_type( $post_id, $type ) {
338 return \get_comments(
339 array(
340 'post_id' => $post_id,
341 'status' => 'approve',
342 'type' => $type,
343 'count' => true,
344 'paging' => false,
345 'fields' => 'ids',
346 )
347 );
348 }
349 }
350