handle_track_download( $user_id ); } elseif ( 'album' === $type ) { $this->handle_album_download( $user_id ); } } /** * Handle track download. * * @param int $user_id User ID. * @return void */ private function handle_track_download( int $user_id ): void { $track_id = isset( $_GET['track_id'] ) ? absint( $_GET['track_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $format = isset( $_GET['format'] ) ? sanitize_text_field( wp_unslash( $_GET['format'] ) ) : 'mp3'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! $track_id ) { wp_die( esc_html__( 'Invalid track.', 'wp-fedistream' ) ); } // Verify purchase. if ( ! Integration::user_has_purchased( $user_id, 'track', $track_id ) ) { wp_die( esc_html__( 'You have not purchased this track.', 'wp-fedistream' ) ); } $track = get_post( $track_id ); if ( ! $track || 'fedistream_track' !== $track->post_type ) { wp_die( esc_html__( 'Track not found.', 'wp-fedistream' ) ); } // Get audio file. $audio_id = get_post_meta( $track_id, '_fedistream_audio_file', true ); if ( ! $audio_id ) { wp_die( esc_html__( 'No audio file available.', 'wp-fedistream' ) ); } $file_path = get_attached_file( $audio_id ); if ( ! $file_path || ! file_exists( $file_path ) ) { wp_die( esc_html__( 'File not found.', 'wp-fedistream' ) ); } // Convert format if needed. $converted_file = $this->get_converted_file( $file_path, $format, $track_id ); // Generate filename. $filename = sanitize_file_name( $track->post_title ) . '.' . $format; // Serve the file. $this->serve_file( $converted_file, $filename, $format ); } /** * Handle album download (ZIP of all tracks). * * @param int $user_id User ID. * @return void */ private function handle_album_download( int $user_id ): void { $album_id = isset( $_GET['album_id'] ) ? absint( $_GET['album_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $format = isset( $_GET['format'] ) ? sanitize_text_field( wp_unslash( $_GET['format'] ) ) : 'mp3'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! $album_id ) { wp_die( esc_html__( 'Invalid album.', 'wp-fedistream' ) ); } // Verify purchase. if ( ! Integration::user_has_purchased( $user_id, 'album', $album_id ) ) { wp_die( esc_html__( 'You have not purchased this album.', 'wp-fedistream' ) ); } $album = get_post( $album_id ); if ( ! $album || 'fedistream_album' !== $album->post_type ) { wp_die( esc_html__( 'Album not found.', 'wp-fedistream' ) ); } // Get tracks. $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, ), ), ) ); if ( empty( $tracks ) ) { wp_die( esc_html__( 'No tracks found in this album.', 'wp-fedistream' ) ); } // Create ZIP file. $zip_file = $this->create_album_zip( $album, $tracks, $format ); if ( ! $zip_file ) { wp_die( esc_html__( 'Failed to create download package.', 'wp-fedistream' ) ); } // Generate filename. $artist_id = get_post_meta( $album_id, '_fedistream_album_artist', true ); $artist = $artist_id ? get_post( $artist_id ) : null; $artist_name = $artist ? $artist->post_title : 'Unknown Artist'; $filename = sanitize_file_name( $artist_name . ' - ' . $album->post_title ) . '.' . strtoupper( $format ) . '.zip'; // Serve the file. $this->serve_file( $zip_file, $filename, 'zip' ); // Clean up temp file. wp_delete_file( $zip_file ); } /** * Get converted file path. * * @param string $source_path Source file path. * @param string $format Target format. * @param int $track_id Track ID for caching. * @return string Converted file path. */ private function get_converted_file( string $source_path, string $format, int $track_id ): string { $source_ext = strtolower( pathinfo( $source_path, PATHINFO_EXTENSION ) ); // If same format, return source. if ( $source_ext === $format ) { return $source_path; } // Check for cached conversion. $cache_dir = wp_upload_dir()['basedir'] . '/fedistream-cache/'; $cache_file = $cache_dir . 'track-' . $track_id . '.' . $format; if ( file_exists( $cache_file ) ) { return $cache_file; } // Create cache directory. if ( ! file_exists( $cache_dir ) ) { wp_mkdir_p( $cache_dir ); } // For now, return source file (format conversion would require FFmpeg). // In production, you'd use FFmpeg or similar for conversion. // This is a placeholder for the conversion logic. return $source_path; } /** * Create ZIP file for album download. * * @param \WP_Post $album Album post. * @param array $tracks Track posts. * @param string $format Audio format. * @return string|null ZIP file path or null on failure. */ private function create_album_zip( \WP_Post $album, array $tracks, string $format ): ?string { if ( ! class_exists( 'ZipArchive' ) ) { return null; } $temp_dir = get_temp_dir(); $zip_path = $temp_dir . 'fedistream-album-' . $album->ID . '-' . time() . '.zip'; $zip = new \ZipArchive(); if ( $zip->open( $zip_path, \ZipArchive::CREATE ) !== true ) { return null; } $track_number = 0; foreach ( $tracks as $track ) { ++$track_number; $audio_id = get_post_meta( $track->ID, '_fedistream_audio_file', true ); if ( ! $audio_id ) { continue; } $file_path = get_attached_file( $audio_id ); if ( ! $file_path || ! file_exists( $file_path ) ) { continue; } // Get converted file. $converted_file = $this->get_converted_file( $file_path, $format, $track->ID ); // Create filename with track number. $filename = sprintf( '%02d - %s.%s', $track_number, sanitize_file_name( $track->post_title ), $format ); $zip->addFile( $converted_file, $filename ); } // Add cover art if available. $thumbnail_id = get_post_thumbnail_id( $album->ID ); if ( $thumbnail_id ) { $cover_path = get_attached_file( $thumbnail_id ); if ( $cover_path && file_exists( $cover_path ) ) { $cover_ext = pathinfo( $cover_path, PATHINFO_EXTENSION ); $zip->addFile( $cover_path, 'cover.' . $cover_ext ); } } $zip->close(); return file_exists( $zip_path ) ? $zip_path : null; } /** * Serve a file for download. * * @param string $file_path File path. * @param string $filename Download filename. * @param string $format File format. * @return void */ private function serve_file( string $file_path, string $filename, string $format ): void { if ( ! file_exists( $file_path ) ) { wp_die( esc_html__( 'File not found.', 'wp-fedistream' ) ); } // Get MIME type. $mime_types = array( 'mp3' => 'audio/mpeg', 'flac' => 'audio/flac', 'wav' => 'audio/wav', 'ogg' => 'audio/ogg', 'aac' => 'audio/aac', 'zip' => 'application/zip', ); $mime_type = $mime_types[ $format ] ?? 'application/octet-stream'; // Clean output buffer. while ( ob_get_level() ) { ob_end_clean(); } // Set headers. nocache_headers(); header( 'Content-Type: ' . $mime_type ); header( 'Content-Description: File Transfer' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Transfer-Encoding: binary' ); header( 'Content-Length: ' . filesize( $file_path ) ); // Read file. readfile( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile exit; } /** * Add download links to order confirmation email. * * @param \WC_Order $order Order object. * @param bool $sent_to_admin Whether sent to admin. * @param bool $plain_text Whether plain text. * @param object $email Email object. * @return void */ public function add_download_links_to_email( \WC_Order $order, bool $sent_to_admin, bool $plain_text, $email ): void { if ( $sent_to_admin || 'completed' !== $order->get_status() ) { return; } $has_fedistream = false; foreach ( $order->get_items() as $item ) { $product_type = \WC_Product_Factory::get_product_type( $item->get_product_id() ); if ( in_array( $product_type, array( 'fedistream_album', 'fedistream_track' ), true ) ) { $has_fedistream = true; break; } } if ( ! $has_fedistream ) { return; } $downloads_url = wc_get_account_endpoint_url( 'downloads' ); if ( $plain_text ) { echo "\n\n"; echo esc_html__( 'Your FediStream Downloads', 'wp-fedistream' ) . "\n"; echo esc_html__( 'Access your purchased music at:', 'wp-fedistream' ) . ' ' . esc_url( $downloads_url ) . "\n"; } else { ?>
' . esc_html__( 'account downloads', 'wp-fedistream' ) . '' ); ?>
get_user_purchases( $user_id ); if ( empty( $purchases ) ) { return; } ?>