diff --git a/standalone/indexer.php b/standalone/indexer.php index de556be..69e743d 100755 --- a/standalone/indexer.php +++ b/standalone/indexer.php @@ -5,7 +5,7 @@ * * @license https://github.com/sixem/eyy-indexer/blob/master/LICENSE GPL-3.0 * @author emy - * @version dev-1.1.8 + * @version 1.1.8 */ /** @@ -15,44 +15,46 @@ */ /* Used to bust the cache and to display footer version number. */ -$version = 'dev-1.1.8'; +$version = '1.1.8'; $config = array( /* Authentication options. */ 'authentication' => false, /* Formatting options. */ 'format' => array( - 'title' => 'Index of %s', /* title format where %s is the current path. */ - 'date' => array('d/m/y H:i', 'd/m/y'), /* date formats (desktop, mobile). */ - 'sizes' => array(' B', ' kB', ' MB', ' GB', ' TB') /* size formats. */ + 'title' => 'Index of %s', /* Title format where %s is the current path. */ + 'date' => array('d/m/y H:i', 'd/m/y'), /* Date formats (desktop, mobile). */ + 'sizes' => array(' B', ' kB', ' MB', ' GB', ' TB') /* Size formats. */ ), /* Favicon options. */ 'icon' => array( - 'path' => '/favicon.ico', /* what favicon to use. */ - 'mime' => 'image/x-icon' /* favicon mime type. */ + 'path' => '/favicon.ico', /* What favicon to use. */ + 'mime' => 'image/x-icon' /* Favicon mime type. */ ), /* Sorting options. Used as default until the client sets their own sorting settings. */ 'sorting' => array( - 'enabled' => false, /* whether the server should sort the items. */ - 'order' => SORT_ASC, /* sorting order. asc or desc. */ - 'types' => 0, /* what item types to sort. 0 = both. 1 = files only. 2 = directories only. */ - 'sort_by' => 'name', /* what to sort by. available options are name, modified, type and size. */ - 'use_mbstring' => false /* enabled mbstring when sorting. */ + 'enabled' => false, /* Whether the server should sort the items. */ + 'order' => SORT_ASC, /* Sorting order. asc or desc. */ + 'types' => 0, /* What item types to sort. 0 = both. 1 = files only. 2 = directories only. */ + 'sort_by' => 'name', /* What to sort by. available options are name, modified, type and size. */ + 'use_mbstring' => false /* Enabled mbstring when sorting. */ ), /* Gallery options. */ 'gallery' => array( - 'enabled' => true, /* whether the gallery plugin should be enabled. */ - 'fade' => 0, /* fade in ms when navigating */ - 'reverse_options' => false, /* reverse search options for images (when hovering over them). */ - 'scroll_interval' => 50, /* break in ms between scroll navigation events. */ - 'list_alignment' => 0, /* list alignment where 0 is right and 1 is left. */ - 'fit_content' => true /* whether the media should be forced to fill the screen space. */ + 'enabled' => true, /* Whether the gallery plugin should be enabled. */ + 'fade' => 0, /* Fade in ms when navigating */ + 'reverse_options' => false, /* Reverse search options for images (when hovering over them). */ + 'scroll_interval' => 50, /* Break in ms between scroll navigation events. */ + 'list_alignment' => 0, /* List alignment where 0 is right and 1 is left. */ + 'fit_content' => true, /* Whether the media should be forced to fill the screen space. */ + 'image_sharpen' => false, /* Attempts to disable browser blurriness on images. */ + 'blur' => true /* Enables gallery background blur (can affect performance negatively on larger directories). */ ), /* Preview options. */ 'preview' => array( - 'enabled' => true, /* whether the preview plugin should be enabled. */ - 'hover_delay' => 75, /* delay in ms before the preview is shown. */ - 'cursor_indicator' => true /* displays a loading cursor while the preview is loading. */ + 'enabled' => true, /* Whether the preview plugin should be enabled. */ + 'hover_delay' => 75, /* Delay in milliseconds before the preview is shown. */ + 'cursor_indicator' => true /* Displays a loading cursor while the preview is loading. */ ), /* Extension that should be marked as media. * These extensions will have potential previews and will be included in the gallery. */ @@ -83,9 +85,13 @@ 'directory_sizes' => array( /* Whether directory sizes should be calculated or not. */ 'enabled' => false, - /* Whether directories should be scanned recursively or not when calculating size. */ + /* Recursively scans the directories when calculating the size. */ 'recursive' => false ), + /* Processing functions. */ + 'processor' => false, + /* Should ? and # characters be encoded when processing URLs. */ + 'encode_all' => false, /* Whether this .php file should be directly accessible. */ 'allow_direct_access' => false, /* Set to 'strict' or 'weak'. @@ -114,8 +120,9 @@ } /* Default configuration values. Used if values from the above config are unset. */ -$defaults = array('authentication' => false,'format' => array('title' => 'Index of %s','date' => array('m/d/y H:i:s', 'd/m/y'),'sizes' => array(' B', ' kB', ' MB', ' GB', ' TB')),'icon' => array('path' => '/favicon.png','mime' => 'image/png'),'sorting' => array('enabled' => false,'order' => SORT_ASC,'types' => 0,'sort_by' => 'name','use_mbstring' => false),'gallery' => array('enabled' => true,'fade' => 0,'reverse_options' => false,'scroll_interval' => 50,'list_alignment' => 0,'fit_content' => true),'preview' => array('enabled' => true,'hover_delay' => 75,'cursor_indicator' => true),'extensions' => array('image' => array('jpg', 'jpeg', 'png', 'gif', 'ico', 'svg', 'bmp', 'webp'),'video' => array('webm', 'mp4', 'ogg', 'ogv')),'style' => array('themes' => array('path' => false,'default' => false),'compact' => false),'filter' => array('file' => false,'directory' => false),'directory_sizes' => array('enabled' => false, 'recursive' => false),'allow_direct_access' => false,'path_checking' => 'strict','footer' => true,'credits' => true,'debug' => false); +$defaults = array('authentication' => false,'format' => array('title' => 'Index of %s','date' => array('m/d/y H:i:s', 'd/m/y'),'sizes' => array(' B', ' kB', ' MB', ' GB', ' TB')),'icon' => array('path' => '/favicon.png','mime' => 'image/png'),'sorting' => array('enabled' => false,'order' => SORT_ASC,'types' => 0,'sort_by' => 'name','use_mbstring' => false),'gallery' => array('enabled' => true,'fade' => 0,'reverse_options' => false,'scroll_interval' => 50,'list_alignment' => 0,'fit_content' => true,'image_sharpen' => false,'blur' => true),'preview' => array('enabled' => true,'hover_delay' => 75,'cursor_indicator' => true),'extensions' => array('image' => array('jpg', 'jpeg', 'png', 'gif', 'ico', 'svg', 'bmp', 'webp'),'video' => array('webm', 'mp4', 'ogg', 'ogv')),'style' => array('themes' => array('path' => false,'default' => false),'compact' => false),'filter' => array('file' => false,'directory' => false),'directory_sizes' => array('enabled' => false, 'recursive' => false),'processor' => false,'encode_all' => false,'allow_direct_access' => false,'path_checking' => 'strict','footer' => true,'credits' => true,'debug' => false); +/* Authentication function. */ function authenticate($users, $realm) { function http_digest_parse($text) @@ -145,20 +152,24 @@ function http_digest_parse($text) return $needed_parts ? false : $data; } + /* Create header for when unathorized. */ function createHeader($realm) { header($_SERVER['SERVER_PROTOCOL'] . '401 Unauthorized'); header('WWW-Authenticate: Digest realm="' . $realm . '",qop="auth",nonce="' . uniqid() . '",opaque="' . md5($realm) . '"'); } + /* Deny access if no digest is set. */ if(empty($_SERVER['PHP_AUTH_DIGEST'])) { createHeader($realm); die('401 Unauthorized'); } + /* Get digest data. */ $data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST']); + /* Deny access if data is invalid or username is unset. */ if(!$data || !isset($users[$data['username']])) { createHeader($realm); @@ -170,6 +181,7 @@ function createHeader($realm) $valid_response = md5($a1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $a2); + /* Deny access if data can't be verified. */ if($data['response'] != $valid_response) { createHeader($realm); @@ -177,8 +189,11 @@ function createHeader($realm) } } - -if($config['authentication'] && is_array($config['authentication']) && count($config['authentication']) > 0) +/* Call authentication function if authentication is enabled. */ +if(isset($config['authentication']) && + $config['authentication'] && + is_array($config['authentication']) && + count($config['authentication']) > 0) { authenticate($config['authentication'], 'Restricted content.'); } @@ -202,11 +217,13 @@ function createHeader($realm) } } +/* Set start time for page render calculations. */ if($config['footer'] === true) { $render = microtime(true); } +/* Enable debugging if enabled. */ if($config['debug'] === true) { ini_set('display_errors', 1); @@ -245,8 +262,10 @@ class Indexer function __construct($path, $options = array()) { + /* Get requested path. */ $requested = rawurldecode(strpos($path, '?') !== false ? explode('?', $path)[0] : $path); + /* Set relative path. */ if(isset($options['path']['relative']) && $options['path']['relative'] !== NULL) { $this->relative = $options['path']['relative']; @@ -254,38 +273,61 @@ function __construct($path, $options = array()) $this->relative = dirname(__FILE__); } + /* Declare array for optional processing of data. */ + $this->processor = array( + 'item' => NULL + ); + + /* Check for passed processing functions. */ + if(isset($options['processor']) && is_array($options['processor'])) + { + if(isset($options['processor']['item'])) + { + $this->processor['item'] = $options['processor']['item']; + } + } + + /* Set remaining options/variables. */ $this->client = isset($options['client']) ? $options['client'] : NULL; $this->allow_direct = isset($options['allow_direct_access']) ? $options['allow_direct_access'] : true; $this->path = rtrim(self::joinPaths($this->relative, $requested), '/'); $this->timestamp = time(); $this->directory_sizes = $options['directory_sizes']; + /* Is requested path a directory? */ if(is_dir($this->path)) { + /* Check if the directory is above the base directory (or same level). */ if(self::isAboveCurrent($this->path, $this->relative)) { $this->requested = $requested; } else { + /* Directory is below the base directory. */ if($options['path_checking'] === 'strict' || $options['path_checking'] !== 'weak') { throw new Exception("requested path (is_dir) is below the public working directory. (mode: {$options['path_checking']})", 1); } else if($options['path_checking'] === 'weak') { + /* If path checking is 'weak' do another test using a 'realpath' alternative instead (string-based approach which doesn't solve links). */ if(self::isAboveCurrent($this->path, $this->relative, false) || is_link($this->path)) { $this->requested = $requested; } else { + /* Even the 'weak' check failed, throw an exception. */ throw new Exception("requested path (is_dir) is below the public working directory. (mode: {$options['path_checking']})", 2); } } } } else { + /* Is requested path a file (this can only be the indexer as we don't have control over any other files)? */ if(is_file($this->path)) { + /* If direct access is disabled, deny access. */ if($this->allow_direct === false) { http_response_code(403); die('Forbidden'); } else { + /* If direct access is allowed, show current directory of script (if it is above base directory). */ $this->path = dirname($this->path); if(self::isAboveCurrent($this->path, $this->relative)) @@ -296,10 +338,12 @@ function __construct($path, $options = array()) } } } else { + /* If requested path is neither a file nor a directory. */ throw new Exception('invalid path. path does not exist.', 4); } } + /* Set extension variables. */ if(isset($options['extensions'])) { $this->types = array(); @@ -317,11 +361,15 @@ function __construct($path, $options = array()) 'ico' => 'image', 'svg' => 'image', 'bmp' => 'image', + 'webp' => 'image', 'webm' => 'video', - 'mp4' => 'video' + 'mp4' => 'video', + 'ogg' => 'video', + 'ogv' => 'video' ); } + /* Set filter variables. */ if(isset($options['filter']) && is_array($options['filter'])) { $this->filter = $options['filter']; @@ -332,6 +380,7 @@ function __construct($path, $options = array()) ); } + /* Set size format variables. */ if(isset($options['format']['sizes']) && $options['format']['sizes'] !== NULL) { $this->format['sizes'] = $options['format']['sizes']; @@ -342,15 +391,26 @@ function __construct($path, $options = array()) $this->format['date'] = $options['format']['date']; } + /* Gets file/directory information and constructs the HTML of the table. */ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modified', $use_mb = false) { + /* Get client timezone offset. */ + $cookies = array( 'timezone_offset' => intval(is_array($this->client) ? (isset($this->client['timezone_offset']) ? $this->client['timezone_offset'] : 0) : 0) ); + $timezone = array( + 'offset' => $cookies['timezone_offset'] > 0 ? -$cookies['timezone_offset'] * 60 : abs($cookies['timezone_offset']) * 60 + ); + + /* Gets the filename of this .php file. Used to hide it from the folder. */ $script_name = basename(__FILE__); + /* Gets the current directory. */ $directory = self::getCurrentDirectory(); + /* Gets the files from the current path using 'scandir'. */ $files = self::getFiles(); + /* Is this the base directory (/)?*/ $is_base = ($directory === '/'); $op = sprintf( @@ -358,10 +418,6 @@ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modi dirname($directory) ); - $timezone = array( - 'offset' => $cookies['timezone_offset'] > 0 ? -$cookies['timezone_offset'] * 60 : abs($cookies['timezone_offset']) * 60 - ); - $data = array( 'files' => array(), 'directories' => array(), @@ -375,6 +431,7 @@ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modi ) ); + /* Hide directories / files if they match the filter or if they are indexer components. */ foreach($files as $file) { if($file[0] === '.') continue; @@ -421,23 +478,43 @@ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modi { $item = &$data['directories'][$index]; - $item['name'] = $use_mb === true ? mb_strtolower($dir[1], 'UTF-8') : strtolower($dir[1]); + /* We only need to set 'name' key if we're sorting by name. */ + if($sort_type === 'name') + { + $item['name'] = $use_mb === true ? mb_strtolower($dir[1], 'UTF-8') : strtolower($dir[1]); + } + + /* Set directory data values. */ $item['modified'] = self::getModified($dir[0], $timezone['offset']); $item['type'] = 'directory'; $item['size'] = $this->directory_sizes['enabled'] ? ($this->directory_sizes['recursive'] ? self::getDirectorySizeRecursively($dir[0]) : self::getDirectorySize($dir[0])) : 0; + $item['url'] = rtrim(self::joinPaths($this->requested, $dir[1]), '/'); } foreach($data['files'] as $index => $file) { $item = &$data['files'][$index]; - $item['name'] = $use_mb === true ? mb_strtolower($file[1], 'UTF-8') : strtolower($file[1]); + /* We only need to set 'name' key if we're sorting by name. */ + if($sort_type === 'name') + { + $item['name'] = $use_mb === true ? mb_strtolower($file[1], 'UTF-8') : strtolower($file[1]); + } + + /* Set file data values. */ $item['type'] = self::getFileType($file[1]); $item['size'] = self::getSize($file[0]); $item['modified'] = self::getModified($file[0], $timezone['offset']); - $item['path'] = rtrim(self::joinPaths($this->requested, $file[1]), '/'); + $item['url'] = rtrim(self::joinPaths($this->requested, $file[1]), '/'); + } + + /* Pass data to processor if it is set. */ + if($this->processor['item']) + { + $data = $this->processor['item']($data, $this); } + /* Sort items server-side. */ if($sorting) { if($sort_items === 0 || $sort_items === 1) @@ -459,16 +536,17 @@ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modi } } + /* Iterate over the directories, get and store data. */ foreach($data['directories'] as $dir) { if($this->directory_sizes['enabled']) { $data['size']['total'] = ($data['size']['total'] + $dir['size']); } - + $op .= sprintf( '[%s]%s', - $dir[1], rtrim(self::joinPaths($this->requested, $dir[1]), '/'), $dir[1], $dir['modified'][0], $dir['modified'][1] + $dir[1], $dir['url'], $dir[1], $dir['modified'][0], $dir['modified'][1] ); if($data['recent']['directory'] === 0 || $dir['modified'][0] > $data['recent']['directory']) @@ -485,6 +563,7 @@ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modi $op .= '-'; } + /* Iterate over the files, get and store data. */ foreach($data['files'] as $file) { $data['size']['total'] = ($data['size']['total'] + $file['size'][0]); @@ -501,7 +580,7 @@ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modi $op .= sprintf( '%s', - (($file['type'][0] === 'image' || $file['type'][0] === 'video' ? true : false) ? ' class="preview" ' : ' '), $file['path'], $file[1] + (($file['type'][0] === 'image' || $file['type'][0] === 'video' ? true : false) ? ' class="preview" ' : ' '), $file['url'], $file[1] ); $op .= sprintf( @@ -516,7 +595,7 @@ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modi $op .= sprintf( '%s', - $file['type'][0], $file['path'], $file[1], ('[Save][Download]') + $file['type'][0], $file['url'], $file[1], ('[Save][Download]') ); } @@ -527,11 +606,14 @@ public function buildTable($sorting = false, $sort_items = 0, $sort_type = 'modi return $op; } + /* Gets the current files from set path. */ private function getFiles() { return scandir($this->path, SCANDIR_SORT_NONE); } + /* A 'realpath' alternative, doesn't resolve links, relies purely on strings instead. + * Used with 'weak' path checking. */ private function removeDotSegments($input) { $output = ''; @@ -564,16 +646,19 @@ private function removeDotSegments($input) return $output; } + /* Checks if $path is above $base. Reverse path traversal is bad? */ private function isAboveCurrent($path, $base, $use_realpath = true) { return self::startsWith($use_realpath ? realpath($path) : self::removeDotSegments($path), $use_realpath ? realpath($base) : self::removeDotSegments($base)); } + /* Some data is stored in $this->data, this retrieves that. */ public function getLastData() { return isset($this->data) ? $this->data : false; } + /* Gets the current directory. */ public function getCurrentDirectory() { $requested = trim($this->requested); @@ -586,6 +671,7 @@ public function getCurrentDirectory() } } + /* Identifies file type by matching it against the extension arrays. */ private function getFileType($filename) { $extension = strtolower(ltrim(pathinfo($filename, PATHINFO_EXTENSION), '.')); @@ -593,6 +679,7 @@ private function getFileType($filename) return array(isset($this->types[$extension]) ? $this->types[$extension] : 'other', $extension); } + /* Converts the current path into clickable a[href] links. */ public function makePathClickable($path) { $paths = explode('/', ltrim($path, '/')); @@ -612,11 +699,13 @@ public function makePathClickable($path) return $op; } + /* Formats a unix timestamp. */ private function formatDate($format, $stamp, $modifier = 0) { return gmdate($format, $stamp + $modifier); } + /* Gets the last modified date of a file. */ private function getModified($path, $modifier = 0) { $stamp = filemtime($path); @@ -636,11 +725,13 @@ private function getModified($path, $modifier = 0) ); } + /* Gets a client cookie key (if it exists). */ private function getCookie($key, $default = NULL) { return isset($_COOKIE[$key]) ? $_COOKIE[$key] : $default; } + /* Gets the size of a file. */ private function getSize($path) { $fs = filesize($path); @@ -649,48 +740,65 @@ private function getSize($path) return array($size, self::readableFilesize($size)); } + /* Gets the size of a directory. */ private function getDirectorySize($path) { $size = 0; - foreach(scandir($path, SCANDIR_SORT_NONE) as $file) + try { - if($file[0] === '.') + foreach(scandir($path, SCANDIR_SORT_NONE) as $file) { - continue; - } else { - $filesize = filesize(self::joinPaths($path, $file)); - - if($filesize && $filesize > 0) + if($file[0] === '.') { - $size += $filesize; + continue; + } else { + $filesize = filesize(self::joinPaths($path, $file)); + + if($filesize && $filesize > 0) + { + $size += $filesize; + } } } + } catch (Exception $e) + { + $size += 0; } return $size; } + /* Gets the full size of a director using. */ private function getDirectorySizeRecursively($path) { $size = 0; $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); - foreach($iterator as $file) + try { - if($file->isDir()) + foreach($iterator as $file) { - continue; - } else { - $size += filesize($file->getPathname()); + if($file->isDir()) + { + continue; + } else { + $size += filesize($file->getPathname()); + } } + } catch (Exception $e) + { + $size += 0; } return $size; } + /* Converts bytes to a readable file size. */ private function readableFilesize($bytes, $decimals = 2) { + /* If file size is -1, return '-'. + * This can happen to very large file size (PHP/Server limitations). */ if($bytes === -1) return '-'; $factor = floor((strlen($bytes) - 1) / 3); @@ -707,11 +815,13 @@ private function readableFilesize($bytes, $decimals = 2) return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $x; } + /* Checks if a string starts with a string. */ private function startsWith($haystack, $needle) { return $needle === '' || strrpos($haystack, $needle, - strlen($haystack)) !== false; } + /* Concentrates path components into a merged path. */ public function joinPaths(...$params) { $paths = array(); @@ -725,13 +835,16 @@ public function joinPaths(...$params) } } -$client = isset($_COOKIE['ei-client']) ? $_COOKIE['ei-client'] : NULL; +/* Is cookie set? */ +$client = isset($_COOKIE['ei-client']) ? $_COOKIE['ei-client'] : NULL; +/* If client cookie is set, parse it. */ if($client) { $client = json_decode($client, true); } +/* Validate that the cookie is a valid array. */ $validate = is_array($client); $cookies = array( @@ -741,12 +854,13 @@ public function joinPaths(...$params) ) ); -/* override the config value if the cookie value is set */ +/* Override the config value if the cookie value is set */ if($validate && isset($client['style']['compact']) && $client['style']['compact']) { $config['style']['compact'] = $client['style']['compact']; } +/* Set sorting settings. */ $sorting = array( 'enabled' => $config['sorting']['enabled'], 'order' => $config['sorting']['order'], @@ -775,6 +889,7 @@ public function joinPaths(...$params) $sorting['enabled'] = true; } +/* Is 'INDEXER_BASE_PATH' set? Change base path.*/ if(isset($_SERVER['INDEXER_BASE_PATH'])) { $base_path = $_SERVER['INDEXER_BASE_PATH']; @@ -784,6 +899,7 @@ public function joinPaths(...$params) try { + /* Call class with options set. */ $indexer = new Indexer( rawurldecode($_SERVER['REQUEST_URI']), array( @@ -799,6 +915,7 @@ public function joinPaths(...$params) 'filter' => $config['filter'], 'extensions' => $config['extensions'], 'path_checking' => strtolower($config['path_checking']), + 'processor' => $config['processor'], 'allow_direct_access' => $config['allow_direct_access'] ) ); @@ -816,6 +933,7 @@ public function joinPaths(...$params) exit('

Fatal error - Exiting.

'); } +/* Call 'buildTable', get content. */ $contents = $indexer->buildTable( $sorting['enabled'] ? $sorting['order'] : false, $sorting['enabled'] ? $sorting['types'] : 0, @@ -825,6 +943,7 @@ public function joinPaths(...$params) $data = $indexer->getLastData(); +/* Set some data like file count etc. */ $counts = array( 'files' => count($data['files']), 'directories' => count($data['directories']) @@ -832,27 +951,30 @@ public function joinPaths(...$params) $themes = array(); +/* Are themes enabled? */ if($config['style']['themes']['path']) { + /* Trim the string of set directory path. */ $directory = rtrim($indexer->joinPaths($base_path, $config['style']['themes']['path']), '/'); + /* If set theme path is valid directory, scan it for .css files and add them to the theme pool. */ if(is_dir($directory)) { foreach(preg_grep('~\.css$~', scandir($directory, SCANDIR_SORT_NONE)) as $theme) { - if($theme[0] != '.') array_push($themes, substr($theme, 0, strrpos($theme, '.'))); + if($theme[0] !== '.') array_push($themes, substr($theme, 0, strrpos($theme, '.'))); } } + /* Prepend default theme to the beginning of the array. */ if(count($themes) > 0) array_unshift($themes, 'default'); } -// $current_theme = count($themes) > 0 && is_array($client) && isset($client['style']['theme']) ? (in_array($client['style']['theme'], $themes) ? $client['style']['theme'] : NULL) : NULL; - $current_theme = NULL; if(count($themes) > 0) { + /* Check if a theme is already set. */ if(is_array($client) && isset($client['style']['theme'])) { $current_theme = in_array($client['style']['theme'], $themes) ? $client['style']['theme'] : NULL; @@ -864,6 +986,7 @@ public function joinPaths(...$params) $compact = NULL; +/* Apply compact mode if that is set. */ if(is_array($client) && isset($client['style']['compact'])) { $compact = $client['style']['compact']; @@ -872,7 +995,9 @@ public function joinPaths(...$params) } $footer = $config['footer'] === true || $config['credits'] !== false; -?> <?=sprintf($config['format']['title'], $indexer->getCurrentDirectory());?> ' . PHP_EOL : ''?>
data-count="files">
data-count="directories">
Index of makePathClickable($indexer->getCurrentDirectory());?>
FilenameModifiedSizeOptions
<?=sprintf($config['format']['title'], $indexer->getCurrentDirectory());?> ' . PHP_EOL : ''?>
data-count="files">
data-count="directories">
Index of makePathClickable($indexer->getCurrentDirectory());?>
FilenameModifiedSizeOptions
'; @@ -910,7 +1035,9 @@ public function joinPaths(...$params) 'fade' => $config['gallery']['fade'], 'scroll_interval' => $config['gallery']['scroll_interval'], 'list_alignment' => $config['gallery']['list_alignment'], - 'fit_content' => $config['gallery']['fit_content'] + 'fit_content' => $config['gallery']['fit_content'], + 'image_sharpen' => $config['gallery']['image_sharpen'], + 'blur' => $config['gallery']['blur'] ), 'extensions' => array( 'image' => $config['extensions']['image'], @@ -925,45 +1052,46 @@ public function joinPaths(...$params) 'compact' => $config['style']['compact'] ), 'format' => array_intersect_key($config['format'], array_flip(array('sizes', 'date'))), + 'encode_all' => $config['encode_all'], 'timestamp' => $indexer->timestamp, 'debug' => $config['debug'], 'mobile' => false -)));?> \ No newline at end of file +function(){var d=JSON.parse($("#__INDEXER_DATA__").html()),u={store:{preview:{},defaults:{},selection:{},selected:null,refresh:!1},debounce:function(t){var n;return function(e){n&&clearTimeout(n),n=setTimeout(t,100,e)}},checkNested:function(e){for(var t=arguments.length,n=new Array(1 table > tbody > tr.file > td:first-child > a:visible").each(function(e,t){var n=$(t).text().split(".").pop().toLowerCase().trim();r.includes(n)||r.push(n)}),'wget -r -np -nH -nd -e robots=off --accept "'.concat(r.join(","),'" "').concat(e,'"')},stripUrl:function(e){return e.includes("?")?e.split("?")[0]:e},identifyExtension:function(e){var t=e.split(".").pop().toLowerCase();return d.extensions.image.includes(t)?[t,0]:d.extensions.video.includes(t)?[t,1]:null},client:{get:function(){var n,t=["gallery","sort","style"],r={gallery:{reverse_options:d.gallery.reverse_options,list_alignment:d.gallery.list_alignment,fit_content:d.gallery.fit_content,extensions:{image:d.extensions.image,video:d.extensions.video},autoplay:!0,volume:.25},style:{compact:d.style.compact,theme:!1}};try{n=JSON.parse(Cookies.get("ei-client")),t.forEach(function(e){Object.prototype.hasOwnProperty.call(n,e)||(n[e]=Object.prototype.hasOwnProperty.call(r,e)?r[e]:{})});var i=!1;Object.keys(r).forEach(function(t){Object.keys(r[t]).forEach(function(e){Object.prototype.hasOwnProperty.call(n[t],e)||(n[t][e]=r[t][e],i=!0)})}),i&&u.client.set(n)}catch(e){n={},d.style.themes.set&&(r.style.theme=d.style.themes.set),t.forEach(function(e){return n[e]={}}),u.client.set(Object.assign(n,r))}return n},set:function(e,t){var n=1")).parent().wrap($("
",a)).parent().prepend($("
",l))},section:function(e,t){var n=1",{class:"section","data-key":e}).append($("
",{class:"header",text:n||u.capitalize(e)}))},select:function(e,t,n){var r=2",1",e);return null!==r&&!0===r(n,t,i)&&(n[0].selected=!0,i[0].selectedIndex=t),n})),i},check:function(e,t){var n=0",Object.assign(n,{type:"checkbox"}));return o[0].checked=i,o}},close:function(){$("body > div.focus-overlay, body > div.settings-container").remove()},update:{style:{theme:function(e){u.theme.set(!1===e?null:e,!1)},compact:function(e){$("body")[e?"addClass":"removeClass"]("compact")}},gallery:{list_alignment:function(t){var e,n,r;u.gallery.instance&&((e=["body > div.gallery-container > div.content-container > div.media > div.loader","body > div.gallery-container > div.content-container > div.list","body > div.gallery-container > div.content-container > div.list > div.drag"]).forEach(function(e){return 0===t?$(e).removeClass("reversed"):$(e).addClass("reversed")}),n=$(e[1]).detach(),r="body > div.gallery-container > div.content-container > div.media",1===t?n.insertBefore(r):n.insertAfter(r),u.gallery.instance.store.list.reverse=0!==t)},reverse_options:function(e){var t;u.gallery.instance&&(u.gallery.instance.store.reverse_options=e,0<(t=$("body > div.gallery-container > div.content-container > div.media > div.wrapper > div.cover .reverse")).length&&t.remove())},autoplay:function(e){u.gallery.instance&&(u.gallery.instance.store.autoplay=e)},fit_content:function(e){var t;u.gallery.instance&&(u.gallery.instance.store.fit_content=e,(t=$("body > div.gallery-container > div.content-container > div.media > div.wrapper"))&&e?(t.addClass("fill"),u.store.refresh=!0,u.store.selected=null):t&&(t.removeClass("fill"),[".cover",".cover img","video"].forEach(function(e){return $(e).css({height:"",width:""})})))}}},options:{gather:function(e){var i={};return e.find(["select",'input[type="checkbox"]'].join(",")).each(function(e,t){var n,r;(t=$(t))[0].hasAttribute("name")&&(n=t.attr("name"),r=t[0].hasAttribute("data-key")?t.attr("data-key"):t.closest(".section").attr("data-key"),Object.prototype.hasOwnProperty.call(i,r)||(i[r]={}),t.is("select")?i[r][n]=t[0].selectedIndex:t.is('input[type="checkbox"]')&&(i[r][n]=t[0].checked))}),i},set:function(o,e){var a=(a=1 div.settings-container").length||(0===$("body > div.focus-overlay").length&&$("
",{class:"focus-overlay"}).appendTo($("body")).on("click",function(){return u.settings.close()}),e=$("
",{class:"settings-container"}),l=u.client.get(),t=[function(e,t){var n=0",{class:"wrapper"}).append(t.map(function(e){return 0",{class:"bottom"}).appendTo(e),$("
",{class:"apply ns",text:"Apply"}).appendTo(n).on("click",function(){return u.settings.apply(e,l)}),$("
",{class:"cancel ns",text:"Cancel"}).appendTo(n).on("click",function(){return u.settings.close()}),$("body").append(e),e.find("div.section > .option.interactable").on("mouseup",function(e){var t;window.getSelection().toString()||0<(t=$(e.currentTarget).find('input[type="checkbox"]')).length&&!$(e.target).is("input")&&(t[0].checked=!t[0].checked)}))}},menu:{create:function(){var n=$("
",{class:"menu"}).appendTo($("body")),e=[{text:"[Show] Filter",id:"filter"},{text:"[Copy] WGET",id:"copy"}];return!0===d.gallery.enabled&&0<$("body > table > tbody > tr.file > td > a.preview").length&&e.unshift({text:"[Open] Gallery",id:"gallery"}),u.settings.available()&&e.unshift({text:"[Open] Settings",id:"settings",class:"settings"}),e.forEach(function(e){var t=$("
",{text:e.text,class:"ns"+(Object.prototype.hasOwnProperty.call(e,"class")?" "+e.class:"")}).appendTo(n);Object.prototype.hasOwnProperty.call(e,"id")&&t.attr("id",e.id)}),n[0].addEventListener("click",function(e){"DIV"==e.target.tagName&&("gallery"==e.target.id&&!0===d.gallery.enabled?(u.gallery.load(null),u.menu.toggle(!1)):"copy"==e.target.id?(u.copyTextToClipboard(u.generateWget()),u.menu.toggle(!1)):"settings"==e.target.id?(u.settings.show(),u.menu.toggle(!1)):"filter"==e.target.id&&(u.filter.toggle(),u.menu.toggle()))}),n},toggle:function(e){var t=0 div.menu");return n.css("display","boolean"==typeof t?t?"inline-block":"none":n.is(":hidden")?"inline-block":"none"),$("body > .top-bar > div.extend").html(n.is(":hidden")?"▾":"▴"),n.is(":hidden")}},theme:{set:function(e,t){var n=0 link[rel="stylesheet"]').filter(function(e,t){return t.hasAttribute("href")&&t.getAttribute("href").match(new RegExp("/(themes)/","i"))});if(null===(d.style.themes.set=n)||!n)return i.each(function(e,t){return t.remove()}),!1;r&&u.client.set(u.client.get().style.theme=n),$("head").append($("",{rel:"stylesheet",type:"text/css",href:"".concat(d.style.themes.path,"/").concat(n,".css")})),i.each(function(e,t){return t.remove()})}},filter:{apply:function(e){var o=0 table > tbody > tr.file, body > table > tbody > tr.directory").each(function(e,t){if(t=$(t),!0===a.reset)return t[0].removeAttribute("hidden"),!0;var n,r=t.hasClass("file"),i=t.hasClass("directory");try{l={valid:!0,data:t.find("td:eq(0)").attr("data-raw").match(new RegExp(o,"i"))}}catch(e){l={valid:!1,reason:e}}l.valid&&l.data?t[0].removeAttribute("hidden"):t[0].setAttribute("hidden",""),(l.valid&&l.data&&r||s&&l.valid&&l.data&&i)&&(n=t.find("td:eq(2)").attr("data-raw"),isNaN(n)||(a.size=a.size+parseInt(n))),l.valid&&l.data?r?a.shown.files++:a.shown.directories++:r?a.hidden.files++:a.hidden.directories++});var t={container:$("body > div.top-bar")};["size","files","directories"].forEach(function(e){return t[e]=t.container.find('[data-count="'.concat(e,'"]'))}),Object.prototype.hasOwnProperty.call(u.store.defaults,"top_values")||(u.store.defaults.top_values={size:t.size.text(),files:t.files.text(),directories:t.directories.text()}),t.size.text(a.reset?u.store.defaults.top_values.size:u.getReadableSize(a.size)),t.files.text(a.reset?u.store.defaults.top_values.files:"".concat(a.shown.files," file").concat(1===a.shown.files?"":"s")),t.directories.text(a.reset?u.store.defaults.top_values.directories:"".concat(a.shown.directories," ").concat(1===a.shown.directories?"directory":"directories"));var n,r=$("body > div.menu > #gallery"),i=$("body > table tr.file:visible a.preview").length,c=$("body > div.filter-container div.status");0 div.filter-container"),t=e.find('input[type="text"]');e.is(":visible")?e.hide():(t.val(""),u.filter.apply(null),e.show()),t.focus()}},dates:{format:function(e,t){function n(e,t){return c[e]?c[e]():t}function r(e,t){for(e=String(e);e.length table > tbody > tr.directory > td:nth-child(2), tbody tr.file > td[data-raw]:nth-child(2)").each(function(e,t){t=$(t);var r=parseInt(t.attr("data-raw")),n=function(e){if(0===e)return"Now";if(e<0)return!1;for(var t={year:31556926,month:2629743,week:604800,day:86e3,hour:3600,minute:60,second:1},n=Object.keys(t),r=n.length-1,i=!1,o=0;o"):t.find("> span");!0===a&&(d.format.date.forEach(function(e,t){var n;t<=1&&(n=$("",{text:u.dates.format(e,r)}),1 div.top-bar > .directory-info div[data-count="files"], \t\t\t\t\t\tbody > div.top-bar > .directory-info div[data-count="directories"]').each(function(e,t){(t=$(t))[0].hasAttribute("data-raw")&&$(t).attr("title","Newest: "+u.dates.format(d.format.date[0],parseInt(t.attr("data-raw"))))})}(e,n)}},sort:{load:function(){var e=document.querySelectorAll("table th span[sortable]");if(Object.prototype.hasOwnProperty.call(d,"sorting")&&d.sorting.enabled&&(0===d.sorting.types||1===d.sorting.types)){var t,n="asc"===d.sorting.order,r=null;switch(d.sorting.sort_by){case"name":r=0;break;case"modified":r=1;break;case"size":r=2;break;case"type":r=3;break;default:r=null}null===r||0<(t=$(e[r]).closest("th")).length&&(t[0].asc=n,t.find("> span.sort-indicator").addClass(n?"down":"up").fadeIn(350))}}},gallery:{instance:null,load:function(e){var t=0",t);var n=$("body > div.preview-container > video");if(u.gallery.instance&&!1!==u.gallery.instance){u.gallery.instance.store.continue.video=0 .filter-container"),func:u.filter.toggle},{e:$("body > div.menu"),func:u.menu.toggle}].forEach(function(e){0 table > tbody > tr.file > td:first-child > a.preview").forEach(function(e){var t=e.parentNode,n=t.parentNode;if(n.hasAttribute("hidden"))return!1;var r=e.getAttribute("href");void 0!==r&&"undefined"!=typeof name&&i.push({url:r,name:t.getAttribute("data-raw"),size:n.querySelector("td:nth-child(3)").innerHTML})}),i},events:{scroll:function(){var e=$("body > div.path"),t=$("body > div.top-bar > div.directory-info > div.quick-path");$(window).scrollTop()",{class:"quick-path","data-view":"desktop"}).html($("body > div.path").html()),$("body > div.top-bar > div.directory-info").append(t)),t.fadeIn(150).css("display","inline-block"))},sortTableColumn:function(e){var t=$(e).parent(),n=t.index(),r=$(e).is("th")?e:t[0],i=$("body > table"),o={directories:i.find("tbody > tr.directory").toArray(),files:i.find("tbody > tr.file").toArray()},a=!(Object.prototype.hasOwnProperty.call(d.sorting,"directory_sizes")&&d.sorting.directory_sizes)&&Object.prototype.hasOwnProperty.call(d.sorting,"sort_by")&&(2===n||3===n);0!==d.sorting.types&&2!==d.sorting.types||a||o.directories.sort(u.comparer($(r).index())),0!==d.sorting.types&&1!==d.sorting.types||o.files.sort(u.comparer($(r).index())),r.asc=!r.asc,$("body > table > thead > tr > th span.sort-indicator").removeClass("up down"),t.find("> span.sort-indicator").addClass(r.asc?"down":"up").show();var l=u.client.get();l.sort.ascending=r.asc?1:0,l.sort.row=n,u.client.set(l),r.asc||(0!==d.sorting.types&&2!==d.sorting.types||a||(o.directories=o.directories.reverse()),0!==d.sorting.types&&1!==d.sorting.types||(o.files=o.files.reverse())),Object.keys(o).forEach(function(e){return o[e].forEach(function(e){return i.append(e)})}),u.store.refresh=!0,u.store.selected=null,$("body > table > tbody > tr.last").removeClass("last")}},bind:function(){$(document).off("keydown").on("keydown",function(t){var e;t.shiftKey&&70===t.keyCode?(t.preventDefault(),u.filter.toggle()):27===t.keyCode?u.overlay.hide(function(e){!0===e&&t.preventDefault()}):71===t.keyCode&&!0===d.gallery.enabled&&(!1!==(e=$("body > .filter-container")).is(":visible")&&e.find('input[type="text"]').is(":focus")||(u.gallery.load(null),u.menu.toggle(!1)))}),$(window).on("scroll",u.debounce(function(){u.events.scroll()}))}};$("body > div.top-bar > div.extend").on("click",function(e){u.menu.toggle(e.currentTarget)}),$("body > div.filter-container > div.close > span").on("click",function(){u.filter.toggle()}),$('body > div.filter-container > div input[type="text"]').on("input",function(e){var t=$(e.currentTarget);u.filter.apply(t.val())}),document.querySelector("body > table").addEventListener("click",function(e){var t;"SPAN"==e.target.tagName&&e.target.hasAttribute("sortable")?u.events.sortTableColumn(e.target):!0===d.gallery.enabled&&"A"==e.target.tagName&&"preview"==e.target.className&&(e.preventDefault(),t=$(e.target).closest("table").find("tr.file:visible").filter(function(e,t){return 0<$(t).find("a.preview").length}).index($(e.target).closest("tr.file")),u.gallery.load(-1!==t?t:0))},!0),window.addEventListener("resize",u.debounce(function(){d.debug&&console.log("resized"),d.mobile=Modernizr.mq("(max-width: 640px)"),u.gallery.instance&&(u.gallery.instance.store.mobile=d.mobile,u.gallery.instance.update.listWidth())})),$(document).ready(function(){var l,e,t,n,r,i,o;u.bind(),u.dates.load(),$('body > .filter-container > input[type="text"]').val(""),d.mobile=Modernizr.mq("(max-width: 640px)"),u.menu.create().css({top:$("body > div.top-bar").innerHeight()+"px",visibility:"unset",display:"none"}),!1===d.mobile&&!0===d.preview.enabled&&(document.querySelectorAll("body > table tr.file > td > a.preview").forEach(function(e,t){e.itemIndex=t}),l={},0<(e=$("body").find("> table tr.file > td > a.preview")).length&&(t=e[0].getAttribute("href"),(n=u.identifyExtension(u.stripUrl(t)))&&(i=(r=_slicedToArray(n,2))[0],o=r[1],l[e[0].itemIndex]=window.hoverPreview(e[0],{delay:d.preview.hover_delay,cursor:d.preview.cursor_indicator,encodeAll:d.encode_all,force:{extension:i,type:o}}))),document.querySelector("body > table").addEventListener("mouseenter",function(e){var t,n,r,i,o,a;"A"==e.target.tagName&&"preview"==e.target.className&&(t=e.target.itemIndex,Object.prototype.hasOwnProperty.call(l,t)||(n=e.target.getAttribute("href"),(r=u.identifyExtension(u.stripUrl(n)))&&(o=(i=_slicedToArray(r,2))[0],a=i[1],l[t]=window.hoverPreview(e.target,{delay:d.preview.hover_delay,cursor:d.preview.cursor_indicator,encodeAll:d.encode_all,force:{extension:o,type:a}}))))},!0)),u.events.scroll()}),u.client.get(),u.sort.load(),d.debug&&console.log("config",d)}(); \ No newline at end of file