init_hooks(); } /** * Initialize WordPress hooks. * * @return void */ private function init_hooks(): void { add_action( 'wp_ajax_fedistream_validate_license', array( $this, 'ajax_validate_license' ) ); add_action( 'wp_ajax_fedistream_activate_license', array( $this, 'ajax_activate_license' ) ); add_action( 'wp_ajax_fedistream_deactivate_license', array( $this, 'ajax_deactivate_license' ) ); add_action( 'wp_ajax_fedistream_check_license_status', array( $this, 'ajax_check_status' ) ); } /** * Initialize the license client. * * @return bool True if client was initialized successfully. */ private function init_client(): bool { if ( null !== $this->client ) { return true; } $server_url = self::get_server_url(); $server_secret = self::get_server_secret(); if ( empty( $server_url ) || empty( $server_secret ) ) { return false; } try { $this->client = new SecureLicenseClient( httpClient: HttpClient::create(), baseUrl: $server_url, serverSecret: $server_secret, ); return true; } catch ( \Throwable $e ) { return false; } } /** * Validate the current license. * * @return array{success: bool, message: string, data?: array} */ public function validate(): array { if ( ! $this->init_client() ) { return array( 'success' => false, 'message' => __( 'License server configuration is incomplete.', 'wp-fedistream' ), ); } $license_key = self::get_license_key(); if ( empty( $license_key ) ) { return array( 'success' => false, 'message' => __( 'No license key provided.', 'wp-fedistream' ), ); } $domain = wp_parse_url( home_url(), PHP_URL_HOST ); try { $result = $this->client->validate( $license_key, $domain ); // Update cached status. $this->update_cached_status( 'valid', array( 'product_id' => $result->productId, 'expires_at' => $result->expiresAt?->format( 'c' ), 'version_id' => $result->versionId, ) ); return array( 'success' => true, 'message' => __( 'License validated successfully.', 'wp-fedistream' ), 'data' => array( 'status' => 'valid', 'product_id' => $result->productId, 'expires_at' => $result->expiresAt?->format( 'Y-m-d' ), 'lifetime' => $result->isLifetime(), ), ); } catch ( LicenseNotFoundException $e ) { $this->update_cached_status( 'invalid' ); return array( 'success' => false, 'message' => __( 'License key not found. Please check your license key.', 'wp-fedistream' ), ); } catch ( LicenseExpiredException $e ) { $this->update_cached_status( 'expired' ); return array( 'success' => false, 'message' => __( 'Your license has expired. Please renew to continue.', 'wp-fedistream' ), ); } catch ( LicenseRevokedException $e ) { $this->update_cached_status( 'revoked' ); return array( 'success' => false, 'message' => __( 'Your license has been revoked.', 'wp-fedistream' ), ); } catch ( LicenseInactiveException $e ) { $this->update_cached_status( 'inactive' ); return array( 'success' => false, 'message' => __( 'License is inactive. Please activate it first.', 'wp-fedistream' ), ); } catch ( DomainMismatchException $e ) { $this->update_cached_status( 'invalid' ); return array( 'success' => false, 'message' => __( 'This license is not activated for this domain.', 'wp-fedistream' ), ); } catch ( SignatureException $e ) { return array( 'success' => false, 'message' => __( 'License verification failed. Please check your server secret.', 'wp-fedistream' ), ); } catch ( RateLimitExceededException $e ) { return array( 'success' => false, 'message' => __( 'Too many requests. Please try again later.', 'wp-fedistream' ), ); } catch ( LicenseException $e ) { return array( 'success' => false, 'message' => sprintf( /* translators: %s: Error message */ __( 'License validation failed: %s', 'wp-fedistream' ), $e->getMessage() ), ); } catch ( \Throwable $e ) { return array( 'success' => false, 'message' => __( 'Unable to verify license. Please try again later.', 'wp-fedistream' ), ); } } /** * Activate the license for this domain. * * @return array{success: bool, message: string, data?: array} */ public function activate(): array { if ( ! $this->init_client() ) { return array( 'success' => false, 'message' => __( 'License server configuration is incomplete.', 'wp-fedistream' ), ); } $license_key = self::get_license_key(); if ( empty( $license_key ) ) { return array( 'success' => false, 'message' => __( 'No license key provided.', 'wp-fedistream' ), ); } $domain = wp_parse_url( home_url(), PHP_URL_HOST ); try { $result = $this->client->activate( $license_key, $domain ); if ( $result->success ) { // Validate after activation to get full license info. return $this->validate(); } return array( 'success' => false, 'message' => $result->message, ); } catch ( MaxActivationsReachedException $e ) { return array( 'success' => false, 'message' => __( 'Maximum number of activations reached. Please deactivate another site first.', 'wp-fedistream' ), ); } catch ( LicenseNotFoundException $e ) { return array( 'success' => false, 'message' => __( 'License key not found. Please check your license key.', 'wp-fedistream' ), ); } catch ( LicenseExpiredException $e ) { return array( 'success' => false, 'message' => __( 'Your license has expired. Please renew to continue.', 'wp-fedistream' ), ); } catch ( SignatureException $e ) { return array( 'success' => false, 'message' => __( 'License verification failed. Please check your server secret.', 'wp-fedistream' ), ); } catch ( LicenseException $e ) { return array( 'success' => false, 'message' => sprintf( /* translators: %s: Error message */ __( 'License activation failed: %s', 'wp-fedistream' ), $e->getMessage() ), ); } catch ( \Throwable $e ) { return array( 'success' => false, 'message' => __( 'Unable to activate license. Please try again later.', 'wp-fedistream' ), ); } } /** * Get the current license status. * * @param bool $force_refresh Force a fresh check from the server. * @return array{success: bool, message: string, data?: array} */ public function get_status( bool $force_refresh = false ): array { // Check cached status first. if ( ! $force_refresh ) { $cached = $this->get_cached_validation(); if ( null !== $cached ) { return $cached; } } if ( ! $this->init_client() ) { return array( 'success' => false, 'message' => __( 'License server configuration is incomplete.', 'wp-fedistream' ), 'data' => array( 'status' => 'unconfigured', ), ); } $license_key = self::get_license_key(); if ( empty( $license_key ) ) { return array( 'success' => false, 'message' => __( 'No license key configured.', 'wp-fedistream' ), 'data' => array( 'status' => 'unchecked', ), ); } try { $result = $this->client->status( $license_key ); $status_map = array( LicenseState::Active->value => 'valid', LicenseState::Inactive->value => 'inactive', LicenseState::Expired->value => 'expired', LicenseState::Revoked->value => 'revoked', ); $status = $status_map[ $result->status->value ] ?? 'invalid'; $data = array( 'status' => $status, 'valid' => $result->valid, 'domain' => $result->domain, 'expires_at' => $result->expiresAt?->format( 'Y-m-d' ), 'lifetime' => $result->isLifetime(), 'activations_count' => $result->activationsCount, 'max_activations' => $result->maxActivations, ); // Cache the result. $this->cache_validation( array( 'success' => $result->valid, 'message' => $result->valid ? __( 'License is active.', 'wp-fedistream' ) : __( 'License is not active.', 'wp-fedistream' ), 'data' => $data, ) ); $this->update_cached_status( $status, $data ); return array( 'success' => $result->valid, 'message' => $result->valid ? __( 'License is active.', 'wp-fedistream' ) : __( 'License is not active.', 'wp-fedistream' ), 'data' => $data, ); } catch ( LicenseNotFoundException $e ) { $this->update_cached_status( 'invalid' ); return array( 'success' => false, 'message' => __( 'License key not found.', 'wp-fedistream' ), 'data' => array( 'status' => 'invalid', ), ); } catch ( SignatureException $e ) { return array( 'success' => false, 'message' => __( 'License verification failed. Please check your server secret.', 'wp-fedistream' ), 'data' => array( 'status' => 'error', ), ); } catch ( \Throwable $e ) { return array( 'success' => false, 'message' => __( 'Unable to check license status.', 'wp-fedistream' ), 'data' => array( 'status' => 'error', ), ); } } /** * Deactivate the license (clear local data). * * @return bool */ public function deactivate(): bool { self::clear_license_data(); return true; } /** * Check if the license is currently valid. * * Uses cached status for performance. * * @return bool */ public static function is_license_valid(): bool { $status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' ); return 'valid' === $status; } /** * Get the license key. * * @return string */ public static function get_license_key(): string { return get_option( self::OPTION_LICENSE_KEY, '' ); } /** * Get the license server URL. * * @return string */ public static function get_server_url(): string { return get_option( self::OPTION_SERVER_URL, '' ); } /** * Get the server secret. * * @return string */ public static function get_server_secret(): string { return get_option( self::OPTION_SERVER_SECRET, '' ); } /** * Get cached license status. * * @return string */ public static function get_cached_status(): string { return get_option( self::OPTION_LICENSE_STATUS, 'unchecked' ); } /** * Get cached license data. * * @return array */ public static function get_cached_data(): array { return get_option( self::OPTION_LICENSE_DATA, array() ); } /** * Get last check timestamp. * * @return int */ public static function get_last_check(): int { return (int) get_option( self::OPTION_LAST_CHECK, 0 ); } /** * Save license settings. * * @param array $data Settings data. * @return bool */ public static function save_settings( array $data ): bool { if ( isset( $data['license_key'] ) ) { update_option( self::OPTION_LICENSE_KEY, sanitize_text_field( $data['license_key'] ) ); } if ( isset( $data['server_url'] ) ) { update_option( self::OPTION_SERVER_URL, esc_url_raw( $data['server_url'] ) ); } if ( isset( $data['server_secret'] ) ) { // Only update if a new secret is provided. $secret = sanitize_text_field( $data['server_secret'] ); if ( ! empty( $secret ) ) { update_option( self::OPTION_SERVER_SECRET, $secret ); } } // Reset status when settings change. update_option( self::OPTION_LICENSE_STATUS, 'unchecked' ); delete_transient( self::TRANSIENT_LICENSE_CHECK ); return true; } /** * Clear all license data. * * @return void */ public static function clear_license_data(): void { update_option( self::OPTION_LICENSE_STATUS, 'unchecked' ); update_option( self::OPTION_LICENSE_DATA, array() ); update_option( self::OPTION_LAST_CHECK, 0 ); delete_transient( self::TRANSIENT_LICENSE_CHECK ); } /** * Update cached license status. * * @param string $status Status value. * @param array $data Additional data. * @return void */ private function update_cached_status( string $status, array $data = array() ): void { update_option( self::OPTION_LICENSE_STATUS, $status ); update_option( self::OPTION_LICENSE_DATA, $data ); update_option( self::OPTION_LAST_CHECK, time() ); } /** * Cache validation result. * * @param array $result Validation result. * @return void */ private function cache_validation( array $result ): void { set_transient( self::TRANSIENT_LICENSE_CHECK, $result, self::CACHE_TTL ); } /** * Get cached validation result. * * @return array|null */ private function get_cached_validation(): ?array { $cached = get_transient( self::TRANSIENT_LICENSE_CHECK ); return false === $cached ? null : $cached; } /** * AJAX handler: Validate license. * * @return void */ public function ajax_validate_license(): void { check_ajax_referer( 'fedistream_license_action', 'nonce' ); if ( ! current_user_can( 'manage_fedistream_settings' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'wp-fedistream' ), ) ); } $result = $this->validate(); if ( $result['success'] ) { wp_send_json_success( $result ); } else { wp_send_json_error( $result ); } } /** * AJAX handler: Activate license. * * @return void */ public function ajax_activate_license(): void { check_ajax_referer( 'fedistream_license_action', 'nonce' ); if ( ! current_user_can( 'manage_fedistream_settings' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'wp-fedistream' ), ) ); } $result = $this->activate(); if ( $result['success'] ) { wp_send_json_success( $result ); } else { wp_send_json_error( $result ); } } /** * AJAX handler: Deactivate license. * * @return void */ public function ajax_deactivate_license(): void { check_ajax_referer( 'fedistream_license_action', 'nonce' ); if ( ! current_user_can( 'manage_fedistream_settings' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'wp-fedistream' ), ) ); } $this->deactivate(); wp_send_json_success( array( 'success' => true, 'message' => __( 'License deactivated.', 'wp-fedistream' ), ) ); } /** * AJAX handler: Check license status. * * @return void */ public function ajax_check_status(): void { check_ajax_referer( 'fedistream_license_action', 'nonce' ); if ( ! current_user_can( 'manage_fedistream_settings' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'wp-fedistream' ), ) ); } $force_refresh = isset( $_POST['force'] ) && 'true' === $_POST['force']; $result = $this->get_status( $force_refresh ); if ( $result['success'] ) { wp_send_json_success( $result ); } else { wp_send_json_error( $result ); } } }