![]() 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/ |
<?php
/**
* Public Crate View Page
* Displays a shared crate with all its tracks
* URL: /crate/123 or /crate.php?id=123
*/
session_start();
require_once 'config/database.php';
require_once 'includes/translations.php';
// Get crate ID from URL
$crate_id = 0;
if (isset($_GET['id'])) {
$crate_id = (int)$_GET['id'];
}
if (!$crate_id) {
header('Location: /community.php');
exit;
}
$pdo = getDBConnection();
// IMPORTANT: Allow bots/crawlers (like Facebook scraper) to access crates even without session
// This is critical for Facebook, Twitter, etc. to scrape the page and show previews
$isBot = isset($_SERVER['HTTP_USER_AGENT']) && (
stripos($_SERVER['HTTP_USER_AGENT'], 'facebookexternalhit') !== false ||
stripos($_SERVER['HTTP_USER_AGENT'], 'Twitterbot') !== false ||
stripos($_SERVER['HTTP_USER_AGENT'], 'LinkedInBot') !== false ||
stripos($_SERVER['HTTP_USER_AGENT'], 'WhatsApp') !== false ||
stripos($_SERVER['HTTP_USER_AGENT'], 'bot') !== false ||
stripos($_SERVER['HTTP_USER_AGENT'], 'crawler') !== false ||
stripos($_SERVER['HTTP_USER_AGENT'], 'spider') !== false
);
// Check if is_description_public column exists
$checkDescCol = $pdo->query("SHOW COLUMNS FROM artist_playlists LIKE 'is_description_public'");
$hasDescPublicColumn = $checkDescCol->rowCount() > 0;
$descPublicField = $hasDescPublicColumn ? ", ap.is_description_public" : "";
// Get crate details (only public crates)
// IMPORTANT: Bots can access public crates for OG tag scraping
$stmt = $pdo->prepare("
SELECT
ap.id,
ap.name,
ap.description,
ap.user_id,
ap.created_at,
ap.updated_at $descPublicField,
u.name as artist_name,
u.id as artist_id,
up.profile_image
FROM artist_playlists ap
JOIN users u ON ap.user_id = u.id
LEFT JOIN user_profiles up ON u.id = up.user_id
WHERE ap.id = ? AND ap.is_public = 1
");
$stmt->execute([$crate_id]);
$crate = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$crate) {
// Crate not found or is private
header('Location: /community.php');
exit;
}
// Hide description if not public
$descPublic = $hasDescPublicColumn ? ($crate['is_description_public'] ?? 1) : 1;
if (!$descPublic) {
$crate['description'] = null;
}
// Check if is_public column exists in playlist_tracks
$checkColumn = $pdo->query("SHOW COLUMNS FROM playlist_tracks LIKE 'is_public'");
$hasTrackPublicColumn = $checkColumn->rowCount() > 0;
$trackPublicFilter = $hasTrackPublicColumn ? "AND (pt.is_public = 1 OR pt.is_public IS NULL)" : "";
// Get tracks in crate - SECURITY: Never expose audio_url, task_id, or metadata in public view
// Client must use get_audio_token.php API to get signed URLs for playback
$stmt = $pdo->prepare("
SELECT
mt.id,
mt.title,
mt.duration,
mt.price,
u.name as artist_name,
u.id as artist_id,
pt.position
FROM playlist_tracks pt
JOIN music_tracks mt ON pt.track_id = mt.id
JOIN users u ON mt.user_id = u.id
WHERE pt.playlist_id = ? AND mt.status = 'complete' $trackPublicFilter
ORDER BY pt.position ASC
");
$stmt->execute([$crate_id]);
$tracks = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Calculate totals
$total_duration = 0;
$set_duration_minutes = 0;
foreach ($tracks as $track) {
$total_duration += intval($track['duration']);
$trackDuration = intval($track['duration']);
if ($trackDuration >= 300) {
$set_duration_minutes += 2.5;
} else {
$set_duration_minutes += $trackDuration * 0.5 / 60;
}
}
$is_2_hour_set = $set_duration_minutes >= 120;
// Format duration
$hours = floor($total_duration / 3600);
$minutes = floor(($total_duration % 3600) / 60);
$duration_formatted = $hours > 0
? sprintf('%dh %dm', $hours, $minutes)
: sprintf('%dm', $minutes);
// Page metadata
$page_title = htmlspecialchars($crate['name']) . ' - Crate by ' . htmlspecialchars($crate['artist_name']) . ' | SoundStudioPro';
$page_description = 'Listen to ' . htmlspecialchars($crate['name']) . ', a curated crate by ' . htmlspecialchars($crate['artist_name']) . ' with ' . count($tracks) . ' tracks on SoundStudioPro.';
include 'includes/header.php';
?>
<style>
.crate-page {
background: linear-gradient(180deg, #0a0a0a 0%, #1a1a1a 100%);
min-height: 100vh;
padding: 2rem 0 4rem;
}
.crate-page-container {
max-width: 1000px;
margin: 0 auto;
padding: 0 2rem;
}
/* Crate Hero */
.crate-hero {
display: flex;
gap: 3rem;
padding: 3rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 24px;
border: 1px solid rgba(102, 126, 234, 0.2);
margin-bottom: 3rem;
}
.crate-artwork {
width: 220px;
height: 220px;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.4) 0%, rgba(118, 75, 162, 0.4) 100%);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
overflow: hidden;
}
.crate-artwork::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="vinyl" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="8" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23vinyl)"/></svg>');
}
.crate-artwork .crate-icon {
font-size: 5rem;
color: white;
z-index: 1;
text-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.crate-details {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.crate-type-label {
font-size: 0.85rem;
color: #667eea;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
margin-bottom: 0.5rem;
}
.crate-title {
font-size: 3rem;
font-weight: 700;
color: white;
margin: 0 0 1rem 0;
line-height: 1.2;
}
.crate-artist-link {
display: inline-flex;
align-items: center;
gap: 0.75rem;
color: #e0e0e0;
text-decoration: none;
font-size: 1.1rem;
margin-bottom: 1.5rem;
transition: color 0.2s ease;
}
.crate-artist-link:hover {
color: #667eea;
}
.crate-artist-link img {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
}
.crate-artist-link .artist-initial {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 0.9rem;
}
.crate-stats {
display: flex;
gap: 2rem;
margin-bottom: 1.5rem;
}
.crate-stat {
display: flex;
align-items: center;
gap: 0.5rem;
color: #a0aec0;
font-size: 1rem;
}
.crate-stat i {
color: #667eea;
}
.crate-description {
color: #a0aec0;
font-size: 1.05rem;
line-height: 1.6;
margin-bottom: 1.5rem;
}
/* Set Progress */
.crate-set-progress {
background: rgba(255, 255, 255, 0.05);
padding: 1rem 1.5rem;
border-radius: 12px;
margin-bottom: 1.5rem;
}
.set-progress-bar {
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.set-progress-fill {
height: 100%;
border-radius: 4px;
transition: width 0.3s ease;
}
.set-progress-fill.complete {
background: linear-gradient(90deg, #10b981, #059669);
}
.set-progress-fill.incomplete {
background: linear-gradient(90deg, #f59e0b, #d97706);
}
.set-progress-label {
display: flex;
justify-content: space-between;
font-size: 0.9rem;
color: #a0aec0;
}
.set-progress-label .ready {
color: #10b981;
font-weight: 600;
}
/* Action Buttons */
.crate-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.crate-action-btn {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 2rem;
border-radius: 12px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.2s ease;
}
.crate-action-btn.primary {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
}
.crate-action-btn.primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3);
}
.crate-action-btn.secondary {
background: rgba(102, 126, 234, 0.15);
color: #667eea;
border: 1px solid rgba(102, 126, 234, 0.3);
}
.crate-action-btn.secondary:hover {
background: rgba(102, 126, 234, 0.25);
}
/* Tracks List */
.crate-tracks-section {
background: rgba(255, 255, 255, 0.03);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
overflow: hidden;
}
.tracks-header {
padding: 1.5rem 2rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
justify-content: space-between;
align-items: center;
}
.tracks-header h3 {
margin: 0;
color: white;
font-size: 1.3rem;
font-weight: 600;
}
.tracks-header .track-count {
color: #a0aec0;
font-size: 0.95rem;
}
.tracks-list {
padding: 1rem;
}
.track-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.5rem;
border-radius: 12px;
transition: background 0.2s ease;
}
.track-item:hover {
background: rgba(255, 255, 255, 0.05);
}
.track-number {
width: 28px;
text-align: center;
color: #666;
font-size: 0.95rem;
font-weight: 500;
}
.track-play-btn {
width: 42px;
height: 42px;
border-radius: 50%;
background: rgba(102, 126, 234, 0.15);
border: none;
color: #667eea;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
font-size: 1rem;
}
.track-play-btn:hover {
background: #667eea;
color: white;
transform: scale(1.1);
}
.track-play-btn.playing {
background: #667eea;
color: white;
box-shadow: 0 0 15px rgba(102, 126, 234, 0.5);
}
.track-info {
flex: 1;
min-width: 0;
}
.track-title {
color: white;
font-weight: 500;
font-size: 1.05rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 0.25rem;
text-decoration: none;
display: block;
transition: all 0.2s ease;
cursor: pointer;
position: relative;
z-index: 10;
}
.track-title:hover {
color: #667eea;
text-decoration: underline;
}
.track-artist {
font-size: 0.9rem;
color: #a0aec0;
text-decoration: none;
display: block;
transition: all 0.2s ease;
cursor: pointer;
position: relative;
z-index: 10;
}
.track-artist:hover {
color: #667eea;
text-decoration: underline;
}
.track-duration {
color: #666;
font-size: 0.95rem;
font-family: monospace;
min-width: 50px;
text-align: right;
}
.track-actions {
display: flex;
gap: 0.5rem;
}
.track-cart-btn {
background: linear-gradient(135deg, #10b981, #059669);
border: none;
color: white;
padding: 0.6rem 1.2rem;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.4rem;
transition: all 0.2s ease;
}
.track-cart-btn:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
.track-cart-btn.added {
background: linear-gradient(135deg, #22c55e, #16a34a);
pointer-events: none;
}
.track-cart-btn:disabled {
opacity: 0.8;
cursor: wait;
}
/* Responsive */
@media (max-width: 768px) {
.crate-hero {
flex-direction: column;
text-align: center;
padding: 2rem;
}
.crate-artwork {
width: 180px;
height: 180px;
margin: 0 auto;
}
.crate-title {
font-size: 2rem;
}
.crate-stats {
justify-content: center;
flex-wrap: wrap;
}
.crate-artist-link {
justify-content: center;
}
.crate-actions {
justify-content: center;
}
.track-item {
flex-wrap: wrap;
gap: 0.75rem;
}
.track-actions {
width: 100%;
margin-top: 0.5rem;
justify-content: flex-end;
}
}
</style>
<div class="crate-page">
<div class="crate-page-container">
<!-- Crate Hero -->
<div class="crate-hero">
<div class="crate-artwork">
<div class="crate-icon">📦</div>
</div>
<div class="crate-details">
<div class="crate-type-label">Public Crate</div>
<h1 class="crate-title"><?= htmlspecialchars($crate['name']) ?></h1>
<a href="/artist/<?= $crate['artist_id'] ?>" class="crate-artist-link">
<?php if (!empty($crate['profile_image'])): ?>
<img src="<?= htmlspecialchars($crate['profile_image']) ?>" alt="<?= htmlspecialchars($crate['artist_name']) ?>">
<?php else: ?>
<span class="artist-initial"><?= strtoupper(substr($crate['artist_name'], 0, 1)) ?></span>
<?php endif; ?>
<span><?= htmlspecialchars($crate['artist_name']) ?></span>
</a>
<div class="crate-stats">
<div class="crate-stat">
<i class="fas fa-music"></i>
<span><?= count($tracks) ?> tracks</span>
</div>
<div class="crate-stat">
<i class="fas fa-clock"></i>
<span><?= $duration_formatted ?></span>
</div>
</div>
<?php if (!empty($crate['description'])): ?>
<p class="crate-description"><?= htmlspecialchars($crate['description']) ?></p>
<?php endif; ?>
<!-- Actions -->
<div class="crate-actions">
<button class="crate-action-btn primary" onclick="playAllTracks()">
<i class="fas fa-play"></i>
Play All
</button>
<button class="crate-action-btn secondary" onclick="shareCrate()">
<i class="fas fa-share-alt"></i>
Share
</button>
</div>
</div>
</div>
<!-- Tracks List -->
<div class="crate-tracks-section">
<div class="tracks-header">
<h3><i class="fas fa-list"></i> Tracks</h3>
<span class="track-count"><?= count($tracks) ?> tracks</span>
</div>
<div class="tracks-list">
<?php if (empty($tracks)): ?>
<div style="text-align: center; padding: 3rem; color: #a0aec0;">
<div style="font-size: 3rem; margin-bottom: 1rem; opacity: 0.5;">🎵</div>
<p>No tracks in this crate yet.</p>
</div>
<?php else: ?>
<?php foreach ($tracks as $index => $track):
$trackDuration = intval($track['duration']);
$mins = floor($trackDuration / 60);
$secs = $trackDuration % 60;
$durationFormatted = sprintf('%d:%02d', $mins, $secs);
$price = floatval($track['price'] ?? 0);
?>
<div class="track-item" data-track-id="<?= $track['id'] ?>" data-index="<?= $index ?>">
<div class="track-number"><?= $index + 1 ?></div>
<button class="track-play-btn" data-index="<?= $index ?>" onclick="togglePlayTrack(<?= $index ?>, this)">
<i class="fas fa-play"></i>
</button>
<div class="track-info">
<a href="/track/<?= $track['id'] ?>" class="track-title" target="_blank" onclick="event.stopPropagation();"><?= htmlspecialchars($track['title'] ?? 'Untitled') ?></a>
<a href="/artist/<?= $track['artist_id'] ?>" class="track-artist" target="_blank" onclick="event.stopPropagation();"><?= htmlspecialchars($track['artist_name']) ?></a>
</div>
<div class="track-duration"><?= $durationFormatted ?></div>
<div class="track-actions">
<?php if ($price > 0): ?>
<button class="track-cart-btn" onclick="addToCart(<?= $track['id'] ?>, '<?= htmlspecialchars($track['title'], ENT_QUOTES) ?>', <?= $price ?>, this)">
<i class="fas fa-cart-plus"></i>
$<?= number_format($price, 2) ?>
</button>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
<script>
// SECURITY: Track metadata only - NO audio URLs exposed
// Audio URLs are fetched via signed tokens when needed
const crateTracks = <?= json_encode(array_map(function($t) {
return [
'id' => $t['id'],
'title' => $t['title'] ?? 'Untitled',
'artist_name' => $t['artist_name'],
'artist_id' => $t['artist_id'],
'duration' => $t['duration']
];
}, $tracks)) ?>;
// Get tracks already in cart from session
const tracksInCart = <?php
$cartTrackIds = [];
if (isset($_SESSION['cart']) && !empty($_SESSION['cart'])) {
foreach ($_SESSION['cart'] as $item) {
if (isset($item['track_id'])) {
$cartTrackIds[] = (int)$item['track_id'];
}
}
}
echo json_encode($cartTrackIds);
?>;
// Translations for cart buttons
const cartTranslations = {
inCart: <?= json_encode(t('crates.in_cart')) ?>,
added: <?= json_encode(t('crates.added')) ?>,
addedToCart: <?= json_encode(t('crates.added_to_cart')) ?>,
alreadyInCart: <?= json_encode(t('crates.already_in_cart')) ?>,
failedToAdd: <?= json_encode(t('crates.failed_to_add')) ?>
};
let currentPlayingIndex = -1;
// Toggle play/pause for crate track
function togglePlayTrack(index, button) {
const audioElement = document.getElementById('globalAudioElement');
const isCurrentlyPlaying = currentPlayingIndex === index;
// If this track is currently playing, toggle pause/play
if (isCurrentlyPlaying && audioElement) {
if (audioElement.paused) {
audioElement.play();
updatePlayButtons(index, true);
} else {
audioElement.pause();
updatePlayButtons(index, false);
}
return;
}
// Otherwise play new track
playTrackSecure(index, button);
}
// Secure play - fetches signed token before playing
async function playTrackSecure(index, button) {
if (!crateTracks[index]) return;
const track = crateTracks[index];
const trackId = track.id;
const title = track.title;
const artist = track.artist_name;
const artistId = track.artist_id;
const duration = track.duration || 300;
// Show loading state
if (button) {
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
button.disabled = true;
}
try {
// Fetch signed audio token from API
const response = await fetch(`/api/get_audio_token.php?track_id=${trackId}&duration=${duration}`);
const data = await response.json();
if (!data.success || !data.url) {
throw new Error(data.error || 'Failed to get audio');
}
const signedUrl = data.url;
currentPlayingIndex = index;
// Update button to pause
updatePlayButtons(index, true);
if (button) button.disabled = false;
// Show now playing notification
if (typeof window.showNotification === 'function') {
window.showNotification('🎵 Now playing: ' + title, 'info');
}
// Play using global player
if (window.enhancedGlobalPlayer && typeof window.enhancedGlobalPlayer.playTrack === 'function') {
// Set up crate as playlist so auto-advance works
if (typeof window.enhancedGlobalPlayer.loadPagePlaylist === 'function') {
window.enhancedGlobalPlayer.loadPagePlaylist(crateTracks, 'crate', index);
}
window.enhancedGlobalPlayer.playTrack(signedUrl, title, artist, trackId, artistId);
} else if (typeof window.playTrack === 'function') {
// Fallback: set playlist on window for global player to pick up
window._communityPlaylist = crateTracks;
window._communityPlaylistType = 'crate';
window._communityTrackIndex = index;
window.playTrack(signedUrl, title, artist, trackId, artistId);
} else {
// Fallback to basic audio
const audio = document.getElementById('globalAudioElement');
if (audio) {
audio.src = signedUrl;
audio.play();
}
}
// Set up event listeners for button sync
const audioElement = document.getElementById('globalAudioElement');
if (audioElement) {
audioElement.onpause = () => updatePlayButtons(currentPlayingIndex, false);
audioElement.onplay = () => updatePlayButtons(currentPlayingIndex, true);
audioElement.onended = () => {
updatePlayButtons(-1, false);
// Auto-play next track
if (currentPlayingIndex < crateTracks.length - 1) {
playTrackSecure(currentPlayingIndex + 1, null);
}
};
}
} catch (error) {
console.error('Error playing track:', error);
if (button) {
button.innerHTML = '<i class="fas fa-play"></i>';
button.disabled = false;
}
if (typeof window.showNotification === 'function') {
window.showNotification('Error loading track. Please try again.', 'error');
}
}
}
function updatePlayButtons(playingIndex, isPlaying) {
document.querySelectorAll('.track-play-btn').forEach((btn, idx) => {
const btnIndex = parseInt(btn.getAttribute('data-index'));
if (btnIndex === playingIndex && isPlaying) {
btn.innerHTML = '<i class="fas fa-pause"></i>';
btn.classList.add('playing');
} else {
btn.innerHTML = '<i class="fas fa-play"></i>';
btn.classList.remove('playing');
}
});
}
function playAllTracks() {
if (crateTracks.length === 0) return;
// Pre-load the crate as playlist before playing
if (window.enhancedGlobalPlayer && typeof window.enhancedGlobalPlayer.loadPagePlaylist === 'function') {
window.enhancedGlobalPlayer.loadPagePlaylist(crateTracks, 'crate', 0);
} else {
// Fallback for window-based playlist
window._communityPlaylist = crateTracks;
window._communityPlaylistType = 'crate';
window._communityTrackIndex = 0;
}
// Start playing from first track
playTrackSecure(0, document.querySelector('.track-play-btn[data-index="0"]'));
}
function shareCrate() {
const url = window.location.href;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url)
.then(() => {
if (typeof window.showNotification === 'function') {
window.showNotification('Crate link copied to clipboard!', 'success');
} else {
alert('Link copied!');
}
});
} else {
const textarea = document.createElement('textarea');
textarea.value = url;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('Link copied!');
}
}
function addToCart(trackId, title, price, button) {
console.log('🛒 Adding to cart:', { trackId, title, price });
// Store original button content
const originalHTML = button ? button.innerHTML : null;
// Add loading state to button
if (button) {
button.disabled = true;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
}
const formData = new FormData();
formData.append('action', 'add');
formData.append('track_id', trackId);
fetch('/cart.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log('🛒 Cart response:', data);
if (data.success) {
// Add track to tracksInCart array immediately
if (!tracksInCart.includes(trackId)) {
tracksInCart.push(trackId);
}
// Show success state on button
if (button) {
button.innerHTML = '<i class="fas fa-check"></i> ' + cartTranslations.added;
button.classList.add('added');
// After showing "Added!", change to "In Cart" and keep it
setTimeout(() => {
if (button) {
button.innerHTML = '<i class="fas fa-check"></i> ' + cartTranslations.inCart;
button.disabled = false;
// Keep the 'added' class to maintain the visual state
}
}, 1500);
}
// Update cart counter in header
const cartCounts = document.querySelectorAll('.cart-count, .cart-counter');
if (cartCounts.length > 0 && data.cart_count !== undefined) {
cartCounts.forEach(count => {
count.textContent = data.cart_count;
if (data.cart_count > 0) {
count.style.display = 'flex';
}
});
} else if (cartCounts.length > 0) {
// Fallback: manually increment
cartCounts.forEach(count => {
const currentCount = parseInt(count.textContent) || 0;
count.textContent = currentCount + 1;
count.style.display = 'flex';
});
}
// Refresh cart modal if open
const cartModal = document.getElementById('cartModal');
if (cartModal && cartModal.style.display === 'flex') {
if (typeof refreshCartModal === 'function') {
refreshCartModal();
}
}
if (typeof window.showNotification === 'function') {
window.showNotification(`🛒 "${title}" ${cartTranslations.addedToCart}`, 'success');
}
} else {
// Handle "already in cart" case - IMMEDIATELY show "In Cart" and keep it
if (data.already_in_cart) {
// Ensure track is in tracksInCart array
if (!tracksInCart.includes(trackId)) {
tracksInCart.push(trackId);
}
if (button) {
button.innerHTML = '<i class="fas fa-check"></i> ' + cartTranslations.inCart;
button.classList.add('added');
button.disabled = false;
// Don't reset - keep it in "In Cart" state permanently
}
if (typeof window.showNotification === 'function') {
window.showNotification(cartTranslations.alreadyInCart, 'info');
}
} else {
// Reset button on error
if (button) {
button.innerHTML = originalHTML;
button.disabled = false;
}
if (typeof window.showNotification === 'function') {
window.showNotification(data.message || data.error || cartTranslations.failedToAdd, 'error');
}
}
}
})
.catch(error => {
console.error('🛒 Cart error:', error);
// Reset button on error
if (button) {
button.innerHTML = originalHTML;
button.disabled = false;
}
if (typeof window.showNotification === 'function') {
window.showNotification(cartTranslations.failedToAdd, 'error');
}
});
}
// Check which tracks are already in cart on page load
function checkCartStatus() {
// Get all track items from the page
const trackItems = document.querySelectorAll('.track-item[data-track-id]');
if (trackItems.length === 0) return;
// Update buttons for tracks that are in cart
trackItems.forEach(item => {
const trackId = parseInt(item.getAttribute('data-track-id'));
if (tracksInCart.includes(trackId)) {
const cartButton = item.querySelector('.track-cart-btn');
if (cartButton && !cartButton.classList.contains('added')) {
// Store original price for potential future use
const originalHTML = cartButton.innerHTML;
cartButton.setAttribute('data-original-html', originalHTML);
cartButton.innerHTML = '<i class="fas fa-check"></i> ' + cartTranslations.inCart;
cartButton.classList.add('added');
cartButton.disabled = false;
}
}
});
}
// Run cart status check on page load
document.addEventListener('DOMContentLoaded', function() {
checkCartStatus();
});
</script>
<?php include 'includes/footer.php'; ?>