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/private_html/utils/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/soundstudiopro.com/private_html/utils/subscription_helpers.php
<?php
/**
 * Subscription Helper Functions
 * Functions to check subscription status and monthly track limits
 */

require_once __DIR__ . '/../config/database.php';

/**
 * Check if user has active subscription
 */
function hasActiveSubscription($user_id) {
    try {
        $pdo = getDBConnection();
        
        // Check if table exists first
        $table_check = $pdo->query("SHOW TABLES LIKE 'user_subscriptions'");
        if ($table_check->rowCount() === 0) {
            error_log("hasActiveSubscription: user_subscriptions table does not exist");
            return false; // Table doesn't exist, so no subscriptions
        }
        
        $stmt = $pdo->prepare("
            SELECT id, plan_name, status, current_period_start, current_period_end
            FROM user_subscriptions
            WHERE user_id = ?
            AND status IN ('active', 'trialing')
            AND current_period_end > NOW()
            ORDER BY created_at DESC
            LIMIT 1
        ");
        $stmt->execute([$user_id]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result ? $result : false;
    } catch (PDOException $e) {
        error_log("hasActiveSubscription PDO error: " . $e->getMessage());
        return false; // Return false on error instead of throwing
    } catch (Exception $e) {
        error_log("hasActiveSubscription error: " . $e->getMessage());
        return false;
    }
}

/**
 * Check if subscription is in a state that allows track creation
 * Returns true if subscription is active/trialing, false if past_due/canceled
 */
function isSubscriptionActiveForUsage($user_id) {
    $pdo = getDBConnection();
    
    $stmt = $pdo->prepare("
        SELECT status
        FROM user_subscriptions
        WHERE user_id = ?
        AND status IN ('active', 'trialing')
        AND current_period_end > NOW()
        ORDER BY created_at DESC
        LIMIT 1
    ");
    $stmt->execute([$user_id]);
    $result = $stmt->fetch(PDO::FETCH_ASSOC);
    
    return $result !== false;
}

/**
 * Get or create monthly track usage record based on subscription period
 */
function getMonthlyTrackUsage($user_id, $plan_name = null) {
    $pdo = getDBConnection();
    
    // Get active subscription to determine current period
    $subscription = hasActiveSubscription($user_id);
    if (!$subscription) {
        return null; // No subscription, no usage tracking
    }
    
    $subscription_id = $subscription['id'] ?? null;
    $period_start = $subscription['current_period_start'] ?? null;
    
    if (!$period_start) {
        return null;
    }
    
    // Convert period_start to datetime string if it's not already
    if (is_numeric($period_start)) {
        $period_start = date('Y-m-d H:i:s', $period_start);
    }
    
    // If plan_name not provided, get it from subscription
    if (!$plan_name) {
        $plan_name = $subscription['plan_name'];
    }
    
    // Get track limit for current plan
    $plans_config = require __DIR__ . '/../config/subscription_plans.php';
    $expected_track_limit = 0;
    if (isset($plans_config[$plan_name])) {
        $expected_track_limit = $plans_config[$plan_name]['tracks_per_month'];
    }
    
    // Get or create usage record for this subscription period
    $stmt = $pdo->prepare("
        SELECT * FROM monthly_track_usage
        WHERE user_id = ? AND subscription_period_start = ?
    ");
    $stmt->execute([$user_id, $period_start]);
    $usage = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if (!$usage) {
        // Create new record for this subscription period
        // Also set year_month for backward compatibility
        $year_month = date('Y-m', strtotime($period_start));
        
        $stmt = $pdo->prepare("
            INSERT INTO monthly_track_usage (
                user_id, subscription_id, subscription_period_start, 
                year_month, tracks_created, track_limit, reset_at
            )
            VALUES (?, ?, ?, ?, 0, ?, NOW())
        ");
        $stmt->execute([
            $user_id, 
            $subscription_id, 
            $period_start, 
            $year_month, 
            $expected_track_limit
        ]);
        
        // Get the created record
        $stmt = $pdo->prepare("
            SELECT * FROM monthly_track_usage
            WHERE user_id = ? AND subscription_period_start = ?
        ");
        $stmt->execute([$user_id, $period_start]);
        $usage = $stmt->fetch(PDO::FETCH_ASSOC);
    } else {
        // Record exists - check if track_limit needs to be updated (plan changed)
        if ($expected_track_limit > 0 && $usage['track_limit'] != $expected_track_limit) {
            // Plan changed - update track_limit
            // CRITICAL: If downgrading (new limit < old limit), cap tracks_created to new limit
            $old_track_limit = $usage['track_limit'];
            $current_tracks_created = (int)($usage['tracks_created'] ?? 0);
            $is_downgrade = $expected_track_limit < $old_track_limit;
            
            // If downgrading and tracks_created exceeds new limit, cap it
            $new_tracks_created = $current_tracks_created;
            if ($is_downgrade && $current_tracks_created > $expected_track_limit) {
                $new_tracks_created = $expected_track_limit;
                error_log("SECURITY: User {$user_id} downgraded from {$old_track_limit} to {$expected_track_limit} tracks. Capping tracks_created from {$current_tracks_created} to {$expected_track_limit}");
            }
            
            $update_stmt = $pdo->prepare("
                UPDATE monthly_track_usage
                SET track_limit = ?,
                    tracks_created = ?,
                    updated_at = NOW()
                WHERE user_id = ? AND subscription_period_start = ?
            ");
            $update_stmt->execute([
                $expected_track_limit,
                $new_tracks_created,
                $user_id,
                $period_start
            ]);
            
            // Refresh usage record
            $stmt = $pdo->prepare("
                SELECT * FROM monthly_track_usage
                WHERE user_id = ? AND subscription_period_start = ?
            ");
            $stmt->execute([$user_id, $period_start]);
            $usage = $stmt->fetch(PDO::FETCH_ASSOC);
            
            $action = $is_downgrade ? 'Downgraded' : 'Upgraded';
            error_log("{$action} track_limit from {$old_track_limit} to {$expected_track_limit} for user {$user_id} (plan: {$plan_name}). Tracks created: {$current_tracks_created} → {$new_tracks_created}");
        }
    }
    
    return $usage;
}

/**
 * Check if user can create a track (monthly limit check)
 * Returns array with 'allowed' => true/false and 'message' => string
 */
function canCreateTrack($user_id) {
    $pdo = getDBConnection();
    
    // Get user info
    $stmt = $pdo->prepare("SELECT plan FROM users WHERE id = ?");
    $stmt->execute([$user_id]);
    $user = $stmt->fetch(PDO::FETCH_ASSOC);
    
    if (!$user) {
        return ['allowed' => false, 'message' => 'User not found'];
    }
    
    // Check if user has active subscription (all subscription tiers)
    $subscription = hasActiveSubscription($user_id);
    
    if ($subscription) {
        // Check if subscription status allows usage (exclude past_due, unpaid, canceled)
        if (!in_array($subscription['status'], ['active', 'trialing'])) {
            // Subscription exists but not in usable state - fall back to credits
            // Check if user has credits available
            $stmt = $pdo->prepare("SELECT credits FROM users WHERE id = ?");
            $stmt->execute([$user_id]);
            $user_credits = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if ($user_credits && (int)$user_credits['credits'] >= 1) {
                // User has credits - allow using credits instead
                return [
                    'allowed' => true,
                    'system' => 'credits',
                    'message' => "Your subscription is {$subscription['status']}. Using your available credits instead.",
                    'status' => $subscription['status']
                ];
            }
            
            // No credits available - block creation
            return [
                'allowed' => false,
                'message' => "Your subscription is {$subscription['status']}. Please update your payment method, purchase credits, or contact support.",
                'status' => $subscription['status']
            ];
        }
        
        // User has subscription - check monthly limit for all subscription tiers
        $subscription_plans = ['essential', 'starter', 'pro', 'premium', 'enterprise'];
        
        if (in_array(strtolower($subscription['plan_name']), $subscription_plans)) {
            $usage = getMonthlyTrackUsage($user_id, $subscription['plan_name']);
            
            if (!$usage) {
                // Usage record not found - getMonthlyTrackUsage should have created it, but if not, try manual creation
                error_log("WARNING: Usage record not found for user $user_id with active subscription. Attempting manual creation...");
                
                try {
                    $period_start = $subscription['current_period_start'] ?? null;
                    if ($period_start) {
                        if (is_numeric($period_start)) {
                            $period_start = date('Y-m-d H:i:s', $period_start);
                        }
                        
                        $plans_config = require __DIR__ . '/../config/subscription_plans.php';
                        $track_limit = $plans_config[$subscription['plan_name']]['tracks_per_month'] ?? 5;
                        $year_month = date('Y-m', strtotime($period_start));
                        
                        $create_stmt = $pdo->prepare("
                            INSERT INTO monthly_track_usage (
                                user_id, subscription_id, subscription_period_start, 
                                year_month, tracks_created, track_limit, reset_at, created_at, updated_at
                            )
                            VALUES (?, ?, ?, ?, 0, ?, NOW(), NOW(), NOW())
                        ");
                        $create_stmt->execute([
                            $user_id,
                            $subscription['id'],
                            $period_start,
                            $year_month,
                            $track_limit
                        ]);
                        
                        // Retry getting usage
                        $usage = getMonthlyTrackUsage($user_id, $subscription['plan_name']);
                        error_log("Manually created usage record for user $user_id, retry result: " . ($usage ? 'success' : 'still failed'));
                    }
                } catch (Exception $e) {
                    error_log("Error manually creating usage record: " . $e->getMessage());
                }
                
                if (!$usage) {
                    // Still no usage record - this is an error, but allow track creation using subscription
                    // The incrementMonthlyTrackUsage function will create it if needed
                    error_log("ERROR: Could not create usage record for user $user_id. Will attempt creation in incrementMonthlyTrackUsage().");
                    return [
                        'allowed' => true,
                        'system' => 'subscription', // Force subscription system
                        'tracks_used' => 0,
                        'track_limit' => $plans_config[$subscription['plan_name']]['tracks_per_month'] ?? 5,
                        'tracks_remaining' => $plans_config[$subscription['plan_name']]['tracks_per_month'] ?? 5,
                        'usage_record_missing' => true
                    ];
                }
            }
            
            if ($usage['tracks_created'] >= $usage['track_limit']) {
                // Subscription limit reached - check if user has credits to fall back to
                $stmt = $pdo->prepare("SELECT credits FROM users WHERE id = ?");
                $stmt->execute([$user_id]);
                $user_credits = $stmt->fetch(PDO::FETCH_ASSOC);
                
                if ($user_credits && (int)$user_credits['credits'] >= 1) {
                    // User has credits available - allow using credits
                    error_log("canCreateTrack: Subscription limit reached for user {$user_id}, falling back to credits");
                    return [
                        'allowed' => true,
                        'system' => 'credits',
                        'tracks_used' => $usage['tracks_created'],
                        'track_limit' => $usage['track_limit'],
                        'subscription_limit_reached' => true,
                        'message' => "You've reached your monthly subscription limit. Using your available credits instead."
                    ];
                }
                
                // No credits available - block creation
                $subscription = hasActiveSubscription($user_id);
                $next_reset = 'the start of your next billing period';
                if ($subscription && isset($subscription['current_period_end'])) {
                    $period_end = $subscription['current_period_end'];
                    if (is_numeric($period_end)) {
                        $period_end = date('Y-m-d H:i:s', $period_end);
                    }
                    $next_reset = date('F j, Y', strtotime($period_end));
                }
                
                return [
                    'allowed' => false,
                    'message' => "You've reached your monthly limit of {$usage['track_limit']} tracks. Your limit will reset on {$next_reset} (your next billing date). You can purchase extra credits if you need more tracks now.",
                    'tracks_used' => $usage['tracks_created'],
                    'track_limit' => $usage['track_limit']
                ];
            }
            
            // Return allowed with subscription system (no 'system' key means subscription)
            return [
                'allowed' => true,
                'system' => 'subscription', // Explicitly set to subscription
                'tracks_used' => $usage['tracks_created'],
                'track_limit' => $usage['track_limit'],
                'tracks_remaining' => $usage['track_limit'] - $usage['tracks_created']
            ];
        }
    }
    
    // For non-subscription plans (free, or credit-based), use credit system
    return ['allowed' => true, 'system' => 'credits'];
}

/**
 * Increment monthly track usage for current subscription period
 * Creates usage record if it doesn't exist
 */
function incrementMonthlyTrackUsage($user_id) {
    $pdo = getDBConnection();
    
    // Get active subscription to find current period
    $subscription = hasActiveSubscription($user_id);
    if (!$subscription) {
        error_log("incrementMonthlyTrackUsage: No active subscription for user {$user_id}");
        return false;
    }
    
    $period_start = $subscription['current_period_start'] ?? null;
    if (!$period_start) {
        error_log("incrementMonthlyTrackUsage: No current_period_start for user {$user_id}");
        return false;
    }
    
    // Convert period_start to datetime string if needed
    if (is_numeric($period_start)) {
        $period_start = date('Y-m-d H:i:s', $period_start);
    }
    
    $plan_name = $subscription['plan_name'] ?? 'essential';
    
    // Get track limit for plan
    require_once __DIR__ . '/../config/subscription_plans.php';
    $plans_config = require __DIR__ . '/../config/subscription_plans.php';
    $track_limit = $plans_config[$plan_name]['tracks_per_month'] ?? 5;
    
    // Get year_month for the period
    $year_month = date('Y-m', strtotime($period_start));
    
    // Try to update existing record first
    $stmt = $pdo->prepare("
        UPDATE monthly_track_usage
        SET tracks_created = tracks_created + 1,
            updated_at = NOW()
        WHERE user_id = ? AND subscription_period_start = ?
    ");
    $stmt->execute([$user_id, $period_start]);
    
    if ($stmt->rowCount() > 0) {
        error_log("incrementMonthlyTrackUsage: Updated usage for user {$user_id}, period_start: {$period_start}");
        return true;
    }
    
    // Record doesn't exist - create it with tracks_created = 1
    try {
        // Get subscription_id
        $subscription_id = $subscription['id'] ?? null;
        if (!$subscription_id) {
            // Try to get from database
            $sub_stmt = $pdo->prepare("SELECT id FROM user_subscriptions WHERE user_id = ? AND status IN ('active', 'trialing') ORDER BY created_at DESC LIMIT 1");
            $sub_stmt->execute([$user_id]);
            $sub_data = $sub_stmt->fetch(PDO::FETCH_ASSOC);
            $subscription_id = $sub_data['id'] ?? null;
        }
        
        $insert_stmt = $pdo->prepare("
            INSERT INTO monthly_track_usage (
                user_id, 
                subscription_id, 
                subscription_period_start,
                year_month,
                tracks_created, 
                track_limit, 
                reset_at,
                created_at,
                updated_at
            )
            VALUES (?, ?, ?, ?, 1, ?, NOW(), NOW(), NOW())
            ON DUPLICATE KEY UPDATE 
                tracks_created = tracks_created + 1,
                updated_at = NOW()
        ");
        $insert_stmt->execute([
            $user_id,
            $subscription_id,
            $period_start,
            $year_month,
            $track_limit
        ]);
        
        error_log("incrementMonthlyTrackUsage: Created usage record for user {$user_id}, period_start: {$period_start}, tracks_created: 1");
        return true;
    } catch (Exception $e) {
        error_log("incrementMonthlyTrackUsage: Error creating usage record for user {$user_id}: " . $e->getMessage());
        return false;
    }
}

/**
 * Get subscription info for user
 */
function getSubscriptionInfo($user_id) {
    try {
        $pdo = getDBConnection();
        
        $stmt = $pdo->prepare("
            SELECT 
                us.*,
                u.name as user_name,
                u.email as user_email
            FROM user_subscriptions us
            JOIN users u ON us.user_id = u.id
            WHERE us.user_id = ?
            ORDER BY us.created_at DESC
            LIMIT 1
        ");
        $stmt->execute([$user_id]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result ? $result : null;
    } catch (Exception $e) {
        error_log("Error in getSubscriptionInfo: " . $e->getMessage());
        return null;
    }
}

/**
 * Get effective plan for user - single source of truth
 * Returns the plan the user should be on based on:
 * 1. Active subscription (if exists and valid)
 * 2. users.plan field (if no active subscription)
 * 3. 'free' if subscription is canceled/refunded
 * 
 * Also validates plan_name is not corrupted (e.g., "5/5" instead of "essential")
 */
function getEffectivePlan($user_id) {
    try {
        $pdo = getDBConnection();
        
        // Valid plan keys
        $valid_plans = ['free', 'essential', 'starter', 'pro', 'premium', 'enterprise'];
        
        // First, check for active subscription
        $active_sub = hasActiveSubscription($user_id);
        if ($active_sub && isset($active_sub['plan_name'])) {
            $plan_name = $active_sub['plan_name'];
            
            // Validate plan_name is not corrupted
            if (!in_array($plan_name, $valid_plans)) {
                error_log("WARNING: Corrupted plan_name detected: '{$plan_name}' for user {$user_id}. Falling back to users.plan");
                // Fall through to check users.plan
            } else {
                // Check if subscription status allows usage
                if (in_array($active_sub['status'] ?? '', ['active', 'trialing'])) {
                    return $plan_name;
                }
                // Subscription exists but is canceled/refunded - return 'free'
                return 'free';
            }
        }
        
        // No active subscription - check users.plan
        $stmt = $pdo->prepare("SELECT plan FROM users WHERE id = ?");
        $stmt->execute([$user_id]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if ($user && isset($user['plan'])) {
            $user_plan = $user['plan'];
            
            // Validate plan is valid
            if (in_array($user_plan, $valid_plans)) {
                // Check if there's a canceled/refunded subscription
                $sub_info = getSubscriptionInfo($user_id);
                if ($sub_info && in_array($sub_info['status'] ?? '', ['canceled', 'past_due', 'unpaid'])) {
                    // Subscription was canceled/refunded
                    // BUT: If users.plan is set to a valid plan, use that as source of truth
                    // (The subscription might be old/canceled but user should still see their plan)
                    // Only return 'free' if users.plan is also 'free'
                    if ($user_plan === 'free') {
                        return 'free';
                    } else {
                        // User has a valid plan set - use that (subscription might be old)
                        error_log("User {$user_id} has canceled subscription but users.plan is '{$user_plan}'. Using users.plan as source of truth.");
                        return $user_plan;
                    }
                }
                
                return $user_plan;
            } else {
                error_log("WARNING: Invalid plan in users.plan: '{$user_plan}' for user {$user_id}. Setting to 'free'");
                // Fix corrupted plan
                $fix_stmt = $pdo->prepare("UPDATE users SET plan = 'free' WHERE id = ?");
                $fix_stmt->execute([$user_id]);
                return 'free';
            }
        }
        
        // No plan set - default to 'free'
        return 'free';
    } catch (Exception $e) {
        error_log("Error in getEffectivePlan: " . $e->getMessage());
        return 'free';
    }
}

/**
 * Get effective subscription for display purposes
 * Returns subscription info with plan corrected if needed
 */
function getEffectiveSubscription($user_id) {
    $effective_plan = getEffectivePlan($user_id);
    
    // Get subscription info
    $subscription = getSubscriptionInfo($user_id);
    
    // If subscription exists but plan doesn't match effective plan, return corrected version
    if ($subscription) {
        // If subscription is canceled/refunded and effective plan is 'free', show as free
        if (in_array($subscription['status'] ?? '', ['canceled', 'past_due', 'unpaid']) && $effective_plan === 'free') {
            $subscription['effective_plan'] = 'free';
            $subscription['plan_name'] = $subscription['plan_name']; // Keep original for display
        } else {
            $subscription['effective_plan'] = $effective_plan;
        }
    }
    
    return $subscription;
}
?>


CasperSecurity Mini