T.ME/BIBIL_0DAY
CasperSecurity


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/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/soundstudiopro.com/public_html/api_global_search.php
<?php
// Error handling - ensure JSON is always returned
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);

// Set JSON header early
header('Content-Type: application/json');

// Function to return error JSON
function returnError($message, $code = 500) {
    http_response_code($code);
    echo json_encode([
        'error' => $message,
        'tracks' => [],
        'artists' => [],
        'genres' => [],
        'events' => [],
        'crates' => [],
        'keywords' => []
    ]);
    exit;
}

try {
    session_start();
    require_once 'config/database.php';
} catch (Exception $e) {
    error_log('Global search API error loading dependencies: ' . $e->getMessage());
    returnError('Failed to initialize search service', 500);
}

$query = trim($_GET['q'] ?? '');
$limit = min((int)($_GET['limit'] ?? 10), 20); // Max 20 results per category

if (empty($query) || strlen($query) < 2) {
    echo json_encode([
        'tracks' => [],
        'artists' => [],
        'genres' => [],
        'events' => [],
        'crates' => [],
        'keywords' => []
    ]);
    exit;
}

try {
    $pdo = getDBConnection();
    if (!$pdo) {
        throw new Exception('Database connection failed');
    }
} catch (Exception $e) {
    error_log('Global search API database error: ' . $e->getMessage());
    returnError('Database connection failed', 500);
}
// Create search parameter with wildcards for partial matching
// This will match "psy" in "Psytrance" or "deep" in "Deep House"
$search_param = '%' . strtolower($query) . '%';

// Debug logging (remove in production if needed)
error_log("Global search query: " . $query);
error_log("Global search param: " . $search_param);
$results = [
    'tracks' => [],
    'artists' => [],
    'genres' => [],
    'events' => [],
    'crates' => [],
    'keywords' => []
];

// Search tracks (title, artist, tags, metadata)
$tracks_sql = "
    SELECT DISTINCT
        mt.id,
        mt.title,
        mt.image_url,
        mt.task_id,
        mt.genre,
        mt.tags,
        mt.metadata,
        u.name as artist_name,
        u.id as artist_id,
        u.profile_image,
        COALESCE(play_stats.play_count, 0) as play_count
    FROM music_tracks mt
    INNER JOIN users u ON mt.user_id = u.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
    WHERE mt.status = 'complete'
        AND (
            (mt.audio_url IS NOT NULL AND mt.audio_url != '') 
            OR COALESCE(mt.variations_count, 0) > 0
        )
        AND mt.user_id IS NOT NULL
        AND u.id IS NOT NULL
        AND (mt.is_public = 1 OR mt.is_public IS NULL)
        AND (
            LOWER(mt.title) LIKE ?
            OR LOWER(u.name) LIKE ?
            OR LOWER(mt.tags) LIKE ?
            OR LOWER(mt.genre) LIKE ?
            OR LOWER(CAST(JSON_EXTRACT(mt.metadata, '$.tags') AS CHAR)) LIKE ?
            OR LOWER(JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.tags'))) LIKE ?
            OR LOWER(CAST(JSON_EXTRACT(mt.metadata, '$.genre') AS CHAR)) LIKE ?
            OR LOWER(JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.genre'))) LIKE ?
            OR LOWER(CAST(JSON_EXTRACT(mt.metadata, '$.style') AS CHAR)) LIKE ?
            OR LOWER(JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.style'))) LIKE ?
            OR LOWER(CAST(JSON_EXTRACT(mt.metadata, '$.mood') AS CHAR)) LIKE ?
            OR LOWER(JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.mood'))) LIKE ?
        )
    ORDER BY play_stats.play_count DESC, mt.created_at DESC
    LIMIT ?
";

try {
    $stmt = $pdo->prepare($tracks_sql);
    $stmt->execute([
        $search_param, $search_param, $search_param, $search_param,
        $search_param, $search_param, $search_param, $search_param,
        $search_param, $search_param, $search_param, $search_param,
        $limit
    ]);
    $tracks = $stmt->fetchAll(PDO::FETCH_ASSOC);
    error_log("Global search found " . count($tracks) . " tracks");
} catch (PDOException $e) {
    error_log('Global search tracks query error: ' . $e->getMessage());
    error_log('SQL: ' . $tracks_sql);
    $tracks = [];
}

// Extract image URLs from metadata if image_url is empty
foreach ($tracks as &$track) {
    $imageUrl = $track['image_url'] ?? null;
    
    // If image_url is empty, try to get from metadata
    if (empty($imageUrl) || $imageUrl === 'null' || $imageUrl === 'NULL') {
        if (!empty($track['metadata'])) {
            $metadata = is_string($track['metadata']) ? json_decode($track['metadata'], true) : $track['metadata'];
            if (is_array($metadata)) {
                // Check multiple possible locations in metadata
                if (!empty($metadata['image_url'])) {
                    $imageUrl = $metadata['image_url'];
                } elseif (!empty($metadata['cover_url'])) {
                    $imageUrl = $metadata['cover_url'];
                } elseif (!empty($metadata['raw_callback']['image_url'])) {
                    $imageUrl = $metadata['raw_callback']['image_url'];
                } elseif (!empty($metadata['raw_callback']['cover_url'])) {
                    $imageUrl = $metadata['raw_callback']['cover_url'];
                } elseif (!empty($metadata['raw_callback']['data']['image_url'])) {
                    $imageUrl = $metadata['raw_callback']['data']['image_url'];
                } elseif (!empty($metadata['raw_callback']['data']['cover_url'])) {
                    $imageUrl = $metadata['raw_callback']['data']['cover_url'];
                }
            }
        }
        
        // Normalize the image URL if found
        if (!empty($imageUrl) && $imageUrl !== 'null' && $imageUrl !== 'NULL') {
            // Only use local paths (no external URLs)
            if (!str_starts_with($imageUrl, 'http://') && !str_starts_with($imageUrl, 'https://')) {
                if (!str_starts_with($imageUrl, '/')) {
                    $imageUrl = '/' . ltrim($imageUrl, '/');
                }
                $track['image_url'] = $imageUrl;
            } else {
                $track['image_url'] = null;
            }
        } else {
            $track['image_url'] = null;
        }
    } else {
        // Normalize existing image_url
        if (!str_starts_with($imageUrl, 'http://') && !str_starts_with($imageUrl, 'https://')) {
            if (!str_starts_with($imageUrl, '/')) {
                $imageUrl = '/' . ltrim($imageUrl, '/');
            }
            $track['image_url'] = $imageUrl;
        } else {
            $track['image_url'] = null;
        }
    }
    
    // Fallback: Try to find image file by task_id pattern if image_url is still empty
    if (empty($track['image_url']) || $track['image_url'] === 'null' || $track['image_url'] === 'NULL') {
        if (!empty($track['task_id'])) {
            $uploadsDir = $_SERVER['DOCUMENT_ROOT'] . '/uploads/track_covers/';
            if (is_dir($uploadsDir)) {
                $pattern = $uploadsDir . "track_{$track['task_id']}_*";
                $files = glob($pattern);
                if (!empty($files)) {
                    $mostRecent = end($files);
                    $track['image_url'] = '/uploads/track_covers/' . basename($mostRecent);
                }
            }
        }
    }
}
unset($track);

$results['tracks'] = $tracks;

// Search artists (all users, regardless of public track count)
$artists_sql = "
    SELECT DISTINCT
        u.id,
        u.name,
        COALESCE(up.profile_image, u.profile_image) as profile_image,
        COALESCE(up.profile_position, 'center center') as profile_position,
        up.bio,
        up.location,
        up.genres,
        COALESCE((SELECT COUNT(*) FROM music_tracks WHERE user_id = u.id AND status = 'complete' AND (is_public = 1 OR is_public IS NULL)), 0) as track_count,
        COALESCE((SELECT COUNT(*) FROM user_follows WHERE following_id = u.id), 0) as followers_count
    FROM users u
    LEFT JOIN user_profiles up ON u.id = up.user_id
    WHERE (
        u.name LIKE ?
        OR up.bio LIKE ?
        OR up.location LIKE ?
        OR up.music_style LIKE ?
        OR up.genres LIKE ?
    )
    ORDER BY followers_count DESC, track_count DESC, u.name ASC
    LIMIT ?
";

try {
    $stmt = $pdo->prepare($artists_sql);
    $stmt->execute([
        $search_param, $search_param, $search_param, $search_param,
        $search_param, $limit
    ]);
    $results['artists'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    error_log('Global search artists query error: ' . $e->getMessage());
    $results['artists'] = [];
}

// Search events (title, description, location, venue)
$events_sql = "
    SELECT 
        e.id,
        e.title,
        e.description,
        e.event_type,
        e.location,
        e.venue_name,
        e.start_date,
        e.cover_image,
        e.status,
        u.name as creator_name,
        u.id as creator_id
    FROM events e
    INNER JOIN users u ON e.creator_id = u.id
    WHERE e.status = 'published'
        AND (
            e.title LIKE ?
            OR e.description LIKE ?
            OR e.location LIKE ?
            OR e.venue_name LIKE ?
        )
    ORDER BY e.start_date ASC
    LIMIT ?
";

try {
    $stmt = $pdo->prepare($events_sql);
    $stmt->execute([$search_param, $search_param, $search_param, $search_param, $limit]);
    $results['events'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    error_log('Global search events query error: ' . $e->getMessage());
    $results['events'] = [];
}

// Search crates (public playlists)
// Note: is_public = 1 OR is_public IS NULL to include crates without explicit public flag
$crates_sql = "
    SELECT 
        ap.id,
        ap.name,
        ap.description,
        ap.created_at,
        COUNT(DISTINCT pt.track_id) as track_count,
        COALESCE(SUM(mt.duration), 0) as total_duration,
        u.name as artist_name,
        u.id as artist_id,
        COALESCE(up.profile_image, u.profile_image) as artist_image
    FROM artist_playlists ap
    INNER JOIN users u ON ap.user_id = u.id
    LEFT JOIN user_profiles up ON u.id = up.user_id
    LEFT JOIN playlist_tracks pt ON ap.id = pt.playlist_id
    LEFT JOIN music_tracks mt ON pt.track_id = mt.id AND mt.status = 'complete'
    WHERE (ap.is_public = 1 OR ap.is_public IS NULL)
        AND (
            LOWER(ap.name) LIKE ?
            OR LOWER(ap.description) LIKE ?
            OR LOWER(u.name) LIKE ?
        )
    GROUP BY ap.id, ap.name, ap.description, ap.created_at, u.name, u.id, up.profile_image, u.profile_image
    HAVING track_count > 0
    ORDER BY track_count DESC, ap.updated_at DESC
    LIMIT ?
";

try {
    $stmt = $pdo->prepare($crates_sql);
    $stmt->execute([$search_param, $search_param, $search_param, $limit]);
    $crates = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
    // Format duration for display
    foreach ($crates as &$crate) {
        $totalSeconds = intval($crate['total_duration']);
        $hours = floor($totalSeconds / 3600);
        $minutes = floor(($totalSeconds % 3600) / 60);
        $crate['duration_formatted'] = $hours > 0 
            ? sprintf('%dh %dm', $hours, $minutes)
            : sprintf('%dm', $minutes);
    }
    unset($crate);
    
    $results['crates'] = $crates;
} catch (PDOException $e) {
    error_log('Global search crates query error: ' . $e->getMessage());
    $results['crates'] = [];
}

// Search genres specifically (from tracks and artist profiles)
// Use case-insensitive search and match partial words
// Also check metadata.style as some tracks store genre there
$genres_sql = "
    SELECT DISTINCT
        COALESCE(
            JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.genre')),
            JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.style')),
            mt.genre
        ) as genre,
        COUNT(DISTINCT mt.id) as track_count
    FROM music_tracks mt
    WHERE mt.status = 'complete'
        AND (mt.is_public = 1 OR mt.is_public IS NULL)
        AND (
            LOWER(mt.genre) LIKE ?
            OR LOWER(JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.genre'))) LIKE ?
            OR LOWER(JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.style'))) LIKE ?
            OR LOWER(CAST(JSON_EXTRACT(mt.metadata, '$.genre') AS CHAR)) LIKE ?
            OR LOWER(CAST(JSON_EXTRACT(mt.metadata, '$.style') AS CHAR)) LIKE ?
        )
        AND COALESCE(
            JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.genre')),
            JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.style')),
            mt.genre
        ) IS NOT NULL
        AND COALESCE(
            JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.genre')),
            JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.style')),
            mt.genre
        ) != ''
    GROUP BY genre
    HAVING genre IS NOT NULL AND genre != ''
    ORDER BY track_count DESC, genre ASC
    LIMIT ?
";

try {
    $stmt = $pdo->prepare($genres_sql);
    $stmt->execute([$search_param, $search_param, $search_param, $search_param, $search_param, $limit]);
    $genres = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    error_log('Global search genres query error: ' . $e->getMessage());
    $genres = [];
}

// Also search genres from artist profiles
$artist_genres_sql = "
    SELECT DISTINCT
        JSON_UNQUOTE(JSON_EXTRACT(up.genres, CONCAT('$[', idx.idx, ']'))) as genre
    FROM user_profiles up
    CROSS JOIN (
        SELECT 0 as idx UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
        UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
    ) idx
    WHERE up.genres IS NOT NULL
        AND LOWER(JSON_UNQUOTE(JSON_EXTRACT(up.genres, CONCAT('$[', idx.idx, ']')))) LIKE ?
        AND JSON_UNQUOTE(JSON_EXTRACT(up.genres, CONCAT('$[', idx.idx, ']'))) IS NOT NULL
        AND JSON_UNQUOTE(JSON_EXTRACT(up.genres, CONCAT('$[', idx.idx, ']'))) != ''
    LIMIT ?
";

try {
    $stmt = $pdo->prepare($artist_genres_sql);
    $stmt->execute([$search_param, $limit]);
    $artist_genres = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
    // Merge artist genres with track genres
    foreach ($artist_genres as $ag) {
        if (!empty($ag['genre'])) {
            $found = false;
            foreach ($genres as &$g) {
                if (strtolower(trim($g['genre'])) === strtolower(trim($ag['genre']))) {
                    $found = true;
                    break;
                }
            }
            if (!$found) {
                $genres[] = ['genre' => $ag['genre'], 'track_count' => 0];
            }
        }
    }
} catch (Exception $e) {
    // If JSON extraction fails, skip artist genres
}

// Filter and deduplicate genres
$unique_genres = [];
foreach ($genres as $g) {
    if (!empty($g['genre']) && strlen($g['genre']) > 1) {
        $key = strtolower(trim($g['genre']));
        if (!isset($unique_genres[$key])) {
            $unique_genres[$key] = [
                'genre' => trim($g['genre']),
                'track_count' => $g['track_count'] ?? 0
            ];
        } else {
            // Merge track counts if duplicate found
            $unique_genres[$key]['track_count'] += ($g['track_count'] ?? 0);
        }
    }
}

// Sort by track count descending, then alphabetically
usort($unique_genres, function($a, $b) {
    if ($a['track_count'] != $b['track_count']) {
        return $b['track_count'] - $a['track_count'];
    }
    return strcasecmp($a['genre'], $b['genre']);
});

$results['genres'] = array_slice($unique_genres, 0, $limit);

// Extract unique keywords from tracks (style, mood, tags - excluding genres which are now separate)
$keywords_sql = "
    SELECT DISTINCT
        JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.style')) as keyword,
        'style' as type
    FROM music_tracks mt
    WHERE mt.status = 'complete'
        AND JSON_EXTRACT(mt.metadata, '$.style') IS NOT NULL
        AND JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.style')) LIKE ?
        AND JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.style')) != ''
    UNION
    SELECT DISTINCT
        JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.mood')) as keyword,
        'mood' as type
    FROM music_tracks mt
    WHERE mt.status = 'complete'
        AND JSON_EXTRACT(mt.metadata, '$.mood') IS NOT NULL
        AND JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.mood')) LIKE ?
        AND JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.mood')) != ''
        AND JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.mood')) != 'neutral'
    UNION
    SELECT DISTINCT
        JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.tags')) as keyword,
        'tag' as type
    FROM music_tracks mt
    WHERE mt.status = 'complete'
        AND JSON_EXTRACT(mt.metadata, '$.tags') IS NOT NULL
        AND JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.tags')) LIKE ?
        AND JSON_UNQUOTE(JSON_EXTRACT(mt.metadata, '$.tags')) != ''
    LIMIT ?
";

try {
    $stmt = $pdo->prepare($keywords_sql);
    $stmt->execute([$search_param, $search_param, $search_param, $limit]);
    $keywords = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    error_log('Global search keywords query error: ' . $e->getMessage());
    $keywords = [];
}

// Filter and deduplicate keywords - split comma-separated values
$unique_keywords = [];
foreach ($keywords as $kw) {
    if (!empty($kw['keyword'])) {
        $keywordValue = $kw['keyword'];
        
        // Handle JSON arrays (tags might be stored as JSON array)
        if (is_string($keywordValue) && (str_starts_with($keywordValue, '[') || str_starts_with($keywordValue, '"'))) {
            $decoded = json_decode($keywordValue, true);
            if (is_array($decoded)) {
                // If it's an array, extract individual tags
                foreach ($decoded as $tag) {
                    if (is_string($tag) && strlen(trim($tag)) > 1) {
                        $tag = trim($tag);
                        // Split by comma if it's a comma-separated string
                        $tags = explode(',', $tag);
                        foreach ($tags as $singleTag) {
                            $singleTag = trim($singleTag);
                            if (strlen($singleTag) > 1 && strlen($singleTag) < 50) {
                                $key = strtolower($singleTag);
                                if (!isset($unique_keywords[$key])) {
                                    $unique_keywords[$key] = [
                                        'keyword' => $singleTag,
                                        'type' => $kw['type'] ?? 'tag'
                                    ];
                                }
                            }
                        }
                    }
                }
                continue;
            } elseif (is_string($decoded)) {
                $keywordValue = $decoded;
            }
        }
        
        // Handle single string values - split by comma if comma-separated
        if (is_string($keywordValue)) {
            $keywordValue = trim($keywordValue);
            // Split by comma to handle comma-separated keywords
            $keywordParts = explode(',', $keywordValue);
            foreach ($keywordParts as $part) {
                $part = trim($part);
                // Skip if too short, too long, or contains special characters that suggest it's code
                if (strlen($part) > 1 && strlen($part) < 50 && 
                    !str_starts_with($part, '{') && !str_starts_with($part, '[') &&
                    !str_contains($part, '=>') && !str_contains($part, ':')) {
                    $key = strtolower($part);
                    if (!isset($unique_keywords[$key])) {
                        $unique_keywords[$key] = [
                            'keyword' => $part,
                            'type' => $kw['type'] ?? 'tag'
                        ];
                    }
                }
            }
        }
    }
}
$results['keywords'] = array_values($unique_keywords);

// Ensure all required keys exist
$final_results = [
    'tracks' => $results['tracks'] ?? [],
    'artists' => $results['artists'] ?? [],
    'genres' => $results['genres'] ?? [],
    'events' => $results['events'] ?? [],
    'crates' => $results['crates'] ?? [],
    'keywords' => $results['keywords'] ?? []
];

// Debug: Log result counts
error_log("Global search results - Tracks: " . count($final_results['tracks']) . 
          ", Artists: " . count($final_results['artists']) . 
          ", Genres: " . count($final_results['genres']) . 
          ", Events: " . count($final_results['events']) . 
          ", Crates: " . count($final_results['crates']) . 
          ", Keywords: " . count($final_results['keywords']));

// Output JSON with error handling
try {
    echo json_encode($final_results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} catch (Exception $e) {
    error_log('Global search JSON encoding error: ' . $e->getMessage());
    returnError('Failed to format search results', 500);
}


CasperSecurity Mini