PluginProbe
ActivityPub / 2.6.0
ActivityPub v2.6.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-post.php

class-post.php in ActivityPub 2.6.0, at includes/transformer/class-post.php

882 lines 23.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Activitypub\Transformer;
3
4 use WP_Post;
5 use Activitypub\Shortcodes;
6 use Activitypub\Model\Blog;
7 use Activitypub\Transformer\Base;
8 use Activitypub\Collection\Users;
9
10 use function Activitypub\esc_hashtag;
11 use function Activitypub\is_single_user;
12 use function Activitypub\get_enclosures;
13 use function Activitypub\get_rest_url_by_path;
14 use function Activitypub\site_supports_blocks;
15
16 /**
17 * WordPress Post Transformer
18 *
19 * The Post Transformer is responsible for transforming a WP_Post object into different other
20 * Object-Types.
21 *
22 * Currently supported are:
23 *
24 * - Activitypub\Activity\Base_Object
25 */
26 class Post extends Base {
27 /**
28 * Returns the ID of the WordPress Post.
29 *
30 * @return int The ID of the WordPress Post
31 */
32 public function get_wp_user_id() {
33 return $this->wp_object->post_author;
34 }
35
36 /**
37 * Change the User-ID of the WordPress Post.
38 *
39 * @return int The User-ID of the WordPress Post
40 */
41 public function change_wp_user_id( $user_id ) {
42 $this->wp_object->post_author = $user_id;
43
44 return $this;
45 }
46
47 /**
48 * Transforms the WP_Post object to an ActivityPub Object
49 *
50 * @see \Activitypub\Activity\Base_Object
51 *
52 * @return \Activitypub\Activity\Base_Object The ActivityPub Object
53 */
54 public function to_object() {
55 $post = $this->wp_object;
56 $object = parent::to_object();
57
58 $published = \strtotime( $post->post_date_gmt );
59
60 $object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
61
62 $updated = \strtotime( $post->post_modified_gmt );
63
64 if ( $updated > $published ) {
65 $object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
66 }
67
68 $object->set_content_map(
69 array(
70 $this->get_locale() => $this->get_content(),
71 )
72 );
73 $path = sprintf( 'actors/%d/followers', intval( $post->post_author ) );
74
75 $object->set_to(
76 array(
77 'https://www.w3.org/ns/activitystreams#Public',
78 get_rest_url_by_path( $path ),
79 )
80 );
81
82 return $object;
83 }
84
85 /**
86 * Returns the ID of the Post.
87 *
88 * @return string The Posts ID.
89 */
90 public function get_id() {
91 return $this->get_url();
92 }
93
94 /**
95 * Returns the URL of the Post.
96 *
97 * @return string The Posts URL.
98 */
99 public function get_url() {
100 $post = $this->wp_object;
101
102 if ( 'trash' === get_post_status( $post ) ) {
103 $permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
104 } elseif ( 'draft' === get_post_status( $post ) && get_sample_permalink( $post->ID ) ) {
105 $sample = get_sample_permalink( $post->ID );
106 $permalink = str_replace( array( '%pagename%', '%postname%' ), $sample[1], $sample[0] );
107 } else {
108 $permalink = \get_permalink( $post );
109 }
110
111 return \esc_url( $permalink );
112 }
113
114 /**
115 * Returns the User-URL of the Author of the Post.
116 *
117 * If `single_user` mode is enabled, the URL of the Blog-User is returned.
118 *
119 * @return string The User-URL.
120 */
121 protected function get_attributed_to() {
122 $blog_user = new Blog();
123
124 if ( is_single_user() ) {
125 return $blog_user->get_url();
126 }
127
128 $user = Users::get_by_id( $this->wp_object->post_author );
129
130 if ( $user && ! is_wp_error( $user ) ) {
131 return $user->get_url();
132 }
133
134 return $blog_user->get_url();
135 }
136
137 /**
138 * Generates all Media Attachments for a Post.
139 *
140 * @return array The Attachments.
141 */
142 protected function get_attachment() {
143 // Remove attachments from drafts.
144 if ( 'draft' === \get_post_status( $this->wp_object ) ) {
145 return array();
146 }
147
148 // Once upon a time we only supported images, but we now support audio/video as well.
149 // We maintain the image-centric naming for backwards compatibility.
150 $max_media = \intval(
151 \apply_filters(
152 'activitypub_max_image_attachments',
153 \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS )
154 )
155 );
156
157 $media = array(
158 'audio' => array(),
159 'video' => array(),
160 'image' => array(),
161 );
162 $id = $this->wp_object->ID;
163
164 // list post thumbnail first if this post has one
165 if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
166 $media['image'][] = array( 'id' => \get_post_thumbnail_id( $id ) );
167 }
168
169 $media = $this->get_enclosures( $media );
170
171 if ( site_supports_blocks() && \has_blocks( $this->wp_object->post_content ) ) {
172 $media = $this->get_block_attachments( $media, $max_media );
173 } else {
174 $media = $this->get_classic_editor_images( $media, $max_media );
175 }
176
177 $media = self::filter_media_by_object_type( $media, \get_post_format( $this->wp_object ), $this->wp_object );
178 $unique_ids = \array_unique( \array_column( $media, 'id' ) );
179 $media = \array_intersect_key( $media, $unique_ids );
180 $media = \array_slice( $media, 0, $max_media );
181
182 return \array_filter( \array_map( array( self::class, 'wp_attachment_to_activity_attachment' ), $media ) );
183 }
184
185 /**
186 * Get media attachments from blocks. They will be formatted as ActivityPub attachments, not as WP attachments.
187 *
188 * @param array $media The media array grouped by type.
189 * @param int $max_media The maximum number of attachments to return.
190 *
191 * @return array The attachments.
192 */
193 protected function get_block_attachments( $media, $max_media ) {
194 // max media can't be negative or zero
195 if ( $max_media <= 0 ) {
196 return array();
197 }
198
199 $blocks = \parse_blocks( $this->wp_object->post_content );
200 $media = self::get_media_from_blocks( $blocks, $media );
201
202 return $media;
203 }
204
205 /**
206 * Get image attachments from the classic editor.
207 * This is imperfect as the contained images aren't necessarily the
208 * same as the attachments.
209 *
210 * @param int $max_images The maximum number of images to return.
211 *
212 * @return array The attachment IDs.
213 */
214 protected function get_classic_editor_image_attachments( $max_images ) {
215 // max images can't be negative or zero
216 if ( $max_images <= 0 ) {
217 return array();
218 }
219
220 $images = array();
221 $query = new \WP_Query(
222 array(
223 'post_parent' => $this->wp_object->ID,
224 'post_status' => 'inherit',
225 'post_type' => 'attachment',
226 'post_mime_type' => 'image',
227 'order' => 'ASC',
228 'orderby' => 'menu_order ID',
229 'posts_per_page' => $max_images,
230 )
231 );
232
233 foreach ( $query->get_posts() as $attachment ) {
234 if ( ! \in_array( $attachment->ID, $images, true ) ) {
235 $images[] = array( 'id' => $attachment->ID );
236 }
237 }
238
239 return $images;
240 }
241
242 /**
243 * Get image embeds from the classic editor by parsing HTML.
244 *
245 * @param int $max_images The maximum number of images to return.
246 *
247 * @return array The attachments.
248 */
249 protected function get_classic_editor_image_embeds( $max_images ) {
250 // if someone calls that function directly, bail
251 if ( ! \class_exists( '\WP_HTML_Tag_Processor' ) ) {
252 return array();
253 }
254
255 // max images can't be negative or zero
256 if ( $max_images <= 0 ) {
257 return array();
258 }
259
260 $images = array();
261 $base = \wp_get_upload_dir()['baseurl'];
262 $content = \get_post_field( 'post_content', $this->wp_object );
263 $tags = new \WP_HTML_Tag_Processor( $content );
264
265 // This linter warning is a false positive - we have to
266 // re-count each time here as we modify $images.
267 // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
268 while ( $tags->next_tag( 'img' ) && ( \count( $images ) <= $max_images ) ) {
269 $src = $tags->get_attribute( 'src' );
270
271 // If the img source is in our uploads dir, get the
272 // associated ID. Note: if there's a -500x500
273 // type suffix, we remove it, but we try the original
274 // first in case the original image is actually called
275 // that. Likewise, we try adding the -scaled suffix for
276 // the case that this is a small version of an image
277 // that was big enough to get scaled down on upload:
278 // https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
279 if ( null !== $src && \str_starts_with( $src, $base ) ) {
280 $img_id = \attachment_url_to_postid( $src );
281
282 if ( 0 === $img_id ) {
283 $count = 0;
284 $src = preg_replace( '/-(?:\d+x\d+)(\.[a-zA-Z]+)$/', '$1', $src, 1, $count );
285 if ( $count > 0 ) {
286 $img_id = \attachment_url_to_postid( $src );
287 }
288 }
289
290 if ( 0 === $img_id ) {
291 $src = preg_replace( '/(\.[a-zA-Z]+)$/', '-scaled$1', $src );
292 $img_id = \attachment_url_to_postid( $src );
293 }
294
295 if ( 0 !== $img_id ) {
296 $images[] = array(
297 'id' => $img_id,
298 'alt' => $tags->get_attribute( 'alt' ),
299 );
300 }
301 }
302 }
303
304 return $images;
305 }
306
307 /**
308 * Get post images from the classic editor.
309 * Note that audio/video attachments are only supported in the block editor.
310 *
311 * @param array $media The media array grouped by type.
312 * @param int $max_images The maximum number of images to return.
313 *
314 * @return array The attachments.
315 */
316 protected function get_classic_editor_images( $media, $max_images ) {
317 // max images can't be negative or zero
318 if ( $max_images <= 0 ) {
319 return array();
320 }
321
322 if ( \count( $media['image'] ) <= $max_images ) {
323 if ( \class_exists( '\WP_HTML_Tag_Processor' ) ) {
324 $media['image'] = \array_merge( $media['image'], $this->get_classic_editor_image_embeds( $max_images ) );
325 } else {
326 $media['image'] = \array_merge( $media['image'], $this->get_classic_editor_image_attachments( $max_images ) );
327 }
328 }
329
330 return $media;
331 }
332
333 /**
334 * Get enclosures for a post.
335 *
336 * @param array $media The media array grouped by type.
337 *
338 * @return array The media array extended with enclosures.
339 */
340 public function get_enclosures( $media ) {
341 $enclosures = get_enclosures( $this->wp_object->ID );
342
343 if ( ! $enclosures ) {
344 return $media;
345 }
346
347 foreach ( $enclosures as $enclosure ) {
348 // check if URL is an attachment
349 $attachment_id = \attachment_url_to_postid( $enclosure['url'] );
350 if ( $attachment_id ) {
351 $enclosure['id'] = $attachment_id;
352 $enclosure['url'] = \wp_get_attachment_url( $attachment_id );
353 $enclosure['mediaType'] = \get_post_mime_type( $attachment_id );
354 }
355
356 $mime_type = $enclosure['mediaType'];
357 $mime_type_parts = \explode( '/', $mime_type );
358
359 switch ( $mime_type_parts[0] ) {
360 case 'image':
361 $media['image'][] = $enclosure;
362 break;
363 case 'audio':
364 $media['audio'][] = $enclosure;
365 break;
366 case 'video':
367 $media['video'][] = $enclosure;
368 break;
369 }
370 }
371
372 return $media;
373 }
374
375 /**
376 * Recursively get media IDs from blocks.
377 * @param array $blocks The blocks to search for media IDs
378 * @param array $media The media IDs to append new IDs to
379 * @param int $max_media The maximum number of media to return.
380 *
381 * @return array The image IDs.
382 */
383 protected static function get_media_from_blocks( $blocks, $media ) {
384 foreach ( $blocks as $block ) {
385 // recurse into inner blocks
386 if ( ! empty( $block['innerBlocks'] ) ) {
387 $media = self::get_media_from_blocks( $block['innerBlocks'], $media );
388 }
389
390 switch ( $block['blockName'] ) {
391 case 'core/image':
392 case 'core/cover':
393 if ( ! empty( $block['attrs']['id'] ) ) {
394 $alt = '';
395 $check = preg_match( '/<img.*?alt\s*=\s*([\"\'])(.*?)\1.*>/i', $block['innerHTML'], $match );
396
397 if ( $check ) {
398 $alt = $match[2];
399 }
400
401 $media['image'][] = array(
402 'id' => $block['attrs']['id'],
403 'alt' => $alt,
404 );
405 }
406 break;
407 case 'core/audio':
408 if ( ! empty( $block['attrs']['id'] ) ) {
409 $media['audio'][] = array( 'id' => $block['attrs']['id'] );
410 }
411 break;
412 case 'core/video':
413 case 'videopress/video':
414 if ( ! empty( $block['attrs']['id'] ) ) {
415 $media['video'][] = array( 'id' => $block['attrs']['id'] );
416 }
417 break;
418 case 'jetpack/slideshow':
419 case 'jetpack/tiled-gallery':
420 if ( ! empty( $block['attrs']['ids'] ) ) {
421 $media['image'] = array_merge(
422 $media['image'],
423 array_map(
424 function ( $id ) {
425 return array( 'id' => $id );
426 },
427 $block['attrs']['ids']
428 )
429 );
430 }
431 break;
432 case 'jetpack/image-compare':
433 if ( ! empty( $block['attrs']['beforeImageId'] ) ) {
434 $media['image'][] = array( 'id' => $block['attrs']['beforeImageId'] );
435 }
436 if ( ! empty( $block['attrs']['afterImageId'] ) ) {
437 $media['image'][] = array( 'id' => $block['attrs']['afterImageId'] );
438 }
439 break;
440 }
441 }
442
443 return $media;
444 }
445
446 /**
447 * Filter media IDs by object type.
448 *
449 * @param array $media The media array grouped by type.
450 * @param string $type The object type.
451 *
452 * @return array The filtered media IDs.
453 */
454 protected static function filter_media_by_object_type( $media, $type, $wp_object ) {
455 $type = \apply_filters( 'filter_media_by_object_type', \strtolower( $type ), $wp_object );
456
457 if ( ! empty( $media[ $type ] ) ) {
458 return $media[ $type ];
459 }
460
461 return array_filter( array_merge( array(), ...array_values( $media ) ) );
462 }
463
464 /**
465 * Converts a WordPress Attachment to an ActivityPub Attachment.
466 *
467 * @param array $media The Attachment array.
468 *
469 * @return array The ActivityPub Attachment.
470 */
471 public static function wp_attachment_to_activity_attachment( $media ) {
472 if ( ! isset( $media['id'] ) ) {
473 return $media;
474 }
475
476 $id = $media['id'];
477 $attachment = array();
478 $mime_type = \get_post_mime_type( $id );
479 $mime_type_parts = \explode( '/', $mime_type );
480 // switching on image/audio/video
481 switch ( $mime_type_parts[0] ) {
482 case 'image':
483 $image_size = 'large';
484
485 /**
486 * Filter the image URL returned for each post.
487 *
488 * @param array|false $thumbnail The image URL, or false if no image is available.
489 * @param int $id The attachment ID.
490 * @param string $image_size The image size to retrieve. Set to 'large' by default.
491 */
492 $thumbnail = apply_filters(
493 'activitypub_get_image',
494 self::get_wordpress_attachment( $id, $image_size ),
495 $id,
496 $image_size
497 );
498
499 if ( $thumbnail ) {
500 $image = array(
501 'type' => 'Image',
502 'url' => \esc_url( $thumbnail[0] ),
503 'mediaType' => \esc_attr( $mime_type ),
504 );
505
506 if ( ! empty( $media['alt'] ) ) {
507 $image['name'] = \wp_strip_all_tags( \html_entity_decode( $media['alt'] ) );
508 } else {
509 $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
510 if ( $alt ) {
511 $image['name'] = \wp_strip_all_tags( \html_entity_decode( $alt ) );
512 }
513 }
514
515 $attachment = $image;
516 }
517 break;
518
519 case 'audio':
520 case 'video':
521 $attachment = array(
522 'type' => 'Document',
523 'mediaType' => \esc_attr( $mime_type ),
524 'url' => \esc_url( \wp_get_attachment_url( $id ) ),
525 'name' => \esc_attr( \get_the_title( $id ) ),
526 );
527 $meta = wp_get_attachment_metadata( $id );
528 // height and width for videos
529 if ( isset( $meta['width'] ) && isset( $meta['height'] ) ) {
530 $attachment['width'] = \esc_attr( $meta['width'] );
531 $attachment['height'] = \esc_attr( $meta['height'] );
532 }
533 // @todo: add `icon` support for audio/video attachments. Maybe use post thumbnail?
534 break;
535 }
536
537 return \apply_filters( 'activitypub_attachment', $attachment, $id );
538 }
539
540 /**
541 * Return details about an image attachment.
542 *
543 * @param int $id The attachment ID.
544 * @param string $image_size The image size to retrieve. Set to 'large' by default.
545 *
546 * @return array|false Array of image data, or boolean false if no image is available.
547 */
548 protected static function get_wordpress_attachment( $id, $image_size = 'large' ) {
549 /**
550 * Hook into the image retrieval process. Before image retrieval.
551 *
552 * @param int $id The attachment ID.
553 * @param string $image_size The image size to retrieve. Set to 'large' by default.
554 */
555 do_action( 'activitypub_get_image_pre', $id, $image_size );
556
557 $image = \wp_get_attachment_image_src( $id, $image_size );
558
559 /**
560 * Hook into the image retrieval process. After image retrieval.
561 *
562 * @param int $id The attachment ID.
563 * @param string $image_size The image size to retrieve. Set to 'large' by default.
564 */
565 do_action( 'activitypub_get_image_post', $id, $image_size );
566
567 return $image;
568 }
569
570 /**
571 * Returns the ActivityStreams 2.0 Object-Type for a Post based on the
572 * settings and the Post-Type.
573 *
574 * @see https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
575 *
576 * @return string The Object-Type.
577 */
578 protected function get_type() {
579 $post_format_setting = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE );
580
581 if ( 'wordpress-post-format' !== $post_format_setting ) {
582 return \ucfirst( $post_format_setting );
583 }
584
585 $has_title = post_type_supports( $this->wp_object->post_type, 'title' );
586
587 if ( ! $has_title ) {
588 return 'Note';
589 }
590
591 // Default to Article.
592 $object_type = 'Note';
593 $post_format = 'standard';
594
595 if ( \get_theme_support( 'post-formats' ) ) {
596 $post_format = \get_post_format( $this->wp_object );
597 }
598
599 $post_type = \get_post_type( $this->wp_object );
600 switch ( $post_type ) {
601 case 'post':
602 switch ( $post_format ) {
603 case 'standard':
604 case '':
605 $object_type = 'Article';
606 break;
607 default:
608 $object_type = 'Note';
609 break;
610 }
611 break;
612 case 'page':
613 $object_type = 'Page';
614 break;
615 default:
616 $object_type = 'Note';
617 break;
618 }
619
620 return $object_type;
621 }
622
623 /**
624 * Returns a list of Mentions, used in the Post.
625 *
626 * @see https://docs.joinmastodon.org/spec/activitypub/#Mention
627 *
628 * @return array The list of Mentions.
629 */
630 protected function get_cc() {
631 $cc = array();
632
633 $mentions = $this->get_mentions();
634 if ( $mentions ) {
635 foreach ( $mentions as $url ) {
636 $cc[] = $url;
637 }
638 }
639
640 return $cc;
641 }
642
643
644 public function get_audience() {
645 if ( is_single_user() ) {
646 return null;
647 } else {
648 $blog = new Blog();
649 return $blog->get_id();
650 }
651 }
652
653 /**
654 * Returns a list of Tags, used in the Post.
655 *
656 * This includes Hash-Tags and Mentions.
657 *
658 * @return array The list of Tags.
659 */
660 protected function get_tag() {
661 $tags = array();
662
663 $post_tags = \get_the_tags( $this->wp_object->ID );
664 if ( $post_tags ) {
665 foreach ( $post_tags as $post_tag ) {
666 $tag = array(
667 'type' => 'Hashtag',
668 'href' => \esc_url( \get_tag_link( $post_tag->term_id ) ),
669 'name' => esc_hashtag( $post_tag->name ),
670 );
671 $tags[] = $tag;
672 }
673 }
674
675 $mentions = $this->get_mentions();
676 if ( $mentions ) {
677 foreach ( $mentions as $mention => $url ) {
678 $tag = array(
679 'type' => 'Mention',
680 'href' => \esc_url( $url ),
681 'name' => \esc_html( $mention ),
682 );
683 $tags[] = $tag;
684 }
685 }
686
687 return $tags;
688 }
689
690 /**
691 * Returns the summary for the ActivityPub Item.
692 *
693 * The summary will be generated based on the user settings and only if the
694 * object type is not set to `note`.
695 *
696 * @return string|null The summary or null if the object type is `note`.
697 */
698 protected function get_summary() {
699 if ( 'Note' === $this->get_type() ) {
700 return null;
701 }
702
703 // Remove Teaser from drafts.
704 if ( 'draft' === \get_post_status( $this->wp_object ) ) {
705 return \__( '(This post is being modified)', 'activitypub' );
706 }
707
708 $content = \get_post_field( 'post_content', $this->wp_object->ID );
709 $content = \html_entity_decode( $content );
710 $content = \wp_strip_all_tags( $content );
711 $content = \trim( $content );
712 $content = \preg_replace( '/\R+/m', "\n\n", $content );
713 $content = \preg_replace( '/[\r\t]/', '', $content );
714
715 $excerpt_more = \apply_filters( 'activitypub_excerpt_more', '[...]' );
716 $length = 500;
717 $length = $length - strlen( $excerpt_more );
718
719 if ( \strlen( $content ) > $length ) {
720 $content = \wordwrap( $content, $length, '</activitypub-summary>' );
721 $content = \explode( '</activitypub-summary>', $content, 2 );
722 $content = $content[0];
723 }
724
725 return $content . ' ' . $excerpt_more;
726 }
727
728 /**
729 * Returns the title for the ActivityPub Item.
730 *
731 * The title will be generated based on the user settings and only if the
732 * object type is not set to `note`.
733 *
734 * @return string|null The title or null if the object type is `note`.
735 */
736 protected function get_name() {
737 if ( 'Note' === $this->get_type() ) {
738 return null;
739 }
740
741 $title = \get_the_title( $this->wp_object->ID );
742
743 if ( $title ) {
744 return \wp_strip_all_tags(
745 \html_entity_decode(
746 $title
747 )
748 );
749 }
750
751 return null;
752 }
753
754 /**
755 * Returns the content for the ActivityPub Item.
756 *
757 * The content will be generated based on the user settings.
758 *
759 * @return string The content.
760 */
761 protected function get_content() {
762 // Remove Content from drafts.
763 if ( 'draft' === \get_post_status( $this->wp_object ) ) {
764 return \__( '(This post is being modified)', 'activitypub' );
765 }
766
767 global $post;
768
769 /**
770 * Provides an action hook so plugins can add their own hooks/filters before AP content is generated.
771 *
772 * Example: if a plugin adds a filter to `the_content` to add a button to the end of posts, it can also remove that filter here.
773 *
774 * @param WP_Post $post The post object.
775 */
776 do_action( 'activitypub_before_get_content', $post );
777
778 add_filter( 'render_block_core/embed', array( self::class, 'revert_embed_links' ), 10, 2 );
779
780 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
781 $post = $this->wp_object;
782 $content = $this->get_post_content_template();
783
784 // Register our shortcodes just in time.
785 Shortcodes::register();
786 // Fill in the shortcodes.
787 setup_postdata( $post );
788 $content = do_shortcode( $content );
789 wp_reset_postdata();
790
791 $content = \wpautop( $content );
792 $content = \preg_replace( '/[\n\r\t]/', '', $content );
793 $content = \trim( $content );
794
795 $content = \apply_filters( 'activitypub_the_content', $content, $post );
796
797 // Don't need these any more, should never appear in a post.
798 Shortcodes::unregister();
799
800 return $content;
801 }
802
803 /**
804 * Gets the template to use to generate the content of the activitypub item.
805 *
806 * @return string The Template.
807 */
808 protected function get_post_content_template() {
809 $type = \get_option( 'activitypub_post_content_type', 'content' );
810
811 switch ( $type ) {
812 case 'excerpt':
813 $template = "[ap_excerpt]\n\n[ap_permalink type=\"html\"]";
814 break;
815 case 'title':
816 $template = "<h2>[ap_title]</h2>\n\n[ap_permalink type=\"html\"]";
817 break;
818 case 'content':
819 $template = "[ap_content]\n\n[ap_permalink type=\"html\"]\n\n[ap_hashtags]";
820 break;
821 default:
822 $template = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
823 break;
824 }
825
826 $post_format_setting = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE );
827
828 if ( 'wordpress-post-format' === $post_format_setting ) {
829 $template = '[ap_content]';
830 }
831
832 return apply_filters( 'activitypub_object_content_template', $template, $this->wp_object );
833 }
834
835 /**
836 * Helper function to get the @-Mentions from the post content.
837 *
838 * @return array The list of @-Mentions.
839 */
840 protected function get_mentions() {
841 return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_object->post_content, $this->wp_object );
842 }
843
844 /**
845 * Returns the locale of the post.
846 *
847 * @return string The locale of the post.
848 */
849 public function get_locale() {
850 $post_id = $this->wp_object->ID;
851 $lang = \strtolower( \strtok( \get_locale(), '_-' ) );
852
853 /**
854 * Filter the locale of the post.
855 *
856 * @param string $lang The locale of the post.
857 * @param int $post_id The post ID.
858 * @param WP_Post $post The post object.
859 *
860 * @return string The filtered locale of the post.
861 */
862 return apply_filters( 'activitypub_post_locale', $lang, $post_id, $this->wp_object );
863 }
864
865 /**
866 * Transform Embed blocks to block level link.
867 *
868 * Remote servers will simply drop iframe elements, rendering incomplete content.
869 *
870 * @see https://www.w3.org/TR/activitypub/#security-sanitizing-content
871 * @see https://www.w3.org/wiki/ActivityPub/Primer/HTML
872 *
873 * @param string $block_content The block content (html)
874 * @param object $block The block object
875 *
876 * @return string A block level link
877 */
878 public static function revert_embed_links( $block_content, $block ) {
879 return '<p><a href="' . esc_url( $block['attrs']['url'] ) . '">' . $block['attrs']['url'] . '</a></p>';
880 }
881 }
882