PluginProbe
ActivityPub / 1.0.6
ActivityPub v1.0.6
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 1.0.6, at includes/transformer/class-post.php

513 lines 13.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\Collection\Users;
6 use Activitypub\Model\Blog_User;
7 use Activitypub\Activity\Base_Object;
8
9 use function Activitypub\esc_hashtag;
10 use function Activitypub\is_single_user;
11 use function Activitypub\get_rest_url_by_path;
12 use function Activitypub\site_supports_blocks;
13
14 /**
15 * WordPress Post Transformer
16 *
17 * The Post Transformer is responsible for transforming a WP_Post object into different othe
18 * Object-Types.
19 *
20 * Currently supported are:
21 *
22 * - Activitypub\Activity\Base_Object
23 */
24 class Post {
25
26 /**
27 * The WP_Post object.
28 *
29 * @var WP_Post
30 */
31 protected $wp_post;
32
33 /**
34 * Static function to Transform a WP_Post Object.
35 *
36 * This helps to chain the output of the Transformer.
37 *
38 * @param WP_Post $wp_post The WP_Post object
39 *
40 * @return void
41 */
42 public static function transform( WP_Post $wp_post ) {
43 return new static( $wp_post );
44 }
45
46 /**
47 *
48 *
49 * @param WP_Post $wp_post
50 */
51 public function __construct( WP_Post $wp_post ) {
52 $this->wp_post = $wp_post;
53 }
54
55 /**
56 * Transforms the WP_Post object to an ActivityPub Object
57 *
58 * @see \Activitypub\Activity\Base_Object
59 *
60 * @return \Activitypub\Activity\Base_Object The ActivityPub Object
61 */
62 public function to_object() {
63 $wp_post = $this->wp_post;
64 $object = new Base_Object();
65
66 $object->set_id( $this->get_id() );
67 $object->set_url( $this->get_url() );
68 $object->set_type( $this->get_object_type() );
69
70 $published = \strtotime( $wp_post->post_date_gmt );
71
72 $object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
73
74 $updated = \strtotime( $wp_post->post_modified_gmt );
75
76 if ( $updated > $published ) {
77 $object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
78 }
79
80 $object->set_attributed_to( $this->get_attributed_to() );
81 $object->set_content( $this->get_content() );
82 $object->set_content_map(
83 array(
84 \strstr( \get_locale(), '_', true ) => $this->get_content(),
85 )
86 );
87 $path = sprintf( 'users/%d/followers', intval( $wp_post->post_author ) );
88
89 $object->set_to(
90 array(
91 'https://www.w3.org/ns/activitystreams#Public',
92 get_rest_url_by_path( $path ),
93 )
94 );
95 $object->set_cc( $this->get_cc() );
96 $object->set_attachment( $this->get_attachments() );
97 $object->set_tag( $this->get_tags() );
98
99 return $object;
100 }
101
102 /**
103 * Returns the ID of the Post.
104 *
105 * @return string The Posts ID.
106 */
107 public function get_id() {
108 return $this->get_url();
109 }
110
111 /**
112 * Returns the URL of the Post.
113 *
114 * @return string The Posts URL.
115 */
116 public function get_url() {
117 $post = $this->wp_post;
118
119 if ( 'trash' === get_post_status( $post ) ) {
120 $permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
121 } else {
122 $permalink = \get_permalink( $post );
123 }
124
125 return \esc_url( $permalink );
126 }
127
128 /**
129 * Returns the User-URL of the Author of the Post.
130 *
131 * If `single_user` mode is enabled, the URL of the Blog-User is returned.
132 *
133 * @return string The User-URL.
134 */
135 protected function get_attributed_to() {
136 if ( is_single_user() ) {
137 $user = new Blog_User();
138 return $user->get_url();
139 }
140
141 return Users::get_by_id( $this->wp_post->post_author )->get_url();
142 }
143
144 /**
145 * Returns the Image Attachments for this Post, parsed from blocks.
146 * @param int $max_images The maximum number of images to return.
147 * @param array $image_ids The image IDs to append new IDs to.
148 *
149 * @return array The image IDs.
150 */
151 protected function get_block_image_ids( $max_images, $image_ids = [] ) {
152 $blocks = \parse_blocks( $this->wp_post->post_content );
153 return self::get_image_ids_from_blocks( $blocks, $image_ids, $max_images );
154 }
155
156 /**
157 * Recursively get image IDs from blocks.
158 * @param array $blocks The blocks to search for image IDs
159 * @param array $image_ids The image IDs to append new IDs to
160 * @param int $max_images The maximum number of images to return.
161 *
162 * @return array The image IDs.
163 */
164 protected static function get_image_ids_from_blocks( $blocks, $image_ids, $max_images ) {
165 foreach ( $blocks as $block ) {
166 // recurse into inner blocks
167 if ( ! empty( $block['innerBlocks'] ) ) {
168 $image_ids = self::get_image_ids_from_blocks( $block['innerBlocks'], $image_ids, $max_images );
169 }
170
171 switch ( $block['blockName'] ) {
172 case 'core/image':
173 case 'core/cover':
174 if ( ! empty( $block['attrs']['id'] ) ) {
175 $image_ids[] = $block['attrs']['id'];
176 }
177 break;
178 case 'jetpack/slideshow':
179 case 'jetpack/tiled-gallery':
180 if ( ! empty( $block['attrs']['ids'] ) ) {
181 $image_ids = array_merge( $image_ids, $block['attrs']['ids'] );
182 }
183 break;
184 case 'jetpack/image-compare':
185 if ( ! empty( $block['attrs']['beforeImageId'] ) ) {
186 $image_ids[] = $block['attrs']['beforeImageId'];
187 }
188 if ( ! empty( $block['attrs']['afterImageId'] ) ) {
189 $image_ids[] = $block['attrs']['afterImageId'];
190 }
191 break;
192 }
193
194 // we could be at or over max, stop unneeded work
195 if ( count( $image_ids ) >= $max_images ) {
196 break;
197 }
198 }
199
200 // still need to slice it because one gallery could knock us over the limit
201 return \array_slice( $image_ids, 0, $max_images );
202 }
203
204 /**
205 * Generates all Image Attachments for a Post.
206 *
207 * @return array The Image Attachments.
208 */
209 protected function get_attachments() {
210 $max_images = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) );
211
212 $images = array();
213
214 // max images can't be negative or zero
215 if ( $max_images <= 0 ) {
216 return $images;
217 }
218
219 $id = $this->wp_post->ID;
220
221 $image_ids = array();
222
223 // list post thumbnail first if this post has one
224 if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
225 $image_ids[] = \get_post_thumbnail_id( $id );
226 --$max_images;
227 }
228
229 if ( $max_images > 0 ) {
230 // first try to get images that are actually in the post content
231 if ( site_supports_blocks() && \has_blocks( $this->wp_post->post_content ) ) {
232 $block_image_ids = $this->get_block_image_ids( $max_images, $image_ids );
233 $image_ids = \array_merge( $image_ids, $block_image_ids );
234 } else {
235 // fallback to images attached to the post
236 $query = new \WP_Query(
237 array(
238 'post_parent' => $id,
239 'post_status' => 'inherit',
240 'post_type' => 'attachment',
241 'post_mime_type' => 'image',
242 'order' => 'ASC',
243 'orderby' => 'menu_order ID',
244 'posts_per_page' => $max_images,
245 )
246 );
247 foreach ( $query->get_posts() as $attachment ) {
248 if ( ! \in_array( $attachment->ID, $image_ids, true ) ) {
249 $image_ids[] = $attachment->ID;
250 }
251 }
252 }
253 }
254
255 $image_ids = \array_unique( $image_ids );
256
257 // get URLs for each image
258 foreach ( $image_ids as $id ) {
259 $image_size = 'full';
260
261 /**
262 * Filter the image URL returned for each post.
263 *
264 * @param array|false $thumbnail The image URL, or false if no image is available.
265 * @param int $id The attachment ID.
266 * @param string $image_size The image size to retrieve. Set to 'full' by default.
267 */
268 $thumbnail = apply_filters(
269 'activitypub_get_image',
270 $this->get_image( $id, $image_size ),
271 $id,
272 $image_size
273 );
274
275 if ( $thumbnail ) {
276 $mimetype = \get_post_mime_type( $id );
277 $alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
278 $image = array(
279 'type' => 'Image',
280 'url' => $thumbnail[0],
281 'mediaType' => $mimetype,
282 );
283
284 if ( $alt ) {
285 $image['name'] = $alt;
286 }
287 $images[] = $image;
288 }
289 }
290
291 return $images;
292 }
293
294 /**
295 * Return details about an image attachment.
296 *
297 * @param int $id The attachment ID.
298 * @param string $image_size The image size to retrieve. Set to 'full' by default.
299 *
300 * @return array|false Array of image data, or boolean false if no image is available.
301 */
302 protected function get_image( $id, $image_size = 'full' ) {
303 /**
304 * Hook into the image retrieval process. Before image retrieval.
305 *
306 * @param int $id The attachment ID.
307 * @param string $image_size The image size to retrieve. Set to 'full' by default.
308 */
309 do_action( 'activitypub_get_image_pre', $id, $image_size );
310
311 $thumbnail = \wp_get_attachment_image_src( $id, $image_size );
312
313 /**
314 * Hook into the image retrieval process. After image retrieval.
315 *
316 * @param int $id The attachment ID.
317 * @param string $image_size The image size to retrieve. Set to 'full' by default.
318 */
319 do_action( 'activitypub_get_image_post', $id, $image_size );
320
321 return $thumbnail;
322 }
323
324 /**
325 * Returns the ActivityStreams 2.0 Object-Type for a Post based on the
326 * settings and the Post-Type.
327 *
328 * @see https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
329 *
330 * @return string The Object-Type.
331 */
332 protected function get_object_type() {
333 if ( 'wordpress-post-format' !== \get_option( 'activitypub_object_type', 'note' ) ) {
334 return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) );
335 }
336
337 $post_type = \get_post_type( $this->wp_post );
338 switch ( $post_type ) {
339 case 'post':
340 $post_format = \get_post_format( $this->wp_post );
341 switch ( $post_format ) {
342 case 'aside':
343 case 'status':
344 case 'quote':
345 case 'note':
346 $object_type = 'Note';
347 break;
348 case 'gallery':
349 case 'image':
350 $object_type = 'Image';
351 break;
352 case 'video':
353 $object_type = 'Video';
354 break;
355 case 'audio':
356 $object_type = 'Audio';
357 break;
358 default:
359 $object_type = 'Article';
360 break;
361 }
362 break;
363 case 'page':
364 $object_type = 'Page';
365 break;
366 case 'attachment':
367 $mime_type = \get_post_mime_type();
368 $media_type = \preg_replace( '/(\/[a-zA-Z]+)/i', '', $mime_type );
369 switch ( $media_type ) {
370 case 'audio':
371 $object_type = 'Audio';
372 break;
373 case 'video':
374 $object_type = 'Video';
375 break;
376 case 'image':
377 $object_type = 'Image';
378 break;
379 }
380 break;
381 default:
382 $object_type = 'Article';
383 break;
384 }
385
386 return $object_type;
387 }
388
389 /**
390 * Returns a list of Mentions, used in the Post.
391 *
392 * @see https://docs.joinmastodon.org/spec/activitypub/#Mention
393 *
394 * @return array The list of Mentions.
395 */
396 protected function get_cc() {
397 $cc = array();
398
399 $mentions = $this->get_mentions();
400 if ( $mentions ) {
401 foreach ( $mentions as $url ) {
402 $cc[] = $url;
403 }
404 }
405
406 return $cc;
407 }
408
409 /**
410 * Returns a list of Tags, used in the Post.
411 *
412 * This includes Hash-Tags and Mentions.
413 *
414 * @return array The list of Tags.
415 */
416 protected function get_tags() {
417 $tags = array();
418
419 $post_tags = \get_the_tags( $this->wp_post->ID );
420 if ( $post_tags ) {
421 foreach ( $post_tags as $post_tag ) {
422 $tag = array(
423 'type' => 'Hashtag',
424 'href' => \esc_url( \get_tag_link( $post_tag->term_id ) ),
425 'name' => esc_hashtag( $post_tag->name ),
426 );
427 $tags[] = $tag;
428 }
429 }
430
431 $mentions = $this->get_mentions();
432 if ( $mentions ) {
433 foreach ( $mentions as $mention => $url ) {
434 $tag = array(
435 'type' => 'Mention',
436 'href' => \esc_url( $url ),
437 'name' => \esc_html( $mention ),
438 );
439 $tags[] = $tag;
440 }
441 }
442
443 return $tags;
444 }
445
446 /**
447 * Returns the content for the ActivityPub Item.
448 *
449 * The content will be generated based on the user settings.
450 *
451 * @return string The content.
452 */
453 protected function get_content() {
454 global $post;
455
456 /**
457 * Provides an action hook so plugins can add their own hooks/filters before AP content is generated.
458 *
459 * 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.
460 *
461 * @param WP_Post $post The post object.
462 */
463 do_action( 'activitypub_before_get_content', $post );
464
465 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
466 $post = $this->wp_post;
467 $content = $this->get_post_content_template();
468
469 // Fill in the shortcodes.
470 setup_postdata( $post );
471 $content = do_shortcode( $content );
472 wp_reset_postdata();
473
474 $content = \wpautop( $content );
475 $content = \preg_replace( '/[\n\r\t]/', '', $content );
476 $content = \trim( $content );
477
478 $content = \apply_filters( 'activitypub_the_content', $content, $post );
479
480 return $content;
481 }
482
483 /**
484 * Gets the template to use to generate the content of the activitypub item.
485 *
486 * @return string The Template.
487 */
488 protected function get_post_content_template() {
489 if ( 'excerpt' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
490 return "[ap_excerpt]\n\n[ap_permalink type=\"html\"]";
491 }
492
493 if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
494 return "[ap_title]\n\n[ap_permalink type=\"html\"]";
495 }
496
497 if ( 'content' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
498 return "[ap_content]\n\n[ap_permalink type=\"html\"]\n\n[ap_hashtags]";
499 }
500
501 return \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
502 }
503
504 /**
505 * Helper function to get the @-Mentions from the post content.
506 *
507 * @return array The list of @-Mentions.
508 */
509 protected function get_mentions() {
510 return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_post->post_content, $this->wp_post );
511 }
512 }
513