Created
July 22, 2018 13:53
-
-
Save DvdGiessen/17e04db10619824f181688999dc87532 to your computer and use it in GitHub Desktop.
Simple PHP file browser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// By Black / DvdGiessen | |
// http://www.dvdgiessen.nl/ | |
// Layout based on Mozilla Firefox's file:// display | |
// Wheter to rewrite directory URL's to the form ?dir=PATH | |
// Disable if you have mod_rewrite/htaccess configured to rewrite directories to this script | |
$rewrite_directory_urls = FALSE; | |
// Skip hidden files? | |
$list_hiddenfiles = FALSE; | |
// Sort directories as one group. | |
$sort_directories_together = TRUE; | |
// Maximum size of files served through this script, in bytes. | |
// Set to 0 to disable file serving, set to -1 to disable the size limit. | |
$max_file_size = 5 * 1024 * 1024; // 5 MiB | |
// Set a base directory for the file listings. Will force serving files through the script. | |
// Use NULL to use the current directory (default). | |
$base_dir = NULL; | |
// Translations of all texts | |
$translations = array( | |
'indexof' => 'Index van', | |
'up' => 'Naar map op hoger niveau gaan', | |
'showhidden' => 'Verborgen objecten tonen', | |
'name' => 'Naam', | |
'size' => 'Grootte', | |
'lastchanged' => 'Laatst gewijzigd', | |
'file' => 'Bestand', | |
'errors' => array( | |
'unknown' => 'Er is een onbekende fout opgetreden.', | |
'notfound' => 'De gevraagde locatie kon niet worden gevonden.', | |
'forbidden' => 'Toegang geweigerd.', | |
), | |
); | |
// That's all there is to configure! | |
// From this point below the script will figure everything out by itself. | |
// Basic PHP settings | |
error_reporting(0); | |
date_default_timezone_set('UTC'); | |
// List of available icons | |
$icons = array( | |
'disk' => 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNqsU8uOElEQPffR3XQ3ONASdBJCSBxHos5+3Bg3rvkCv8PElS78gPkO/ATjQoUdO2ftrJiRh6aneTb9sOpC4weMN6lcuFV16pxDIfI8x12OYIDhcPiu2Wx+/HF5CW1Z6Jyegt/TNEWSJIjjGFEUIQxDrFYrWFSzXC4/dLvd95pRKpXKy+pRFZ7nwaWo1+sGnQG2260BKJfLKJVKGI1GEEJw7ateryd0v993W63WEwjgxfn5obGYzgCbzcaEbdsIggDj8Riu6z6iUk9SYZMSx8W0LMsM/SKK75xnJlIq80anQXdbEp0OhcPJ0eiaJnGRMEyyPDsAKKUM9clkYoDo3SZJzzSdp0VSKYmfV1co+z580kw5KDIM8RbRfEnUf1HzxtQyMAGcaGruTKczMzEIaqhKifV6jd+zGQQB5llunF/M52BizC2K5sYPYvZcu653tjOM9O93wnYc08gmkgg4VAxixfqFUJT36AYBZGd6PJkFCZnnlBxMp38gqIgLpZB0y4Nph18lyWh5FFbrOSxbl3V4G+VB7T4ajYYxTyuLtO+CvWGgJE1Mc7JNsJEhvgw/QV4fo/24nbEsX2u1d5sVyn8sJO0ZAQiIYnFh+xrfLz/j29cBS/O14zg3i8XigW3ZkErDtmKoeM+AJGRMnXeEPGKf0nCD1ydvkDzU9Jbc6OpR7WIw6L8lQ+4pQ1/lPF0RGM9Ns91Wmptk0GfB4EJkt77vXYj/8m+8y/krwABHbz2H9V68DQAAAABJRU5ErkJggg==', | |
'up' => 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU0toU0EUPfPysx/tTxuDH9SCWhUDooIbd7oRUUTMouqi2iIoCO6lceHWhegy4EJFinWjrlQUpVm0IIoFpVDEIthm0dpikpf3ZuZ6Z94nrXhhMjM3c8895977BBHB2PznK8WPtDgyWH5q77cPH8PpdXuhpQT4ifR9u5sfJb1bmw6VivahATDrxcRZ2njfoaMv+2j7mLDn93MPiNRMvGbL18L9IpF8h9/TN+EYkMffSiOXJ5+hkD+PdqcLpICWHOHc2CC+LEyA/K+cKQMnlQHJX8wqYG3MAJy88Wa4OLDvEqAEOpJd0LxHIMdHBziowSwVlF8D6QaicK01krw/JynwcKoEwZczewroTvZirlKJs5CqQ5CG8pb57FnJUA0LYCXMX5fibd+p8LWDDemcPZbzQyjvH+Ki1TlIciElA7ghwLKV4kRZstt2sANWRjYTAGzuP2hXZFpJ/GsxgGJ0ox1aoFWsDXyyxqCs26+ydmagFN/rRjymJ1898bzGzmQE0HCZpmk5A0RFIv8Pn0WYPsiu6t/Rsj6PauVTwffTSzGAGZhUG2F06hEc9ibS7OPMNp6ErYFlKavo7MkhmTqCxZ/jwzGA9Hx82H2BZSw1NTN9Gx8ycHkajU/7M+jInsDC7DiaEmo1bNl1AMr9ASFgqVu9MCTIzoGUimXVAnnaN0PdBBDCCYbEtMk6wkpQwIG0sn0PQIUF4GsTwLSIFKNqF6DVrQq+IWVrQDxAYQC/1SsYOI4pOxKZrfifiUSbDUisif7XlpGIPufXd/uvdvZm760M0no1FZcnrzUdjw7au3vu/BVgAFLXeuTxhTXVAAAAAElFTkSuQmCC', | |
'directory' => 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAd5JREFUeNqMU79rFUEQ/vbuodFEEkzAImBpkUabFP4ldpaJhZXYm/RiZWsv/hkWFglBUyTIgyAIIfgIRjHv3r39MePM7N3LcbxAFvZ2b2bn22/mm3XMjF+HL3YW7q28YSIw8mBKoBihhhgCsoORot9d3/ywg3YowMXwNde/PzGnk2vn6PitrT+/PGeNaecg4+qNY3D43vy16A5wDDd4Aqg/ngmrjl/GoN0U5V1QquHQG3q+TPDVhVwyBffcmQGJmSVfyZk7R3SngI4JKfwDJ2+05zIg8gbiereTZRHhJ5KCMOwDFLjhoBTn2g0ghagfKeIYJDPFyibJVBtTREwq60SpYvh5++PpwatHsxSm9QRLSQpEVSd7/TYJUb49TX7gztpjjEffnoVw66+Ytovs14Yp7HaKmUXeX9rKUoMoLNW3srqI5fWn8JejrVkK0QcrkFLOgS39yoKUQe292WJ1guUHG8K2o8K00oO1BTvXoW4yasclUTgZYJY9aFNfAThX5CZRmczAV52oAPoupHhWRIUUAOoyUIlYVaAa/VbLbyiZUiyFbjQFNwiZQSGl4IDy9sO5Wrty0QLKhdZPxmgGcDo8ejn+c/6eiK9poz15Kw7Dr/vN/z6W7q++091/AQYA5mZ8GYJ9K0AAAAAASUVORK5CYII=', | |
'unknown' => 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAvVBMVEUAAAD////c3Nzh5OH09PS5ubm/v7+5ubl5eXmGhoaQkJCdnZ2srKytra2xsbGzs7O1tbW4uLi6urq8vLy9vb2/v7/AwMDFxcXKysrg39/m5eXy8PDz8fHz8vHz8vL08vL08/P18/P19PP19PT29PT29fT29fX29vb39fX39vX39vb49vb49/b49/f4+Pf5+Pf5+Pj5+fj6+fj6+fn6+vn7+vn7+vr7+/r8+/r8+/v8/Pv9/Pv9/Pz9/fz+/v3xv/NuAAAACHRSTlMAAF9pefn8/Qk6euYAAACbSURBVBiVTchXEoIwAAVAYqcT7BWxgyCIKBIl9z+WeZQZ93MlQiaNXkeSiCQiZ0XBOeOzfruMcVYNn567LcQorcem1EYMk/T1LmcxL8OO6hFFETSI7g8x3yYsv5wMYyHMk4d5ZuxTmAjjIOYWY5iB0N390QtCTK4jNAdzDeMkzTSEusFcfIyKUFZrx91hokRByEsx22pkxOAf+QGB3h9M82xjJwAAAABJRU5ErkJggg==', | |
); | |
// Return the requested icon upon request | |
if(isset($_GET['icon'])) { | |
$data = isset($icons[$_GET['icon']]) ? $icons[$_GET['icon']] : $icons['unknown']; | |
$data = base64_decode($data); | |
header('Cache-Control: max-age=' . 30 * 24 * 60 * 60 . ', public'); | |
header('Content-Type: image/png'); | |
header('Content-Length: ' . strlen($data)); | |
die($data); | |
} | |
// Regex for normalizing directory separators in URL's | |
$url_directoryseparator_regex = '#[\\\\/]#'; | |
// Determine base URL and directory | |
$is_ssl = isset($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) == 'on' || $_SERVER['HTTPS'] == '1'); | |
$base = ($is_ssl ? 'https://' : 'http://') . $_SERVER['SERVER_NAME']; | |
if(isset($_SERVER['SERVER_PORT']) && ((!$is_ssl && (int) $_SERVER['SERVER_PORT'] != 80) || ($is_ssl && (int) $_SERVER['SERVER_PORT'] != 443))) { | |
$base .= ':' . (int) $_SERVER['SERVER_PORT']; | |
} | |
if(is_null($base_dir)) { | |
$realbase = @realpath(dirname(__FILE__)); | |
$base_dir = '/'; | |
if(isset($_SERVER['DOCUMENT_ROOT'])) { | |
$base .= ($base_dir = substr($realbase, strlen(@realpath($_SERVER['DOCUMENT_ROOT'])))); | |
} | |
$base_dir_autodetected = TRUE; | |
} else { | |
$realbase = @realpath($base_dir); | |
if(isset($_SERVER['DOCUMENT_ROOT'])) { | |
$base .= preg_replace($url_directoryseparator_regex, '/', ($base_dir = substr(@realpath(dirname(__FILE__)), strlen(@realpath($_SERVER['DOCUMENT_ROOT']))))); | |
} | |
$base_dir_autodetected = FALSE; | |
} | |
// Helper function for detection of file sizes | |
function filesize64($file) { | |
if(!file_exists($file) || !is_readable($file)) return FALSE; | |
static $iswin; | |
if(!isset($iswin)) { | |
$iswin = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN'); | |
} | |
static $exec_works; | |
if(!isset($exec_works)) { | |
$exec_works = (function_exists('exec') && !ini_get('safe_mode') && @exec('echo EXEC') == 'EXEC'); | |
} | |
// Try a shell command | |
if($exec_works) { | |
$cmd = ($iswin) ? ('for %F in ("' . $file . '") do @echo %~zF') : ('stat -c%s "' . $file . '"'); | |
@exec($cmd, $output); | |
if(is_array($output) && ctype_digit($size = trim(implode("\n", $output)))) { | |
return $size; | |
} | |
} | |
// Try the Windows COM interface | |
if($iswin) { | |
$fsobj = NULL; | |
if(class_exists('DOTNET')) { | |
try { | |
$fsobj = new DOTNET('mscorlib', 'Scripting.FileSystemObject'); | |
} catch (Exception $e) {} | |
} elseif(class_exists('COM')) { | |
try { | |
$fsobj = new COM('Scripting.FileSystemObject'); | |
} catch (Exception $e) {} | |
} | |
if($fsobj) { | |
try { | |
$f = $fsobj->GetFile(@realpath($file)); | |
$size = $f->Size; | |
if(ctype_digit($size)) { | |
return $size; | |
} | |
} catch(Exception $e) { | |
$size = NULL; | |
} | |
} | |
} | |
// if all else fails | |
return filesize($file); | |
} | |
// Requested directory, relative to the base URL | |
$dir = isset($_GET['dir']) ? $_GET['dir'] : substr('/' . trim(preg_replace('/\?.*$/', '', $_SERVER['REQUEST_URI']), '\\/'), strlen($base_dir)); | |
$dir = '/' . preg_replace($url_directoryseparator_regex, '/', trim(urldecode($dir), '\\/')); | |
$dirnamedir = preg_replace($url_directoryseparator_regex, '/', @dirname($dir)); | |
$realdir = @realpath($realbase . DIRECTORY_SEPARATOR . preg_replace($url_directoryseparator_regex, DIRECTORY_SEPARATOR, $dir)); | |
if(!$realdir || !@file_exists($realdir) || strpos($dir, '..') !== FALSE) { | |
// Indicate failure, redirect to parent directory | |
header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); | |
header('Location: ' . $base . $dirnamedir . '?error=notfound'); | |
exit; | |
} elseif(!@is_dir($realdir)) { | |
// Appearantly, we are requested to serve a file! | |
if(($list_hiddenfiles || substr(basename($realdir), 0, 1) != '.') && @is_file($realdir) && @is_readable($realdir) && filesize64($realdir) >= 0 && ($max_file_size < 0 || filesize64($realdir) <= $max_file_size)) { | |
// Try to disable any time limits we may have | |
@set_time_limit(0); | |
// Determine the MIME type | |
$mimetype = 'application/octet-stream'; | |
if(function_exists('finfo_open')) { | |
$finfo = @finfo_open(FILEINFO_MIME_TYPE); | |
$mimetype = @finfo_file($finfo, $realdir); | |
} | |
// Remove all headers | |
header_remove(); | |
// Check for partial requests | |
$file_size = filesize64($realdir); | |
$range = FALSE; | |
if(isset($_SERVER['HTTP_RANGE'])) { | |
$range = $_SERVER['HTTP_RANGE']; | |
} elseif(function_exists('apache_request_headers')) { | |
$headers = @apache_request_headers(); | |
if(isset($headers['Range'])) { | |
$range = $headers['Range']; | |
} else { | |
foreach($headers as $header => $value) { | |
if(strtolower($header) == 'range') { | |
$range = $value; | |
break; | |
} | |
} | |
} | |
} | |
if($range && preg_match('/bytes=(\d*)-(\d*)/', $range, $matches)) { | |
if($matches[1] == '' && $matches[2] != '') { | |
$partial_start = $file_size - intval($matches[2]); | |
$partial_end = $file_size; | |
} elseif($matches[1] != '' && $matches[2] == '') { | |
$partial_start = intval($matches[1]); | |
$partial_end = $file_size; | |
} elseif($matches[1] != '' && $matches[2] != '') { | |
$partial_start = intval($matches[1]); | |
$partial_end = intval($matches[2]); | |
} else { | |
header($_SERVER['SERVER_PROTOCOL'] . ' 400 Invalid Request'); | |
exit; | |
} | |
} | |
if($partial_end > $file_size || (!$partial_start && (!$partial_end || $partial_end == ($file_size)))) { | |
$partial_start = 0; | |
$partial_end = $file_size; | |
header($_SERVER['SERVER_PROTOCOL'] . ' 200 OK'); | |
header('Content-Length: ' . $file_size); | |
} else { | |
header('HTTP/1.1 206 Partial Content'); | |
header('Content-Range: bytes ' . $partial_start . '-' . $partial_end . '/' . $file_size); | |
} | |
// Notice: if the browser doesn't display the file size, try disabling Apache's mod_deflate using the following .htaccess directive: | |
// SetEnvIfNoCase Request_URI ^/index\.php no-gzip=1 dont-vary=1 | |
// Set headers | |
header('Content-Type: ' . $mimetype); | |
header('Content-Disposition: inline; filename="' . @basename($realdir) . '"'); | |
header('Accept-Ranges: bytes'); | |
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($realdir)) . ' GMT'); | |
header('Cache-Control: must-revalidate'); | |
header('Pragma: public'); | |
// Send the file | |
if(!$file_handle = @fopen($realdir, 'r')) { | |
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error'); | |
header('Location: ' . $base . $dirnamedir . '?error=unknown'); | |
exit; | |
} | |
@fseek($file_handle, $partial_start); | |
$length = $partial_end - $partial_start; | |
while($length) { | |
// Read in chunks of 8192 bytes to reduce memory usage | |
$read = ($length > 8192) ? 8192 : $length; | |
$length -= $read; | |
echo @fread($file_handle, $read); | |
} | |
@fclose($file_handle); | |
// Close connection | |
exit; | |
} else { | |
// Indicate failure, redirect to parent directory | |
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); | |
header('Location: ' . $base . $dirnamedir . '?error=forbidden'); | |
exit; | |
} | |
} | |
if(!defined('PHP_INT_MIN')) define('PHP_INT_MIN', -1 * (PHP_INT_MAX - 1)); | |
// Scan dir | |
if(!($dir_contents = @scandir($realdir))) { | |
// Indicate failure, redirect to parent directory | |
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error'); | |
header('Location: ' . $base . $dirnamedir . '?error=unknown'); | |
exit; | |
} | |
usort($dir_contents, function($a, $b) use($sort_directories_together, $realdir) { | |
$aCmp = strtolower($a); | |
$bCmp = strtolower($b); | |
if($sort_directories_together) { | |
$aCmp = (@is_dir($realdir . DIRECTORY_SEPARATOR . $a) ? '0' : '1') . $aCmp; | |
$bCmp = (@is_dir($realdir . DIRECTORY_SEPARATOR . $b) ? '0' : '1') . $bCmp; | |
} | |
return strcoll($aCmp, $bCmp); | |
}); | |
// Show hidden cookie? | |
$showHidden = FALSE; | |
if(isset($_COOKIE['showHidden']) && $_COOKIE['showHidden'] == 'true') $showHidden = TRUE; | |
// Set content type | |
header('Content-Type: text/html; charset=UTF-8'); | |
?><!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8" /> | |
<title><?php echo htmlspecialchars($translations['indexof'] . ' ' . $base . $dir); ?></title> | |
<link type="image/png" rel="icon" href="<?php echo $base; ?>/?icon=disk" /> | |
<style type="text/css"> | |
/* <![CDATA[ */ | |
html { | |
background-color: #F0F0F0; | |
color: #000; | |
font-family: 'Segoe UI', Tahoma, Arial, sans-serif; | |
font-size: 12px; | |
padding-left: 32px; | |
padding-right: 32px; | |
} | |
body { | |
border: 1px solid #A0A0A0; | |
border-radius: 10px; | |
padding: 48px; | |
min-width: 360px; | |
max-width: 780px; | |
margin: 64px auto; | |
background-color: #FFFFFF; | |
} | |
#errorMessage { | |
display: block; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
min-height: 40px; | |
background-color: rgba(255, 60, 60, 1); | |
color: white; | |
border-bottom: 1px solid red; | |
text-align: center; | |
line-height: 40px; | |
font-weight: bold; | |
-webkit-transition: all 0.5s ease; | |
-moz-transition: all 0.5s ease; | |
-ms-transition: all 0.5s ease; | |
-o-transition: all 0.5s ease; | |
transition: all 0.5s ease; | |
} | |
h1 { | |
font-size: 19.2px; | |
margin: 0 0 11.5px; | |
border-bottom: 1px solid #E3E3E3; | |
font-weight: normal; | |
} | |
th { | |
text-align: left; | |
white-space: nowrap; | |
} | |
td { | |
vertical-align: middle; | |
} | |
/* Name */ | |
th:first-child { | |
padding-right: 32px; | |
} | |
/* Size */ | |
th:first-child + th { | |
padding-right: 16px; | |
} | |
td:first-child + td { | |
text-align: end; | |
padding-right: 16px; | |
white-space: nowrap; | |
} | |
/* Date */ | |
th:last-child { | |
padding-left: 16px; | |
} | |
td:first-child + td + td { | |
padding-left: 16px; | |
padding-right: 8px; | |
white-space: nowrap; | |
} | |
/* Time */ | |
td:last-child { | |
padding-left: 8px; | |
white-space: nowrap; | |
} | |
img { | |
border: 0; | |
} | |
a { | |
text-decoration: none; | |
} | |
a.dir, | |
a.file { | |
margin-left: 20px; | |
} | |
a.dir::before, | |
.file > img { | |
margin-right: 4px; | |
margin-left: -20px; | |
max-width: 16px; | |
max-height: 16px; | |
vertical-align: middle; | |
} | |
a:link { color: blue; } | |
a:visited { color: blue; } | |
a:hover { color: blue; } | |
a:active { color: blue; } | |
a:hover { | |
text-decoration: underline; | |
} | |
p { | |
font-size: 110%; | |
} | |
#UI_goUp { | |
margin-top: 0; | |
float: left; | |
} | |
#UI_showHidden { | |
margin-top: 0; | |
float: right; | |
} | |
table { | |
clear: both; | |
width: 90%; | |
margin: 0 auto; | |
} | |
thead { | |
font-size: 130%; | |
} | |
/* last modified */ | |
th:last-child { | |
text-align: center; | |
} | |
th:hover > a { | |
text-decoration: underline; | |
} | |
tbody > tr:hover { | |
outline: 1px solid ThreeDLightShadow; | |
-moz-outline-radius: .3em; | |
} | |
/* let 'Size' and 'Last Modified' take only as much space as they need and 'Name' all the rest */ | |
td:not(:first-child) { | |
width: 0; | |
} | |
.up { | |
padding: 0 .5em; | |
margin-left: 20px; | |
} | |
.up::before { | |
margin-right: 4px; | |
margin-left: -20px; | |
vertical-align: middle; | |
content: url(<?php echo htmlspecialchars($base); ?>/?icon=up); | |
} | |
.dir::before { | |
content: url(<?php echo htmlspecialchars($base); ?>/?icon=directory); | |
} | |
@keyframes spin { | |
to { transform: rotate(1turn); } | |
} | |
.progress { | |
position: relative; | |
display: inline-block; | |
width: 5em; | |
height: 5em; | |
margin: 0 .5em; | |
font-size: 12px; | |
text-indent: 999em; | |
overflow: hidden; | |
animation: spin 1s infinite steps(8); | |
} | |
.small.progress { | |
font-size: 6px; | |
} | |
.large.progress { | |
font-size: 24px; | |
} | |
.progress:before, | |
.progress:after, | |
.progress > div:before, | |
.progress > div:after { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 2.25em; /* (container width - part width)/2 */ | |
width: .5em; | |
height: 1.5em; | |
border-radius: .2em; | |
background: #eee; | |
box-shadow: 0 3.5em #eee; /* container height - part height */ | |
transform-origin: 50% 2.5em; /* container height / 2 */ | |
} | |
.progress:before { | |
background: #555; | |
} | |
.progress:after { | |
transform: rotate(-45deg); | |
background: #777; | |
} | |
.progress > div:before { | |
transform: rotate(-90deg); | |
background: #999; | |
} | |
.progress > div:after { | |
transform: rotate(-135deg); | |
background: #bbb; | |
} | |
/* ]]> */ | |
</style> | |
</head> | |
<body> | |
<?php if(isset($_GET['error'])) echo '<span id="errorMessage">', htmlspecialchars(isset($translations['errors'][$_GET['error']]) ? $translations['errors'][$_GET['error']] : $translations['errors']['unknown']), '</span>'; ?> | |
<h1><?php echo $translations['indexof'], ' ', $base, $dir, substr($dir, -1) != '/' ? '/' : ''; ?></h1> | |
<?php if($realdir != $realbase) { | |
if($rewrite_directory_urls) { | |
$url = $base . '?dir=' . urlencode($dirnamedir); | |
} else { | |
$url = $base . $dirnamedir; | |
} | |
if(substr($url, -1) != '/') $url .= '/'; | |
?> | |
<p><a class="up" href="<?php echo htmlspecialchars($url); ?>"><?php echo htmlspecialchars($translations['up']); ?></a></p> | |
<?php } if($list_hiddenfiles) { ?> | |
<p><input type="checkbox" id="showHidden" <?php if($showHidden) echo 'checked="checked" '; ?>/><label for="showHidden"><?php echo htmlspecialchars($translations['showhidden']); ?></label></p> | |
<?php } ?> | |
<table id="fileTable"> | |
<thead> | |
<tr> | |
<th data-sorttype="string"><?php echo htmlspecialchars($translations['name']); ?></th> | |
<th data-sorttype="integer"><?php echo htmlspecialchars($translations['size']); ?></th> | |
<th data-sorttype="integer" colspan="2"><?php echo htmlspecialchars($translations['lastchanged']); ?></th> | |
</tr> | |
</thead> | |
<tbody id="fileList"> | |
<?php | |
// Suffixes for file sizes. Defined outside the loop for performance and clean code | |
$file_size_suffixes = array(' B', ' KiB', ' MiB', ' GiB', ' TiB'); | |
$dir_count = array(); | |
foreach($dir_contents as $file) if(@is_dir(@realpath($realdir . DIRECTORY_SEPARATOR . $file))) { | |
$dir_count[$file] = @count(@scandir(@realpath($realdir . DIRECTORY_SEPARATOR . $file), SCANDIR_SORT_NONE)); | |
} | |
// Loop over dir contents | |
foreach($dir_contents as $file) { | |
// Skip special entries | |
if($file == '.' || $file == '..') continue; | |
// Skip hidden files if these are disabled | |
if(!$list_hiddenfiles && $file[0] == '.') continue; | |
// Determine real file (if it cannot be determined, skip it) | |
if(!($realfile = @realpath($realdir . DIRECTORY_SEPARATOR . $file))) continue; | |
// Special case: skip this file itself | |
if($realfile === @realpath(__FILE__)) continue; | |
// Output table row start | |
echo '<tr', ($file[0] == '.' ? (' class="hiddenItem"' . ($showHidden ? '' : ' style="display: none;"')) : ''), '><td data-sortdata="', ($sort_directories_together && @is_dir($realfile) ? '0' : '1'), htmlspecialchars($file), '">'; | |
// Output link | |
if($rewrite_directory_urls && (!$base_dir_autodetected || @is_dir($realfile))) { | |
$url = $base . '?dir=' . urlencode(($dir == '/' ? '/' : ($dir . '/')) . $file); | |
} else { | |
$url = $base . ($dir == '/' ? '/' : ($dir . '/')) . $file; | |
} | |
if(is_dir($realfile)) $url .= '/'; | |
echo '<a href="', htmlspecialchars($url), '" class="', (@is_dir($realfile) ? 'dir' : 'file'), '">'; | |
// Output icon | |
if(!@is_dir($realfile)) { | |
$file_extension = preg_match('/.*(\.[^\.]+)$/', $file, $matches) ? $matches[1] : 'unknown'; | |
echo '<img src="', htmlspecialchars($base), '/?icon=', (isset($icons[$file_extension]) ? htmlspecialchars($file_extension) : 'unknown'), '" data-extension="', htmlspecialchars($file_extension), '" width="16" height="16" alt="', htmlspecialchars($translations['file']), ':" />'; | |
} | |
// Output name | |
echo htmlspecialchars($file), '</a></td>'; | |
// Output size | |
$file_size = 0; | |
if(@is_dir($realfile)) { | |
$file_size = -1 * (max($dir_count) + 1) + @count(@scandir($realfile, SCANDIR_SORT_NONE)); | |
} else { | |
$file_size = filesize64($realfile); | |
if($file_size < 0) $file_size = PHP_INT_MAX; | |
} | |
$file_size_base = $file_size == 0 ? 0 : log($file_size) / log(1024); | |
echo '<td data-sortdata="', $file_size, '">'; | |
if($file_size >= 0) switch($file_size) { | |
case 0: | |
echo '0 B'; | |
break; | |
case PHP_INT_MAX: | |
echo '> '; | |
default: | |
echo round(pow(1024, $file_size_base - floor($file_size_base)), floor($file_size_base) > 2 ? 1 : 0) . $file_size_suffixes[floor($file_size_base)]; | |
} | |
echo '</td>'; | |
// Output modification time | |
$file_mtime = (int) @filemtime($realfile); | |
echo '<td data-sortdata="', $file_mtime, '">', date('Y-m-d', $file_mtime), '</td><td>', date('H:i:s', $file_mtime), '</td>'; | |
// End of row | |
echo '</tr>'; | |
} | |
?> | |
</tbody> | |
</table> | |
<script type="application/javascript"> | |
/* <![CDATA[ */ | |
// Older versions of Firefox (<43) support type-based file icons using a moz-icon:// resource URL | |
if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) { | |
var i, elems = document.getElementsByTagName('img'); | |
for(i in elems) if(elems[i] instanceof HTMLImageElement) { | |
var el = elems[i]; | |
var img = new Image(); | |
img.addEventListener('load', function(ev) { | |
el.setAttribute('src', img.src); | |
}); | |
img.src = 'moz-icon://' + el.getAttribute('data-extension') + '?size=16'; | |
} | |
} | |
<?php if(isset($_GET['error'])) { ?> | |
// Remove the error message after a short timeout | |
if(typeof window.history != 'undefined' && typeof history.replaceState != 'undefined') { | |
setTimeout(function() { | |
document.getElementById('errorMessage').style.opacity = 0; | |
history.replaceState(null, null, location.href.replace(/[\?&]error(=\w+)?/gi, '')); | |
}, 4000); | |
} | |
<?php } ?> | |
// Various functionality directly tied to the file table | |
var table = document.getElementById('fileTable'); | |
if(table.rows.length > 1) { | |
<?php if($list_hiddenfiles) { ?> | |
// Showing/hiding hidden files | |
var hiddenItems = document.getElementsByClassName('hiddenItem'); | |
document.getElementById('showHidden').addEventListener('change', function(e) { | |
for(var i = 0; i < hiddenItems.length; i++) hiddenItems.item(i).style.display = this.checked ? 'table-row' : 'none'; | |
document.cookie = 'showHidden=' + this.checked; | |
}); | |
<?php } ?> | |
// Sorting by clicking a table header | |
var headerOffset = 0; | |
var currentSort = 0; | |
var reverseSort = true; | |
var headers = table.tHead.rows.item(0).children; | |
var rows = Array.prototype.slice.call(table.tBodies.item(0).rows); | |
var getSortFunction = function(currentHeaderOffset) { | |
return function(e) { | |
// Determine sorting order | |
if(currentSort == currentHeaderOffset) { | |
reverseSort = !reverseSort; | |
} else { | |
reverseSort = true; | |
} | |
// Sort by currentHeaderOffset | |
rows.sort(function(a, b) { | |
var aSortData = a.children.item(currentHeaderOffset).getAttribute('data-sortdata'); | |
var bSortData = b.children.item(currentHeaderOffset).getAttribute('data-sortdata'); | |
switch(headers.item(currentHeaderOffset).getAttribute('data-sorttype')) { | |
case 'string': | |
return bSortData.toLowerCase().localeCompare(aSortData.toLowerCase()); | |
case 'integer': | |
return parseInt(bSortData) - parseInt(aSortData); | |
default: | |
return 0; | |
} | |
}); | |
currentSort = currentHeaderOffset; | |
// Reorder the DOM elements | |
for(var j = reverseSort ? rows.length - 1 : 0; reverseSort && j >= 0 || !reverseSort && j < rows.length; j += reverseSort ? -1 : 1) { | |
rows[j].parentNode.appendChild(rows[j]); | |
} | |
}; | |
} | |
for(var i = 0; i < headers.length; i++) { | |
headers.item(i).addEventListener('click', getSortFunction(headerOffset)); | |
headers.item(i).style.cursor = 'pointer'; | |
headerOffset += (headers[i].hasAttribute('colspan') ? parseInt(headers[i].getAttribute('colspan')) : 1); | |
} | |
} | |
/* ]]> */ | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment