prepare(" SELECT sv.*, ul.license_key, ul.purchased_at FROM user_licenses ul JOIN software_versions sv ON ul.version_id = sv.id WHERE ul.user_id = ? AND ul.status = 'active' ORDER BY sv.major_version DESC, sv.minor_version DESC "); $stmt->execute([$userId]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } function getLatestOwnedVersion($userId) { $versions = getUserOwnedVersions($userId); return !empty($versions) ? $versions[0] : null; } function checkVersionAccess($userId, $versionId) { global $pdo; // Get version and its access rules $stmt = $pdo->prepare(" SELECT sv.*, var.access_type, var.requires_base_version, var.price FROM software_versions sv JOIN version_access_rules var ON sv.id = var.version_id WHERE sv.id = ? "); $stmt->execute([$versionId]); $version = $stmt->fetch(PDO::FETCH_ASSOC); if (!$version) { return ['accessible' => false, 'reason' => 'Version not found']; } switch ($version['access_type']) { case 'free_all': // Free for everyone (like v0.99) return [ 'accessible' => true, 'reason' => 'free_for_all', 'price' => 0.00, 'requires_payment' => false ]; case 'free_for_owners': // Free for owners of required base version (like v1.1 for v1.0 owners) if ($version['requires_base_version']) { $hasBaseVersion = userOwnsVersion($userId, $version['requires_base_version']); if ($hasBaseVersion) { return [ 'accessible' => true, 'reason' => 'free_upgrade', 'price' => 0.00, 'requires_payment' => false ]; } else { return [ 'accessible' => false, 'reason' => 'requires_base_version', 'required_version' => $version['requires_base_version'], 'price' => $version['price'], 'requires_payment' => true ]; } } return ['accessible' => false, 'reason' => 'invalid_access_rule']; case 'paid': case 'paid_upgrade': // Check if user already owns this version if (userOwnsVersionById($userId, $versionId)) { return [ 'accessible' => true, 'reason' => 'already_owned', 'price' => 0.00, 'requires_payment' => false ]; } // Check for upgrade pricing $upgradeInfo = getUpgradePrice($userId, $versionId); return [ 'accessible' => false, 'reason' => 'requires_purchase', 'price' => $upgradeInfo['price'], 'original_price' => $version['price'], 'is_upgrade' => $upgradeInfo['is_upgrade'], 'requires_payment' => true ]; default: return ['accessible' => false, 'reason' => 'unknown_access_type']; } } function userOwnsVersion($userId, $version) { global $pdo; $stmt = $pdo->prepare(" SELECT COUNT(*) FROM user_licenses ul JOIN software_versions sv ON ul.version_id = sv.id WHERE ul.user_id = ? AND sv.version = ? AND ul.status = 'active' "); $stmt->execute([$userId, $version]); return $stmt->fetchColumn() > 0; } function userOwnsVersionById($userId, $versionId) { global $pdo; $stmt = $pdo->prepare(" SELECT COUNT(*) FROM user_licenses WHERE user_id = ? AND version_id = ? AND status = 'active' "); $stmt->execute([$userId, $versionId]); return $stmt->fetchColumn() > 0; } function getUpgradePrice($userId, $targetVersionId) { global $pdo; // Get user's owned versions $ownedVersions = getUserOwnedVersions($userId); if (empty($ownedVersions)) { // No owned versions, return full price $stmt = $pdo->prepare(" SELECT var.price FROM version_access_rules var WHERE var.version_id = ? "); $stmt->execute([$targetVersionId]); $result = $stmt->fetch(PDO::FETCH_ASSOC); return [ 'price' => $result['price'] ?? 0.00, 'is_upgrade' => false ]; } // Check for upgrade paths $bestUpgradePrice = null; $fromVersion = null; foreach ($ownedVersions as $ownedVersion) { $stmt = $pdo->prepare(" SELECT upgrade_price, is_free FROM upgrade_paths WHERE from_version_id = ? AND to_version_id = ? "); $stmt->execute([$ownedVersion['id'], $targetVersionId]); $upgrade = $stmt->fetch(PDO::FETCH_ASSOC); if ($upgrade) { if ($upgrade['is_free']) { return [ 'price' => 0.00, 'is_upgrade' => true, 'from_version' => $ownedVersion['version'] ]; } if ($bestUpgradePrice === null || $upgrade['upgrade_price'] < $bestUpgradePrice) { $bestUpgradePrice = $upgrade['upgrade_price']; $fromVersion = $ownedVersion['version']; } } } if ($bestUpgradePrice !== null) { return [ 'price' => $bestUpgradePrice, 'is_upgrade' => true, 'from_version' => $fromVersion ]; } // No upgrade path, return full price $stmt = $pdo->prepare(" SELECT var.price FROM version_access_rules var WHERE var.version_id = ? "); $stmt->execute([$targetVersionId]); $result = $stmt->fetch(PDO::FETCH_ASSOC); return [ 'price' => $result['price'] ?? 0.00, 'is_upgrade' => false ]; } function grantLicense($pdo, $userId, $versionId, $transactionId = null) { // Generate unique license key $licenseKey = generateLicenseKey($userId, $versionId); $stmt = $pdo->prepare(" INSERT INTO user_licenses (user_id, version_id, license_key, transaction_id, status) VALUES (?, ?, ?, ?, 'active') ON DUPLICATE KEY UPDATE status = 'active', license_key = ? "); return $stmt->execute([$userId, $versionId, $licenseKey, $transactionId, $licenseKey]); } function generateLicenseKey($userId, $versionId) { // Generate a unique license key $data = $userId . '-' . $versionId . '-' . time() . '-' . bin2hex(random_bytes(8)); return strtoupper(substr(hash('sha256', $data), 0, 29)); // Format: XXXXX-XXXXX-XXXXX-XXXXX-XXXXX } function generateSecureDownloadToken($pdo, $userId, $versionId) { // Generate random token $token = bin2hex(random_bytes(32)); // Store token with expiration (5 minutes) $expiresAt = date('Y-m-d H:i:s', time() + 300); $stmt = $pdo->prepare( "INSERT INTO download_tokens (token, user_id, version_id, expires_at, used) VALUES (?, ?, ?, ?, 0)" ); $stmt->execute([$token, $userId, $versionId, $expiresAt]); return $token; } function validateUserAccess($userId, $versionId) { global $pdo; // Check if version requires payment $stmt = $pdo->prepare(" SELECT var.access_type FROM version_access_rules var WHERE var.version_id = ? "); $stmt->execute([$versionId]); $accessRule = $stmt->fetch(PDO::FETCH_ASSOC); if (!$accessRule) { return false; } if ($accessRule['access_type'] === 'free_all') { return true; // Free for everyone } // Check if user has valid license $stmt = $pdo->prepare( "SELECT COUNT(*) FROM user_licenses WHERE user_id = ? AND version_id = ? AND status = 'active'" ); $stmt->execute([$userId, $versionId]); return $stmt->fetchColumn() > 0; } function logDownload($pdo, $userId, $versionId) { $stmt = $pdo->prepare(" INSERT INTO download_logs (user_id, version_id, ip_address, user_agent, downloaded_at) VALUES (?, ?, ?, ?, NOW()) "); $stmt->execute([ $userId, $versionId, $_SERVER['REMOTE_ADDR'] ?? '', $_SERVER['HTTP_USER_AGENT'] ?? '' ]); } ?>