PluginProbe
ActivityPub / 8.0.2
ActivityPub v8.0.2
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
← All changes | includes/transformer/class-comment.php +275 -131 2.0.18.0.2 View file →
@@ -1,19 +1,26 @@
1 1 <?php
2 +/**
3 + * WordPress Comment Transformer file.
4 + *
5 + * @package Activitypub
6 + */
7 +
2 8 namespace Activitypub\Transformer;
3 9
4 -use WP_Comment;
5 -use WP_Comment_Query;
10 +use Activitypub\Collection\Actors;
11 +use Activitypub\Collection\Replies;
12 +use Activitypub\Comment as Comment_Utils;
13 +use Activitypub\Model\Blog;
14 +use Activitypub\Webfinger;
6 15
7 -use Activitypub\Model\Blog_User;
8 -use Activitypub\Collection\Users;
9 -use Activitypub\Transformer\Base;
10 -
16 +use function Activitypub\get_comment_ancestors;
17 +use function Activitypub\get_rest_url_by_path;
11 18 use function Activitypub\is_single_user;
12 -use function Activitypub\get_rest_url_by_path;
19 +use function Activitypub\was_comment_received;
13 20
14 21 /**
15 - * WordPress Comment Transformer
22 + * WordPress Comment Transformer.
16 23 *
17 24 * The Comment Transformer is responsible for transforming a WP_Comment object into different
18 25 * Object-Types.
19 26 *
@@ -22,45 +29,32 @@
22 29 * - Activitypub\Activity\Base_Object
23 30 */
24 31 class Comment extends Base {
25 32 /**
26 - * Returns the User-ID of the WordPress Comment.
33 + * The User as Actor Object.
27 34 *
28 - * @return int The User-ID of the WordPress Comment
35 + * @var \Activitypub\Activity\Actor
29 36 */
30 - public function get_wp_user_id() {
31 - return $this->wp_object->user_id;
32 - }
37 + private $actor_object = null;
33 38
34 39 /**
35 - * Change the User-ID of the WordPress Comment.
40 + * Transforms the WP_Comment object to an ActivityPub Object.
36 41 *
37 - * @return int The User-ID of the WordPress Comment
42 + * @return \Activitypub\Activity\Base_Object The ActivityPub Object.
38 43 */
39 - public function change_wp_user_id( $user_id ) {
40 - $this->wp_object->user_id = $user_id;
41 - }
42 -
43 - /**
44 - * Transforms the WP_Comment object to an ActivityPub Object
45 - *
46 - * @see \Activitypub\Activity\Base_Object
47 - *
48 - * @return \Activitypub\Activity\Base_Object The ActivityPub Object
49 - */
50 44 public function to_object() {
51 - $comment = $this->wp_object;
45 + $comment = $this->item;
52 46 $object = parent::to_object();
53 47
54 - $object->set_url( \get_comment_link( $comment->comment_ID ) );
48 + $object->set_url( $this->get_id() );
55 49 $object->set_type( 'Note' );
56 50
57 51 $published = \strtotime( $comment->comment_date_gmt );
58 - $object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
52 + $object->set_published( \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, $published ) );
59 53
60 54 $updated = \get_comment_meta( $comment->comment_ID, 'activitypub_comment_modified', true );
61 55 if ( $updated > $published ) {
62 - $object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
56 + $object->set_updated( \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, $updated ) );
63 57 }
64 58
65 59 $object->set_content_map(
66 60 array(
@@ -66,18 +60,38 @@
66 60 array(
67 61 $this->get_locale() => $this->get_content(),
68 62 )
69 63 );
70 - $path = sprintf( 'users/%d/followers', intval( $comment->comment_author ) );
71 64
72 - $object->set_to(
73 - array(
74 - 'https://www.w3.org/ns/activitystreams#Public',
75 - get_rest_url_by_path( $path ),
76 - )
77 - );
65 + return $object;
66 + }
78 67
79 - return $object;
68 + /**
69 + * Get the content visibility.
70 + *
71 + * @return string The content visibility.
72 + */
73 + public function get_content_visibility() {
74 + if ( $this->content_visibility ) {
75 + return $this->content_visibility;
76 + }
77 +
78 + $comment = $this->item;
79 + $post = \get_post( $comment->comment_post_ID );
80 +
81 + if ( ! $post ) {
82 + return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC;
83 + }
84 +
85 + $content_visibility = \get_post_meta( $post->ID, 'activitypub_content_visibility', true );
86 +
87 + if ( ! $content_visibility ) {
88 + return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC;
89 + }
90 +
91 + $this->content_visibility = $content_visibility;
92 +
93 + return $this->content_visibility;
80 94 }
81 95
82 96 /**
83 97 * Returns the User-URL of the Author of the Post.
@@ -86,14 +100,14 @@
86 100 *
87 101 * @return string The User-URL.
88 102 */
89 103 protected function get_attributed_to() {
90 - if ( is_single_user() ) {
91 - $user = new Blog_User();
92 - return $user->get_url();
104 + // If the comment was received via ActivityPub, return the author URL.
105 + if ( was_comment_received( $this->item ) ) {
106 + return $this->item->comment_author_url;
93 107 }
94 108
95 - return Users::get_by_id( $this->wp_object->user_id )->get_url();
109 + return $this->get_actor_object()->get_id();
96 110 }
97 111
98 112 /**
99 113 * Returns the content for the ActivityPub Item.
@@ -102,39 +116,63 @@
102 116 *
103 117 * @return string The content.
104 118 */
105 119 protected function get_content() {
106 - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
107 - $comment = $this->wp_object;
108 - $content = $comment->comment_content;
120 + $comment = $this->item;
121 + $content = $comment->comment_content;
122 + $mentions = '';
109 123
110 - $content = \wpautop( $content );
124 + foreach ( $this->extract_reply_context() as $acct => $url ) {
125 + $mentions .= sprintf(
126 + '<a rel="mention" class="u-url mention" href="%1$s" title="%2$s">%3$s</a> ',
127 + esc_url( $url ),
128 + esc_attr( $acct ),
129 + esc_html( '@' . strtok( $acct, '@' ) )
130 + );
131 + }
132 + $content = $mentions . $content;
133 +
134 + /**
135 + * Filter the content of the comment.
136 + *
137 + * @param string $content The content of the comment.
138 + * @param \WP_Comment $comment The comment object.
139 + * @param array $args The arguments.
140 + *
141 + * @return string The filtered content of the comment.
142 + */
143 + $content = \apply_filters( 'comment_text', $content, $comment, array() );
111 144 $content = \preg_replace( '/[\n\r\t]/', '', $content );
112 145 $content = \trim( $content );
113 - $content = \apply_filters( 'the_content', $content, $comment );
114 146
115 - return $content;
147 + /**
148 + * Filter the content of the comment.
149 + *
150 + * @param string $content The content of the comment.
151 + * @param \WP_Comment $comment The comment object.
152 + *
153 + * @return string The filtered content of the comment.
154 + */
155 + return \apply_filters( 'activitypub_the_content', $content, $comment );
116 156 }
117 157
118 158 /**
119 159 * Returns the in-reply-to for the ActivityPub Item.
120 160 *
121 - * @return int The URL of the in-reply-to.
161 + * @return false|string|null The URL of the in-reply-to.
122 162 */
123 163 protected function get_in_reply_to() {
124 - $comment = $this->wp_object;
164 + $comment = $this->item;
165 + $parent_comment = null;
125 166
126 - $parent_comment = \get_comment( $comment->comment_parent );
167 + if ( $comment->comment_parent ) {
168 + $parent_comment = \get_comment( $comment->comment_parent );
169 + }
127 170
128 171 if ( $parent_comment ) {
129 - $comment_meta = \get_comment_meta( $parent_comment->comment_ID );
130 -
131 - if ( ! empty( $comment_meta['source_id'][0] ) ) {
132 - $in_reply_to = $comment_meta['source_id'][0];
133 - } elseif ( ! empty( $comment_meta['source_url'][0] ) ) {
134 - $in_reply_to = $comment_meta['source_url'][0];
135 - } else {
136 - $in_reply_to = $this->generate_id( $parent_comment );
172 + $in_reply_to = Comment_Utils::get_source_id( $parent_comment->comment_ID );
173 + if ( ! $in_reply_to && ! empty( $parent_comment->user_id ) ) {
174 + $in_reply_to = Comment_Utils::generate_id( $parent_comment );
137 175 }
138 176 } else {
139 177 $in_reply_to = \get_permalink( $comment->comment_post_ID );
140 178 }
@@ -150,125 +188,231 @@
150 188 *
151 189 * @return string ActivityPub URI for comment
152 190 */
153 191 protected function get_id() {
154 - $comment = $this->wp_object;
155 - return $this->generate_id( $comment );
192 + $comment = $this->item;
193 + return Comment_Utils::generate_id( $comment );
156 194 }
157 195
158 196 /**
159 - * Generates an ActivityPub URI for a comment
197 + * Returns the User-Object of the Author of the Post.
160 198 *
161 - * @param WP_Comment|int $comment A comment object or comment ID
199 + * If `single_user` mode is enabled, the Blog-User is returned.
162 200 *
163 - * @return string ActivityPub URI for comment
201 + * @return \Activitypub\Activity\Actor The User-Object.
164 202 */
165 - protected function generate_id( $comment ) {
166 - $comment = get_comment( $comment );
203 + protected function get_actor_object() {
204 + if ( $this->actor_object ) {
205 + return $this->actor_object;
206 + }
167 207
168 - return \add_query_arg(
169 - array(
170 - 'c' => $comment->comment_ID,
171 - ),
172 - \trailingslashit( site_url() )
173 - );
208 + $blog_user = new Blog();
209 + $this->actor_object = $blog_user;
210 +
211 + if ( is_single_user() ) {
212 + return $blog_user;
213 + }
214 +
215 + $user = Actors::get_by_id( $this->item->user_id );
216 +
217 + if ( $user && ! is_wp_error( $user ) ) {
218 + $this->actor_object = $user;
219 + return $user;
220 + }
221 +
222 + return $blog_user;
174 223 }
175 224
176 225 /**
177 - * Returns a list of Mentions, used in the Comment.
226 + * Helper function to get the @-Mentions from the comment content.
178 227 *
179 - * @see https://docs.joinmastodon.org/spec/activitypub/#Mention
228 + * @return array The list of @-Mentions.
229 + */
230 + protected function get_mentions() {
231 + \add_filter( 'activitypub_extract_mentions', array( $this, 'extract_reply_context' ) );
232 +
233 + /**
234 + * Filter the mentions in the comment.
235 + *
236 + * @param array $mentions The list of mentions.
237 + * @param string $content The content of the comment.
238 + * @param \WP_Comment $comment The comment object.
239 + *
240 + * @return array The filtered list of mentions.
241 + */
242 + return apply_filters( 'activitypub_extract_mentions', array(), $this->item->comment_content, $this->item );
243 + }
244 +
245 + /**
246 + * Gets the ancestors of the comment, but only the ones that are ActivityPub comments.
180 247 *
181 - * @return array The list of Mentions.
248 + * @return array The list of ancestors.
182 249 */
183 - protected function get_cc() {
184 - $cc = array();
250 + protected function get_comment_ancestors() {
251 + $ancestors = get_comment_ancestors( $this->item );
185 252
186 - $mentions = $this->get_mentions();
187 - if ( $mentions ) {
188 - foreach ( $mentions as $mention => $url ) {
189 - $cc[] = $url;
253 + // Now that we have the full tree of ancestors, only return the ones received from the fediverse.
254 + return array_filter(
255 + $ancestors,
256 + static function ( $comment_id ) {
257 + return \get_comment_meta( $comment_id, 'protocol', true ) === 'activitypub';
190 258 }
259 + );
260 + }
261 +
262 + /**
263 + * Collect all other Users that participated in this comment-thread
264 + * to send them a notification about the new reply.
265 + *
266 + * @param array $mentions Optional. The already mentioned ActivityPub users. Default empty array.
267 + *
268 + * @return array The list of all Repliers.
269 + */
270 + public function extract_reply_context( $mentions = array() ) {
271 + // Check if `$this->item` is a WP_Comment.
272 + if ( 'WP_Comment' !== get_class( $this->item ) ) {
273 + return $mentions;
191 274 }
192 275
193 - $comment_query = new WP_Comment_Query(
194 - array(
195 - 'post_id' => $this->wp_object->comment_post_ID,
196 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
197 - 'meta_query' => array(
198 - array(
199 - 'key' => 'source_id',
200 - 'compare' => 'EXISTS',
201 - ),
202 - ),
203 - )
204 - );
276 + $ancestors = $this->get_comment_ancestors();
277 + if ( ! $ancestors ) {
278 + return $mentions;
279 + }
205 280
206 - if ( $comment_query->comments ) {
207 - foreach ( $comment_query->comments as $comment ) {
208 - if ( empty( $comment->comment_author_url ) ) {
209 - continue;
281 + foreach ( $ancestors as $comment_id ) {
282 + $comment = \get_comment( $comment_id );
283 + if ( $comment && ! empty( $comment->comment_author_url ) ) {
284 + $acct = Webfinger::uri_to_acct( $comment->comment_author_url );
285 + if ( $acct && ! is_wp_error( $acct ) ) {
286 + $acct = str_replace( 'acct:', '@', $acct );
287 + $mentions[ $acct ] = $comment->comment_author_url;
210 288 }
211 - $cc[] = \esc_url( $comment->comment_author_url );
212 289 }
213 290 }
214 291
215 - $cc = \array_unique( $cc );
292 + return $mentions;
293 + }
216 294
217 - return $cc;
295 + /**
296 + * Returns the updated date of the comment.
297 + *
298 + * @return string|null The updated date of the comment.
299 + */
300 + public function get_updated() {
301 + $updated = \get_comment_meta( $this->item->comment_ID, 'activitypub_comment_modified', true );
302 + $published = \get_comment_meta( $this->item->comment_ID, 'activitypub_comment_published', true );
303 +
304 + if ( $updated > $published ) {
305 + return \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, $updated );
306 + }
307 +
308 + return null;
218 309 }
219 310
220 311 /**
221 - * Returns a list of Tags, used in the Comment.
312 + * Returns the published date of the comment.
222 313 *
223 - * This includes Hash-Tags and Mentions.
314 + * @return string The published date of the comment.
315 + */
316 + public function get_published() {
317 + return \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, \strtotime( $this->item->comment_date_gmt ) );
318 + }
319 +
320 + /**
321 + * Returns the URL of the comment.
224 322 *
225 - * @return array The list of Tags.
323 + * @return string The URL of the comment.
226 324 */
227 - protected function get_tag() {
228 - $tags = array();
325 + public function get_url() {
326 + return $this->get_id();
327 + }
229 328
230 - $mentions = $this->get_mentions();
231 - if ( $mentions ) {
232 - foreach ( $mentions as $mention => $url ) {
233 - $tag = array(
234 - 'type' => 'Mention',
235 - 'href' => \esc_url( $url ),
236 - 'name' => \esc_html( $mention ),
237 - );
238 - $tags[] = $tag;
239 - }
329 + /**
330 + * Returns the type of the comment.
331 + *
332 + * @return string The type of the comment.
333 + */
334 + public function get_type() {
335 + return 'Note';
336 + }
337 +
338 + /**
339 + * Get the context of the post.
340 + *
341 + * @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-context
342 + *
343 + * @return string The context of the post.
344 + */
345 + protected function get_context() {
346 + if ( $this->item->comment_post_ID ) {
347 + return get_rest_url_by_path( sprintf( 'posts/%d/context', $this->item->comment_post_ID ) );
240 348 }
241 349
242 - return \array_unique( $tags, SORT_REGULAR );
350 + return null;
243 351 }
244 352
245 353 /**
246 - * Helper function to get the @-Mentions from the comment content.
354 + * Get the replies Collection.
247 355 *
248 - * @return array The list of @-Mentions.
356 + * @return array|null The replies collection on success or null on failure.
249 357 */
250 - protected function get_mentions() {
251 - return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_object->comment_content, $this->wp_object );
358 + public function get_replies() {
359 + return Replies::get_collection( $this->item );
252 360 }
253 361
254 362 /**
255 - * Returns the locale of the post.
363 + * Get the attachment for the comment.
256 364 *
257 - * @return string The locale of the post.
365 + * Extracts images from comment content and returns them as ActivityPub attachments.
366 + *
367 + * @return array The attachments array for ActivityPub.
258 368 */
259 - public function get_locale() {
260 - $comment_id = $this->wp_object->ID;
261 - $lang = \strtolower( \strtok( \get_locale(), '_-' ) );
369 + protected function get_attachment() {
370 + $max_media = \get_option( 'activitypub_max_image_attachments', \ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS );
262 371
263 372 /**
264 - * Filter the locale of the comment.
373 + * Filters the maximum number of media attachments allowed in a comment.
265 374 *
266 - * @param string $lang The locale of the comment.
267 - * @param int $comment_id The comment ID.
268 - * @param WP_Post $post The comment object.
375 + * @param int $max_media Maximum number of media attachments.
376 + * @param \WP_Comment $item The comment object.
377 + */
378 + $max_media = (int) \apply_filters( 'activitypub_max_image_attachments', $max_media, $this->item );
379 +
380 + if ( 0 === $max_media ) {
381 + return array();
382 + }
383 +
384 + $media = array(
385 + 'image' => array(),
386 + 'audio' => array(),
387 + 'video' => array(),
388 + );
389 +
390 + // Get comment content and parse for image embeds.
391 + $media = $this->parse_html_images( $media, $max_media, $this->item->comment_content );
392 + $media = $this->filter_unique_attachments( $media['image'] );
393 + $media = \array_slice( $media, 0, $max_media );
394 +
395 + /**
396 + * Filter the attachment IDs for a comment.
269 397 *
270 - * @return string The filtered locale of the comment.
398 + * @param array $media The media array.
399 + * @param \WP_Comment $item The comment object.
400 + *
401 + * @return array The filtered attachment IDs.
271 402 */
272 - return apply_filters( 'activitypub_comment_locale', $lang, $comment_id, $this->wp_object );
403 + $media = \apply_filters( 'activitypub_comment_attachment_ids', $media, $this->item );
404 +
405 + // Transform to ActivityStreams format using Base class method.
406 + $attachments = \array_filter( \array_map( array( $this, 'transform_attachment' ), $media ) );
407 +
408 + /**
409 + * Filter the attachments for a comment.
410 + *
411 + * @param array $attachments The attachments.
412 + * @param \WP_Comment $item The comment object.
413 + *
414 + * @return array The filtered attachments.
415 + */
416 + return \apply_filters( 'activitypub_comment_attachments', $attachments, $this->item );
273 417 }
274 418 }