isLocalhost()) { return true; } // Always valid when self-licensing (server URL points to this installation) if ($this->isSelfLicensing()) { return true; } // Check cache first $cached = get_transient(self::CACHE_KEY); if ($cached !== false) { return (bool) $cached; } // Validate against server return $this->validateLicense(); } /** * Validate license against the server * * @param bool $forceRefresh Force refresh even if cached * @return bool True if license is valid */ public function validateLicense(bool $forceRefresh = false): bool { // Always valid on localhost if ($this->isLocalhost()) { return true; } // Always valid when self-licensing (server URL points to this installation) if ($this->isSelfLicensing()) { return true; } // Check settings are configured $serverUrl = $this->getLicenseServerUrl(); $licenseKey = $this->getLicenseKey(); if (empty($serverUrl) || empty($licenseKey)) { set_transient( self::ERROR_CACHE_KEY, __('License settings not configured.', 'wc-licensed-product'), self::ERROR_CACHE_TTL ); return false; } // Check cache unless force refresh if (!$forceRefresh) { $cached = get_transient(self::CACHE_KEY); if ($cached !== false) { return (bool) $cached; } } try { $client = $this->createLicenseClient(); $domain = $this->getCurrentDomain(); // Validate the license $client->validate($licenseKey, $domain); // Valid license - cache success set_transient(self::CACHE_KEY, 1, self::CACHE_TTL); delete_transient(self::ERROR_CACHE_KEY); return true; } catch (LicenseException $e) { // License-specific error (invalid, expired, revoked, etc.) set_transient(self::CACHE_KEY, 0, self::CACHE_TTL); set_transient(self::ERROR_CACHE_KEY, $e->getMessage(), self::ERROR_CACHE_TTL); return false; } catch (\Throwable $e) { // Network/server error - use shorter cache to allow retry set_transient( self::ERROR_CACHE_KEY, __('Could not connect to license server.', 'wc-licensed-product') . ' ' . $e->getMessage(), self::ERROR_CACHE_TTL ); // Don't cache validation failure on network errors - allow retry on next page load return false; } } /** * Get the last error message */ public function getLastError(): ?string { $error = get_transient(self::ERROR_CACHE_KEY); return $error !== false ? (string) $error : null; } /** * Clear the validation cache */ public function clearCache(): void { delete_transient(self::CACHE_KEY); delete_transient(self::ERROR_CACHE_KEY); $this->isLocalhostCached = null; $this->isSelfLicensingCached = null; } /** * Check if running on localhost * * Matches localhost, 127.0.0.1, ::1, and any port number. */ public function isLocalhost(): bool { if ($this->isLocalhostCached !== null) { return $this->isLocalhostCached; } $domain = $this->getCurrentDomain(); // Remove port number if present $domainWithoutPort = preg_replace('/:[\d]+$/', '', $domain); // Check for localhost variants $localhostNames = ['localhost', '127.0.0.1', '::1']; if (in_array($domainWithoutPort, $localhostNames, true)) { $this->isLocalhostCached = true; return true; } // Check for .localhost and .local subdomains if ( str_ends_with($domainWithoutPort, '.localhost') || str_ends_with($domainWithoutPort, '.local') ) { $this->isLocalhostCached = true; return true; } $this->isLocalhostCached = false; return false; } /** * Check if self-licensing (license server URL points to this installation) * * Prevents circular dependency where plugin tries to validate against itself. * Plugins can only be validated against the original store from which they were obtained. */ public function isSelfLicensing(): bool { if ($this->isSelfLicensingCached !== null) { return $this->isSelfLicensingCached; } $serverUrl = $this->getLicenseServerUrl(); // No server URL configured - not self-licensing if (empty($serverUrl)) { $this->isSelfLicensingCached = false; return false; } // Parse both URLs to compare domains $serverParsed = parse_url($serverUrl); $siteUrl = get_site_url(); $siteParsed = parse_url($siteUrl); // Get normalized domains (lowercase, no www prefix) $serverDomain = $this->normalizeDomain($serverParsed['host'] ?? ''); $siteDomain = $this->normalizeDomain($siteParsed['host'] ?? ''); // If domains match, this is self-licensing if ($serverDomain === $siteDomain) { $this->isSelfLicensingCached = true; return true; } $this->isSelfLicensingCached = false; return false; } /** * Normalize a domain for comparison (lowercase, strip www) */ private function normalizeDomain(string $domain): string { $domain = strtolower(trim($domain)); // Strip www. prefix if (str_starts_with($domain, 'www.')) { $domain = substr($domain, 4); } return $domain; } /** * Get the current domain from the site URL */ private function getCurrentDomain(): string { $siteUrl = get_site_url(); $parsed = parse_url($siteUrl); $host = $parsed['host'] ?? 'localhost'; // Include port if non-standard if (isset($parsed['port'])) { $host .= ':' . $parsed['port']; } return strtolower($host); } /** * Get the license server URL from settings */ private function getLicenseServerUrl(): string { return (string) get_option('wc_licensed_product_plugin_license_server_url', ''); } /** * Get the license key from settings */ private function getLicenseKey(): string { return (string) get_option('wc_licensed_product_plugin_license_key', ''); } /** * Get the server secret from settings (optional) */ private function getServerSecret(): ?string { $secret = get_option('wc_licensed_product_plugin_license_server_secret', ''); return !empty($secret) ? (string) $secret : null; } /** * Create the license client instance */ private function createLicenseClient(): LicenseClientInterface { $httpClient = HttpClient::create([ 'timeout' => 10, 'verify_peer' => true, ]); $serverUrl = $this->getLicenseServerUrl(); $serverSecret = $this->getServerSecret(); // Use secure client if server secret is configured if ($serverSecret !== null) { return new SecureLicenseClient( httpClient: $httpClient, baseUrl: $serverUrl, serverSecret: $serverSecret, ); } return new LicenseClient( httpClient: $httpClient, baseUrl: $serverUrl, ); } }