PluginProbe
ActivityPub / 2.5.0
ActivityPub v2.5.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.5.0, at includes/transformer/class-post.php

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