esc_url_raw( admin_url( 'admin-ajax.php' ) ),
'nonce' => wp_create_nonce( 'adamca_admin_nonce' ),
'savedModel' => get_option( 'adamca_ai_model', '' ),
'i18n' => array(
'working' => __( 'Working...', 'adams-crypto-analysis' ),
'success' => __( 'Success!', 'adams-crypto-analysis' ),
'errorOccurred' => __( 'Error occurred.', 'adams-crypto-analysis' ),
'requestFailed' => __( 'Request failed: ', 'adams-crypto-analysis' ),
),
) );
}
/**
* Add the settings page under the Settings menu.
*/
public static function add_settings_page() {
add_options_page(
__( 'Adams Crypto Analysis', 'adams-crypto-analysis' ),
__( 'Adams Crypto Analysis', 'adams-crypto-analysis' ),
'manage_options',
'adams-crypto-analysis',
array( __CLASS__, 'render_settings_page' )
);
}
/**
* Register all plugin settings.
*/
public static function register_settings() {
$settings_group = 'adamca_settings_group';
register_setting( $settings_group, 'adamca_coingecko_api_key', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
) );
register_setting( $settings_group, 'adamca_ai_provider', array(
'type' => 'string',
'sanitize_callback' => array( __CLASS__, 'sanitize_provider' ),
'default' => 'openai',
) );
register_setting( $settings_group, 'adamca_ai_api_key', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
) );
register_setting( $settings_group, 'adamca_ai_model', array(
'type' => 'string',
'sanitize_callback' => array( __CLASS__, 'sanitize_model' ),
'default' => '',
) );
register_setting( $settings_group, 'adamca_top10_coins', array(
'type' => 'string',
'sanitize_callback' => array( __CLASS__, 'sanitize_coins_list' ),
'default' => '',
) );
register_setting( $settings_group, 'adamca_cache_expiry', array(
'type' => 'integer',
'sanitize_callback' => array( __CLASS__, 'sanitize_cache_expiry' ),
'default' => 43200,
) );
// CoinGecko section.
add_settings_section(
'adamca_coingecko_section',
__( 'CoinGecko Settings', 'adams-crypto-analysis' ),
'__return_null',
'adams-crypto-analysis'
);
add_settings_field(
'adamca_coingecko_api_key',
__( 'CoinGecko API Key (Pro)', 'adams-crypto-analysis' ),
array( __CLASS__, 'render_password_field' ),
'adams-crypto-analysis',
'adamca_coingecko_section',
array( 'option_name' => 'adamca_coingecko_api_key', 'description' => __( 'Leave blank for free tier.', 'adams-crypto-analysis' ) )
);
// AI section.
add_settings_section(
'adamca_ai_section',
__( 'AI Provider Settings', 'adams-crypto-analysis' ),
'__return_null',
'adams-crypto-analysis'
);
add_settings_field(
'adamca_ai_provider',
__( 'AI Provider', 'adams-crypto-analysis' ),
array( __CLASS__, 'render_provider_field' ),
'adams-crypto-analysis',
'adamca_ai_section'
);
add_settings_field(
'adamca_ai_api_key',
__( 'AI API Key', 'adams-crypto-analysis' ),
array( __CLASS__, 'render_password_field' ),
'adams-crypto-analysis',
'adamca_ai_section',
array( 'option_name' => 'adamca_ai_api_key', 'description' => __( 'API key for the selected provider.', 'adams-crypto-analysis' ) )
);
add_settings_field(
'adamca_ai_model',
__( 'AI Model', 'adams-crypto-analysis' ),
array( __CLASS__, 'render_model_field' ),
'adams-crypto-analysis',
'adamca_ai_section'
);
// Cache section.
add_settings_section(
'adamca_cache_section',
__( 'Cache Settings', 'adams-crypto-analysis' ),
'__return_null',
'adams-crypto-analysis'
);
add_settings_field(
'adamca_cache_expiry',
__( 'Cache Expiry (seconds)', 'adams-crypto-analysis' ),
array( __CLASS__, 'render_number_field' ),
'adams-crypto-analysis',
'adamca_cache_section',
array( 'option_name' => 'adamca_cache_expiry', 'description' => __( 'How long to cache analysis results. Default: 43200 (12 hours). Minimum: 3600 (1 hour).', 'adams-crypto-analysis' ) )
);
add_settings_field(
'adamca_top10_coins',
__( 'Top 10 Coins', 'adams-crypto-analysis' ),
array( __CLASS__, 'render_textarea_field' ),
'adams-crypto-analysis',
'adamca_cache_section',
array( 'option_name' => 'adamca_top10_coins', 'description' => __( 'CoinGecko IDs, one per line. These get a lightning bolt in the frontend dropdown.', 'adams-crypto-analysis' ) )
);
}
/**
* Render the settings page.
*/
public static function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
'OpenAI',
'xai' => 'xAI / Grok',
'anthropic' => 'Anthropic / Claude',
);
?>
array( 'gpt-5', 'gpt-5-mini', 'gpt-4o', 'gpt-4o-mini' ),
'xai' => array( 'grok-4-fast-non-reasoning', 'grok-4', 'grok-3' ),
'anthropic' => array( 'claude-opus-4-5', 'claude-sonnet-4-5' ),
);
?>
15,
'headers' => $headers_arr,
) );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response->get_error_message() );
}
$status_code = wp_remote_retrieve_response_code( $response );
if ( 200 === $status_code ) {
wp_send_json_success( array( 'message' => __( 'CoinGecko connection successful!', 'adams-crypto-analysis' ) ) );
} else {
wp_send_json_error(
sprintf(
/* translators: %d: HTTP status code */
__( 'CoinGecko returned HTTP %d.', 'adams-crypto-analysis' ),
$status_code
)
);
}
}
/**
* AJAX handler: Test AI provider connection.
*/
public static function ajax_test_ai_provider() {
check_ajax_referer( 'adamca_admin_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( __( 'Unauthorized.', 'adams-crypto-analysis' ) );
}
$provider = get_option( 'adamca_ai_provider', 'openai' );
$api_key = get_option( 'adamca_ai_api_key', '' );
$model_name = get_option( 'adamca_ai_model', '' );
if ( empty( $api_key ) ) {
wp_send_json_error( __( 'AI API key is not configured.', 'adams-crypto-analysis' ) );
}
$test_prompt = 'Reply with exactly the word OK.';
$system_text = 'You are a helpful assistant.';
switch ( $provider ) {
case 'xai':
$effective_model = $model_name ?: 'grok-3';
$xai_body = array(
'model' => $effective_model,
'messages' => array(
array( 'role' => 'system', 'content' => $system_text ),
array( 'role' => 'user', 'content' => $test_prompt ),
),
'max_tokens' => 10,
'temperature' => 0,
'stream' => false,
);
$request_url = 'https://api.x.ai/v1/chat/completions';
$request_body = wp_json_encode( $xai_body );
$headers_arr = array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $api_key,
);
break;
case 'anthropic':
$request_url = 'https://api.anthropic.com/v1/messages';
$request_body = wp_json_encode( array(
'model' => $model_name ?: 'claude-opus-4-5',
'max_tokens' => 10,
'system' => $system_text,
'messages' => array(
array( 'role' => 'user', 'content' => $test_prompt ),
),
) );
$headers_arr = array(
'Content-Type' => 'application/json',
'x-api-key' => $api_key,
'anthropic-version' => '2023-06-01',
);
break;
case 'openai':
default:
$effective_model = $model_name ?: 'gpt-4o';
if ( 0 === strpos( $effective_model, 'gpt-5' ) ) {
$request_url = 'https://api.openai.com/v1/responses';
$combined_prompt = $system_text . "\n\n" . $test_prompt;
$request_body = wp_json_encode( array(
'model' => $effective_model,
'input' => array(
array(
'role' => 'user',
'content' => array(
array(
'type' => 'input_text',
'text' => $combined_prompt,
),
),
),
),
'max_output_tokens' => 16,
) );
} else {
$request_url = 'https://api.openai.com/v1/chat/completions';
$request_body = wp_json_encode( array(
'model' => $effective_model,
'messages' => array(
array( 'role' => 'system', 'content' => $system_text ),
array( 'role' => 'user', 'content' => $test_prompt ),
),
'max_tokens' => 10,
'temperature' => 0,
) );
}
$headers_arr = array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $api_key,
);
break;
}
$response = wp_remote_post( $request_url, array(
'timeout' => 30,
'headers' => $headers_arr,
'body' => $request_body,
) );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response->get_error_message() );
}
$status_code = wp_remote_retrieve_response_code( $response );
if ( 200 === $status_code ) {
wp_send_json_success( array(
'message' => sprintf(
/* translators: %s: AI provider name */
__( '%s connection successful!', 'adams-crypto-analysis' ),
ucfirst( $provider )
),
) );
} else {
$error_body = wp_remote_retrieve_body( $response );
$safe_error = sanitize_text_field( substr( $error_body, 0, 200 ) );
wp_send_json_error(
sprintf(
/* translators: 1: AI provider name, 2: HTTP status code, 3: truncated error message */
__( '%1$s returned HTTP %2$d: %3$s', 'adams-crypto-analysis' ),
ucfirst( $provider ),
$status_code,
$safe_error
)
);
}
}
/**
* AJAX handler: Clear cache for a coin or all coins.
*/
public static function ajax_clear_cache() {
check_ajax_referer( 'adamca_admin_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( __( 'Unauthorized.', 'adams-crypto-analysis' ) );
}
$coin_id = isset( $_POST['coin_id'] ) ? sanitize_key( wp_unslash( $_POST['coin_id'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
if ( empty( $coin_id ) ) {
wp_send_json_error( __( 'No coin ID provided.', 'adams-crypto-analysis' ) );
}
if ( 'all' === $coin_id ) {
$clear_count = ADAMCA_Cache::clear_all_cache();
wp_send_json_success( array(
'message' => sprintf(
/* translators: %d: number of coins cleared */
__( 'Cleared cache for %d coin(s).', 'adams-crypto-analysis' ),
$clear_count
),
) );
} else {
ADAMCA_Cache::clear_cache( $coin_id );
wp_send_json_success( array(
'message' => sprintf(
/* translators: %s: coin ID */
__( 'Cleared cache for %s.', 'adams-crypto-analysis' ),
$coin_id
),
) );
}
}
}