PluginProbe
ActivityPub / 7.8.2
ActivityPub v7.8.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-post.php +901 -304 1.0.87.8.2 View file →
@@ -1,22 +1,33 @@
1 1 <?php
2 +/**
3 + * WordPress Post Transformer Class file.
4 + *
5 + * @package Activitypub
6 + */
7 +
2 8 namespace Activitypub\Transformer;
3 9
4 -use WP_Post;
5 -use Activitypub\Collection\Users;
6 -use Activitypub\Model\Blog_User;
7 -use Activitypub\Activity\Base_Object;
10 +use Activitypub\Blocks;
11 +use Activitypub\Collection\Actors;
12 +use Activitypub\Collection\Interactions;
13 +use Activitypub\Collection\Replies;
14 +use Activitypub\Model\Blog;
8 15 use Activitypub\Shortcodes;
9 16
10 17 use function Activitypub\esc_hashtag;
18 +use function Activitypub\generate_post_summary;
19 +use function Activitypub\get_content_visibility;
20 +use function Activitypub\get_content_warning;
21 +use function Activitypub\get_enclosures;
22 +use function Activitypub\get_rest_url_by_path;
11 23 use function Activitypub\is_single_user;
12 -use function Activitypub\get_rest_url_by_path;
13 24 use function Activitypub\site_supports_blocks;
14 25
15 26 /**
16 - * WordPress Post Transformer
27 + * WordPress Post Transformer.
17 28 *
18 - * The Post Transformer is responsible for transforming a WP_Post object into different othe
29 + * The Post Transformer is responsible for transforming a WP_Post object into different other
19 30 * Object-Types.
20 31 *
21 32 * Currently supported are:
22 33 *
@@ -21,84 +32,134 @@
21 32 * Currently supported are:
22 33 *
23 34 * - Activitypub\Activity\Base_Object
24 35 */
25 -class Post {
36 +class Post extends Base {
37 + /**
38 + * The User as Actor Object.
39 + *
40 + * @var \Activitypub\Activity\Actor
41 + */
42 + private $actor_object = null;
26 43
27 44 /**
28 - * The WP_Post object.
45 + * The content.
29 46 *
30 - * @var WP_Post
47 + * @var string|false False indicates not yet computed.
31 48 */
32 - protected $wp_post;
49 + private $content = false;
33 50
34 51 /**
35 - * Static function to Transform a WP_Post Object.
52 + * The summary.
36 53 *
37 - * This helps to chain the output of the Transformer.
54 + * @var string|null|false False indicates not yet computed.
55 + */
56 + private $summary = false;
57 +
58 + /**
59 + * The tags.
38 60 *
39 - * @param WP_Post $wp_post The WP_Post object
61 + * @var array|false False indicates not yet computed.
62 + */
63 + private $tags = false;
64 +
65 + /**
66 + * The attachment.
40 67 *
41 - * @return void
68 + * @var array|false False indicates not yet computed.
42 69 */
43 - public static function transform( WP_Post $wp_post ) {
44 - return new static( $wp_post );
45 - }
70 + private $attachment = false;
46 71
47 72 /**
73 + * The mentions.
48 74 *
75 + * @var array|false False indicates not yet computed.
76 + */
77 + private $mentions = false;
78 +
79 + /**
80 + * The in_reply_to.
49 81 *
50 - * @param WP_Post $wp_post
82 + * @var string|array|null|false False indicates not yet computed.
51 83 */
52 - public function __construct( WP_Post $wp_post ) {
53 - $this->wp_post = $wp_post;
54 - }
84 + private $in_reply_to = false;
55 85
56 86 /**
57 87 * Transforms the WP_Post object to an ActivityPub Object
58 88 *
59 - * @see \Activitypub\Activity\Base_Object
60 - *
61 89 * @return \Activitypub\Activity\Base_Object The ActivityPub Object
62 90 */
63 91 public function to_object() {
64 - $wp_post = $this->wp_post;
65 - $object = new Base_Object();
92 + $post = $this->item;
93 + $object = parent::to_object();
66 94
67 - $object->set_id( $this->get_id() );
68 - $object->set_url( $this->get_url() );
69 - $object->set_type( $this->get_object_type() );
95 + $content_warning = get_content_warning( $post );
96 + if ( ! empty( $content_warning ) ) {
97 + $object->set_sensitive( true );
98 + $object->set_summary( $content_warning );
99 + $object->set_summary_map( null );
100 + $object->set_dcterms( array( 'subject' => $content_warning ) );
101 + }
70 102
71 - $published = \strtotime( $wp_post->post_date_gmt );
103 + return $object;
104 + }
72 105
73 - $object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
106 + /**
107 + * Get the content visibility.
108 + *
109 + * @return string The content visibility.
110 + */
111 + public function get_content_visibility() {
112 + if ( ! $this->content_visibility ) {
113 + return get_content_visibility( $this->item );
114 + }
74 115
75 - $updated = \strtotime( $wp_post->post_modified_gmt );
116 + return $this->content_visibility;
117 + }
76 118
77 - if ( $updated > $published ) {
78 - $object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
119 + /**
120 + * Get the Interaction Policy.
121 + *
122 + * @see https://docs.gotosocial.org/en/latest/federation/interaction_policy/
123 + *
124 + * @return array The interaction policy.
125 + */
126 + public function get_interaction_policy() {
127 + return array(
128 + 'canAnnounce' => $this->get_public_interaction_policy(),
129 + 'canLike' => $this->get_public_interaction_policy(),
130 + 'canQuote' => $this->get_quote_policy(),
131 + 'canReply' => $this->get_public_interaction_policy(),
132 + );
133 + }
134 +
135 + /**
136 + * Returns the User-Object of the Author of the Post.
137 + *
138 + * If `single_user` mode is enabled, the Blog-User is returned.
139 + *
140 + * @return \Activitypub\Activity\Actor The User-Object.
141 + */
142 + public function get_actor_object() {
143 + if ( $this->actor_object ) {
144 + return $this->actor_object;
79 145 }
80 146
81 - $object->set_attributed_to( $this->get_attributed_to() );
82 - $object->set_content( $this->get_content() );
83 - $object->set_content_map(
84 - array(
85 - \strstr( \get_locale(), '_', true ) => $this->get_content(),
86 - )
87 - );
88 - $path = sprintf( 'users/%d/followers', intval( $wp_post->post_author ) );
147 + $blog_user = new Blog();
148 + $this->actor_object = $blog_user;
89 149
90 - $object->set_to(
91 - array(
92 - 'https://www.w3.org/ns/activitystreams#Public',
93 - get_rest_url_by_path( $path ),
94 - )
95 - );
96 - $object->set_cc( $this->get_cc() );
97 - $object->set_attachment( $this->get_attachments() );
98 - $object->set_tag( $this->get_tags() );
150 + if ( is_single_user() ) {
151 + return $blog_user;
152 + }
99 153
100 - return $object;
154 + $user = Actors::get_by_id( $this->item->post_author );
155 +
156 + if ( $user && ! is_wp_error( $user ) ) {
157 + $this->actor_object = $user;
158 + return $user;
159 + }
160 +
161 + return $blog_user;
101 162 }
102 163
103 164 /**
104 165 * Returns the ID of the Post.
@@ -105,8 +166,16 @@
105 166 *
106 167 * @return string The Posts ID.
107 168 */
108 169 public function get_id() {
170 + $last_legacy_id = (int) \get_option( 'activitypub_last_post_with_permalink_as_id', 0 );
171 + $post_id = (int) $this->item->ID;
172 +
173 + if ( $post_id > $last_legacy_id ) {
174 + // Generate URI based on post ID.
175 + return \add_query_arg( 'p', $post_id, \home_url( '/' ) );
176 + }
177 +
109 178 return $this->get_url();
110 179 }
111 180
112 181 /**
@@ -114,14 +183,25 @@
114 183 *
115 184 * @return string The Posts URL.
116 185 */
117 186 public function get_url() {
118 - $post = $this->wp_post;
187 + $post = $this->item;
119 188
120 - if ( 'trash' === get_post_status( $post ) ) {
121 - $permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
122 - } else {
123 - $permalink = \get_permalink( $post );
189 + switch ( \get_post_status( $post ) ) {
190 + case 'trash':
191 + $permalink = \get_post_meta( $post->ID, '_activitypub_canonical_url', true );
192 + break;
193 + case 'draft':
194 + // Get_sample_permalink is in wp-admin, not always loaded.
195 + if ( ! \function_exists( '\get_sample_permalink' ) ) {
196 + require_once ABSPATH . 'wp-admin/includes/post.php';
197 + }
198 + $sample = \get_sample_permalink( $post->ID );
199 + $permalink = \str_replace( array( '%pagename%', '%postname%' ), $sample[1], $sample[0] );
200 + break;
201 + default:
202 + $permalink = \get_permalink( $post );
203 + break;
124 204 }
125 205
126 206 return \esc_url( $permalink );
127 207 }
@@ -133,194 +213,210 @@
133 213 *
134 214 * @return string The User-URL.
135 215 */
136 216 protected function get_attributed_to() {
137 - if ( is_single_user() ) {
138 - $user = new Blog_User();
139 - return $user->get_url();
140 - }
141 -
142 - return Users::get_by_id( $this->wp_post->post_author )->get_url();
217 + return $this->get_actor_object()->get_id();
143 218 }
144 219
145 220 /**
146 - * Returns the Image Attachments for this Post, parsed from blocks.
147 - * @param int $max_images The maximum number of images to return.
148 - * @param array $image_ids The image IDs to append new IDs to.
221 + * Returns the featured image as `Image`.
149 222 *
150 - * @return array The image IDs.
223 + * @return array|null The Image or null if no image is available.
151 224 */
152 - protected function get_block_image_ids( $max_images, $image_ids = [] ) {
153 - $blocks = \parse_blocks( $this->wp_post->post_content );
154 - return self::get_image_ids_from_blocks( $blocks, $image_ids, $max_images );
225 + protected function get_image() {
226 + $post_id = $this->item->ID;
227 +
228 + // List post thumbnail first if this post has one.
229 + if (
230 + ! \function_exists( 'has_post_thumbnail' ) ||
231 + ! \has_post_thumbnail( $post_id )
232 + ) {
233 + return null;
234 + }
235 +
236 + $id = \get_post_thumbnail_id( $post_id );
237 + $image_size = 'large';
238 +
239 + /**
240 + * Filter the image URL returned for each post.
241 + *
242 + * @param array|false $thumbnail The image URL, or false if no image is available.
243 + * @param int $id The attachment ID.
244 + * @param string $image_size The image size to retrieve. Set to 'large' by default.
245 + */
246 + $thumbnail = apply_filters(
247 + 'activitypub_get_image',
248 + $this->get_attachment_image_src( $id, $image_size ),
249 + $id,
250 + $image_size
251 + );
252 +
253 + if ( ! $thumbnail ) {
254 + return null;
255 + }
256 +
257 + $mime_type = \get_post_mime_type( $id );
258 +
259 + $image = array(
260 + 'type' => 'Image',
261 + 'url' => \esc_url( $thumbnail[0] ),
262 + 'mediaType' => \esc_attr( $mime_type ),
263 + );
264 +
265 + $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
266 + if ( $alt ) {
267 + $image['name'] = \html_entity_decode( \wp_strip_all_tags( $alt ), ENT_QUOTES, 'UTF-8' );
268 + }
269 +
270 + return $image;
155 271 }
156 272
157 273 /**
158 - * Recursively get image IDs from blocks.
159 - * @param array $blocks The blocks to search for image IDs
160 - * @param array $image_ids The image IDs to append new IDs to
161 - * @param int $max_images The maximum number of images to return.
274 + * Returns an Icon, based on the Featured Image with a fallback to the site-icon.
162 275 *
163 - * @return array The image IDs.
276 + * @return array|null The Icon or null if no icon is available.
164 277 */
165 - protected static function get_image_ids_from_blocks( $blocks, $image_ids, $max_images ) {
166 - foreach ( $blocks as $block ) {
167 - // recurse into inner blocks
168 - if ( ! empty( $block['innerBlocks'] ) ) {
169 - $image_ids = self::get_image_ids_from_blocks( $block['innerBlocks'], $image_ids, $max_images );
170 - }
278 + protected function get_icon() {
279 + $post_id = $this->item->ID;
171 280
172 - switch ( $block['blockName'] ) {
173 - case 'core/image':
174 - case 'core/cover':
175 - if ( ! empty( $block['attrs']['id'] ) ) {
176 - $image_ids[] = $block['attrs']['id'];
177 - }
178 - break;
179 - case 'jetpack/slideshow':
180 - case 'jetpack/tiled-gallery':
181 - if ( ! empty( $block['attrs']['ids'] ) ) {
182 - $image_ids = array_merge( $image_ids, $block['attrs']['ids'] );
183 - }
184 - break;
185 - case 'jetpack/image-compare':
186 - if ( ! empty( $block['attrs']['beforeImageId'] ) ) {
187 - $image_ids[] = $block['attrs']['beforeImageId'];
188 - }
189 - if ( ! empty( $block['attrs']['afterImageId'] ) ) {
190 - $image_ids[] = $block['attrs']['afterImageId'];
191 - }
192 - break;
193 - }
281 + // List post thumbnail first if this post has one.
282 + if ( \has_post_thumbnail( $post_id ) ) {
283 + $id = \get_post_thumbnail_id( $post_id );
284 + } else {
285 + // Try site_logo, falling back to site_icon, first.
286 + $id = get_option( 'site_icon' );
287 + }
194 288
195 - // we could be at or over max, stop unneeded work
196 - if ( count( $image_ids ) >= $max_images ) {
197 - break;
198 - }
289 + if ( ! $id ) {
290 + return null;
199 291 }
200 292
201 - // still need to slice it because one gallery could knock us over the limit
202 - return \array_slice( $image_ids, 0, $max_images );
293 + $image_size = 'thumbnail';
294 +
295 + /**
296 + * Filter the image URL returned for each post.
297 + *
298 + * @param array|false $thumbnail The image URL, or false if no image is available.
299 + * @param int $id The attachment ID.
300 + * @param string $image_size The image size to retrieve. Set to 'large' by default.
301 + */
302 + $thumbnail = apply_filters(
303 + 'activitypub_get_image',
304 + $this->get_attachment_image_src( $id, $image_size ),
305 + $id,
306 + $image_size
307 + );
308 +
309 + if ( ! $thumbnail ) {
310 + return null;
311 + }
312 +
313 + $mime_type = \get_post_mime_type( $id );
314 +
315 + $image = array(
316 + 'type' => 'Image',
317 + 'url' => \esc_url( $thumbnail[0] ),
318 + 'mediaType' => \esc_attr( $mime_type ),
319 + );
320 +
321 + $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
322 + if ( $alt ) {
323 + $image['name'] = \html_entity_decode( \wp_strip_all_tags( $alt ), ENT_QUOTES, 'UTF-8' );
324 + }
325 +
326 + return $image;
203 327 }
204 328
205 329 /**
206 - * Generates all Image Attachments for a Post.
330 + * Generates all Media Attachments for a Post.
207 331 *
208 - * @return array The Image Attachments.
332 + * @return array The Attachments.
209 333 */
210 - protected function get_attachments() {
211 - $max_images = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) );
334 + protected function get_attachment() {
335 + if ( false !== $this->attachment ) {
336 + return $this->attachment;
337 + }
212 338
213 - $images = array();
339 + /*
340 + * Remove attachments from the Fediverse if a post was federated and then set back to draft.
341 + * Except in preview mode, where we want to show attachments.
342 + */
343 + if ( ! $this->is_preview() && 'draft' === \get_post_status( $this->item ) ) {
344 + $this->attachment = array();
214 345
215 - // max images can't be negative or zero
216 - if ( $max_images <= 0 ) {
217 - return $images;
346 + return $this->attachment;
218 347 }
219 348
220 - $id = $this->wp_post->ID;
349 + $max_media = \get_post_meta( $this->item->ID, 'activitypub_max_image_attachments', true );
221 350
222 - $image_ids = array();
351 + if ( ! is_numeric( $max_media ) ) {
352 + $max_media = \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS );
353 + }
223 354
224 - // list post thumbnail first if this post has one
225 - if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
226 - $image_ids[] = \get_post_thumbnail_id( $id );
227 - --$max_images;
228 - }
355 + /**
356 + * Filters the maximum number of media attachments allowed in a post.
357 + *
358 + * Despite the name suggesting only images, this filter controls the maximum number
359 + * of all media attachments (images, audio, and video) that can be included in an
360 + * ActivityPub post. The name is maintained for backwards compatibility.
361 + *
362 + * @param int $max_media Maximum number of media attachments. Default ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS.
363 + */
364 + $max_media = (int) \apply_filters( 'activitypub_max_image_attachments', $max_media );
229 365
230 - if ( $max_images > 0 ) {
231 - // first try to get images that are actually in the post content
232 - if ( site_supports_blocks() && \has_blocks( $this->wp_post->post_content ) ) {
233 - $block_image_ids = $this->get_block_image_ids( $max_images, $image_ids );
234 - $image_ids = \array_merge( $image_ids, $block_image_ids );
235 - } else {
236 - // fallback to images attached to the post
237 - $query = new \WP_Query(
238 - array(
239 - 'post_parent' => $id,
240 - 'post_status' => 'inherit',
241 - 'post_type' => 'attachment',
242 - 'post_mime_type' => 'image',
243 - 'order' => 'ASC',
244 - 'orderby' => 'menu_order ID',
245 - 'posts_per_page' => $max_images,
246 - )
247 - );
248 - foreach ( $query->get_posts() as $attachment ) {
249 - if ( ! \in_array( $attachment->ID, $image_ids, true ) ) {
250 - $image_ids[] = $attachment->ID;
251 - }
252 - }
253 - }
366 + if ( 0 === $max_media ) {
367 + $this->attachment = array();
368 +
369 + return $this->attachment;
254 370 }
255 371
256 - $image_ids = \array_unique( $image_ids );
372 + $media = array(
373 + 'image' => array(),
374 + 'audio' => array(),
375 + 'video' => array(),
376 + );
377 + $id = $this->item->ID;
257 378
258 - // get URLs for each image
259 - foreach ( $image_ids as $id ) {
260 - $image_size = 'full';
379 + // List post thumbnail first if this post has one.
380 + if ( \has_post_thumbnail( $id ) ) {
381 + $media['image'][] = array( 'id' => \get_post_thumbnail_id( $id ) );
382 + }
261 383
262 - /**
263 - * Filter the image URL returned for each post.
264 - *
265 - * @param array|false $thumbnail The image URL, or false if no image is available.
266 - * @param int $id The attachment ID.
267 - * @param string $image_size The image size to retrieve. Set to 'full' by default.
268 - */
269 - $thumbnail = apply_filters(
270 - 'activitypub_get_image',
271 - $this->get_image( $id, $image_size ),
272 - $id,
273 - $image_size
274 - );
384 + $media = $this->get_enclosures( $media );
275 385
276 - if ( $thumbnail ) {
277 - $mimetype = \get_post_mime_type( $id );
278 - $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
279 - $image = array(
280 - 'type' => 'Image',
281 - 'url' => $thumbnail[0],
282 - 'mediaType' => $mimetype,
283 - );
284 -
285 - if ( $alt ) {
286 - $image['name'] = $alt;
287 - }
288 - $images[] = $image;
289 - }
386 + if ( site_supports_blocks() && \has_blocks( $this->item->post_content ) ) {
387 + $media = $this->get_block_attachments( $media, $max_media );
388 + } else {
389 + $media = $this->parse_html_images( $media, $max_media, $this->item->post_content );
290 390 }
291 391
292 - return $images;
293 - }
392 + $media = $this->filter_media_by_object_type( $media, \get_post_format( $this->item ), $this->item );
393 + $media = $this->filter_unique_attachments( $media );
394 + $media = \array_slice( $media, 0, $max_media );
294 395
295 - /**
296 - * Return details about an image attachment.
297 - *
298 - * @param int $id The attachment ID.
299 - * @param string $image_size The image size to retrieve. Set to 'full' by default.
300 - *
301 - * @return array|false Array of image data, or boolean false if no image is available.
302 - */
303 - protected function get_image( $id, $image_size = 'full' ) {
304 396 /**
305 - * Hook into the image retrieval process. Before image retrieval.
397 + * Filter the attachment IDs for a post.
306 398 *
307 - * @param int $id The attachment ID.
308 - * @param string $image_size The image size to retrieve. Set to 'full' by default.
399 + * @param array $media The media array grouped by type.
400 + * @param \WP_Post $item The post object.
401 + *
402 + * @return array The filtered attachment IDs.
309 403 */
310 - do_action( 'activitypub_get_image_pre', $id, $image_size );
404 + $media = \apply_filters( 'activitypub_attachment_ids', $media, $this->item );
311 405
312 - $thumbnail = \wp_get_attachment_image_src( $id, $image_size );
406 + $attachments = \array_filter( \array_map( array( $this, 'transform_attachment' ), $media ) );
313 407
314 408 /**
315 - * Hook into the image retrieval process. After image retrieval.
409 + * Filter the attachments for a post.
316 410 *
317 - * @param int $id The attachment ID.
318 - * @param string $image_size The image size to retrieve. Set to 'full' by default.
411 + * @param array $attachments The attachments.
412 + * @param \WP_Post $item The post object.
413 + *
414 + * @return array The filtered attachments.
319 415 */
320 - do_action( 'activitypub_get_image_post', $id, $image_size );
416 + $this->attachment = \apply_filters( 'activitypub_attachments', $attachments, $this->item );
321 417
322 - return $thumbnail;
418 + return $this->attachment;
323 419 }
324 420
325 421 /**
326 422 * Returns the ActivityStreams 2.0 Object-Type for a Post based on the
@@ -329,83 +425,47 @@
329 425 * @see https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
330 426 *
331 427 * @return string The Object-Type.
332 428 */
333 - protected function get_object_type() {
334 - if ( 'wordpress-post-format' !== \get_option( 'activitypub_object_type', 'note' ) ) {
335 - return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) );
429 + protected function get_type() {
430 + $post_format_setting = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE );
431 +
432 + if ( 'wordpress-post-format' !== $post_format_setting ) {
433 + return \ucfirst( $post_format_setting );
336 434 }
337 435
338 - $post_type = \get_post_type( $this->wp_post );
339 - switch ( $post_type ) {
340 - case 'post':
341 - $post_format = \get_post_format( $this->wp_post );
342 - switch ( $post_format ) {
343 - case 'aside':
344 - case 'status':
345 - case 'quote':
346 - case 'note':
347 - $object_type = 'Note';
348 - break;
349 - case 'gallery':
350 - case 'image':
351 - $object_type = 'Image';
352 - break;
353 - case 'video':
354 - $object_type = 'Video';
355 - break;
356 - case 'audio':
357 - $object_type = 'Audio';
358 - break;
359 - default:
360 - $object_type = 'Article';
361 - break;
362 - }
363 - break;
364 - case 'page':
365 - $object_type = 'Page';
366 - break;
367 - case 'attachment':
368 - $mime_type = \get_post_mime_type();
369 - $media_type = \preg_replace( '/(\/[a-zA-Z]+)/i', '', $mime_type );
370 - switch ( $media_type ) {
371 - case 'audio':
372 - $object_type = 'Audio';
373 - break;
374 - case 'video':
375 - $object_type = 'Video';
376 - break;
377 - case 'image':
378 - $object_type = 'Image';
379 - break;
380 - }
381 - break;
382 - default:
383 - $object_type = 'Article';
384 - break;
436 + // Check if the post has a title.
437 + if ( ! \post_type_supports( $this->item->post_type, 'title' ) || ! $this->item->post_title ) {
438 + return 'Note';
385 439 }
386 440
441 + // Default to Note.
442 + $object_type = 'Note';
443 + $post_type = \get_post_type( $this->item );
444 +
445 + if ( 'page' === $post_type ) {
446 + $object_type = 'Page';
447 + } elseif ( ! \get_post_format( $this->item ) ) {
448 + $object_type = 'Article';
449 + }
450 +
387 451 return $object_type;
388 452 }
389 453
390 454 /**
391 - * Returns a list of Mentions, used in the Post.
455 + * Returns the Audience for the Post.
392 456 *
393 - * @see https://docs.joinmastodon.org/spec/activitypub/#Mention
394 - *
395 - * @return array The list of Mentions.
457 + * @return string|null The audience.
396 458 */
397 - protected function get_cc() {
398 - $cc = array();
459 + public function get_audience() {
460 + $actor_mode = \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
399 461
400 - $mentions = $this->get_mentions();
401 - if ( $mentions ) {
402 - foreach ( $mentions as $url ) {
403 - $cc[] = $url;
404 - }
462 + if ( ACTIVITYPUB_ACTOR_AND_BLOG_MODE === $actor_mode ) {
463 + $blog = new Blog();
464 + return $blog->get_id();
405 465 }
406 466
407 - return $cc;
467 + return null;
408 468 }
409 469
410 470 /**
411 471 * Returns a list of Tags, used in the Post.
@@ -413,39 +473,92 @@
413 473 * This includes Hash-Tags and Mentions.
414 474 *
415 475 * @return array The list of Tags.
416 476 */
417 - protected function get_tags() {
418 - $tags = array();
477 + protected function get_tag() {
478 + if ( false !== $this->tags ) {
479 + return $this->tags;
480 + }
419 481
420 - $post_tags = \get_the_tags( $this->wp_post->ID );
482 + $tags = parent::get_tag();
483 +
484 + $post_tags = \get_the_tags( $this->item->ID );
421 485 if ( $post_tags ) {
422 486 foreach ( $post_tags as $post_tag ) {
423 - $tag = array(
487 + // Tag can be empty.
488 + if ( ! $post_tag ) {
489 + continue;
490 + }
491 +
492 + $tags[] = array(
424 493 'type' => 'Hashtag',
425 494 'href' => \esc_url( \get_tag_link( $post_tag->term_id ) ),
426 495 'name' => esc_hashtag( $post_tag->name ),
427 496 );
428 - $tags[] = $tag;
429 497 }
430 498 }
431 499
432 - $mentions = $this->get_mentions();
433 - if ( $mentions ) {
434 - foreach ( $mentions as $mention => $url ) {
435 - $tag = array(
436 - 'type' => 'Mention',
437 - 'href' => \esc_url( $url ),
438 - 'name' => \esc_html( $mention ),
439 - );
440 - $tags[] = $tag;
441 - }
500 + $this->tags = \array_unique( $tags, SORT_REGULAR );
501 +
502 + return $this->tags;
503 + }
504 +
505 + /**
506 + * Returns the summary for the ActivityPub Item.
507 + *
508 + * The summary will be generated based on the user settings and only if the
509 + * object type is not set to `note`.
510 + *
511 + * @return string|null The summary or null if the object type is `note`.
512 + */
513 + protected function get_summary() {
514 + if ( 'Note' === $this->get_type() ) {
515 + return null;
442 516 }
443 517
444 - return $tags;
518 + if ( false !== $this->summary ) {
519 + return $this->summary;
520 + }
521 +
522 + // Remove Teaser from drafts.
523 + if ( ! $this->is_preview() && 'draft' === \get_post_status( $this->item ) ) {
524 + $this->summary = \__( '(This post is being modified)', 'activitypub' );
525 +
526 + return $this->summary;
527 + }
528 +
529 + $this->summary = generate_post_summary( $this->item );
530 +
531 + return $this->summary;
445 532 }
446 533
447 534 /**
535 + * Returns the title for the ActivityPub Item.
536 + *
537 + * The title will be generated based on the user settings and only if the
538 + * object type is not set to `note`.
539 + *
540 + * @return string|null The title or null if the object type is `note`.
541 + */
542 + protected function get_name() {
543 + if ( 'Note' === $this->get_type() ) {
544 + return null;
545 + }
546 +
547 + $title = \get_the_title( $this->item->ID );
548 +
549 + if ( ! $title ) {
550 + return null;
551 + }
552 +
553 + return \wp_strip_all_tags(
554 + \html_entity_decode(
555 + $title
556 + )
557 + );
558 + }
559 +
560 + /**
448 561 * Returns the content for the ActivityPub Item.
449 562 *
450 563 * The content will be generated based on the user settings.
451 564 *
@@ -451,68 +564,552 @@
451 564 *
452 565 * @return string The content.
453 566 */
454 567 protected function get_content() {
568 + if ( false !== $this->content ) {
569 + return $this->content;
570 + }
571 +
572 + // Remove Content from drafts.
573 + if ( ! $this->is_preview() && 'draft' === \get_post_status( $this->item ) ) {
574 + $this->content = \__( '(This post is being modified)', 'activitypub' );
575 +
576 + return $this->content;
577 + }
578 +
455 579 global $post;
456 580
581 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
582 + $post = $this->item;
583 + $content = $this->get_post_content_template();
584 +
457 585 /**
458 586 * Provides an action hook so plugins can add their own hooks/filters before AP content is generated.
459 587 *
460 588 * 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.
461 589 *
462 - * @param WP_Post $post The post object.
590 + * @param \WP_Post $post The post object.
463 591 */
464 - do_action( 'activitypub_before_get_content', $post );
592 + \do_action( 'activitypub_before_get_content', $post );
465 593
466 - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
467 - $post = $this->wp_post;
468 - $content = $this->get_post_content_template();
594 + // It seems that shortcodes are only applied to published posts.
595 + if ( is_preview() ) {
596 + $post->post_status = 'publish';
597 + }
469 598
470 599 // Register our shortcodes just in time.
471 600 Shortcodes::register();
472 601 // Fill in the shortcodes.
473 - setup_postdata( $post );
474 - $content = do_shortcode( $content );
475 - wp_reset_postdata();
602 + \setup_postdata( $post );
603 + $content = \do_shortcode( $content );
604 + \wp_reset_postdata();
476 605
477 - $content = \wpautop( $content );
478 - $content = \preg_replace( '/[\n\r\t]/', '', $content );
479 - $content = \trim( $content );
606 + // Don't need these anymore, should never appear in a post.
607 + Shortcodes::unregister();
480 608
481 - $content = \apply_filters( 'activitypub_the_content', $content, $post );
609 + /**
610 + * Filters the post content after it was transformed for ActivityPub.
611 + *
612 + * @param string $content The transformed post content.
613 + * @param \WP_Post $post The post object being transformed.
614 + */
615 + $this->content = \apply_filters( 'activitypub_the_content', $content, $post );
482 616
483 - // Don't need these any more, should never appear in a post.
484 - Shortcodes::unregister();
617 + return $this->content;
618 + }
485 619
486 - return $content;
620 + /**
621 + * Generate HTML @ link for reply block.
622 + *
623 + * @deprecated 7.4.0 Use {@see Blocks::generate_reply_link()}.
624 + *
625 + * @param string $block_content The block content.
626 + * @param array $block The block data.
627 + *
628 + * @return string The HTML @ link.
629 + */
630 + public function generate_reply_link( $block_content, $block ) {
631 + _deprecated_function( __METHOD__, '7.4.0', 'Activitypub\Blocks::generate_reply_link' );
632 +
633 + return Blocks::generate_reply_link( $block_content, $block );
487 634 }
488 635
489 636 /**
637 + * Returns the in-reply-to URL of the post.
638 + *
639 + * @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-inreplyto
640 + *
641 + * @return string|array|null The in-reply-to URL of the post.
642 + */
643 + protected function get_in_reply_to() {
644 + if ( false !== $this->in_reply_to ) {
645 + return $this->in_reply_to;
646 + }
647 +
648 + if ( ! site_supports_blocks() ) {
649 + $this->in_reply_to = null;
650 + return $this->in_reply_to;
651 + }
652 +
653 + $reply_urls = array();
654 + $blocks = \parse_blocks( $this->item->post_content );
655 +
656 + foreach ( $blocks as $block ) {
657 + if ( 'activitypub/reply' === $block['blockName'] && isset( $block['attrs']['url'] ) ) {
658 +
659 + // Check if the URL has been validated as ActivityPub. Default to true for backwards compatibility.
660 + if ( $block['attrs']['isValidActivityPub'] ?? true ) {
661 + $reply_urls[] = $block['attrs']['url'];
662 + }
663 + }
664 + }
665 +
666 + if ( empty( $reply_urls ) ) {
667 + $this->in_reply_to = null;
668 +
669 + return $this->in_reply_to;
670 + }
671 +
672 + if ( 1 === count( $reply_urls ) ) {
673 + $this->in_reply_to = \current( $reply_urls );
674 +
675 + return $this->in_reply_to;
676 + }
677 +
678 + $this->in_reply_to = \array_values( \array_unique( $reply_urls ) );
679 +
680 + return $this->in_reply_to;
681 + }
682 +
683 + /**
684 + * Returns the published date of the post.
685 + *
686 + * @return string The published date of the post.
687 + */
688 + protected function get_published() {
689 + $published = \strtotime( $this->item->post_date_gmt );
690 +
691 + return \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, $published );
692 + }
693 +
694 + /**
695 + * Returns the updated date of the post.
696 + *
697 + * @return string|null The updated date of the post.
698 + */
699 + protected function get_updated() {
700 + $published = \strtotime( $this->item->post_date_gmt );
701 + $updated = \strtotime( $this->item->post_modified_gmt );
702 +
703 + if ( $updated > $published ) {
704 + return \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, $updated );
705 + }
706 +
707 + return null;
708 + }
709 +
710 + /**
711 + * Helper function to extract the @-Mentions from the post content.
712 + *
713 + * @return array The list of @-Mentions.
714 + */
715 + protected function get_mentions() {
716 + if ( false !== $this->mentions ) {
717 + return $this->mentions;
718 + }
719 +
720 + /**
721 + * Filter the mentions in the post content.
722 + *
723 + * @param array $mentions The mentions.
724 + * @param string $content The post content.
725 + * @param \WP_Post $post The post object.
726 + *
727 + * @return array The filtered mentions.
728 + */
729 + $this->mentions = apply_filters(
730 + 'activitypub_extract_mentions',
731 + array(),
732 + $this->item->post_content . ' ' . $this->item->post_excerpt,
733 + $this->item
734 + );
735 +
736 + return $this->mentions;
737 + }
738 +
739 + /**
740 + * Transform Embed blocks to block level link.
741 + *
742 + * Remote servers will simply drop iframe elements, rendering incomplete content.
743 + *
744 + * @deprecated 7.4.0 Use {@see Blocks::revert_embed_links()}.
745 + *
746 + * @see https://www.w3.org/TR/activitypub/#security-sanitizing-content
747 + * @see https://www.w3.org/wiki/ActivityPub/Primer/HTML
748 + *
749 + * @param string $block_content The block content (html).
750 + * @param object $block The block object.
751 + *
752 + * @return string A block level link
753 + */
754 + public function revert_embed_links( $block_content, $block ) {
755 + _deprecated_function( __METHOD__, '7.4.0', 'Activitypub\Blocks::revert_embed_links' );
756 +
757 + return Blocks::revert_embed_links( $block_content, $block );
758 + }
759 +
760 + /**
761 + * Check if the post is a preview.
762 + *
763 + * @return boolean True if the post is a preview, false otherwise.
764 + */
765 + private function is_preview() {
766 + return defined( 'ACTIVITYPUB_PREVIEW' ) && ACTIVITYPUB_PREVIEW;
767 + }
768 +
769 + /**
770 + * Get enclosures for a post.
771 + *
772 + * @param array $media The media array grouped by type.
773 + *
774 + * @return array The media array extended with enclosures.
775 + */
776 + protected function get_enclosures( $media ) {
777 + $enclosures = get_enclosures( $this->item->ID );
778 +
779 + if ( ! $enclosures ) {
780 + return $media;
781 + }
782 +
783 + foreach ( $enclosures as $enclosure ) {
784 + // Check if URL is an attachment.
785 + $attachment_id = \attachment_url_to_postid( $enclosure['url'] );
786 +
787 + if ( $attachment_id ) {
788 + $enclosure['id'] = $attachment_id;
789 + $enclosure['url'] = \wp_get_attachment_url( $attachment_id );
790 + $enclosure['mediaType'] = \get_post_mime_type( $attachment_id );
791 + }
792 +
793 + $mime_type = $enclosure['mediaType'];
794 + $media_type = \strtok( $mime_type, '/' );
795 + $enclosure['type'] = \ucfirst( $media_type );
796 +
797 + switch ( $media_type ) {
798 + case 'image':
799 + $media['image'][] = $enclosure;
800 + break;
801 + case 'audio':
802 + $media['audio'][] = $enclosure;
803 + break;
804 + case 'video':
805 + $media['video'][] = $enclosure;
806 + break;
807 + }
808 + }
809 +
810 + return $media;
811 + }
812 +
813 + /**
814 + * Get media attachments from blocks. They will be formatted as ActivityPub attachments, not as WP attachments.
815 + *
816 + * @param array $media The media array grouped by type.
817 + * @param int $max_media The maximum number of attachments to return.
818 + *
819 + * @return array The attachments.
820 + */
821 + protected function get_block_attachments( $media, $max_media ) {
822 + // Max media can't be negative or zero.
823 + if ( $max_media <= 0 ) {
824 + return array();
825 + }
826 +
827 + $blocks = \parse_blocks( $this->item->post_content );
828 +
829 + return $this->get_media_from_blocks( $blocks, $media );
830 + }
831 +
832 + /**
833 + * Recursively get media IDs from blocks.
834 + *
835 + * @param array $blocks The blocks to search for media IDs.
836 + * @param array $media The media IDs to append new IDs to.
837 + *
838 + * @return array The image IDs.
839 + */
840 + protected function get_media_from_blocks( $blocks, $media ) {
841 + foreach ( $blocks as $block ) {
842 + // Recurse into inner blocks.
843 + if ( ! empty( $block['innerBlocks'] ) ) {
844 + $media = $this->get_media_from_blocks( $block['innerBlocks'], $media );
845 + }
846 +
847 + switch ( $block['blockName'] ) {
848 + case 'core/image':
849 + case 'core/cover':
850 + if ( ! empty( $block['attrs']['id'] ) ) {
851 + $alt = '';
852 + $check = preg_match( '/<img.*?alt\s*=\s*([\"\'])(.*?)\1.*>/i', $block['innerHTML'], $match );
853 +
854 + if ( $check ) {
855 + $alt = $match[2];
856 + }
857 +
858 + $found = false;
859 + foreach ( $media['image'] as $i => $image ) {
860 + if ( isset( $image['id'] ) && $image['id'] === $block['attrs']['id'] ) {
861 + $media['image'][ $i ]['alt'] = $alt;
862 + $found = true;
863 + break;
864 + }
865 + }
866 +
867 + if ( ! $found ) {
868 + $media['image'][] = array(
869 + 'id' => $block['attrs']['id'],
870 + 'alt' => $alt,
871 + );
872 + }
873 + }
874 + break;
875 + case 'core/audio':
876 + if ( ! empty( $block['attrs']['id'] ) ) {
877 + $media['audio'][] = array( 'id' => $block['attrs']['id'] );
878 + }
879 + break;
880 + case 'core/video':
881 + case 'videopress/video':
882 + if ( ! empty( $block['attrs']['id'] ) ) {
883 + $media['video'][] = array( 'id' => $block['attrs']['id'] );
884 + }
885 + break;
886 + case 'jetpack/slideshow':
887 + case 'jetpack/tiled-gallery':
888 + if ( ! empty( $block['attrs']['ids'] ) ) {
889 + $media['image'] = array_merge(
890 + $media['image'],
891 + array_map(
892 + static function ( $id ) {
893 + return array( 'id' => $id );
894 + },
895 + $block['attrs']['ids']
896 + )
897 + );
898 + }
899 + break;
900 + case 'jetpack/image-compare':
901 + if ( ! empty( $block['attrs']['beforeImageId'] ) ) {
902 + $media['image'][] = array( 'id' => $block['attrs']['beforeImageId'] );
903 + }
904 + if ( ! empty( $block['attrs']['afterImageId'] ) ) {
905 + $media['image'][] = array( 'id' => $block['attrs']['afterImageId'] );
906 + }
907 + break;
908 + }
909 + }
910 +
911 + return $media;
912 + }
913 +
914 + /**
915 + * Filter media IDs by object type.
916 + *
917 + * @param array $media The media array grouped by type.
918 + * @param string $type The object type.
919 + * @param \WP_Post $item The post object.
920 + *
921 + * @return array The filtered media IDs.
922 + */
923 + protected function filter_media_by_object_type( $media, $type, $item ) {
924 + /**
925 + * Filter the object type for media attachments.
926 + *
927 + * @param string $type The object type.
928 + * @param \WP_Post $item The post object.
929 + *
930 + * @return string The filtered object type.
931 + */
932 + $type = \apply_filters( 'filter_media_by_object_type', \strtolower( $type ), $item );
933 +
934 + if ( ! empty( $media[ $type ] ) ) {
935 + return $media[ $type ];
936 + }
937 +
938 + return array_filter( array_merge( ...array_values( $media ) ) );
939 + }
940 +
941 + /**
942 + * Converts a WordPress Attachment to an ActivityPub Attachment.
943 + *
944 + * @deprecated 7.2.0 Use {@see Base::transform_attachment()} instead.
945 + *
946 + * @param array $media The Attachment array.
947 + *
948 + * @return array The ActivityPub Attachment.
949 + */
950 + public function wp_attachment_to_activity_attachment( $media ) {
951 + _deprecated_function( __METHOD__, '7.2.0', '\Activitypub\Transformer\Base::transform_attachment()' );
952 +
953 + return parent::transform_attachment( $media );
954 + }
955 +
956 + /**
957 + * Get the context of the post.
958 + *
959 + * @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-context
960 + *
961 + * @return string The context of the post.
962 + */
963 + protected function get_context() {
964 + return get_rest_url_by_path( sprintf( 'posts/%d/context', $this->item->ID ) );
965 + }
966 +
967 + /**
490 968 * Gets the template to use to generate the content of the activitypub item.
491 969 *
492 970 * @return string The Template.
493 971 */
494 972 protected function get_post_content_template() {
495 - if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
496 - return "[ap_excerpt]\n\n[ap_permalink type=\"html\"]";
973 + $content = \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
974 + $template = $content ?: ACTIVITYPUB_CUSTOM_POST_CONTENT;
975 +
976 + $post_format_setting = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE );
977 + $type = $this->get_type();
978 +
979 + if ( 'wordpress-post-format' === $post_format_setting ) {
980 + $template = '';
981 +
982 + /*
983 + * If the post is a note, not a reply, and does not have mentions
984 + * force the inclusion of the post title.
985 + */
986 + if (
987 + 'Note' === $type
988 + && empty( $this->get_in_reply_to() )
989 + && empty( $this->get_mentions() )
990 + ) {
991 + $template .= '[ap_title type="html"]';
992 + }
993 +
994 + $template .= '[ap_content]';
497 995 }
498 996
499 - if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
500 - return "[ap_title]\n\n[ap_permalink type=\"html\"]";
997 + /**
998 + * Filters the template used to generate ActivityPub object content.
999 + *
1000 + * This filter allows developers to modify the template that determines how post
1001 + * content is formatted in ActivityPub objects. The template can include special
1002 + * shortcodes like [ap_title] and [ap_content] that are processed during content
1003 + * generation.
1004 + *
1005 + * @since 7.6.0 Added the $type parameter.
1006 + *
1007 + * @param string $template The template string containing shortcodes.
1008 + * @param \WP_Post $item The WordPress post object being transformed.
1009 + * @param string $type ActivityStreams 2.0 Object-Type for the post.
1010 + */
1011 + return apply_filters( 'activitypub_object_content_template', $template, $this->item, $type );
1012 + }
1013 +
1014 + /**
1015 + * Get the replies Collection.
1016 + *
1017 + * @return array|null The replies collection on success or null on failure.
1018 + */
1019 + public function get_replies() {
1020 + return Replies::get_collection( $this->item );
1021 + }
1022 +
1023 + /**
1024 + * Get the likes Collection.
1025 + *
1026 + * @return array The likes collection.
1027 + */
1028 + public function get_likes() {
1029 + return array(
1030 + 'id' => get_rest_url_by_path( sprintf( 'posts/%d/likes', $this->item->ID ) ),
1031 + 'type' => 'Collection',
1032 + 'totalItems' => Interactions::count_by_type( $this->item->ID, 'like' ),
1033 + );
1034 + }
1035 +
1036 + /**
1037 + * Get the shares Collection.
1038 + *
1039 + * @return array The Shares collection.
1040 + */
1041 + public function get_shares() {
1042 + return array(
1043 + 'id' => get_rest_url_by_path( sprintf( 'posts/%d/shares', $this->item->ID ) ),
1044 + 'type' => 'Collection',
1045 + 'totalItems' => Interactions::count_by_type( $this->item->ID, 'repost' ),
1046 + );
1047 + }
1048 +
1049 + /**
1050 + * Get the preview of the post.
1051 + *
1052 + * @return array|null The preview of the post or null if the post is not an Article.
1053 + */
1054 + public function get_preview() {
1055 + if ( 'Article' !== $this->get_type() ) {
1056 + return null;
501 1057 }
502 1058
503 - if ( 'content' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
504 - return "[ap_content]\n\n[ap_permalink type=\"html\"]\n\n[ap_hashtags]";
1059 + return array(
1060 + 'type' => 'Note',
1061 + 'content' => $this->get_summary(),
1062 + );
1063 + }
1064 +
1065 + /**
1066 + * Get the quote policy.
1067 + *
1068 + * @return array The quote policy.
1069 + */
1070 + private function get_quote_policy() {
1071 + switch ( \get_post_meta( $this->item->ID, 'activitypub_interaction_policy_quote', true ) ) {
1072 + case ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS:
1073 + return array( 'automaticApproval' => get_rest_url_by_path( sprintf( 'actors/%d/followers', $this->item->post_author ) ) );
1074 +
1075 + case ACTIVITYPUB_INTERACTION_POLICY_ME:
1076 + return array( 'automaticApproval' => $this->get_self_interaction_policy() );
1077 +
1078 + default:
1079 + return $this->get_public_interaction_policy();
505 1080 }
1081 + }
506 1082
507 - return \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
1083 + /**
1084 + * Get the public interaction policy.
1085 + *
1086 + * @return array The public interaction policy.
1087 + */
1088 + private function get_public_interaction_policy() {
1089 + return array(
1090 + 'automaticApproval' => 'https://www.w3.org/ns/activitystreams#Public',
1091 + 'always' => 'https://www.w3.org/ns/activitystreams#Public',
1092 + );
508 1093 }
509 1094
510 1095 /**
511 - * Helper function to get the @-Mentions from the post content.
1096 + * Get the actor ID(s) for the `me` audience for use in interaction policies.
512 1097 *
513 - * @return array The list of @-Mentions.
1098 + * @return string|array The actor ID(s).
514 1099 */
515 - protected function get_mentions() {
516 - return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_post->post_content, $this->wp_post );
1100 + private function get_self_interaction_policy() {
1101 + switch ( \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
1102 + case ACTIVITYPUB_BLOG_MODE:
1103 + return ( new Blog() )->get_id();
1104 +
1105 + case ACTIVITYPUB_ACTOR_AND_BLOG_MODE:
1106 + return array(
1107 + $this->get_actor_object()->get_id(),
1108 + ( new Blog() )->get_id(),
1109 + );
1110 +
1111 + default:
1112 + return $this->get_actor_object()->get_id();
1113 + }
517 1114 }
518 1115 }