|
|
|
@ -3,13 +3,13 @@ namespace Activitypub\Transformer;
|
|
|
|
|
|
|
|
|
|
use WP_Post;
|
|
|
|
|
use Activitypub\Shortcodes;
|
|
|
|
|
use Activitypub\Model\Blog_User;
|
|
|
|
|
use Activitypub\Model\Blog;
|
|
|
|
|
use Activitypub\Transformer\Base;
|
|
|
|
|
use Activitypub\Collection\Users;
|
|
|
|
|
use Activitypub\Activity\Base_Object;
|
|
|
|
|
|
|
|
|
|
use function Activitypub\esc_hashtag;
|
|
|
|
|
use function Activitypub\is_single_user;
|
|
|
|
|
use function Activitypub\get_enclosures;
|
|
|
|
|
use function Activitypub\get_rest_url_by_path;
|
|
|
|
|
use function Activitypub\site_supports_blocks;
|
|
|
|
|
|
|
|
|
@ -70,7 +70,7 @@ class Post extends Base {
|
|
|
|
|
$this->get_locale() => $this->get_content(),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
$path = sprintf( 'users/%d/followers', intval( $post->post_author ) );
|
|
|
|
|
$path = sprintf( 'actors/%d/followers', intval( $post->post_author ) );
|
|
|
|
|
|
|
|
|
|
$object->set_to(
|
|
|
|
|
array(
|
|
|
|
@ -116,7 +116,7 @@ class Post extends Base {
|
|
|
|
|
* @return string The User-URL.
|
|
|
|
|
*/
|
|
|
|
|
protected function get_attributed_to() {
|
|
|
|
|
$blog_user = new Blog_User();
|
|
|
|
|
$blog_user = new Blog();
|
|
|
|
|
|
|
|
|
|
if ( is_single_user() ) {
|
|
|
|
|
return $blog_user->get_url();
|
|
|
|
@ -139,14 +139,34 @@ class Post extends Base {
|
|
|
|
|
protected function get_attachment() {
|
|
|
|
|
// Once upon a time we only supported images, but we now support audio/video as well.
|
|
|
|
|
// We maintain the image-centric naming for backwards compatibility.
|
|
|
|
|
$max_media = \intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) );
|
|
|
|
|
$max_media = \intval(
|
|
|
|
|
\apply_filters(
|
|
|
|
|
'activitypub_max_image_attachments',
|
|
|
|
|
\get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS )
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( site_supports_blocks() && \has_blocks( $this->wp_object->post_content ) ) {
|
|
|
|
|
$media = $this->get_block_attachments( $max_media );
|
|
|
|
|
} else {
|
|
|
|
|
$media = $this->get_classic_editor_images( $max_media );
|
|
|
|
|
$media = array(
|
|
|
|
|
'audio' => array(),
|
|
|
|
|
'video' => array(),
|
|
|
|
|
'image' => array(),
|
|
|
|
|
);
|
|
|
|
|
$id = $this->wp_object->ID;
|
|
|
|
|
|
|
|
|
|
// list post thumbnail first if this post has one
|
|
|
|
|
if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
|
|
|
|
|
$media['image'][] = array( 'id' => \get_post_thumbnail_id( $id ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$media = $this->get_enclosures( $media );
|
|
|
|
|
|
|
|
|
|
if ( site_supports_blocks() && \has_blocks( $this->wp_object->post_content ) ) {
|
|
|
|
|
$media = $this->get_block_attachments( $media, $max_media );
|
|
|
|
|
} else {
|
|
|
|
|
$media = $this->get_classic_editor_images( $media, $max_media );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$media = self::filter_media_by_object_type( $media, \get_post_format( $this->wp_object ), $this->wp_object );
|
|
|
|
|
$unique_ids = \array_unique( \array_column( $media, 'id' ) );
|
|
|
|
|
$media = \array_intersect_key( $media, $unique_ids );
|
|
|
|
|
$media = \array_slice( $media, 0, $max_media );
|
|
|
|
@ -157,35 +177,21 @@ class Post extends Base {
|
|
|
|
|
/**
|
|
|
|
|
* Get media attachments from blocks. They will be formatted as ActivityPub attachments, not as WP attachments.
|
|
|
|
|
*
|
|
|
|
|
* @param int $max_media The maximum number of attachments to return.
|
|
|
|
|
* @param array $media The media array grouped by type.
|
|
|
|
|
* @param int $max_media The maximum number of attachments to return.
|
|
|
|
|
*
|
|
|
|
|
* @return array The attachments.
|
|
|
|
|
*/
|
|
|
|
|
protected function get_block_attachments( $max_media ) {
|
|
|
|
|
protected function get_block_attachments( $media, $max_media ) {
|
|
|
|
|
// max media can't be negative or zero
|
|
|
|
|
if ( $max_media <= 0 ) {
|
|
|
|
|
return array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$id = $this->wp_object->ID;
|
|
|
|
|
$blocks = \parse_blocks( $this->wp_object->post_content );
|
|
|
|
|
$media = self::get_media_from_blocks( $blocks, $media );
|
|
|
|
|
|
|
|
|
|
$media = array(
|
|
|
|
|
'image' => array(),
|
|
|
|
|
'audio' => array(),
|
|
|
|
|
'video' => array(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// list post thumbnail first if this post has one
|
|
|
|
|
if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
|
|
|
|
|
$media['image'][] = array( 'id' => \get_post_thumbnail_id( $id ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $max_media > 0 ) {
|
|
|
|
|
$blocks = \parse_blocks( $this->wp_object->post_content );
|
|
|
|
|
$media = self::get_media_from_blocks( $blocks, $media );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self::filter_media_by_object_type( $media, \get_post_format( $this->wp_object ) );
|
|
|
|
|
return $media;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -202,8 +208,9 @@ class Post extends Base {
|
|
|
|
|
if ( $max_images <= 0 ) {
|
|
|
|
|
return array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$images = array();
|
|
|
|
|
$query = new \WP_Query(
|
|
|
|
|
$query = new \WP_Query(
|
|
|
|
|
array(
|
|
|
|
|
'post_parent' => $this->wp_object->ID,
|
|
|
|
|
'post_status' => 'inherit',
|
|
|
|
@ -214,6 +221,7 @@ class Post extends Base {
|
|
|
|
|
'posts_per_page' => $max_images,
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach ( $query->get_posts() as $attachment ) {
|
|
|
|
|
if ( ! \in_array( $attachment->ID, $images, true ) ) {
|
|
|
|
|
$images[] = array( 'id' => $attachment->ID );
|
|
|
|
@ -249,7 +257,7 @@ class Post extends Base {
|
|
|
|
|
// This linter warning is a false positive - we have to
|
|
|
|
|
// re-count each time here as we modify $images.
|
|
|
|
|
// phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
|
|
|
|
|
while ( $tags->next_tag( 'img' ) && ( \count( $images ) < $max_images ) ) {
|
|
|
|
|
while ( $tags->next_tag( 'img' ) && ( \count( $images ) <= $max_images ) ) {
|
|
|
|
|
$src = $tags->get_attribute( 'src' );
|
|
|
|
|
|
|
|
|
|
// If the img source is in our uploads dir, get the
|
|
|
|
@ -292,34 +300,68 @@ class Post extends Base {
|
|
|
|
|
* Get post images from the classic editor.
|
|
|
|
|
* Note that audio/video attachments are only supported in the block editor.
|
|
|
|
|
*
|
|
|
|
|
* @param int $max_images The maximum number of images to return.
|
|
|
|
|
* @param array $media The media array grouped by type.
|
|
|
|
|
* @param int $max_images The maximum number of images to return.
|
|
|
|
|
*
|
|
|
|
|
* @return array The attachments.
|
|
|
|
|
*/
|
|
|
|
|
protected function get_classic_editor_images( $max_images ) {
|
|
|
|
|
protected function get_classic_editor_images( $media, $max_images ) {
|
|
|
|
|
// max images can't be negative or zero
|
|
|
|
|
if ( $max_images <= 0 ) {
|
|
|
|
|
return array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$id = $this->wp_object->ID;
|
|
|
|
|
|
|
|
|
|
$images = array();
|
|
|
|
|
|
|
|
|
|
// list post thumbnail first if this post has one
|
|
|
|
|
if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
|
|
|
|
|
$images[] = \get_post_thumbnail_id( $id );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( \count( $images ) < $max_images ) {
|
|
|
|
|
if ( \count( $media['image'] ) <= $max_images ) {
|
|
|
|
|
if ( \class_exists( '\WP_HTML_Tag_Processor' ) ) {
|
|
|
|
|
$images = \array_merge( $images, $this->get_classic_editor_image_embeds( $max_images ) );
|
|
|
|
|
$media['image'] = \array_merge( $media['image'], $this->get_classic_editor_image_embeds( $max_images ) );
|
|
|
|
|
} else {
|
|
|
|
|
$images = \array_merge( $images, $this->get_classic_editor_image_attachments( $max_images ) );
|
|
|
|
|
$media['image'] = \array_merge( $media['image'], $this->get_classic_editor_image_attachments( $max_images ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $images;
|
|
|
|
|
return $media;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get enclosures for a post.
|
|
|
|
|
*
|
|
|
|
|
* @param array $media The media array grouped by type.
|
|
|
|
|
*
|
|
|
|
|
* @return array The media array extended with enclosures.
|
|
|
|
|
*/
|
|
|
|
|
public function get_enclosures( $media ) {
|
|
|
|
|
$enclosures = get_enclosures( $this->wp_object->ID );
|
|
|
|
|
|
|
|
|
|
if ( ! $enclosures ) {
|
|
|
|
|
return $media;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ( $enclosures as $enclosure ) {
|
|
|
|
|
// check if URL is an attachment
|
|
|
|
|
$attachment_id = \attachment_url_to_postid( $enclosure['url'] );
|
|
|
|
|
if ( $attachment_id ) {
|
|
|
|
|
$enclosure['id'] = $attachment_id;
|
|
|
|
|
$enclosure['url'] = \wp_get_attachment_url( $attachment_id );
|
|
|
|
|
$enclosure['mediaType'] = \get_post_mime_type( $attachment_id );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$mime_type = $enclosure['mediaType'];
|
|
|
|
|
$mime_type_parts = \explode( '/', $mime_type );
|
|
|
|
|
|
|
|
|
|
switch ( $mime_type_parts[0] ) {
|
|
|
|
|
case 'image':
|
|
|
|
|
$media['image'][] = $enclosure;
|
|
|
|
|
break;
|
|
|
|
|
case 'audio':
|
|
|
|
|
$media['audio'][] = $enclosure;
|
|
|
|
|
break;
|
|
|
|
|
case 'video':
|
|
|
|
|
$media['video'][] = $enclosure;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $media;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -331,7 +373,6 @@ class Post extends Base {
|
|
|
|
|
* @return array The image IDs.
|
|
|
|
|
*/
|
|
|
|
|
protected static function get_media_from_blocks( $blocks, $media ) {
|
|
|
|
|
|
|
|
|
|
foreach ( $blocks as $block ) {
|
|
|
|
|
// recurse into inner blocks
|
|
|
|
|
if ( ! empty( $block['innerBlocks'] ) ) {
|
|
|
|
@ -397,19 +438,19 @@ class Post extends Base {
|
|
|
|
|
/**
|
|
|
|
|
* Filter media IDs by object type.
|
|
|
|
|
*
|
|
|
|
|
* @param array $media The media array grouped by type.
|
|
|
|
|
* @param array $type The object type.
|
|
|
|
|
* @param array $media The media array grouped by type.
|
|
|
|
|
* @param string $type The object type.
|
|
|
|
|
*
|
|
|
|
|
* @return array The filtered media IDs.
|
|
|
|
|
*/
|
|
|
|
|
protected static function filter_media_by_object_type( $media, $type ) {
|
|
|
|
|
$type = \apply_filters( 'filter_media_by_object_type', \strtolower( $type ) );
|
|
|
|
|
protected static function filter_media_by_object_type( $media, $type, $wp_object ) {
|
|
|
|
|
$type = \apply_filters( 'filter_media_by_object_type', \strtolower( $type ), $wp_object );
|
|
|
|
|
|
|
|
|
|
if ( ! empty( $media[ $type ] ) ) {
|
|
|
|
|
return $media[ $type ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return array_merge( array(), ...array_values( $media ) );
|
|
|
|
|
return array_filter( array_merge( array(), ...array_values( $media ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -420,6 +461,10 @@ class Post extends Base {
|
|
|
|
|
* @return array The ActivityPub Attachment.
|
|
|
|
|
*/
|
|
|
|
|
public static function wp_attachment_to_activity_attachment( $media ) {
|
|
|
|
|
if ( ! isset( $media['id'] ) ) {
|
|
|
|
|
return $media;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$id = $media['id'];
|
|
|
|
|
$attachment = array();
|
|
|
|
|
$mime_type = \get_post_mime_type( $id );
|
|
|
|
@ -427,14 +472,14 @@ class Post extends Base {
|
|
|
|
|
// switching on image/audio/video
|
|
|
|
|
switch ( $mime_type_parts[0] ) {
|
|
|
|
|
case 'image':
|
|
|
|
|
$image_size = 'full';
|
|
|
|
|
$image_size = 'large';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Filter the image URL returned for each post.
|
|
|
|
|
*
|
|
|
|
|
* @param array|false $thumbnail The image URL, or false if no image is available.
|
|
|
|
|
* @param int $id The attachment ID.
|
|
|
|
|
* @param string $image_size The image size to retrieve. Set to 'full' by default.
|
|
|
|
|
* @param string $image_size The image size to retrieve. Set to 'large' by default.
|
|
|
|
|
*/
|
|
|
|
|
$thumbnail = apply_filters(
|
|
|
|
|
'activitypub_get_image',
|
|
|
|
@ -488,16 +533,16 @@ class Post extends Base {
|
|
|
|
|
* Return details about an image attachment.
|
|
|
|
|
*
|
|
|
|
|
* @param int $id The attachment ID.
|
|
|
|
|
* @param string $image_size The image size to retrieve. Set to 'full' by default.
|
|
|
|
|
* @param string $image_size The image size to retrieve. Set to 'large' by default.
|
|
|
|
|
*
|
|
|
|
|
* @return array|false Array of image data, or boolean false if no image is available.
|
|
|
|
|
*/
|
|
|
|
|
protected static function get_wordpress_attachment( $id, $image_size = 'full' ) {
|
|
|
|
|
protected static function get_wordpress_attachment( $id, $image_size = 'large' ) {
|
|
|
|
|
/**
|
|
|
|
|
* Hook into the image retrieval process. Before image retrieval.
|
|
|
|
|
*
|
|
|
|
|
* @param int $id The attachment ID.
|
|
|
|
|
* @param string $image_size The image size to retrieve. Set to 'full' by default.
|
|
|
|
|
* @param string $image_size The image size to retrieve. Set to 'large' by default.
|
|
|
|
|
*/
|
|
|
|
|
do_action( 'activitypub_get_image_pre', $id, $image_size );
|
|
|
|
|
|
|
|
|
@ -507,7 +552,7 @@ class Post extends Base {
|
|
|
|
|
* Hook into the image retrieval process. After image retrieval.
|
|
|
|
|
*
|
|
|
|
|
* @param int $id The attachment ID.
|
|
|
|
|
* @param string $image_size The image size to retrieve. Set to 'full' by default.
|
|
|
|
|
* @param string $image_size The image size to retrieve. Set to 'large' by default.
|
|
|
|
|
*/
|
|
|
|
|
do_action( 'activitypub_get_image_post', $id, $image_size );
|
|
|
|
|
|
|
|
|
@ -536,7 +581,7 @@ class Post extends Base {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Default to Article.
|
|
|
|
|
$object_type = 'Article';
|
|
|
|
|
$object_type = 'Note';
|
|
|
|
|
$post_format = 'standard';
|
|
|
|
|
|
|
|
|
|
if ( \get_theme_support( 'post-formats' ) ) {
|
|
|
|
@ -547,18 +592,12 @@ class Post extends Base {
|
|
|
|
|
switch ( $post_type ) {
|
|
|
|
|
case 'post':
|
|
|
|
|
switch ( $post_format ) {
|
|
|
|
|
case 'aside':
|
|
|
|
|
case 'status':
|
|
|
|
|
case 'quote':
|
|
|
|
|
case 'note':
|
|
|
|
|
case 'gallery':
|
|
|
|
|
case 'image':
|
|
|
|
|
case 'video':
|
|
|
|
|
case 'audio':
|
|
|
|
|
$object_type = 'Note';
|
|
|
|
|
case 'standard':
|
|
|
|
|
case '':
|
|
|
|
|
$object_type = 'Article';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
$object_type = 'Article';
|
|
|
|
|
$object_type = 'Note';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
@ -566,7 +605,7 @@ class Post extends Base {
|
|
|
|
|
$object_type = 'Page';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
$object_type = 'Article';
|
|
|
|
|
$object_type = 'Note';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -593,6 +632,16 @@ class Post extends Base {
|
|
|
|
|
return $cc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function get_audience() {
|
|
|
|
|
if ( is_single_user() ) {
|
|
|
|
|
return null;
|
|
|
|
|
} else {
|
|
|
|
|
$blog = new Blog();
|
|
|
|
|
return $blog->get_id();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a list of Tags, used in the Post.
|
|
|
|
|
*
|
|
|
|
@ -708,6 +757,8 @@ class Post extends Base {
|
|
|
|
|
*/
|
|
|
|
|
do_action( 'activitypub_before_get_content', $post );
|
|
|
|
|
|
|
|
|
|
add_filter( 'render_block_core/embed', array( self::class, 'revert_embed_links' ), 10, 2 );
|
|
|
|
|
|
|
|
|
|
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
|
|
|
|
$post = $this->wp_object;
|
|
|
|
|
$content = $this->get_post_content_template();
|
|
|
|
@ -792,4 +843,21 @@ class Post extends Base {
|
|
|
|
|
*/
|
|
|
|
|
return apply_filters( 'activitypub_post_locale', $lang, $post_id, $this->wp_object );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Transform Embed blocks to block level link.
|
|
|
|
|
*
|
|
|
|
|
* Remote servers will simply drop iframe elements, rendering incomplete content.
|
|
|
|
|
*
|
|
|
|
|
* @see https://www.w3.org/TR/activitypub/#security-sanitizing-content
|
|
|
|
|
* @see https://www.w3.org/wiki/ActivityPub/Primer/HTML
|
|
|
|
|
*
|
|
|
|
|
* @param string $block_content The block content (html)
|
|
|
|
|
* @param object $block The block object
|
|
|
|
|
*
|
|
|
|
|
* @return string A block level link
|
|
|
|
|
*/
|
|
|
|
|
public static function revert_embed_links( $block_content, $block ) {
|
|
|
|
|
return '<p><a href="' . esc_url( $block['attrs']['url'] ) . '">' . $block['attrs']['url'] . '</a></p>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|