get_current_domain(); // Remove port number if present $domain = preg_replace('/:[\d]+$/', '', $domain); $domain = strtolower($domain); // Check exact matches if (in_array($domain, self::LOCALHOST_HOSTS, true)) { return true; } // Check TLD patterns foreach (self::LOCALHOST_TLDS as $tld) { if (str_ends_with($domain, $tld)) { return true; } } // Check for private IP ranges (Docker, VMs, etc.) if ($this->is_private_ip($domain)) { return true; } return false; } /** * Check if domain is a private IP address * * @param string $domain The domain to check. * @return bool */ private function is_private_ip(string $domain): bool { // Check if it's a valid IP first if (!filter_var($domain, FILTER_VALIDATE_IP)) { return false; } // Check for private/reserved ranges return !filter_var( $domain, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ); } /** * Check if the current site is self-licensing * * Returns true if the license server URL and site URL are on the same domain. * This allows the license server itself to use the plugin without a license. * * @return bool */ public function is_self_licensing(): bool { $server_url = get_option('wc_tpp_license_server_url', ''); if (empty($server_url)) { return false; } $server_domain = $this->normalize_domain($server_url); $site_domain = $this->normalize_domain(get_site_url()); return $server_domain === $site_domain; } /** * Normalize a URL to its domain * * @param string $url The URL to normalize. * @return string */ private function normalize_domain(string $url): string { $parsed = wp_parse_url($url); $host = $parsed['host'] ?? ''; // Convert to lowercase $host = strtolower($host); // Remove www. prefix $host = preg_replace('/^www\./', '', $host); return $host; } /** * Get current domain from site URL * * @return string */ public function get_current_domain(): string { $site_url = get_site_url(); $parsed = wp_parse_url($site_url); $host = $parsed['host'] ?? 'localhost'; // Include port if non-standard if (isset($parsed['port'])) { $host .= ':' . $parsed['port']; } return strtolower($host); } /** * Check if the license is valid * * This is the main entry point for license validation. * It implements the bypass logic for localhost and self-licensing. * * @return bool */ public function is_license_valid(): bool { // Always valid on localhost if ($this->is_localhost()) { return true; } // Always valid for self-licensing if ($this->is_self_licensing()) { return true; } // Check cached status $cached = $this->get_cached_status(); if (false !== $cached) { return !empty($cached['valid']); } // Validate against server return $this->validate_license(); } /** * Get the license bypass reason if applicable * * @return string|null 'localhost', 'self_licensing', or null */ public function get_bypass_reason(): ?string { if ($this->is_localhost()) { return 'localhost'; } if ($this->is_self_licensing()) { return 'self_licensing'; } return null; } /** * Get cached license status * * @return array|false */ public function get_cached_status() { return get_transient(self::CACHE_KEY); } /** * Validate license against the server * * @return bool */ private function validate_license(): bool { $license_key = get_option('wc_tpp_license_key', ''); $server_url = get_option('wc_tpp_license_server_url', ''); $server_secret = get_option('wc_tpp_license_server_secret', ''); // Can't validate without credentials if (empty($license_key) || empty($server_url) || empty($server_secret)) { return false; } try { $client = $this->get_license_client($server_url, $server_secret); $domain = $this->get_current_domain(); // Remove port for validation $domain = preg_replace('/:[\d]+$/', '', $domain); $result = $client->validate($license_key, $domain); // Cache successful validation set_transient(self::CACHE_KEY, array( 'valid' => true, 'product_id' => $result->productId, 'expires_at' => $result->expiresAt?->format('Y-m-d H:i:s'), 'is_lifetime' => $result->isLifetime(), 'checked_at' => current_time('mysql'), ), self::CACHE_TTL_SUCCESS); return true; } catch (\Exception $e) { // Cache validation failure set_transient(self::CACHE_KEY, array( 'valid' => false, 'error' => $e->getMessage(), 'checked_at' => current_time('mysql'), ), self::CACHE_TTL_ERROR); return false; } } /** * Get license client instance * * @param string $server_url License server URL. * @param string $server_secret Shared secret for signature verification. * @return \Magdev\WcLicensedProductClient\LicenseClientInterface */ private function get_license_client(string $server_url, string $server_secret): \Magdev\WcLicensedProductClient\LicenseClientInterface { $httpClient = \Symfony\Component\HttpClient\HttpClient::create(); return new \Magdev\WcLicensedProductClient\SecureLicenseClient( httpClient: $httpClient, baseUrl: $server_url, serverSecret: $server_secret, ); } /** * Clear the license cache */ public function clear_cache(): void { delete_transient(self::CACHE_KEY); } /** * Force revalidation of the license * * @return bool */ public function revalidate(): bool { $this->clear_cache(); return $this->is_license_valid(); } } }