You've already forked wp-fedistream
feat: Initial release v0.1.0
WP FediStream - Stream music over ActivityPub Features: - Custom post types: Artist, Album, Track, Playlist - Custom taxonomies: Genre, Mood, License - User roles: Artist, Label - Admin dashboard with statistics - Frontend templates and shortcodes - Audio player with queue management - ActivityPub integration with actor support - WooCommerce product types for albums/tracks - User library with favorites and history - Notification system (in-app and email) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
433
includes/ActivityPub/PlaylistTransformer.php
Normal file
433
includes/ActivityPub/PlaylistTransformer.php
Normal file
@@ -0,0 +1,433 @@
|
||||
<?php
|
||||
/**
|
||||
* Playlist Transformer for ActivityPub.
|
||||
*
|
||||
* @package WP_FediStream
|
||||
*/
|
||||
|
||||
namespace WP_FediStream\ActivityPub;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms Playlist posts to ActivityPub OrderedCollection objects.
|
||||
*/
|
||||
class PlaylistTransformer {
|
||||
|
||||
/**
|
||||
* The playlist post.
|
||||
*
|
||||
* @var \WP_Post
|
||||
*/
|
||||
protected \WP_Post $post;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \WP_Post $post The playlist post.
|
||||
*/
|
||||
public function __construct( \WP_Post $post ) {
|
||||
$this->post = $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ActivityPub object type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type(): string {
|
||||
return 'OrderedCollection';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (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 (playlist creator).
|
||||
*
|
||||
* @return string User's ActivityPub actor URI.
|
||||
*/
|
||||
public function get_attributed_to(): string {
|
||||
$user_id = $this->post->post_author;
|
||||
|
||||
// Check if there's an artist associated with this user.
|
||||
$artist_args = array(
|
||||
'post_type' => 'fedistream_artist',
|
||||
'posts_per_page' => 1,
|
||||
'author' => $user_id,
|
||||
);
|
||||
|
||||
$artists = get_posts( $artist_args );
|
||||
|
||||
if ( ! empty( $artists ) ) {
|
||||
return get_permalink( $artists[0]->ID );
|
||||
}
|
||||
|
||||
// Fall back to user profile URL.
|
||||
return get_author_posts_url( $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 total duration.
|
||||
*
|
||||
* @return string ISO 8601 duration.
|
||||
*/
|
||||
public function get_duration(): string {
|
||||
$seconds = (int) get_post_meta( $this->post->ID, '_fedistream_playlist_duration', true );
|
||||
|
||||
if ( ! $seconds ) {
|
||||
// Calculate from tracks.
|
||||
$tracks = $this->get_tracks();
|
||||
foreach ( $tracks as $track ) {
|
||||
$seconds += (int) get_post_meta( $track->ID, '_fedistream_duration', true );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $seconds ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->format_duration_iso8601( $seconds );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tracks in this playlist.
|
||||
*
|
||||
* @return array Array of WP_Post objects.
|
||||
*/
|
||||
public function get_tracks(): array {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . 'fedistream_playlist_tracks';
|
||||
|
||||
// Get track IDs in order.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$track_ids = $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT track_id FROM {$table} WHERE playlist_id = %d ORDER BY position ASC",
|
||||
$this->post->ID
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $track_ids ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Get track posts in order.
|
||||
$args = array(
|
||||
'post_type' => 'fedistream_track',
|
||||
'post_status' => 'publish',
|
||||
'post__in' => $track_ids,
|
||||
'orderby' => 'post__in',
|
||||
'posts_per_page' => -1,
|
||||
);
|
||||
|
||||
$query = new \WP_Query( $args );
|
||||
|
||||
return $query->posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total item count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_items(): int {
|
||||
$count = (int) get_post_meta( $this->post->ID, '_fedistream_track_count', true );
|
||||
|
||||
if ( ! $count ) {
|
||||
$count = count( $this->get_tracks() );
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ordered items (track objects).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_ordered_items(): array {
|
||||
$tracks = $this->get_tracks();
|
||||
$items = array();
|
||||
|
||||
foreach ( $tracks as $track ) {
|
||||
$transformer = new TrackTransformer( $track );
|
||||
$items[] = $transformer->to_object();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image/cover attachment.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function get_image_attachment(): ?array {
|
||||
$thumbnail_id = get_post_thumbnail_id( $this->post->ID );
|
||||
|
||||
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 (moods).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_tags(): array {
|
||||
$tags = array();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the playlist is public.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_public(): bool {
|
||||
$visibility = get_post_meta( $this->post->ID, '_fedistream_playlist_visibility', true );
|
||||
|
||||
return 'public' === $visibility || empty( $visibility );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(),
|
||||
'totalItems' => $this->get_total_items(),
|
||||
'orderedItems' => $this->get_ordered_items(),
|
||||
);
|
||||
|
||||
// Add duration.
|
||||
$duration = $this->get_duration();
|
||||
if ( $duration ) {
|
||||
$object['duration'] = $duration;
|
||||
}
|
||||
|
||||
// Add image.
|
||||
$image = $this->get_image_attachment();
|
||||
if ( $image ) {
|
||||
$object['image'] = $image;
|
||||
}
|
||||
|
||||
// Add tags.
|
||||
$tags = $this->get_tags();
|
||||
if ( ! empty( $tags ) ) {
|
||||
$object['tag'] = $tags;
|
||||
}
|
||||
|
||||
// Add visibility indicator.
|
||||
if ( ! $this->is_public() ) {
|
||||
$object['sensitive'] = false;
|
||||
$visibility = get_post_meta( $this->post->ID, '_fedistream_playlist_visibility', true );
|
||||
if ( 'unlisted' === $visibility ) {
|
||||
$object['visibility'] = 'unlisted';
|
||||
}
|
||||
}
|
||||
|
||||
// Add collaborative flag.
|
||||
$collaborative = get_post_meta( $this->post->ID, '_fedistream_playlist_collaborative', true );
|
||||
if ( $collaborative ) {
|
||||
$object['collaborative'] = true;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Create activity for this playlist.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_create_activity(): array {
|
||||
$actor = $this->get_attributed_to();
|
||||
|
||||
// Determine audience based on visibility.
|
||||
$visibility = get_post_meta( $this->post->ID, '_fedistream_playlist_visibility', true );
|
||||
|
||||
$to = array();
|
||||
$cc = array();
|
||||
|
||||
if ( 'public' === $visibility || empty( $visibility ) ) {
|
||||
$to[] = 'https://www.w3.org/ns/activitystreams#Public';
|
||||
$cc[] = $actor . '/followers';
|
||||
} elseif ( 'unlisted' === $visibility ) {
|
||||
$to[] = $actor . '/followers';
|
||||
$cc[] = 'https://www.w3.org/ns/activitystreams#Public';
|
||||
} else {
|
||||
// Private - only followers.
|
||||
$to[] = $actor . '/followers';
|
||||
}
|
||||
|
||||
return array(
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'type' => 'Create',
|
||||
'id' => $this->get_id() . '#activity-create',
|
||||
'actor' => $actor,
|
||||
'published' => $this->get_published(),
|
||||
'to' => $to,
|
||||
'cc' => $cc,
|
||||
'object' => $this->to_object(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Update activity for this playlist.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_update_activity(): array {
|
||||
$actor = $this->get_attributed_to();
|
||||
|
||||
return array(
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'type' => 'Update',
|
||||
'id' => $this->get_id() . '#activity-update-' . time(),
|
||||
'actor' => $actor,
|
||||
'published' => gmdate( 'c' ),
|
||||
'to' => array( 'https://www.w3.org/ns/activitystreams#Public' ),
|
||||
'cc' => array( $actor . '/followers' ),
|
||||
'object' => $this->to_object(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user