decrypt($encrypted); } /** * Set API secret (encrypts before storing) * * @param string $secret API secret * @return bool */ public function set_api_secret($secret) { if (empty($secret)) { return update_option(self::PREFIX . 'api_secret', ''); } return update_option(self::PREFIX . 'api_secret', $this->encrypt($secret)); } /** * Generate new consumer credentials * * Generates a new consumer key and consumer secret for REST API access. * * @return array Array with 'consumer_key' and 'consumer_secret' */ public function generate_consumer_credentials() { $consumer_key = 'ck_' . wp_generate_password(32, false, false); $consumer_secret = 'cs_' . wp_generate_password(32, false, false); $this->set_consumer_key($consumer_key); $this->set_consumer_secret($consumer_secret); return array( 'consumer_key' => $consumer_key, 'consumer_secret' => $consumer_secret, ); } /** * Get consumer key * * @return string */ public function get_consumer_key() { return get_option(self::PREFIX . 'consumer_key', ''); } /** * Set consumer key * * @param string $key Consumer key * @return bool */ public function set_consumer_key($key) { return update_option(self::PREFIX . 'consumer_key', sanitize_text_field($key)); } /** * Get consumer secret (encrypted in WordPress options) * * @return string */ public function get_consumer_secret() { $encrypted = get_option(self::PREFIX . 'consumer_secret', ''); if (empty($encrypted)) { return ''; } return $this->decrypt($encrypted); } /** * Set consumer secret (encrypts before storing) * * @param string $secret Consumer secret * @return bool */ public function set_consumer_secret($secret) { if (empty($secret)) { return update_option(self::PREFIX . 'consumer_secret', ''); } return update_option(self::PREFIX . 'consumer_secret', $this->encrypt($secret)); } /** * Check if consumer credentials exist * * @return bool */ public function has_consumer_credentials() { return !empty($this->get_consumer_key()) && !empty($this->get_consumer_secret()); } /** * Regenerate consumer credentials * * Generates new consumer key and secret, replacing the existing ones. * * @return array Array with 'consumer_key' and 'consumer_secret' */ public function regenerate_consumer_credentials() { return $this->generate_consumer_credentials(); } /** * Clear consumer credentials */ public function clear_consumer_credentials() { $this->set_consumer_key(''); $this->set_consumer_secret(''); } /** * Get connection status * * @return string connected|disconnected */ public function get_connection_status() { return get_option(self::PREFIX . 'connection_status', 'disconnected'); } /** * Set connection status * * @param string $status Status * @return bool */ public function set_connection_status($status) { return update_option(self::PREFIX . 'connection_status', sanitize_text_field($status)); } /** * Get organization name * * @return string */ public function get_organization_name() { return get_option(self::PREFIX . 'organization_name', ''); } /** * Set organization name * * @param string $name Organization name * @return bool */ public function set_organization_name($name) { return update_option(self::PREFIX . 'organization_name', sanitize_text_field($name)); } /** * Get subscription tier * * @return string */ public function get_subscription_tier() { return get_option(self::PREFIX . 'subscription_tier', ''); } /** * Set subscription tier * * @param string $tier Subscription tier * @return bool */ public function set_subscription_tier($tier) { return update_option(self::PREFIX . 'subscription_tier', sanitize_text_field($tier)); } /** * Get publishing defaults * * @return array */ public function get_publishing_defaults() { $defaults = get_option(self::PREFIX . 'publishing_defaults', '{}'); $decoded = json_decode($defaults, true); if (!is_array($decoded)) { return array( 'post_status' => 'draft', 'default_category_id' => 0, 'default_author_id' => 0 ); } // Migrate legacy keys if (isset($decoded['category']) && !isset($decoded['default_category_id'])) { $decoded['default_category_id'] = $decoded['category']; unset($decoded['category']); } if (isset($decoded['author']) && !isset($decoded['default_author_id'])) { $decoded['default_author_id'] = $decoded['author']; unset($decoded['author']); } return $decoded; } /** * Set publishing defaults * * @param array $defaults Publishing defaults * @return bool */ public function set_publishing_defaults($defaults) { $sanitized = array( 'post_status' => isset($defaults['post_status']) ? sanitize_text_field($defaults['post_status']) : 'draft', 'default_category_id' => isset($defaults['default_category_id']) ? absint($defaults['default_category_id']) : 0, 'default_author_id' => isset($defaults['default_author_id']) ? absint($defaults['default_author_id']) : 0 ); return update_option(self::PREFIX . 'publishing_defaults', wp_json_encode($sanitized)); } /** * Check if WooCommerce integration is enabled * * @return bool */ public function is_woocommerce_enabled() { return (bool) get_option(self::PREFIX . 'woocommerce_enabled', false); } /** * Set WooCommerce enabled status * * @param bool $enabled Enabled status * @return bool */ public function set_woocommerce_enabled($enabled) { return update_option(self::PREFIX . 'woocommerce_enabled', (bool) $enabled); } /** * Get enabled WooCommerce categories * * @return array */ public function get_woocommerce_categories() { return get_option(self::PREFIX . 'woocommerce_categories', array()); } /** * Set enabled WooCommerce categories * * @param array $categories Category IDs * @return bool */ public function set_woocommerce_categories($categories) { $sanitized = array_map('absint', (array) $categories); return update_option(self::PREFIX . 'woocommerce_categories', $sanitized); } /** * Check if setup is complete * * @return bool */ public function is_setup_complete() { return (bool) get_option(self::PREFIX . 'setup_complete', false); } /** * Set setup complete status * * @param bool $complete Complete status * @return bool */ public function set_setup_complete($complete) { return update_option(self::PREFIX . 'setup_complete', (bool) $complete); } /** * Check if connected to AICP * * @return bool */ public function is_connected() { return $this->get_connection_status() === 'connected' && !empty($this->get_api_key()) && !empty($this->get_api_secret()); } /** * Check if linked to AI Content Plan (via consumer credentials + linking code) * * @return bool */ public function is_linked_to_blogai() { return $this->is_connected() && $this->has_consumer_credentials(); } /** * Get selected capabilities (output/input channels) * * @return array */ public function get_selected_capabilities() { return get_option(self::PREFIX . 'chosen_capabilities', array('output' => false, 'input' => false)); } /** * Get API base URL * * @return string */ public function get_api_url() { return get_option(self::PREFIX . 'api_url', ''); } /** * Set API base URL * * @param string $url API URL * @return bool */ public function set_api_url($url) { return update_option(self::PREFIX . 'api_url', esc_url_raw($url)); } /** * Get input channel settings * * @return array */ public function get_input_channel_settings() { $defaults = get_option(self::PREFIX . 'input_channel_settings', '{}'); $decoded = json_decode($defaults, true); return is_array($decoded) ? $decoded : array( 'share_posts' => false, 'post_category_ids' => array(), 'share_products' => false, 'product_category_ids' => array() ); } /** * Set input channel settings * * @param array $settings Input channel settings * @return bool */ public function set_input_channel_settings($settings) { return update_option(self::PREFIX . 'input_channel_settings', wp_json_encode($settings)); } /** * Check if using legacy ak_/sk_ credentials * * @return bool */ public function is_legacy_auth() { $api_key = $this->get_api_key(); return !empty($api_key) && strpos($api_key, 'ak_') === 0; } /** * Reconfigure capabilities (output/input channels) * * Updates the selected capabilities and determines what wizard steps * need to be shown for newly enabled or reconfigured capabilities. * * @param bool $enable_output Enable output capability * @param bool $enable_input Enable input capability * @param bool $force_reconfigure Force reconfiguration even if already enabled * @return array Array with changes, needs_configuration, and next_step */ public function reconfigure_capabilities($enable_output, $enable_input, $force_reconfigure = true) { // Get current capabilities $current_capabilities = get_option(self::PREFIX . 'chosen_capabilities', array()); // Ensure we have an array with both keys if (!is_array($current_capabilities)) { $current_capabilities = array('output' => false, 'input' => false); } if (!isset($current_capabilities['output'])) { $current_capabilities['output'] = false; } if (!isset($current_capabilities['input'])) { $current_capabilities['input'] = false; } // Track changes and what needs configuration $changes = array(); $needs_configuration = array(); $next_step = -1; // Check for output changes or force reconfiguration if ($enable_output && !$current_capabilities['output']) { $changes[] = 'output_enabled'; $needs_configuration[] = 'output'; delete_option(self::PREFIX . 'output_channel_configured'); // Output configuration is step 3 $next_step = 3; } elseif ($enable_output && $current_capabilities['output'] && $force_reconfigure) { // Capability staying enabled but user wants to reconfigure $changes[] = 'output_reconfigured'; $needs_configuration[] = 'output'; delete_option(self::PREFIX . 'output_channel_configured'); // Output configuration is step 3 $next_step = 3; } elseif (!$enable_output && $current_capabilities['output']) { $changes[] = 'output_disabled'; } // Check for input changes or force reconfiguration if ($enable_input && !$current_capabilities['input']) { $changes[] = 'input_enabled'; $needs_configuration[] = 'input'; delete_option(self::PREFIX . 'input_channel_configured'); // Input configuration is step 4 // Only set as next step if output wasn't just enabled if ($next_step === -1) { $next_step = 4; } } elseif ($enable_input && $current_capabilities['input'] && $force_reconfigure) { // Capability staying enabled but user wants to reconfigure $changes[] = 'input_reconfigured'; $needs_configuration[] = 'input'; delete_option(self::PREFIX . 'input_channel_configured'); // Input configuration is step 4 // Only set as next step if output wasn't just enabled/reconfigured if ($next_step === -1) { $next_step = 4; } } elseif (!$enable_input && $current_capabilities['input']) { $changes[] = 'input_disabled'; } // Update capabilities $new_capabilities = array( 'output' => $enable_output, 'input' => $enable_input ); update_option(self::PREFIX . 'chosen_capabilities', $new_capabilities); return array( 'changes' => $changes, 'needs_configuration' => $needs_configuration, 'next_step' => $next_step ); } /** * Get singleton instance * * @return AICP_Settings */ public static function get_instance() { static $instance = null; if ($instance === null) { $instance = new self(); } return $instance; } /** * Clear all credentials and connection data */ public function disconnect() { $this->set_api_key(''); $this->set_api_secret(''); $this->set_connection_status('disconnected'); $this->set_organization_name(''); $this->set_subscription_tier(''); $this->set_setup_complete(false); // Clear wizard state so a fresh setup runs all steps delete_option(self::PREFIX . 'capabilities_chosen'); delete_option(self::PREFIX . 'chosen_capabilities'); delete_option(self::PREFIX . 'output_channel_configured'); delete_option(self::PREFIX . 'input_channel_configured'); delete_option(self::PREFIX . 'wizard_success_shown'); delete_option(self::PREFIX . 'output_channel_defaults'); delete_option(self::PREFIX . 'input_channel_defaults'); delete_option(self::PREFIX . 'input_channel_settings'); // Clear cached data delete_transient('aicp_connection_info'); delete_transient('aicp_statistics'); } /** * Encrypt a value using WordPress's AUTH_KEY * * @param string $value Value to encrypt * @return string Encrypted value (base64 encoded) */ private function encrypt($value) { if (!extension_loaded('openssl')) { // Fallback if OpenSSL is not available - use simple obfuscation return base64_encode($value); } $key = substr(hash('sha256', AUTH_KEY . SECURE_AUTH_KEY, true), 0, 32); $iv = openssl_random_pseudo_bytes(16); $encrypted = openssl_encrypt($value, 'AES-256-CBC', $key, 0, $iv); return base64_encode($iv . $encrypted); } /** * Decrypt a value * * @param string $encrypted Encrypted value (base64 encoded) * @return string Decrypted value */ private function decrypt($encrypted) { if (!extension_loaded('openssl')) { // Fallback if OpenSSL is not available return base64_decode($encrypted); } $data = base64_decode($encrypted); if ($data === false || strlen($data) < 17) { return ''; } $key = substr(hash('sha256', AUTH_KEY . SECURE_AUTH_KEY, true), 0, 32); $iv = substr($data, 0, 16); $encrypted_value = substr($data, 16); $decrypted = openssl_decrypt($encrypted_value, 'AES-256-CBC', $key, 0, $iv); return $decrypted !== false ? $decrypted : ''; } }