<?php
/**
* Simple File Manager (Full Filesystem Access)
* Aplikasi manajemen file berbasis web untuk administrasi server.
*
* @package SimpleFileManager
* @author Administrator
* @version 2.1.0
* @license MIT
*/
declare(strict_types=1);
session_start();
// ================== APPLICATION CONFIG ==================
define('APP_NAME', 'Simple File Manager');
define('APP_VERSION', '2.1.0');
$config = [
'password' => 'admin123', // GANTI dengan password kuat!
'max_upload_size' => 30 * 1024 * 1024, // 30 MB
'max_edit_size' => 2 * 1024 * 1024, // 2 MB
];
// Ekstensi yang tidak diizinkan untuk upload
$disallowedUploadExtensions = [
'php3','php4','php5','php7','php8','phar','pht','phps',
'cgi','pl','py','asp','aspx','jsp','sh','bash',
'exe','bat','cmd','com'
];
// Ekstensi yang dapat diedit sebagai teks
$textFileExtensions = [
'php','txt','html','htm','css','js','json','xml','md','log',
'ini','conf','sql','csv','env','yml','yaml','htaccess'
];
// Ekstensi gambar
$imageExtensions = [
'jpg','jpeg','png','gif','webp','bmp','svg','ico'
];
// ================== ERROR HANDLING ==================
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
// ================== SECURITY HEADERS ==================
if (!headers_sent()) {
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: same-origin');
}
// ================== UTILITY FUNCTIONS ==================
/**
* Memeriksa apakah pengguna sudah login.
*/
function isUserLoggedIn(): bool
{
return isset($_SESSION['fm_auth']) && $_SESSION['fm_auth'] === true;
}
/**
* Memverifikasi password pengguna.
*/
function verifyPassword(string $inputPassword): bool
{
global $config;
return hash_equals((string) $config['password'], $inputPassword);
}
/**
* Mendapatkan CSRF token.
*/
function getCsrfToken(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
/**
* Memverifikasi CSRF token.
*/
function verifyCsrfToken(?string $token): bool
{
if (empty($_SESSION['csrf_token']) || empty($token)) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
/**
* Menyelesaikan path absolut dari input pengguna.
* Mendukung akses ke seluruh filesystem root.
*/
function resolveSafePath(string $userPath = ''): string
{
// Normalisasi separator & hapus null byte
$userPath = str_replace(["\0", '\\'], ['', '/'], $userPath);
$userPath = preg_replace('#/+#', '/', $userPath);
$userPath = trim($userPath, '/');
// Path kosong = default ke direktori script
if ($userPath === '') {
$default = realpath(__DIR__);
return $default !== false ? $default : __DIR__;
}
// Coba resolve path absolut dari root filesystem
$target = '/' . $userPath;
$real = @realpath($target);
if ($real !== false) {
return $real;
}
// Fallback: cari parent terdekat yang valid
$parts = explode('/', $userPath);
while (!empty($parts)) {
array_pop($parts);
$try = empty($parts) ? '/' : '/' . implode('/', $parts);
$real = @realpath($try);
if ($real !== false) {
return $real;
}
}
return DIRECTORY_SEPARATOR;
}
/**
* Mendapatkan path relatif terhadap root filesystem.
*/
function getRelativePath(string $absolutePath): string
{
$normalized = str_replace('\\', '/', $absolutePath);
return $normalized === '/' ? '' : trim($normalized, '/');
}
/**
* Format ukuran bytes menjadi string mudah dibaca.
*/
function humanReadableSize(int $bytes): string
{
if ($bytes >= 1073741824) {
return round($bytes / 1073741824, 2) . ' GB';
}
if ($bytes >= 1048576) {
return round($bytes / 1048576, 2) . ' MB';
}
if ($bytes >= 1024) {
return round($bytes / 1024, 2) . ' KB';
}
return $bytes . ' B';
}
/**
* Mendapatkan ekstensi file huruf kecil.
*/
function getFileExtension(string $filename): string
{
return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
}
/**
* Memeriksa apakah file diizinkan untuk diupload.
*/
function isUploadAllowed(string $filename): bool
{
global $disallowedUploadExtensions;
return !in_array(getFileExtension($filename), $disallowedUploadExtensions, true);
}
/**
* Memeriksa apakah file dapat diedit sebagai teks.
*/
function isTextEditable(string $filename): bool
{
global $textFileExtensions;
return in_array(getFileExtension($filename), $textFileExtensions, true);
}
/**
* Memeriksa apakah file adalah gambar.
*/
function isImageFile(string $filename): bool
{
global $imageExtensions;
return in_array(getFileExtension($filename), $imageExtensions, true);
}
/**
* Menghapus direktori beserta isinya secara aman.
*/
function removeDirectoryTree(string $path): bool
{
if (!file_exists($path)) {
return true;
}
if (!is_dir($path)) {
return @unlink($path);
}
$entries = @scandir($path);
if ($entries === false) {
return false;
}
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$childPath = $path . DIRECTORY_SEPARATOR . $entry;
if (is_dir($childPath)) {
if (!removeDirectoryTree($childPath)) {
return false;
}
} else {
if (!@unlink($childPath)) {
return false;
}
}
}
return @rmdir($path);
}
// ================== AUTHENTICATION ==================
if (isset($_POST['do_login'])) {
if (!verifyCsrfToken($_POST['csrf_token'] ?? null)) {
$loginError = true;
} elseif (verifyPassword((string) ($_POST['password'] ?? ''))) {
session_regenerate_id(true);
$_SESSION['fm_auth'] = true;
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
} else {
$loginError = true;
}
}
if (isset($_GET['logout'])) {
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params['path'], $params['domain'], $params['secure'], $params['httponly']
);
}
session_destroy();
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
if (!isUserLoggedIn()) {
$token = getCsrfToken();
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Login - <?= APP_NAME ?></title>
<style>
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0f172a;font-family:system-ui,sans-serif}
.box{background:#1e293b;padding:2rem;border-radius:12px;width:320px;box-shadow:0 10px 30px rgba(0,0,0,.4)}
h1{color:#f1f5f9;font-size:1.25rem;margin:0 0 1.5rem;text-align:center}
input{width:100%;padding:12px;border:1px solid #334155;border-radius:8px;background:#0f172a;color:#f1f5f9;margin-bottom:1rem;box-sizing:border-box}
button{width:100%;padding:12px;background:#3b82f6;color:white;border:none;border-radius:8px;font-size:1rem;cursor:pointer}
button:hover{background:#2563eb}
.err{color:#f87171;text-align:center;margin-bottom:1rem;font-size:.9rem}
</style>
</head>
<body>
<div class="box">
<h1><?= APP_NAME ?></h1>
<?php if (!empty($loginError)): ?><div class="err">Password salah</div><?php endif; ?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($token) ?>">
<input type="password" name="password" placeholder="Password" required autofocus>
<button type="submit" name="do_login">Masuk</button>
</form>
</div>
</body>
</html>
<?php
exit;
}
// ================== MAIN APPLICATION ==================
$currentPath = resolveSafePath((string) ($_GET['p'] ?? ''));
$relativePath = getRelativePath($currentPath);
$message = '';
$error = '';
$csrfToken = getCsrfToken();
// ===================== VIEW FILE =====================
if (isset($_GET['view'])) {
$fileName = basename((string) $_GET['view']);
$filePath = rtrim($currentPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $fileName;
$realPath = @realpath($filePath);
if (!$realPath || !is_file($realPath) || !is_readable($realPath)) {
$error = 'File tidak ditemukan atau tidak bisa dibaca';
} else {
if (isImageFile($fileName)) {
$mimeType = 'application/octet-stream';
if (function_exists('mime_content_type')) {
$detected = @mime_content_type($realPath);
if ($detected !== false) {
$mimeType = $detected;
}
}
header('Content-Type: ' . $mimeType);
header('Content-Length: ' . filesize($realPath));
header('Cache-Control: private, max-age=3600');
readfile($realPath);
exit;
}
$fileSize = filesize($realPath);
if (isTextEditable($fileName) || $fileSize <= $config['max_edit_size']) {
$content = @file_get_contents($realPath);
if ($content === false) {
$error = 'Gagal membaca isi file';
} else {
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>View - <?= htmlspecialchars($fileName) ?></title>
<style>
body{margin:0;background:#0f172a;color:#e2e8f0;font-family:system-ui,sans-serif}
header{background:#1e293b;padding:12px 20px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px}
header a{color:#94a3b8;text-decoration:none}
.wrap{max-width:1200px;margin:20px auto;padding:0 16px}
pre{background:#1e293b;padding:20px;border-radius:10px;overflow:auto;font-size:13.5px;line-height:1.55;white-space:pre-wrap;word-break:break-word}
.info{color:#94a3b8;font-size:.9rem}
</style>
</head>
<body>
<header>
<div>
<strong><?= htmlspecialchars($fileName) ?></strong>
<span class="info"> â <?= humanReadableSize((int) $fileSize) ?></span>
</div>
<div>
<?php if (isTextEditable($fileName)): ?>
<a href="?p=<?= urlencode($relativePath) ?>&edit=<?= urlencode($fileName) ?>" style="color:#3b82f6;margin-right:16px">Edit</a>
<?php endif; ?>
<a href="?p=<?= urlencode($relativePath) ?>">â Kembali</a>
</div>
</header>
<div class="wrap">
<pre><?= htmlspecialchars($content) ?></pre>
</div>
</body>
</html>
<?php
exit;
}
} else {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"');
header('Content-Length: ' . $fileSize);
readfile($realPath);
exit;
}
}
}
// ===================== EDIT FILE =====================
if (isset($_GET['edit'])) {
$fileName = basename((string) $_GET['edit']);
$filePath = rtrim($currentPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $fileName;
$realPath = @realpath($filePath);
if (!$realPath || !is_file($realPath) || !is_readable($realPath)) {
$error = 'File tidak ditemukan atau tidak bisa dibaca';
} elseif (!isTextEditable($fileName)) {
$error = 'File ini tidak bisa diedit (hanya file teks yang diizinkan)';
} elseif (filesize($realPath) > $config['max_edit_size']) {
$error = 'File terlalu besar untuk diedit (maks ' . humanReadableSize((int) $config['max_edit_size']) . ')';
} else {
if (isset($_POST['save_content'])) {
if (!verifyCsrfToken($_POST['csrf_token'] ?? null)) {
$error = 'Token keamanan tidak valid. Silakan coba lagi.';
} else {
$newContent = (string) ($_POST['content'] ?? '');
if (@file_put_contents($realPath, $newContent) !== false) {
$message = 'File berhasil disimpan';
} else {
$error = 'Gagal menyimpan file (cek permission folder)';
}
}
}
$content = @file_get_contents($realPath);
if ($content === false) {
$content = '';
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Edit - <?= htmlspecialchars($fileName) ?></title>
<style>
body{margin:0;background:#0f172a;color:#e2e8f0;font-family:system-ui,sans-serif}
header{background:#1e293b;padding:12px 20px;display:flex;justify-content:space-between;align-items:center}
header a{color:#94a3b8;text-decoration:none}
.wrap{max-width:1200px;margin:20px auto;padding:0 16px}
textarea{width:100%;height:70vh;background:#1e293b;color:#e2e8f0;border:1px solid #334155;border-radius:10px;padding:16px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:14px;line-height:1.5;resize:vertical;box-sizing:border-box}
.btn{background:#3b82f6;color:#fff;border:none;padding:10px 22px;border-radius:8px;cursor:pointer;font-size:1rem}
.btn:hover{background:#2563eb}
.alert{padding:12px 16px;border-radius:8px;margin-bottom:16px}
.ok{background:#166534;color:#dcfce7}
.bad{background:#991b1b;color:#fee2e2}
</style>
</head>
<body>
<header>
<strong>Edit: <?= htmlspecialchars($fileName) ?></strong>
<a href="?p=<?= urlencode($relativePath) ?>">â Kembali</a>
</header>
<div class="wrap">
<?php if ($message): ?><div class="alert ok"><?= htmlspecialchars($message) ?></div><?php endif; ?>
<?php if ($error): ?><div class="alert bad"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<textarea name="content" spellcheck="false"><?= htmlspecialchars($content) ?></textarea>
<div style="margin-top:16px;display:flex;gap:12px;align-items:center">
<button type="submit" name="save_content" class="btn">Simpan Perubahan</button>
<a href="?p=<?= urlencode($relativePath) ?>&view=<?= urlencode($fileName) ?>" style="color:#94a3b8">Lihat saja</a>
</div>
</form>
</div>
</body>
</html>
<?php
exit;
}
}
// ===================== CREATE FOLDER =====================
if (isset($_POST['create_folder'])) {
if (!verifyCsrfToken($_POST['csrf_token'] ?? null)) {
$error = 'Token keamanan tidak valid. Silakan coba lagi.';
} else {
$folderName = basename(trim((string) ($_POST['folder_name'] ?? '')));
if ($folderName === '' || $folderName === '.' || $folderName === '..') {
$error = 'Nama folder tidak valid';
} else {
$targetPath = rtrim($currentPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $folderName;
if (file_exists($targetPath)) {
$error = 'Folder/file dengan nama tersebut sudah ada';
} elseif (@mkdir($targetPath, 0755)) {
$message = 'Folder berhasil dibuat';
} else {
$error = 'Gagal membuat folder (permission?)';
}
}
}
}
// ===================== UPLOAD =====================
if (isset($_FILES['upload']) && $_FILES['upload']['error'] === UPLOAD_ERR_OK) {
if (!verifyCsrfToken($_POST['csrf_token'] ?? null)) {
$error = 'Token keamanan tidak valid. Silakan coba lagi.';
} else {
$uploadName = basename((string) $_FILES['upload']['name']);
if (!isUploadAllowed($uploadName)) {
$error = 'Ekstensi file dilarang diupload';
} elseif ($_FILES['upload']['size'] > $config['max_upload_size']) {
$error = 'File terlalu besar (maksimal ' . humanReadableSize((int) $config['max_upload_size']) . ')';
} else {
$targetPath = rtrim($currentPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $uploadName;
if (@move_uploaded_file($_FILES['upload']['tmp_name'], $targetPath)) {
$message = 'Upload berhasil';
} else {
$error = 'Gagal upload (permission folder?)';
}
}
}
}
// ===================== DELETE =====================
if (isset($_GET['del'])) {
if (!verifyCsrfToken($_GET['csrf_token'] ?? null)) {
$error = 'Token keamanan tidak valid. Silakan coba lagi.';
} else {
$deleteName = basename((string) $_GET['del']);
$deletePath = rtrim($currentPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $deleteName;
$realDeletePath = @realpath($deletePath);
// Proteksi minimal: hanya cegah hapus root "/" itu sendiri
if (!$realDeletePath || $realDeletePath === DIRECTORY_SEPARATOR) {
$error = 'Tidak diizinkan menghapus path ini';
} else {
if (is_dir($realDeletePath)) {
if (removeDirectoryTree($realDeletePath)) {
$message = 'Folder dan seluruh isinya berhasil dihapus';
} else {
$error = 'Gagal menghapus folder (permission?)';
}
} else {
if (@unlink($realDeletePath)) {
$message = 'File berhasil dihapus';
} else {
$error = 'Gagal menghapus file';
}
}
}
}
}
// ===================== RENAME =====================
if (isset($_POST['rename'])) {
if (!verifyCsrfToken($_POST['csrf_token'] ?? null)) {
$error = 'Token keamanan tidak valid. Silakan coba lagi.';
} else {
$oldName = basename((string) ($_POST['old'] ?? ''));
$newName = basename(trim((string) ($_POST['new'] ?? '')));
if ($newName === '' || $newName === '.' || $newName === '..') {
$error = 'Nama baru tidak valid';
} else {
$oldPath = rtrim($currentPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $oldName;
$newPath = rtrim($currentPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $newName;
if (!file_exists($oldPath)) {
$error = 'File/folder asal tidak ditemukan';
} elseif (file_exists($newPath)) {
$error = 'Nama baru sudah digunakan';
} elseif (@rename($oldPath, $newPath)) {
$message = 'Berhasil diubah nama';
} else {
$error = 'Gagal rename (permission?)';
}
}
}
}
// ===================== LIST FILES =====================
$files = [];
if (is_readable($currentPath)) {
$entries = @scandir($currentPath);
if ($entries !== false) {
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$fullPath = rtrim($currentPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $entry;
$files[] = [
'name' => $entry,
'dir' => is_dir($fullPath),
'size' => is_file($fullPath) ? (int) @filesize($fullPath) : 0,
'time' => (int) (@filemtime($fullPath) ?: time()),
];
}
usort($files, static function (array $a, array $b): int {
if ($a['dir'] === $b['dir']) {
return strcasecmp($a['name'], $b['name']);
}
return $b['dir'] <=> $a['dir'];
});
} else {
$error = 'Tidak bisa membaca directory (permission denied)';
}
} else {
$error = 'Directory tidak bisa dibaca';
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= APP_NAME ?></title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#f1f5f9;font-family:system-ui,-apple-system,sans-serif;color:#1e293b}
header{background:#0f172a;color:#f8fafc;padding:14px 20px;display:flex;justify-content:space-between;align-items:center}
header h1{margin:0;font-size:1.1rem}
header a{color:#94a3b8;text-decoration:none;font-size:.9rem}
.wrap{max-width:1100px;margin:20px auto;padding:0 16px}
.path{background:#fff;padding:12px 16px;border-radius:10px;margin-bottom:16px;font-size:.9rem;box-shadow:0 1px 3px rgba(0,0,0,.06);word-break:break-all}
.path a{color:#3b82f6;text-decoration:none}
.alert{padding:12px 16px;border-radius:8px;margin-bottom:16px;font-size:.9rem}
.ok{background:#dcfce7;color:#166534}
.bad{background:#fee2e2;color:#991b1b}
.tools{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px}
.card{background:#fff;padding:14px;border-radius:10px;box-shadow:0 1px 3px rgba(0,0,0,.06);flex:1;min-width:240px}
input[type=text],input[type=file]{padding:8px 12px;border:1px solid #cbd5e1;border-radius:6px;width:100%;margin-bottom:8px}
button{background:#3b82f6;color:#fff;border:none;padding:8px 14px;border-radius:6px;cursor:pointer;font-size:.875rem}
button:hover{background:#2563eb}
table{width:100%;border-collapse:collapse;background:#fff;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.06)}
th,td{padding:11px 14px;text-align:left;border-bottom:1px solid #e2e8f0;font-size:.9rem}
th{background:#f8fafc;color:#64748b;font-weight:600}
tr:hover{background:#f8fafc}
.name a{color:#1e293b;text-decoration:none;font-weight:500}
.folder{color:#d97706}
.act a{color:#3b82f6;text-decoration:none;margin-right:10px;font-size:.85rem}
.act a.del{color:#ef4444}
.act a.view{color:#059669}
.act a.edit{color:#7c3aed}
.rename-box{display:none;margin-top:6px}
.rename-box input{width:180px;display:inline-block;margin-right:6px}
@media(max-width:640px){.size,.time{display:none}}
</style>
</head>
<body>
<header>
<h1><?= APP_NAME ?> <small style="color:#64748b;font-weight:400;font-size:.8rem">v<?= APP_VERSION ?></small></h1>
<a href="?logout=1">Keluar</a>
</header>
<div class="wrap">
<div class="path">
<a href="?p=">/</a>
<?php
if ($relativePath !== '') {
$parts = explode('/', $relativePath);
$build = '';
foreach ($parts as $part) {
$build .= ($build === '' ? '' : '/') . $part;
echo ' / <a href="?p=' . urlencode($build) . '">' . htmlspecialchars($part) . '</a>';
}
}
?>
</div>
<?php if ($message): ?><div class="alert ok"><?= htmlspecialchars($message) ?></div><?php endif; ?>
<?php if ($error): ?><div class="alert bad"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<div class="tools">
<div class="card">
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<input type="file" name="upload" required>
<button type="submit">Upload</button>
</form>
</div>
<div class="card">
<form method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<input type="text" name="folder_name" placeholder="Nama folder baru" required>
<button type="submit" name="create_folder">Buat Folder</button>
</form>
</div>
</div>
<table>
<thead>
<tr>
<th>Nama</th>
<th class="size">Ukuran</th>
<th class="time">Tanggal</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<?php if ($relativePath !== ''): ?>
<tr>
<td class="name">
<?php
$parent = dirname($relativePath);
$parentParam = ($parent === '.' || $parent === DIRECTORY_SEPARATOR) ? '' : $parent;
?>
<a href="?p=<?= urlencode($parentParam) ?>">..</a>
</td>
<td class="size">-</td>
<td class="time">-</td>
<td></td>
</tr>
<?php endif; ?>
<?php foreach ($files as $file): ?>
<tr>
<td class="name">
<?php if ($file['dir']): ?>
<a href="?p=<?= urlencode(($relativePath ? $relativePath . '/' : '') . $file['name']) ?>" class="folder">
đ <?= htmlspecialchars($file['name']) ?>
</a>
<?php else: ?>
<?= htmlspecialchars($file['name']) ?>
<?php endif; ?>
</td>
<td class="size"><?= $file['dir'] ? '-' : humanReadableSize($file['size']) ?></td>
<td class="time"><?= date('d M Y H:i', $file['time']) ?></td>
<td class="act">
<?php if (!$file['dir']): ?>
<a href="?p=<?= urlencode($relativePath) ?>&view=<?= urlencode($file['name']) ?>" class="view">View</a>
<?php if (isTextEditable($file['name'])): ?>
<a href="?p=<?= urlencode($relativePath) ?>&edit=<?= urlencode($file['name']) ?>" class="edit">Edit</a>
<?php endif; ?>
<?php endif; ?>
<a href="javascript:void(0)" onclick="toggleRename(this)">Rename</a>
<a href="?p=<?= urlencode($relativePath) ?>&del=<?= urlencode($file['name']) ?>&csrf_token=<?= urlencode($csrfToken) ?>"
class="del"
onclick="return confirm('Hapus <?= htmlspecialchars(addslashes($file['name']), ENT_QUOTES) ?>?\n\nJika folder, SEMUA isinya juga akan ikut terhapus!')">Hapus</a>
<div class="rename-box">
<form method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<input type="hidden" name="old" value="<?= htmlspecialchars($file['name']) ?>">
<input type="text" name="new" value="<?= htmlspecialchars($file['name']) ?>" required>
<button type="submit" name="rename">OK</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($files) && empty($error)): ?>
<tr><td colspan="4" style="text-align:center;color:#94a3b8;padding:30px">Folder kosong</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<script>
function toggleRename(el) {
var box = el.parentNode.querySelector('.rename-box');
var allBoxes = document.querySelectorAll('.rename-box');
for (var i = 0; i < allBoxes.length; i++) {
if (allBoxes[i] !== box) {
allBoxes[i].style.display = 'none';
}
}
box.style.display = box.style.display === 'block' ? 'none' : 'block';
}
</script>
</body>
</html>
Size: 27.85 KBType: text/x-phpLines: 751