Secure
index.php
← Back to Folder Raw Code
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315
<?php
require_once __DIR__ . '/.indexer_files/php/URLRouter.php';
require_once __DIR__ . '/.indexer_files/php/Database.php';
require_once __DIR__ . '/.indexer_files/php/Cache.php';
require_once __DIR__ . '/.indexer_files/php/Markdown.php';
require_once __DIR__ . '/.indexer_files/php/CodeHighlight.php';
require_once __DIR__ . '/.indexer_files/php/Img.php';
require_once __DIR__ . '/.indexer_files/php/Audio.php';
$scriptDir = dirname(__FILE__);
$baseDir = $scriptDir . '/files';
$documentRoot = $_SERVER['DOCUMENT_ROOT'];
$scriptPath = str_replace($documentRoot, '', $scriptDir);
$webPath = rtrim($scriptPath, '/');
if ($webPath === '' || $webPath === '/') {
    $webPath = '';
} else {
    $webPath = '/' . ltrim($webPath, '/');
}
$indexerFilesDir = $scriptDir . '/.indexer_files';
$iconsDir = $indexerFilesDir . '/icons';
$localApiDir = $indexerFilesDir . '/local_api';
$localStyleDir = $localApiDir . '/style';
$configFile = $indexerFilesDir . '/config.json';
$localExtensionMapFile = $localApiDir . '/extensionMap.json';
$localIconsFile = $localApiDir . '/icons.json';
$router = new URLRouter($scriptDir, $webPath, $baseDir);
$currentPath = $router->parseCleanURL();
$currentPath = ltrim($currentPath, '/');
$currentPath = str_replace(['../', './'], '', $currentPath);
$fullPath = $baseDir . '/' . $currentPath;
$webCurrentPath = '/files' . ($currentPath ? '/' . $currentPath : '');
$config = loadConfiguration();
$disableFileDownloads = isset($config['main']['disable_file_downloads']) ? $config['main']['disable_file_downloads'] : false;
$disableFolderDownloads = isset($config['main']['disable_folder_downloads']) ? $config['main']['disable_folder_downloads'] : false;
$iconType = isset($config['main']['icon_type']) ? $config['main']['icon_type'] : 'default';
$denyList = parseDenyAllowList(isset($config['main']['deny_list']) ? $config['main']['deny_list'] : '');
$allowList = parseDenyAllowList(isset($config['main']['allow_list']) ? $config['main']['allow_list'] : '');
$conflictingRules = findConflictingRules($denyList, $allowList);
$cacheInstance = initializeCache();
if (!empty($currentPath)) {
    if ($router->isFileRequest($currentPath)) {
        $router->handleFileRequest($currentPath);
        exit;
    }
    if ($router->isFolderRequest($currentPath)) {
        if (isset($_GET['download']) && $_GET['download'] === 'archive') {
            $router->handleFolderRequest($currentPath);
            exit;
        }
        $router->handleFolderRequest($currentPath);
    } else {
        http_response_code(404);
        exit('Path not found');
    }
}
function getSecurityStatus() {
    global $config;
    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') 
                || $_SERVER['SERVER_PORT'] == 443
                || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
    $hostname = '';
    if (isset($config['main']['access_url']) && !empty($config['main']['access_url'])) {
        $parsedUrl = parse_url($config['main']['access_url']);
        $hostname = $parsedUrl['host'] ?? $_SERVER['HTTP_HOST'];
    } else {
        $hostname = $_SERVER['HTTP_HOST'];
    }
    return [
        'secure' => $isHttps,
        'hostname' => $hostname
    ];
}
function getVersionInfo() {
    global $config, $indexerFilesDir;
    static $versionInfo = null;
    if ($versionInfo !== null) {
        return $versionInfo;
    }
    $currentVersion = $config['version'] ?? null;
    $cache = initializeCache();
    $cachedVersionData = $cache->getVersionInfo();
    if ($cachedVersionData !== null) {
        $versionInfo = [
            'current' => $currentVersion,
            'remote' => $cachedVersionData['remote_version'],
            'type' => $cachedVersionData['installation_type']
        ];
        return $versionInfo;
    }
    // CRITICAL: Don't check on every request - only return cached or nothing
    // Let a cron job or manual check update this instead
    $installationType = file_exists($indexerFilesDir . '/.docker') ? 'docker' :
                        (file_exists($indexerFilesDir . '/.script') ? 'script' : 'manual');
    $versionInfo = [
        'current' => $currentVersion,
        'remote' => null,
        'type' => $installationType
    ];
    return $versionInfo;
}
function generateUpdateNotice() {
    $info = getVersionInfo();
    if (!$info['remote'] || !version_compare($info['current'], $info['remote'], '<')) {
        return '';
    }
    $instructions = [
        'docker' => 'Update using Docker: Pull the latest image and restart your container.',
        'script' => 'Update using the installation script: Run "5q12-index update".',
        'manual' => 'Manual installation: Download and replace files manually from the repository.'
    ];
    $changelogUrl = 'https://ccls.icu/src/repositories/5q12-indexer/releases/latest/changelog.md/?view=default';
    return '
    <div class="update-notice">
        <div class="update-notice-content">
            <strong>Update Available</strong><br>
            Current: v' . htmlspecialchars($info['current']) . ' | Latest: v' . htmlspecialchars($info['remote']) . '<br>
            <small>' . htmlspecialchars($instructions[$info['type']]) . '</small><br>
            <a href="' . htmlspecialchars($changelogUrl) . '" target="_blank" class="changelog-link">View Changelog</a>
        </div>
    </div>';
}
function parseDenyAllowList($listString) {
    if (empty(trim($listString))) {
        return [];
    }
    $items = array_map('trim', explode(',', $listString));
    $parsedList = [];
    foreach ($items as $item) {
        if (empty($item)) continue;
        $rule = [
            'original' => $item,
            'path' => '',
            'type' => 'exact',
            'target' => 'both'
        ];
        if (substr($item, -2) === '/*') {
            $rule['type'] = 'folder_recursive';
            $rule['path'] = substr($item, 0, -2);
            $rule['target'] = 'folder';
        }
        elseif (substr($item, -1) === '*' && substr($item, -2) !== '/*') {
            if (strpos($item, '.') !== false && strrpos($item, '.') > strrpos($item, '/')) {
                $rule['type'] = 'wildcard';
                $rule['path'] = $item;
                $rule['target'] = 'file';
            } else {
                $rule['type'] = 'wildcard';
                $rule['path'] = substr($item, 0, -1);
                $rule['target'] = 'folder';
            }
        }
        elseif (strpos($item, '*') !== false) {
            $rule['type'] = 'wildcard';
            $rule['path'] = $item;
            $rule['target'] = 'file';
        }
        else {
            $rule['type'] = 'exact';
            $rule['path'] = $item;
            if (strpos(basename($item), '.') !== false) {
                $rule['target'] = 'file';
            } else {
                $rule['target'] = 'folder';
            }
        }
        $parsedList[] = $rule;
    }
    return $parsedList;
}
function findConflictingRules($denyList, $allowList) {
    $conflicts = [];
    foreach ($denyList as $denyRule) {
        foreach ($allowList as $allowRule) {
            if ($denyRule['path'] === $allowRule['path'] && 
                $denyRule['type'] === $allowRule['type']) {
                $conflicts[] = [
                    'deny' => $denyRule['original'],
                    'allow' => $allowRule['original']
                ];
            }
        }
    }
    return $conflicts;
}
function pathMatchesRule($relativePath, $rule, $isFolder = false) {
    $rulePath = $rule['path'];
    if ($rule['type'] === 'exact') {
        return $relativePath === $rulePath;
    }
    if ($rule['type'] === 'wildcard') {
        if ($rule['target'] === 'file' && !$isFolder) {
            if (strpos($rulePath, '/') !== false) {
                $lastSlashPos = strrpos($rulePath, '/');
                $directory = substr($rulePath, 0, $lastSlashPos);
                $pattern = substr($rulePath, $lastSlashPos + 1);
                $fileDir = dirname($relativePath);
                $fileName = basename($relativePath);
                $directory = rtrim($directory, '/');
                $fileDir = rtrim($fileDir, '/');
                if ($fileDir === $directory) {
                    if (strpos($pattern, '.') === 0) {
                        $extension = substr($pattern, 1, -1);
                        $fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
                        return $fileExtension === $extension;
                    } else {
                        $regexPattern = str_replace('*', '.*', preg_quote($pattern, '/'));
                        return preg_match('/^' . $regexPattern . '$/i', $fileName);
                    }
                }
            }
        } elseif ($rule['target'] === 'folder') {
            if ($isFolder) {
                $pathParts = explode('/', $relativePath);
                $topLevelFolder = $pathParts[0];
                if (count($pathParts) === 1 && strpos($topLevelFolder, $rulePath) === 0) {
                    return true;
                }
                return false;
            } else {
                $fileDir = dirname($relativePath);
                if ($fileDir === '.') $fileDir = '';
                if (strpos($fileDir, '/') === false) {
                    return strpos($fileDir, $rulePath) === 0;
                }
                return false;
            }
        }
    }
    if ($rule['type'] === 'folder_recursive') {
        if ($relativePath === $rulePath) {
            return true;
        }
        return strpos($relativePath, $rulePath . '/') === 0;
    }
    return false;
}
function isPathDenied($relativePath, $isFolder = false) {
    global $denyList, $conflictingRules;
    foreach ($denyList as $rule) {
        $isConflicting = false;
        foreach ($conflictingRules as $conflict) {
            if ($conflict['deny'] === $rule['original']) {
                $isConflicting = true;
                break;
            }
        }
        if ($isConflicting) continue;
        if (pathMatchesRule($relativePath, $rule, $isFolder)) {
            return true;
        }
    }
    return false;
}
function isPathAllowed($relativePath, $isFolder = false) {
    global $allowList, $conflictingRules;
    foreach ($allowList as $rule) {
        $isConflicting = false;
        foreach ($conflictingRules as $conflict) {
            if ($conflict['allow'] === $rule['original']) {
                $isConflicting = true;
                break;
            }
        }
        if ($isConflicting) continue;
        if (pathMatchesRule($relativePath, $rule, $isFolder)) {
            return true;
        }
    }
    return false;
}
function isContentAllowedByWildcard($relativePath, $isFolder = false) {
    global $allowList, $conflictingRules;
    foreach ($allowList as $rule) {
        $isConflicting = false;
        foreach ($conflictingRules as $conflict) {
            if ($conflict['allow'] === $rule['original']) {
                $isConflicting = true;
                break;
            }
        }
        if ($isConflicting) continue;
        if ($rule['type'] === 'wildcard' && $rule['target'] === 'folder') {
            if (strpos($relativePath, $rule['path']) === 0) {
                return true;
            }
        }
    }
    return false;
}
function isFolderAccessible($currentPath) {
    global $denyList, $allowList, $conflictingRules, $config;
    if (isset($config['main']['index_all']) && $config['main']['index_all']) {
        return true;
    }
    foreach ($allowList as $rule) {
        $isConflicting = false;
        foreach ($conflictingRules as $conflict) {
            if ($conflict['allow'] === $rule['original']) {
                $isConflicting = true;
                break;
            }
        }
        if ($isConflicting) continue;
        if (pathMatchesRule($currentPath, $rule, true)) {
            return true;
        }
    }
    foreach ($denyList as $rule) {
        $isConflicting = false;
        foreach ($conflictingRules as $conflict) {
            if ($conflict['deny'] === $rule['original']) {
                $isConflicting = true;
                break;
            }
        }
        if ($isConflicting) continue;
        if ($rule['type'] === 'folder_recursive') {
            if ($currentPath === $rule['path'] || strpos($currentPath, $rule['path'] . '/') === 0) {
                return false;
            }
        } elseif ($rule['type'] === 'wildcard' && $rule['target'] === 'folder') {
            $pathParts = explode('/', $currentPath);
            $topLevelFolder = $pathParts[0];
            if (strpos($topLevelFolder, $rule['path']) === 0) {
                if ($currentPath === $topLevelFolder) {
                    return false;
                }
            }
        } elseif ($rule['type'] === 'exact') {
            if ($currentPath === $rule['path']) {
                return false;
            }
        }
    }
    return true;
}
function shouldIndexFile($filename, $extension) {
    global $config, $currentPath;
    $relativePath = $currentPath ? $currentPath . '/' . basename($filename) : basename($filename);
    if (empty($extension) || trim($extension) === '') {
        $indexNonDescript = isset($config['exclusions']['index_non-descript-files']) ? 
                           $config['exclusions']['index_non-descript-files'] : true;
        if (!$indexNonDescript) {
            return false;
        }
        if (isPathDenied($relativePath, false)) {
            if (isPathAllowed($relativePath, false)) {
                return true;
            }
            return false;
        }
        if (isContentAllowedByWildcard($relativePath, false)) {
            return true;
        }
        if (isPathAllowed($relativePath, false)) {
            return true;
        }
        if (isset($config['main']['index_all']) && $config['main']['index_all']) {
            return true;
        }
        if (strpos(basename($filename), '.') === 0) {
            if (!isset($config['main']['index_hidden']) || !$config['main']['index_hidden']) {
                return false;
            }
        }
        return true;
    }
    if (isPathDenied($relativePath, false)) {
        if (isPathAllowed($relativePath, false)) {
            return true;
        }
        return false;
    }
    if (isContentAllowedByWildcard($relativePath, false)) {
        return true;
    }
    if (isPathAllowed($relativePath, false)) {
        return true;
    }
    if (isset($config['main']['index_all']) && $config['main']['index_all']) {
        return true;
    }
    if (strpos(basename($filename), '.') === 0) {
        if (!isset($config['main']['index_hidden']) || !$config['main']['index_hidden']) {
            return false;
        }
    }
    $settingKey = getExtensionSetting($extension, 'indexing');
    if ($settingKey !== null) {
        return isset($config['exclusions'][$settingKey]) ? $config['exclusions'][$settingKey] : true;
    }
    return true;
}
function shouldIndexFolder($foldername) {
    global $config, $currentPath;
    $relativePath = $currentPath ? $currentPath . '/' . basename($foldername) : basename($foldername);
    if (basename($foldername) === '.indexer_files') {
        if (isset($config['main']['index_all']) && $config['main']['index_all']) {
            return true;
        }
        return false;
    }
    if (isPathDenied($relativePath, true)) {
        if (isPathAllowed($relativePath, true)) {
            return true;
        }
        return false;
    }
    if (isContentAllowedByWildcard($relativePath, true)) {
        return true;
    }
    if (isPathAllowed($relativePath, true)) {
        return true;
    }
    if (isset($config['main']['index_all']) && $config['main']['index_all']) {
        return true;
    }
    if (strpos(basename($foldername), '.') === 0) {
        if (!isset($config['main']['index_hidden']) || !$config['main']['index_hidden']) {
            return false;
        }
    }
    return isset($config['exclusions']['index_folders']) ? $config['exclusions']['index_folders'] : true;
}
function isFileAccessible($filePath, $currentPath, $extension) {
    global $config, $disableFileDownloads;
    if (!isFolderAccessible($currentPath)) {
        return false;
    }
    $fileName = basename($filePath);
    $relativePath = $currentPath ? $currentPath . '/' . $fileName : $fileName;
    if (isPathDenied($relativePath, false)) {
        if (!isPathAllowed($relativePath, false)) {
            return false;
        }
    }
    if (!shouldIndexFile($filePath, $extension)) {
        return false;
    }
    return true;
}
function loadConfiguration() {
    $db = initializeDatabase();
    $config = $db->getConfig();
    if (empty($config)) {
        global $configFile;
        if (file_exists($configFile)) {
            $config = json_decode(file_get_contents($configFile), true);
            if ($config) {
                $db->syncSystemFiles();
            }
        }
    }
    return $config ?: [];
}
function getExtensionSetting($extension, $type = 'indexing') {
    if (empty($extension) || trim($extension) === '') {
        if ($type === 'indexing') {
            return 'index_non-descript-files';
        } elseif ($type === 'viewing') {
            return 'view_non-descript-files';
        }
        return null;
    }
    $db = initializeDatabase();
    return $db->getExtensionSetting($extension, $type);
}
function loadLocalExtensionMappings() {
    global $localExtensionMapFile;
    if (!file_exists($localExtensionMapFile)) {
        return null;
    }
    $mappingData = file_get_contents($localExtensionMapFile);
    $mappings = json_decode($mappingData, true);
    return $mappings ?: null;
}
function loadLocalIconMappings() {
    $db = initializeDatabase();
    try {
        $mappings = [];
        $stmt = $db->pdo->query("SELECT identifier, icon_filename FROM icon_mappings");
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $mappings[$row['identifier']] = $row['icon_filename'];
        }
        return $mappings;
    } catch (Exception $e) {
        global $localIconsFile;
        if (file_exists($localIconsFile)) {
            $mappingData = file_get_contents($localIconsFile);
            return json_decode($mappingData, true) ?: [];
        }
        return [];
    }
}
function getIconPath($type, $extension = '') {
    global $webPath, $iconsDir, $iconType, $scriptDir;
    if ($iconType === 'disabled') {
        return null;
    }
    if ($iconType === 'emoji') {
        return null;
    }
    if ($iconType === 'minimal') {
        $iconFilename = ($type === 'folder') ? 'folder-proto.png' : 'non-descript-default-file.png';
        $iconPath = $iconsDir . '/' . $iconFilename;
        if (file_exists($iconPath)) {
            $relativePath = str_replace($scriptDir, '', $iconPath);
            return $webPath . $relativePath;
        }
        return null;
    }
    if ($iconType === 'default') {
        if ($type === 'folder') {
            $iconInfo = getIconFromLocal($type, $extension);
            if ($iconInfo !== null) {
                $relativePath = str_replace($scriptDir, '', $iconInfo['path']);
                return $webPath . $relativePath;
            }
        }
        if ($type === 'file') {
            if (empty($extension) || trim($extension) === '') {
                $iconFilename = 'non-descript-default-file.png';
                $iconPath = $iconsDir . '/' . $iconFilename;
                if (file_exists($iconPath)) {
                    $relativePath = str_replace($scriptDir, '', $iconPath);
                    return $webPath . $relativePath;
                }
                return null;
            }
            $iconMappings = loadLocalIconMappings();
            if ($iconMappings && is_array($iconMappings)) {
                $extension = strtolower($extension);
                if (!isset($iconMappings[$extension])) {
                    $iconFilename = 'non-descript-default-file.png';
                    $iconPath = $iconsDir . '/' . $iconFilename;
                    if (file_exists($iconPath)) {
                        $relativePath = str_replace($scriptDir, '', $iconPath);
                        return $webPath . $relativePath;
                    }
                    return null;
                }
            }
            $iconInfo = getIconFromLocal($type, $extension);
            if ($iconInfo !== null) {
                $relativePath = str_replace($scriptDir, '', $iconInfo['path']);
                return $webPath . $relativePath;
            }
        }
    }
    return null;
}
function getIconFromLocal($type, $extension = '') {
    global $iconsDir;
    $iconMappings = loadLocalIconMappings();
    if ($type === 'folder') {
        $iconFile = isset($iconMappings['folder']) ? $iconMappings['folder'] : 'folder-proto.png';
    } else {
        $extension = strtolower($extension);
        $iconFile = isset($iconMappings[$extension]) ? $iconMappings[$extension] : 'non-descript-default-file.png';
    }
    $iconPath = $iconsDir . '/' . $iconFile;
    if (!file_exists($iconPath)) {
        if ($type === 'folder') {
            $iconFile = 'folder-proto.png';
        } else {
            $iconFile = 'non-descript-default-file.png';
        }
        $iconPath = $iconsDir . '/' . $iconFile;
        if (!file_exists($iconPath)) {
            return null;
        }
    }
    return [
        'filename' => $iconFile,
        'path' => $iconPath,
        'size' => filesize($iconPath),
        'last_modified' => filemtime($iconPath)
    ];
}
function isFileViewable($extension) {
    global $config;
    if (empty($extension) || trim($extension) === '') {
        $viewNonDescript = isset($config['viewable_files']['view_non-descript-files']) ? 
                          $config['viewable_files']['view_non-descript-files'] : false;
        return $viewNonDescript;
    }
    $settingKey = getExtensionSetting($extension, 'viewing');
    if ($settingKey !== null) {
        return isset($config['viewable_files'][$settingKey]) ? $config['viewable_files'][$settingKey] : false;
    }
    return false;
}
function cleanupOldTempFiles() {
    $cache = initializeCache();
    $zipDir = $cache->getPaths()['zips'];
    if (!is_dir($zipDir)) return;
    $files = glob($zipDir . '/*');
    $fiveMinutesAgo = time() - 300;
    foreach ($files as $file) {
        if (filemtime($file) < $fiveMinutesAgo) {
            if (is_dir($file)) {
                deleteDirectory($file);
            } else {
                unlink($file);
            }
        }
    }
}
function deleteDirectory($dir) {
    if (!is_dir($dir)) return;
    $files = glob($dir . '/*');
    foreach ($files as $file) {
        if (is_dir($file)) {
            deleteDirectory($file);
        } else {
            unlink($file);
        }
    }
    rmdir($dir);
}
function getDirectorySize($path) {
    $size = 0;
    if (is_dir($path)) {
        $files = scandir($path);
        foreach ($files as $file) {
            if ($file == '.' || $file == '..') continue;
            $filePath = $path . '/' . $file;
            if (is_dir($filePath)) {
                $size += getDirectorySize($filePath);
            } else {
                $size += filesize($filePath);
            }
        }
    }
    return $size;
}
$database = null;
$cache = null;
function initializeDatabase() {
    global $database, $indexerFilesDir, $baseDir;
    if ($database === null) {
        $database = new Database($indexerFilesDir, $baseDir);
    }
    return $database;
}
function initializeCache() {
    global $cache, $indexerFilesDir;
    if ($cache === null) {
        $cache = new Cache($indexerFilesDir);
    }
    return $cache;
}
$database = initializeDatabase();
$cache = initializeCache();
function copyDirectoryExcludePhp($source, $destination) {
    global $currentPath;
    if (!is_dir($source)) return false;
    if (!is_dir($destination)) {
        mkdir($destination, 0755, true);
    }
    $files = scandir($source);
    foreach ($files as $file) {
        if ($file == '.' || $file == '..') continue;
        $sourcePath = $source . '/' . $file;
        $destPath = $destination . '/' . $file;
        if (is_dir($sourcePath)) {
            if (shouldIndexFolder($sourcePath)) {
                copyDirectoryExcludePhp($sourcePath, $destPath);
            }
        } else {
            $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
            if (shouldIndexFile($sourcePath, $extension)) {
                copy($sourcePath, $destPath);
            }
        }
    }
    return true;
}
function addDirectoryToZip($zip, $dir, $zipPath = '') {
    $files = scandir($dir);
    foreach ($files as $file) {
        if ($file == '.' || $file == '..') continue;
        $filePath = $dir . '/' . $file;
        $zipFilePath = $zipPath ? $zipPath . '/' . $file : $file;
        if (is_dir($filePath)) {
            $zip->addEmptyDir($zipFilePath);
            addDirectoryToZip($zip, $filePath, $zipFilePath);
        } else {
            $zip->addFile($filePath, $zipFilePath);
        }
    }
}
function getFileUrl($path, $filename) {
    global $router, $disableFileDownloads;
    $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    if (isFileViewable($extension)) {
        return $router->generateFileURL($path, $filename, 'view', 'default');
    } else {
        if ($disableFileDownloads) {
            return '#';
        }
        return $router->generateFileURL($path, $filename, 'download');
    }
}
function getSortUrl($sortBy, $currentSort, $currentDir, $currentPath) {
    global $router;
    return $router->generateSortURL($sortBy, $currentSort, $currentDir, $currentPath);
}
function getBreadcrumbs($currentPath) {
    global $router;
    return $router->generateBreadcrumbs($currentPath);
}
function getBaseUrl($currentPath, $webPath) {
    if (empty($currentPath)) {
        return $webPath . '/';
    } else {
        return $webPath . '/' . $currentPath . '/';
    }
}
function generateSharePopup($shareType, $shareFile, $currentPath) {
    global $router, $webPath;
    if ($shareType === 'view') {
        if (is_dir($GLOBALS['fullPath'] . '/' . $shareFile)) {
            $shareUrl = $router->generateFolderURL($currentPath, $shareFile, 'view');
            $popupTitle = 'Share Folder Link';
            $popupText = 'Copy the following URL to share folder';
        } else {
            $shareUrl = $router->generateFileURL($currentPath, $shareFile, 'view', 'default');
            $popupTitle = 'Share File Link';
            $popupText = 'Copy the following URL to view file';
        }
    } elseif ($shareType === 'folder') {
        $shareUrl = $router->generateFolderURL($currentPath, '', 'view');
        $popupTitle = 'Share Folder Location';
        $popupText = 'Copy the following URL to share the folder containing';
    } elseif ($shareType === 'download') {
        $isFolder = is_dir($GLOBALS['fullPath'] . '/' . $shareFile);
        if ($isFolder) {
            $shareUrl = $router->generateFolderURL($currentPath, $shareFile, 'download');
        } else {
            $shareUrl = $router->generateFileURL($currentPath, $shareFile, 'download');
        }
        $popupTitle = 'Share Download Link';
        $popupText = 'Copy the following URL to download';
    }
    $absoluteShareUrl = getAbsoluteUrl($shareUrl);
    $closeParams = $_GET;
    unset($closeParams['share_popup']);
    unset($closeParams['share_file']);
    $closeUrl = $router->generateFolderURL($currentPath, '', 'view');
    if ($closeParams) {
        $closeUrl .= '?' . http_build_query($closeParams);
    }
    return '
    <div class="share-popup-overlay">
        <div class="share-popup">
            <h3>' . htmlspecialchars($popupTitle) . '</h3>
            <p>' . htmlspecialchars($popupText) . ' "<strong>' . htmlspecialchars($shareFile) . '</strong>":</p>
            <div class="share-url-container">' . htmlspecialchars($absoluteShareUrl) . '</div>
            <p><small>Select the URL above and copy it (Ctrl+C / Cmd+C)</small></p>
            <div class="popup-buttons">
                <a href="' . htmlspecialchars($closeUrl) . '" class="popup-btn">Close</a>
            </div>
        </div>
    </div>';
}
function getFileActionMenu($file, $currentPath) {
    global $disableFileDownloads, $router, $webPath;
    $extension = $file['extension'];
    $fileName = $file['name'];
    $isViewable = isFileViewable($extension);
    $showActions = isset($_GET['action']) && $_GET['action'] === $fileName;
    $anchorId = 'file-' . preg_replace('/[^a-zA-Z0-9-_]/', '-', $fileName);
    $menu = '<div class="item-actions-menu" id="' . htmlspecialchars($anchorId) . '">';
    if ($showActions) {
        $closeParams = $_GET;
        unset($closeParams['action']);
        unset($closeParams['options']);
        $closeUrl = $router->generateFolderURL($currentPath, '', 'view');
        if ($closeParams) {
            $closeUrl .= '?' . http_build_query($closeParams);
        }
        $closeUrl .= '#' . $anchorId;
        $menu .= '<a href="' . $closeUrl . '" class="actions-toggle">×</a>';
        $menu .= '<div class="actions-dropdown">';
        if ($isViewable) {
            $openUrl = $router->generateFileURL($currentPath, $fileName, 'view', 'default');
            $menu .= '<a href="' . htmlspecialchars($openUrl) . '">Open</a>';
            $menu .= '<a href="' . htmlspecialchars($openUrl) . '" target="_blank">Open in new tab</a>';
        }
        if (!$disableFileDownloads) {
            $downloadUrl = $router->generateFileURL($currentPath, $fileName, 'download');
            $menu .= '<a href="' . htmlspecialchars($downloadUrl) . '">Download</a>';
        }
        $baseUrl = $router->generateFolderURL($currentPath, '', 'view');
        $shareParams = array_merge($_GET, ['share_popup' => 'view', 'share_file' => $fileName]);
        $shareUrl = $baseUrl . '?' . http_build_query($shareParams);
        $menu .= '<a href="' . $shareUrl . '">Share</a>';
        if (!$disableFileDownloads) {
            $shareParams = array_merge($_GET, ['share_popup' => 'download', 'share_file' => $fileName]);
            $shareUrl = $baseUrl . '?' . http_build_query($shareParams);
            $menu .= '<a href="' . $shareUrl . '">Share Download</a>';
        }
        $menu .= '</div>';
    } else {
        $actionParams = $_GET;
        unset($actionParams['options']);
        if (isset($actionParams['action'])) {
            unset($actionParams['action']);
        }
        $actionParams['action'] = $fileName;
        $actionUrl = $router->generateFolderURL($currentPath, '', 'view');
        $actionUrl .= '?' . http_build_query($actionParams) . '#' . $anchorId;
        $menu .= '<a href="' . $actionUrl . '" class="actions-toggle">⋯</a>';
    }
    $menu .= '</div>';
    return $menu;
}
function getFolderActionMenu($folder, $currentPath) {
    global $disableFolderDownloads, $router, $webPath;
    $folderName = $folder['name'];
    $showActions = isset($_GET['action']) && $_GET['action'] === $folderName;
    $anchorId = 'folder-' . preg_replace('/[^a-zA-Z0-9-_]/', '-', $folderName);
    $menu = '<div class="item-actions-menu" id="' . htmlspecialchars($anchorId) . '">';
    if ($showActions) {
        $closeParams = $_GET;
        unset($closeParams['action']);
        unset($closeParams['options']);
        $closeUrl = $router->generateFolderURL($currentPath, '', 'view');
        if ($closeParams) {
            $closeUrl .= '?' . http_build_query($closeParams);
        }
        $closeUrl .= '#' . $anchorId;
        $menu .= '<a href="' . $closeUrl . '" class="actions-toggle">×</a>';
        $menu .= '<div class="actions-dropdown">';
        $openUrl = $router->generateFolderURL($currentPath, $folderName, 'view');
        $menu .= '<a href="' . htmlspecialchars($openUrl) . '">Open</a>';
        $menu .= '<a href="' . htmlspecialchars($openUrl) . '" target="_blank">Open in new tab</a>';
        if (!$disableFolderDownloads) {
            $downloadUrl = $router->generateFolderURL($currentPath, $folderName, 'download');
            $menu .= '<a href="' . htmlspecialchars($downloadUrl) . '">Download</a>';
        }
        $baseUrl = $router->generateFolderURL($currentPath, '', 'view');
        $shareParams = array_merge($_GET, ['share_popup' => 'view', 'share_file' => $folderName]);
        $shareUrl = $baseUrl . '?' . http_build_query($shareParams);
        $menu .= '<a href="' . $shareUrl . '">Share</a>';
        if (!$disableFolderDownloads) {
            $shareParams = array_merge($_GET, ['share_popup' => 'download', 'share_file' => $folderName]);
            $shareUrl = $baseUrl . '?' . http_build_query($shareParams);
            $menu .= '<a href="' . $shareUrl . '">Share Download</a>';
        }
        $menu .= '</div>';
    } else {
        $actionParams = $_GET;
        unset($actionParams['options']);
        if (isset($actionParams['action'])) {
            unset($actionParams['action']);
        }
        $actionParams['action'] = $folderName;
        $actionUrl = $router->generateFolderURL($currentPath, '', 'view');
        $actionUrl .= '?' . http_build_query($actionParams) . '#' . $anchorId;
        $menu .= '<a href="' . $actionUrl . '" class="actions-toggle">⋯</a>';
    }
    $menu .= '</div>';
    return $menu;
}
function formatBytes($size) {
    if ($size === null) return '-';
    $units = ['B', 'KB', 'MB', 'GB'];
    for ($i = 0; $size > 1024 && $i < count($units) - 1; $i++) {
        $size /= 1024;
    }
    return round($size, 1) . ' ' . $units[$i];
}
function getSortParams() {
    $sortBy = $_GET['sort'] ?? 'name';
    $sortDir = $_GET['dir'] ?? 'asc';
    $validSorts = ['name', 'size', 'modified', 'type'];
    $validDirs = ['asc', 'desc'];
    if (!in_array($sortBy, $validSorts)) $sortBy = 'name';
    if (!in_array($sortDir, $validDirs)) $sortDir = 'asc';
    return ['sort' => $sortBy, 'dir' => $sortDir];
}
function sortItems($items, $sortBy, $sortDir) {
    usort($items, function($a, $b) use ($sortBy, $sortDir) {
        $result = 0;
        switch ($sortBy) {
            case 'name':
                $result = strcasecmp($a['name'], $b['name']);
                break;
            case 'size':
                $result = $a['size'] <=> $b['size'];
                break;
            case 'modified':
                $result = $a['modified'] <=> $b['modified'];
                break;
            case 'type':
                $aExt = isset($a['extension']) ? $a['extension'] : ($a['is_dir'] ? 'folder' : '');
                $bExt = isset($b['extension']) ? $b['extension'] : ($b['is_dir'] ? 'folder' : '');
                $result = strcasecmp($aExt, $bExt);
                break;
        }
        return $sortDir === 'desc' ? -$result : $result;
    });
    return $items;
}
function getSortIndicator($column, $currentSort, $currentDir) {
    if ($column !== $currentSort) {
        return '';
    }
    return $currentDir === 'asc' ? ' ↑' : ' ↓';
}
function getAbsoluteUrl($relativeUrl) {
    global $config;
    if (isset($config['main']['access_url']) && !empty($config['main']['access_url'])) {
        $baseUrl = rtrim($config['main']['access_url'], '/');
        if (strpos($relativeUrl, '/') === 0) {
            return $baseUrl . $relativeUrl;
        } else {
            $currentDir = dirname($_SERVER['REQUEST_URI']);
            if ($currentDir === '/' || $currentDir === '\\') {
                return $baseUrl . '/' . $relativeUrl;
            } else {
                return $baseUrl . rtrim($currentDir, '/') . '/' . $relativeUrl;
            }
        }
    }
    $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'];
    if (strpos($relativeUrl, '/') === 0) {
        return $protocol . '://' . $host . $relativeUrl;
    } else {
        $currentDir = dirname($_SERVER['REQUEST_URI']);
        return $protocol . '://' . $host . rtrim($currentDir, '/') . '/' . $relativeUrl;
    }
}
if (!is_dir($fullPath)) {
    http_response_code(404);
    die('Directory not found');
}
if (!isFolderAccessible($currentPath)) {
    http_response_code(403);
    header('Location: ' . $_SERVER['SCRIPT_NAME']);
    exit;
}
$sortParams = getSortParams();
$sortBy = $sortParams['sort'];
$sortDir = $sortParams['dir'];
$directories = [];
$files = [];
$db = initializeDatabase();
$items = scandir($fullPath);
$needsFullRescan = false;
foreach ($items as $item) {
    if ($item == '.' || $item == '..') continue;
    $itemPath = $fullPath . '/' . $item;
    $relativePath = $currentPath ? $currentPath . '/' . $item : $item;
    if (is_dir($itemPath)) {
        $metadata = $db->getFileMetadata($relativePath);
        if (!$metadata || 
            $metadata['modified'] < filemtime($itemPath) || 
            $metadata['size'] == 0) {
            $needsFullRescan = true;
            break;
        }
    } else {
        $metadata = $db->getFileMetadata($relativePath);
        if (!$metadata || $metadata['modified'] < filemtime($itemPath)) {
            $needsFullRescan = true;
            break;
        }
    }
}
if ($needsFullRescan) {
    indexDirectoryContents($currentPath, $fullPath);
}
$dbContents = $db->getDirectoryContents($currentPath ?: null);
foreach ($dbContents as $item) {
    $itemPath = $fullPath . '/' . $item['filename'];
    if (!file_exists($itemPath)) {
        continue;
    }
    $itemInfo = [
        'name' => $item['filename'],
        'modified' => $item['modified'],
        'is_dir' => $item['is_directory'] == 1,
        'size' => $item['size']
    ];
    if ($itemInfo['is_dir']) {
        $relativePath = $currentPath ? $currentPath . '/' . $item['filename'] : $item['filename'];
        if (shouldIndexFolder($relativePath)) {
            $directories[] = $itemInfo;
        }
    } else {
        $extension = $item['extension'] ?? '';
        $relativePath = $currentPath ? $currentPath . '/' . $item['filename'] : $item['filename'];
        if (shouldIndexFile($relativePath, $extension)) {
            $itemInfo['extension'] = $extension;
            $files[] = $itemInfo;
        }
    }
}
$directories = sortItems($directories, $sortBy, $sortDir);
$files = sortItems($files, $sortBy, $sortDir);
function indexDirectoryContents($currentPath, $fullPath) {
    $db = initializeDatabase();
    $items = scandir($fullPath);
    $indexedPaths = [];
    $dirKey = $currentPath ?: 'ROOT_DIR';
    $indexedPaths[] = $dirKey;
    foreach ($items as $item) {
        if ($item == '.' || $item == '..') continue;
        $itemPath = $fullPath . '/' . $item;
        $relativePath = $currentPath ? $currentPath . '/' . $item : $item;
        $indexedPaths[] = $relativePath;
        $itemInfo = [
            'name' => $item,
            'modified' => filemtime($itemPath),
            'is_dir' => is_dir($itemPath)
        ];
        if ($itemInfo['is_dir']) {
            if (shouldIndexFolder($relativePath)) {
                $existingMetadata = $db->getFileMetadata($relativePath);
                $size = 0;
                if ($existingMetadata && 
                    $existingMetadata['modified'] >= $itemInfo['modified'] && 
                    $existingMetadata['size'] > 0) {
                    $size = $existingMetadata['size'];
                } else {
                    $size = getDirectorySize($itemPath);
                }
                $db->indexPath(
                    $relativePath,
                    $item,
                    null,
                    $size,
                    $itemInfo['modified'],
                    true,
                    $currentPath ?: null
                );
            }
        } else {
            $extension = strtolower(pathinfo($item, PATHINFO_EXTENSION));
            if (shouldIndexFile($relativePath, $extension)) {
                $size = filesize($itemPath);
                $db->indexPath(
                    $relativePath,
                    $item,
                    $extension,
                    $size,
                    $itemInfo['modified'],
                    false,
                    $currentPath ?: null
                );
            }
        }
    }
    $db->cleanupDeletedPaths($currentPath ?: null, $indexedPaths);
}
function parseSizeString($sizeString) {
    if (empty($sizeString) || !is_string($sizeString)) {
        return null;
    } 
    $sizeString = trim(strtoupper($sizeString));
    if (preg_match('/^(\d+(?:\.\d+)?)\s*(KB|MB|GB|TB|B)?$/', $sizeString, $matches)) {
        $number = floatval($matches[1]);
        $unit = isset($matches[2]) ? $matches[2] : 'B';
        $multipliers = [
            'B' => 1,
            'KB' => 1024,
            'MB' => 1024 * 1024,
            'GB' => 1024 * 1024 * 1024,
            'TB' => 1024 * 1024 * 1024 * 1024
        ];
        if (isset($multipliers[$unit])) {
            return intval($number * $multipliers[$unit]);
        }
    }
    return null;
}
function getMaxDownloadSize($type) {
    global $config;
    if ($type === 'file') {
        $defaultSize = 2 * 1024 * 1024 * 1024;
    } else {
        $defaultSize = 50 * 1024 * 1024;
    }
    $configKey = ($type === 'file') ? 'max_download_size_file' : 'max_download_size_folder';
    if (isset($config['main'][$configKey])) {
        $parsedSize = parseSizeString($config['main'][$configKey]);
        return $parsedSize !== null ? $parsedSize : $defaultSize;
    }
    return $defaultSize;
}
function formatSizeForError($bytes) {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $i = 0;
    while ($bytes >= 1024 && $i < count($units) - 1) {
        $bytes /= 1024;
        $i++;
    }
    return round($bytes, 1) . ' ' . $units[$i];
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="application-name" content="5q12's Indexer">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="apple-mobile-web-app-title" content="Indexer">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="theme-color" content="#2a2a2a">
    <link rel="manifest" href="<?php echo $webPath; ?>/.indexer_files/local_api/manifest.json">
    <title><?php echo empty($currentPath) ? '5q12 Indexer' : htmlspecialchars(basename($currentPath)); ?></title>
    <link rel="icon" type="image/x-icon" href="<?php echo $webPath; ?>/.indexer_files/favicon/icon.ico">
    <link rel="icon" type="image/png" sizes="16x16" href="<?php echo $webPath; ?>/.indexer_files/favicon/16x16.png">
    <link rel="icon" type="image/png" sizes="32x32" href="<?php echo $webPath; ?>/.indexer_files/favicon/32x32.png">
    <link rel="icon" type="image/png" sizes="48x48" href="<?php echo $webPath; ?>/.indexer_files/favicon/48x48.png">
    <link rel="icon" type="image/png" sizes="96x96" href="<?php echo $webPath; ?>/.indexer_files/favicon/96x96.png">
    <link rel="icon" type="image/png" sizes="144x144" href="<?php echo $webPath; ?>/.indexer_files/favicon/144x144.png">
    <link rel="icon" type="image/png" sizes="192x192" href="<?php echo $webPath; ?>/.indexer_files/favicon/192x192.png">
    <link rel="apple-touch-icon" sizes="180x180" href="<?php echo $webPath; ?>/.indexer_files/favicon/180x180.png">
    <link rel="stylesheet" href="<?php echo $webPath; ?>/.indexer_files/local_api/style/base-2.0.0-r0.min.css">
    <link rel="stylesheet" href="<?php echo $webPath; ?>/.indexer_files/local_api/style/index-2.0.0-r0.min.css">
    <style>
    </style>
</head>
<body<?php if ($iconType === 'disabled') echo ' class="icon-disabled"'; ?>>
    <?php
    $securityStatus = getSecurityStatus();
    $lockIcon = $securityStatus['secure'] 
        ? $webPath . '/.indexer_files/icons/app/green.png'
        : $webPath . '/.indexer_files/icons/app/red.png';
    ?>
    <div class="security-bar">
        <span class="security-lock" data-tooltip="<?php echo $securityStatus['secure'] ? 'Connection is secure (HTTPS)' : 'Connection is not secure - Consider using HTTPS'; ?>">
            <img src="<?php echo htmlspecialchars($lockIcon); ?>" alt="<?php echo $securityStatus['secure'] ? 'Secure' : 'Not Secure'; ?>">
        </span>
        <span class="security-hostname"><?php echo htmlspecialchars($securityStatus['hostname']); ?></span>
    </div>
    <div class="container">
        <div class="header">
            <h1><?php echo empty($currentPath) ? '5q12-Indexer' : '5q12-Indexer: /' . htmlspecialchars(basename($currentPath)); ?></h1>
            <div class="breadcrumbs"><?php echo getBreadcrumbs($currentPath); ?></div>
        </div>
        <?php
        $sortParams = getSortParams();
        $currentSort = $sortParams['sort'];
        $currentDir = $sortParams['dir'];
        $showOptions = isset($_GET['options']);
        ?>
        <div class="file-list">
            <div class="header-row">
                <div class="file-item">
                    <div class="file-icon"></div>
                    <div class="file-name">
                        <a href="<?php echo getSortUrl('name', $currentSort, $currentDir, $currentPath); ?>" 
                        class="sortable-header <?php echo $currentSort === 'name' ? 'active' : ''; ?>">
                            Name<?php echo getSortIndicator('name', $currentSort, $currentDir); ?>
                        </a>
                    </div>
                    <div class="file-size">
                        <a href="<?php echo getSortUrl('size', $currentSort, $currentDir, $currentPath); ?>" 
                        class="sortable-header <?php echo $currentSort === 'size' ? 'active' : ''; ?>">
                            Size<?php echo getSortIndicator('size', $currentSort, $currentDir); ?>
                        </a>
                    </div>
                    <div class="file-date">
                        <a href="<?php echo getSortUrl('modified', $currentSort, $currentDir, $currentPath); ?>" 
                        class="sortable-header <?php echo $currentSort === 'modified' ? 'active' : ''; ?>">
                            Modified<?php echo getSortIndicator('modified', $currentSort, $currentDir); ?>
                        </a>
                    </div>
                    <div class="options-menu-container">
                        <div class="options-menu">
                            <?php if ($showOptions): ?>
                                <a href="<?php 
                                    $closeParams = $_GET;
                                    unset($closeParams['options']);
                                    unset($closeParams['action']);
                                    $closeUrl = $router->generateFolderURL($currentPath, '', 'view');
                                    if ($closeParams) {
                                        $closeUrl .= '?' . http_build_query($closeParams);
                                    }
                                    echo $closeUrl;
                                ?>" class="options-toggle">×</a>
                                <div class="options-dropdown">
                                    <?php
                                    $baseParams = $_GET;
                                    unset($baseParams['options']);
                                    $baseUrl = $router->generateFolderURL($currentPath, '', 'view');
                                    $sortOptions = [
                                        ['sort' => 'name', 'dir' => 'asc', 'label' => 'Name (A-Z)'],
                                        ['sort' => 'name', 'dir' => 'desc', 'label' => 'Name (Z-A)'],
                                        ['sort' => 'size', 'dir' => 'asc', 'label' => 'Size (Small to Large)'],
                                        ['sort' => 'size', 'dir' => 'desc', 'label' => 'Size (Large to Small)'],
                                        ['sort' => 'modified', 'dir' => 'asc', 'label' => 'Date Modified (Oldest First)'],
                                        ['sort' => 'modified', 'dir' => 'desc', 'label' => 'Date Modified (Newest First)'],
                                        ['sort' => 'type', 'dir' => 'asc', 'label' => 'Type (A-Z)'],
                                        ['sort' => 'type', 'dir' => 'desc', 'label' => 'Type (Z-A)']
                                    ];
                                    foreach ($sortOptions as $option) {
                                        $params = array_merge($baseParams, [
                                            'sort' => $option['sort'],
                                            'dir' => $option['dir']
                                        ]);
                                        $isActive = ($currentSort === $option['sort'] && $currentDir === $option['dir']);
                                        echo '<a href="' . $baseUrl . '?' . http_build_query($params) . '" class="' . ($isActive ? 'active' : '') . '">' . $option['label'] . '</a>';
                                    }
                                    ?>
                                </div>
                            <?php else: ?>
                                <?php
                                $optionsParams = $_GET;
                                unset($optionsParams['action']);
                                $optionsParams['options'] = '1';
                                $baseUrl = $router->generateFolderURL($currentPath, '', 'view');
                                ?>
                                <a href="<?php echo $baseUrl . '?' . http_build_query($optionsParams); ?>" class="options-toggle">⋯</a>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>
            </div>
            <?php if ($currentPath): ?>
            <div class="parent-dir">
                <div class="file-item">
                    <div class="file-icon">
                        <?php 
                        $folderIconPath = getIconPath('folder');
                        if ($iconType === 'disabled'): ?>
                        <?php elseif ($iconType === 'emoji' || $folderIconPath === null): ?>
                            <div class="file-icon-emoji">📁</div>
                        <?php else: ?>
                            <img src="<?php echo htmlspecialchars($folderIconPath); ?>" alt="Folder">
                        <?php endif; ?>
                    </div>
                    <div class="file-name">
                        <a href="<?php echo $router->generateFolderURL(dirname($currentPath) === '.' ? '' : dirname($currentPath), '', 'view'); ?>">
                            ../
                        </a>
                    </div>
                    <div class="file-size">-</div>
                    <div class="file-date">-</div>
                    <div class="item-actions-menu-container"></div>
                </div>
            </div>
            <?php endif; ?>
            <?php foreach ($directories as $dir): ?>
            <div class="directory">
                <div class="file-item">
                    <div class="file-icon">
                        <?php 
                        $folderIconPath = getIconPath('folder');
                        if ($iconType === 'disabled'): ?>
                        <?php elseif ($iconType === 'emoji' || $folderIconPath === null): ?>
                            <div class="file-icon-emoji">📁</div>
                        <?php else: ?>
                            <img src="<?php echo htmlspecialchars($folderIconPath); ?>" alt="Folder">
                        <?php endif; ?>
                    </div>
                    <div class="file-name">
                        <a href="<?php echo $router->generateFolderURL($currentPath, $dir['name'], 'view'); ?>">
                            <?php echo htmlspecialchars($dir['name']); ?>/
                        </a>
                    </div>
                    <div class="file-size"><?php echo formatBytes($dir['size']); ?></div>
                    <div class="file-date"><?php echo date('Y-m-d H:i', $dir['modified']); ?></div>
                    <div class="item-actions-menu-container">
                        <?php echo getFolderActionMenu($dir, $currentPath); ?>
                    </div>
                </div>
            </div>
            <?php endforeach; ?>
            <?php foreach ($files as $file): ?>
            <div class="file">
                <div class="file-item">
                    <div class="file-icon">
                        <?php 
                        $fileIconPath = getIconPath('file', $file['extension']);
                        if ($iconType === 'disabled'): ?>
                        <?php elseif ($iconType === 'emoji' || $fileIconPath === null): ?>
                            <div class="file-icon-emoji">📄</div>
                        <?php else: ?>
                            <img src="<?php echo htmlspecialchars($fileIconPath); ?>" alt="File">
                        <?php endif; ?>
                    </div>
                    <div class="file-name">
                        <a href="<?php echo getFileUrl($currentPath, $file['name']); ?>" <?php echo isFileViewable($file['extension']) ? 'target="_self"' : ''; ?>>
                            <?php echo htmlspecialchars($file['name']); ?>
                        </a>
                    </div>
                    <div class="file-size"><?php echo formatBytes($file['size']); ?></div>
                    <div class="file-date"><?php echo date('Y-m-d H:i', $file['modified']); ?></div>
                    <div class="item-actions-menu-container">
                        <?php echo getFileActionMenu($file, $currentPath); ?>
                    </div>
                </div>
            </div>
            <?php endforeach; ?>
        </div>
    </div>
    <?php
    if (isset($_GET['share_popup']) && isset($_GET['share_file'])) {
        $shareType = $_GET['share_popup'];
        $shareFile = $_GET['share_file'];
        echo generateSharePopup($shareType, $shareFile, $currentPath);
    }
    echo generateUpdateNotice();
    ?>
</body>
</html>