admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('advocate_loop_admin_nonce'), ]); } public function add_admin_menu() { $icon_path = ADVOCATE_LOOP_DIR . 'assets/images/logo-icon-menu.svg'; if (is_readable($icon_path)) { $icon_svg = file_get_contents($icon_path); } else { $icon_svg = ''; } $icon_base64 = 'data:image/svg+xml;base64,' . base64_encode($icon_svg); add_menu_page( 'AdvocateLoop', 'AdvocateLoop', 'manage_options', 'advocate-loop', [$this, 'settings_page'], $icon_base64, 30 ); add_submenu_page( 'advocate-loop', 'Settings', 'Settings', 'manage_options', 'advocate-loop', [$this, 'settings_page'] ); } public function register_settings() { register_setting('advocate_loop_api', 'advocate_loop_api_key', [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', 'show_in_rest' => false, ]); register_setting('advocate_loop_api', 'advocate_loop_brand_id', [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', 'show_in_rest' => false, ]); register_setting('advocate_loop_settings_group', 'advocate_loop_settings', [ 'type' => 'array', 'sanitize_callback' => [$this, 'sanitize_settings_option'], 'default' => [], 'show_in_rest' => false, ]); } /** * Sanitize callback for the advocate_loop_settings option (recursively * sanitizes every value). Public so register_setting() can invoke it. */ public function sanitize_settings_option($value) { if (!is_array($value)) { return []; } return $this->sanitize_settings_recursive($value); } /** * Render the settings page. */ public function settings_page() { include ADVOCATE_LOOP_DIR . 'includes/Admin/views/settings.php'; } /** * AJAX: Save API Credentials. */ public function save_api_credentials() { check_ajax_referer('advocate_loop_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); } $api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : ''; $brand_id = isset($_POST['brand_id']) ? sanitize_text_field(wp_unslash($_POST['brand_id'])) : ''; if (empty($api_key) || empty($brand_id)) { wp_send_json_error(['message' => 'Both API Key and Brand ID are required']); } update_option('advocate_loop_api_key', $api_key); update_option('advocate_loop_brand_id', $brand_id); wp_send_json_success(['message' => 'API credentials saved successfully']); } /** * AJAX: Check Connection Status (used for periodic UI polling). */ public function check_connection_status() { check_ajax_referer('advocate_loop_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); } $api_key = get_option('advocate_loop_api_key'); $brand_id = get_option('advocate_loop_brand_id'); if (empty($api_key) || empty($brand_id)) { wp_send_json_success([ 'connected' => false, 'message' => 'API credentials not configured' ]); return; } $response = wp_remote_get( ADVOCATE_LOOP_API_URL . '/api/v1/account', [ 'headers' => [ 'X-API-Key' => $api_key, 'X-Brand-ID' => $brand_id ], 'timeout' => 10 ] ); if (is_wp_error($response)) { wp_send_json_success([ 'connected' => false, 'message' => 'Connection failed: ' . $response->get_error_message() ]); return; } $body = json_decode(wp_remote_retrieve_body($response), true); if (isset($body['success']) && $body['success'] && isset($body['data'])) { $data = $body['data']; $license = $data['license'] ?? []; $usage = $data['usage'] ?? []; update_option('advocate_loop_ever_connected', '1'); wp_send_json_success([ 'connected' => true, 'message' => 'Connected to AdvocateLoop', 'brand_name' => $data['brand']['name'] ?? '', 'plan' => $license['plan'] ?? 'free', 'license_status' => ucfirst($license['status'] ?? 'active'), 'expires_at' => $license['expires_at'] ?? null, 'api_calls' => $usage['calls_used'] ?? $license['api_calls'] ?? 0, 'api_limit' => $usage['calls_limit'] ?? $license['api_limit'] ?? 10000, 'api_calls_reset_at' => $usage['period_resets_at'] ?? $license['api_calls_reset_at'] ?? null, 'usage_percentage' => $usage['usage_percentage'] ?? 0 ]); } else { wp_send_json_success([ 'connected' => false, 'message' => $body['error'] ?? 'Authentication failed' ]); } } /** * AJAX: Test API Connection. */ public function test_connection() { check_ajax_referer('advocate_loop_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); } $api_key = get_option('advocate_loop_api_key'); $brand_id = get_option('advocate_loop_brand_id'); if (empty($api_key) || empty($brand_id)) { wp_send_json_error(['message' => 'API Key and Brand ID are required']); } $response = wp_remote_get( ADVOCATE_LOOP_API_URL . '/api/v1/auth/verify', [ 'headers' => [ 'X-API-Key' => $api_key, 'X-Brand-ID' => $brand_id ], 'timeout' => 10 ] ); if (is_wp_error($response)) { wp_send_json_error(['message' => 'Connection failed: ' . $response->get_error_message()]); } $body = json_decode(wp_remote_retrieve_body($response), true); if (isset($body['success']) && $body['success']) { $account_response = wp_remote_get( ADVOCATE_LOOP_API_URL . '/api/v1/account', [ 'headers' => [ 'X-API-Key' => $api_key, 'X-Brand-ID' => $brand_id, ], 'timeout' => 10, ] ); if (!is_wp_error($account_response)) { $account_body = json_decode(wp_remote_retrieve_body($account_response), true); \AdvocateLoop\Core\Signing::maybe_store_secret($account_body); } update_option('advocate_loop_ever_connected', '1'); wp_send_json_success(['message' => 'Connection successful!']); } else { wp_send_json_error(['message' => $body['error'] ?? 'Authentication failed']); } } /** * AJAX: Save settings for a configuration section. */ public function save_settings() { check_ajax_referer('advocate_loop_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); } $section = isset($_POST['section']) ? sanitize_text_field(wp_unslash($_POST['section'])) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- recursively sanitized via sanitize_settings_recursive() (sanitize_text_field on every value). $settings_data = isset($_POST['settings']) ? $this->sanitize_settings_recursive(wp_unslash($_POST['settings'])) : []; if (empty($section)) { wp_send_json_error(['message' => 'Invalid section']); } $settings = json_decode(get_option('advocate_loop_settings', '{}'), true); $settings[$section] = $settings_data; $updated = update_option('advocate_loop_settings', wp_json_encode($settings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); if ($updated) { wp_send_json_success(['message' => 'Settings saved successfully']); } else { $current = json_decode(get_option('advocate_loop_settings', '{}'), true); if (isset($current[$section]) && $current[$section] === $settings_data) { wp_send_json_success(['message' => 'Settings saved (no changes)']); } else { wp_send_json_error(['message' => 'Failed to save settings']); } } } private function sanitize_settings_recursive($data) { if (is_array($data)) { return array_map([$this, 'sanitize_settings_recursive'], $data); } return sanitize_text_field($data); } }