38471-vm/lib/LicenseService.php
2026-05-10 03:10:50 +00:00

819 lines
30 KiB
PHP

<?php
class LicenseService {
private static $remote_api_url = null;
private static $api_secret = null;
private static $app_slug = null;
private static $app_name = null;
private static $settings_cache = null;
private static $identity_cache = null;
private static $client_config_cache = null;
private static function loadClientConfig() {
if (self::$client_config_cache !== null) {
return self::$client_config_cache;
}
self::$client_config_cache = [];
$configPath = __DIR__ . '/../license_client_config.php';
if (!is_file($configPath)) {
return self::$client_config_cache;
}
try {
$config = include $configPath;
if (is_array($config)) {
self::$client_config_cache = $config;
}
} catch (Throwable $e) {
self::$client_config_cache = [];
}
return self::$client_config_cache;
}
private static function getClientConfigValue($key) {
$config = self::loadClientConfig();
return trim((string)($config[$key] ?? ''));
}
private static function normalizeApiBaseUrl($url) {
$url = trim((string)$url);
if ($url === '') {
return '';
}
$url = preg_replace('#/(?:index|manage|install)\.php(?:\?.*)?$#i', '', $url);
return rtrim((string)$url, '/');
}
private static function getCurrentRequestBaseParts() {
if (PHP_SAPI === 'cli') {
return ['', '', ''];
}
$host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
if ($host === '') {
return ['', '', ''];
}
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower((string)$_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https');
$scheme = $isHttps ? 'https' : 'http';
$scriptName = str_replace('\\', '/', (string)($_SERVER['SCRIPT_NAME'] ?? '/index.php'));
$baseDir = rtrim(dirname($scriptName), '/');
if ($baseDir === '.' || $baseDir === '/') {
$baseDir = '';
}
return [$scheme, $host, $baseDir];
}
private static function buildLocalApiCandidates() {
[$scheme, $host, $baseDir] = self::getCurrentRequestBaseParts();
if ($host === '') {
return [];
}
$prefixes = [];
if ($baseDir !== '') {
$prefixes[] = $baseDir;
}
$prefixes[] = '';
$suffixes = ['/central_license_manager', '/key', '/keys'];
$candidates = [];
foreach ($prefixes as $prefix) {
foreach ($suffixes as $suffix) {
$candidate = self::normalizeApiBaseUrl($scheme . '://' . $host . $prefix . $suffix);
if ($candidate !== '') {
$candidates[$candidate] = true;
}
}
}
return array_keys($candidates);
}
private static function isLocalHostUrl($url) {
$host = strtolower((string)(parse_url((string)$url, PHP_URL_HOST) ?: ''));
return in_array($host, ['localhost', '127.0.0.1', '::1'], true);
}
private static function urlLooksLikeHealthyLicenseApi($baseUrl) {
$baseUrl = self::normalizeApiBaseUrl($baseUrl);
if ($baseUrl === '') {
return false;
}
$url = rtrim($baseUrl, '/') . '/index.php?action=health';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$resp = curl_exec($ch);
$http_code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($resp === false || $http_code < 200 || $http_code >= 300) {
return false;
}
$data = json_decode($resp, true);
return is_array($data)
&& !empty($data['success'])
&& stripos((string)($data['manager'] ?? ''), 'license') !== false;
}
private static function detectLocalApiUrl() {
foreach (self::buildLocalApiCandidates() as $candidate) {
if (self::urlLooksLikeHealthyLicenseApi($candidate)) {
return $candidate;
}
}
[$scheme, $host, $baseDir] = self::getCurrentRequestBaseParts();
if ($host === '') {
return '';
}
return self::normalizeApiBaseUrl($scheme . '://' . $host . $baseDir . '/central_license_manager');
}
private static function getApiUrl() {
if (self::$remote_api_url !== null) {
return self::$remote_api_url;
}
$configOverride = self::normalizeApiBaseUrl(self::getClientConfigValue('license_api_url'));
if ($configOverride !== '') {
self::$remote_api_url = $configOverride;
return self::$remote_api_url;
}
$configured = self::normalizeApiBaseUrl(getenv('LICENSE_API_URL') ?: '');
if ($configured !== '') {
if (self::isLocalHostUrl($configured) && !self::urlLooksLikeHealthyLicenseApi($configured)) {
$detectedLocalUrl = self::detectLocalApiUrl();
self::$remote_api_url = $detectedLocalUrl !== '' ? $detectedLocalUrl : $configured;
} else {
self::$remote_api_url = $configured;
}
return self::$remote_api_url;
}
// Do not auto-switch to a bundled local license manager just because the module exists.
// Customer activation keys for this project are issued centrally from omanapp.cloud, so
// local/offline copies should keep using the central service unless an explicit project
// config or environment variable points somewhere else.
self::$remote_api_url = 'https://omanapp.cloud/central_license_manager';
return self::$remote_api_url;
}
private static function getApiSecret() {
if (self::$api_secret === null) {
$secret = trim((string)(self::getClientConfigValue('license_api_secret') ?: getenv('LICENSE_API_SECRET') ?: getenv('CLM_API_SECRET') ?: '1485-5215-2578'));
self::$api_secret = $secret !== '' ? $secret : '1485-5215-2578';
}
return self::$api_secret;
}
private static function buildActivationDebugContext() {
$identity = self::getClientIdentity();
$apiUrl = self::getApiUrl();
$slug = (string)($identity['app_slug'] ?? '');
$slugSource = (string)($identity['app_slug_source'] ?? 'unknown');
$name = (string)($identity['app_name'] ?? '');
$parts = [];
if ($apiUrl !== '') {
$parts[] = 'Activation server: ' . $apiUrl . '.';
}
if ($slug !== '') {
$parts[] = 'App slug sent: ' . $slug . ' (' . $slugSource . ').';
}
if ($name !== '') {
$parts[] = 'App name sent: ' . $name . '.';
}
return implode(' ', $parts);
}
public static function sanitizeAppSlug($value, $allowEmpty = false) {
$value = strtolower(trim((string)$value));
$value = preg_replace('/[^a-z0-9]+/', '-', $value);
$value = trim((string)$value, '-');
if ($value === '') {
return $allowEmpty ? '' : 'bilingual-accounting';
}
return substr($value, 0, 120);
}
private static function loadIdentitySettings() {
if (self::$settings_cache !== null) {
return self::$settings_cache;
}
self::$settings_cache = [
'license_app_name' => null,
'license_app_slug' => null,
];
try {
require_once __DIR__ . '/../db/config.php';
$db = db();
$tableExists = $db->query("SHOW TABLES LIKE 'settings'")->fetch();
if ($tableExists) {
$stmt = $db->prepare("SELECT `key`, `value` FROM settings WHERE `key` IN ('license_app_name', 'license_app_slug')");
$stmt->execute();
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$key = (string)($row['key'] ?? '');
$value = trim((string)($row['value'] ?? ''));
if (array_key_exists($key, self::$settings_cache) && $value !== '') {
self::$settings_cache[$key] = $value;
}
}
}
} catch (Throwable $e) {
// Ignore local settings lookup errors and fall back to env/default values.
}
return self::$settings_cache;
}
private static function resolveClientIdentity() {
if (self::$identity_cache !== null) {
return self::$identity_cache;
}
$settings = self::loadIdentitySettings();
$storedName = trim((string)($settings['license_app_name'] ?? ''));
$configName = self::getClientConfigValue('license_app_name');
$envName = trim((string)(getenv('LICENSE_APP_NAME') ?: ''));
if ($storedName !== '') {
$name = $storedName;
$nameSource = 'settings';
} elseif ($configName !== '') {
$name = $configName;
$nameSource = 'project_config';
} elseif ($envName !== '') {
$name = $envName;
$nameSource = 'environment';
} else {
$name = 'Bilingual Accounting';
$nameSource = 'default';
}
$storedSlug = trim((string)($settings['license_app_slug'] ?? ''));
$configSlug = self::getClientConfigValue('license_app_slug');
$envSlug = trim((string)(getenv('LICENSE_APP_SLUG') ?: ''));
if ($storedSlug !== '') {
$slug = self::sanitizeAppSlug($storedSlug);
$slugSource = 'settings';
} elseif ($configSlug !== '') {
$slug = self::sanitizeAppSlug($configSlug);
$slugSource = 'project_config';
} elseif ($envSlug !== '') {
$slug = self::sanitizeAppSlug($envSlug);
$slugSource = 'environment';
} else {
$slug = self::sanitizeAppSlug($name);
$slugSource = $nameSource === 'default' ? 'default' : 'derived';
}
self::$app_name = $name;
self::$app_slug = $slug;
self::$identity_cache = [
'app_name' => $name,
'app_slug' => $slug,
'app_name_source' => $nameSource,
'app_slug_source' => $slugSource,
];
return self::$identity_cache;
}
public static function getClientIdentity() {
return self::resolveClientIdentity();
}
private static function slugify($value) {
return self::sanitizeAppSlug($value);
}
private static function getAppName() {
return (string)(self::resolveClientIdentity()['app_name'] ?? 'Bilingual Accounting');
}
private static function getAppSlug() {
return (string)(self::resolveClientIdentity()['app_slug'] ?? 'bilingual-accounting');
}
private static function getProductName() {
$name = trim((string)(getenv('LICENSE_PRODUCT_NAME') ?: self::getAppName()));
return $name !== '' ? $name : self::getAppName();
}
private static function getProductVersion() {
return trim((string)(getenv('LICENSE_PRODUCT_VERSION') ?: ''));
}
private static function normalizeAllowedActivations(array $response) {
return max(1, (int)($response['allowed_activations'] ?? $response['max_activations'] ?? 1));
}
private static function normalizeMaxCounters(array $response) {
return max(1, (int)($response['max_counters'] ?? self::normalizeAllowedActivations($response)));
}
/**
* Returns the number of days remaining in the trial.
*/
public static function getTrialRemainingDays() {
require_once __DIR__ . '/../db/config.php';
self::ensureTrialSchema();
$stmt = db()->prepare("SELECT trial_started_at FROM system_license LIMIT 1");
$stmt->execute();
$res = $stmt->fetch();
if (!$res || !$res['trial_started_at']) {
return 0;
}
$started = strtotime((string)$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();
$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,
max_counters INT DEFAULT 1,
app_slug VARCHAR(120) DEFAULT NULL,
app_name VARCHAR(190) DEFAULT NULL,
license_source_url VARCHAR(255) DEFAULT NULL,
activated_at DATETIME DEFAULT NULL,
last_checked_at DATETIME DEFAULT NULL,
trial_started_at DATETIME DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
return;
}
$columnChecks = [
'trial_started_at' => "ALTER TABLE system_license ADD COLUMN trial_started_at DATETIME DEFAULT NULL",
'allowed_activations' => "ALTER TABLE system_license ADD COLUMN allowed_activations INT DEFAULT 1",
'max_counters' => "ALTER TABLE system_license ADD COLUMN max_counters INT DEFAULT 1",
'app_slug' => "ALTER TABLE system_license ADD COLUMN app_slug VARCHAR(120) DEFAULT NULL",
'app_name' => "ALTER TABLE system_license ADD COLUMN app_name VARCHAR(190) DEFAULT NULL",
'license_source_url' => "ALTER TABLE system_license ADD COLUMN license_source_url VARCHAR(255) DEFAULT NULL",
'last_checked_at' => "ALTER TABLE system_license ADD COLUMN last_checked_at DATETIME DEFAULT NULL",
];
foreach ($columnChecks as $column => $sql) {
$stmt = $db->query("SHOW COLUMNS FROM system_license LIKE '" . addslashes($column) . "'");
if (!$stmt->fetch()) {
$db->exec($sql);
}
}
$stmt = $db->query("SHOW COLUMNS FROM system_license LIKE 'status'");
$statusCol = $stmt->fetch();
if ($statusCol && strpos((string)$statusCol['Type'], "'trial'") === false) {
$db->exec("ALTER TABLE system_license MODIFY COLUMN status ENUM('pending', 'active', 'expired', 'suspended', 'trial') DEFAULT 'pending'");
}
} catch (Exception $e) {
// Keep licensing checks fail-safe; UI will show remote/local activation errors separately.
}
}
/**
* Public wrapper to initialize the trial period.
*/
public static function initTrial() {
try {
self::startTrial();
return ['success' => true];
} catch (Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* 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 ((int)$stmt->fetchColumn() === 0) {
db()->exec("INSERT INTO system_license (license_key, status, trial_started_at) VALUES ('TRIAL', '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'),
php_uname('m'),
PHP_OS,
];
return hash('sha256', implode('|', $data));
}
/**
* Returns the number of allowed activations/counters.
*/
public static function getAllowedActivations() {
require_once __DIR__ . '/../db/config.php';
self::ensureTrialSchema();
$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'];
}
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';
self::ensureTrialSchema();
$fingerprint = self::getFingerprint();
$stmt = db()->prepare("SELECT * FROM system_license WHERE status = 'active' AND fingerprint = ? ORDER BY id DESC LIMIT 1");
$stmt->execute([$fingerprint]);
$license = $stmt->fetch();
if (!$license) {
return false;
}
if (($license['fingerprint'] ?? '') !== $fingerprint) {
return false;
}
$lastCheckRaw = $license['last_checked_at'] ?? ($license['activated_at'] ?? null);
$lastCheck = $lastCheckRaw ? strtotime((string)$lastCheckRaw) : false;
if ($lastCheck === false || time() - $lastCheck > (7 * 24 * 60 * 60)) {
$res = self::callRemoteApi('/verify', [
'license_key' => (string)$license['license_key'],
'fingerprint' => (string)$license['fingerprint'],
'token' => (string)($license['activation_token'] ?? ''),
'app_slug' => (string)($license['app_slug'] ?: self::getAppSlug()),
]);
if (empty($res['success'])) {
db()->prepare("UPDATE system_license SET status = 'suspended', last_checked_at = NOW() WHERE id = ?")->execute([(int)$license['id']]);
return false;
}
$allowed = self::normalizeAllowedActivations($res);
$maxCounters = self::normalizeMaxCounters($res);
$appSlug = (string)($res['app_slug'] ?? ($license['app_slug'] ?: self::getAppSlug()));
$appName = (string)($res['app_name'] ?? ($license['app_name'] ?: self::getAppName()));
db()->prepare("UPDATE system_license SET status = 'active', allowed_activations = ?, max_counters = ?, app_slug = ?, app_name = ?, license_source_url = ?, last_checked_at = NOW() WHERE id = ?")
->execute([$allowed, $maxCounters, $appSlug, $appName, self::getApiUrl(), (int)$license['id']]);
}
return true;
}
/**
* Attempts to activate the product online.
*/
public static function activate($license_key) {
$license_key = strtoupper(trim((string)$license_key));
if ($license_key === '') {
return ['success' => false, 'error' => 'License key is required.'];
}
$fingerprint = self::getFingerprint();
$response = self::callRemoteApi('/activate', [
'license_key' => $license_key,
'fingerprint' => $fingerprint,
'domain' => $_SERVER['HTTP_HOST'] ?? 'unknown',
'product' => self::getProductName(),
'product_version' => self::getProductVersion(),
'app_slug' => self::getAppSlug(),
'app_name' => self::getAppName(),
]);
if (empty($response['success'])) {
$error = trim((string)($response['error'] ?? 'Remote activation failed.'));
$needsContext = $error === ''
|| stripos($error, 'Remote request failed') !== false
|| stripos($error, 'Invalid response from remote server') !== false
|| stripos($error, 'License does not belong to this app') !== false
|| stripos($error, 'Invalid license key') !== false;
if ($needsContext) {
$error = rtrim($error !== '' ? $error : 'Remote activation failed.', '.');
$error .= '. ' . self::buildActivationDebugContext();
}
return ['success' => false, 'error' => $error !== '' ? $error : 'Remote activation failed.'];
}
require_once __DIR__ . '/../db/config.php';
self::ensureTrialSchema();
$allowed = self::normalizeAllowedActivations($response);
$maxCounters = self::normalizeMaxCounters($response);
$appSlug = (string)($response['app_slug'] ?? self::getAppSlug());
$appName = (string)($response['app_name'] ?? self::getAppName());
$token = (string)($response['activation_token'] ?? bin2hex(random_bytes(32)));
$stmt = db()->prepare("DELETE FROM system_license WHERE fingerprint = ? OR license_key = ? OR status = 'trial'");
$stmt->execute([$fingerprint, $license_key]);
$stmt = db()->prepare("INSERT INTO system_license (license_key, fingerprint, status, activated_at, last_checked_at, activation_token, allowed_activations, max_counters, app_slug, app_name, license_source_url, trial_started_at) VALUES (?, ?, 'active', NOW(), NOW(), ?, ?, ?, ?, ?, ?, NULL)");
$stmt->execute([
$license_key,
$fingerprint,
$token,
$allowed,
$maxCounters,
$appSlug,
$appName,
self::getApiUrl(),
]);
return [
'success' => true,
'app_slug' => $appSlug,
'app_name' => $appName,
'allowed_activations' => $allowed,
'max_counters' => $maxCounters,
];
}
/**
* Fetches all licenses from the remote server.
*/
public static function listLicenses() {
return self::callRemoteApi('/list', [
'secret' => self::getApiSecret(),
'app_slug' => self::getAppSlug(),
]);
}
/**
* Updates an existing license.
*/
public static function updateLicense($id, $data) {
$params = array_merge([
'id' => $id,
'secret' => self::getApiSecret(),
'app_slug' => self::getAppSlug(),
], is_array($data) ? $data : []);
return self::callRemoteApi('/update', $params);
}
/**
* Issues a new license.
*/
public static function issueLicense($max_activations, $prefix = 'FLAT', $owner = null, $address = null) {
$payload = [
'secret' => self::getApiSecret(),
'max_activations' => max(1, (int)$max_activations),
'max_counters' => max(1, (int)$max_activations),
'prefix' => $prefix,
'owner' => $owner,
'address' => $address,
'app_slug' => self::getAppSlug(),
'app_name' => self::getAppName(),
];
return self::callRemoteApi('/issue', $payload);
}
/**
* Remote API Caller.
*/
private static function callRemoteApi($endpoint, $params) {
$action = ltrim((string)$endpoint, '/');
$baseUrl = self::getApiUrl();
if ($baseUrl === '' || strpos($baseUrl, 'your-domain.com') !== false) {
return self::simulateApi($endpoint, $params);
}
if (strpos($baseUrl, 'index.php') !== false) {
$separator = strpos($baseUrl, '?') === false ? '?' : '&';
$url = $baseUrl . $separator . 'action=' . urlencode($action);
} else {
$url = rtrim($baseUrl, '/') . '/index.php?action=' . urlencode($action);
}
$payload = json_encode($params);
if ($payload === false) {
return ['success' => false, 'error' => 'Failed to encode remote activation payload.'];
}
$resp = false;
$http_code = 0;
$content_type = '';
$curl_error = '';
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$resp = curl_exec($ch);
$http_code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$content_type = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
$curl_error = curl_error($ch);
curl_close($ch);
} else {
$headers = [
'Content-Type: application/json',
'Content-Length: ' . strlen($payload),
];
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => implode("\r\n", $headers) . "\r\n",
'content' => $payload,
'timeout' => 10,
'ignore_errors' => true,
],
]);
$resp = @file_get_contents($url, false, $context);
$streamHeaders = isset($http_response_header) && is_array($http_response_header) ? $http_response_header : [];
foreach ($streamHeaders as $headerLine) {
if (preg_match('#^HTTP/\S+\s+(\d{3})#i', (string)$headerLine, $matches)) {
$http_code = (int)$matches[1];
}
if (stripos((string)$headerLine, 'Content-Type:') === 0) {
$content_type = trim((string)substr((string)$headerLine, strlen('Content-Type:')));
}
}
if ($resp === false) {
$lastError = error_get_last();
$curl_error = trim((string)($lastError['message'] ?? 'HTTP stream transport failed.'));
}
}
if ($resp === false) {
$networkError = $curl_error !== '' ? $curl_error : 'Unknown network error.';
return ['success' => false, 'error' => 'Remote request failed: ' . $networkError . ' URL: ' . $url];
}
$data = json_decode($resp, true);
if (!is_array($data)) {
$preview = trim((string)preg_replace('/\s+/', ' ', strip_tags((string)$resp)));
if ($preview !== '' && strlen($preview) > 180) {
$preview = substr($preview, 0, 177) . '...';
}
$error = 'Invalid response from remote server.';
if ($http_code > 0) {
$error .= ' HTTP ' . $http_code . '.';
}
if ($content_type !== '') {
$error .= ' Content-Type: ' . $content_type . '.';
}
$error .= ' URL: ' . $url . '.';
if ($preview !== '') {
$error .= ' Preview: ' . $preview;
}
return ['success' => false, 'error' => $error];
}
if ($http_code < 200 || $http_code >= 300) {
return [
'success' => false,
'error' => (string)($data['error'] ?? ('Remote server returned error code ' . $http_code . '.')),
'http_code' => $http_code,
];
}
return $data;
}
/**
* Local simulation for development purposes.
*/
private static function simulateApi($endpoint, $params) {
$clean_key = strtoupper(trim((string)($params['license_key'] ?? '')));
$pattern = '/^FLAT-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}$/';
if ($endpoint === '/list') {
return ['success' => true, 'data' => []];
}
if ($endpoint === '/apps') {
return ['success' => true, 'data' => [['slug' => self::getAppSlug(), 'name' => self::getAppName()]]];
}
if ($endpoint === '/update') {
return ['success' => true, 'data' => $params];
}
if ($endpoint === '/issue') {
return [
'success' => true,
'license_key' => 'FLAT-' . strtoupper(bin2hex(random_bytes(2))) . '-' . strtoupper(bin2hex(random_bytes(2))) . '-' . strtoupper(bin2hex(random_bytes(2))),
'max_activations' => max(1, (int)($params['max_activations'] ?? 1)),
'allowed_activations' => max(1, (int)($params['max_activations'] ?? 1)),
'max_counters' => max(1, (int)($params['max_counters'] ?? $params['max_activations'] ?? 1)),
'app_slug' => self::getAppSlug(),
'app_name' => self::getAppName(),
];
}
if (!preg_match($pattern, $clean_key)) {
return [
'success' => false,
'error' => 'Invalid License Format. Expected FLAT-XXXX-XXXX-XXXX (Simulation Mode).',
];
}
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,
'max_activations' => 2,
'allowed_activations' => 2,
'max_counters' => 2,
'app_slug' => self::getAppSlug(),
'app_name' => self::getAppName(),
];
}
return [
'success' => true,
'allowed_activations' => 2,
'max_activations' => 2,
'max_counters' => 2,
'activation_token' => hash('sha256', $clean_key . (string)($params['fingerprint'] ?? '') . 'DEBUG_SALT'),
'app_slug' => self::getAppSlug(),
'app_name' => self::getAppName(),
];
}
}