settings = $settings; } /** * Make an API request * * @param string $endpoint API endpoint (relative to base URL) * @param string $method HTTP method (GET, POST, PUT, DELETE) * @param array $body Request body (for POST/PUT) * @return array|WP_Error Response data or error */ public function request($endpoint, $method = 'GET', $body = null) { $api_key = $this->settings->get_api_key(); $api_secret = $this->settings->get_api_secret(); if (empty($api_key) || empty($api_secret)) { $this->log_error('API credentials not configured'); return new WP_Error( 'aicp_not_configured', __('API credentials are not configured.', 'ai-content-plan') ); } $url = trailingslashit(aicp_bridge()->get_api_url()) . ltrim($endpoint, '/'); $args = array( 'method' => $method, 'timeout' => $this->timeout, 'headers' => array( 'X-Consumer-Key' => $api_key, 'X-Consumer-Secret' => $api_secret, 'Content-Type' => 'application/json', 'Accept' => 'application/json', 'User-Agent' => 'AICP-WordPress-Plugin/' . AICP_BRIDGE_VERSION ) ); if ($body !== null && in_array($method, array('POST', 'PUT', 'PATCH'), true)) { $args['body'] = wp_json_encode($body); } $this->log_debug('API request', array( 'method' => $method, 'endpoint' => $endpoint, 'url' => $url, 'has_body' => $body !== null, )); $response = wp_remote_request($url, $args); if (is_wp_error($response)) { $this->log_error('API request failed', array( 'endpoint' => $endpoint, 'error' => $response->get_error_message() )); return $response; } $status_code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); $this->log_debug('API response', array( 'endpoint' => $endpoint, 'status_code' => $status_code, 'has_data' => !empty($data), 'response_body' => substr($body, 0, 200) )); if ($status_code >= 400) { $error_message = isset($data['error']) ? $data['error'] : (isset($data['message']) ? $data['message'] : __('API request failed.', 'ai-content-plan')); $this->log_error('API error response', array( 'endpoint' => $endpoint, 'status_code' => $status_code, 'message' => $error_message )); return new WP_Error( 'aicp_api_error', $error_message, array('status_code' => $status_code, 'response' => $data) ); } return $data; } /** * Test connection to AICP API * * @param string $api_key API key to test * @param string $api_secret API secret to test * @return array|WP_Error Connection info or error */ public function test_connection($api_key = null, $api_secret = null) { // Temporarily override credentials for testing if ($api_key !== null && $api_secret !== null) { $original_key = $this->settings->get_api_key(); $original_secret = $this->settings->get_api_secret(); $this->settings->set_api_key($api_key); $this->settings->set_api_secret($api_secret); } $result = $this->request('api/external/wordpress/connection-info', 'GET'); // Restore original credentials if we were testing new ones if ($api_key !== null && $api_secret !== null) { $this->settings->set_api_key($original_key); $this->settings->set_api_secret($original_secret); } return $result; } /** * Get publishing statistics * * @param bool $force_refresh Force refresh (bypass cache) * @return array|WP_Error Statistics data or error */ public function get_statistics($force_refresh = false) { $cache_key = 'aicp_statistics'; $cached = get_transient($cache_key); if (!$force_refresh && $cached !== false) { return $cached; } $result = $this->request('api/external/wordpress/statistics', 'GET'); if (!is_wp_error($result)) { // Cache for 15 minutes set_transient($cache_key, $result, 15 * MINUTE_IN_SECONDS); } return $result; } /** * Configure WooCommerce integration * * @param array $config WooCommerce configuration * @return array|WP_Error Response or error */ public function configure_woocommerce($config) { return $this->request('api/external/wordpress/woocommerce/configure', 'POST', array( 'storeUrl' => $config['store_url'], 'consumerKey' => $config['consumer_key'], 'consumerSecret' => $config['consumer_secret'], 'enabledCategories' => isset($config['categories']) ? $config['categories'] : array() )); } /** * Register WordPress site with AI Content Plan using a linking code * * @param array $data Registration data (linking_code, consumer_key, consumer_secret, site_url, site_name) * @return array|WP_Error Response data or error */ public function register_with_blogai($data) { $this->log_debug('register_with_blogai called', $data); // Validate required fields $required_fields = array('linking_code', 'consumer_key', 'consumer_secret', 'site_url'); foreach ($required_fields as $field) { if (empty($data[$field])) { /* translators: %s: name of the missing field */ $error_msg = sprintf(__('Missing required field: %s', 'ai-content-plan'), $field); $this->log_error('Registration failed - missing field', array('field' => $field)); return new WP_Error('aicp_missing_field', $error_msg); } } $url = trailingslashit(aicp_bridge()->get_api_url()) . 'api/external/wordpress/register'; $body = array( 'linkingCode' => $data['linking_code'], 'siteUrl' => $data['site_url'], 'consumerKey' => $data['consumer_key'], 'consumerSecret' => $data['consumer_secret'] ); $args = array( 'method' => 'POST', 'timeout' => $this->timeout, 'headers' => array( 'Content-Type' => 'application/json', 'Accept' => 'application/json', 'User-Agent' => 'AICP-WordPress-Plugin/' . AICP_BRIDGE_VERSION ), 'body' => wp_json_encode($body) ); $this->log_debug('Sending registration request', array( 'url' => $url, 'body' => array_merge($body, array('consumerSecret' => '[REDACTED]')) )); $response = wp_remote_request($url, $args); if (is_wp_error($response)) { $this->log_error('Registration request failed', array( 'error' => $response->get_error_message() )); return $response; } $status_code = wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); $data_response = json_decode($response_body, true); $this->log_debug('Registration response received', array( 'status_code' => $status_code, 'response' => $data_response )); if ($status_code >= 400) { $error_message = isset($data_response['message']) ? $data_response['message'] : (isset($data_response['error']) ? $data_response['error'] : __('Registration failed.', 'ai-content-plan')); $this->log_error('Registration failed', array( 'status_code' => $status_code, 'message' => $error_message )); return new WP_Error( 'aicp_registration_error', $error_message, array('status_code' => $status_code, 'response' => $data_response) ); } // The backend returns {success, message, credentialId} // But the wizard expects api_key and api_secret // Use the consumer credentials as the API credentials $result = array( 'success' => $data_response['success'] ?? false, 'message' => $data_response['message'] ?? '', 'credential_id' => $data_response['credentialId'] ?? null, 'api_key' => $data['consumer_key'], 'api_secret' => $data['consumer_secret'] ); $this->log_info('Registration successful', array( 'credential_id' => $result['credential_id'] )); return $result; } /** * Configure WordPress output channel (publishing defaults) * * @param array $config Configuration data * @return array|WP_Error Response or error */ public function configure_output_channel($config) { $this->log_debug('configure_output_channel called', $config); $body = array( 'publishingDefaults' => array( 'postStatus' => $config['post_status'] ?? 'draft', 'defaultCategoryId' => $config['category'] ?? null, 'defaultCategoryName' => $config['category_name'] ?? null, 'defaultAuthorId' => $config['author'] ?? null, 'defaultAuthorName' => $config['author_name'] ?? null ) ); $result = $this->request('api/external/wordpress/output-channel/configure', 'POST', $body); if (is_wp_error($result)) { $this->log_error('Output channel configuration failed', array( 'error' => $result->get_error_message() )); } else { $this->log_info('Output channel configured successfully', $result); } return $result; } /** * Configure WordPress input channel (content sharing) * * @param array $config Configuration data * @return array|WP_Error Response or error */ public function configure_input_channel($config) { $this->log_debug('configure_input_channel called', $config); $body = array( 'sharePosts' => !empty($config['content_types']) && in_array('posts', $config['content_types'], true), 'postCategoryIds' => array(), // Could be extended 'shareWooCommerceProducts' => !empty($config['content_types']) && in_array('products', $config['content_types'], true), 'productCategoryIds' => array() // Could be extended ); $result = $this->request('api/external/wordpress/input-channel/configure', 'POST', $body); if (is_wp_error($result)) { $this->log_error('Input channel configuration failed', array( 'error' => $result->get_error_message() )); } else { $this->log_info('Input channel configured successfully', $result); } return $result; } /** * Log debug message * * @param string $message Log message * @param array $context Additional context */ private function log_debug($message, $context = array()) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->write_log('DEBUG', $message, $context); } } /** * Log info message * * @param string $message Log message * @param array $context Additional context */ private function log_info($message, $context = array()) { $this->write_log('INFO', $message, $context); } /** * Log error message * * @param string $message Log message * @param array $context Additional context */ private function log_error($message, $context = array()) { $this->write_log('ERROR', $message, $context); } /** * Write log entry * * @param string $level Log level * @param string $message Log message * @param array $context Additional context */ private function write_log($level, $message, $context = array()) { if (!defined('WP_DEBUG') || !WP_DEBUG) { return; } $timestamp = current_time('Y-m-d H:i:s'); $context_str = !empty($context) ? ' | ' . wp_json_encode($context) : ''; $log_message = sprintf('[%s] AICP %s: %s%s', $timestamp, $level, $message, $context_str); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Guarded by WP_DEBUG error_log($log_message); $logs = get_option('aicp_debug_logs', array()); $logs[] = array( 'timestamp' => $timestamp, 'level' => $level, 'message' => $message, 'context' => $context ); // Keep only last 100 entries if (count($logs) > 100) { $logs = array_slice($logs, -100); } update_option('aicp_debug_logs', $logs, false); } /** * Clear all cached data */ public function clear_cache() { delete_transient('aicp_connection_info'); delete_transient('aicp_statistics'); } }