![]() 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/private_html/admin_includes/ |
<?php
// Track Management Tab
// This file handles all track management functionality
// Get filter parameters
$status_filter = $_GET['status'] ?? 'all';
$user_filter = $_GET['user'] ?? 'all';
$search_term = $_GET['search'] ?? '';
$page = max(1, intval($_GET['page'] ?? 1));
$per_page = 50;
$offset = ($page - 1) * $per_page;
// Build WHERE clause
$where_conditions = [];
$params = [];
if ($status_filter !== 'all') {
$where_conditions[] = "mt.status = ?";
$params[] = $status_filter;
}
if ($user_filter !== 'all') {
if ($user_filter === 'orphaned') {
$where_conditions[] = "(mt.user_id IS NULL OR mt.user_id = 0)";
} else {
$where_conditions[] = "mt.user_id = ?";
$params[] = $user_filter;
}
}
if (!empty($search_term)) {
$where_conditions[] = "(mt.title LIKE ? OR mt.prompt LIKE ? OR u.name LIKE ?)";
$search_param = "%$search_term%";
$params[] = $search_param;
$params[] = $search_param;
$params[] = $search_param;
}
$where_clause = !empty($where_conditions) ? "WHERE " . implode(" AND ", $where_conditions) : "";
// Save original params for count query (before adding LIMIT/OFFSET)
$count_params = $params;
// Get tracks for management with enhanced data
try {
$stmt = $pdo->prepare("
SELECT
mt.*,
u.name as user_name,
u.email as user_email,
u.plan as user_plan,
CASE
WHEN mt.user_id IS NULL OR mt.user_id = 0 THEN 'Orphaned'
ELSE u.name
END as display_name,
CASE
WHEN mt.user_id IS NULL OR mt.user_id = 0 THEN 'orphaned@system.com'
ELSE u.email
END as display_email,
COALESCE(like_stats.like_count, 0) as like_count,
COALESCE(comment_stats.comment_count, 0) as comment_count,
COALESCE(play_stats.play_count, 0) as play_count,
COALESCE(view_stats.view_count, 0) as view_count
FROM music_tracks mt
LEFT JOIN users u ON mt.user_id = u.id
LEFT JOIN (SELECT track_id, COUNT(*) as like_count FROM track_likes GROUP BY track_id) like_stats ON mt.id = like_stats.track_id
LEFT JOIN (SELECT track_id, COUNT(*) as comment_count FROM track_comments GROUP BY track_id) comment_stats ON mt.id = comment_stats.track_id
LEFT JOIN (SELECT track_id, COUNT(*) as play_count FROM track_plays GROUP BY track_id) play_stats ON mt.id = play_stats.track_id
LEFT JOIN (SELECT track_id, COUNT(*) as view_count FROM track_views GROUP BY track_id) view_stats ON mt.id = view_stats.track_id
$where_clause
ORDER BY mt.created_at DESC
LIMIT ? OFFSET ?
");
// Add LIMIT and OFFSET parameters
$params[] = $per_page;
$params[] = $offset;
$stmt->execute($params);
$tracks = $stmt->fetchAll();
} catch (Exception $e) {
$tracks = [];
error_log("Admin tracks query error: " . $e->getMessage());
}
// Get total count for pagination (using original params without LIMIT/OFFSET)
$count_stmt = $pdo->prepare("
SELECT COUNT(*) as total
FROM music_tracks mt
LEFT JOIN users u ON mt.user_id = u.id
$where_clause
");
$count_stmt->execute($count_params);
$total_tracks = $count_stmt->fetch()['total'];
$total_pages = ceil($total_tracks / $per_page);
// Get track statistics
$track_stats = $pdo->query("
SELECT
COUNT(*) as total_tracks,
COUNT(CASE WHEN status = 'complete' THEN 1 END) as completed,
COUNT(CASE WHEN status = 'processing' THEN 1 END) as processing,
COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed,
COUNT(CASE WHEN user_id IS NULL OR user_id = 0 THEN 1 END) as orphaned,
COUNT(CASE WHEN audio_url IS NOT NULL AND audio_url != '' THEN 1 END) as with_audio,
COUNT(CASE WHEN audio_url LIKE '%apiboxfiles.erweima.ai%' THEN 1 END) as cdn_tracks,
AVG(duration) as avg_duration,
SUM(duration) as total_duration
FROM music_tracks
")->fetch();
// Get all users for assignment
$users = $pdo->query("
SELECT id, name, email, plan, credits, is_admin
FROM users
ORDER BY name ASC
")->fetchAll();
?>
<!-- Track Management -->
<div class="section-header">
<h2><i class="fas fa-music"></i> Track Management</h2>
<p>Comprehensive track management with user assignment and transfer capabilities</p>
<div style="margin-top: 10px; display: flex; gap: 1rem; flex-wrap: wrap;">
<a href="/fix_track_titles.php" class="btn btn-primary" style="text-decoration: none;">
<i class="fas fa-edit"></i> Fix Track Titles
</a>
<a href="/fix_external_audio_urls.php" class="btn btn-primary" style="text-decoration: none;">
<i class="fas fa-link"></i> Fix External Audio URLs
</a>
<button class="btn btn-warning" onclick="fixAllFailedTracks()" style="text-decoration: none;">
<i class="fas fa-redo"></i> Fix All Failed Tracks (<?= number_format($track_stats['failed']) ?>)
</button>
<button class="btn btn-info" onclick="showFailedTracksAnalysis()" style="text-decoration: none;">
<i class="fas fa-chart-bar"></i> Failed Tracks Analysis
</button>
<button class="btn btn-success" onclick="assignAllOrphanedTracks()" style="text-decoration: none;">
<i class="fas fa-users"></i> Assign All Orphaned (<?= number_format($track_stats['orphaned']) ?>)
</button>
</div>
</div>
<!-- Track Statistics Cards -->
<div class="stats-grid" style="margin-bottom: 3rem;">
<div class="stat-card">
<div class="stat-number"><?= number_format($track_stats['total_tracks']) ?></div>
<div class="stat-label">Total Tracks</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= number_format($track_stats['completed']) ?></div>
<div class="stat-label">Completed</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= number_format($track_stats['orphaned']) ?></div>
<div class="stat-label">Orphaned</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= number_format($track_stats['with_audio']) ?></div>
<div class="stat-label">With Audio</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= number_format($track_stats['processing']) ?></div>
<div class="stat-label">Processing</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= number_format($track_stats['failed']) ?></div>
<div class="stat-label">Failed</div>
</div>
</div>
<!-- Filters and Search -->
<div class="filters-section" style="background: rgba(255, 255, 255, 0.05); border-radius: 12px; padding: 2rem; margin-bottom: 2rem;">
<h3 style="margin-top: 0; color: white;">🔍 Filters & Search</h3>
<form method="GET" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; align-items: end;">
<input type="hidden" name="tab" value="tracks">
<div>
<label style="display: block; color: #a0aec0; margin-bottom: 0.5rem;">Status Filter</label>
<select name="status" style="width: 100%; padding: 0.8rem; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 8px; background: rgba(255, 255, 255, 0.05); color: white;">
<option value="all" <?= $status_filter === 'all' ? 'selected' : '' ?>>All Statuses</option>
<option value="complete" <?= $status_filter === 'complete' ? 'selected' : '' ?>>Completed</option>
<option value="processing" <?= $status_filter === 'processing' ? 'selected' : '' ?>>Processing</option>
<option value="failed" <?= $status_filter === 'failed' ? 'selected' : '' ?>>Failed</option>
</select>
</div>
<div>
<label style="display: block; color: #a0aec0; margin-bottom: 0.5rem;">User Filter</label>
<select name="user" style="width: 100%; padding: 0.8rem; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 8px; background: rgba(255, 255, 255, 0.05); color: white;">
<option value="all" <?= $user_filter === 'all' ? 'selected' : '' ?>>All Users</option>
<option value="orphaned" <?= $user_filter === 'orphaned' ? 'selected' : '' ?>>Orphaned Tracks</option>
<?php foreach ($users as $user): ?>
<option value="<?= $user['id'] ?>" <?= $user_filter == $user['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($user['name']) ?> (<?= $user['plan'] ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label style="display: block; color: #a0aec0; margin-bottom: 0.5rem;">Search</label>
<input type="text" name="search" value="<?= htmlspecialchars($search_term) ?>"
placeholder="Search tracks..."
style="width: 100%; padding: 0.8rem; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 8px; background: rgba(255, 255, 255, 0.05); color: white;">
</div>
<div>
<button type="submit" class="btn btn-primary" style="width: 100%;">
<i class="fas fa-search"></i> Apply Filters
</button>
</div>
</form>
</div>
<!-- Results Info -->
<div style="margin-bottom: 2rem; color: #a0aec0;">
Showing <?= $offset + 1 ?>-<?= min($offset + $per_page, $total_tracks) ?> of <?= number_format($total_tracks) ?> tracks
<?php if (!empty($search_term) || $status_filter !== 'all' || $user_filter !== 'all'): ?>
<a href="?tab=tracks" class="btn btn-sm btn-secondary" style="margin-left: 1rem;">
<i class="fas fa-times"></i> Clear Filters
</a>
<?php endif; ?>
</div>
<input type="text" id="trackSearch" placeholder="Search tracks by title, user, or status..." class="search-input">
<table class="data-table">
<thead>
<tr>
<th>Track</th>
<th>User</th>
<th>Status</th>
<th>Audio</th>
<th>Social Stats</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($tracks as $track): ?>
<tr data-track-id="<?= $track['id'] ?>">
<td>
<div style="display: flex; align-items: center; gap: 1rem;">
<div style="width: 50px; height: 50px; background: linear-gradient(135deg, #667eea, #764ba2); border-radius: 8px; display: flex; align-items: center; justify-content: center; color: white;">
<i class="fas fa-music"></i>
</div>
<div>
<div style="font-weight: 600; color: white;"><?= htmlspecialchars($track['title'] ?? 'Untitled') ?></div>
<div style="font-size: 1.2rem; color: #a0aec0;">ID: <?= $track['id'] ?></div>
<?php if (!empty($track['prompt'])): ?>
<div style="font-size: 1.2rem; color: #a0aec0;"><?= htmlspecialchars(substr($track['prompt'], 0, 50)) ?>...</div>
<?php endif; ?>
<?php if (isset($track['music_type'])): ?>
<div style="font-size: 1.2rem; color: #a0aec0;">Type: <?= htmlspecialchars($track['music_type']) ?></div>
<?php endif; ?>
</div>
</div>
</td>
<td>
<?php if ($track['user_name']): ?>
<div>
<div style="font-weight: 600; color: white;"><?= htmlspecialchars($track['display_name']) ?></div>
<div style="font-size: 1.2rem; color: #a0aec0;"><?= htmlspecialchars($track['display_email']) ?></div>
<div style="font-size: 1.2rem; color: #a0aec0;"><?= ucfirst($track['user_plan'] ?? 'free') ?></div>
</div>
<?php else: ?>
<span style="color: #f56565; font-weight: bold;">⚠️ Orphaned</span>
<?php endif; ?>
</td>
<td>
<span class="status-badge status-<?= $track['status'] ?>">
<?= ucfirst($track['status']) ?>
</span>
<?php if ($track['duration']): ?>
<div style="font-size: 1.2rem; color: #a0aec0; margin-top: 0.5rem;">
<?= floor($track['duration'] / 60) ?>m <?= $track['duration'] % 60 ?>s
</div>
<?php endif; ?>
</td>
<td>
<?php if ($track['audio_url']): ?>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<i class="fas fa-music" style="color: #48bb78;"></i>
<span style="color: #48bb78;">Available</span>
<?php if (strpos($track['audio_url'], 'apiboxfiles.erweima.ai') !== false): ?>
<i class="fas fa-cloud" style="color: #667eea;" title="CDN Track"></i>
<?php endif; ?>
</div>
<button class="btn btn-sm btn-secondary" onclick="playTrack('<?= htmlspecialchars($track['audio_url']) ?>', '<?= htmlspecialchars($track['title'] ?? 'Untitled') ?>')" style="margin-top: 0.5rem;">
<i class="fas fa-play"></i> Play
</button>
<?php else: ?>
<span style="color: #f56565;">No Audio</span>
<?php endif; ?>
</td>
<td>
<div style="display: flex; flex-direction: column; gap: 0.2rem; font-size: 1.2rem;">
<span><i class="fas fa-heart" style="color: #e53e3e;"></i> <?= number_format($track['like_count']) ?></span>
<span><i class="fas fa-comment" style="color: #3182ce;"></i> <?= number_format($track['comment_count']) ?></span>
<span><i class="fas fa-play" style="color: #38a169;"></i> <?= number_format($track['play_count']) ?></span>
<span><i class="fas fa-eye" style="color: #d69e2e;"></i> <?= number_format($track['view_count']) ?></span>
</div>
</td>
<td><?= date('M j, Y H:i', strtotime($track['created_at'])) ?></td>
<td>
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
<select class="user-transfer" data-track-id="<?= $track['id'] ?>" style="background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; padding: 0.5rem; border-radius: 4px; font-size: 1.2rem;">
<option value="">Transfer to...</option>
<?php foreach ($users as $user): ?>
<option value="<?= $user['id'] ?>"><?= htmlspecialchars($user['name']) ?> (<?= $user['plan'] ?>)</option>
<?php endforeach; ?>
</select>
<div style="display: flex; gap: 0.5rem;">
<button class="btn btn-sm btn-primary" onclick="editTrack(<?= $track['id'] ?>)">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-warning" onclick="retryTrack(<?= $track['id'] ?>)">
<i class="fas fa-redo"></i>
</button>
<button class="btn btn-sm btn-danger" onclick="deleteTrack(<?= $track['id'] ?>)">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<div class="pagination-container" style="margin-top: 3rem; text-align: center;">
<div class="pagination-controls" style="display: flex; justify-content: center; gap: 0.5rem; flex-wrap: wrap;">
<?php if ($page > 1): ?>
<a href="?tab=tracks&page=1&status=<?= $status_filter ?>&user=<?= $user_filter ?>&search=<?= urlencode($search_term) ?>" class="btn btn-secondary">
<i class="fas fa-angle-double-left"></i> First
</a>
<a href="?tab=tracks&page=<?= $page - 1 ?>&status=<?= $status_filter ?>&user=<?= $user_filter ?>&search=<?= urlencode($search_term) ?>" class="btn btn-secondary">
<i class="fas fa-angle-left"></i> Previous
</a>
<?php endif; ?>
<?php
$start_page = max(1, $page - 2);
$end_page = min($total_pages, $page + 2);
for ($i = $start_page; $i <= $end_page; $i++):
?>
<a href="?tab=tracks&page=<?= $i ?>&status=<?= $status_filter ?>&user=<?= $user_filter ?>&search=<?= urlencode($search_term) ?>"
class="btn <?= $i == $page ? 'btn-primary' : 'btn-secondary' ?>">
<?= $i ?>
</a>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<a href="?tab=tracks&page=<?= $page + 1 ?>&status=<?= $status_filter ?>&user=<?= $user_filter ?>&search=<?= urlencode($search_term) ?>" class="btn btn-secondary">
Next <i class="fas fa-angle-right"></i>
</a>
<a href="?tab=tracks&page=<?= $total_pages ?>&status=<?= $status_filter ?>&user=<?= $user_filter ?>&search=<?= urlencode($search_term) ?>" class="btn btn-secondary">
Last <i class="fas fa-angle-double-right"></i>
</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<script>
// Play track in global player
function playTrack(audioUrl, title) {
if (typeof window.playTrackWithGlobalPlayer === 'function') {
window.playTrackWithGlobalPlayer(audioUrl, title, 'Admin Track');
} else if (typeof window.globalPlayer !== 'undefined' && window.globalPlayer.playTrack) {
window.globalPlayer.playTrack(audioUrl, title, 'Admin Track');
} else {
alert('Global player not available');
}
}
// Transfer track to different user
document.querySelectorAll('.user-transfer').forEach(select => {
select.addEventListener('change', function() {
if (this.value) {
const trackId = this.getAttribute('data-track-id');
const userId = this.value;
if (confirm('Transfer this track to the selected user?')) {
transferTrack(trackId, userId, this);
} else {
this.value = ''; // Reset selection
}
}
});
});
function transferTrack(trackId, userId, select) {
fetch('/admin_api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'transfer_track',
track_id: trackId,
user_id: userId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the user display in the table
const row = select.closest('tr');
const userCell = row.querySelector('td:nth-child(2)');
const selectedUser = select.options[select.selectedIndex].text;
userCell.innerHTML = `
<div>
<div style="font-weight: 600; color: white;">${selectedUser}</div>
<div style="font-size: 1.2rem; color: #a0aec0;">Transferred</div>
</div>
`;
select.value = ''; // Reset selection
showNotification('Track transferred successfully!', 'success');
} else {
alert('Error: ' + data.message);
select.value = ''; // Reset selection
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to transfer track');
select.value = ''; // Reset selection
});
}
// Edit track
function editTrack(trackId) {
// Open edit modal or redirect to edit page
window.open(`/edit_track.php?id=${trackId}`, '_blank');
}
// Retry failed track
function retryTrack(trackId) {
if (confirm('Retry this track?')) {
fetch('/admin_api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'retry_track',
track_id: trackId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to retry track');
});
}
}
// Delete track
function deleteTrack(trackId) {
if (confirm('Are you sure you want to delete this track? This action cannot be undone.')) {
fetch('/admin_api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'delete_track',
track_id: trackId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const row = document.querySelector(`[data-track-id="${trackId}"]`);
row.style.background = 'rgba(245, 101, 101, 0.1)';
setTimeout(() => {
row.remove();
}, 1000);
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to delete track');
});
}
}
// Assign all orphaned tracks to admin
function assignAllOrphanedTracks() {
if (confirm('Assign all orphaned tracks to admin user? This will organize all tracks properly.')) {
fetch('/admin_api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'assign_all_orphaned_tracks'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(`Successfully assigned ${data.assigned_count} tracks to admin`);
location.reload();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to assign tracks');
});
}
}
// Search functionality
document.getElementById('trackSearch').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const rows = document.querySelectorAll('.data-table tbody tr');
rows.forEach(row => {
const title = row.querySelector('td:first-child').textContent.toLowerCase();
const user = row.querySelector('td:nth-child(2)').textContent.toLowerCase();
const status = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
if (title.includes(searchTerm) || user.includes(searchTerm) || status.includes(searchTerm)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
});
// Show notification
function showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#48bb78' : '#667eea'};
color: white;
padding: 1rem 2rem;
border-radius: 8px;
z-index: 10000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
</script>