PluginProbe
ActivityPub / 4.1.1
ActivityPub v4.1.1
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 4.1.1, at includes/collection/class-interactions.php

323 lines 8.7 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 WP_Comment_Query;
11 use Activitypub\Comment;
12
13 use function Activitypub\object_to_uri;
14 use function Activitypub\url_to_commentid;
15 use function Activitypub\object_id_to_comment;
16 use function Activitypub\get_remote_metadata_by_actor;
17
18 /**
19 * ActivityPub Interactions Collection.
20 */
21 class Interactions {
22 const INSERT = 'insert';
23 const UPDATE = 'update';
24
25 /**
26 * Add a comment to a post.
27 *
28 * @param array $activity The activity-object.
29 *
30 * @return array|false The comment data or false on failure.
31 */
32 public static function add_comment( $activity ) {
33 $commentdata = self::activity_to_comment( $activity );
34
35 if ( ! $commentdata || ! isset( $activity['object']['inReplyTo'] ) ) {
36 return false;
37 }
38
39 $in_reply_to = object_to_uri( $activity['object']['inReplyTo'] );
40 $in_reply_to = \esc_url_raw( $in_reply_to );
41 $comment_post_id = \url_to_postid( $in_reply_to );
42 $parent_comment_id = url_to_commentid( $in_reply_to );
43
44 // Save only replies and reactions.
45 if ( ! $comment_post_id && $parent_comment_id ) {
46 $parent_comment = get_comment( $parent_comment_id );
47 $comment_post_id = $parent_comment->comment_post_ID;
48 }
49
50 // Not a reply to a post or comment.
51 if ( ! $comment_post_id ) {
52 return false;
53 }
54
55 $commentdata['comment_post_ID'] = $comment_post_id;
56 $commentdata['comment_parent'] = $parent_comment_id ? $parent_comment_id : 0;
57
58 return self::persist( $commentdata, self::INSERT );
59 }
60
61 /**
62 * Update a comment.
63 *
64 * @param array $activity The activity object.
65 *
66 * @return array|string|int|\WP_Error|false The comment data or false on failure.
67 */
68 public static function update_comment( $activity ) {
69 $meta = get_remote_metadata_by_actor( $activity['actor'] );
70
71 // Determine comment_ID.
72 $comment = object_id_to_comment( \esc_url_raw( $activity['object']['id'] ) );
73 $commentdata = \get_comment( $comment, ARRAY_A );
74
75 if ( ! $commentdata ) {
76 return false;
77 }
78
79 // Found a local comment id.
80 $commentdata['comment_author'] = \esc_attr( $meta['name'] ? $meta['name'] : $meta['preferredUsername'] );
81 $commentdata['comment_content'] = \addslashes( $activity['object']['content'] );
82
83 return self::persist( $commentdata, self::UPDATE );
84 }
85
86 /**
87 * Adds an incoming Like, Announce, ... as a comment to a post.
88 *
89 * @param array $activity Activity array.
90 *
91 * @return array|false Comment data or `false` on failure.
92 */
93 public static function add_reaction( $activity ) {
94 $commentdata = self::activity_to_comment( $activity );
95
96 if ( ! $commentdata ) {
97 return false;
98 }
99
100 $url = object_to_uri( $activity['object'] );
101 $comment_post_id = url_to_postid( $url );
102 $parent_comment_id = url_to_commentid( $url );
103
104 if ( ! $comment_post_id && $parent_comment_id ) {
105 $parent_comment = get_comment( $parent_comment_id );
106 $comment_post_id = $parent_comment->comment_post_ID;
107 }
108
109 if ( ! $comment_post_id ) {
110 // Not a reply to a post or comment.
111 return false;
112 }
113
114 $type = $activity['type'];
115
116 if ( ! Comment::is_registered_comment_type( $type ) ) {
117 // Not a valid comment type.
118 return false;
119 }
120
121 $comment_type = Comment::get_comment_type( $type );
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 'compare' => '=',
191 ),
192 ),
193 );
194 $comment_query = new WP_Comment_Query( $args );
195 return $comment_query->comments;
196 }
197
198 /**
199 * Adds line breaks to the list of allowed comment tags.
200 *
201 * @param array $allowed_tags Allowed HTML tags.
202 * @param string $context Optional. Context. Default empty.
203 *
204 * @return array Filtered tag list.
205 */
206 public static function allowed_comment_html( $allowed_tags, $context = '' ) {
207 if ( 'pre_comment_content' !== $context ) {
208 // Do nothing.
209 return $allowed_tags;
210 }
211
212 // Add `p` and `br` to the list of allowed tags.
213 if ( ! array_key_exists( 'br', $allowed_tags ) ) {
214 $allowed_tags['br'] = array();
215 }
216
217 if ( ! array_key_exists( 'p', $allowed_tags ) ) {
218 $allowed_tags['p'] = array();
219 }
220
221 return $allowed_tags;
222 }
223
224 /**
225 * Convert an Activity to a WP_Comment
226 *
227 * @param array $activity The Activity array.
228 *
229 * @return array|false The comment data or false on failure.
230 */
231 public static function activity_to_comment( $activity ) {
232 $comment_content = null;
233 $actor = object_to_uri( $activity['actor'] );
234 $actor = get_remote_metadata_by_actor( $actor );
235
236 // Check Actor-Meta.
237 if ( ! $actor || is_wp_error( $actor ) ) {
238 return false;
239 }
240
241 // Check Actor-Name.
242 if ( isset( $actor['name'] ) ) {
243 $comment_author = $actor['name'];
244 } elseif ( isset( $actor['preferredUsername'] ) ) {
245 $comment_author = $actor['preferredUsername'];
246 } else {
247 return false;
248 }
249
250 $url = object_to_uri( $actor['url'] );
251
252 if ( ! $url ) {
253 object_to_uri( $actor['id'] );
254 }
255
256 if ( isset( $activity['object']['content'] ) ) {
257 $comment_content = \addslashes( $activity['object']['content'] );
258 }
259
260 $commentdata = array(
261 'comment_author' => \esc_attr( $comment_author ),
262 'comment_author_url' => \esc_url_raw( $url ),
263 'comment_content' => $comment_content,
264 'comment_type' => 'comment',
265 'comment_author_email' => '',
266 'comment_meta' => array(
267 'source_id' => \esc_url_raw( object_to_uri( $activity['object'] ) ),
268 'protocol' => 'activitypub',
269 ),
270 );
271
272 if ( isset( $actor['icon']['url'] ) ) {
273 $commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $actor['icon']['url'] );
274 }
275
276 if ( isset( $activity['object']['url'] ) ) {
277 $commentdata['comment_meta']['source_url'] = \esc_url_raw( object_to_uri( $activity['object']['url'] ) );
278 }
279
280 return $commentdata;
281 }
282
283 /**
284 * Persist a comment.
285 *
286 * @param array $commentdata The commentdata array.
287 * @param string $action Optional. Either 'insert' or 'update'. Default 'insert'.
288 *
289 * @return array|string|int|\WP_Error|false The comment data or false on failure
290 */
291 public static function persist( $commentdata, $action = self::INSERT ) {
292 // Disable flood control.
293 \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
294 // Do not require email for AP entries.
295 \add_filter( 'pre_option_require_name_email', '__return_false' );
296 // No nonce possible for this submission route.
297 \add_filter(
298 'akismet_comment_nonce',
299 function () {
300 return 'inactive';
301 }
302 );
303 \add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
304
305 if ( self::INSERT === $action ) {
306 $state = \wp_new_comment( $commentdata, true );
307 } else {
308 $state = \wp_update_comment( $commentdata, true );
309 }
310
311 \remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10 );
312 \remove_filter( 'pre_option_require_name_email', '__return_false' );
313 // Restore flood control.
314 \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
315
316 if ( 1 === $state ) {
317 return $commentdata;
318 } else {
319 return $state; // Either WP_Comment, false, a WP_Error, 0, or 1!
320 }
321 }
322 }
323