licenseManager = $licenseManager; $this->registerHooks(); } /** * Register WordPress hooks */ private function registerHooks(): void { add_action('rest_api_init', [$this, 'registerRoutes']); } /** * Check rate limit for current IP * * @return WP_REST_Response|null Returns error response if rate limited, null if OK */ private function checkRateLimit(): ?WP_REST_Response { $ip = $this->getClientIp(); $transientKey = 'wclp_rate_' . md5($ip); $data = get_transient($transientKey); if ($data === false) { // First request, start counting set_transient($transientKey, ['count' => 1, 'start' => time()], self::RATE_LIMIT_WINDOW); return null; } $count = (int) ($data['count'] ?? 0); $start = (int) ($data['start'] ?? time()); // Check if window has expired if (time() - $start >= self::RATE_LIMIT_WINDOW) { // Reset counter set_transient($transientKey, ['count' => 1, 'start' => time()], self::RATE_LIMIT_WINDOW); return null; } // Check if limit exceeded if ($count >= self::RATE_LIMIT_REQUESTS) { $retryAfter = self::RATE_LIMIT_WINDOW - (time() - $start); $response = new WP_REST_Response([ 'success' => false, 'error' => 'rate_limit_exceeded', 'message' => __('Too many requests. Please try again later.', 'wc-licensed-product'), 'retry_after' => $retryAfter, ], 429); $response->header('Retry-After', (string) $retryAfter); return $response; } // Increment counter set_transient($transientKey, ['count' => $count + 1, 'start' => $start], self::RATE_LIMIT_WINDOW); return null; } /** * Get client IP address */ private function getClientIp(): string { $headers = [ 'HTTP_CF_CONNECTING_IP', // Cloudflare 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR', ]; foreach ($headers as $header) { if (!empty($_SERVER[$header])) { $ips = explode(',', $_SERVER[$header]); $ip = trim($ips[0]); if (filter_var($ip, FILTER_VALIDATE_IP)) { return $ip; } } } return '0.0.0.0'; } /** * Register REST API routes */ public function registerRoutes(): void { // Validate license endpoint (public) register_rest_route(self::NAMESPACE, '/validate', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'validateLicense'], 'permission_callback' => '__return_true', 'args' => [ 'license_key' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function ($value): bool { return !empty($value) && strlen($value) <= 64; }, ], 'domain' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function ($value): bool { return !empty($value) && strlen($value) <= 255; }, ], ], ]); // Check license status endpoint (public) register_rest_route(self::NAMESPACE, '/status', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'checkStatus'], 'permission_callback' => '__return_true', 'args' => [ 'license_key' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], ], ]); // Activate license on domain endpoint (public) register_rest_route(self::NAMESPACE, '/activate', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'activateLicense'], 'permission_callback' => '__return_true', 'args' => [ 'license_key' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], 'domain' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], ], ]); } /** * Validate license endpoint */ public function validateLicense(WP_REST_Request $request): WP_REST_Response { $rateLimitResponse = $this->checkRateLimit(); if ($rateLimitResponse !== null) { return $rateLimitResponse; } $licenseKey = $request->get_param('license_key'); $domain = $request->get_param('domain'); $result = $this->licenseManager->validateLicense($licenseKey, $domain); $statusCode = $result['valid'] ? 200 : 403; return new WP_REST_Response($result, $statusCode); } /** * Check license status endpoint */ public function checkStatus(WP_REST_Request $request): WP_REST_Response { $rateLimitResponse = $this->checkRateLimit(); if ($rateLimitResponse !== null) { return $rateLimitResponse; } $licenseKey = $request->get_param('license_key'); $license = $this->licenseManager->getLicenseByKey($licenseKey); if (!$license) { return new WP_REST_Response([ 'valid' => false, 'error' => 'license_not_found', 'message' => __('License key not found.', 'wc-licensed-product'), ], 404); } return new WP_REST_Response([ 'valid' => $license->isValid(), 'status' => $license->getStatus(), 'domain' => $license->getDomain(), 'expires_at' => $license->getExpiresAt()?->format('Y-m-d'), 'activations_count' => $license->getActivationsCount(), 'max_activations' => $license->getMaxActivations(), ]); } /** * Activate license on domain endpoint */ public function activateLicense(WP_REST_Request $request): WP_REST_Response { $rateLimitResponse = $this->checkRateLimit(); if ($rateLimitResponse !== null) { return $rateLimitResponse; } $licenseKey = $request->get_param('license_key'); $domain = $request->get_param('domain'); $license = $this->licenseManager->getLicenseByKey($licenseKey); if (!$license) { return new WP_REST_Response([ 'success' => false, 'error' => 'license_not_found', 'message' => __('License key not found.', 'wc-licensed-product'), ], 404); } if (!$license->isValid()) { return new WP_REST_Response([ 'success' => false, 'error' => 'license_invalid', 'message' => __('This license is not valid.', 'wc-licensed-product'), ], 403); } $normalizedDomain = $this->licenseManager->normalizeDomain($domain); // Check if already activated on this domain if ($license->getDomain() === $normalizedDomain) { return new WP_REST_Response([ 'success' => true, 'message' => __('License is already activated for this domain.', 'wc-licensed-product'), ]); } // Check if can activate on another domain if (!$license->canActivate()) { return new WP_REST_Response([ 'success' => false, 'error' => 'max_activations_reached', 'message' => __('Maximum number of activations reached.', 'wc-licensed-product'), ], 403); } // Update domain (in this simple implementation, we replace the domain) $success = $this->licenseManager->updateLicenseDomain($license->getId(), $domain); if (!$success) { return new WP_REST_Response([ 'success' => false, 'error' => 'activation_failed', 'message' => __('Failed to activate license.', 'wc-licensed-product'), ], 500); } return new WP_REST_Response([ 'success' => true, 'message' => __('License activated successfully.', 'wc-licensed-product'), ]); } }