diff --git a/lib/LicenseService.php b/lib/LicenseService.php index 41b1604..c642186 100644 --- a/lib/LicenseService.php +++ b/lib/LicenseService.php @@ -164,12 +164,10 @@ class LicenseService { return self::$remote_api_url; } - if (file_exists(__DIR__ . '/../central_license_manager/index.php')) { - $detectedLocalUrl = self::detectLocalApiUrl(); - self::$remote_api_url = $detectedLocalUrl !== '' ? $detectedLocalUrl : 'http://127.0.0.1/central_license_manager'; - 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; } @@ -183,6 +181,27 @@ class LicenseService { 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); @@ -528,7 +547,19 @@ class LicenseService { ]); if (empty($response['success'])) { - return ['success' => false, 'error' => $response['error'] ?? 'Remote activation failed.']; + $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'; @@ -623,20 +654,66 @@ class LicenseService { $url = rtrim($baseUrl, '/') . '/index.php?action=' . urlencode($action); } - $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 = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); - $content_type = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE); - $curl_error = curl_error($ch); - curl_close($ch); + $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) { - return ['success' => false, 'error' => 'Remote request failed: ' . $curl_error]; + $networkError = $curl_error !== '' ? $curl_error : 'Unknown network error.'; + return ['success' => false, 'error' => 'Remote request failed: ' . $networkError . ' URL: ' . $url]; } $data = json_decode($resp, true);