65535 ) { continue; } $key = $ip . ':' . $port_int; $enabled = 1; if ( isset( $existing_map[ $key ]['enabled'] ) ) { $enabled = (int) $existing_map[ $key ]['enabled']; } $sanitized_user = sanitize_text_field( $user ); // Пароль хранится зашифрованным и НЕ показывается в форме. // Если в строке пароль не введён — сохраняем ранее сохранённый // (для того же ip:port и логина), иначе шифруем новый. if ( '' !== $pass ) { $enc_pass = AIPMSP_Crypto::encrypt( $pass ); } elseif ( isset( $existing_map[ $key ]['pass'], $existing_map[ $key ]['user'] ) && '' !== (string) $existing_map[ $key ]['pass'] && $existing_map[ $key ]['user'] === $sanitized_user ) { $enc_pass = $existing_map[ $key ]['pass']; } else { $enc_pass = ''; } $result[] = array( 'ip' => $ip, 'port' => $port_int, 'user' => $sanitized_user, 'pass' => $enc_pass, 'type' => $type, 'enabled' => $enabled ? 1 : 0, ); } return $result; } /** * Возвращает список включённых прокси. * * @since 1.0.0 * * @param array|null $settings Настройки (если не переданы — загружаются). * @return array */ public static function get_enabled_proxies( $settings = null ) { if ( null === $settings ) { $settings = self::get_settings(); } $enabled = array(); foreach ( $settings['proxies'] as $proxy ) { if ( ! empty( $proxy['enabled'] ) ) { $enabled[] = $proxy; } } return $enabled; } /** * Проверяет один прокси через тестовый cURL-запрос. * * @since 1.0.0 * * @param array $proxy Структура прокси. * @param string $test_url URL для проверки. * @return array Результат: success(bool), message(string), time(float ms), http_code(int). */ public static function health_check( array $proxy, $test_url = 'https://api.openai.com/v1/models' ) { if ( ! function_exists( 'curl_init' ) ) { return array( 'success' => false, 'message' => __( 'cURL недоступен на сервере', 'ai-proxy-manager' ), 'time' => 0, 'http_code' => 0, ); } $ch = curl_init(); // phpcs:ignore WordPress.WP.AlternativeFunctions $proxy_type = self::curl_proxy_type( $proxy['type'] ); $pass = isset( $proxy['pass'] ) ? AIPMSP_Crypto::decrypt( $proxy['pass'] ) : ''; curl_setopt( $ch, CURLOPT_URL, $test_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); // phpcs:ignore WordPress.WP.AlternativeFunctions curl_setopt( $ch, CURLOPT_NOBODY, true ); // phpcs:ignore WordPress.WP.AlternativeFunctions curl_setopt( $ch, CURLOPT_PROXY, $proxy['ip'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions curl_setopt( $ch, CURLOPT_PROXYPORT, (int) $proxy['port'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions curl_setopt( $ch, CURLOPT_PROXYTYPE, $proxy_type ); // phpcs:ignore WordPress.WP.AlternativeFunctions curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 ); // phpcs:ignore WordPress.WP.AlternativeFunctions curl_setopt( $ch, CURLOPT_TIMEOUT, 15 ); // phpcs:ignore WordPress.WP.AlternativeFunctions if ( ! empty( $proxy['user'] ) && '' !== $pass ) { curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy['user'] . ':' . $pass ); // phpcs:ignore WordPress.WP.AlternativeFunctions } $start = microtime( true ); $exec = curl_exec( $ch ); // phpcs:ignore WordPress.WP.AlternativeFunctions $elapsed = round( ( microtime( true ) - $start ) * 1000 ); $http_code = (int) curl_getinfo( $ch, CURLINFO_RESPONSE_CODE ); // phpcs:ignore WordPress.WP.AlternativeFunctions $err_no = curl_errno( $ch ); // phpcs:ignore WordPress.WP.AlternativeFunctions $err_msg = curl_error( $ch ); // phpcs:ignore WordPress.WP.AlternativeFunctions curl_close( $ch ); // phpcs:ignore WordPress.WP.AlternativeFunctions if ( 0 !== $err_no || false === $exec ) { return array( 'success' => false, 'message' => sprintf( /* translators: %s: текст ошибки cURL */ __( 'Недоступен: %s', 'ai-proxy-manager' ), $err_msg ), 'time' => $elapsed, 'http_code' => $http_code, ); } // Любой HTTP-ответ означает, что соединение через прокси установлено. return array( 'success' => true, 'message' => sprintf( /* translators: 1: HTTP-код, 2: время ответа в мс */ __( 'Доступен (HTTP %1$d, %2$d мс)', 'ai-proxy-manager' ), $http_code, $elapsed ), 'time' => $elapsed, 'http_code' => $http_code, ); } /** * Преобразует строковый тип прокси в константу CURLPROXY_*. * * @since 1.0.0 * * @param string $type Тип (http|https|socks5). * @return int */ public static function curl_proxy_type( $type ) { switch ( self::sanitize_type( $type ) ) { case 'socks5': return defined( 'CURLPROXY_SOCKS5' ) ? CURLPROXY_SOCKS5 : 5; case 'https': // HTTPS-прокси использует тот же тип-хэндлер, что и HTTP, но через TLS-соединение. return defined( 'CURLPROXY_HTTP' ) ? CURLPROXY_HTTP : 0; case 'http': default: return defined( 'CURLPROXY_HTTP' ) ? CURLPROXY_HTTP : 0; } } /** * Нормализует строковый тип прокси. * * @since 1.0.0 * * @param string $type Тип. * @return string Один из: http|https|socks5. */ public static function sanitize_type( $type ) { $type = strtolower( sanitize_text_field( (string) $type ) ); $allowed = array( 'http', 'https', 'socks5' ); return in_array( $type, $allowed, true ) ? $type : 'http'; } /** * Возвращает нейтральную маску для отображения наличия пароля. * * Символы пароля не раскрываются (включая длину) — только факт его наличия. * * @since 1.0.0 * * @param string $pass Открытый пароль. * @return string Фиксированная маска при наличии пароля, иначе пустая строка. */ public static function mask_password( $pass ) { return ( '' === (string) $pass ) ? '' : '••••••••'; } /** * Валидирует хост (домен) по символам. * * @since 1.0.0 * * @param string $host Хост. * @return bool */ public static function is_valid_host( $host ) { $host = (string) $host; if ( '' === $host || strlen( $host ) > 253 ) { return false; } return (bool) preg_match( '/^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/', $host ); } /** * Санитизирует список доменов из textarea. * * @since 1.0.0 * * @param string $raw Сырой текст. * @return array */ public static function parse_domains( $raw ) { $lines = preg_split( '/\r\n|\r|\n/', (string) $raw ); $result = array(); foreach ( $lines as $line ) { $line = strtolower( trim( $line ) ); if ( '' === $line ) { continue; } // Если вставили URL — извлекаем хост. if ( false !== strpos( $line, '/' ) ) { $parsed = wp_parse_url( $line ); if ( ! empty( $parsed['host'] ) ) { $line = $parsed['host']; } } if ( self::is_valid_host( $line ) && ! in_array( $line, $result, true ) ) { $result[] = $line; } } return $result; } }