PluginProbe
ActivityPub / 9.1.0
ActivityPub v9.1.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 / transformer / class-comment.php

class-comment.php in ActivityPub 9.1.0, at includes/transformer/class-comment.php

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