menu_icon(), 81 ); } /** * Добавляет ссылку «Настройки» рядом с кнопкой деактивации плагина. * * @since 1.0.5 * * @param array $links Существующие ссылки действий. * @return array */ public function action_links( $links ) { $settings_link = sprintf( '%s', esc_url( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ), esc_html__( 'Настройки', 'ai-proxy-manager' ) ); array_unshift( $links, $settings_link ); return $links; } /** * Добавляет ссылку «Детали» в строку плагина в списке плагинов. * * @since 1.0.5 * * @param array $links Метаданные строки плагина. * @param string $file Путь к файлу плагина. * @return array */ public function row_meta( $links, $file ) { if ( plugin_basename( AIPMSP_PLUGIN_FILE ) === $file ) { $links[] = sprintf( '%s', esc_url( self::PLUGIN_PAGE_URL ), esc_html__( 'Детали', 'ai-proxy-manager' ) ); } return $links; } /** * Подключает CSS/JS только на странице плагина. * * @since 1.0.0 * * @param string $hook Текущий хук админ-страницы. * @return void */ public function enqueue_assets( $hook ) { if ( 'toplevel_page_' . self::PAGE_SLUG !== $hook ) { return; } wp_enqueue_style( 'aipmsp-admin', AIPMSP_PLUGIN_URL . 'assets/css/admin.css', array(), AIPMSP_VERSION ); wp_enqueue_script( 'aipmsp-admin', AIPMSP_PLUGIN_URL . 'assets/js/admin.js', array( 'jquery' ), AIPMSP_VERSION, true ); wp_localize_script( 'aipmsp-admin', 'aipmspData', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'aipmsp_ajax_nonce' ), 'checking' => __( 'Проверка…', 'ai-proxy-manager' ), 'checkLabel' => __( 'Проверить', 'ai-proxy-manager' ), ) ); } /** * Обрабатывает сохранение формы настроек. * * @since 1.0.0 * * @return void */ public function handle_save() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Недостаточно прав.', 'ai-proxy-manager' ) ); } check_admin_referer( 'aipmsp_save_settings', 'aipmsp_nonce' ); $settings = AIPMSP_Proxy::get_settings(); // Тип прокси. $type = isset( $_POST['aipmsp_proxy_type'] ) ? AIPMSP_Proxy::sanitize_type( wp_unslash( $_POST['aipmsp_proxy_type'] ) ) : 'http'; // Прокси (с сохранением состояния enabled из прежних настроек). $raw_proxies = isset( $_POST['aipmsp_proxies'] ) ? sanitize_textarea_field( wp_unslash( $_POST['aipmsp_proxies'] ) ) : ''; $new_proxies = AIPMSP_Proxy::parse_proxies( $raw_proxies, $type, $settings['proxies'] ); // Применяем индивидуальные переключатели enabled из чекбоксов (по индексу ip:port). $enabled_keys = array(); if ( isset( $_POST['aipmsp_proxy_enabled_keys'] ) && is_array( $_POST['aipmsp_proxy_enabled_keys'] ) ) { foreach ( wp_unslash( $_POST['aipmsp_proxy_enabled_keys'] ) as $k ) { $enabled_keys[ sanitize_text_field( $k ) ] = true; } } // Только если форма прислала набор ключей-переключателей — применяем их. if ( isset( $_POST['aipmsp_toggles_present'] ) ) { foreach ( $new_proxies as &$proxy ) { $key = $proxy['ip'] . ':' . $proxy['port']; $proxy['enabled'] = isset( $enabled_keys[ $key ] ) ? 1 : 0; } unset( $proxy ); } $settings['proxies'] = $new_proxies; $settings['proxy_type'] = $type; $settings['proxy_enabled'] = isset( $_POST['aipmsp_proxy_master'] ) ? 1 : 0; $settings['debug_enabled'] = isset( $_POST['aipmsp_debug'] ) ? 1 : 0; // Домены. $raw_domains = isset( $_POST['aipmsp_domains'] ) ? sanitize_textarea_field( wp_unslash( $_POST['aipmsp_domains'] ) ) : ''; $settings['domains'] = AIPMSP_Proxy::parse_domains( $raw_domains ); if ( empty( $settings['domains'] ) ) { $defaults = AIPMSP_Plugin::get_default_settings(); $settings['domains'] = $defaults['domains']; } // Список провайдеров, отмеченных переключателем «Использовать для проверки». $use_keys = array(); if ( isset( $_POST['aipmsp_key_use'] ) && is_array( $_POST['aipmsp_key_use'] ) ) { foreach ( wp_unslash( $_POST['aipmsp_key_use'] ) as $slug ) { $use_keys[ sanitize_text_field( $slug ) ] = true; } } // API-ключи: шифруем только если поле непустое, иначе сохраняем прежний. $providers = array( 'openai', 'anthropic', 'google' ); foreach ( $providers as $provider ) { $field = 'aipmsp_key_' . $provider; if ( isset( $_POST[ $field ] ) ) { $raw_key = trim( sanitize_text_field( wp_unslash( $_POST[ $field ] ) ) ); if ( '' !== $raw_key ) { $settings['keys'][ $provider ] = AIPMSP_Crypto::encrypt( $raw_key ); } } // Очистка ключа, если установлен флаг. if ( isset( $_POST[ 'aipmsp_clear_' . $provider ] ) ) { $settings['keys'][ $provider ] = ''; } // Состояние переключателя «использовать для проверки». $settings['key_enabled'][ $provider ] = isset( $use_keys[ $provider ] ) ? 1 : 0; } AIPMSP_Proxy::save_settings( $settings ); $redirect = add_query_arg( array( 'page' => self::PAGE_SLUG, 'aipmsp_saved' => '1', ), admin_url( 'admin.php' ) ); wp_safe_redirect( $redirect ); exit; } /** * Рендерит страницу настроек. * * @since 1.0.0 * * @return void */ public function render_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $settings = AIPMSP_Proxy::get_settings(); // Восстанавливаем textarea прокси БЕЗ пароля (пароль не выводится в HTML). // Формат строки: ЛОГИН@IP:ПОРТ либо IP:ПОРТ. При сохранении пустого пароля // ранее сохранённый пароль остаётся без изменений. $proxy_lines = array(); foreach ( $settings['proxies'] as $proxy ) { if ( ! empty( $proxy['user'] ) ) { $line = $proxy['user'] . '@' . $proxy['ip'] . ':' . $proxy['port']; } else { $line = $proxy['ip'] . ':' . $proxy['port']; } $proxy_lines[] = $line; } $proxies_text = implode( "\n", $proxy_lines ); $domains_text = implode( "\n", $settings['domains'] ); $curl_available = function_exists( 'curl_init' ); $ssl_available = function_exists( 'openssl_encrypt' ); // Сообщение об успешном сохранении. if ( isset( $_GET['aipmsp_saved'] ) && '1' === $_GET['aipmsp_saved'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended echo '
' . esc_html__( 'Настройки сохранены.', 'ai-proxy-manager' ) . '