post = $post; } /** * Get the ActivityPub object type. * * @return string */ public function get_type(): string { return 'Collection'; } /** * 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 { $album_type = get_post_meta( $this->post->ID, '_fedistream_album_type', true ); $type_label = ''; switch ( $album_type ) { case 'ep': $type_label = ' (EP)'; break; case 'single': $type_label = ' (Single)'; break; case 'compilation': $type_label = ' (Compilation)'; break; } return $this->post->post_title . $type_label; } /** * Get the content (liner notes/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 ); } // Build summary from album info. $artist_id = get_post_meta( $this->post->ID, '_fedistream_album_artist', true ); $artist = $artist_id ? get_post( $artist_id ) : null; $release_date = get_post_meta( $this->post->ID, '_fedistream_release_date', true ); $track_count = (int) get_post_meta( $this->post->ID, '_fedistream_total_tracks', true ); $summary = ''; if ( $artist ) { $summary .= sprintf( __( 'By %s', 'wp-fedistream' ), $artist->post_title ); } if ( $release_date ) { /* translators: %s: release date */ $summary .= ' ' . sprintf( __( '- Released %s', 'wp-fedistream' ), $release_date ); } if ( $track_count ) { /* translators: %d: number of tracks */ $summary .= ' ' . sprintf( _n( '- %d track', '- %d tracks', $track_count, 'wp-fedistream' ), $track_count ); } return trim( $summary ); } /** * Get the URL (permalink). * * @return string */ public function get_url(): string { return get_permalink( $this->post->ID ); } /** * Get the attributed actor. * * @return string Artist URI. */ public function get_attributed_to(): string { $artist_id = get_post_meta( $this->post->ID, '_fedistream_album_artist', true ); if ( ! $artist_id ) { return ''; } return get_permalink( $artist_id ); } /** * Get the published date. * * @return string ISO 8601 date. */ public function get_published(): string { // Use release date if available, otherwise post date. $release_date = get_post_meta( $this->post->ID, '_fedistream_release_date', true ); if ( $release_date ) { $date = \DateTime::createFromFormat( 'Y-m-d', $release_date ); if ( $date ) { return $date->format( 'c' ); } } 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_total_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 album. * * @return array Array of WP_Post objects. */ public function get_tracks(): array { $args = array( 'post_type' => 'fedistream_track', 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_key' => '_fedistream_track_number', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'orderby' => 'meta_value_num', 'order' => 'ASC', 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query array( 'key' => '_fedistream_album_id', 'value' => $this->post->ID, ), ), ); $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_total_tracks', true ); if ( ! $count ) { $count = count( $this->get_tracks() ); } return $count; } /** * Get the collection items (track URIs). * * @return array */ public function get_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/artwork 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 (genres). * * @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 ), ); } } return $tags; } /** * 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(), 'items' => $this->get_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 album-specific metadata. $album_type = get_post_meta( $this->post->ID, '_fedistream_album_type', true ); if ( $album_type ) { $object['albumType'] = $album_type; } $upc = get_post_meta( $this->post->ID, '_fedistream_upc', true ); if ( $upc ) { $object['upc'] = $upc; } $catalog = get_post_meta( $this->post->ID, '_fedistream_catalog_number', true ); if ( $catalog ) { $object['catalogNumber'] = $catalog; } $release_date = get_post_meta( $this->post->ID, '_fedistream_release_date', true ); if ( $release_date ) { $object['releaseDate'] = $release_date; } return $object; } /** * Create a Create activity for this album. * * @return array */ public function to_create_activity(): array { $actor = $this->get_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 album. * * @param string $actor_uri The actor announcing the album. * @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; } }