array( $this, 'sanitize_settings' ), 'default' => array( 'model_preference' => 'anthropic', 'temperature' => 3, 'max_file_size_mb' => 10, 'allow_shop_managers' => false, ), ) ); // Section: Analysis. add_settings_section( 'ai_log_analyzer_analysis', __( 'Analysis', 'ai-log-analyzer-for-woocommerce' ), '__return_false', 'ailwc_log_analyzer' ); add_settings_field( 'model_preference', __( 'AI Model Preference', 'ai-log-analyzer-for-woocommerce' ), array( $this, 'render_model_preference_field' ), 'ailwc_log_analyzer', 'ai_log_analyzer_analysis' ); add_settings_field( 'temperature', __( 'Temperature (0–10)', 'ai-log-analyzer-for-woocommerce' ), array( $this, 'render_temperature_field' ), 'ailwc_log_analyzer', 'ai_log_analyzer_analysis' ); // Section: Limits & Permissions. add_settings_section( 'ai_log_analyzer_limits', __( 'Limits & Permissions', 'ai-log-analyzer-for-woocommerce' ), '__return_false', 'ailwc_log_analyzer' ); add_settings_field( 'max_file_size_mb', __( 'Maximum File Size (MB)', 'ai-log-analyzer-for-woocommerce' ), array( $this, 'render_max_file_size_field' ), 'ailwc_log_analyzer', 'ai_log_analyzer_limits' ); add_settings_field( 'allow_shop_managers', __( 'Allow Shop Managers', 'ai-log-analyzer-for-woocommerce' ), array( $this, 'render_allow_shop_managers_field' ), 'ailwc_log_analyzer', 'ai_log_analyzer_limits' ); } /** * Sanitizes the settings array before it is saved. * * @param mixed $input Raw input from the settings form. * @return array Sanitized settings. */ public function sanitize_settings( mixed $input ): array { $current = (array) get_option( AILWC_LOG_ANALYZER_OPTION, array() ); $sanitized = $current; if ( isset( $input['model_preference'] ) ) { $allowed = array_keys( $this->get_known_providers() ); $value = sanitize_text_field( wp_unslash( $input['model_preference'] ) ); if ( in_array( $value, $allowed, true ) ) { $sanitized['model_preference'] = $value; } } if ( isset( $input['temperature'] ) ) { $sanitized['temperature'] = min( 10, max( 0, absint( $input['temperature'] ) ) ); } if ( isset( $input['max_file_size_mb'] ) ) { $sanitized['max_file_size_mb'] = min( 50, max( 1, absint( $input['max_file_size_mb'] ) ) ); } // Checkbox is absent from POST when unchecked. $sanitized['allow_shop_managers'] = isset( $input['allow_shop_managers'] ); return $sanitized; } /** * Renders the AI Model Preference radio buttons. * * On WordPress 7.0+ only providers whose connector plugin is active are * shown; when none are connected, a prompt to manage connectors is shown * instead. On older installs (no Connectors API) all three choices render. * * @return void */ public function render_model_preference_field(): void { $options = (array) get_option( AILWC_LOG_ANALYZER_OPTION, array() ); $current = $options['model_preference'] ?? 'anthropic'; $choices = $this->get_available_providers(); $name = AILWC_LOG_ANALYZER_OPTION . '[model_preference]'; if ( empty( $choices ) ) { printf( '

%s %s

', esc_html__( 'No AI providers are connected yet. Connect one to run analysis.', 'ai-log-analyzer-for-woocommerce' ), esc_url( admin_url( 'options-connectors.php' ) ), esc_html__( 'Manage connectors', 'ai-log-analyzer-for-woocommerce' ) ); return; } foreach ( $choices as $value => $label ) { printf( '', esc_attr( $name ), esc_attr( $value ), checked( $current, $value, false ), esc_html( $label ) ); } if ( function_exists( 'wp_get_connectors' ) ) { printf( '

%s %s

', esc_html__( 'Only connected AI providers are shown.', 'ai-log-analyzer-for-woocommerce' ), esc_url( admin_url( 'options-connectors.php' ) ), esc_html__( 'Manage connectors', 'ai-log-analyzer-for-woocommerce' ) ); } } /** * Returns the full set of AI providers this plugin can request. * * Used for saving/validating the model preference regardless of which * connectors are currently active. * * @return array Slug-to-label map of known providers. */ private function get_known_providers(): array { return array( 'anthropic' => __( 'Anthropic', 'ai-log-analyzer-for-woocommerce' ), 'google' => __( 'Google', 'ai-log-analyzer-for-woocommerce' ), 'openai' => __( 'OpenAI', 'ai-log-analyzer-for-woocommerce' ), ); } /** * Returns AI providers whose connector plugin is currently active. * * Uses the WordPress Connectors API (WP 7.0+) when available and reports * only providers whose associated connector plugin is active. Returns an * empty array when the API is available but nothing is connected. Falls * back to all known providers on older WordPress installs. * * @return array Slug-to-label map of available providers. */ private function get_available_providers(): array { $known = $this->get_known_providers(); if ( ! function_exists( 'wp_get_connectors' ) ) { return $known; } $available = array(); foreach ( \wp_get_connectors() as $id => $connector ) { if ( 'ai_provider' !== ( $connector['type'] ?? '' ) ) { continue; } if ( ! isset( $known[ $id ] ) ) { continue; } if ( ! $this->connector_plugin_is_active( $connector ) ) { continue; } $available[ $id ] = $known[ $id ]; } return $available; } /** * Determines whether a connector's associated plugin is active. * * The Connectors API exposes the provider plugin as a `plugin.file` path * relative to the plugins directory. This never reads connector secrets. * * @param array $connector Connector data from wp_get_connectors(). * @return bool */ private function connector_plugin_is_active( array $connector ): bool { $plugin_file = $connector['plugin']['file'] ?? ''; if ( ! is_string( $plugin_file ) || '' === $plugin_file ) { return false; } if ( ! function_exists( 'is_plugin_active' ) ) { $plugin_functions = ABSPATH . 'wp-admin/includes/plugin.php'; if ( is_readable( $plugin_functions ) ) { require_once $plugin_functions; } } if ( ! function_exists( 'is_plugin_active' ) ) { return false; } return is_plugin_active( $plugin_file ); } /** * Renders the Temperature number input. * * @return void */ public function render_temperature_field(): void { $options = (array) get_option( AILWC_LOG_ANALYZER_OPTION, array() ); $value = $options['temperature'] ?? 3; $name = AILWC_LOG_ANALYZER_OPTION . '[temperature]'; printf( '', esc_attr( $name ), esc_attr( (string) $value ) ); echo '

' . esc_html__( 'Stored as an integer and divided by 10 when passed to the AI (e.g. 3 → 0.3).', 'ai-log-analyzer-for-woocommerce' ) . '

'; } /** * Renders the Maximum File Size number input. * * @return void */ public function render_max_file_size_field(): void { $options = (array) get_option( AILWC_LOG_ANALYZER_OPTION, array() ); $value = $options['max_file_size_mb'] ?? 10; $name = AILWC_LOG_ANALYZER_OPTION . '[max_file_size_mb]'; printf( '', esc_attr( $name ), esc_attr( (string) $value ) ); echo '

' . esc_html__( 'Log files larger than this limit will be truncated from the start (most recent content kept).', 'ai-log-analyzer-for-woocommerce' ) . '

'; } /** * Renders the Allow Shop Managers checkbox. * * @return void */ public function render_allow_shop_managers_field(): void { $options = (array) get_option( AILWC_LOG_ANALYZER_OPTION, array() ); $checked = ! empty( $options['allow_shop_managers'] ); $name = AILWC_LOG_ANALYZER_OPTION . '[allow_shop_managers]'; printf( '', esc_attr( $name ), checked( $checked, true, false ), esc_html__( 'Allow users with the Shop Manager role to run log analysis (in addition to admins).', 'ai-log-analyzer-for-woocommerce' ) ); } /** * Registers the AI Log Analyzer submenu page under WooCommerce. * * @return void */ public function register_menu(): void { add_submenu_page( 'woocommerce', __( 'AI Log Analyzer', 'ai-log-analyzer-for-woocommerce' ), __( 'AI Log Analyzer', 'ai-log-analyzer-for-woocommerce' ), 'manage_woocommerce', 'ai-log-analyzer', array( $this, 'render_settings_page' ) ); } /** * Renders the settings page template. * * @return void */ public function render_settings_page(): void { require_once AILWC_LOG_ANALYZER_PATH . 'admin/views/settings-page.php'; } }