<?php

namespace onespace\tools\widgets\tinyfilebrowser\base;

use Exception;
use FilesystemIterator;
use onespace\tools\widgets\tinyfilebrowser\lib\FMConfig;
use onespace\tools\widgets\tinyfilebrowser\traits\PathClean;
use PharData;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;
use SplFileInfo;
use yii\base\Widget;
use yii\web\View;

abstract class BaseFileManager extends Widget {

    const CONFIG = [
        "lang" => "en",
        "error_reporting" => false,
        "show_hidden" => false,
        "hide_columns" => false,
        "theme" => "light",
    ];

    const VERSION = '2.5.3';
    const APP_TITLE = 'Tiny File Manager';

    const EXTERNAL = [
        'css-bootstrap' => '<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">',
        'css-dropzone' => '<link href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/min/dropzone.min.css" rel="stylesheet">',
        'css-font-awesome' => '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" crossorigin="anonymous">',
        'css-highlightjs' => '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/vs.min.css">',
        'js-ace' => '<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.13.1/ace.js"></script>',
        'js-bootstrap' => '<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>',
        'js-dropzone' => '<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/min/dropzone.min.js"></script>',
        'js-jquery' => '<script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>',
        'js-jquery-datatables' => '<script src="https://cdn.datatables.net/1.13.1/js/jquery.dataTables.min.js" crossorigin="anonymous" defer></script>',
        'js-highlightjs' => '<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>',
        'pre-jsdelivr' => '<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin/><link rel="dns-prefetch" href="https://cdn.jsdelivr.net"/>',
        'pre-cloudflare' => '<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin/><link rel="dns-prefetch" href="https://cdnjs.cloudflare.com"/>'
    ];


    protected $use_auth = false;

    protected $use_highlightjs = true;
    protected $highlightjs_style = 'vs';

    protected $root_path = ROOT_PATH;
    protected $root_url = '';
    protected $http_host = HTTP_HOST;

    protected $iconv_input_encoding = 'UTF-8';

    protected $path_display_mode = 'full';
    protected $allowed_file_extensions = '';
    protected $allowed_upload_extensions = '';
    protected $favicon_path = '';
    protected $exclude_items = [];


    protected $online_viewer = 'google';
    protected $sticky_navbar = true;
    protected $max_upload_size_bytes = 5000000000; // size 5,000,000,000 bytes (~5GB)
    protected $upload_chunk_size_bytes = 2000000; // chunk size 2,000,000 bytes (~2MB)
    protected $ip_ruleset = 'OFF';
    protected $ip_silent = true;

    protected $ip_whitelist = array(
        '127.0.0.1',    // local ipv4
        '::1'           // local ipv6
    );
    protected $ip_blacklist = array(
        '0.0.0.0',      // non-routable meta ipv4
        '::'            // non-routable meta ipv6
    );

    protected $config_file = __DIR__ . '/config.php';

    protected FMConfig $cfg;

    use PathClean;

    protected View $view;

    public function __construct($config = []) {
        parent::__construct($config);

        $this->view = $this->getView();
    }


    /**
     * HTTP Redirect
     * @param string $url
     * @param int $code
     */
    protected function fm_redirect($url, $code = 302) {
        header('Location: ' . $url, true, $code);
        exit;
    }


    /**
     * Verify CSRF TOKEN and remove after cerify
     * @param string $token
     * @return bool
     */
    protected function verifyToken($token) {
        if (hash_equals($_SESSION['token'], $token)) {
            return true;
        }
        return false;
    }

    /**
     * Delete  file or folder (recursively)
     * @param string $path
     * @return bool
     */
    protected function fm_rdelete($path) {
        if (is_link($path)) {
            return unlink($path);
        } elseif (is_dir($path)) {
            $objects = scandir($path);
            $ok = true;
            if (is_array($objects)) {
                foreach ($objects as $file) {
                    if ($file != '.' && $file != '..') {
                        if (!$this->fm_rdelete($path . '/' . $file)) {
                            $ok = false;
                        }
                    }
                }
            }
            return ($ok) ? rmdir($path) : false;
        } elseif (is_file($path)) {
            return unlink($path);
        }
        return false;
    }

    /**
     * Recursive chmod
     * @param string $path
     * @param int $filemode
     * @param int $dirmode
     * @return bool
     * @todo Will use in mass chmod
     */
    protected function fm_rchmod($path, $filemode, $dirmode) {
        if (is_dir($path)) {
            if (!chmod($path, $dirmode)) {
                return false;
            }
            $objects = scandir($path);
            if (is_array($objects)) {
                foreach ($objects as $file) {
                    if ($file != '.' && $file != '..') {
                        if (!$this->fm_rchmod($path . '/' . $file, $filemode, $dirmode)) {
                            return false;
                        }
                    }
                }
            }
            return true;
        } elseif (is_link($path)) {
            return true;
        } elseif (is_file($path)) {
            return chmod($path, $filemode);
        }
        return false;
    }

    /**
     * Check the file extension which is allowed or not
     * @param string $filename
     * @return bool
     */
    protected function fm_is_valid_ext($filename) {
        $allowed = (FM_FILE_EXTENSION) ? explode(',', FM_FILE_EXTENSION) : false;

        $ext = pathinfo($filename, PATHINFO_EXTENSION);
        $isFileAllowed = ($allowed) ? in_array($ext, $allowed) : true;

        return ($isFileAllowed) ? true : false;
    }

    /**
     * Safely rename
     * @param string $old
     * @param string $new
     * @return bool|null
     */
    protected function fm_rename($old, $new) {
        $isFileAllowed = $this->fm_is_valid_ext($new);

        if (!is_dir($old)) {
            if (!$isFileAllowed) return false;
        }

        return (!file_exists($new) && file_exists($old)) ? rename($old, $new) : null;
    }

    /**
     * Copy file or folder (recursively).
     * @param string $path
     * @param string $dest
     * @param bool $upd Update files
     * @param bool $force Create folder with same names instead file
     * @return bool
     */
    protected function fm_rcopy($path, $dest, $upd = true, $force = true) {
        if (is_dir($path)) {
            if (!$this->fm_mkdir($dest, $force)) {
                return false;
            }
            $objects = scandir($path);
            $ok = true;
            if (is_array($objects)) {
                foreach ($objects as $file) {
                    if ($file != '.' && $file != '..') {
                        if (!$this->fm_rcopy($path . '/' . $file, $dest . '/' . $file)) {
                            $ok = false;
                        }
                    }
                }
            }
            return $ok;
        } elseif (is_file($path)) {
            return $this->fm_copy($path, $dest, $upd);
        }
        return false;
    }

    /**
     * Safely create folder
     * @param string $dir
     * @param bool $force
     * @return bool
     */
    protected function fm_mkdir($dir, $force) {
        if (file_exists($dir)) {
            if (is_dir($dir)) {
                return $dir;
            } elseif (!$force) {
                return false;
            }
            unlink($dir);
        }
        return mkdir($dir, 0777, true);
    }

    /**
     * Safely copy file
     * @param string $f1
     * @param string $f2
     * @param bool $upd Indicates if file should be updated with new content
     * @return bool
     */
    protected function fm_copy($f1, $f2, $upd) {
        $time1 = filemtime($f1);
        if (file_exists($f2)) {
            $time2 = filemtime($f2);
            if ($time2 >= $time1 && $upd) {
                return false;
            }
        }
        $ok = copy($f1, $f2);
        if ($ok) {
            touch($f2, $time1);
        }
        return $ok;
    }

    /**
     * Get mime type
     * @param string $file_path
     * @return mixed|string
     */
    protected function fm_get_mime_type($file_path) {
        if (function_exists('finfo_open')) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $mime = finfo_file($finfo, $file_path);
            finfo_close($finfo);
            return $mime;
        } elseif (function_exists('mime_content_type')) {
            return mime_content_type($file_path);
        } elseif (!stristr(ini_get('disable_functions'), 'shell_exec')) {
            $file = escapeshellarg($file_path);
            $mime = shell_exec('file -bi ' . $file);
            return $mime;
        } else {
            return '--';
        }
    }


    // /**
    //  * Path traversal prevention and clean the url
    //  * It replaces (consecutive) occurrences of / and \\ with whatever is in DIRECTORY_SEPARATOR, and processes /. and /.. fine.
    //  * @param $path
    //  * @return string
    //  */
    // protected function get_absolute_path($path) {
    //     $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
    //     $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
    //     $absolutes = [];
    //     foreach ($parts as $part) {
    //         if ('.' == $part) continue;
    //         if ('..' == $part) {
    //             array_pop($absolutes);
    //         } else {
    //             $absolutes[] = $part;
    //         }
    //     }
    //     return implode(DIRECTORY_SEPARATOR, $absolutes);
    // }



    /**
     * Get parent path
     * @param string $path
     * @return bool|string
     */
    protected function fm_get_parent_path($path) {
        $path = $this->fm_clean_path($path);
        if ($path != '') {
            $array = explode('/', $path);
            if (count($array) > 1) {
                $array = array_slice($array, 0, -1);
                return implode('/', $array);
            }
            return '';
        }
        return false;
    }

    protected function fm_get_display_path($file_path) {
        global $path_display_mode, $root_path, $root_url;
        switch ($path_display_mode) {
            case 'relative':
                return array(
                    'label' => 'Path',
                    'path' => $this->fm_enc($this->fm_convert_win(str_replace($root_path, '', $file_path)))
                );
            case 'host':
                $relative_path = str_replace($root_path, '', $file_path);
                return array(
                    'label' => 'Host Path',
                    'path' => $this->fm_enc($this->fm_convert_win('/' . $root_url . '/' . ltrim(str_replace('\\', '/', $relative_path), '/')))
                );
            case 'full':
            default:
                return array(
                    'label' => 'Full Path',
                    'path' => $this->fm_enc($this->fm_convert_win($file_path))
                );
        }
    }

    /**
     * Check file is in exclude list
     * @param string $file
     * @return bool
     */
    protected function fm_is_exclude_items($file) {
        $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        if (isset($exclude_items) and sizeof($exclude_items)) {
            unset($exclude_items);
        }

        $exclude_items = FM_EXCLUDE_ITEMS;
        if (version_compare(PHP_VERSION, '7.0.0', '<')) {
            $exclude_items = unserialize($exclude_items);
        }
        if (!in_array($file, $exclude_items) && !in_array("*.$ext", $exclude_items)) {
            return true;
        }
        return false;
    }

    /**
     * get language translations from json file
     * @param int $tr
     * @return array
     */
    protected function fm_get_translations($tr) {
        try {
            $content = @file_get_contents('translation.json');
            if ($content !== FALSE) {
                $lng = json_decode($content, TRUE);
                global $lang_list;
                foreach ($lng["language"] as $key => $value) {
                    $code = $value["code"];
                    $lang_list[$code] = $value["name"];
                    if ($tr)
                        $tr[$code] = $value["translation"];
                }
                return $tr;
            }
        } catch (Exception $e) {
            echo $e;
        }
    }

    /**
     * @param string $file
     * Recover all file sizes larger than > 2GB.
     * Works on php 32bits and 64bits and supports linux
     * @return int|string
     */
    protected 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 $exec_works;
        if (!isset($exec_works)) {
            $exec_works = (function_exists('exec') && !ini_get('safe_mode') && @exec('echo EXEC') == 'EXEC');
        }

        // try a shell command
        if ($exec_works) {
            $arg = escapeshellarg($file);
            $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)))) {
                return $size;
            }
        }

        // try the Windows COM interface
        if ($iswin && class_exists("COM")) {
            try {
                $fsobj = new \COM('Scripting.FileSystemObject');
                $f = $fsobj->GetFile(realpath($file));
                $size = $f->Size;
            } catch (Exception $e) {
                $size = null;
            }
            if (ctype_digit($size)) {
                return $size;
            }
        }

        // if all else fails
        return filesize($file);
    }

    /**
     * Get nice filesize
     * @param int $size
     * @return string
     */
    protected function fm_get_filesize($size) {
        $size = (float) $size;
        $units = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
        $power = ($size > 0) ? floor(log($size, 1024)) : 0;
        $power = ($power > (count($units) - 1)) ? (count($units) - 1) : $power;
        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.
     */
    protected 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
     */
    protected function fm_get_zif_info($path, $ext) {
        if ($ext == 'zip' && function_exists('zip_open')) {
            $arch = @zip_open($path);
            if ($arch) {
                $filenames = [];
                while ($zip_entry = @zip_read($arch)) {
                    $zip_name = @zip_entry_name($zip_entry);
                    $zip_folder = substr($zip_name, -1) == '/';
                    $filenames[] = array(
                        'name' => $zip_name,
                        'filesize' => @zip_entry_filesize($zip_entry),
                        'compressed_size' => @zip_entry_compressedsize($zip_entry),
                        'folder' => $zip_folder
                        //'compression_method' => zip_entry_compressionmethod($zip_entry),
                    );
                }
                @zip_close($arch);
                return $filenames;
            }
        } elseif ($ext == 'tar' && class_exists('PharData')) {
            $archive = new PharData($path);
            $filenames = [];
            foreach (new RecursiveIteratorIterator($archive) as $file) {
                $parent_info = $file->getPathInfo();
                $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);
                $filenames[] = array(
                    'name' => $zip_name,
                    'filesize' => $zip_info->getSize(),
                    'compressed_size' => $file->getCompressedSize(),
                    'folder' => $zip_folder
                );
            }
            return $filenames;
        }
        return false;
    }

    /**
     * Encode html entities
     * @param string $text
     * @return string
     */
    protected function fm_enc($text) {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }

    /**
     * Prevent XSS attacks
     * @param string $text
     * @return string
     */
    protected function fm_isvalid_filename($text) {
        return (strpbrk($text, '/?%*:|"<>') === FALSE) ? true : false;
    }

    /**
     * Save message in session
     * @param string $msg
     * @param string $status
     */
    protected function fm_set_msg($msg, $status = 'ok') {
        $_SESSION[FM_SESSION_ID]['message'] = $msg;
        $_SESSION[FM_SESSION_ID]['status'] = $status;
    }

    /**
     * Check if string is in UTF-8
     * @param string $string
     * @return int
     */
    protected function fm_is_utf8($string) {
        return preg_match('//u', $string);
    }

    /**
     * Convert file name to UTF-8 in Windows
     * @param string $filename
     * @return string
     */
    protected function fm_convert_win($filename) {
        if (FM_IS_WIN && function_exists('iconv')) {
            $filename = iconv(FM_ICONV_INPUT_ENC, 'UTF-8//IGNORE', $filename);
        }
        return $filename;
    }

    /**
     * @param $obj
     * @return array
     */
    protected function fm_object_to_array($obj) {
        if (!is_object($obj) && !is_array($obj)) {
            return $obj;
        }
        if (is_object($obj)) {
            $obj = get_object_vars($obj);
        }
        return array_map('fm_object_to_array', $obj);
    }

    /**
     * Get CSS classname for file
     * @param string $path
     * @return string
     */
    protected function fm_get_file_icon_class($path) {
        // get extension
        $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));

        switch ($ext) {
            case 'ico':
            case 'gif':
            case 'jpg':
            case 'jpeg':
            case 'jpc':
            case 'jp2':
            case 'jpx':
            case 'xbm':
            case 'wbmp':
            case 'png':
            case 'bmp':
            case 'tif':
            case 'tiff':
            case 'webp':
            case 'avif':
            case 'svg':
                $img = 'fa fa-picture-o';
                break;
            case 'passwd':
            case 'ftpquota':
            case 'sql':
            case 'js':
            case 'ts':
            case 'jsx':
            case 'tsx':
            case 'hbs':
            case 'json':
            case 'sh':
            case 'config':
            case 'twig':
            case 'tpl':
            case 'md':
            case 'gitignore':
            case 'c':
            case 'cpp':
            case 'cs':
            case 'py':
            case 'rs':
            case 'map':
            case 'lock':
            case 'dtd':
                $img = 'fa fa-file-code-o';
                break;
            case 'txt':
            case 'ini':
            case 'conf':
            case 'log':
            case 'htaccess':
            case 'yaml':
            case 'yml':
            case 'toml':
            case 'tmp':
            case 'top':
            case 'bot':
            case 'dat':
            case 'bak':
            case 'htpasswd':
            case 'pl':
                $img = 'fa fa-file-text-o';
                break;
            case 'css':
            case 'less':
            case 'sass':
            case 'scss':
                $img = 'fa fa-css3';
                break;
            case 'bz2':
            case 'zip':
            case 'rar':
            case 'gz':
            case 'tar':
            case '7z':
            case 'xz':
                $img = 'fa fa-file-archive-o';
                break;
            case 'php':
            case 'php4':
            case 'php5':
            case 'phps':
            case 'phtml':
                $img = 'fa fa-code';
                break;
            case 'htm':
            case 'html':
            case 'shtml':
            case 'xhtml':
                $img = 'fa fa-html5';
                break;
            case 'xml':
            case 'xsl':
                $img = 'fa fa-file-excel-o';
                break;
            case 'wav':
            case 'mp3':
            case 'mp2':
            case 'm4a':
            case 'aac':
            case 'ogg':
            case 'oga':
            case 'wma':
            case 'mka':
            case 'flac':
            case 'ac3':
            case 'tds':
                $img = 'fa fa-music';
                break;
            case 'm3u':
            case 'm3u8':
            case 'pls':
            case 'cue':
            case 'xspf':
                $img = 'fa fa-headphones';
                break;
            case 'avi':
            case 'mpg':
            case 'mpeg':
            case 'mp4':
            case 'm4v':
            case 'flv':
            case 'f4v':
            case 'ogm':
            case 'ogv':
            case 'mov':
            case 'mkv':
            case '3gp':
            case 'asf':
            case 'wmv':
            case 'webm':
                $img = 'fa fa-file-video-o';
                break;
            case 'eml':
            case 'msg':
                $img = 'fa fa-envelope-o';
                break;
            case 'xls':
            case 'xlsx':
            case 'ods':
                $img = 'fa fa-file-excel-o';
                break;
            case 'csv':
                $img = 'fa fa-file-text-o';
                break;
            case 'bak':
            case 'swp':
                $img = 'fa fa-clipboard';
                break;
            case 'doc':
            case 'docx':
            case 'odt':
                $img = 'fa fa-file-word-o';
                break;
            case 'ppt':
            case 'pptx':
                $img = 'fa fa-file-powerpoint-o';
                break;
            case 'ttf':
            case 'ttc':
            case 'otf':
            case 'woff':
            case 'woff2':
            case 'eot':
            case 'fon':
                $img = 'fa fa-font';
                break;
            case 'pdf':
                $img = 'fa fa-file-pdf-o';
                break;
            case 'psd':
            case 'ai':
            case 'eps':
            case 'fla':
            case 'swf':
                $img = 'fa fa-file-image-o';
                break;
            case 'exe':
            case 'msi':
                $img = 'fa fa-file-o';
                break;
            case 'bat':
                $img = 'fa fa-terminal';
                break;
            default:
                $img = 'fa fa-info-circle';
        }

        return $img;
    }

    /**
     * Get image files extensions
     * @return array
     */
    protected function fm_get_image_exts() {
        return array('ico', 'gif', 'jpg', 'jpeg', 'jpc', 'jp2', 'jpx', 'xbm', 'wbmp', 'png', 'bmp', 'tif', 'tiff', 'psd', 'svg', 'webp', 'avif');
    }

    /**
     * Get video files extensions
     * @return array
     */
    protected function fm_get_video_exts() {
        return array('avi', 'webm', 'wmv', 'mp4', 'm4v', 'ogm', 'ogv', 'mov', 'mkv');
    }

    /**
     * Get audio files extensions
     * @return array
     */
    protected function fm_get_audio_exts() {
        return array('wav', 'mp3', 'ogg', 'm4a');
    }

    /**
     * Get text file extensions
     * @return array
     */
    protected 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'
        );
    }

    /**
     * Get mime types of text files
     * @return array
     */
    protected function fm_get_text_mimes() {
        return array(
            'application/xml',
            'application/javascript',
            'application/x-javascript',
            'image/svg+xml',
            'message/rfc822',
            'application/json',
        );
    }

    /**
     * Get file names of text files w/o extensions
     * @return array
     */
    protected function fm_get_text_names() {
        return array(
            'license',
            'readme',
            'authors',
            'contributors',
            'changelog',
        );
    }

    /**
     * Get online docs viewer supported files extensions
     * @return array
     */
    protected function fm_get_onlineViewer_exts() {
        return array('doc', 'docx', 'xls', 'xlsx', 'pdf', 'ppt', 'pptx', 'ai', 'psd', 'dxf', 'xps', 'rar', 'odt', 'ods');
    }

    /**
     * It returns the mime type of a file based on its extension.
     * @param extension The file extension of the file you want to get the mime type for.
     * @return string|string[] The mime type of the file.
     */
    protected function fm_get_file_mimes($extension) {
        $fileTypes['swf'] = 'application/x-shockwave-flash';
        $fileTypes['pdf'] = 'application/pdf';
        $fileTypes['exe'] = 'application/octet-stream';
        $fileTypes['zip'] = 'application/zip';
        $fileTypes['doc'] = 'application/msword';
        $fileTypes['xls'] = 'application/vnd.ms-excel';
        $fileTypes['ppt'] = 'application/vnd.ms-powerpoint';
        $fileTypes['gif'] = 'image/gif';
        $fileTypes['png'] = 'image/png';
        $fileTypes['jpeg'] = 'image/jpg';
        $fileTypes['jpg'] = 'image/jpg';
        $fileTypes['webp'] = 'image/webp';
        $fileTypes['avif'] = 'image/avif';
        $fileTypes['rar'] = 'application/rar';

        $fileTypes['ra'] = 'audio/x-pn-realaudio';
        $fileTypes['ram'] = 'audio/x-pn-realaudio';
        $fileTypes['ogg'] = 'audio/x-pn-realaudio';

        $fileTypes['wav'] = 'video/x-msvideo';
        $fileTypes['wmv'] = 'video/x-msvideo';
        $fileTypes['avi'] = 'video/x-msvideo';
        $fileTypes['asf'] = 'video/x-msvideo';
        $fileTypes['divx'] = 'video/x-msvideo';

        $fileTypes['mp3'] = 'audio/mpeg';
        $fileTypes['mp4'] = 'audio/mpeg';
        $fileTypes['mpeg'] = 'video/mpeg';
        $fileTypes['mpg'] = 'video/mpeg';
        $fileTypes['mpe'] = 'video/mpeg';
        $fileTypes['mov'] = 'video/quicktime';
        $fileTypes['swf'] = 'video/quicktime';
        $fileTypes['3gp'] = 'video/quicktime';
        $fileTypes['m4a'] = 'video/quicktime';
        $fileTypes['aac'] = 'video/quicktime';
        $fileTypes['m3u'] = 'video/quicktime';

        $fileTypes['php'] = ['application/x-php'];
        $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'];
        }
        return $fileTypes[$extension];
    }

    /**
     * This function scans the files and folder recursively, and return matching files
     * @param string $dir
     * @param string $filter
     * @return array|null
     */
    protected function scan($dir = '', $filter = '') {
        $path = FM_ROOT_PATH . '/' . $dir;
        if ($path) {
            $ite = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
            $rii = new RegexIterator($ite, "/(" . $filter . ")/i");

            $files = [];
            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
     */
    protected function fm_download_file($fileLocation, $fileName, $chunkSize = 1024) {
        if (connection_status() != 0) {
            return false;
        }
        $extension = pathinfo($fileName, PATHINFO_EXTENSION);

        $contentType = $this->fm_get_file_mimes($extension);

        if (is_array($contentType)) {
            $contentType = implode(' ', $contentType);
        }

        $size = filesize($fileLocation);

        if ($size == 0) {
            $this->fm_set_msg($this->lng('Zero byte file! Aborting download'), 'error');
            $FM_PATH = FM_PATH;
            $this->fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));

            return (false);
        }

        @ini_set('magic_quotes_runtime', 0);
        $fp = fopen("$fileLocation", "rb");

        if ($fp === false) {
            $this->fm_set_msg($this->lng('Cannot open file! Aborting download'), 'error');
            $FM_PATH = FM_PATH;
            $this->fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
            return (false);
        }

        // headers
        header('Content-Description: File Transfer');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header("Content-Transfer-Encoding: binary");
        header("Content-Type: {$contentType}");

        $contentDisposition = 'attachment';

        if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) {
            $fileName = preg_replace('/\./', '%2e', $fileName, substr_count($fileName, '.') - 1);
            header("Content-Disposition: $contentDisposition;filename=\"$fileName\"");
        } else {
            header("Content-Disposition: $contentDisposition;filename=\"$fileName\"");
        }

        header("Accept-Ranges: bytes");
        $range = 0;

        if (isset($_SERVER['HTTP_RANGE'])) {
            list($a, $range) = explode("=", $_SERVER['HTTP_RANGE']);
            str_replace($range, "-", $range);
            $size2 = $size - 1;
            $new_length = $size - $range;
            header("HTTP/1.1 206 Partial Content");
            header("Content-Length: $new_length");
            header("Content-Range: bytes $range$size2/$size");
        } else {
            $size2 = $size - 1;
            header("Content-Range: bytes 0-$size2/$size");
            header("Content-Length: " . $size);
        }
        $fileLocation = realpath($fileLocation);
        while (ob_get_level()) ob_end_clean();
        readfile($fileLocation);

        fclose($fp);

        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.
     */
    protected function fm_get_theme() {
        $result = '';
        if (FM_THEME == "dark") {
            $result = "text-white bg-dark";
        }
        return $result;
    }


    /**
     * Language Translation System
     * @param string $txt
     * @return string
     * 
     * @deprecated  In the context of this app
     */
    protected function lng($txt) {
        return match ($txt) {
            'AppName'                                          => 'Tiny File Manager',
            'AppTitle'                                         => 'File Manager',
            'Login'                                            => 'Sign in',
            'Username'                                         => 'Username',
            'Password'                                         => 'Password',
            'Logout'                                           => 'Sign Out',
            'Move'                                             => 'Move',
            'Copy'                                             => 'Copy',
            'Save'                                             => 'Save',
            'SelectAll'                                        => 'Select all',
            'UnSelectAll'                                      => 'Unselect all',
            'File'                                             => 'File',
            'Back'                                             => 'Back',
            'Size'                                             => 'Size',
            'Perms'                                            => 'Perms',
            'Modified'                                         => 'Modified',
            'Owner'                                            => 'Owner',
            'Search'                                           => 'Search',
            'NewItem'                                          => 'New Folder',
            'Folder'                                           => 'Folder',
            'Delete'                                           => 'Delete',
            'Rename'                                           => 'Rename',
            'CopyTo'                                           => 'Copy to',
            'DirectLink'                                       => 'Direct link',
            'UploadingFiles'                                   => 'Upload Files',
            'ChangePermissions'                                => 'Change Permissions',
            'Copying'                                          => 'Copying',
            'CreateNewItem'                                    => 'Create New Folder',
            'Name'                                             => 'Name',
            'AdvancedEditor'                                   => 'Advanced Editor',
            'Actions'                                          => 'Actions',
            'Folder is empty'                                  => 'Folder is empty',
            'Upload'                                           => 'Upload',
            'Cancel'                                           => 'Cancel',
            'InvertSelection'                                  => 'Invert Selection',
            'DestinationFolder'                                => 'Destination Folder',
            'ItemType'                                         => 'Item Type',
            'ItemName'                                         => 'Item Name',
            'CreateNow'                                        => 'Create Folder',
            'Download'                                         => 'Download',
            'Open'                                             => 'Open',
            'UnZip'                                            => 'UnZip',
            'UnZipToFolder'                                    => 'UnZip to folder',
            'Edit'                                             => 'Edit',
            'NormalEditor'                                     => 'Normal Editor',
            'BackUp'                                           => 'Back Up',
            'SourceFolder'                                     => 'Source Folder',
            'Files'                                            => 'Files',
            'Move'                                             => 'Move',
            'Change'                                           => 'Change',
            'Settings'                                         => 'Settings',
            'Language'                                         => 'Language',
            'ErrorReporting'                                   =>  'Error Reporting',
            'ShowHiddenFiles'                                  => 'Show Hidden Files',
            'Help'                                             => 'Help',
            'Created'                                          => 'Created',
            'Help Documents'                                   => 'Help Documents',
            'Report Issue'                                     => 'Report Issue',
            'Generate'                                         => 'Generate',
            'FullSize'                                         => 'Full Size',
            'HideColumns'                                      => 'Hide Perms/Owner columns',
            'You are logged in'                                => 'You are logged in',
            'Nothing selected'                                 => 'Nothing selected',
            'Paths must be not equal'                          => 'Paths must be not equal',
            'Renamed from'                                     => 'Renamed from',
            'Archive not unpacked'                             => 'Archive not unpacked',
            'Deleted'                                          => 'Deleted',
            'Archive not created'                              => 'Archive not created',
            'Copied from'                                      => 'Copied from',
            'Permissions changed'                              => 'Permissions changed',
            'to'                                               => 'to',
            'Saved Successfully'                               => 'Saved Successfully',
            'not found!'                                       => 'not found!',
            'File Saved Successfully'                          => 'File Saved Successfully',
            'Archive'                                          => 'Archive',
            'Permissions not changed'                          => 'Permissions not changed',
            'Select folder'                                    => 'Select folder',
            'Source path not defined'                          => 'Source path not defined',
            'already exists'                                   => 'already exists',
            'Error while moving from'                          => 'Error while moving from',
            'Create archive?'                                  => 'Create archive?',
            'Invalid file or folder name'                      => 'Invalid file or folder name',
            'Archive unpacked'                                 => 'Archive unpacked',
            'File extension is not allowed'                    => 'File extension is not allowed',
            'Root path'                                        => 'Root path',
            'Error while renaming from'                        => 'Error while renaming from',
            'File not found'                                   => 'File not found',
            'Error while deleting items'                       => 'Error while deleting items',
            'Moved from'                                       => 'Moved from',
            'Generate new password hash'                       => 'Generate new password hash',
            'Login failed. Invalid username or password'       => 'Login failed. Invalid username or password',
            'password_hash not supported, Upgrade PHP version' => 'password_hash not supported, Upgrade PHP version',
            'Advanced Search'                                  => 'Advanced Search',
            'Error while copying from'                         => 'Error while copying from',
            'Invalid characters in file name'                  => 'Invalid characters in file name',
            'FILE EXTENSION HAS NOT SUPPORTED'                 => 'FILE EXTENSION HAS NOT SUPPORTED',
            'Selected files and folder deleted'                => 'Selected files and folder deleted',
            'Error while fetching archive info'                => 'Error while fetching archive info',
            'Delete selected files and folders?'               => 'Delete selected files and folders?',
            'Search file in folder and subfolders...'          => 'Search file in folder and subfolders...',
            'Access denied. IP restriction applicable'         => 'Access denied. IP restriction applicable',
            'Invalid characters in file or folder name'        => 'Invalid characters in file or folder name',
            'Operations with archives are not available'       => 'Operations with archives are not available',
            'File or folder with this path already exists'     => 'File or folder with this path already exists',
            default                                            => $txt,
        };
    }


    /**
     * It prints the css/js files into html
     * @param key The key of the external file to print.
     * @return The value of the key in the  array.
     */
    protected function print_external($key, bool $return = false) {
        if ($return) {
            if (!array_key_exists($key, self::EXTERNAL)) {
                // throw new Exception('Key missing in external: ' . key);
                return "<!-- EXTERNAL: MISSING KEY {$key} -->";
            }
            return self::EXTERNAL[$key];
        }
        if (!array_key_exists($key, self::EXTERNAL)) {
            // throw new Exception('Key missing in external: ' . key);
            echo "<!-- EXTERNAL: MISSING KEY {$key} -->";
            return;
        }

        echo self::EXTERNAL[$key];
    }
}
