_page_aeo-bob-lite-settings", never "toplevel_page_..."; the old // check never matched and Show/Hide and Test Connection did nothing. if (strpos((string) $hook_suffix, '_page_aeo-bob-lite-settings') === false) { return; } // Enqueue external settings script wp_enqueue_script( 'aeo-bob-lite-settings', AEO_BOB_LITE_URL . 'assets/js/settings.js', array( 'jquery' ), aeo_bob_lite_asset_ver( 'assets/js/settings.js' ), true ); // Localize script with data and translatable strings wp_localize_script( 'aeo-bob-lite-settings', 'aeoBobLiteSettings', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'testApiNonce' => wp_create_nonce( 'aeo_bob_lite_test_api' ), 'hasSavedKey' => '' !== (string) get_option( 'aeo_bob_lite_openai_key', '' ), // v1.5.86: a flag, never the key 'modelsNonce' => wp_create_nonce( 'aeo_bob_lite_refresh_models' ), 'i18n' => array( 'enterApiKeyFirst' => __( 'Please enter an API key first.', 'aeo-bob-lite' ), 'testing' => __( 'Testing...', 'aeo-bob-lite' ), 'connecting' => __( 'Connecting to OpenAI...', 'aeo-bob-lite' ), 'connectionFailed' => __( 'Connection failed. Please try again.', 'aeo-bob-lite' ), 'testConnection' => __( 'Test Connection', 'aeo-bob-lite' ), 'refreshModels' => __( 'Refresh Models', 'aeo-bob-lite' ), 'refreshing' => __( 'Refreshing...', 'aeo-bob-lite' ), /* translators: %s: model id */ 'recommendedAuto' => __( 'Recommended (currently %s)', 'aeo-bob-lite' ), 'recommended' => __( 'Recommended', 'aeo-bob-lite' ), 'allModels' => __( 'All models on your account', 'aeo-bob-lite' ), /* translators: %s: human-readable time, e.g. "5 minutes ago" */ 'lastRefreshed' => __( 'Last refreshed: %s', 'aeo-bob-lite' ), ), ) ); // Enqueue admin styles wp_enqueue_style( 'aeo-bob-lite-admin' ); } /** * Register settings */ public function register_settings() { register_setting('aeo_bob_lite_settings', 'aeo_bob_lite_openai_key', array( 'type' => 'string', 'sanitize_callback' => array($this, 'sanitize_api_key'), 'default' => '', )); add_settings_section( 'aeo_bob_lite_api_section', esc_html__('OpenAI API Configuration', 'aeo-bob-lite'), array($this, 'render_section_description'), 'aeo_bob_lite_settings' ); add_settings_field( 'openai_api_key', esc_html__('OpenAI API Key', 'aeo-bob-lite'), array($this, 'render_api_key_field'), 'aeo_bob_lite_settings', 'aeo_bob_lite_api_section' ); // v1.5.80: model from the account's live list ('' = Recommended low-cost default) register_setting('aeo_bob_lite_settings', AEO_Bob_Lite_OpenAI::OPTION_MODEL, array( 'type' => 'string', 'sanitize_callback' => array($this, 'sanitize_model'), 'default' => '', )); add_settings_field( 'openai_model', esc_html__('Model', 'aeo-bob-lite'), array($this, 'render_model_field'), 'aeo_bob_lite_settings', 'aeo_bob_lite_api_section' ); // v1.5.78: opt-in removal of scores, post meta and tables on uninstall (read by uninstall.php). register_setting('aeo_bob_lite_settings', 'aeo_bob_lite_delete_data', array( 'type' => 'boolean', 'sanitize_callback' => array($this, 'sanitize_checkbox'), 'default' => false, )); add_settings_section( 'aeo_bob_lite_data_section', esc_html__('Data', 'aeo-bob-lite'), '__return_false', 'aeo_bob_lite_settings' ); add_settings_field( 'delete_data', esc_html__('Uninstall', 'aeo-bob-lite'), array($this, 'render_delete_data_field'), 'aeo_bob_lite_settings', 'aeo_bob_lite_data_section' ); } /** * Sanitize checkbox (an unchecked box is not posted, so it arrives as null) */ public function sanitize_checkbox($value) { return !empty($value); } /** * Render "delete data on uninstall" checkbox */ public function render_delete_data_field() { ?>
esc_html__('Permission denied', 'aeo-bob-lite'))); } $api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : ''; if ($api_key === '') { $api_key = (string) get_option('aeo_bob_lite_openai_key', ''); // v1.5.86: test the saved key } if (empty($api_key)) { wp_send_json_error(array('message' => esc_html__('API key is required', 'aeo-bob-lite'))); } // Test the API key with a simple request (v1.5.80: the model Lite will use, Pro's // request-parameter policy; no token limit, like Pro's ping, so reasoning models can answer) $model = AEO_Bob_Lite_OpenAI::model(); $response = AEO_Bob_Lite_OpenAI::post($api_key, AEO_Bob_Lite_OpenAI::chat_body($model, array( array('role' => 'user', 'content' => 'Say "OK"'), )), array('timeout' => 15)); if (is_wp_error($response)) { wp_send_json_error(array('message' => esc_html__('Connection failed: ', 'aeo-bob-lite') . $response->get_error_message())); } $status_code = wp_remote_retrieve_response_code($response); if ($status_code === 200) { /* translators: %s: model id */ wp_send_json_success(array('message' => sprintf(esc_html__('API key is valid and working with %s!', 'aeo-bob-lite'), $model))); } elseif ($status_code === 401) { wp_send_json_error(array('message' => esc_html__('Invalid API key. Please check your key.', 'aeo-bob-lite'))); } elseif ($status_code === 429) { wp_send_json_error(array('message' => esc_html__('Rate limit exceeded. Your key works but you need to add billing.', 'aeo-bob-lite'))); } else { $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); // Validate JSON parsing succeeded if (json_last_error() !== JSON_ERROR_NONE) { wp_send_json_error(array('message' => esc_html__('Invalid response from API', 'aeo-bob-lite'))); return; } $error_msg = isset($data['error']['message']) ? $data['error']['message'] : esc_html__('Unknown error', 'aeo-bob-lite'); wp_send_json_error(array('message' => $error_msg)); } } /** * Render settings page */ public function render_settings_page() { if (!current_user_can('manage_options')) { return; } ?>