get_meta( '_fedistream_linked_album', true ); } /** * Get the linked album post. * * @return \WP_Post|null */ public function get_linked_album(): ?\WP_Post { $album_id = $this->get_linked_album_id(); if ( ! $album_id ) { return null; } $album = get_post( $album_id ); if ( ! $album || 'fedistream_album' !== $album->post_type ) { return null; } return $album; } /** * 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 tracks in this album. * * @return array Array of WP_Post objects. */ public function get_tracks(): array { $album_id = $this->get_linked_album_id(); if ( ! $album_id ) { return array(); } $tracks = get_posts( 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' => $album_id, ), ), ) ); return $tracks; } /** * Get track count. * * @return int */ public function get_track_count(): int { $album_id = $this->get_linked_album_id(); if ( ! $album_id ) { return 0; } $count = get_post_meta( $album_id, '_fedistream_total_tracks', true ); if ( $count ) { return (int) $count; } return count( $this->get_tracks() ); } /** * Get total duration in seconds. * * @return int */ public function get_total_duration(): int { $album_id = $this->get_linked_album_id(); if ( ! $album_id ) { return 0; } $duration = get_post_meta( $album_id, '_fedistream_total_duration', true ); if ( $duration ) { return (int) $duration; } // Calculate from tracks. $tracks = $this->get_tracks(); $duration = 0; foreach ( $tracks as $track ) { $duration += (int) get_post_meta( $track->ID, '_fedistream_duration', true ); } return $duration; } /** * Get formatted duration. * * @return string */ public function get_formatted_duration(): string { $seconds = $this->get_total_duration(); if ( ! $seconds ) { return ''; } $hours = floor( $seconds / 3600 ); $mins = floor( ( $seconds % 3600 ) / 60 ); $secs = $seconds % 60; if ( $hours > 0 ) { return sprintf( '%d:%02d:%02d', $hours, $mins, $secs ); } return sprintf( '%d:%02d', $mins, $secs ); } /** * Get artist name(s). * * @return string */ public function get_artist_name(): string { $album_id = $this->get_linked_album_id(); if ( ! $album_id ) { return ''; } $artist_id = get_post_meta( $album_id, '_fedistream_album_artist', true ); if ( ! $artist_id ) { return ''; } $artist = get_post( $artist_id ); return $artist ? $artist->post_title : ''; } /** * Get album artwork URL. * * @param string $size Image size. * @return string */ public function get_album_artwork( string $size = 'medium' ): string { $album_id = $this->get_linked_album_id(); if ( ! $album_id ) { return ''; } $thumbnail_id = get_post_thumbnail_id( $album_id ); if ( ! $thumbnail_id ) { return ''; } $image = wp_get_attachment_image_url( $thumbnail_id, $size ); return $image ?: ''; } /** * Get release date. * * @return string */ public function get_release_date(): string { $album_id = $this->get_linked_album_id(); if ( ! $album_id ) { return ''; } return get_post_meta( $album_id, '_fedistream_release_date', true ) ?: ''; } /** * Get album type (album, ep, single, compilation). * * @return string */ public function get_album_type(): string { $album_id = $this->get_linked_album_id(); if ( ! $album_id ) { return ''; } return get_post_meta( $album_id, '_fedistream_album_type', true ) ?: 'album'; } /** * 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 album. if ( empty( $downloads ) && $this->get_linked_album_id() ) { $downloads = $this->generate_album_downloads(); } return $downloads; } /** * Generate download files from linked album. * * @return array */ private function generate_album_downloads(): array { $downloads = array(); $tracks = $this->get_tracks(); $formats = $this->get_available_formats(); $album = $this->get_linked_album(); if ( empty( $tracks ) || ! $album ) { return $downloads; } // For each format, create a download entry. foreach ( $formats as $format ) { $format_label = strtoupper( $format ); // Create album ZIP download entry. $download_id = 'album-' . $album->ID . '-' . $format; $downloads[ $download_id ] = array( 'id' => $download_id, 'name' => sprintf( /* translators: 1: Album name, 2: Format name */ __( '%1$s (%2$s)', 'wp-fedistream' ), $album->post_title, $format_label ), 'file' => add_query_arg( array( 'fedistream_download' => 'album', 'album_id' => $album->ID, 'format' => $format, ), home_url( '/' ) ), ); } return $downloads; } /** * Check if purchasable. * * @return bool */ public function is_purchasable(): bool { // Must have a linked album. if ( ! $this->get_linked_album_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_album' !== $product->get_type() ) { return $passed; } $pricing_type = $product->get_pricing_type(); if ( 'pwyw' === $pricing_type || 'nyp' === $pricing_type ) { // Check if custom price is set. $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; } // Store custom price in session for cart. WC()->session->set( 'fedistream_custom_price_' . $product_id, $custom_price ); } return $passed; } }