diff --git a/README.md b/README.md index 336c02e6..65c6905f 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,11 @@ [![Paypal](https://img.shields.io/badge/Donate-Paypal-lightgrey.svg?style=flat-square)](https://www.paypal.me/prasathmani) ![GitHub Sponsors](https://img.shields.io/github/sponsors/prasathmani) -> TinyFileManager is web based PHP file manager and it is a simple, fast and small size in single-file PHP file that can be dropped into any folder on your server, multi-language ready web application for storing, uploading, editing and managing files and folders online via web browser. The Application runs on PHP 5.5+, It allows the creation of multiple users and each user can have its own directory and a build-in support for managing text files with cloud9 IDE and it supports syntax highlighting for over 150+ languages and over 35+ themes. +> TinyFileManager is a versatile web-based PHP file manager designed for simplicity and efficiency. This lightweight single-file PHP application can be effortlessly integrated into any server directory, allowing users to store, upload, edit, and manage files and folders directly through their web browser. +With multi-language support and compatibility with PHP 5.5+, TinyFileManager enables the creation of individual user accounts, each with its dedicated directory. The platform also includes built-in functionality for handling text files using the Cloud9 IDE. +Featuring syntax highlighting for over 150 languages and more than 35 themes, TinyFileManager offers a comprehensive solution for file management in an online environment. -**Caution!** _Avoid utilizing this script as a standard file manager in public spaces. It is imperative to remove this script from the server after completing any tasks._ +**Caution!** _Avoid utilizing this script as a standard file manager in public spaces. It is imperative to remove this script from the server after completing any tasks._ ## Demo @@ -46,81 +48,23 @@ To enable/disable authentication set `$use_auth` to true or false. ### :loudspeaker: Features -- :cd: Open Source, light and extremely simple -- :iphone: Mobile friendly view for touch devices -- :information_source: Basic features likes Create, Delete, Modify, View, Download, Copy and Move files -- :arrow_double_up: Ajax Upload, Ability to drag & drop, upload from URL, multiple files upload with file extensions filter -- :file_folder: Ability to create folders and files -- :gift: Ability to compress, extract files (`zip`, `tar`) -- :sunglasses: Support user permissions - based on session and each user root folder mapping -- :floppy_disk: Copy direct file URL -- :pencil2: Cloud9 IDE - Syntax highlighting for over `150+` languages, Over `35+` themes with your favorite programming style -- :page_facing_up: Google/Microsoft doc viewer helps you preview `PDF/DOC/XLS/PPT/etc`. 25 MB can be previewed with the Google Drive viewer -- :zap: Backup files and IP blacklist and whitelist -- :mag_right: Search - Search and filter files using `datatable js` -- :file_folder: Exclude folders and files from listing -- :globe_with_meridians: Multi-language(32+) support and for translations `translation.json` is file required -- :bangbang: lots more... - -## Deploy by Docker - -Make sure you have **already installed docker**, [Install reference](https://docs.docker.com/engine/install/) - -> **Notice:** Your need an absolute path, and it will be served by tinyfilemanager. -> -> If you want to serve this project at **raspberry pi or another special platform**, you can download project and **build image by yourself**. - -You can execute this following commands: - -```shell -$ docker run -d -v /absolute/path:/var/www/html/data -p 80:80 --restart=always --name tinyfilemanager tinyfilemanager/tinyfilemanager:master -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -648dfba9c0ff tinyfilemanager/tinyfilemanager:master "docker-php-entrypoi…" 4 minutes ago Up 4 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp tinyfilemanager -``` -Access `http://127.0.0.1/` and enter default username and password, then enjoy it. - -DockerHub: [https://hub.docker.com/r/tinyfilemanager/tinyfilemanager](https://hub.docker.com/r/tinyfilemanager/tinyfilemanager) - -#### How to change config within docker - -Origin: - -```php -// Root path for file manager -// use absolute path of directory i.e: '/var/www/folder' or $_SERVER['DOCUMENT_ROOT'].'/folder' -$root_path = $_SERVER['DOCUMENT_ROOT']; - -// Root url for links in file manager.Relative to $http_host. Variants: '', 'path/to/subfolder' -// Will not working if $root_path will be outside of server document root -$root_url = ''; -``` - -Modified: - -```php -// Root path for file manager -// use absolute path of directory i.e: '/var/www/folder' or $_SERVER['DOCUMENT_ROOT'].'/folder' -$root_path = $_SERVER['DOCUMENT_ROOT'].'/data'; - -// Root url for links in file manager.Relative to $http_host. Variants: '', 'path/to/subfolder' -// Will not working if $root_path will be outside of server document root -$root_url = 'data/'; -``` - -Then, change another config what you want, and add a new volume `-v /absolute/path/index.php:/var/www/html/index.php` in `docker run` command, like this: - -```shell -$ docker run -d -v /absolute/path:/var/www/html/data -v /absolute/path/index.php:/var/www/html/index.php -p 80:80 --restart=always --name tinyfilemanager tinyfilemanager/tinyfilemanager:master -``` - -#### Stop running - -If you want to stop a running docker service, or you want to restart a service, you should stop it first, or you got `docker: Error response from daemon: Conflict. The container name "/tinyfilemanager" is already in use by container ...` problem. You can execute this command: - -```shell -$ docker rm -f tinyfilemanager -``` +- :cd: **Open Source:** Lightweight, minimalist, and extremely simple to set up. +- :iphone: **Mobile Friendly:** Optimized for touch devices and mobile viewing. +- :information_source: **Core Features:** Easily create, delete, modify, view, download, copy, and move files. +- :arrow_double_up: **Advanced Upload Options:** Ajax-powered uploads with drag-and-drop support, URL imports, and multi-file uploads with extension filtering. +- :file_folder: **Folder & File Management:** Create and organize folders and files effortlessly. +- :gift: **Compression Tools:** Compress and extract files in `zip` and `tar` formats. +- :sunglasses: **User Permissions:** User-specific root folder mapping and session-based access control. +- :floppy_disk: **Direct URLs:** Easily copy direct URLs for files. +- :pencil2: **Code Editor:** Includes Cloud9 IDE with syntax highlighting for 150+ languages and 35+ themes. +- :page_facing_up: **Document Preview:** Google/Microsoft document viewer for PDF/DOC/XLS/PPT, supporting previews up to 25 MB. +- :zap: **Security Features:** Backup capabilities, IP blacklisting, and whitelisting. +- :mag_right: **Search Functionality:** Use `datatable.js` for fast file search and filtering. +- :file_folder: **Customizable Listings:** Exclude specific folders and files from directory views. +- :globe_with_meridians: **Multi-language Support:** Translations available in 35+ languages with `translation.json`. +- :bangbang: **And Much More!** + +### [Deploy by Docker](https://github.com/prasathmani/tinyfilemanager/wiki/Deploy-by-Docker) ### License, Credit diff --git a/tinyfilemanager.php b/tinyfilemanager.php index e9b05cf1..e6633e7f 100644 --- a/tinyfilemanager.php +++ b/tinyfilemanager.php @@ -3,14 +3,14 @@ $CONFIG = '{"lang":"en","error_reporting":false,"show_hidden":false,"hide_Cols":false,"theme":"light"}'; /** - * H3K - Tiny File Manager V2.5.5 + * H3K - Tiny File Manager V2.6 * @author CCP Programmers * @github https://github.com/prasathmani/tinyfilemanager * @link https://tinyfilemanager.github.io */ //TFM version -define('VERSION', '2.5.5'); +define('VERSION', '2.6'); //Application Title define('APP_TITLE', 'Tiny File Manager'); @@ -59,6 +59,7 @@ // Root path for file manager // use absolute path of directory i.e: '/var/www/folder' or $_SERVER['DOCUMENT_ROOT'].'/folder' +//make sure update $root_url in next section $root_path = $_SERVER['DOCUMENT_ROOT']; // Root url for links in file manager.Relative to $http_host. Variants: '', 'path/to/subfolder' @@ -143,23 +144,23 @@ // if User has the external config file, try to use it to override the default config above [config.php] // sample config - https://tinyfilemanager.github.io/config-sample.txt -$config_file = __DIR__.'/config.php'; +$config_file = __DIR__ . '/config.php'; if (is_readable($config_file)) { @include($config_file); } // External CDN resources that can be used in the HTML (replace for GDPR compliance) $external = array( - 'css-bootstrap' => '', + 'css-bootstrap' => '', 'css-dropzone' => '', 'css-font-awesome' => '', - 'css-highlightjs' => '', - 'js-ace' => '', - 'js-bootstrap' => '', + 'css-highlightjs' => '', + 'js-ace' => '', + 'js-bootstrap' => '', 'js-dropzone' => '', 'js-jquery' => '', 'js-jquery-datatables' => '', - 'js-highlightjs' => '', + 'js-highlightjs' => '', 'pre-jsdelivr' => '', 'pre-cloudflare' => '' ); @@ -173,7 +174,7 @@ define('UPLOAD_CHUNK_SIZE', $upload_chunk_size_bytes); // private key and session name to store to the session -if ( !defined( 'FM_SESSION_ID')) { +if (!defined('FM_SESSION_ID')) { define('FM_SESSION_ID', 'filemanager'); } @@ -228,8 +229,9 @@ } session_cache_limiter('nocache'); // Prevent logout issue after page was cached - session_name(FM_SESSION_ID ); - function session_error_handling_function($code, $msg, $file, $line) { + session_name(FM_SESSION_ID); + function session_error_handling_function($code, $msg, $file, $line) + { // Permission denied for default session, try to create a new one if ($code == 2) { session_abort(); @@ -261,7 +263,7 @@ function session_error_handling_function($code, $msg, $file, $line) { // update $root_url based on user specific directories if (isset($_SESSION[FM_SESSION_ID]['logged']) && !empty($directories_users[$_SESSION[FM_SESSION_ID]['logged']])) { $wd = fm_clean_path(dirname($_SERVER['PHP_SELF'])); - $root_url = $root_url.$wd.DIRECTORY_SEPARATOR.$directories_users[$_SESSION[FM_SESSION_ID]['logged']]; + $root_url = $root_url . $wd . DIRECTORY_SEPARATOR . $directories_users[$_SESSION[FM_SESSION_ID]['logged']]; } // clean $root_url $root_url = fm_clean_path($root_url); @@ -273,20 +275,21 @@ function session_error_handling_function($code, $msg, $file, $line) { // logout if (isset($_GET['logout'])) { unset($_SESSION[FM_SESSION_ID]['logged']); - unset( $_SESSION['token']); + unset($_SESSION['token']); fm_redirect(FM_SELF_URL); } // Validate connection IP if ($ip_ruleset != 'OFF') { - function getClientIP() { + function getClientIP() + { if (array_key_exists('HTTP_CF_CONNECTING_IP', $_SERVER)) { return $_SERVER["HTTP_CF_CONNECTING_IP"]; - }else if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { + } else if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { return $_SERVER["HTTP_X_FORWARDED_FOR"]; - }else if (array_key_exists('REMOTE_ADDR', $_SERVER)) { + } else if (array_key_exists('REMOTE_ADDR', $_SERVER)) { return $_SERVER['REMOTE_ADDR']; - }else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { + } else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { return $_SERVER['HTTP_CLIENT_IP']; } return ''; @@ -297,21 +300,21 @@ function getClientIP() { $whitelisted = in_array($clientIp, $ip_whitelist); $blacklisted = in_array($clientIp, $ip_blacklist); - if($ip_ruleset == 'AND'){ - if($whitelisted == true && $blacklisted == false){ + if ($ip_ruleset == 'AND') { + if ($whitelisted == true && $blacklisted == false) { $proceed = true; } } else - if($ip_ruleset == 'OR'){ - if($whitelisted == true || $blacklisted == false){ + if ($ip_ruleset == 'OR') { + if ($whitelisted == true || $blacklisted == false) { $proceed = true; } } - if($proceed == false){ + if ($proceed == false) { trigger_error('User connection denied from: ' . $clientIp, E_USER_WARNING); - if($ip_silent == false){ + if ($ip_silent == false) { fm_set_msg(lng('Access denied. IP restriction applicable'), 'error'); fm_show_header_login(); fm_show_message(); @@ -327,7 +330,7 @@ function getClientIP() { } elseif (isset($_POST['fm_usr'], $_POST['fm_pwd'], $_POST['token'])) { // Logging In sleep(1); - if(function_exists('password_verify')) { + if (function_exists('password_verify')) { if (isset($auth_users[$_POST['fm_usr']]) && isset($_POST['fm_pwd']) && password_verify($_POST['fm_pwd'], $auth_users[$_POST['fm_usr']]) && verifyToken($_POST['token'])) { $_SESSION[FM_SESSION_ID]['logged'] = $_POST['fm_usr']; fm_set_msg(lng('You are logged in')); @@ -344,21 +347,22 @@ function getClientIP() { // Form unset($_SESSION[FM_SESSION_ID]['logged']); fm_show_header_login(); - ?> +?>
-
+
-
+
- ".lng('Root path')." \"{$root_path}\" ".lng('not found!')." "; + echo "

" . lng('Root path') . " \"{$root_path}\" " . lng('not found!') . "

"; exit; } @@ -456,14 +460,14 @@ function getClientIP() { // Handle all AJAX Request if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ID]['logged']]) || !FM_USE_AUTH) && isset($_POST['ajax'], $_POST['token']) && !FM_READONLY) { - if(!verifyToken($_POST['token'])) { + if (!verifyToken($_POST['token'])) { header('HTTP/1.0 401 Unauthorized'); die("Invalid Token."); } //search : get list of files from the current folder - if(isset($_POST['type']) && $_POST['type']=="search") { - $dir = $_POST['path'] == "." ? '': $_POST['path']; + if (isset($_POST['type']) && $_POST['type'] == "search") { + $dir = $_POST['path'] == "." ? '' : $_POST['path']; $response = scan(fm_clean_path($dir), $_POST['content']); echo json_encode($response); exit(); @@ -485,7 +489,8 @@ function getClientIP() { $file = str_replace('/', '', $file); if ($file == '' || !is_file($path . '/' . $file)) { fm_set_msg(lng('File not found'), 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } header('X-XSS-Protection:0'); $file_path = $path . '/' . $file; @@ -494,7 +499,7 @@ function getClientIP() { $fd = fopen($file_path, "w"); $write_results = @fwrite($fd, $writedata); fclose($fd); - if ($write_results === false){ + if ($write_results === false) { header("HTTP/1.1 500 Internal Server Error"); die("Could Not Write File! - Check Permissions / Ownership"); } @@ -575,20 +580,22 @@ function getClientIP() { } //upload using url - if(isset($_POST['type']) && $_POST['type'] == "upload" && !empty($_REQUEST["uploadurl"])) { + if (isset($_POST['type']) && $_POST['type'] == "upload" && !empty($_REQUEST["uploadurl"])) { $path = FM_ROOT_PATH; if (FM_PATH != '') { $path .= '/' . FM_PATH; } - function event_callback ($message) { + function event_callback($message) + { global $callback; echo json_encode($message); } - function get_file_path () { + function get_file_path() + { global $path, $fileinfo, $temp_file; - return $path."/".basename($fileinfo->name); + return $path . "/" . basename($fileinfo->name); } $url = !empty($_REQUEST["uploadurl"]) && preg_match("|^http(s)?://.+$|", stripslashes($_REQUEST["uploadurl"])) ? stripslashes($_REQUEST["uploadurl"]) : null; @@ -615,7 +622,7 @@ function get_file_path () { $err = false; - if(!$isFileAllowed) { + if (!$isFileAllowed) { $err = array("message" => "File extension is not allowed"); event_callback(array("fail" => $err)); exit(); @@ -626,7 +633,7 @@ function get_file_path () { } else if ($use_curl) { @$fp = fopen($temp_file, "w"); @$ch = curl_init($url); - curl_setopt($ch, CURLOPT_NOPROGRESS, false ); + curl_setopt($ch, CURLOPT_NOPROGRESS, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_FILE, $fp); @$success = curl_exec($ch); @@ -665,7 +672,7 @@ function get_file_path () { // Delete file / folder if (isset($_GET['del'], $_POST['token']) && !FM_READONLY) { - $del = str_replace( '/', '', fm_clean_path( $_GET['del'] ) ); + $del = str_replace('/', '', fm_clean_path($_GET['del'])); if ($del != '' && $del != '..' && $del != '.' && verifyToken($_POST['token'])) { $path = FM_ROOT_PATH; if (FM_PATH != '') { @@ -673,22 +680,23 @@ function get_file_path () { } $is_dir = is_dir($path . '/' . $del); if (fm_rdelete($path . '/' . $del)) { - $msg = $is_dir ? lng('Folder').' %s '.lng('Deleted') : lng('File').' %s '.lng('Deleted'); + $msg = $is_dir ? lng('Folder') . ' %s ' . lng('Deleted') : lng('File') . ' %s ' . lng('Deleted'); fm_set_msg(sprintf($msg, fm_enc($del))); } else { - $msg = $is_dir ? lng('Folder').' %s '.lng('not deleted') : lng('File').' %s '.lng('not deleted'); + $msg = $is_dir ? lng('Folder') . ' %s ' . lng('not deleted') : lng('File') . ' %s ' . lng('not deleted'); fm_set_msg(sprintf($msg, fm_enc($del)), 'error'); } } else { fm_set_msg(lng('Invalid file or folder name'), 'error'); } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // Create a new file/folder if (isset($_POST['newfilename'], $_POST['newfile'], $_POST['token']) && !FM_READONLY) { $type = urldecode($_POST['newfile']); - $new = str_replace( '/', '', fm_clean_path( strip_tags( $_POST['newfilename'] ) ) ); + $new = str_replace('/', '', fm_clean_path(strip_tags($_POST['newfilename']))); if (fm_isvalid_filename($new) && $new != '' && $new != '..' && $new != '.' && verifyToken($_POST['token'])) { $path = FM_ROOT_PATH; if (FM_PATH != '') { @@ -696,28 +704,29 @@ function get_file_path () { } if ($type == "file") { if (!file_exists($path . '/' . $new)) { - if(fm_is_valid_ext($new)) { + if (fm_is_valid_ext($new)) { @fopen($path . '/' . $new, 'w') or die('Cannot open file: ' . $new); - fm_set_msg(sprintf(lng('File').' %s '.lng('Created'), fm_enc($new))); + fm_set_msg(sprintf(lng('File') . ' %s ' . lng('Created'), fm_enc($new))); } else { fm_set_msg(lng('File extension is not allowed'), 'error'); } } else { - fm_set_msg(sprintf(lng('File').' %s '.lng('already exists'), fm_enc($new)), 'alert'); + fm_set_msg(sprintf(lng('File') . ' %s ' . lng('already exists'), fm_enc($new)), 'alert'); } } else { if (fm_mkdir($path . '/' . $new, false) === true) { - fm_set_msg(sprintf(lng('Folder').' %s '.lng('Created'), $new)); + fm_set_msg(sprintf(lng('Folder') . ' %s ' . lng('Created'), $new)); } elseif (fm_mkdir($path . '/' . $new, false) === $path . '/' . $new) { - fm_set_msg(sprintf(lng('Folder').' %s '.lng('already exists'), fm_enc($new)), 'alert'); + fm_set_msg(sprintf(lng('Folder') . ' %s ' . lng('already exists'), fm_enc($new)), 'alert'); } else { - fm_set_msg(sprintf(lng('Folder').' %s '.lng('not created'), fm_enc($new)), 'error'); + fm_set_msg(sprintf(lng('Folder') . ' %s ' . lng('not created'), fm_enc($new)), 'error'); } } } else { fm_set_msg(lng('Invalid characters in file or folder name'), 'error'); } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // Copy folder / file @@ -728,7 +737,8 @@ function get_file_path () { // empty path if ($copy == '') { fm_set_msg(lng('Source path not defined'), 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // abs path from $from = FM_ROOT_PATH . '/' . $copy; @@ -747,57 +757,57 @@ function get_file_path () { if ($move) { // Move and to != from so just perform move $rename = fm_rename($from, $dest); if ($rename) { - fm_set_msg(sprintf(lng('Moved from').' %s '.lng('to').' %s', fm_enc($copy), fm_enc($msg_from))); + fm_set_msg(sprintf(lng('Moved from') . ' %s ' . lng('to') . ' %s', fm_enc($copy), fm_enc($msg_from))); } elseif ($rename === null) { fm_set_msg(lng('File or folder with this path already exists'), 'alert'); } else { - fm_set_msg(sprintf(lng('Error while moving from').' %s '.lng('to').' %s', fm_enc($copy), fm_enc($msg_from)), 'error'); + fm_set_msg(sprintf(lng('Error while moving from') . ' %s ' . lng('to') . ' %s', fm_enc($copy), fm_enc($msg_from)), 'error'); } } else { // Not move and to != from so copy with original name if (fm_rcopy($from, $dest)) { - fm_set_msg(sprintf(lng('Copied from').' %s '.lng('to').' %s', fm_enc($copy), fm_enc($msg_from))); + fm_set_msg(sprintf(lng('Copied from') . ' %s ' . lng('to') . ' %s', fm_enc($copy), fm_enc($msg_from))); } else { - fm_set_msg(sprintf(lng('Error while copying from').' %s '.lng('to').' %s', fm_enc($copy), fm_enc($msg_from)), 'error'); + fm_set_msg(sprintf(lng('Error while copying from') . ' %s ' . lng('to') . ' %s', fm_enc($copy), fm_enc($msg_from)), 'error'); } } } else { - if (!$move){ //Not move and to = from so duplicate + if (!$move) { //Not move and to = from so duplicate $msg_from = trim(FM_PATH . '/' . basename($from), '/'); $fn_parts = pathinfo($from); $extension_suffix = ''; - if(!is_dir($from)){ - $extension_suffix = '.'.$fn_parts['extension']; + if (!is_dir($from)) { + $extension_suffix = '.' . $fn_parts['extension']; } //Create new name for duplicate - $fn_duplicate = $fn_parts['dirname'].'/'.$fn_parts['filename'].'-'.date('YmdHis').$extension_suffix; + $fn_duplicate = $fn_parts['dirname'] . '/' . $fn_parts['filename'] . '-' . date('YmdHis') . $extension_suffix; $loop_count = 0; $max_loop = 1000; // Check if a file with the duplicate name already exists, if so, make new name (edge case...) - while(file_exists($fn_duplicate) & $loop_count < $max_loop){ - $fn_parts = pathinfo($fn_duplicate); - $fn_duplicate = $fn_parts['dirname'].'/'.$fn_parts['filename'].'-copy'.$extension_suffix; - $loop_count++; + while (file_exists($fn_duplicate) & $loop_count < $max_loop) { + $fn_parts = pathinfo($fn_duplicate); + $fn_duplicate = $fn_parts['dirname'] . '/' . $fn_parts['filename'] . '-copy' . $extension_suffix; + $loop_count++; } if (fm_rcopy($from, $fn_duplicate, False)) { fm_set_msg(sprintf('Copied from %s to %s', fm_enc($copy), fm_enc($fn_duplicate))); } else { fm_set_msg(sprintf('Error while copying from %s to %s', fm_enc($copy), fm_enc($fn_duplicate)), 'error'); } - } - else{ - fm_set_msg(lng('Paths must be not equal'), 'alert'); - } + } else { + fm_set_msg(lng('Paths must be not equal'), 'alert'); + } } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // Mass copy files/ folders if (isset($_POST['file'], $_POST['copy_to'], $_POST['finish'], $_POST['token']) && !FM_READONLY) { - if(!verifyToken($_POST['token'])) { + if (!verifyToken($_POST['token'])) { fm_set_msg(lng('Invalid Token.'), 'error'); } - + // from $path = FM_ROOT_PATH; if (FM_PATH != '') { @@ -811,12 +821,14 @@ function get_file_path () { } if ($path == $copy_to_path) { fm_set_msg(lng('Paths must be not equal'), 'alert'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } if (!is_dir($copy_to_path)) { if (!fm_mkdir($copy_to_path, true)) { fm_set_msg('Unable to create destination folder', 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } } // move? @@ -855,12 +867,13 @@ function get_file_path () { } else { fm_set_msg(lng('Nothing selected'), 'alert'); } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // Rename if (isset($_POST['rename_from'], $_POST['rename_to'], $_POST['token']) && !FM_READONLY) { - if(!verifyToken($_POST['token'])) { + if (!verifyToken($_POST['token'])) { fm_set_msg("Invalid Token.", 'error'); } // old name @@ -879,14 +892,15 @@ function get_file_path () { // rename if (fm_isvalid_filename($new) && $old != '' && $new != '') { if (fm_rename($path . '/' . $old, $path . '/' . $new)) { - fm_set_msg(sprintf(lng('Renamed from').' %s '. lng('to').' %s', fm_enc($old), fm_enc($new))); + fm_set_msg(sprintf(lng('Renamed from') . ' %s ' . lng('to') . ' %s', fm_enc($old), fm_enc($new))); } else { - fm_set_msg(sprintf(lng('Error while renaming from').' %s '. lng('to').' %s', fm_enc($old), fm_enc($new)), 'error'); + fm_set_msg(sprintf(lng('Error while renaming from') . ' %s ' . lng('to') . ' %s', fm_enc($old), fm_enc($new)), 'error'); } } else { fm_set_msg(lng('Invalid characters in file name'), 'error'); } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // Download @@ -899,7 +913,7 @@ function get_file_path () { // Clean the download file path $dl = urldecode($_GET['dl']); - $dl = fm_clean_path($dl); + $dl = fm_clean_path($dl); $dl = str_replace('/', '', $dl); // Prevent directory traversal attacks // Define the file path @@ -928,14 +942,16 @@ function get_file_path () { // Upload if (!empty($_FILES) && !FM_READONLY) { - if(isset($_POST['token'])) { - if(!verifyToken($_POST['token'])) { - $response = array ('status' => 'error','info' => "Invalid Token."); - echo json_encode($response); exit(); + if (isset($_POST['token'])) { + if (!verifyToken($_POST['token'])) { + $response = array('status' => 'error', 'info' => "Invalid Token."); + echo json_encode($response); + exit(); } } else { - $response = array ('status' => 'error','info' => "Token Missing."); - echo json_encode($response); exit(); + $response = array('status' => 'error', 'info' => "Token Missing."); + echo json_encode($response); + exit(); } $chunkIndex = $_POST['dzchunkindex']; @@ -952,7 +968,7 @@ function get_file_path () { $errors = 0; $uploads = 0; $allowed = (FM_UPLOAD_EXTENSION) ? explode(',', FM_UPLOAD_EXTENSION) : false; - $response = array ( + $response = array( 'status' => 'error', 'info' => 'Oops! Try again' ); @@ -962,16 +978,17 @@ function get_file_path () { $ext = pathinfo($filename, PATHINFO_FILENAME) != '' ? strtolower(pathinfo($filename, PATHINFO_EXTENSION)) : ''; $isFileAllowed = ($allowed) ? in_array($ext, $allowed) : true; - if(!fm_isvalid_filename($filename) && !fm_isvalid_filename($fullPathInput)) { - $response = array ( + if (!fm_isvalid_filename($filename) && !fm_isvalid_filename($fullPathInput)) { + $response = array( 'status' => 'error', 'info' => "Invalid File name!", ); - echo json_encode($response); exit(); + echo json_encode($response); + exit(); } $targetPath = $path . $ds; - if ( is_writable($targetPath) ) { + if (is_writable($targetPath)) { $fullPath = $path . '/' . $fullPathInput; $folder = substr($fullPath, 0, strrpos($fullPath, "/")); @@ -982,7 +999,7 @@ function get_file_path () { } if (empty($f['file']['error']) && !empty($tmp_name) && $tmp_name != 'none' && $isFileAllowed) { - if ($chunkTotal){ + if ($chunkTotal) { $out = @fopen("{$fullPath}.part", $chunkIndex == 0 ? "wb" : "ab"); if ($out) { $in = @fopen($tmp_name, "rb"); @@ -1001,64 +1018,63 @@ function get_file_path () { } else { stream_copy_to_stream($in, $out); } - $response = array ( + $response = array( 'status' => 'success', 'info' => "file upload successful" ); } else { - $response = array ( - 'status' => 'error', - 'info' => "failed to open output stream", - 'errorDetails' => error_get_last() + $response = array( + 'status' => 'error', + 'info' => "failed to open output stream", + 'errorDetails' => error_get_last() ); } @fclose($in); @fclose($out); @unlink($tmp_name); - $response = array ( + $response = array( 'status' => 'success', 'info' => "file upload successful" ); } else { - $response = array ( + $response = array( 'status' => 'error', 'info' => "failed to open output stream" - ); + ); } if ($chunkIndex == $chunkTotal - 1) { - if (file_exists ($fullPath)) { - $ext_1 = $ext ? '.'.$ext : ''; - $fullPathTarget = $path . '/' . basename($fullPathInput, $ext_1) .'_'. date('ymdHis'). $ext_1; + if (file_exists($fullPath)) { + $ext_1 = $ext ? '.' . $ext : ''; + $fullPathTarget = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1; } else { $fullPathTarget = $fullPath; } rename("{$fullPath}.part", $fullPathTarget); } - } else if (move_uploaded_file($tmp_name, $fullPath)) { // Be sure that the file has been uploaded - if ( file_exists($fullPath) ) { - $response = array ( + if (file_exists($fullPath)) { + $response = array( 'status' => 'success', 'info' => "file upload successful" ); } else { - $response = array ( + $response = array( 'status' => 'error', 'info' => 'Couldn\'t upload the requested file.' ); } } else { - $response = array ( + $response = array( 'status' => 'error', 'info' => "Error while uploading files. Uploaded files $uploads", ); } } } else { - $response = array ( + $response = array( 'status' => 'error', 'info' => 'The specified folder for upload isn\'t writeable.' ); @@ -1068,79 +1084,10 @@ function get_file_path () { exit(); } -// Mass downloading -if (isset($_POST['group'], $_POST['download'], $_POST['token']) && !FM_READONLY) { - - // Verify token to ensure it's valid - if (!verifyToken($_POST['token'])) { - fm_set_msg(lng("Invalid Token."), 'error'); - exit; - } - - $path = FM_ROOT_PATH; - if (FM_PATH != '') { - $path .= '/' . FM_PATH; - } - - $errors = 0; - $files = $_POST['file']; // List of selected files - if (is_array($files) && count($files)) { - - // Create a new ZIP archive - $zip = new ZipArchive(); - $zip_filename = 'download_' . date('Y-m-d_H-i-s') . '.zip'; - $zip_filepath = sys_get_temp_dir() . '/' . $zip_filename; - - if ($zip->open($zip_filepath, ZipArchive::CREATE) !== TRUE) { - fm_set_msg(lng('Cannot create ZIP file'), 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); - } - - foreach ($files as $f) { - if ($f != '') { - $new_path = $path . '/' . fm_clean_path($f); // Sanitize the file path - - if (is_file($new_path)) { - // Add the file to the ZIP archive - $zip->addFile($new_path, basename($new_path)); - } else { - $errors++; - } - } - } - - // Close the ZIP archive - $zip->close(); - - // Check for errors - if ($errors == 0) { - // Serve the ZIP file for download - if (file_exists($zip_filepath)) { - header('Content-Type: application/zip'); - header('Content-Disposition: attachment; filename="' . $zip_filename . '"'); - header('Content-Length: ' . filesize($zip_filepath)); - readfile($zip_filepath); - // Remove the ZIP file from the temporary directory after download - unlink($zip_filepath); - exit; - } else { - fm_set_msg(lng('Error creating ZIP file'), 'error'); - } - } else { - fm_set_msg(lng('Error while adding items to ZIP'), 'error'); - } - } else { - fm_set_msg(lng('Nothing selected'), 'alert'); - } - - $FM_PATH = FM_PATH; - fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); -} - // Mass deleting if (isset($_POST['group'], $_POST['delete'], $_POST['token']) && !FM_READONLY) { - if(!verifyToken($_POST['token'])) { + if (!verifyToken($_POST['token'])) { fm_set_msg(lng("Invalid Token."), 'error'); } @@ -1169,13 +1116,14 @@ function get_file_path () { fm_set_msg(lng('Nothing selected'), 'alert'); } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // Pack files zip, tar if (isset($_POST['group'], $_POST['token']) && (isset($_POST['zip']) || isset($_POST['tar'])) && !FM_READONLY) { - if(!verifyToken($_POST['token'])) { + if (!verifyToken($_POST['token'])) { fm_set_msg(lng("Invalid Token."), 'error'); } @@ -1190,31 +1138,32 @@ function get_file_path () { if (($ext == "zip" && !class_exists('ZipArchive')) || ($ext == "tar" && !class_exists('PharData'))) { fm_set_msg(lng('Operations with archives are not available'), 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } $files = $_POST['file']; $sanitized_files = array(); // clean path - foreach($files as $file){ + foreach ($files as $file) { array_push($sanitized_files, fm_clean_path($file)); } - + $files = $sanitized_files; - + if (!empty($files)) { chdir($path); if (count($files) == 1) { $one_file = reset($files); $one_file = basename($one_file); - $zipname = $one_file . '_' . date('ymd_His') . '.'.$ext; + $zipname = $one_file . '_' . date('ymd_His') . '.' . $ext; } else { - $zipname = 'archive_' . date('ymd_His') . '.'.$ext; + $zipname = 'archive_' . date('ymd_His') . '.' . $ext; } - if($ext == 'zip') { + if ($ext == 'zip') { $zipper = new FM_Zipper(); $res = $zipper->create($zipname, $files); } elseif ($ext == 'tar') { @@ -1223,7 +1172,7 @@ function get_file_path () { } if ($res) { - fm_set_msg(sprintf(lng('Archive').' %s '.lng('Created'), fm_enc($zipname))); + fm_set_msg(sprintf(lng('Archive') . ' %s ' . lng('Created'), fm_enc($zipname))); } else { fm_set_msg(lng('Archive not created'), 'error'); } @@ -1231,13 +1180,14 @@ function get_file_path () { fm_set_msg(lng('Nothing selected'), 'alert'); } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // Unpack zip, tar if (isset($_POST['unzip'], $_POST['token']) && !FM_READONLY) { - if(!verifyToken($_POST['token'])) { + if (!verifyToken($_POST['token'])) { fm_set_msg(lng("Invalid Token."), 'error'); } @@ -1261,7 +1211,8 @@ function get_file_path () { if (($ext == "zip" && !class_exists('ZipArchive')) || ($ext == "tar" && !class_exists('PharData'))) { fm_set_msg(lng('Operations with archives are not available'), 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } if ($isValid) { @@ -1274,13 +1225,13 @@ function get_file_path () { } } - if($ext == "zip") { + if ($ext == "zip") { $zipper = new FM_Zipper(); $res = $zipper->unzip($zip_path, $path); } elseif ($ext == "tar") { try { $gzipper = new PharData($zip_path); - if (@$gzipper->extractTo($path,null, true)) { + if (@$gzipper->extractTo($path, null, true)) { $res = true; } else { $res = false; @@ -1299,16 +1250,17 @@ function get_file_path () { } else { fm_set_msg(lng('File not found'), 'error'); } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } // Change Perms (not for Windows) if (isset($_POST['chmod'], $_POST['token']) && !FM_READONLY && !FM_IS_WIN) { - if(!verifyToken($_POST['token'])) { + if (!verifyToken($_POST['token'])) { fm_set_msg(lng("Invalid Token."), 'error'); } - + $path = FM_ROOT_PATH; if (FM_PATH != '') { $path .= '/' . FM_PATH; @@ -1319,7 +1271,8 @@ function get_file_path () { $file = str_replace('/', '', $file); if ($file == '' || (!is_file($path . '/' . $file) && !is_dir($path . '/' . $file))) { fm_set_msg(lng('File not found'), 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } $mode = 0; @@ -1357,7 +1310,8 @@ function get_file_path () { fm_set_msg(lng('Permissions not changed'), 'error'); } - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); } /*************************** ACTIONS ***************************/ @@ -1379,7 +1333,7 @@ function get_file_path () { $objects = is_readable($path) ? scandir($path) : array(); $folders = array(); $files = array(); -$current_path = array_slice(explode("/",$path), -1)[0]; +$current_path = array_slice(explode("/", $path), -1)[0]; if (is_array($objects) && fm_is_exclude_items($current_path)) { foreach ($objects as $file) { if ($file == '.' || $file == '..') { @@ -1409,10 +1363,13 @@ function get_file_path () { fm_show_header(); // HEADER fm_show_nav_path(FM_PATH); // current path //get the allowed file extensions - function getUploadExt() { + function getUploadExt() + { $extArr = explode(',', FM_UPLOAD_EXTENSION); - if(FM_UPLOAD_EXTENSION && $extArr) { - array_walk($extArr, function(&$x) {$x = ".$x";}); + if (FM_UPLOAD_EXTENSION && $extArr) { + array_walk($extArr, function (&$x) { + $x = ".$x"; + }); return implode(',', $extArr); } return ''; @@ -1421,7 +1378,7 @@ function getUploadExt() {
-
+

- + :

@@ -1443,7 +1400,7 @@ function getUploadExt() {
- +
@@ -1453,7 +1410,11 @@ function getUploadExt() { -
+
+
+
+
+
@@ -1472,19 +1433,19 @@ function getUploadExt() { parallelChunkUploads: false, timeout: 120000, maxFilesize: "", - acceptedFiles : "", - init: function () { - this.on("sending", function (file, xhr, formData) { + acceptedFiles: "", + init: function() { + this.on("sending", function(file, xhr, formData) { let _path = (file.fullPath) ? file.fullPath : file.name; document.getElementById("fullpath").value = _path; xhr.ontimeout = (function() { toast('Error: Server Timeout'); }); - }).on("success", function (res) { + }).on("success", function(res) { try { let _response = JSON.parse(res.xhr.response); - if(_response.status == "error") { + if (_response.status == "error") { toast(_response.info); } } catch (e) { @@ -1496,7 +1457,7 @@ function getUploadExt() { } } - +?>
-
+
@@ -1531,17 +1493,19 @@ function getUploadExt() { /

-

+

+ +

  - +

- +?>

Copying

@@ -1573,20 +1538,21 @@ function getUploadExt() {

    + ?>
  • ..
  • - + ?>
  • -
  • - &copy="> + +
- +?>
-
+
- - + +
@@ -1611,12 +1577,13 @@ function getUploadExt() {
@@ -1626,7 +1593,7 @@ function getSelected($l) {
- /> + />
@@ -1635,7 +1602,7 @@ function getSelected($l) {
- /> + />
@@ -1644,7 +1611,7 @@ function getSelected($l) {
- /> + />
@@ -1652,9 +1619,17 @@ function getSelected($l) {
- + +
@@ -1665,11 +1640,12 @@ function getSelected($l) {
+ * .
- +?>
-
+
- +
-

Tiny File Manager

-

Author: Prasath Mani

-

Mail Us: ccpprogrammers[at]gmail.com

+

+

Tiny File Manager

+

+

Author: PRAŚATH MANİ

+

Mail Us: ccpprogrammers [at] gmail [dot] com

@@ -1724,7 +1702,7 @@ function getSelected($l) {
- +?>
-

""

-

+

    +
  • :
  • - :
    - File size:
    - MIME-type:
    +
  • :
  • +
  • File size:
  • +
  • MIME-type:
  • - :
    - :
    - :
    - : %
    - +
  • :
  • +
  • :
  • +
  • :
  • +
  • : %
  • + '.lng('Image size').': ' . (isset($image_size[0]) ? $image_size[0] : '0') . ' x ' . (isset($image_size[1]) ? $image_size[1] : '0') . '
    '; + echo '
  • ' . lng('Image size') . ': ' . (isset($image_size[0]) ? $image_size[0] : '0') . ' x ' . (isset($image_size[1]) ? $image_size[1] : '0') . '
  • '; } // Text info if ($is_text) { @@ -1826,108 +1804,111 @@ function getSelected($l) { $content = iconv(FM_ICONV_INPUT_ENC, 'UTF-8//IGNORE', $content); } } - echo ''.lng('Charset').': ' . ($is_utf8 ? 'utf-8' : '8 bit') . '
    '; + echo '
  • ' . lng('Charset') . ': ' . ($is_utf8 ? 'utf-8' : '8 bit') . '
  • '; } ?> -

    -
    -
    +
+
+ -   +   - Delete + Delete - + -
+ ?> + - -
  -
+ +
+
-
  - + - - - + ?> + + + + - +
- '; - } else if($online_viewer == 'microsoft') { - echo ''; - } - } elseif ($is_zip) { - // ZIP content - if ($filenames !== false) { - echo ''; - foreach ($filenames as $fn) { - if ($fn['folder']) { - echo '' . fm_enc($fn['name']) . '
'; - } else { - echo $fn['name'] . ' (' . fm_get_filesize($fn['filesize']) . ')
'; +
+ '; + } else if ($online_viewer == 'microsoft') { + echo ''; + } + } elseif ($is_zip) { + // ZIP content + if ($filenames !== false) { + echo ''; + foreach ($filenames as $fn) { + if ($fn['folder']) { + echo '' . fm_enc($fn['name']) . '
'; + } else { + echo $fn['name'] . ' (' . fm_get_filesize($fn['filesize']) . ')
'; + } } + echo '
'; + } else { + echo '

' . lng('Error while fetching archive info') . '

'; } - echo '
'; - } else { - echo '

'.lng('Error while fetching archive info').'

'; - } - } elseif ($is_image) { - // Image content - if (in_array($ext, array('gif', 'jpg', 'jpeg', 'png', 'bmp', 'ico', 'svg', 'webp', 'avif'))) { - echo '

'; - } - } elseif ($is_audio) { - // Audio content - echo '

'; - } elseif ($is_video) { - // Video content - echo '
'; - } elseif ($is_text) { - if (FM_USE_HIGHLIGHTJS) { - // highlight - $hljs_classes = array( - 'shtml' => 'xml', - 'htaccess' => 'apache', - 'phtml' => 'php', - 'lock' => 'json', - 'svg' => 'xml', - ); - $hljs_class = isset($hljs_classes[$ext]) ? 'lang-' . $hljs_classes[$ext] : 'lang-' . $ext; - if (empty($ext) || in_array(strtolower($file), fm_get_text_names()) || preg_match('#\.min\.(css|js)$#i', $file)) { - $hljs_class = 'nohighlight'; + } elseif ($is_image) { + // Image content + if (in_array($ext, array('gif', 'jpg', 'jpeg', 'png', 'bmp', 'ico', 'svg', 'webp', 'avif'))) { + echo '

'; } - $content = '
' . fm_enc($content) . '
'; - } elseif (in_array($ext, array('php', 'php4', 'php5', 'phtml', 'phps'))) { - // php highlight - $content = highlight_string($content, true); - } else { - $content = '
' . fm_enc($content) . '
'; + } elseif ($is_audio) { + // Audio content + echo '

'; + } elseif ($is_video) { + // Video content + echo '
'; + } elseif ($is_text) { + if (FM_USE_HIGHLIGHTJS) { + // highlight + $hljs_classes = array( + 'shtml' => 'xml', + 'htaccess' => 'apache', + 'phtml' => 'php', + 'lock' => 'json', + 'svg' => 'xml', + ); + $hljs_class = isset($hljs_classes[$ext]) ? 'lang-' . $hljs_classes[$ext] : 'lang-' . $ext; + if (empty($ext) || in_array(strtolower($file), fm_get_text_names()) || preg_match('#\.min\.(css|js)$#i', $file)) { + $hljs_class = 'nohighlight'; + } + $content = '
' . fm_enc($content) . '
'; + } elseif (in_array($ext, array('php', 'php4', 'php5', 'phtml', 'phps'))) { + // php highlight + $content = highlight_string($content, true); + } else { + $content = '
' . fm_enc($content) . '
'; + } + echo $content; } - echo $content; - } - ?> + ?> +
- '. $file. ''; + $editFile = ' : ' . $file . ''; header('X-XSS-Protection:0'); fm_show_header(); // HEADER fm_show_nav_path(FM_PATH); // current path @@ -1976,7 +1958,7 @@ class="edit-file"> +?>
@@ -1988,27 +1970,35 @@ class="edit-file"> "> - - - + + +
- - - - - - - - - +
+ + + + + + + + + + - +
- +?>
-
+
@@ -2059,7 +2050,7 @@ class="edit-file"> "> - +
@@ -2068,26 +2059,26 @@ class="edit-file"> - - - + + + - - - + + + - - - + + +

- +  

@@ -2095,7 +2086,7 @@ class="edit-file">
-
- +
- - - - - - - - - - - + + + + + + + + + + + + ?> - + @@ -2151,7 +2141,7 @@ class="edit-file"> - '?'); $group = array('name' => '?'); } - ?> + ?> +
+ + +
+ + - - + - + - - '?'); $group = array('name' => '?'); } - ?> + ?> - + @@ -2269,16 +2265,16 @@ class="edit-file"> - "> - - - +
-
- - -
-
+
+ + +
+
..
-
- - -
-
> -
- ' . readlink($path . '/' . $f) . '' : '') ?>
+
+ + ' . readlink($path . '/' . $f) . '' : '') ?> +
"> + "> + + + + - - - + + + - +
-
- - -
+
+ + +
>
- + - - - + + + - - ' . readlink($path . '/' . $f) . '' : '') ?> + + ' . readlink($path . '/' . $f) . '' : '') ?>
"> - + - + + href="?p=&copy=">
- '.fm_get_filesize($all_files_size).'' ?> - '.$num_files.'' ?> - '.$num_folders.'' ?> + + ' . fm_get_filesize($all_files_size) . '' ?> + ' . $num_files . '' ?> + ' . $num_folders . '' ?>
-
- -
- +
+ +
+ @@ -2342,10 +2336,11 @@ class="edit-file"> "; return; @@ -2359,9 +2354,9 @@ function print_external($key) { * @param string $token * @return bool */ -function verifyToken($token) +function verifyToken($token) { - if (hash_equals($_SESSION['token'], $token)) { + if (hash_equals($_SESSION['token'], $token)) { return true; } return false; @@ -2453,7 +2448,7 @@ function fm_rename($old, $new) { $isFileAllowed = fm_is_valid_ext($new); - if(!is_dir($old)) { + if (!is_dir($old)) { if (!$isFileAllowed) return false; } @@ -2470,28 +2465,31 @@ function fm_rename($old, $new) */ function fm_rcopy($path, $dest, $upd = true, $force = true) { + if (!is_dir($path) && !is_file($path)) { + return false; + } + if (is_dir($path)) { if (!fm_mkdir($dest, $force)) { return false; } - $objects = scandir($path); - $ok = true; - if (is_array($objects)) { - foreach ($objects as $file) { - if ($file != '.' && $file != '..') { - if (!fm_rcopy($path . '/' . $file, $dest . '/' . $file)) { - $ok = false; - } - } + + $objects = array_diff(scandir($path), ['.', '..']); + + foreach ($objects as $file) { + if (!fm_rcopy("$path/$file", "$dest/$file", $upd, $force)) { + return false; } } - return $ok; - } elseif (is_file($path)) { - return fm_copy($path, $dest, $upd); + + return true; } - return false; + + // Handle file copying + return fm_copy($path, $dest, $upd); } + /** * Safely create folder * @param string $dir @@ -2574,7 +2572,8 @@ function fm_redirect($url, $code = 302) * @param $path * @return string */ -function get_absolute_path($path) { +function get_absolute_path($path) +{ $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path); $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); $absolutes = array(); @@ -2654,7 +2653,8 @@ function fm_get_display_path($file_path) * @param string $file * @return bool */ -function fm_is_exclude_items($file) { +function fm_is_exclude_items($file) +{ $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); if (isset($exclude_items) and sizeof($exclude_items)) { unset($exclude_items); @@ -2675,14 +2675,14 @@ function fm_is_exclude_items($file) { * @param int $tr * @return array */ -function fm_get_translations($tr) { +function fm_get_translations($tr) +{ try { $content = @file_get_contents('translation.json'); - if($content !== FALSE) { + if ($content !== FALSE) { $lng = json_decode($content, TRUE); global $lang_list; - foreach ($lng["language"] as $key => $value) - { + foreach ($lng["language"] as $key => $value) { $code = $value["code"]; $lang_list[$code] = $value["name"]; if ($tr) @@ -2690,9 +2690,7 @@ function fm_get_translations($tr) { } return $tr; } - - } - catch (Exception $e) { + } catch (Exception $e) { echo $e; } } @@ -2705,48 +2703,46 @@ function fm_get_translations($tr) { */ function fm_get_size($file) { - static $iswin; - static $isdarwin; - if (!isset($iswin)) { - $iswin = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN'); - } - if (!isset($isdarwin)) { - $isdarwin = (strtoupper(substr(PHP_OS, 0)) == "DARWIN"); - } + static $iswin = null; + static $isdarwin = null; + static $exec_works = null; - static $exec_works; - if (!isset($exec_works)) { - $exec_works = (function_exists('exec') && !ini_get('safe_mode') && @exec('echo EXEC') == 'EXEC'); + // Set static variables once + if ($iswin === null) { + $iswin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + $isdarwin = strtoupper(PHP_OS) === 'DARWIN'; + $exec_works = function_exists('exec') && !ini_get('safe_mode') && @exec('echo EXEC') === 'EXEC'; } - // try a shell command + // Attempt shell command if exec is available if ($exec_works) { $arg = escapeshellarg($file); - $cmd = ($iswin) ? "for %F in (\"$file\") do @echo %~zF" : ($isdarwin ? "stat -f%z $arg" : "stat -c%s $arg"); + $cmd = $iswin ? "for %F in (\"$file\") do @echo %~zF" : ($isdarwin ? "stat -f%z $arg" : "stat -c%s $arg"); @exec($cmd, $output); - if (is_array($output) && ctype_digit($size = trim(implode("\n", $output)))) { + + if (!empty($output) && ctype_digit($size = trim(implode("\n", $output)))) { return $size; } } - // try the Windows COM interface - if ($iswin && class_exists("COM")) { + // Attempt Windows COM interface for Windows systems + if ($iswin && class_exists('COM')) { try { $fsobj = new COM('Scripting.FileSystemObject'); - $f = $fsobj->GetFile( realpath($file) ); - $size = $f->Size; + $f = $fsobj->GetFile(realpath($file)); + if (ctype_digit($size = $f->Size)) { + return $size; + } } catch (Exception $e) { - $size = null; - } - if (ctype_digit($size)) { - return $size; + // COM failed, fallback to filesize } } - // if all else fails + // Default to PHP's filesize function return filesize($file); } + /** * Get nice filesize * @param int $size @@ -2761,29 +2757,13 @@ function fm_get_filesize($size) return sprintf('%s %s', round($size / pow(1024, $power), 2), $units[$power]); } -/** - * Get total size of directory tree. - * - * @param string $directory Relative or absolute directory name. - * @return int Total number of bytes. - */ -function fm_get_directorysize($directory) { - $bytes = 0; - $directory = realpath($directory); - if ($directory !== false && $directory != '' && file_exists($directory)){ - foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS)) as $file){ - $bytes += $file->getSize(); - } - } - return $bytes; -} - /** * Get info about zip archive * @param string $path * @return array|bool */ -function fm_get_zif_info($path, $ext) { +function fm_get_zif_info($path, $ext) +{ if ($ext == 'zip' && function_exists('zip_open')) { $arch = @zip_open($path); if ($arch) { @@ -2802,12 +2782,12 @@ function fm_get_zif_info($path, $ext) { @zip_close($arch); return $filenames; } - } elseif($ext == 'tar' && class_exists('PharData')) { + } elseif ($ext == 'tar' && class_exists('PharData')) { $archive = new PharData($path); $filenames = array(); - foreach(new RecursiveIteratorIterator($archive) as $file) { + foreach (new RecursiveIteratorIterator($archive) as $file) { $parent_info = $file->getPathInfo(); - $zip_name = str_replace("phar://".$path, '', $file->getPathName()); + $zip_name = str_replace("phar://" . $path, '', $file->getPathName()); $zip_name = substr($zip_name, ($pos = strpos($zip_name, '/')) !== false ? $pos + 1 : 0); $zip_folder = $parent_info->getFileName(); $zip_info = new SplFileInfo($file); @@ -2838,7 +2818,8 @@ function fm_enc($text) * @param string $text * @return string */ -function fm_isvalid_filename($text) { +function fm_isvalid_filename($text) +{ return (strpbrk($text, '/?%*:|"<>') === FALSE) ? true : false; } @@ -3131,12 +3112,102 @@ function fm_get_audio_exts() function fm_get_text_exts() { return array( - 'txt', 'css', 'ini', 'conf', 'log', 'htaccess', 'passwd', 'ftpquota', 'sql', 'js', 'ts', 'jsx', 'tsx', 'mjs', 'json', 'sh', 'config', - 'php', 'php4', 'php5', 'phps', 'phtml', 'htm', 'html', 'shtml', 'xhtml', 'xml', 'xsl', 'm3u', 'm3u8', 'pls', 'cue', 'bash', 'vue', - 'eml', 'msg', 'csv', 'bat', 'twig', 'tpl', 'md', 'gitignore', 'less', 'sass', 'scss', 'c', 'cpp', 'cs', 'py', 'go', 'zsh', 'swift', - 'map', 'lock', 'dtd', 'svg', 'asp', 'aspx', 'asx', 'asmx', 'ashx', 'jsp', 'jspx', 'cgi', 'dockerfile', 'ruby', 'yml', 'yaml', 'toml', - 'vhost', 'scpt', 'applescript', 'csx', 'cshtml', 'c++', 'coffee', 'cfm', 'rb', 'graphql', 'mustache', 'jinja', 'http', 'handlebars', - 'java', 'es', 'es6', 'markdown', 'wiki', 'tmp', 'top', 'bot', 'dat', 'bak', 'htpasswd', 'pl', 'ps1' + 'txt', + 'css', + 'ini', + 'conf', + 'log', + 'htaccess', + 'passwd', + 'ftpquota', + 'sql', + 'js', + 'ts', + 'jsx', + 'tsx', + 'mjs', + 'json', + 'sh', + 'config', + 'php', + 'php4', + 'php5', + 'phps', + 'phtml', + 'htm', + 'html', + 'shtml', + 'xhtml', + 'xml', + 'xsl', + 'm3u', + 'm3u8', + 'pls', + 'cue', + 'bash', + 'vue', + 'eml', + 'msg', + 'csv', + 'bat', + 'twig', + 'tpl', + 'md', + 'gitignore', + 'less', + 'sass', + 'scss', + 'c', + 'cpp', + 'cs', + 'py', + 'go', + 'zsh', + 'swift', + 'map', + 'lock', + 'dtd', + 'svg', + 'asp', + 'aspx', + 'asx', + 'asmx', + 'ashx', + 'jsp', + 'jspx', + 'cgi', + 'dockerfile', + 'ruby', + 'yml', + 'yaml', + 'toml', + 'vhost', + 'scpt', + 'applescript', + 'csx', + 'cshtml', + 'c++', + 'coffee', + 'cfm', + 'rb', + 'graphql', + 'mustache', + 'jinja', + 'http', + 'handlebars', + 'java', + 'es', + 'es6', + 'markdown', + 'wiki', + 'tmp', + 'top', + 'bot', + 'dat', + 'bak', + 'htpasswd', + 'pl', + 'ps1' ); } @@ -3228,8 +3299,8 @@ function fm_get_file_mimes($extension) $fileTypes['html'] = ['text/html']; $fileTypes['txt'] = ['text/plain']; //Unknown mime-types should be 'application/octet-stream' - if(empty($fileTypes[$extension])) { - $fileTypes[$extension] = ['application/octet-stream']; + if (empty($fileTypes[$extension])) { + $fileTypes[$extension] = ['application/octet-stream']; } return $fileTypes[$extension]; } @@ -3240,35 +3311,36 @@ function fm_get_file_mimes($extension) * @param string $filter * @return array|null */ - function scan($dir = '', $filter = '') { - $path = FM_ROOT_PATH.'/'.$dir; - if($path) { - $ite = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); - $rii = new RegexIterator($ite, "/(" . $filter . ")/i"); - - $files = array(); - foreach ($rii as $file) { - if (!$file->isDir()) { - $fileName = $file->getFilename(); - $location = str_replace(FM_ROOT_PATH, '', $file->getPath()); - $files[] = array( - "name" => $fileName, - "type" => "file", - "path" => $location, - ); - } - } - return $files; - } +function scan($dir = '', $filter = '') +{ + $path = FM_ROOT_PATH . '/' . $dir; + if ($path) { + $ite = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); + $rii = new RegexIterator($ite, "/(" . $filter . ")/i"); + + $files = array(); + foreach ($rii as $file) { + if (!$file->isDir()) { + $fileName = $file->getFilename(); + $location = str_replace(FM_ROOT_PATH, '', $file->getPath()); + $files[] = array( + "name" => $fileName, + "type" => "file", + "path" => $location, + ); + } + } + return $files; + } } /** -* Parameters: downloadFile(File Location, File Name, -* max speed, is streaming -* If streaming - videos will show as videos, images as images -* instead of download prompt -* https://stackoverflow.com/a/13821992/1164642 -*/ + * Parameters: downloadFile(File Location, File Name, + * max speed, is streaming + * If streaming - videos will show as videos, images as images + * instead of download prompt + * https://stackoverflow.com/a/13821992/1164642 + */ function fm_download_file($fileLocation, $fileName, $chunkSize = 1024) { if (connection_status() != 0) @@ -3277,7 +3349,7 @@ function fm_download_file($fileLocation, $fileName, $chunkSize = 1024) $contentType = fm_get_file_mimes($extension); - if(is_array($contentType)) { + if (is_array($contentType)) { $contentType = implode(' ', $contentType); } @@ -3285,7 +3357,8 @@ function fm_download_file($fileLocation, $fileName, $chunkSize = 1024) if ($size == 0) { fm_set_msg(lng('Zero byte file! Aborting download'), 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); return (false); } @@ -3295,7 +3368,8 @@ function fm_download_file($fileLocation, $fileName, $chunkSize = 1024) if ($fp === false) { fm_set_msg(lng('Cannot open file! Aborting download'), 'error'); - $FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); + $FM_PATH = FM_PATH; + fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH)); return (false); } @@ -3341,18 +3415,6 @@ function fm_download_file($fileLocation, $fileName, $chunkSize = 1024) return ((connection_status() == 0) and !connection_aborted()); } -/** - * If the theme is dark, return the text-white and bg-dark classes. - * @return string the value of the variable. - */ -function fm_get_theme() { - $result = ''; - if(FM_THEME == "dark") { - $result = "text-white bg-dark"; - } - return $result; -} - /** * Class to work with zip files (using ZipArchive) */ @@ -3569,14 +3631,14 @@ private function addDir($path) /** * Save Configuration */ - class FM_Config +class FM_Config { - var $data; + var $data; function __construct() { global $root_path, $root_url, $CONFIG; - $fm_url = $root_url.$_SERVER["PHP_SELF"]; + $fm_url = $root_url . $_SERVER["PHP_SELF"]; $this->data = array( 'lang' => 'en', 'error_reporting' => true, @@ -3605,7 +3667,7 @@ function save() $fm_file = is_readable($config_file) ? $config_file : __FILE__; $var_name = '$CONFIG'; $var_value = var_export(json_encode($this->data), true); - $config_string = " -
- - - - - - - - - - - '; } ?> - <?php echo fm_enc(APP_TITLE) ?> - - - - -"> -
+ + "> + + + + + + + + + '; + } ?> + <?php echo fm_enc(APP_TITLE) ?> + + + + + + "> +
+ + -
- - - - + ?> +
+ + + + + + - - - - - - - - - - '; } ?> - <?php echo isset($_GET['edit']) ? $_GET['edit'] : fm_enc(APP_TITLE); ?> - - - - - - - - - - + + + + + + + + + + + '; + } ?> + <?php echo fm_enc(APP_TITLE) ?> | <?php echo (isset($_GET['view']) ? $_GET['view'] : ((isset($_GET['edit'])) ? $_GET['edit'] : "H3K")); ?> + + + + + + + + - - - "> -
- - + *, + *::before, + *::after { + box-sizing: border-box; + } - - + body { + font-size: 15px; + color: #222; + background: #F7F7F7; + } - - + body.navbar-fixed { + margin-top: 55px; + } - - + a, + a:hover, + a:visited, + a:focus { + text-decoration: none !important; + } - -
- - - - - - - - - - - - -
- - - + .btn-2 { + padding: 4px 10px; + font-size: small; + } + + li.file:before, + li.folder:before { + font: normal normal normal 14px/1 FontAwesome; + content: "\f016"; + margin-right: 5px + } + + li.folder:before { + content: "\f114" + } + + i.fa.fa-folder-o { + color: #0157b3 + } + + i.fa.fa-picture-o { + color: #26b99a + } + + i.fa.fa-file-archive-o { + color: #da7d7d + } + + .btn-2 i.fa.fa-file-archive-o { + color: inherit + } + + i.fa.fa-css3 { + color: #f36fa0 + } + + i.fa.fa-file-code-o { + color: #007bff + } + + i.fa.fa-code { + color: #cc4b4c + } + + i.fa.fa-file-text-o { + color: #0096e6 + } + + i.fa.fa-html5 { + color: #d75e72 + } + + i.fa.fa-file-excel-o { + color: #09c55d + } + + i.fa.fa-file-powerpoint-o { + color: #f6712e + } + + i.go-back { + font-size: 1.2em; + color: #007bff; + } + + .main-nav { + padding: 0.2rem 1rem; + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12), 0 2px 4px -1px rgba(0, 0, 0, .2) + } + + .dataTables_filter { + display: none; + } + + table.dataTable thead .sorting { + cursor: pointer; + background-repeat: no-repeat; + background-position: center right; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7XQMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC'); + } + + table.dataTable thead .sorting_asc { + cursor: pointer; + background-repeat: no-repeat; + background-position: center right; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg=='); + } + + table.dataTable thead .sorting_desc { + cursor: pointer; + background-repeat: no-repeat; + background-position: center right; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII='); + } + + table.dataTable thead tr:first-child th.custom-checkbox-header:first-child { + background-image: none; + } + + .footer-action li { + margin-bottom: 10px; + } + + .app-v-title { + font-size: 24px; + font-weight: 300; + letter-spacing: -.5px; + text-transform: uppercase; + } + + hr.custom-hr { + border-top: 1px dashed #8c8b8b; + border-bottom: 1px dashed #fff; + } + + #snackbar { + visibility: hidden; + min-width: 250px; + margin-left: -125px; + background-color: #333; + color: #fff; + text-align: center; + border-radius: 2px; + padding: 16px; + position: fixed; + z-index: 1; + left: 50%; + bottom: 30px; + font-size: 17px; + } + + #snackbar.show { + visibility: visible; + -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; + animation: fadein 0.5s, fadeout 0.5s 2.5s; + } + + @-webkit-keyframes fadein { + from { + bottom: 0; + opacity: 0; + } + + to { + bottom: 30px; + opacity: 1; + } + } + + @keyframes fadein { + from { + bottom: 0; + opacity: 0; + } + + to { + bottom: 30px; + opacity: 1; + } + } + + @-webkit-keyframes fadeout { + from { + bottom: 30px; + opacity: 1; + } + + to { + bottom: 0; + opacity: 0; + } + } + + @keyframes fadeout { + from { + bottom: 30px; + opacity: 1; + } + + to { + bottom: 0; + opacity: 0; + } + } + + #main-table span.badge { + border-bottom: 2px solid #f8f9fa + } + + #main-table span.badge:nth-child(1) { + border-color: #df4227 + } + + #main-table span.badge:nth-child(2) { + border-color: #f8b600 + } + + #main-table span.badge:nth-child(3) { + border-color: #00bd60 + } + + #main-table span.badge:nth-child(4) { + border-color: #4581ff + } + + #main-table span.badge:nth-child(5) { + border-color: #ac68fc + } + + #main-table span.badge:nth-child(6) { + border-color: #45c3d2 + } + + @media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:landscape) and (-webkit-min-device-pixel-ratio:2) { + .navbar-collapse .col-xs-6 { + padding: 0; + } + } + + .btn.active.focus, + .btn.active:focus, + .btn.focus, + .btn.focus:active, + .btn:active:focus, + .btn:focus { + outline: 0 !important; + outline-offset: 0 !important; + background-image: none !important; + -webkit-box-shadow: none !important; + box-shadow: none !important + } + + .lds-facebook { + display: none; + position: relative; + width: 64px; + height: 64px + } + + .lds-facebook div, + .lds-facebook.show-me { + display: inline-block + } + + .lds-facebook div { + position: absolute; + left: 6px; + width: 13px; + background: #007bff; + animation: lds-facebook 1.2s cubic-bezier(0, .5, .5, 1) infinite + } + + .lds-facebook div:nth-child(1) { + left: 6px; + animation-delay: -.24s + } + + .lds-facebook div:nth-child(2) { + left: 26px; + animation-delay: -.12s + } + + .lds-facebook div:nth-child(3) { + left: 45px; + animation-delay: 0s + } + + @keyframes lds-facebook { + 0% { + top: 6px; + height: 51px + } + + 100%, + 50% { + top: 19px; + height: 26px + } + } + + ul#search-wrapper { + padding-left: 0; + border: 1px solid #ecececcc; + } + + ul#search-wrapper li { + list-style: none; + padding: 5px; + border-bottom: 1px solid #ecececcc; + } + + ul#search-wrapper li:nth-child(odd) { + background: #f9f9f9cc; + } + + .c-preview-img { + max-width: 300px; + } + + .border-radius-0 { + border-radius: 0; + } + + .float-right { + float: right; + } + + .table-hover>tbody>tr:hover>td:first-child { + border-left: 1px solid #1b77fd; + } + + #main-table tr.even { + background-color: #F8F9Fa; + } + + .filename>a>i { + margin-right: 3px; + } + + .fs-7 { + font-size: 14px; + } + + + + + + + "> +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/translation.json b/translation.json index 536e405e..050390b0 100644 --- a/translation.json +++ b/translation.json @@ -1,6 +1,6 @@ { "appName": "Tiny File Manager", - "version": "2.5.1", + "version": "2.6", "language": [ { "name": "Română", @@ -2812,4 +2812,4 @@ } } ] -} +} \ No newline at end of file