HEX
Server: Microsoft-IIS/10.0
System: Windows NT HMW8635 10.0 build 14393 (Windows Server 2016) i586
User: ibratele2 (0)
PHP: 8.0.11
Disabled: escapeshellarg, escapeshellcmd, exec, passthru, proc_close, proc_open, shell_exec, system, dl, popen, php_check_syntax, php_strip_whitespace, symlink, link, openlog, apache_child_terminate
Upload Files
File: E:/Home/ibratele2/Web/wp-content/plugins/cryout-json-file/includes/sender.php
<?php
/**
 * Класс для отправки данных в MongoDB через REST API
 */

// Запрещаем прямой доступ к файлу
if (!defined('ABSPATH')) {
    exit;
}

class My_Monitoring_Sender {
    
    /**
     * URL API endpoint
     * 
     * @var string
     */
    private $api_url;
    
    /**
     * API ключ для аутентификации
     * 
     * @var string
     */
    private $api_key;
    
    /**
     * Тип API: 'standard' или 'jsonbin'
     * 
     * @var string
     */
    private $api_type;
    
    /**
     * Таймаут запроса в секундах
     * 
     * @var int
     */
    private $timeout = 30;
    
    /**
     * Количество попыток отправки
     * 
     * @var int
     */
    private $max_retries = 3;
    
    /**
     * Интервал между попытками в секундах
     * 
     * @var int
     */
    private $retry_delay = 60;
    
    /**
     * Конструктор
     * 
     * @param string $api_url URL API endpoint
     * @param string $api_key API ключ
     * @param int $max_retries Максимальное количество попыток
     * @param int $retry_delay Задержка между попытками в секундах
     */
    public function __construct($api_url, $api_key, $max_retries = 3, $retry_delay = 60) {
        $this->api_url = $api_url;
        $this->api_key = $api_key;
        $this->max_retries = get_option('my_monitoring_max_retries', $max_retries);
        $this->retry_delay = get_option('my_monitoring_retry_delay', $retry_delay);
        
        // Определяем тип API
        $this->api_type = $this->detect_api_type($api_url);
        
        // Для стандартного API добавляем trailing slash
        if ($this->api_type === 'standard') {
            $this->api_url = trailingslashit($api_url);
        }
    }
    
    /**
     * Определить тип API по URL
     * 
     * @param string $api_url URL API
     * @return string 'standard' или 'jsonbin'
     */
    private function detect_api_type($api_url) {
        // Проверяем настройку из опций (приоритет)
        $api_type_option = get_option('my_monitoring_api_type', '');
        if (!empty($api_type_option) && in_array($api_type_option, array('standard', 'jsonbin'))) {
            return $api_type_option;
        }
        
        // Автоопределение по URL
        if (strpos($api_url, 'jsonbin.io') !== false) {
            return 'jsonbin';
        }
        
        return 'standard';
    }
    
    /**
     * Отправить данные в API с механизмом повторных попыток
     * 
     * @param array $data Данные для отправки
     * @return array Результат отправки
     */
    public function send($data) {
        $logger = My_Monitoring_Logger::get_instance();
        $attempt = 0;
        $last_error = null;
        $start_time = microtime(true);
        
        $logger->debug('Начало отправки данных', array(
            'api_url' => $this->api_url,
            'max_retries' => $this->max_retries,
            'data_size' => strlen(json_encode($data))
        ));
        
        while ($attempt < $this->max_retries) {
            $attempt++;
            
            $result = $this->attempt_send($data);
            
            // Если успешно, возвращаем результат
            if ($result['success']) {
                $execution_time = round((microtime(true) - $start_time) * 1000, 2);
                $logger->info('Данные успешно отправлены', array(
                    'attempt' => $attempt,
                    'execution_time_ms' => $execution_time,
                    'response_code' => $result['response_code'] ?? 0
                ));
                
                // Очищаем очередь неудачных отправок при успехе
                $this->clear_failed_queue();
                return $result;
            }
            
            // Сохраняем последнюю ошибку
            $last_error = $result;
            
            // Если это не последняя попытка, ждем перед следующей
            if ($attempt < $this->max_retries) {
                // Сохраняем в очередь для повторной попытки
                $this->save_to_failed_queue($data, $result);
                
                // Логируем попытку
                $logger->warning('Попытка отправки не удалась', array(
                    'attempt' => $attempt,
                    'max_retries' => $this->max_retries,
                    'retry_delay' => $this->retry_delay,
                    'error_message' => $result['message'],
                    'response_code' => $result['response_code'] ?? 0
                ));
                
                // В cron задачах не ждем, просто планируем следующую попытку
                if (defined('DOING_CRON') && DOING_CRON) {
                    // Планируем повторную попытку через заданное время
                    wp_schedule_single_event(time() + $this->retry_delay, 'my_monitoring_retry_send');
                    $logger->debug('Запланирована повторная попытка через cron', array(
                        'delay_seconds' => $this->retry_delay
                    ));
                    break;
                } else {
                    // В синхронных запросах ждем
                    $sleep_time = min($this->retry_delay, 5); // Максимум 5 секунд в синхронном режиме
                    sleep($sleep_time);
                }
            }
        }
        
        // Все попытки исчерпаны
        $execution_time = round((microtime(true) - $start_time) * 1000, 2);
        $this->save_to_failed_queue($data, $last_error);
        $logger->error('Все попытки отправки исчерпаны', array(
            'total_attempts' => $attempt,
            'execution_time_ms' => $execution_time,
            'final_error' => $last_error['message'] ?? 'Неизвестная ошибка',
            'response_code' => $last_error['response_code'] ?? 0
        ));
        
        return $last_error;
    }
    
    /**
     * Попытка отправки данных
     * 
     * @param array $data Данные для отправки
     * @return array Результат отправки
     */
    private function attempt_send($data) {
        $logger = My_Monitoring_Logger::get_instance();
        $result = array(
            'success' => false,
            'message' => '',
            'response_code' => 0,
        );
        
        // Проверяем наличие cURL
        if (!function_exists('curl_init')) {
            $error_msg = __('cURL не доступен на этом сервере', 'my-monitoring-plugin');
            $logger->error($error_msg);
            $result['message'] = $error_msg;
            return $result;
        }
        
        // Подготавливаем данные для отправки в зависимости от типа API
        if ($this->api_type === 'jsonbin') {
            // Для JSONBin отправляем только data (без api_key в теле)
            $payload = array(
                'data' => $data,
            );
        } else {
            // Для стандартного API отправляем api_key и data
            $payload = array(
                'api_key' => $this->api_key,
                'data' => $data,
            );
        }
        
        $json_data = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        
        if ($json_data === false) {
            $error_msg = __('Ошибка кодирования данных в JSON', 'my-monitoring-plugin') . ': ' . json_last_error_msg();
            $logger->error($error_msg, array('json_error' => json_last_error()));
            $result['message'] = $error_msg;
            return $result;
        }
        
        // Сжимаем данные, если они достаточно большие
        $compression_result = My_Monitoring_Data_Compressor::compress_if_needed($json_data);
        $json_data = $compression_result['data'];
        $is_compressed = $compression_result['compressed'];
        
        $logger->debug('Подготовка HTTP запроса', array(
            'url' => $this->api_url,
            'original_size' => $compression_result['original_size'],
            'payload_size' => strlen($json_data),
            'compressed' => $is_compressed,
            'compression_ratio' => $compression_result['compression_ratio']
        ));
        
        // Выполняем запрос
        try {
            $response = $this->make_request($json_data, $is_compressed);
            
            // Для JSONBin логируем дополнительную информацию
            if ($this->api_type === 'jsonbin') {
                $logger->debug('Отправка в JSONBin', array(
                    'api_type' => 'jsonbin',
                    'url' => $this->api_url
                ));
            }
            
            if (is_wp_error($response)) {
                $error_code = $response->get_error_code();
                $error_message = $response->get_error_message();
                $logger->error('Ошибка HTTP запроса', array(
                    'error_code' => $error_code,
                    'error_message' => $error_message
                ));
                $result['message'] = $error_message;
                return $result;
            }
            
            $response_code = wp_remote_retrieve_response_code($response);
            $response_body = wp_remote_retrieve_body($response);
            
            $result['response_code'] = $response_code;
            
            // Проверяем код ответа
            if ($response_code >= 200 && $response_code < 300) {
                $result['success'] = true;
                $result['message'] = __('Данные успешно отправлены', 'my-monitoring-plugin');
                
                // Пытаемся декодировать ответ
                $decoded = json_decode($response_body, true);
                if ($decoded !== null) {
                    $result['response'] = $decoded;
                }
                
                $logger->debug('Успешный ответ от API', array(
                    'response_code' => $response_code,
                    'response_size' => strlen($response_body)
                ));
            } else {
                $result['message'] = sprintf(
                    __('Ошибка отправки данных. Код ответа: %d', 'my-monitoring-plugin'),
                    $response_code
                );
                
                // Пытаемся получить сообщение об ошибке из ответа
                $decoded = json_decode($response_body, true);
                if ($decoded !== null && isset($decoded['message'])) {
                    $result['message'] = $decoded['message'];
                }
                
                $logger->warning('Ошибка ответа от API', array(
                    'response_code' => $response_code,
                    'response_body' => substr($response_body, 0, 500) // Первые 500 символов
                ));
            }
        } catch (Exception $e) {
            $logger->critical('Исключение при отправке данных', array(
                'exception' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ));
            $result['message'] = __('Критическая ошибка при отправке: ', 'my-monitoring-plugin') . $e->getMessage();
        }
        
        return $result;
    }
    
    /**
     * Сохранить данные в очередь неудачных отправок
     * 
     * @param array $data Данные
     * @param array $error Информация об ошибке
     */
    private function save_to_failed_queue($data, $error) {
        $queue = get_option('my_monitoring_failed_queue', array());
        
        // Ограничиваем размер очереди
        if (count($queue) >= MY_MONITORING_MAX_QUEUE_SIZE) {
            array_shift($queue);
        }
        
        $queue[] = array(
            'data' => $data,
            'error' => $error,
            'timestamp' => current_time('mysql'),
            'attempts' => 1
        );
        
        update_option('my_monitoring_failed_queue', $queue);
    }
    
    /**
     * Очистить очередь неудачных отправок
     */
    private function clear_failed_queue() {
        delete_option('my_monitoring_failed_queue');
    }
    
    /**
     * Выполнить HTTP запрос
     * 
     * @param string $json_data JSON данные для отправки
     * @param bool $is_compressed Флаг сжатия данных
     * @return array|WP_Error Ответ сервера или ошибка
     */
    private function make_request($json_data, $is_compressed = false) {
        // Определяем метод и заголовки в зависимости от типа API
        if ($this->api_type === 'jsonbin') {
            // Для JSONBin используем PUT для обновления bin или POST для создания в коллекции
            $method = 'PUT';
            // Если URL содержит /c/{id}/b, это создание нового bin в коллекции - используем POST
            if (preg_match('#/c/[^/]+/b/?$#', $this->api_url)) {
                $method = 'POST';
            }
            
            $headers = array(
                'Content-Type' => 'application/json',
                'X-Master-Key' => $this->api_key,
                'User-Agent' => 'WordPress/' . get_bloginfo('version') . '; ' . home_url(),
            );
            
            // JSONBin не поддерживает сжатие через Content-Encoding
            // Но можно оставить сжатие, если оно было применено
            if ($is_compressed) {
                // Для JSONBin лучше не использовать сжатие, так как это может вызвать проблемы
                // Но если данные уже сжаты, отправляем как есть
                $headers['Content-Encoding'] = 'gzip';
            }
        } else {
            // Стандартный API
            $method = 'POST';
            $headers = array(
                'Content-Type' => 'application/json' . ($is_compressed ? '; charset=utf-8' : ''),
                'Content-Encoding' => $is_compressed ? 'gzip' : '',
                'User-Agent' => 'WordPress/' . get_bloginfo('version') . '; ' . home_url(),
            );
        }
        
        $headers['Content-Length'] = strlen($json_data);
        
        $args = array(
            'method' => $method,
            'timeout' => $this->timeout,
            'redirection' => 5,
            'httpversion' => '1.1',
            'blocking' => true,
            'headers' => $headers,
            'body' => $json_data,
            'cookies' => array(),
            'sslverify' => true, // В продакшене должно быть true
        );
        
        // Если API URL использует HTTPS, проверяем сертификат
        if (strpos($this->api_url, 'https://') === 0) {
            $args['sslverify'] = true;
        }
        
        $response = wp_remote_request($this->api_url, $args);
        
        return $response;
    }
    
    /**
     * Проверить доступность API
     * 
     * @return array Результат проверки
     */
    public function test_connection() {
        $test_data = array(
            'test' => true,
            'timestamp' => current_time('mysql'),
        );
        
        return $this->send($test_data);
    }
}