prepare("SELECT trial_started_at FROM system_license LIMIT 1"); $stmt->execute(); $res = $stmt->fetch(); if (!$res || !$res['trial_started_at']) { self::startTrial(); return 15; } $started = strtotime($res['trial_started_at']); $days_elapsed = floor((time() - $started) / (24 * 60 * 60)); return (int) max(0, 15 - $days_elapsed); } /** * Ensures the database schema for the trial period exists. */ private static function ensureTrialSchema() { require_once __DIR__ . '/../db/config.php'; try { $db = db(); // Check if table exists $tableExists = $db->query("SHOW TABLES LIKE 'system_license'")->fetch(); if (!$tableExists) { $db->exec("CREATE TABLE system_license ( id INT AUTO_INCREMENT PRIMARY KEY, license_key VARCHAR(255) DEFAULT '', activation_token TEXT DEFAULT NULL, fingerprint VARCHAR(255) DEFAULT NULL, status ENUM('pending', 'active', 'expired', 'suspended', 'trial') DEFAULT 'pending', allowed_activations INT DEFAULT 1, activated_at DATETIME DEFAULT NULL, last_checked_at DATETIME DEFAULT NULL, trial_started_at DATETIME DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )"); } else { // Check if trial_started_at column exists $stmt = $db->query("SHOW COLUMNS FROM system_license LIKE 'trial_started_at'"); if (!$stmt->fetch()) { $db->exec("ALTER TABLE system_license ADD COLUMN trial_started_at DATETIME DEFAULT NULL"); } // Check if allowed_activations column exists $stmt = $db->query("SHOW COLUMNS FROM system_license LIKE 'allowed_activations'"); if (!$stmt->fetch()) { $db->exec("ALTER TABLE system_license ADD COLUMN allowed_activations INT DEFAULT 1"); } // Ensure 'trial' status exists in ENUM $stmt = $db->query("SHOW COLUMNS FROM system_license LIKE 'status'"); $statusCol = $stmt->fetch(); if ($statusCol && strpos($statusCol['Type'], "'trial'") === false) { $db->exec("ALTER TABLE system_license MODIFY COLUMN status ENUM('pending', 'active', 'expired', 'suspended', 'trial') DEFAULT 'pending'"); } } } catch (Exception $e) { // Log or ignore } } /** * Initializes the trial period. */ private static function startTrial() { require_once __DIR__ . '/../db/config.php'; self::ensureTrialSchema(); $stmt = db()->prepare("SELECT COUNT(*) FROM system_license"); $stmt->execute(); if ($stmt->fetchColumn() == 0) { db()->exec("INSERT INTO system_license (status, trial_started_at) VALUES ('trial', NOW())"); } else { db()->exec("UPDATE system_license SET trial_started_at = NOW() WHERE trial_started_at IS NULL"); } } /** * Generates a unique fingerprint for this server environment. */ public static function getFingerprint() { $data = [ php_uname('n'), // Nodename (hostname) php_uname('m'), // Machine type PHP_OS ]; return hash('sha256', implode('|', $data)); } /** * Returns the number of allowed activations/counters. */ public static function getAllowedActivations() { require_once __DIR__ . '/../db/config.php'; $stmt = db()->prepare("SELECT allowed_activations FROM system_license WHERE status = 'active' ORDER BY id DESC LIMIT 1"); $stmt->execute(); $res = $stmt->fetch(); if ($res) { return (int)$res['allowed_activations']; } // If in trial, allow maybe 1 or 2? Let's say 1 for now or based on user's preference. // But the user said "same as number of activations". return 1; } /** * Checks if the system is currently activated or within trial period. */ public static function canAccess() { if (self::isActivated()) return true; $daysLeft = self::getTrialRemainingDays(); return $daysLeft > 0; } /** * Checks if the system is currently activated with a valid license key. */ public static function isActivated() { require_once __DIR__ . '/../db/config.php'; $fingerprint = self::getFingerprint(); $stmt = db()->prepare("SELECT * FROM system_license WHERE status = 'active' AND fingerprint = ? LIMIT 1"); $stmt->execute([$fingerprint]); $license = $stmt->fetch(); if (!$license) return false; // 1. Verify fingerprint matches (Physical Protection) if ($license['fingerprint'] !== $fingerprint) { return false; } // 2. Periodic Remote Validation (e.g., every 7 days) // This ensures if you disable a key on your server, the client will stop working. $last_check = strtotime($license['activated_at']); // In real use, store 'last_verified_at' if (time() - $last_check > (7 * 24 * 60 * 60)) { // It's been more than 7 days, let's re-verify $res = self::callRemoteApi('/verify', [ 'license_key' => $license['license_key'], 'fingerprint' => $license['fingerprint'], 'token' => $license['activation_token'] ]); if (!$res['success']) { db()->prepare("UPDATE system_license SET status = 'suspended' WHERE fingerprint = ?")->execute([$fingerprint]); return false; } // Update last verified timestamp db()->prepare("UPDATE system_license SET activated_at = NOW() WHERE fingerprint = ?")->execute([$fingerprint]); } return true; } /** * Attempts to activate the product online. */ public static function activate($license_key) { $license_key = trim($license_key); $fingerprint = self::getFingerprint(); // Call remote API for real validation $response = self::callRemoteApi('/activate', [ 'license_key' => $license_key, 'fingerprint' => $fingerprint, 'domain' => $_SERVER['HTTP_HOST'] ?? 'unknown', 'product' => 'Flatlogic Admin Panel' ]); if (!$response['success']) { return ['success' => false, 'error' => $response['error'] ?? 'Remote activation failed.']; } require_once __DIR__ . '/../db/config.php'; // Clear previous entries for THIS fingerprint to avoid duplicates $stmt = db()->prepare("DELETE FROM system_license WHERE fingerprint = ?"); $stmt->execute([$fingerprint]); // Check if we reached the limit of activations for this key (in a shared DB) $allowed = (int) ($response['allowed_activations'] ?? 1); $stmt = db()->prepare("SELECT COUNT(DISTINCT fingerprint) FROM system_license WHERE license_key = ? AND status = 'active'"); $stmt->execute([$license_key]); $current_activations = (int) $stmt->fetchColumn(); if ($current_activations >= $allowed) { return [ 'success' => false, 'error' => "Activation Limit Reached: This license only allows $allowed machine(s). Please deactivate another machine first." ]; } $stmt = db()->prepare("INSERT INTO system_license (license_key, fingerprint, status, activated_at, activation_token, allowed_activations) VALUES (?, ?, 'active', NOW(), ?, ?)"); $token = $response['activation_token'] ?? bin2hex(random_bytes(32)); $stmt->execute([$license_key, $fingerprint, $token, $allowed]); return ['success' => true]; } /** * Fetches all licenses from the remote server. */ public static function listLicenses() { return self::callRemoteApi('/list', []); } /** * Updates an existing license. */ public static function updateLicense($id, $data) { $params = array_merge(['id' => $id, 'secret' => '1485-5215-2578'], $data); return self::callRemoteApi('/update', $params); } /** * Issues a new license. */ public static function issueLicense($max_activations, $prefix = 'FLAT', $owner = null, $address = null) { return self::callRemoteApi('/issue', [ 'secret' => '1485-5215-2578', 'max_activations' => $max_activations, 'prefix' => $prefix, 'owner' => $owner, 'address' => $address ]); } /** * Remote API Caller */ private static function callRemoteApi($endpoint, $params) { $action = ltrim($endpoint, '/'); $url = rtrim(self::getApiUrl(), '/') . '/index.php?action=' . $action; // Check if we are in local development / simulation mode if (strpos($url, 'your-domain.com') !== false) { return self::simulateApi($endpoint, $params); } $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $resp = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code !== 200) { return ['success' => false, 'error' => "Remote server returned error code $http_code."]; } $data = json_decode($resp, true); if (!$data) { return ['success' => false, 'error' => "Invalid response from remote server."]; } return $data; } /** * Local Simulation for development purposes */ private static function simulateApi($endpoint, $params) { $clean_key = strtoupper(trim($params['license_key'] ?? '')); // Strict format check: FLAT-XXXX-XXXX-XXXX (where X is hex) $pattern = '/^FLAT-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}$/'; if (!preg_match($pattern, $clean_key)) { return [ 'success' => false, 'error' => 'Invalid License Format. Expected FLAT-XXXX-XXXX-XXXX (Simulation Mode).' ]; } // In a real server, you would check if this key is already used by a DIFFERENT fingerprint. // To simulate a "Max Activations Reached" error, you can use a specific key: if ($clean_key === 'FLAT-0000-0000-0000') { return [ 'success' => false, 'error' => 'Activation Failed: This license key is already active on another machine.' ]; } if ($endpoint === '/verify') return ['success' => true]; return [ 'success' => true, 'allowed_activations' => 2, // Simulate a license that allows 2 machines/counters 'activation_token' => hash('sha256', $params['license_key'] . $params['fingerprint'] . 'DEBUG_SALT') ]; } }