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

519 lines 13.3 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 use Activitypub\Shortcodes;
9
10 use function Activitypub\esc_hashtag;
11 use function Activitypub\is_single_user;
12 use function Activitypub\get_rest_url_by_path;
13 use function Activitypub\site_supports_blocks;
14
15 /**
16 * WordPress Post Transformer
17 *
18 * The Post Transformer is responsible for transforming a WP_Post object into different othe
19 * Object-Types.
20 *
21 * Currently supported are:
22 *
23 * - Activitypub\Activity\Base_Object
24 */
25 class Post {
26
27 /**
28 * The WP_Post object.
29 *
30 * @var WP_Post
31 */
32 protected $wp_post;
33
34 /**
35 * Static function to Transform a WP_Post Object.
36 *
37 * This helps to chain the output of the Transformer.
38 *
39 * @param WP_Post $wp_post The WP_Post object
40 *
41 * @return void
42 */
43 public static function transform( WP_Post $wp_post ) {
44 return new static( $wp_post );
45 }
46
47 /**
48 *
49 *
50 * @param WP_Post $wp_post
51 */
52 public function __construct( WP_Post $wp_post ) {
53 $this->wp_post = $wp_post;
54 }
55
56 /**
57 * Transforms the WP_Post object to an ActivityPub Object
58 *
59 * @see \Activitypub\Activity\Base_Object
60 *
61 * @return \Activitypub\Activity\Base_Object The ActivityPub Object
62 */
63 public function to_object() {
64 $wp_post = $this->wp_post;
65 $object = new Base_Object();
66
67 $object->set_id( $this->get_id() );
68 $object->set_url( $this->get_url() );
69 $object->set_type( $this->get_object_type() );
70
71 $published = \strtotime( $wp_post->post_date_gmt );
72
73 $object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
74
75 $updated = \strtotime( $wp_post->post_modified_gmt );
76
77 if ( $updated > $published ) {
78 $object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
79 }
80
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 ) );
89
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() );
99
100 return $object;
101 }
102
103 /**
104 * Returns the ID of the Post.
105 *
106 * @return string The Posts ID.
107 */
108 public function get_id() {
109 return $this->get_url();
110 }
111
112 /**
113 * Returns the URL of the Post.
114 *
115 * @return string The Posts URL.
116 */
117 public function get_url() {
118 $post = $this->wp_post;
119
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 );
124 }
125
126 return \esc_url( $permalink );
127 }
128
129 /**
130 * Returns the User-URL of the Author of the Post.
131 *
132 * If `single_user` mode is enabled, the URL of the Blog-User is returned.
133 *
134 * @return string The User-URL.
135 */
136 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();
143 }
144
145 /**
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.
149 *
150 * @return array The image IDs.
151 */
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 );
155 }
156
157 /**
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.
162 *
163 * @return array The image IDs.
164 */
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 }
171
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 }
194
195 // we could be at or over max, stop unneeded work
196 if ( count( $image_ids ) >= $max_images ) {
197 break;
198 }
199 }
200
201 // still need to slice it because one gallery could knock us over the limit
202 return \array_slice( $image_ids, 0, $max_images );
203 }
204
205 /**
206 * Generates all Image Attachments for a Post.
207 *
208 * @return array The Image Attachments.
209 */
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 ) ) );
212
213 $images = array();
214
215 // max images can't be negative or zero
216 if ( $max_images <= 0 ) {
217 return $images;
218 }
219
220 $id = $this->wp_post->ID;
221
222 $image_ids = array();
223
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 }
229
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 }
254 }
255
256 $image_ids = \array_unique( $image_ids );
257
258 // get URLs for each image
259 foreach ( $image_ids as $id ) {
260 $image_size = 'full';
261
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 );
275
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 }
290 }
291
292 return $images;
293 }
294
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 /**
305 * Hook into the image retrieval process. Before image retrieval.
306 *
307 * @param int $id The attachment ID.
308 * @param string $image_size The image size to retrieve. Set to 'full' by default.
309 */
310 do_action( 'activitypub_get_image_pre', $id, $image_size );
311
312 $thumbnail = \wp_get_attachment_image_src( $id, $image_size );
313
314 /**
315 * Hook into the image retrieval process. After image retrieval.
316 *
317 * @param int $id The attachment ID.
318 * @param string $image_size The image size to retrieve. Set to 'full' by default.
319 */
320 do_action( 'activitypub_get_image_post', $id, $image_size );
321
322 return $thumbnail;
323 }
324
325 /**
326 * Returns the ActivityStreams 2.0 Object-Type for a Post based on the
327 * settings and the Post-Type.
328 *
329 * @see https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
330 *
331 * @return string The Object-Type.
332 */
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' ) );
336 }
337
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;
385 }
386
387 return $object_type;
388 }
389
390 /**
391 * Returns a list of Mentions, used in the Post.
392 *
393 * @see https://docs.joinmastodon.org/spec/activitypub/#Mention
394 *
395 * @return array The list of Mentions.
396 */
397 protected function get_cc() {
398 $cc = array();
399
400 $mentions = $this->get_mentions();
401 if ( $mentions ) {
402 foreach ( $mentions as $url ) {
403 $cc[] = $url;
404 }
405 }
406
407 return $cc;
408 }
409
410 /**
411 * Returns a list of Tags, used in the Post.
412 *
413 * This includes Hash-Tags and Mentions.
414 *
415 * @return array The list of Tags.
416 */
417 protected function get_tags() {
418 $tags = array();
419
420 $post_tags = \get_the_tags( $this->wp_post->ID );
421 if ( $post_tags ) {
422 foreach ( $post_tags as $post_tag ) {
423 $tag = array(
424 'type' => 'Hashtag',
425 'href' => \esc_url( \get_tag_link( $post_tag->term_id ) ),
426 'name' => esc_hashtag( $post_tag->name ),
427 );
428 $tags[] = $tag;
429 }
430 }
431
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 }
442 }
443
444 return $tags;
445 }
446
447 /**
448 * Returns the content for the ActivityPub Item.
449 *
450 * The content will be generated based on the user settings.
451 *
452 * @return string The content.
453 */
454 protected function get_content() {
455 global $post;
456
457 /**
458 * Provides an action hook so plugins can add their own hooks/filters before AP content is generated.
459 *
460 * 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 *
462 * @param WP_Post $post The post object.
463 */
464 do_action( 'activitypub_before_get_content', $post );
465
466 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
467 $post = $this->wp_post;
468 $content = $this->get_post_content_template();
469
470 // Register our shortcodes just in time.
471 Shortcodes::register();
472 // Fill in the shortcodes.
473 setup_postdata( $post );
474 $content = do_shortcode( $content );
475 wp_reset_postdata();
476
477 $content = \wpautop( $content );
478 $content = \preg_replace( '/[\n\r\t]/', '', $content );
479 $content = \trim( $content );
480
481 $content = \apply_filters( 'activitypub_the_content', $content, $post );
482
483 // Don't need these any more, should never appear in a post.
484 Shortcodes::unregister();
485
486 return $content;
487 }
488
489 /**
490 * Gets the template to use to generate the content of the activitypub item.
491 *
492 * @return string The Template.
493 */
494 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\"]";
497 }
498
499 if ( 'title' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
500 return "[ap_title]\n\n[ap_permalink type=\"html\"]";
501 }
502
503 if ( 'content' === \get_option( 'activitypub_post_content_type', 'content' ) ) {
504 return "[ap_content]\n\n[ap_permalink type=\"html\"]\n\n[ap_hashtags]";
505 }
506
507 return \get_option( 'activitypub_custom_post_content', ACTIVITYPUB_CUSTOM_POST_CONTENT );
508 }
509
510 /**
511 * Helper function to get the @-Mentions from the post content.
512 *
513 * @return array The list of @-Mentions.
514 */
515 protected function get_mentions() {
516 return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_post->post_content, $this->wp_post );
517 }
518 }
519