You've already forked wp-fedistream
413 lines
8.9 KiB
PHP
413 lines
8.9 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Track Transformer for ActivityPub.
|
||
|
|
*
|
||
|
|
* @package WP_FediStream
|
||
|
|
*/
|
||
|
|
|
||
|
|
namespace WP_FediStream\ActivityPub;
|
||
|
|
|
||
|
|
// Prevent direct file access.
|
||
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Transforms Track posts to ActivityPub Audio objects.
|
||
|
|
*/
|
||
|
|
class TrackTransformer {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The track post.
|
||
|
|
*
|
||
|
|
* @var \WP_Post
|
||
|
|
*/
|
||
|
|
protected \WP_Post $post;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Constructor.
|
||
|
|
*
|
||
|
|
* @param \WP_Post $post The track post.
|
||
|
|
*/
|
||
|
|
public function __construct( \WP_Post $post ) {
|
||
|
|
$this->post = $post;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the ActivityPub object type.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_type(): string {
|
||
|
|
return 'Audio';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the object ID (URI).
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_id(): string {
|
||
|
|
return get_permalink( $this->post->ID );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the object name (title).
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_name(): string {
|
||
|
|
return $this->post->post_title;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the content (lyrics or description).
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_content(): string {
|
||
|
|
$content = $this->post->post_content;
|
||
|
|
|
||
|
|
// Apply content filters for proper formatting.
|
||
|
|
$content = apply_filters( 'the_content', $content );
|
||
|
|
|
||
|
|
return wp_kses_post( $content );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the summary (excerpt).
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_summary(): string {
|
||
|
|
if ( ! empty( $this->post->post_excerpt ) ) {
|
||
|
|
return wp_strip_all_tags( $this->post->post_excerpt );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate excerpt from content.
|
||
|
|
return wp_trim_words( wp_strip_all_tags( $this->post->post_content ), 30 );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the URL (permalink).
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function get_url(): string {
|
||
|
|
return get_permalink( $this->post->ID );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the attributed actor(s).
|
||
|
|
*
|
||
|
|
* @return array|string Artist(s) URIs.
|
||
|
|
*/
|
||
|
|
public function get_attributed_to() {
|
||
|
|
$artist_ids = get_post_meta( $this->post->ID, '_fedistream_artist_ids', true );
|
||
|
|
|
||
|
|
if ( ! is_array( $artist_ids ) || empty( $artist_ids ) ) {
|
||
|
|
// Fall back to album artist.
|
||
|
|
$album_id = get_post_meta( $this->post->ID, '_fedistream_album_id', true );
|
||
|
|
$artist_id = $album_id ? get_post_meta( $album_id, '_fedistream_album_artist', true ) : 0;
|
||
|
|
|
||
|
|
if ( $artist_id ) {
|
||
|
|
return get_permalink( $artist_id );
|
||
|
|
}
|
||
|
|
|
||
|
|
return array();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return single artist or array of artists.
|
||
|
|
if ( count( $artist_ids ) === 1 ) {
|
||
|
|
return get_permalink( $artist_ids[0] );
|
||
|
|
}
|
||
|
|
|
||
|
|
return array_map( 'get_permalink', $artist_ids );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the published date.
|
||
|
|
*
|
||
|
|
* @return string ISO 8601 date.
|
||
|
|
*/
|
||
|
|
public function get_published(): string {
|
||
|
|
return get_the_date( 'c', $this->post );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the updated date.
|
||
|
|
*
|
||
|
|
* @return string ISO 8601 date.
|
||
|
|
*/
|
||
|
|
public function get_updated(): string {
|
||
|
|
return get_the_modified_date( 'c', $this->post );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the duration.
|
||
|
|
*
|
||
|
|
* @return string ISO 8601 duration.
|
||
|
|
*/
|
||
|
|
public function get_duration(): string {
|
||
|
|
$seconds = (int) get_post_meta( $this->post->ID, '_fedistream_duration', true );
|
||
|
|
|
||
|
|
if ( ! $seconds ) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->format_duration_iso8601( $seconds );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the audio attachment.
|
||
|
|
*
|
||
|
|
* @return array|null
|
||
|
|
*/
|
||
|
|
public function get_audio_attachment(): ?array {
|
||
|
|
$audio_id = get_post_meta( $this->post->ID, '_fedistream_audio_file', true );
|
||
|
|
|
||
|
|
if ( ! $audio_id ) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
$audio_url = wp_get_attachment_url( $audio_id );
|
||
|
|
$mime_type = get_post_mime_type( $audio_id );
|
||
|
|
$audio_meta = wp_get_attachment_metadata( $audio_id );
|
||
|
|
|
||
|
|
if ( ! $audio_url ) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
$attachment = array(
|
||
|
|
'type' => 'Audio',
|
||
|
|
'mediaType' => $mime_type ?: 'audio/mpeg',
|
||
|
|
'url' => $audio_url,
|
||
|
|
'name' => $this->post->post_title,
|
||
|
|
);
|
||
|
|
|
||
|
|
// Add duration if available.
|
||
|
|
$duration = $this->get_duration();
|
||
|
|
if ( $duration ) {
|
||
|
|
$attachment['duration'] = $duration;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $attachment;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the image/thumbnail attachment.
|
||
|
|
*
|
||
|
|
* @return array|null
|
||
|
|
*/
|
||
|
|
public function get_image_attachment(): ?array {
|
||
|
|
$thumbnail_id = get_post_thumbnail_id( $this->post->ID );
|
||
|
|
|
||
|
|
// Fall back to album artwork.
|
||
|
|
if ( ! $thumbnail_id ) {
|
||
|
|
$album_id = get_post_meta( $this->post->ID, '_fedistream_album_id', true );
|
||
|
|
$thumbnail_id = $album_id ? get_post_thumbnail_id( $album_id ) : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! $thumbnail_id ) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
$image = wp_get_attachment_image_src( $thumbnail_id, 'medium' );
|
||
|
|
if ( ! $image ) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return array(
|
||
|
|
'type' => 'Image',
|
||
|
|
'mediaType' => get_post_mime_type( $thumbnail_id ),
|
||
|
|
'url' => $image[0],
|
||
|
|
'width' => $image[1],
|
||
|
|
'height' => $image[2],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get tags (genres, moods).
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function get_tags(): array {
|
||
|
|
$tags = array();
|
||
|
|
|
||
|
|
// Get genres.
|
||
|
|
$genres = get_the_terms( $this->post->ID, 'fedistream_genre' );
|
||
|
|
if ( $genres && ! is_wp_error( $genres ) ) {
|
||
|
|
foreach ( $genres as $genre ) {
|
||
|
|
$tags[] = array(
|
||
|
|
'type' => 'Hashtag',
|
||
|
|
'name' => '#' . sanitize_title( $genre->name ),
|
||
|
|
'href' => get_term_link( $genre ),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get moods.
|
||
|
|
$moods = get_the_terms( $this->post->ID, 'fedistream_mood' );
|
||
|
|
if ( $moods && ! is_wp_error( $moods ) ) {
|
||
|
|
foreach ( $moods as $mood ) {
|
||
|
|
$tags[] = array(
|
||
|
|
'type' => 'Hashtag',
|
||
|
|
'name' => '#' . sanitize_title( $mood->name ),
|
||
|
|
'href' => get_term_link( $mood ),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $tags;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the context (album).
|
||
|
|
*
|
||
|
|
* @return string|null Album URI if available.
|
||
|
|
*/
|
||
|
|
public function get_context(): ?string {
|
||
|
|
$album_id = get_post_meta( $this->post->ID, '_fedistream_album_id', true );
|
||
|
|
|
||
|
|
if ( ! $album_id ) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return get_permalink( $album_id );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Transform to ActivityPub object array.
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function to_object(): array {
|
||
|
|
$object = array(
|
||
|
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||
|
|
'type' => $this->get_type(),
|
||
|
|
'id' => $this->get_id(),
|
||
|
|
'name' => $this->get_name(),
|
||
|
|
'summary' => $this->get_summary(),
|
||
|
|
'content' => $this->get_content(),
|
||
|
|
'url' => $this->get_url(),
|
||
|
|
'attributedTo' => $this->get_attributed_to(),
|
||
|
|
'published' => $this->get_published(),
|
||
|
|
'updated' => $this->get_updated(),
|
||
|
|
);
|
||
|
|
|
||
|
|
// Add duration.
|
||
|
|
$duration = $this->get_duration();
|
||
|
|
if ( $duration ) {
|
||
|
|
$object['duration'] = $duration;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add attachments.
|
||
|
|
$attachments = array();
|
||
|
|
|
||
|
|
$audio = $this->get_audio_attachment();
|
||
|
|
if ( $audio ) {
|
||
|
|
$attachments[] = $audio;
|
||
|
|
}
|
||
|
|
|
||
|
|
$image = $this->get_image_attachment();
|
||
|
|
if ( $image ) {
|
||
|
|
$attachments[] = $image;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! empty( $attachments ) ) {
|
||
|
|
$object['attachment'] = $attachments;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add tags.
|
||
|
|
$tags = $this->get_tags();
|
||
|
|
if ( ! empty( $tags ) ) {
|
||
|
|
$object['tag'] = $tags;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add context (album).
|
||
|
|
$context = $this->get_context();
|
||
|
|
if ( $context ) {
|
||
|
|
$object['context'] = $context;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add additional metadata.
|
||
|
|
$explicit = get_post_meta( $this->post->ID, '_fedistream_explicit', true );
|
||
|
|
if ( $explicit ) {
|
||
|
|
$object['sensitive'] = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add ISRC if available.
|
||
|
|
$isrc = get_post_meta( $this->post->ID, '_fedistream_isrc', true );
|
||
|
|
if ( $isrc ) {
|
||
|
|
$object['isrc'] = $isrc;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $object;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a Create activity for this track.
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function to_create_activity(): array {
|
||
|
|
$attributed_to = $this->get_attributed_to();
|
||
|
|
$actor = is_array( $attributed_to ) ? $attributed_to[0] : $attributed_to;
|
||
|
|
|
||
|
|
return array(
|
||
|
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||
|
|
'type' => 'Create',
|
||
|
|
'id' => $this->get_id() . '#activity-create',
|
||
|
|
'actor' => $actor,
|
||
|
|
'published' => $this->get_published(),
|
||
|
|
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||
|
|
'cc' => array( $actor . '/followers' ),
|
||
|
|
'object' => $this->to_object(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create an Announce activity for this track.
|
||
|
|
*
|
||
|
|
* @param string $actor_uri The actor announcing the track.
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function to_announce_activity( string $actor_uri ): array {
|
||
|
|
return array(
|
||
|
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||
|
|
'type' => 'Announce',
|
||
|
|
'id' => $this->get_id() . '#activity-announce-' . time(),
|
||
|
|
'actor' => $actor_uri,
|
||
|
|
'published' => gmdate( 'c' ),
|
||
|
|
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||
|
|
'cc' => array( $actor_uri . '/followers' ),
|
||
|
|
'object' => $this->get_id(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format duration as ISO 8601.
|
||
|
|
*
|
||
|
|
* @param int $seconds The duration in seconds.
|
||
|
|
* @return string ISO 8601 duration.
|
||
|
|
*/
|
||
|
|
private function format_duration_iso8601( int $seconds ): string {
|
||
|
|
$hours = floor( $seconds / 3600 );
|
||
|
|
$minutes = floor( ( $seconds % 3600 ) / 60 );
|
||
|
|
$secs = $seconds % 60;
|
||
|
|
|
||
|
|
$duration = 'PT';
|
||
|
|
if ( $hours > 0 ) {
|
||
|
|
$duration .= $hours . 'H';
|
||
|
|
}
|
||
|
|
if ( $minutes > 0 ) {
|
||
|
|
$duration .= $minutes . 'M';
|
||
|
|
}
|
||
|
|
if ( $secs > 0 || ( $hours === 0 && $minutes === 0 ) ) {
|
||
|
|
$duration .= $secs . 'S';
|
||
|
|
}
|
||
|
|
|
||
|
|
return $duration;
|
||
|
|
}
|
||
|
|
}
|