get_meta( '_fedistream_linked_track', true );
}
/**
* Get the linked track post.
*
* @return \WP_Post|null
*/
public function get_linked_track(): ?\WP_Post {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return null;
}
$track = get_post( $track_id );
if ( ! $track || 'fedistream_track' !== $track->post_type ) {
return null;
}
return $track;
}
/**
* Get the pricing type.
*
* @return string fixed, pwyw, or nyp
*/
public function get_pricing_type(): string {
return $this->get_meta( '_fedistream_pricing_type', true ) ?: 'fixed';
}
/**
* Get minimum price for PWYW.
*
* @return float
*/
public function get_min_price(): float {
return (float) $this->get_meta( '_fedistream_min_price', true );
}
/**
* Get suggested price for PWYW.
*
* @return float
*/
public function get_suggested_price(): float {
return (float) $this->get_meta( '_fedistream_suggested_price', true );
}
/**
* Check if streaming is included.
*
* @return bool
*/
public function includes_streaming(): bool {
return 'yes' === $this->get_meta( '_fedistream_include_streaming', true );
}
/**
* Get available download formats.
*
* @return array
*/
public function get_available_formats(): array {
$formats = $this->get_meta( '_fedistream_available_formats', true );
return is_array( $formats ) ? $formats : array( 'mp3' );
}
/**
* Get track duration in seconds.
*
* @return int
*/
public function get_duration(): int {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return 0;
}
return (int) get_post_meta( $track_id, '_fedistream_duration', true );
}
/**
* Get formatted duration.
*
* @return string
*/
public function get_formatted_duration(): string {
$seconds = $this->get_duration();
if ( ! $seconds ) {
return '';
}
$mins = floor( $seconds / 60 );
$secs = $seconds % 60;
return sprintf( '%d:%02d', $mins, $secs );
}
/**
* Get artist name(s).
*
* @return string
*/
public function get_artist_name(): string {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return '';
}
$artist_ids = get_post_meta( $track_id, '_fedistream_artist_ids', true );
if ( ! is_array( $artist_ids ) || empty( $artist_ids ) ) {
// Fall back to album artist.
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
$artist_id = $album_id ? get_post_meta( $album_id, '_fedistream_album_artist', true ) : 0;
if ( $artist_id ) {
$artist = get_post( $artist_id );
return $artist ? $artist->post_title : '';
}
return '';
}
$names = array();
foreach ( $artist_ids as $artist_id ) {
$artist = get_post( $artist_id );
if ( $artist ) {
$names[] = $artist->post_title;
}
}
return implode( ', ', $names );
}
/**
* Get album name.
*
* @return string
*/
public function get_album_name(): string {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return '';
}
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
if ( ! $album_id ) {
return '';
}
$album = get_post( $album_id );
return $album ? $album->post_title : '';
}
/**
* Get track artwork URL.
*
* Falls back to album artwork if track has none.
*
* @param string $size Image size.
* @return string
*/
public function get_track_artwork( string $size = 'medium' ): string {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return '';
}
// Try track thumbnail first.
$thumbnail_id = get_post_thumbnail_id( $track_id );
// Fall back to album artwork.
if ( ! $thumbnail_id ) {
$album_id = get_post_meta( $track_id, '_fedistream_album_id', true );
$thumbnail_id = $album_id ? get_post_thumbnail_id( $album_id ) : 0;
}
if ( ! $thumbnail_id ) {
return '';
}
$image = wp_get_attachment_image_url( $thumbnail_id, $size );
return $image ?: '';
}
/**
* Get audio file URL.
*
* @return string
*/
public function get_audio_url(): string {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return '';
}
$audio_id = get_post_meta( $track_id, '_fedistream_audio_file', true );
if ( ! $audio_id ) {
return '';
}
return wp_get_attachment_url( $audio_id ) ?: '';
}
/**
* Check if track is explicit.
*
* @return bool
*/
public function is_explicit(): bool {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return false;
}
return (bool) get_post_meta( $track_id, '_fedistream_explicit', true );
}
/**
* Get track BPM.
*
* @return int
*/
public function get_bpm(): int {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return 0;
}
return (int) get_post_meta( $track_id, '_fedistream_bpm', true );
}
/**
* Get track musical key.
*
* @return string
*/
public function get_musical_key(): string {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return '';
}
return get_post_meta( $track_id, '_fedistream_key', true ) ?: '';
}
/**
* Get ISRC code.
*
* @return string
*/
public function get_isrc(): string {
$track_id = $this->get_linked_track_id();
if ( ! $track_id ) {
return '';
}
return get_post_meta( $track_id, '_fedistream_isrc', true ) ?: '';
}
/**
* Get downloads for this product.
*
* Generates downloadable files based on available formats.
*
* @param string $context View or edit context.
* @return array
*/
public function get_downloads( $context = 'view' ): array {
$downloads = parent::get_downloads( $context );
// If no manual downloads set, generate from linked track.
if ( empty( $downloads ) && $this->get_linked_track_id() ) {
$downloads = $this->generate_track_downloads();
}
return $downloads;
}
/**
* Generate download files from linked track.
*
* @return array
*/
private function generate_track_downloads(): array {
$downloads = array();
$track = $this->get_linked_track();
$formats = $this->get_available_formats();
if ( ! $track ) {
return $downloads;
}
// For each format, create a download entry.
foreach ( $formats as $format ) {
$format_label = strtoupper( $format );
$download_id = 'track-' . $track->ID . '-' . $format;
$downloads[ $download_id ] = array(
'id' => $download_id,
'name' => sprintf(
/* translators: 1: Track name, 2: Format name */
__( '%1$s (%2$s)', 'wp-fedistream' ),
$track->post_title,
$format_label
),
'file' => add_query_arg(
array(
'fedistream_download' => 'track',
'track_id' => $track->ID,
'format' => $format,
),
home_url( '/' )
),
);
}
return $downloads;
}
/**
* Check if purchasable.
*
* @return bool
*/
public function is_purchasable(): bool {
// Must have a linked track.
if ( ! $this->get_linked_track_id() ) {
return false;
}
// Check price for fixed pricing.
if ( 'fixed' === $this->get_pricing_type() ) {
return $this->get_price() !== '' && $this->get_price() >= 0;
}
// PWYW and NYP are always purchasable.
return true;
}
/**
* Get price HTML.
*
* @param string $price Price HTML.
* @return string
*/
public function get_price_html( $price = '' ): string {
$pricing_type = $this->get_pricing_type();
if ( 'nyp' === $pricing_type ) {
return '' . esc_html__( 'Name Your Price', 'wp-fedistream' ) . '';
}
if ( 'pwyw' === $pricing_type ) {
$min_price = $this->get_min_price();
$suggested = $this->get_suggested_price();
$html = '';
if ( $min_price > 0 ) {
$html .= sprintf(
/* translators: %s: Minimum price */
esc_html__( 'From %s', 'wp-fedistream' ),
wc_price( $min_price )
);
} else {
$html .= esc_html__( 'Pay What You Want', 'wp-fedistream' );
}
if ( $suggested > 0 ) {
$html .= ' ';
$html .= sprintf(
/* translators: %s: Suggested price */
esc_html__( '(Suggested: %s)', 'wp-fedistream' ),
wc_price( $suggested )
);
$html .= '';
}
$html .= '';
return $html;
}
return parent::get_price_html( $price );
}
/**
* Add to cart validation for PWYW products.
*
* @param bool $passed Validation passed.
* @param int $product_id Product ID.
* @param int $quantity Quantity.
* @return bool
*/
public static function validate_add_to_cart( bool $passed, int $product_id, int $quantity ): bool {
$product = wc_get_product( $product_id );
if ( ! $product || 'fedistream_track' !== $product->get_type() ) {
return $passed;
}
$pricing_type = $product->get_pricing_type();
if ( 'pwyw' === $pricing_type || 'nyp' === $pricing_type ) {
$custom_price = isset( $_POST['fedistream_custom_price'] ) ? // phpcs:ignore WordPress.Security.NonceVerification.Missing
wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['fedistream_custom_price'] ) ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Missing
0;
$min_price = $product->get_min_price();
if ( 'pwyw' === $pricing_type && $custom_price < $min_price ) {
wc_add_notice(
sprintf(
/* translators: %s: Minimum price */
__( 'Please enter at least %s', 'wp-fedistream' ),
wc_price( $min_price )
),
'error'
);
return false;
}
WC()->session->set( 'fedistream_custom_price_' . $product_id, $custom_price );
}
return $passed;
}
}