![]() 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/ |
<?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;
}
?>