PluginProbe
ActivityPub / 3.2.4
ActivityPub v3.2.4
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 3.2.4, at includes/collection/class-interactions.php

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