Skip to content

Commit 6d5c76f

Browse files
authored
This adds Icon support for Audio and Video attachments (#1053)
1 parent dad75d3 commit 6d5c76f

File tree

6 files changed

+162
-16
lines changed

6 files changed

+162
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* `icon` support for `Audio` and `Video` attachments
1213
* Send "new follower" emails
1314

1415
### Improved

includes/functions.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,9 @@ function object_to_uri( $data ) {
822822

823823
// Return part of Object that makes most sense.
824824
switch ( $type ) {
825+
case 'Image':
826+
$data = $data['url'];
827+
break;
825828
case 'Link':
826829
$data = $data['href'];
827830
break;

includes/transformer/class-post.php

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Activitypub\Collection\Actors;
1414

1515
use function Activitypub\esc_hashtag;
16+
use function Activitypub\object_to_uri;
1617
use function Activitypub\is_single_user;
1718
use function Activitypub\get_enclosures;
1819
use function Activitypub\get_upload_baseurl;
@@ -208,7 +209,63 @@ protected function get_image() {
208209
*/
209210
$thumbnail = apply_filters(
210211
'activitypub_get_image',
211-
self::get_wordpress_attachment( $id, $image_size ),
212+
$this->get_wordpress_attachment( $id, $image_size ),
213+
$id,
214+
$image_size
215+
);
216+
217+
if ( ! $thumbnail ) {
218+
return null;
219+
}
220+
221+
$mime_type = \get_post_mime_type( $id );
222+
223+
$image = array(
224+
'type' => 'Image',
225+
'url' => \esc_url( $thumbnail[0] ),
226+
'mediaType' => \esc_attr( $mime_type ),
227+
);
228+
229+
$alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
230+
if ( $alt ) {
231+
$image['name'] = \wp_strip_all_tags( \html_entity_decode( $alt ) );
232+
}
233+
234+
return $image;
235+
}
236+
237+
/**
238+
* Returns an Icon, based on the Featured Image with a fallback to the site-icon.
239+
*
240+
* @return array|null The Icon or null if no icon is available.
241+
*/
242+
protected function get_icon() {
243+
$post_id = $this->wp_object->ID;
244+
245+
// List post thumbnail first if this post has one.
246+
if ( \has_post_thumbnail( $post_id ) ) {
247+
$id = \get_post_thumbnail_id( $post_id );
248+
} else {
249+
// Try site_logo, falling back to site_icon, first.
250+
$id = get_option( 'site_icon' );
251+
}
252+
253+
if ( ! $id ) {
254+
return null;
255+
}
256+
257+
$image_size = 'thumbnail';
258+
259+
/**
260+
* Filter the image URL returned for each post.
261+
*
262+
* @param array|false $thumbnail The image URL, or false if no image is available.
263+
* @param int $id The attachment ID.
264+
* @param string $image_size The image size to retrieve. Set to 'large' by default.
265+
*/
266+
$thumbnail = apply_filters(
267+
'activitypub_get_image',
268+
$this->get_wordpress_attachment( $id, $image_size ),
212269
$id,
213270
$image_size
214271
);
@@ -273,7 +330,7 @@ protected function get_attachment() {
273330
$media = $this->get_classic_editor_images( $media, $max_media );
274331
}
275332

276-
$media = self::filter_media_by_object_type( $media, \get_post_format( $this->wp_object ), $this->wp_object );
333+
$media = $this->filter_media_by_object_type( $media, \get_post_format( $this->wp_object ), $this->wp_object );
277334
$unique_ids = \array_unique( \array_column( $media, 'id' ) );
278335
$media = \array_intersect_key( $media, $unique_ids );
279336
$media = \array_slice( $media, 0, $max_media );
@@ -288,7 +345,7 @@ protected function get_attachment() {
288345
*/
289346
$media = \apply_filters( 'activitypub_attachment_ids', $media, $this->wp_object );
290347

291-
$attachments = \array_filter( \array_map( array( self::class, 'wp_attachment_to_activity_attachment' ), $media ) );
348+
$attachments = \array_filter( \array_map( array( $this, 'wp_attachment_to_activity_attachment' ), $media ) );
292349

293350
/**
294351
* Filter the attachments for a post.
@@ -361,7 +418,7 @@ protected function get_block_attachments( $media, $max_media ) {
361418

362419
$blocks = \parse_blocks( $this->wp_object->post_content );
363420

364-
return self::get_media_from_blocks( $blocks, $media );
421+
return $this->get_media_from_blocks( $blocks, $media );
365422
}
366423

367424
/**
@@ -372,11 +429,11 @@ protected function get_block_attachments( $media, $max_media ) {
372429
*
373430
* @return array The image IDs.
374431
*/
375-
protected static function get_media_from_blocks( $blocks, $media ) {
432+
protected function get_media_from_blocks( $blocks, $media ) {
376433
foreach ( $blocks as $block ) {
377434
// Recurse into inner blocks.
378435
if ( ! empty( $block['innerBlocks'] ) ) {
379-
$media = self::get_media_from_blocks( $block['innerBlocks'], $media );
436+
$media = $this->get_media_from_blocks( $block['innerBlocks'], $media );
380437
}
381438

382439
switch ( $block['blockName'] ) {
@@ -587,7 +644,7 @@ protected function get_classic_editor_image_attachments( $max_images ) {
587644
*
588645
* @return array The filtered media IDs.
589646
*/
590-
protected static function filter_media_by_object_type( $media, $type, $wp_object ) {
647+
protected function filter_media_by_object_type( $media, $type, $wp_object ) {
591648
/**
592649
* Filter the object type for media attachments.
593650
*
@@ -612,7 +669,7 @@ protected static function filter_media_by_object_type( $media, $type, $wp_object
612669
*
613670
* @return array The ActivityPub Attachment.
614671
*/
615-
public static function wp_attachment_to_activity_attachment( $media ) {
672+
public function wp_attachment_to_activity_attachment( $media ) {
616673
if ( ! isset( $media['id'] ) ) {
617674
return $media;
618675
}
@@ -635,7 +692,7 @@ public static function wp_attachment_to_activity_attachment( $media ) {
635692
*/
636693
$thumbnail = apply_filters(
637694
'activitypub_get_image',
638-
self::get_wordpress_attachment( $id, $image_size ),
695+
$this->get_wordpress_attachment( $id, $image_size ),
639696
$id,
640697
$image_size
641698
);
@@ -674,7 +731,11 @@ public static function wp_attachment_to_activity_attachment( $media ) {
674731
$attachment['width'] = \esc_attr( $meta['width'] );
675732
$attachment['height'] = \esc_attr( $meta['height'] );
676733
}
677-
// @todo: add `icon` support for audio/video attachments. Maybe use post thumbnail?
734+
735+
if ( $this->get_icon() ) {
736+
$attachment['icon'] = object_to_uri( $this->get_icon() );
737+
}
738+
678739
break;
679740
}
680741

@@ -697,7 +758,7 @@ public static function wp_attachment_to_activity_attachment( $media ) {
697758
*
698759
* @return array|false Array of image data, or boolean false if no image is available.
699760
*/
700-
protected static function get_wordpress_attachment( $id, $image_size = 'large' ) {
761+
protected function get_wordpress_attachment( $id, $image_size = 'large' ) {
701762
/**
702763
* Hook into the image retrieval process. Before image retrieval.
703764
*
@@ -920,7 +981,7 @@ protected function get_content() {
920981
*/
921982
do_action( 'activitypub_before_get_content', $post );
922983

923-
add_filter( 'render_block_core/embed', array( self::class, 'revert_embed_links' ), 10, 2 );
984+
add_filter( 'render_block_core/embed', array( $this, 'revert_embed_links' ), 10, 2 );
924985

925986
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
926987
$post = $this->wp_object;
@@ -1119,7 +1180,7 @@ public function get_summary_map() {
11191180
*
11201181
* @return string A block level link
11211182
*/
1122-
public static function revert_embed_links( $block_content, $block ) {
1183+
public function revert_embed_links( $block_content, $block ) {
11231184
if ( ! isset( $block['attrs']['url'] ) ) {
11241185
return $block_content;
11251186
}

integration/class-seriously-simple-podcasting.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use Activitypub\Transformer\Post;
1111

12+
use function Activitypub\object_to_uri;
1213
use function Activitypub\generate_post_summary;
1314

1415
/**
@@ -35,9 +36,13 @@ public function get_attachment() {
3536
'name' => \esc_attr( \get_the_title( $post->ID ) ?? '' ),
3637
);
3738

38-
$cover = \get_post_meta( $post->ID, 'cover_image', true );
39-
if ( $cover ) {
40-
$attachment['icon'] = \esc_url( $cover );
39+
$icon = \get_post_meta( $post->ID, 'cover_image', true );
40+
if ( ! $icon ) {
41+
$icon = $this->get_icon();
42+
}
43+
44+
if ( $icon ) {
45+
$attachment['icon'] = \esc_url( object_to_uri( $icon ) );
4146
}
4247

4348
return array( $attachment );

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ For reasons of data protection, it is not possible to see the followers of other
134134

135135
= Unreleased =
136136

137+
* Added: `icon` support for `Audio` and `Video` attachments
137138
* Added: Send "new follower" emails
138139
* Improved: Email templates for Likes and Reposts
139140
* Improved: Interactions moderation

tests/includes/transformer/class-test-post.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,4 +438,79 @@ public function create_upload_object( $file, $parent_id = 0 ) {
438438

439439
return $id;
440440
}
441+
442+
/**
443+
* Test get_icon method.
444+
*
445+
* @covers ::get_icon
446+
*/
447+
public function test_get_icon() {
448+
$post_id = $this->factory->post->create(
449+
array(
450+
'post_title' => 'Test Post',
451+
'post_content' => 'Test content',
452+
)
453+
);
454+
$post = get_post( $post_id );
455+
456+
// Create test image.
457+
$attachment_id = $this->create_upload_object( dirname( __DIR__, 2 ) . '/assets/test.jpg' );
458+
459+
// Set up reflection method.
460+
$reflection = new ReflectionClass( Post::class );
461+
$method = $reflection->getMethod( 'get_icon' );
462+
$method->setAccessible( true );
463+
464+
// Test with featured image.
465+
set_post_thumbnail( $post_id, $attachment_id );
466+
467+
$transformer = new Post( $post );
468+
$icon = $method->invoke( $transformer );
469+
470+
$this->assertIsArray( $icon );
471+
$this->assertEquals( 'Image', $icon['type'] );
472+
$this->assertArrayHasKey( 'url', $icon );
473+
$this->assertArrayHasKey( 'mediaType', $icon );
474+
$this->assertEquals( get_post_mime_type( $attachment_id ), $icon['mediaType'] );
475+
476+
// Test with site icon.
477+
delete_post_thumbnail( $post_id );
478+
update_option( 'site_icon', $attachment_id );
479+
480+
$icon = $method->invoke( $transformer );
481+
482+
$this->assertIsArray( $icon );
483+
$this->assertEquals( 'Image', $icon['type'] );
484+
$this->assertArrayHasKey( 'url', $icon );
485+
$this->assertArrayHasKey( 'mediaType', $icon );
486+
$this->assertEquals( get_post_mime_type( $attachment_id ), $icon['mediaType'] );
487+
488+
// Test with alt text.
489+
$alt_text = 'Test Alt Text';
490+
update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt_text );
491+
492+
$icon = $method->invoke( $transformer );
493+
494+
$this->assertIsArray( $icon );
495+
$this->assertEquals( 'Image', $icon['type'] );
496+
$this->assertArrayHasKey( 'name', $icon );
497+
$this->assertEquals( $alt_text, $icon['name'] );
498+
499+
// Test without any images.
500+
delete_post_thumbnail( $post_id );
501+
delete_option( 'site_icon' );
502+
delete_post_meta( $attachment_id, '_wp_attachment_image_alt' );
503+
504+
$icon = $method->invoke( $transformer );
505+
$this->assertNull( $icon );
506+
507+
// Test with invalid image.
508+
set_post_thumbnail( $post_id, 99999 );
509+
$icon = $method->invoke( $transformer );
510+
$this->assertNull( $icon );
511+
512+
// Cleanup.
513+
wp_delete_post( $post_id, true );
514+
wp_delete_attachment( $attachment_id, true );
515+
}
441516
}

0 commit comments

Comments
 (0)