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; } }