diff --git a/.github/changelog/add-podlove-episode-summary b/.github/changelog/add-podlove-episode-summary new file mode 100644 index 0000000000..23adc36661 --- /dev/null +++ b/.github/changelog/add-podlove-episode-summary @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Federate the episode summary for Podlove Podcast Publisher episodes. diff --git a/integration/class-podlove-podcast-publisher.php b/integration/class-podlove-podcast-publisher.php index 62ef614281..4d234837cd 100644 --- a/integration/class-podlove-podcast-publisher.php +++ b/integration/class-podlove-podcast-publisher.php @@ -40,6 +40,30 @@ protected function get_episode() { return $this->episode; } + /** + * Returns the summary for the ActivityPub Item. + * + * @return string|null The summary or null. + */ + protected function get_summary() { + $episode = $this->get_episode(); + + // Notes carry no summary, so there is nothing to override for them. + if ( $episode && 'Note' !== $this->get_type() ) { + // Sanitize like generate_post_summary() does, since Podlove stores the summary as raw user input. + $summary = \strip_shortcodes( (string) $episode->summary ); + $summary = \wp_strip_all_tags( $summary ); + $summary = \trim( \html_entity_decode( $summary, ENT_QUOTES, 'UTF-8' ) ); + + // Federate the episode summary explicitly; Podlove keeps it out of post_content. + if ( '' !== $summary ) { + return $summary; + } + } + + return parent::get_summary(); + } + /** * Gets the attachment for a podcast episode. * diff --git a/tests/phpunit/includes/class-episode.php b/tests/phpunit/includes/class-episode.php new file mode 100644 index 0000000000..46abf130d1 --- /dev/null +++ b/tests/phpunit/includes/class-episode.php @@ -0,0 +1,80 @@ +assertTrue( class_exists( '\Activitypub\Integration\Podlove_Podcast_Publisher' ) ); $this->assertTrue( is_subclass_of( '\Activitypub\Integration\Podlove_Podcast_Publisher', '\Activitypub\Transformer\Post' ) ); } + + /** + * Test that the episode summary is federated as the object summary. + * + * @covers ::get_summary + */ + public function test_get_summary_uses_episode_summary() { + \update_option( 'activitypub_object_type', 'wordpress-post-format' ); + + $post = \get_post( + \wp_insert_post( + array( + 'post_author' => 1, + 'post_title' => 'Episode title', + 'post_content' => 'Episode content.', + 'post_status' => 'publish', + 'post_type' => 'post', + ) + ) + ); + + // Raw user input: tags and HTML entities should be sanitized like the default summary. + $episode = new \Podlove\Model\Episode(); + $episode->summary = '

Episode summary & more

'; + \Podlove\Model\Episode::$mock = $episode; + + $object = ( new \Activitypub\Integration\Podlove_Podcast_Publisher( $post ) )->to_object(); + + $this->assertSame( 'Episode summary & more', $object->get_summary() ); + + \wp_delete_post( $post->ID, true ); + } + + /** + * Test that the transformer falls back to the default summary when the episode + * summary is empty (including markup/whitespace that sanitizes to nothing). + * + * @covers ::get_summary + */ + public function test_get_summary_falls_back_without_episode_summary() { + \update_option( 'activitypub_object_type', 'wordpress-post-format' ); + + $post = \get_post( + \wp_insert_post( + array( + 'post_author' => 1, + 'post_title' => 'Episode title', + 'post_content' => 'Episode content that should be summarized.', + 'post_status' => 'publish', + 'post_type' => 'post', + ) + ) + ); + + // Markup/whitespace only: sanitizes to an empty string, so the default summary is used. + $episode = new \Podlove\Model\Episode(); + $episode->summary = '

'; + \Podlove\Model\Episode::$mock = $episode; + + $podlove = ( new \Activitypub\Integration\Podlove_Podcast_Publisher( $post ) )->to_object(); + $default = ( new \Activitypub\Transformer\Post( $post ) )->to_object(); + + $this->assertSame( $default->get_summary(), $podlove->get_summary() ); + + \wp_delete_post( $post->ID, true ); + } }