![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/public_html/api/ |
<?php
require_once '../config/database.php';
session_start();
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
echo 'Unauthorized';
exit;
}
// SECURITY: Validate and sanitize crate_id parameter
$crate_id_raw = $_GET['crate_id'] ?? null;
if (!$crate_id_raw) {
http_response_code(400);
echo 'Crate ID is required';
exit;
}
// SECURITY: Validate that crate_id is a positive integer
if (!is_numeric($crate_id_raw) || (int)$crate_id_raw <= 0) {
error_log("SECURITY: Invalid crate_id attempt: " . htmlspecialchars($crate_id_raw, ENT_QUOTES, 'UTF-8'));
http_response_code(400);
echo 'Invalid crate ID';
exit;
}
$crate_id = (int)$crate_id_raw;
$user_id = $_SESSION['user_id'];
try {
$pdo = getDBConnection();
// Get crate details with artist name
$stmt = $pdo->prepare("
SELECT
ap.id,
ap.name,
ap.user_id,
u.name as artist_name
FROM artist_playlists ap
JOIN users u ON ap.user_id = u.id
WHERE ap.id = ? AND ap.user_id = ?
");
$stmt->execute([$crate_id, $user_id]);
$crate = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$crate) {
http_response_code(404);
echo 'Crate not found or access denied';
exit;
}
// Get all tracks in crate that user owns or has purchased (including metadata for BPM/key)
$stmt = $pdo->prepare("
SELECT
mt.id,
mt.title,
mt.audio_url,
mt.metadata,
mt.user_id as track_owner_id,
pt.position,
CASE WHEN mt.user_id = ? THEN 1 ELSE 0 END as user_owns_track,
CASE WHEN EXISTS(SELECT 1 FROM track_purchases WHERE user_id = ? AND track_id = mt.id) THEN 1 ELSE 0 END as user_purchased_track
FROM playlist_tracks pt
JOIN music_tracks mt ON pt.track_id = mt.id
WHERE pt.playlist_id = ?
AND mt.status = 'complete'
AND (
mt.user_id = ?
OR EXISTS(SELECT 1 FROM track_purchases WHERE user_id = ? AND track_id = mt.id)
)
ORDER BY pt.position ASC
");
$stmt->execute([$user_id, $user_id, $crate_id, $user_id, $user_id]);
$tracks = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($tracks)) {
http_response_code(404);
echo 'No downloadable tracks found in this crate. You need to own or purchase tracks to download them.';
exit;
}
// SECURITY: Validate audio URLs
require_once '../includes/file_security.php';
// Create ZIP file in temp directory (will be automatically cleaned up)
$zip = new ZipArchive();
$zip_filename = tempnam(sys_get_temp_dir(), 'crate_download_') . '.zip';
// Register cleanup function to ensure ZIP is deleted even on errors
register_shutdown_function(function() use ($zip_filename) {
if (file_exists($zip_filename)) {
@unlink($zip_filename);
}
});
if ($zip->open($zip_filename, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
@unlink($zip_filename);
http_response_code(500);
echo 'Failed to create ZIP file';
exit;
}
$added_count = 0;
foreach ($tracks as $track) {
// Validate audio URL
$audio_validation = validateAudioUrl($track['audio_url']);
if ($audio_validation['type'] === 'invalid') {
error_log("SECURITY: Invalid audio URL for track {$track['id']}: " . $track['audio_url']);
continue;
}
$file_path = null;
$file_content = null;
if ($audio_validation['type'] === 'external') {
// Download external file
$file_content = @file_get_contents($audio_validation['url']);
if ($file_content === false) {
error_log("Failed to download external audio file for track {$track['id']}");
continue;
}
} else {
// Local file
$file_path = $audio_validation['path'];
if (!$file_path || !file_exists($file_path)) {
error_log("Local audio file not found for track {$track['id']}: " . ($file_path ?: 'null'));
continue;
}
}
// Parse metadata to get numerical key (Camelot notation)
$numerical_key = '';
if (!empty($track['metadata'])) {
$metadata = json_decode($track['metadata'], true);
if (is_array($metadata) && isset($metadata['numerical_key']) && !empty($metadata['numerical_key'])) {
$numerical_key = $metadata['numerical_key'];
}
}
// Sanitize filename: "Artist - Crate Title - 01 - [1A] Track Title.mp3" (with key if available)
$track_title = $track['title'] ?: 'Untitled Track';
$position = $track['position'] ?: ($added_count + 1);
$artist_name = sanitizeDownloadFilename($crate['artist_name']);
$crate_name = sanitizeDownloadFilename($crate['name']);
// Format: "Artist - Crate Title - 01 - [1A] Track Title.mp3" or "Artist - Crate Title - 01 - Track Title.mp3" (if no key)
if ($numerical_key) {
$filename = sanitizeDownloadFilename(sprintf('%s - %s - %02d - [%s] %s.mp3', $artist_name, $crate_name, $position, $numerical_key, $track_title));
} else {
$filename = sanitizeDownloadFilename(sprintf('%s - %s - %02d - %s.mp3', $artist_name, $crate_name, $position, $track_title));
}
// Add to ZIP
if ($file_content !== null) {
$zip->addFromString($filename, $file_content);
} else {
$zip->addFile($file_path, $filename);
}
$added_count++;
}
$zip->close();
if ($added_count === 0) {
@unlink($zip_filename);
http_response_code(404);
echo 'No valid audio files found to download';
exit;
}
// Generate download filename: "Artist - Crate Title.zip"
$artist_name = sanitizeDownloadFilename($crate['artist_name']);
$crate_name = sanitizeDownloadFilename($crate['name']);
$download_filename = $artist_name . ' - ' . $crate_name . '.zip';
// Send ZIP file
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . $download_filename . '"');
header('Content-Length: ' . filesize($zip_filename));
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
readfile($zip_filename);
// Clean up immediately after sending (file is streamed, so safe to delete)
@unlink($zip_filename);
} catch (Exception $e) {
// Ensure cleanup on error
if (isset($zip_filename) && file_exists($zip_filename)) {
@unlink($zip_filename);
}
error_log("Error downloading crate ZIP: " . $e->getMessage());
http_response_code(500);
echo 'Internal server error. Please try again.';
}