*/ private const DEFAULT_ENDPOINT_ALIASES = array( 'https://api.affilun.st', 'https://cbl.rio.st', 'https://cbl.rio.st/api/v1', ); /** * Whether cloud features are enabled. */ private bool $enabled; /** * API endpoint. */ private string $endpoint; /** * Site identifier issued by affilun Cloud. */ private string $siteId; /** * Site API key. */ private string $apiKey; /** * Cached plan label. */ private string $plan; /** * Default article-draft tone. */ private string $articleDraftTone; /** * Default article-draft target length. */ private string $articleDraftTargetLength; /** * Default article-draft review-source policy. */ private string $articleDraftReviewSourcePolicy; /** * @param bool $enabled Whether cloud is enabled. * @param string $endpoint API endpoint. * @param string $siteId Site id. * @param string $apiKey API key. * @param string $plan Cached plan label. * @param string $articleDraftTone Article-draft tone. * @param string $articleDraftTargetLength Article-draft length. * @param string $articleDraftReviewSourcePolicy Review source policy. * @param bool $allowCustomEndpoint Explicit test/dev opt-in for non-default endpoints. * * @throws InvalidArgumentException When endpoint is not HTTP(S) or is not allowed. */ public function __construct( bool $enabled, string $endpoint, string $siteId, string $apiKey, string $plan, string $articleDraftTone = 'professional_blog', string $articleDraftTargetLength = 'medium', string $articleDraftReviewSourcePolicy = 'product_url', bool $allowCustomEndpoint = false ) { $endpoint = trim( $endpoint ); $normalizedEndpoint = rtrim( $endpoint, '/' ); if ( preg_match( '#\Ahttps?://#i', $endpoint ) !== 1 && $endpoint !== '' ) { throw new InvalidArgumentException( 'affilun Cloud endpoint must be an HTTP(S) URL.' ); } if ( $endpoint === '' || in_array( $normalizedEndpoint, self::DEFAULT_ENDPOINT_ALIASES, true ) ) { $endpoint = self::DEFAULT_ENDPOINT; $normalizedEndpoint = self::DEFAULT_ENDPOINT; } if ( $normalizedEndpoint !== self::DEFAULT_ENDPOINT && ! $allowCustomEndpoint && ! self::customEndpointsAllowed() ) { throw new InvalidArgumentException( 'Custom affilun Cloud endpoints are disabled.' ); } $this->enabled = $enabled; $this->endpoint = rtrim( $endpoint, '/' ); $this->siteId = trim( $siteId ); $this->apiKey = trim( $apiKey ); $this->plan = trim( $plan ); $this->articleDraftTone = self::normalizeArticleDraftTone( $articleDraftTone ); $this->articleDraftTargetLength = self::normalizeArticleDraftTargetLength( $articleDraftTargetLength ); $this->articleDraftReviewSourcePolicy = self::normalizeArticleDraftReviewSourcePolicy( $articleDraftReviewSourcePolicy ); } /** * Default settings. */ public static function defaults(): self { return new self( false, self::DEFAULT_ENDPOINT, '', '', '' ); } /** * Build from persisted array. * * @param array $data Persisted data. */ public static function fromArray( array $data ): self { $articleDraft = is_array( $data['article_draft'] ?? null ) ? $data['article_draft'] : array(); return new self( (bool) ( $data['enabled'] ?? false ), is_scalar( $data['endpoint'] ?? null ) ? (string) $data['endpoint'] : self::DEFAULT_ENDPOINT, is_scalar( $data['site_id'] ?? null ) ? (string) $data['site_id'] : '', is_scalar( $data['api_key'] ?? null ) ? (string) $data['api_key'] : '', is_scalar( $data['plan'] ?? null ) ? (string) $data['plan'] : '', is_scalar( $articleDraft['tone'] ?? null ) ? (string) $articleDraft['tone'] : 'professional_blog', is_scalar( $articleDraft['target_length'] ?? null ) ? (string) $articleDraft['target_length'] : 'medium', is_scalar( $articleDraft['review_source_policy'] ?? null ) ? (string) $articleDraft['review_source_policy'] : 'product_url' ); } /** * Whether custom Cloud endpoints are explicitly enabled for development. */ private static function customEndpointsAllowed(): bool { $allowed = defined( 'AFFILUN_CLOUD_ALLOW_CUSTOM_ENDPOINTS' ) && (bool) AFFILUN_CLOUD_ALLOW_CUSTOM_ENDPOINTS; if ( function_exists( 'apply_filters' ) ) { $allowed = (bool) apply_filters( 'affilun_cloud_allow_custom_endpoints', $allowed ); } return $allowed; } /** * Serialize for persistence. * * @return array */ public function toArray(): array { return array( 'enabled' => $this->enabled, 'endpoint' => $this->endpoint, 'site_id' => $this->siteId, 'api_key' => $this->apiKey, 'plan' => $this->plan, 'article_draft' => array( 'tone' => $this->articleDraftTone, 'target_length' => $this->articleDraftTargetLength, 'review_source_policy' => $this->articleDraftReviewSourcePolicy, ), ); } /** * Whether cloud features are enabled. */ public function enabled(): bool { return $this->enabled; } /** * API endpoint. */ public function endpoint(): string { return $this->endpoint; } /** * Site id. */ public function siteId(): string { return $this->siteId; } /** * API key. */ public function apiKey(): string { return $this->apiKey; } /** * Cached plan. */ public function plan(): string { return $this->plan; } /** * Default article-draft tone. */ public function articleDraftTone(): string { return $this->articleDraftTone; } /** * Default article-draft target length. */ public function articleDraftTargetLength(): string { return $this->articleDraftTargetLength; } /** * Default article-draft review-source policy. */ public function articleDraftReviewSourcePolicy(): string { return $this->articleDraftReviewSourcePolicy; } /** * Whether settings can authenticate to affilun Cloud. */ public function isConfigured(): bool { return $this->enabled && $this->siteId !== '' && $this->apiKey !== ''; } /** * Masked API key for admin display. */ public function maskedApiKey(): string { return SettingsCredentialMask::mask( $this->apiKey ); } /** * Copy with API key. * * @param string $apiKey API key. */ public function withApiKey( string $apiKey ): self { return new self( $this->enabled, $this->endpoint, $this->siteId, $apiKey, $this->plan, $this->articleDraftTone, $this->articleDraftTargetLength, $this->articleDraftReviewSourcePolicy ); } /** * Normalize article-draft tone. * * @param string $value Raw value. */ private static function normalizeArticleDraftTone( string $value ): string { $value = trim( $value ); return in_array( $value, array( 'professional_blog', 'friendly', 'review_comparison', 'concise' ), true ) ? $value : 'professional_blog'; } /** * Normalize article-draft target length. * * @param string $value Raw value. */ private static function normalizeArticleDraftTargetLength( string $value ): string { $value = trim( $value ); return in_array( $value, array( 'short', 'medium', 'long' ), true ) ? $value : 'medium'; } /** * Normalize article-draft review-source policy. * * @param string $value Raw value. */ private static function normalizeArticleDraftReviewSourcePolicy( string $value ): string { $value = trim( $value ); return in_array( $value, array( 'product_url', 'none' ), true ) ? $value : 'product_url'; } /** * Compare equality. * * @param self $other Other settings. */ public function equals( self $other ): bool { return $this->toArray() === $other->toArray(); } }