├── Dockerfile ├── README.md ├── README.zh-CN.md ├── img ├── DockerOperation.png ├── admin.png ├── default.png ├── login.png └── web.png └── src ├── fileadmin.php ├── files.js └── index.php /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:5.6.37-apache 2 | MAINTAINER cjy0526 "1332794849@qq.com" 3 | 4 | ENV MAXSIZE=5000M USER=fileadmin PASSWORD=fileadmin 5 | 6 | ADD src /var/www/html 7 | 8 | # change php.ini 9 | RUN sed -i "s/upload_max_filesize = 2M/upload_max_filesize = ${MAXSIZE}/" $PHP_INI_DIR/php.ini-production \ 10 | && sed -i "s/post_max_size = 8M/post_max_size = ${MAXSIZE}/" $PHP_INI_DIR/php.ini-production \ 11 | && mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" 12 | 13 | # install gd requirement && install extensions 14 | RUN apt-get update && apt-get install -y libgd-dev libzip-dev zip \ 15 | && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ 16 | && docker-php-ext-configure zip --with-libzip \ 17 | && docker-php-ext-install gd mbstring fileinfo exif zip 18 | 19 | 20 | 21 | # set username and password for admin.php which can manage files 22 | RUN cd /var/www/html/ && mkdir content && chmod -R 777 content \ 23 | && sed -i "22s/''/'$USER'/" fileadmin.php \ 24 | && sed -i "23s/''/'$PASSWORD'/" fileadmin.php 25 | 26 | 27 | EXPOSE 80 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Files 2 | 3 | The project provides a docker image for Files App(files.photo.gallery). 4 | 5 | ![Docker Pulls](https://img.shields.io/docker/pulls/cjy0526/files) 6 | 7 |

8 | English | 中文 9 |

10 | 11 | 12 | ### Introduction 13 | 14 | For the purpose of learning,I replace the `files.js` with cracked version which just remove authorization code. 15 | 16 | So you can run this image directly to display your files. 17 | 18 | Please support the original if you could. 19 | 20 | 21 | 22 | ### usage 23 | 24 | you can just run: 25 | 26 | ``` 27 | docker run -d --name files -v /root/content:/var/www/html/content -p 3000:80 cjy0526/files:v1 28 | ``` 29 | 30 | ![DockerOperation](img/DockerOperation.png) 31 | 32 | ![web](img/web.png) 33 | 34 | 35 | 36 | 37 | 38 | If you want to build image yourself,please clone the project and run: 39 | 40 | ``` 41 | docker build -t files:v1 . 42 | 43 | docker run -d --name files -v /root/content:/var/www/html/content -p 3000:80 files:v1 44 | ``` 45 | 46 | 47 | 48 | ### 0.3.1 update 49 | 50 | Files 0.3.1 can upload/delete/rename/new file and folder and download files as zip 51 | 52 | In our docker container , you can login fileadmin.php to manage files. Default username:fileadmin , password: fileadmin , max upload file size: 5000M. 53 | 54 | ![](img/default.png) 55 | 56 | ![](img/login.png) 57 | 58 | ![](img/admin.png) 59 | 60 | 61 | 62 | ``` 63 | docker run -d --name files -v /root/content:/var/www/html/content -p 3000:80 cjy0526/files:v2 64 | ``` 65 | 66 | 67 | 68 | If you want to change username or password or max upload file size, just clone this project and edit Dockerfile then build image yourself. 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # Files 2 | 3 | 这个项目提供了Files App(files.photo.gallery) 一款简洁漂亮的目录程序的docker镜像 4 | 5 | ![Docker Pulls](https://img.shields.io/docker/pulls/cjy0526/files) 6 | 7 |

8 | English | 中文 9 |

10 | 11 | ### 介绍 12 | 13 | 出于学习研究的目的,我将`files.js`替换成了去除授权代码的破解版。 14 | 15 | 因此你可以直接运行这个镜像用于展示自己的文件。 16 | 17 | 如有能力,请支持正版。 18 | 19 | 20 | 21 | ### 使用 22 | 23 | 你可以直接拉取运行已经上传到dockerhub上的镜像: 24 | 25 | ``` 26 | docker pull cjy0526/files:v1 27 | docker run -d --name files -v /root/content:/var/www/html/content -p 3000:80 cjy0526/files:v1 28 | ``` 29 | 30 | ![DockerOperation](/img/DockerOperation.png) 31 | 32 | ![web](/img/web.png) 33 | 34 | 35 | 36 | 37 | 38 | 如果想自行编译这个镜像的话,请把这个项目克隆到本地并运行: 39 | 40 | ``` 41 | docker build -t files:v1 . 42 | docker run -d --name files -v /root/content:/var/www/html/content -p 3000:80 files:v1 43 | ``` 44 | 45 | 46 | 47 | ### 0.3.1版本更新 48 | 49 | Files 0.3.1 更新了文件的上传、删除、重命名、新增 和 文件夹的新增、重命名 以及 以zip打包下载所有的文件 50 | 51 | 在这个容器中,可以通过访问`/fileadmin.php`登录后台管理文件。默认用户名:fileadmin,默认密码:fileadmin,最大文件上传容量:5000M 52 | 53 | ![](img/default.png) 54 | 55 | ![](img/login.png) 56 | 57 | ![](img/admin.png) 58 | 59 | 60 | 61 | ``` 62 | docker run -d --name files -v /root/content:/var/www/html/content -p 3000:80 cjy0526/files:v2 63 | ``` 64 | 65 | 66 | 67 | 如果你想更改默认的用户名、密码、最大文件上传容量,克隆这个项目编辑Dockerfile并且自行编译镜像 -------------------------------------------------------------------------------- /img/DockerOperation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijynb/files/ce7af61a26889ec0e6fb5fbd0c05854bf059ba7c/img/DockerOperation.png -------------------------------------------------------------------------------- /img/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijynb/files/ce7af61a26889ec0e6fb5fbd0c05854bf059ba7c/img/admin.png -------------------------------------------------------------------------------- /img/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijynb/files/ce7af61a26889ec0e6fb5fbd0c05854bf059ba7c/img/default.png -------------------------------------------------------------------------------- /img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijynb/files/ce7af61a26889ec0e6fb5fbd0c05854bf059ba7c/img/login.png -------------------------------------------------------------------------------- /img/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijynb/files/ce7af61a26889ec0e6fb5fbd0c05854bf059ba7c/img/web.png -------------------------------------------------------------------------------- /src/fileadmin.php: -------------------------------------------------------------------------------- 1 | 'content/', // root path relative to script / empty is same as files app location 19 | 'start_path' => false, // start path relative to script. If empty, root is start path 20 | 21 | // login 22 | 'username' => '', 23 | 'password' => '', // Add password directly or use https://files.photo.gallery/tools/hash/ to encrypt the password (encrypted password is more secure, as it prevents your password from being exposed directly in a file). 24 | 25 | // images 26 | 'load_images' => true, 27 | 'load_files_proxy_php' => false, 28 | 'load_images_max_filesize' => 50000000, // maximum file size (bytes) for un-resized images loaded into list 29 | 'load_svg_max_filesize' => 100000, // 100k 30 | 'image_resize_enabled' => true, // resize images for the interface 31 | 'image_resize_cache' => true, // todo: remove this option and just use 'cache? 32 | 'image_resize_dimensions' => 320, 33 | 'image_resize_dimensions_retina' => 480, 34 | 'image_resize_dimensions_allowed' => '', // comma-separated list of allowed resize dimensions 35 | 'image_resize_types' => 'jpeg, png, gif, webp, bmp', // image types to resize / jpeg, png, gif, webp, bmp 36 | 'image_resize_quality' => 85, 37 | 'image_resize_function' => 'imagecopyresampled', // imagecopyresampled / imagecopyresized 38 | 'image_resize_sharpen' => true, 39 | 'image_resize_memory_limit' => 128, // 128 MB is suffient to resize images around 6000 px / 0 = ignore memory 40 | 'image_resize_max_pixels' => 30000000, // 30 MP equivalent to an image 6000 x 5000 / 0 = no limit 41 | 'image_resize_min_ratio' => 1.5, // min size diff original vs resize. Only resizes if ratio > min ratio 42 | 'image_resize_cache_direct' => false, // if enabled and delete cache, must increase cache_key 43 | 'folder_preview_image' => true, // enable folder preview images / might be slow as it requires searching each dir for images 44 | 'folder_preview_default' => '_filespreview.jpg', // use this image as folder preview if exists in dir 45 | 46 | // menu 47 | 'menu_enabled' => true, 48 | 'menu_show' => true, 49 | 'menu_max_depth' => 5, 50 | 'menu_sort' => 'name_asc', // name_asc, name_desc, date_asc, date_desc 51 | 'menu_cache_validate' => true, 52 | 'menu_load_all' => false, 53 | 'menu_recursive_symlinks' => true, // List sub-directories of symlinks in the main menu. May cause loops and duplicates 54 | 55 | // files layout 56 | 'layout' => 'rows', // list, imagelist, blocks, grid, rows, columns 57 | 'sort' => 'name_asc', // name, date, filesize, kind / asc, desc 58 | 'sort_dirs_first' => true, // sort dirs on top 59 | 60 | // cache 61 | 'cache' => true, 62 | 'cache_key' => 0, 63 | 'storage_path' => '_files', 64 | 65 | // exclude files directories regex 66 | 'files_exclude' => '', // '/\.(pdf|jpe?g)$/i' 67 | 'dirs_exclude' => '', //'/\/Convert|\/football|\/node_modules(\/|$)/i', 68 | 'allow_symlinks' => true, // allow symlinks 69 | 70 | // various 71 | 'history' => true, 72 | 'breadcrumbs' => true, 73 | 'transitions' => true, 74 | 'click' => 'popup', // popup, modal, download, window, menu / default item click 75 | 'click_window' => '', // 'pdf, html, php, zip' / list of file extensions to open directly on click 76 | 'click_window_popup' => true, // Popup instead of new tab. Useful for viewing PDF, HTML and text type documents / desktop only 77 | 'code_max_load' => 100000, // max filesize of text files to load and preview 78 | 'topbar_sticky' => 'scroll', // true, false, 'scroll' 79 | 'check_updates' => false, // show notification in topbar with option to update when new version is available 80 | 'allow_tasks' => false, 81 | 'get_mime_type' => false, // get file mime type from server (slow) instead of from extension (fast) 82 | 'context_menu' => true, // disable context-menu button and right-click menu 83 | 'prevent_right_click' => false, // blocks browser right-click menu on sensitive items (images, list items, menu) 84 | 'license_key' => '', 85 | 'filter_live' => true, // live search filtering on keyboard input / does not apply for mobile devices 86 | 'filter_props' => 'name, filetype, mime, features, title', // file properties to filter / name, filetype, mime, features, title, headline, description, creator, credit, copyright, keywords, city, sub-location, province-state' 87 | 'download_dir' => 'zip', // download all files in folder / 'zip' / 'files' / '' false (disabled) 88 | 'download_dir_cache' => 'dir', // enable caching of created zip dirs / 'dir' / 'storage' / '' false (disabled) 89 | 90 | // filemanager options 91 | 'allow_upload' => true, // allow uploader 92 | 'allow_delete' => true, // allow deleting files and folders 93 | 'allow_rename' => true, // allow renaming files and folders 94 | 'allow_new_folder' => true, // allow make new directory 95 | 'allow_new_file' => true, // allow make new empty file 96 | 'allow_duplicate' => true, // allow duplicate files 97 | 'allow_text_edit' => true, // allow editing text-based files in modal 98 | 'demo_mode' => false, // block all filemanager operations but allow them to show in interface / used in Files app demo 99 | 100 | // uploader options 101 | 'upload_allowed_file_types' => '', // comma-separated list of allowed upload file types / empty = allow any / 'jpeg, jpg, image/*' 102 | 'upload_max_filesize' => 0, // [bytes] / 0 = unlimited (but limited by server PHP upload_max_filesize) 103 | 'upload_note' => '', // include a small text note at bottom of uploader / 'Max file size %upload_max_filesize%' 104 | 'upload_exists' => 'increment', // 'increment' / 'overwrite' / 'fail' 105 | 106 | // popup options 107 | 'popup_video' => true, // opens videos in the popup (instead of modal) 108 | 'popup_transition' => 'glide', // none, slide, glide, fade, zoom, pop, elastic 109 | 'popup_transition_play' => 'inherit', // transition in play mode 110 | 'popup_interval' => 5000, // interval ms between slides in play mode 111 | 'popup_caption' => true, // enable popup caption 112 | 'popup_caption_hide' => true, // autohide popup caption after a few seconds without user input 113 | 'popup_caption_style' => 'block', // block, box, gradient, topbar, none 114 | 'popup_caption_align' => 'center-left', // left, center-left, center, right 115 | 116 | // video 117 | 'video_thumbs' => true, // allow video thumbnails / requires FFmpeg and PHP exec() function enabled. 118 | 'video_ffmpeg_path' => 'ffmpeg', // path to ffmpeg command, normally 'ffmpeg' http://ffmpeg.org/ 119 | 'video_autoplay' => true, // video autoplay on click 120 | 121 | // language 122 | 'lang_default' => 'en', // default language if browser lang is not supported/detected or lang_auto is disabled 123 | 'lang_auto' => true, // automatically load language based on detected browser language 124 | 'lang_menu' => false, // display dropdown menu to select language 125 | ); 126 | 127 | // config (will popuplate) 128 | public static $config = array(); 129 | 130 | // app vars 131 | static $__dir__ = __DIR__; 132 | static $__file__ = __FILE__; 133 | static $assets; 134 | static $prod = true; 135 | static $version = '0.3.1'; 136 | static $root; 137 | static $doc_root; 138 | static $has_login = false; 139 | static $storage_path; 140 | static $storage_is_within_doc_root = false; 141 | static $storage_config_realpath; 142 | static $storage_config; 143 | static $cache_path; 144 | static $image_resize_cache_direct; 145 | static $image_resize_dimensions_retina = false; 146 | static $dirs_hash = false; 147 | static $local_config_file = '_filesconfig.php'; 148 | static $username = false; 149 | static $password = false; 150 | static $x3_path = false; 151 | 152 | // get config 153 | private function get_config($path) { 154 | if(empty($path) || !file_exists($path)) return array(); 155 | $config = include $path; 156 | return empty($config) || !is_array($config) ? array() : array_map(function($v){ 157 | return is_string($v) ? trim($v) : $v; 158 | }, $config); 159 | } 160 | 161 | // files check system and config [diagnostics] 162 | private function files_check($local_config, $storage_path, $storage_config, $user_config, $user_valid){ 163 | 164 | // BASIC DIAGNOSTICS 165 | echo 'Files App check system and config.

Files App ' . config::$version . '

' . (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] . '
' : '') . 'PHP ' . phpversion() . '
' . (isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '') . '

* The following tests are only to help diagnose feature-specific issues.

'; 166 | // output helper 167 | function prop($name, $success = 'neutral', $val = false){ 168 | return '
'. $name . ($val ? ': ' . $val . '' : '') . '
'; 169 | } 170 | // filesystem 171 | echo prop('storage_path exists', file_exists(config::$config['storage_path'])); 172 | if(file_exists(config::$config['storage_path'])) echo prop('storage_path is_writeable', is_writable(config::$config['storage_path'])); 173 | echo prop('root is_writeable', is_writable(config::$config['root'])); 174 | // extension_loaded 175 | if(function_exists('extension_loaded')) foreach (['gd', 'exif'] as $name) echo prop($name, extension_loaded($name)); 176 | // zip 177 | echo prop('ZipArchive', class_exists('ZipArchive')); 178 | // function_exsists 179 | foreach (['mime_content_type', 'finfo_file', 'iptcparse', 'exif_imagetype', 'session_start', 'ini_get', 'exec'] as $name) echo prop($name . '()', function_exists($name)); 180 | // ffmpeg 181 | if(function_exists('exec')) echo prop('ffmpeg', !!exec('type -P ' . config::$config['video_ffmpeg_path'])); 182 | // ini_get 183 | if(function_exists('ini_get')) foreach (['memory_limit', 'file_uploads', 'upload_max_filesize', 'post_max_size', 'max_file_uploads'] as $name) echo prop($name, 'neutral', @ini_get($name)); 184 | 185 | // CONFIG OUTPUT 186 | echo '

Config

'; 187 | // invalid and duplicate arrays 188 | $user_invalid = array_diff_key($user_config, self::$default); 189 | $user_duplicate = array_intersect_assoc($user_valid, self::$default); 190 | 191 | // items 192 | $items = array( 193 | ['arr' => $local_config, 'comment' => "// LOCAL CONFIG\n// " . self::$local_config_file], 194 | ['arr' => $storage_config, 'comment' => "// STORAGE CONFIG\n// " . rtrim($storage_path ?: '', '\/') . '/config/config.php'], 195 | ['arr' => $user_invalid, 'comment' => "// INVALID PARAMS\n// The following custom parameters will be ignored as they are not valid:", 'var' => '$invalid', 'hide' => empty($user_invalid)], 196 | ['arr' => $user_duplicate, 'comment' => "// DUPLICATE DEFAULT PARAMS\n// The following custom parameters will have no effect as they are identical to defaults:", 'var' => '$duplicate', 'hide' => empty($user_duplicate)], 197 | ['arr' => $user_valid, 'comment' => "// USER CONFIG\n// User config parameters.", 'var' => '$user', 'hide' => (empty($local_config) || empty($storage_config)) && empty($user_invalid)], 198 | ['arr' => self::$config, 'comment' => "// CONFIG\n// User parameters merged with default parameters.", 'var' => '$config'], 199 | ['arr' => self::$default, 'comment' => "// DEFAULT CONFIG\n// Default config parameters.", 'var' => '$default'], 200 | //['arr' => array_diff_key(get_class_vars('config'), array_flip(['default', 'config'])), 'comment' => "// STATIC VARS\n// Static app vars.", 'var' => '$static'] 201 | ); 202 | 203 | // loop 204 | $output = ' $props) { 206 | $is_empty = empty($props['arr']); 207 | if(isset($props['hide']) && $props['hide']) continue; 208 | foreach (['username', 'password', 'license_key', 'allow_tasks', '__dir__', '__file__'] as $prop) if(isset($props['arr'][$prop]) && !empty($props['arr'][$prop]) && is_string($props['arr'][$prop])) $props['arr'][$prop] = '***'; 209 | $export = $is_empty ? 'array ()' : var_export($props['arr'], true); 210 | $comment = preg_replace('/\n/', " [" . count($props['arr']) . "]\n", $props['comment'], 1); 211 | $var = isset($props['var']) ? $props['var'] . ' = ' : 'return '; 212 | $output .= PHP_EOL . $comment . PHP_EOL . $var . $export . ';' . PHP_EOL; 213 | } 214 | highlight_string($output . PHP_EOL . ';?>'); 215 | echo '
'; 216 | exit; 217 | } 218 | 219 | // save config 220 | public static function save_config($config = array()){ 221 | $save_config = array_intersect_key(array_replace(self::$storage_config, $config), self::$default); 222 | $export = preg_replace("/ '/", " //'", var_export(array_replace(self::$default, $save_config), true)); 223 | foreach ($save_config as $key => $value) if($value !== self::$default[$key]) $export = str_replace("//'" . $key, "'" . $key, $export); 224 | return @file_put_contents(config::$storage_config_realpath, 'storage_path must be a unique dir.'); 241 | self::$storage_config_realpath = $storage_realpath ? $storage_realpath . '/config/config.php' : false; 242 | self::$storage_config = self::get_config(self::$storage_config_realpath); 243 | 244 | // config 245 | $user_config = array_replace(self::$storage_config, $local_config); 246 | $user_valid = array_intersect_key($user_config, self::$default); 247 | self::$config = array_replace(self::$default, $user_valid); 248 | 249 | // files check with ?check=true 250 | if(get('check')) self::files_check($local_config, $storage_path, self::$storage_config, $user_config, $user_valid); 251 | // if(get('phpinfo')) { phpinfo(); exit; } // check system phpinfo with ?phpinfo=true / disabled for security / un-comment if you want to use 252 | 253 | // CDN assets 254 | self::$assets = self::$prod ? 'https://cdn.jsdelivr.net/npm/files.photo.gallery@' . self::$version . '/' : ''; 255 | 256 | // root 257 | self::$root = real_path(self::$config['root']); 258 | if($is_doc && !self::$root) error('root dir "' . self::$config['root'] . '" does not exist.'); 259 | 260 | // doc root 261 | self::$doc_root = real_path($_SERVER['DOCUMENT_ROOT']); 262 | 263 | // login credentials 264 | self::$username = self::$config['username']; 265 | self::$password = self::$config['password']; 266 | 267 | // X3 compatibility / x3 login / images from X3 resize cache / invalidate X3 cache on filemanager op / X3 license 268 | $x3_path = dirname(self::$root); 269 | self::$x3_path = file_exists($x3_path . '/app/x3.inc.php') ? $x3_path : false; 270 | if(self::$x3_path && self::$username === 'x3'){ 271 | $x3_config = file_exists($x3_path . '/config/config.user.json') ? json_decode(file_get_contents($x3_path . '/config/config.user.json'), true) : false; 272 | self::$username = isset($x3_config['back']['panel']['username']) ? $x3_config['back']['panel']['username'] : 'admin'; 273 | self::$password = isset($x3_config['back']['panel']['password']) ? $x3_config['back']['panel']['password'] : 'admin'; 274 | if(self::$username === 'admin' && self::$password === 'admin' && isset($x3_config['back']['panel']['use_db'])) error('Sorry, Files app is not compatible with X3 panel database login. Assign "username" and "password" separately in _files/config/config.php.', 403); 275 | } 276 | 277 | // has_login 278 | self::$has_login = self::$username || self::$password ? true : false; 279 | 280 | // $image_cache 281 | $image_cache = self::$config['image_resize_enabled'] && self::$config['image_resize_cache'] && self::$config['load_images'] ? true : false; 282 | 283 | // cache enabled 284 | if($image_cache || self::$config['cache']){ 285 | 286 | // create storage_path 287 | if(empty($storage_realpath)){ 288 | $storage_path = is_string($storage_path) ? rtrim($storage_path, '\/') : false; 289 | if(empty($storage_path)) error('Invalid storage_path parameter.'); 290 | mkdir_or_error($storage_path); 291 | $storage_realpath = real_path($storage_path); 292 | if(empty($storage_realpath)) error("storage_path $storage_path does not exist and can't be created."); 293 | self::$storage_config_realpath = $storage_realpath . '/config/config.php'; // update since it wasn't assigned 294 | } 295 | self::$storage_path = $storage_realpath; 296 | 297 | // storage path is within doc root 298 | if(is_within_docroot(self::$storage_path)) self::$storage_is_within_doc_root = true; 299 | 300 | // cache_path real path 301 | self::$cache_path = self::$storage_path . '/cache'; 302 | 303 | // create storage dirs 304 | if($is_doc){ 305 | $create_dirs = [$storage_realpath . '/config']; 306 | if($image_cache) $create_dirs[] = self::$cache_path . '/images'; 307 | if(self::$config['cache']) array_push($create_dirs, self::$cache_path . '/folders', self::$cache_path . '/menu'); 308 | foreach($create_dirs as $create_dir) mkdir_or_error($create_dir); 309 | } 310 | 311 | // create/update config file, with default parameters commented out. 312 | if($is_doc && self::$storage_config_realpath && (!file_exists(self::$storage_config_realpath) || filemtime(self::$storage_config_realpath) < filemtime(__FILE__))) self::save_config(); 313 | 314 | // image resize cache direct 315 | if(self::$config['image_resize_cache_direct'] && !self::$has_login && self::$config['load_images'] && self::$config['image_resize_cache'] && self::$config['image_resize_enabled'] && self::$storage_is_within_doc_root) self::$image_resize_cache_direct = true; 316 | } 317 | 318 | // image_resize_dimensions_retina 319 | if(self::$config['image_resize_dimensions_retina'] && self::$config['image_resize_dimensions_retina'] > self::$config['image_resize_dimensions']) self::$image_resize_dimensions_retina = self::$config['image_resize_dimensions_retina']; 320 | 321 | // dirs hash 322 | self::$dirs_hash = substr(md5(self::$doc_root . self::$__dir__ . self::$root . self::$version . self::$config['cache_key'] . self::$image_resize_cache_direct . self::$config['files_exclude'] . self::$config['dirs_exclude']), 0, 6); 323 | 324 | // login 325 | if(self::$has_login) check_login($is_doc); 326 | } 327 | }; 328 | 329 | // login page 330 | function login_page($is_login_attempt, $sidx, $is_logout, $client_hash){ 331 | ?> 332 | 333 | 334 | 335 | 336 | 337 | 338 | Login 339 | 340 | 341 | 342 |
343 | 365 | 366 | = 5.5 && !password_needs_rehash(config::$password, PASSWORD_DEFAULT) ? password_verify(trim($_POST['fpassword']), config::$password) : (trim($_POST['fpassword']) == config::$password)) && 404 | $_POST['client_hash'] === $client_hash && 405 | $_POST['sidx'] === $sidx 406 | ){ 407 | $_SESSION['login'] = $login_hash; 408 | 409 | // display login page and exit 410 | } else { 411 | login_page($is_login_attempt, $sidx, $is_logout, $client_hash); 412 | } 413 | 414 | // not logged in (images or post API requests), don't show form. 415 | } else if(post('action')){ 416 | json_error('login'); 417 | 418 | } else { 419 | error('You are not logged in.', 401); 420 | } 421 | } 422 | } 423 | 424 | // 425 | function mkdir_or_error($path){ 426 | if(!file_exists($path) && !mkdir($path, 0777, true)) error('Failed to create ' . $path, 500); 427 | } 428 | function real_path($path){ 429 | $real_path = realpath($path); 430 | return $real_path ? str_replace('\\', '/', $real_path) : false; 431 | } 432 | function root_relative($dir){ 433 | return ltrim(substr($dir, strlen(config::$root)), '\/'); 434 | } 435 | function root_absolute($dir){ 436 | return config::$root . ($dir ? '/' . $dir : ''); 437 | } 438 | function is_within_path($path, $root){ 439 | return strpos($path . '/', $root . '/') === 0; 440 | } 441 | function is_within_root($path){ 442 | return is_within_path($path, config::$root); 443 | } 444 | function is_within_docroot($path){ 445 | return is_within_path($path, config::$doc_root); 446 | } 447 | function get_folders_cache_path($name){ 448 | return config::$cache_path . '/folders/' . $name . '.json'; 449 | } 450 | function get_json_cache_url($name){ 451 | $file = get_folders_cache_path($name); 452 | return file_exists($file) ? get_url_path($file) : false; 453 | } 454 | function get_dir_cache_path($dir, $mtime = false){ 455 | if(!config::$config['cache'] || !$dir) return; 456 | return get_folders_cache_path(get_dir_cache_hash($dir, $mtime)); 457 | } 458 | function get_dir_cache_hash($dir, $mtime = false){ 459 | return config::$dirs_hash . '.' . substr(md5($dir), 0, 6) . '.' . ($mtime ?: filemtime($dir)); 460 | // 461 | } 462 | function header_memory_time(){ 463 | return (isset($_SERVER['REQUEST_TIME_FLOAT']) ? round(microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'], 3) . 's, ' : '') . round(memory_get_peak_usage() / 1048576, 1) . 'M'; 464 | } 465 | 466 | // read file 467 | // todo: add files-date header 468 | function read_file($path, $mime = false, $msg = false, $props = false, $cache_headers = false, $clone = false){ 469 | if(!$path || !file_exists($path)) return false; 470 | $cloned = $clone && @copy($path, $clone) ? true : false; 471 | //if($mime == 'image/svg') $mime .= '+xml'; 472 | header('content-type: ' . ($mime ?: 'image/jpeg')); 473 | header('content-length: ' . filesize($path)); 474 | header('content-disposition: filename="' . basename($path) . '"'); 475 | if($msg) header('files-msg: ' . $msg . ($cloned ? ' [cloned to ' . basename($clone) . ']' : '') . ' [' . ($props ? $props . ', ' : '') . header_memory_time() . ']'); 476 | if($cache_headers) set_cache_headers(); 477 | if(!is_readable($path) || readfile($path) === false) error('Failed to read file ' . $path . '.', 400); 478 | exit; 479 | } 480 | 481 | // get mime 482 | function get_mime($path){ 483 | if(function_exists('mime_content_type')){ 484 | return mime_content_type($path); 485 | } else { 486 | return function_exists('finfo_file') ? finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path) : false; 487 | } 488 | } 489 | 490 | // set cache headers 491 | function set_cache_headers(){ 492 | $seconds = 31536000; // 1 year; 493 | header('expires: ' . gmdate('D, d M Y H:i:s', time() + $seconds) . ' GMT'); 494 | header("cache-control: public, max-age=$seconds, s-maxage=$seconds, immutable"); 495 | header('pragma: cache'); 496 | // header("Last-Modified:" . gmdate('D, d M Y H:i:s', time() - $seconds) . ' GMT'); 497 | // etag? 498 | } 499 | 500 | // get image cache path 501 | function get_image_cache_path($path, $image_resize_dimensions, $filesize, $filemtime){ 502 | return config::$cache_path . '/images/' . substr(md5($path), 0, 6) . '.' . $filesize . '.' . $filemtime . '.' . $image_resize_dimensions . '.jpg'; 503 | } 504 | 505 | // is exclude 506 | function is_exclude($path = false, $is_dir = true, $symlinked = false){ 507 | 508 | // early exit 509 | if(!$path || $path === config::$root) return; 510 | 511 | // exclude all paths that start with /_files* (reserved for any files and folders to be ignored and hidden from Files app) 512 | if(strpos($path, '/_files') !== false) return true; 513 | 514 | // exclude files PHP application 515 | if($path === config::$__file__) return true; 516 | 517 | // symlinks not allowed 518 | if($symlinked && !config::$config['allow_symlinks']) return true; 519 | 520 | // exclude storage path 521 | if(config::$storage_path && is_within_path($path, config::$storage_path)) return true; 522 | 523 | // dirs_exclude: check root relative dir path 524 | if(config::$config['dirs_exclude']) { 525 | $dirname = $is_dir ? $path : dirname($path); 526 | if($dirname !== config::$root && preg_match(config::$config['dirs_exclude'], substr($dirname, strlen(config::$root)))) return true; 527 | } 528 | 529 | // files_exclude: check vs basename 530 | if(!$is_dir){ 531 | $basename = basename($path); 532 | if($basename === config::$local_config_file) return true; 533 | if(config::$config['files_exclude'] && preg_match(config::$config['files_exclude'], $basename)) return true; 534 | } 535 | } 536 | 537 | // valid root path 538 | function valid_root_path($path, $is_dir = false){ 539 | 540 | // invalid 541 | if($path === false) return; 542 | if(!$is_dir && empty($path)) return; // path cannot be empty if file 543 | if($path && substr($path, -1) == '/') return; // path should never be root absolute or end with / 544 | 545 | // absolute path may differ if path contains symlink 546 | $root_absolute = root_absolute($path); 547 | $real_path = real_path($root_absolute); 548 | 549 | // file does not exist 550 | if(!$real_path) return; 551 | 552 | // security checks if path contains symlink 553 | if($root_absolute !== $real_path) { 554 | if(strpos(($is_dir ? $path : dirname($path)), ':') !== false) return; // dir may not contain ':' 555 | if(strpos($path, '..') !== false) return; // path may not contain '..' 556 | if(is_exclude($root_absolute, $is_dir, true)) return; 557 | } 558 | 559 | // nope 560 | if(!is_readable($real_path)) return; // not readable 561 | if($is_dir && !is_dir($real_path)) return; // dir check 562 | if(!$is_dir && !is_file($real_path)) return; // file check 563 | if(is_exclude($real_path, $is_dir)) return; // exclude path 564 | 565 | // return root_absolute 566 | return $root_absolute; 567 | } 568 | 569 | // image create from 570 | function image_create_from($path, $type){ 571 | if(!$path || !$type) return; 572 | if($type === IMAGETYPE_JPEG){ 573 | return imagecreatefromjpeg($path); 574 | } else if ($type === IMAGETYPE_PNG) { 575 | return imagecreatefrompng($path); 576 | } else if ($type === IMAGETYPE_GIF) { 577 | return imagecreatefromgif($path); 578 | } else if ($type === 18/*IMAGETYPE_WEBP*/) { 579 | if(version_compare(PHP_VERSION, '5.4.0') >= 0) return imagecreatefromwebp($path); 580 | } else if ($type === IMAGETYPE_BMP) { 581 | if(version_compare(PHP_VERSION, '7.2.0') >= 0) return imagecreatefrombmp($path); 582 | } 583 | } 584 | 585 | // get file (proxy or resize image) 586 | function get_file($path, $resize = false){ 587 | 588 | // validate 589 | if(!$path) error('Invalid file request.', 404); 590 | $path = real_path($path); // in case of symlink path 591 | $mime = get_mime($path); // may return false if server does not support mime_content_type() or finfo_file() 592 | 593 | // video thumbnail (FFmpeg) 594 | if($resize == 'video') { 595 | 596 | // requirements with diagnostics / only check $mime if $mime detected 597 | if($mime && strtok($mime, '/') !== 'video') error('' . basename($path) . ' (' . $mime . ') is not a video.', 415); 598 | foreach (array('video_thumbs', 'load_images', 'image_resize_cache', 'video_ffmpeg_path') as $key) if(empty(config::$config[$key])) error($key . ' option disabled.', 400); 599 | if(!function_exists('exec')) error('PHP exec() function is disabled on this server.', 400); 600 | if(empty(exec('type -P ' . config::$config['video_ffmpeg_path']))) error('Can\'t find FFmpeg in location "' . config::$config['video_ffmpeg_path'] . '".', 400); 601 | 602 | // get cache path 603 | $cache = get_image_cache_path($path, 480, filesize($path), filemtime($path)); 604 | 605 | // check for cached video thumbnail / $path, $mime, $msg, $props, $cache_headers 606 | if($cache) read_file($cache, null, 'Video thumb served from cache', null, true); 607 | 608 | // ffmpeg command 609 | $cmd = escapeshellarg(config::$config['video_ffmpeg_path']) . ' -i ' . escapeshellarg($path) . ' -deinterlace -an -ss 1 -t 1 -vf "thumbnail,scale=480:320:force_original_aspect_ratio=increase,crop=480:320" -r 1 -y -f mjpeg ' . $cache . ' 2>&1'; 610 | 611 | // try to execute command 612 | exec($cmd, $output, $result_code); 613 | 614 | // fail if result_code is anything else than 0 615 | if($result_code) error("Error generating thumbnail for video (\$result_code $result_code)", 400); 616 | 617 | // output created video thumbnail 618 | read_file($cache, null, 'Video thumb created', null, true); 619 | 620 | // resize image 621 | } else if($resize){ 622 | if($mime && strtok($mime, '/') !== 'image') error('' . basename($path) . ' (' . $mime . ') is not an image.', 415); 623 | foreach (['load_images', 'image_resize_enabled'] as $key) if(!config::$config[$key]) error('[' .$key . '] disabled.', 400); 624 | $resize_dimensions = intval($resize); 625 | if(!$resize_dimensions) error("Invalid resize parameter $resize.", 400); 626 | $allowed = config::$config['image_resize_dimensions_allowed'] ?: []; 627 | if(!in_array($resize_dimensions, array_merge([config::$config['image_resize_dimensions'], config::$config['image_resize_dimensions_retina']], array_map('intval', is_array($allowed) ? $allowed : explode(',', $allowed))))) error("Resize parameter $resize_dimensions is not allowed.", 400); 628 | resize_image($path, $resize_dimensions); 629 | 630 | // proxy file 631 | } else { 632 | 633 | // disable if !proxy and path is within document root (file should never be proxied) 634 | if(!config::$config['load_files_proxy_php'] && is_within_docroot($path)) error('File cannot be proxied.', 400); 635 | 636 | // read file / $mime or 'application/octet-stream' 637 | read_file($path, ($mime ?: 'application/octet-stream'), $msg = 'File ' . basename($path) . ' proxied.', false, true); 638 | } 639 | } 640 | 641 | // sharpen resized image 642 | function sharpen_image($image){ 643 | $matrix = array( 644 | array(-1, -1, -1), 645 | array(-1, 20, -1), 646 | array(-1, -1, -1), 647 | ); 648 | $divisor = array_sum(array_map('array_sum', $matrix)); 649 | $offset = 0; 650 | imageconvolution($image, $matrix, $divisor, $offset); 651 | } 652 | 653 | // exif orientation 654 | // https://github.com/gumlet/php-image-resize/blob/master/lib/ImageResize.php 655 | function exif_orientation($orientation, &$image){ 656 | if(empty($orientation) || !is_numeric($orientation) || $orientation < 3 || $orientation > 8) return; 657 | $image = imagerotate($image, array(6 => 270, 5 => 270, 3 => 180, 4 => 180, 8 => 90, 7 => 90)[$orientation], null); 658 | if(in_array($orientation, array(5, 4, 7)) && function_exists('imageflip')) imageflip($image, IMG_FLIP_HORIZONTAL); 659 | return true; 660 | } 661 | 662 | // resize image 663 | function resize_image($path, $resize_dimensions, $clone = false){ 664 | 665 | // file size 666 | $file_size = filesize($path); 667 | 668 | // header props 669 | $header_props = 'w:' . $resize_dimensions . ', q:' . config::$config['image_resize_quality'] . ', ' . config::$config['image_resize_function'] . ', cache:' . (config::$config['image_resize_cache'] ? '1' : '0'); 670 | 671 | // cache 672 | $cache = config::$config['image_resize_cache'] ? get_image_cache_path($path, $resize_dimensions, $file_size, filemtime($path)) : NULL; 673 | if($cache) read_file($cache, null, 'Resized image served from cache', $header_props, true, $clone); 674 | 675 | // imagesize 676 | $info = getimagesize($path); 677 | if(empty($info) || !is_array($info)) error('Invalid image / failed getimagesize().', 500); 678 | $resize_ratio = max($info[0], $info[1]) / $resize_dimensions; 679 | 680 | // image_resize_max_pixels early exit 681 | if(config::$config['image_resize_max_pixels'] && $info[0] * $info[1] > config::$config['image_resize_max_pixels']) error('Image resolution ' . $info[0] . ' x ' . $info[1] . ' (' . ($info[0] * $info[1]) . ' px) exceeds image_resize_max_pixels (' . config::$config['image_resize_max_pixels'] . ' px).', 400); 682 | 683 | // header props 684 | $header_props .= ', ' . $info['mime'] . ', ' . $info[0] . 'x' . $info[1] . ', ratio:' . round($resize_ratio, 2); 685 | 686 | // check if image type is in image_resize_types / jpeg, png, gif, webp, bmp 687 | $is_resize_type = in_array(image_type_to_extension($info[2], false), array_map(function($key){ 688 | $type = trim(strtolower($key)); 689 | return $type === 'jpg' ? 'jpeg' : $type; 690 | }, explode(',', config::$config['image_resize_types']))); 691 | 692 | // serve original if !$is_resize_type || resize ratio < image_resize_min_ratio 693 | if((!$is_resize_type || $resize_ratio < max(config::$config['image_resize_min_ratio'], 1)) && !read_file($path, $info['mime'], 'Original image served', $header_props, true, $clone)) error('File does not exist.', 404); 694 | 695 | // Calculate new image dimensions. 696 | $resize_width = round($info[0] / $resize_ratio); 697 | $resize_height = round($info[1] / $resize_ratio); 698 | 699 | // memory 700 | $memory_limit = config::$config['image_resize_memory_limit'] && function_exists('ini_get') ? (int) @ini_get('memory_limit') : false; 701 | if($memory_limit && $memory_limit > -1){ 702 | // $memory_required = ceil(($info[0] * $info[1] * 4 + $resize_width * $resize_height * 4) / 1048576); 703 | $memory_required = round(($info[0] * $info[1] * (isset($info['bits']) ? $info['bits'] / 8 : 1) * (isset($info['channels']) ? $info['channels'] : 3) * 1.33 + $resize_width * $resize_height * 4) / 1048576, 1); 704 | $new_memory_limit = function_exists('ini_set') ? max($memory_limit, config::$config['image_resize_memory_limit']) : $memory_limit; 705 | if($memory_required > $new_memory_limit) error('Resizing this image requires at least ' . $memory_required . 'M. Your current PHP memory_limit is ' . $new_memory_limit .'M.', 400); 706 | if($memory_limit < $new_memory_limit && @ini_set('memory_limit', $new_memory_limit . 'M')) $header_props .= ', ' . $memory_limit . 'M => ' . $new_memory_limit . 'M (min ' . $memory_required . 'M)'; 707 | } 708 | 709 | // new dimensions headers 710 | $header_props .= ', ' . $resize_width . 'x' . $resize_height; 711 | 712 | // create new $image 713 | $image = image_create_from($path, $info[2]); 714 | if(!$image) error('Failed to create image resource.', 500); 715 | 716 | // Create final image with new dimensions. 717 | $new_image = imagecreatetruecolor($resize_width, $resize_height); 718 | if(!call_user_func(config::$config['image_resize_function'], $new_image, $image, 0, 0, 0, 0, $resize_width, $resize_height, $info[0], $info[1])) error('Failed to resize image.', 500); 719 | 720 | // destroy original $image resource 721 | imagedestroy($image); 722 | 723 | // exif orientation 724 | $exif = function_exists('exif_read_data') ? @exif_read_data($path) : false; 725 | if(!empty($exif) && is_array($exif) && isset($exif['Orientation']) && exif_orientation($exif['Orientation'], $new_image)) $header_props .= ', orientated from EXIF:' . $exif['Orientation']; 726 | 727 | // sharpen resized image 728 | if(config::$config['image_resize_sharpen']) sharpen_image($new_image); 729 | 730 | // save to cache 731 | if($cache){ 732 | if(!imagejpeg($new_image, $cache, config::$config['image_resize_quality'])) error('imagejpeg() failed to create and cache resized image.', 500); 733 | 734 | // clone cache (used for folder previews) 735 | if($clone) @copy($cache, $clone); 736 | 737 | // cache disabled / direct output 738 | } else { 739 | set_cache_headers(); 740 | header('content-type: image/jpeg'); 741 | header('files-msg: Resized image served [' . $header_props . ', ' . header_memory_time() . ']'); 742 | if(!imagejpeg($new_image, null, config::$config['image_resize_quality'])) error('imagejpeg() failed to create and output resized image.', 500); 743 | } 744 | 745 | // destroy image 746 | imagedestroy($new_image); 747 | 748 | // cache readfile 749 | if($cache && !read_file($cache, null, 'Resized image cached and served', $header_props, true, $clone)) error('Cache file does not exist.', 404); 750 | 751 | // 752 | exit; 753 | // https://github.com/maxim/smart_resize_image/blob/master/smart_resize_image.function.php 754 | // https://github.com/gavmck/resize/blob/master/php/lib/resize-class.php 755 | // https://github.com/gumlet/php-image-resize/blob/master/lib/ImageResize.php 756 | // https://www.bitrepository.com/resize-an-image-keeping-its-aspect-ratio-using-php-and-gd.html 757 | } 758 | 759 | function get_url_path($dir){ 760 | if(!is_within_docroot($dir)) return false; 761 | 762 | // if in __dir__ path, __dir__ relative 763 | if(is_within_path($dir, config::$__dir__)) return $dir === config::$__dir__ ? '.' : substr($dir, strlen(config::$__dir__) + 1); 764 | 765 | // doc root, doc root relative 766 | return $dir === config::$doc_root ? '/' : substr($dir, strlen(config::$doc_root)); 767 | } 768 | 769 | // get dir 770 | function get_dir($path, $files = false, $json_url = false){ 771 | 772 | // realpath 773 | $realpath = $path ? real_path($path) : false; 774 | if(!$realpath) return; // no real path for any reason 775 | $symlinked = $realpath !== $path; // path is symlinked at some point 776 | 777 | // exclude 778 | if(is_exclude($path, true, $symlinked)) return; // exclude 779 | if($symlinked && is_exclude($realpath, true, $symlinked)) return; // exclude check again symlink realpath 780 | 781 | // vars 782 | $filemtime = filemtime($realpath); 783 | $url_path = get_url_path($realpath) ?: ($symlinked ? get_url_path($path) : false); 784 | $is_readable = is_readable($realpath); 785 | 786 | // array 787 | $arr = array( 788 | 'basename' => basename($realpath) ?: basename($path) ?: '', 789 | 'fileperms' => substr(sprintf('%o', fileperms($realpath)), -4), 790 | 'filetype' => 'dir', 791 | 'is_readable' => $is_readable, 792 | 'is_writeable' => is_writeable($realpath), 793 | 'is_link' => $symlinked ? is_link($path) : false, 794 | 'is_dir' => true, 795 | 'mime' => 'directory', 796 | 'mtime' => $filemtime, 797 | 'path' => root_relative($path) 798 | ); 799 | 800 | // url path 801 | if($url_path) $arr['url_path'] = $url_path; 802 | 803 | // get_files() || config::menu_load_all 804 | if($files && $is_readable) { 805 | 806 | // files array 807 | $arr['files'] = get_files_data($path, $url_path, $arr['dirsize'], $arr['files_count'], $arr['images_count'], $arr['preview']); 808 | 809 | // download_dir cache direct access to zip / better caching and no need to access PHP / only works when download_dir_cache === 'dir' 810 | /*if($url_path && config::$config['download_dir'] === 'zip' && config::$config['download_dir_cache'] === 'dir') { 811 | $zip = $realpath . '/_files.zip'; 812 | if(file_exists($zip) && filemtime($zip) >= $filemtime) $arr['zip'] = get_url_path($zip); 813 | }*/ 814 | } 815 | 816 | // json cache path 817 | if($json_url && config::$storage_is_within_doc_root && !config::$has_login && config::$config['cache']){ 818 | $json_cache = get_json_cache_url(get_dir_cache_hash($realpath, $filemtime)); 819 | if($json_cache) $arr['json_cache'] = $json_cache; 820 | } 821 | 822 | // 823 | return $arr; 824 | } 825 | 826 | // get menu sort 827 | function get_menu_sort($dirs){ 828 | if(strpos(config::$config['menu_sort'], 'date') === 0){ 829 | usort($dirs, function($a, $b) { 830 | return filemtime($a) - filemtime($b); 831 | }); 832 | } else { 833 | natcasesort($dirs); 834 | } 835 | return substr(config::$config['menu_sort'], -4) === 'desc' ? array_reverse($dirs) : $dirs; 836 | } 837 | 838 | // recursive directory scan 839 | function get_dirs($path = false, &$arr = array(), $depth = 0) { 840 | 841 | // get this dir (ignore root, unless load all ... root already loaded into page) 842 | if($depth || config::$config['menu_load_all']) { 843 | $data = get_dir($path, config::$config['menu_load_all'], !config::$config['menu_load_all']); 844 | if(!$data) return $arr; 845 | 846 | // 847 | $arr[] = $data; 848 | 849 | // max depth 850 | if(config::$config['menu_max_depth'] && $depth >= config::$config['menu_max_depth']) return $arr; 851 | 852 | // don't recursive if symlink 853 | if($data['is_link'] && !config::$config['menu_recursive_symlinks']) return $arr; 854 | } 855 | 856 | // get dirs from files array if $data['files'] or glob subdirs 857 | $subdirs = isset($data['files']) ? array_filter(array_map(function($file){ 858 | return $file['filetype'] === 'dir' ? root_absolute($file['path']) : false; 859 | }, $data['files'])) : glob($path . '/*', GLOB_NOSORT|GLOB_ONLYDIR); 860 | 861 | // sort and loop subdirs 862 | if(!empty($subdirs)) foreach(get_menu_sort($subdirs) as $subdir) get_dirs($subdir, $arr, $depth + 1); 863 | 864 | // return 865 | return $arr; 866 | } 867 | 868 | // encode to UTF-8 when required 869 | function safe_iptc_tag($val){ 870 | $val = @substr($val, 0, 1000); 871 | return @mb_detect_encoding($val, 'UTF-8', true) ? $val : @utf8_encode($val); 872 | } 873 | 874 | // get IPTC 875 | function get_iptc($image_info){ 876 | if(!$image_info || !isset($image_info['APP13']) || !function_exists('iptcparse')) return; 877 | $app13 = @iptcparse($image_info['APP13']); 878 | if(empty($app13)) return; 879 | $iptc = array(); 880 | 881 | // loop title, headline, description, creator, credit, copyright, keywords, city, sub-location and province-state 882 | foreach (['title'=>'005', 'headline'=>'105', 'description'=>'120', 'creator'=>'080', 'credit'=>'110', 'copyright'=>'116', 'keywords'=>'025', 'city'=>'090', 'sub-location'=>'092', 'province-state'=>'095'] as $name => $code) { 883 | if(isset($app13['2#' . $code][0]) && !empty($app13['2#' . $code][0])) $iptc[$name] = $name === 'keywords' ? $app13['2#' . $code] : safe_iptc_tag($app13['2#' . $code][0]); 884 | } 885 | 886 | // return IPTC 887 | return $iptc; 888 | } 889 | 890 | // get exif 891 | function get_exif($path){ 892 | if(!function_exists('exif_read_data')) return; 893 | $exif_data = @exif_read_data($path, 'ANY_TAG', 0); // @exif_read_data($path); 894 | if(empty($exif_data) || !is_array($exif_data)) return; 895 | $exif = array(); 896 | foreach (array('DateTime', 'DateTimeOriginal', 'ExposureTime', 'FNumber', 'FocalLength', 'Make', 'Model', 'Orientation', 'ISOSpeedRatings', 'Software') as $name) { 897 | if(isset($exif_data[$name])) $exif[$name] = trim($exif_data[$name]); 898 | } 899 | if(isset($exif['DateTime'])) $exif['DateTime'] = @strtotime($exif['DateTime']); 900 | if(isset($exif['DateTimeOriginal'])) $exif['DateTimeOriginal'] = @strtotime($exif['DateTimeOriginal']); 901 | 902 | /*LensInfo 24-70mm f/? 903 | Lens EF24-70mm f/2.8L USM 904 | LensID 230*/ 905 | 906 | // ApertureFNumber (f_stop) 907 | if(isset($exif_data['COMPUTED']['ApertureFNumber'])) $exif['ApertureFNumber'] = $exif_data['COMPUTED']['ApertureFNumber']; 908 | 909 | // flash 910 | if(isset($exif_data['Flash'])) $exif['Flash'] = ($exif_data['Flash'] & 1) != 0; 911 | 912 | // GPS 913 | $gps = get_image_location($exif_data); 914 | if(!empty($gps)) $exif['gps'] = $gps; 915 | 916 | // return 917 | return $exif; 918 | } 919 | 920 | // exif GPS / get_image_location 921 | function get_image_location($exif) { 922 | $arr = array(); 923 | foreach (array('GPSLatitude', 'GPSLongitude') as $key) { 924 | if(!isset($exif[$key]) || !isset($exif[$key.'Ref'])) return false; 925 | $coordinate = $exif[$key]; 926 | if(is_string($coordinate)) $coordinate = array_map('trim', explode(',', $coordinate)); 927 | for ($i = 0; $i < 3; $i++) { 928 | $part = explode('/', $coordinate[$i]); 929 | if (count($part) == 1) { 930 | $coordinate[$i] = $part[0]; 931 | } else if (count($part) == 2) { 932 | if($part[1] == 0) return false; // can't be 0 / invalid GPS 933 | $coordinate[$i] = floatval($part[0])/floatval($part[1]); 934 | } else { 935 | $coordinate[$i] = 0; 936 | } 937 | } 938 | list($degrees, $minutes, $seconds) = $coordinate; 939 | $sign = ($exif[$key.'Ref'] == 'W' || $exif[$key.'Ref'] == 'S') ? -1 : 1; 940 | $arr[] = $sign * ($degrees + $minutes/60 + $seconds/3600); 941 | } 942 | return $arr; 943 | } 944 | 945 | /*function get_image_location($exif){ 946 | $arr = array('GPSLatitudeRef', 'GPSLatitude', 'GPSLongitudeRef', 'GPSLongitude'); 947 | foreach ($arr as $val) { 948 | if(!isset($exif[$val])) return false; 949 | } 950 | 951 | $GPSLatitudeRef = $exif[$arr[0]]; 952 | $GPSLatitude = $exif[$arr[1]]; 953 | $GPSLongitudeRef= $exif[$arr[2]]; 954 | $GPSLongitude = $exif[$arr[3]]; 955 | 956 | $lat_degrees = count($GPSLatitude) > 0 ? gps2Num($GPSLatitude[0]) : 0; 957 | $lat_minutes = count($GPSLatitude) > 1 ? gps2Num($GPSLatitude[1]) : 0; 958 | $lat_seconds = count($GPSLatitude) > 2 ? gps2Num($GPSLatitude[2]) : 0; 959 | 960 | $lon_degrees = count($GPSLongitude) > 0 ? gps2Num($GPSLongitude[0]) : 0; 961 | $lon_minutes = count($GPSLongitude) > 1 ? gps2Num($GPSLongitude[1]) : 0; 962 | $lon_seconds = count($GPSLongitude) > 2 ? gps2Num($GPSLongitude[2]) : 0; 963 | 964 | $lat_direction = ($GPSLatitudeRef == 'W' or $GPSLatitudeRef == 'S') ? -1 : 1; 965 | $lon_direction = ($GPSLongitudeRef == 'W' or $GPSLongitudeRef == 'S') ? -1 : 1; 966 | 967 | $latitude = $lat_direction * ($lat_degrees + ($lat_minutes / 60) + ($lat_seconds / (60*60))); 968 | $longitude = $lon_direction * ($lon_degrees + ($lon_minutes / 60) + ($lon_seconds / (60*60))); 969 | 970 | return array($latitude, $longitude); 971 | } 972 | 973 | function gps2Num($coordPart){ 974 | $parts = explode('/', $coordPart); 975 | if(count($parts) <= 0) return 0; 976 | if(count($parts) == 1) return $parts[0]; 977 | if($parts[1] == 0) return 0; 978 | return floatval($parts[0]) / floatval($parts[1]); 979 | }*/ 980 | 981 | // 982 | function get_files_data($dir, $url_path = false, &$dirsize = 0, &$files_count = 0, &$images_count = 0, &$preview = false){ 983 | 984 | // scandir 985 | $filenames = scandir($dir, SCANDIR_SORT_NONE); 986 | if(empty($filenames)) return array(); 987 | $items = array(); 988 | 989 | // look for folder_preview_default (might be excluded in loop) 990 | if(config::$config['folder_preview_default'] && in_array(config::$config['folder_preview_default'], $filenames)) $preview = config::$config['folder_preview_default']; 991 | 992 | // loop filenames 993 | foreach($filenames as $filename) { 994 | 995 | // 996 | if($filename === '.' || $filename === '..') continue; 997 | $path = $dir . '/' . $filename; 998 | 999 | // paths 1000 | $realpath = real_path($path); // differs from $path only if is symlinked 1001 | if(!$realpath) continue; // no real path for any reason, for example symlink dead 1002 | $symlinked = $realpath !== $path; // path is symlinked at some point 1003 | 1004 | // filetype 1005 | $filetype = filetype($realpath); 1006 | $is_dir = $filetype === 'dir' ? true : false; 1007 | 1008 | // exclude 1009 | if(is_exclude($path, $is_dir, $symlinked)) continue; // exclude 1010 | if($symlinked && is_exclude($realpath, $is_dir, $symlinked)) continue; // exclude check again symlink realpath 1011 | 1012 | // vars 1013 | if(!$is_dir) $files_count ++; // files count 1014 | $is_link = $symlinked ? is_link($path) : false; // symlink 1015 | $basename = $is_link ? (basename($realpath) ?: $filename) : $filename; 1016 | $filemtime = filemtime($realpath); 1017 | $is_readable = is_readable($realpath); 1018 | $filesize = $is_dir ? false : filesize($realpath); 1019 | if($filesize) $dirsize += $filesize; 1020 | 1021 | // url_path / symlink 1022 | $item_url_path = $symlinked ? get_url_path($realpath) : false; // url_path from realpath if symlinked 1023 | if(!$item_url_path && $url_path) $item_url_path = $url_path . ($url_path === '/' ? '' : '/') . ($is_link ? basename($path) : $basename); 1024 | 1025 | // root path // path relative to config::$root 1026 | if(!$symlinked || is_within_root($realpath)){ 1027 | $root_path = root_relative($realpath); 1028 | 1029 | // path is symlinked and !is_within_root(), get path-relative 1030 | } else { 1031 | 1032 | // root path to symlink 1033 | $root_path = root_relative($path); 1034 | 1035 | // check for symlink loop 1036 | if($is_link && $is_dir && $path && $root_path) { 1037 | $basename_path = basename($root_path); 1038 | if($basename_path && preg_match('/(\/|^)' . $basename_path. '\//', $root_path)){ 1039 | $loop_path = ''; 1040 | $segments = explode('/', $root_path); 1041 | array_pop($segments); 1042 | foreach ($segments as $segment) { 1043 | $loop_path .= ($loop_path ? '/' : '') . $segment; 1044 | if($segment !== $basename_path) continue; 1045 | $loop_abs_path = root_absolute($loop_path); 1046 | if(!is_link($loop_abs_path) || $realpath !== real_path($loop_abs_path)) continue; 1047 | $root_path = $loop_path; 1048 | $item_url_path = get_url_path($loop_abs_path) ?: $item_url_path; // new symlink is within doc_root 1049 | break; 1050 | } 1051 | } 1052 | } 1053 | } 1054 | 1055 | // add properties 1056 | $item = array( 1057 | 'basename' => $basename, 1058 | 'fileperms' => substr(sprintf('%o', fileperms($realpath)), -4), 1059 | 'filetype' => $filetype, 1060 | 'filesize' => $filesize, 1061 | 'is_readable' => $is_readable, 1062 | 'is_writeable' => is_writeable($realpath), 1063 | 'is_link' => $is_link, 1064 | 'is_dir' => $is_dir, 1065 | 'mtime' => $filemtime, 1066 | 'path' => $root_path 1067 | ); 1068 | 1069 | // optional props 1070 | //$ext = !$is_dir ? pathinfo($realpath, PATHINFO_EXTENSION) : false; 1071 | $ext = !$is_dir ? substr(strrchr($realpath, '.'), 1) : false; 1072 | if($ext) { 1073 | $ext = strtolower($ext); 1074 | $item['ext'] = $ext; 1075 | } 1076 | $mime = $is_dir ? 'directory' : ($is_readable && (!$ext || $ext === 'ts' || config::$config['get_mime_type']) ? get_mime($realpath) : false); 1077 | if($mime) $item['mime'] = $mime; 1078 | if($item_url_path) $item['url_path'] = $item_url_path; 1079 | 1080 | // image / check from mime, fallback to extension 1081 | $is_image = $is_dir ? false : ($mime ? (strtok($mime, '/') === 'image' && !strpos($mime, 'svg')) : in_array($ext, array('gif','jpg','jpeg','jpc','jp2','jpx','jb2','png','swf','psd','bmp','tiff','tif','wbmp','xbm','ico','webp'))); 1082 | if($is_image){ 1083 | 1084 | // imagesize 1085 | $imagesize = $is_readable ? @getimagesize($realpath, $info) : false; 1086 | 1087 | // image count and icon 1088 | $images_count ++; 1089 | $item['icon'] = 'image'; 1090 | 1091 | // is imagesize 1092 | if(!empty($imagesize) && is_array($imagesize)){ 1093 | 1094 | // set folder_preview 1095 | if(!$preview && in_array($ext, array('gif','jpg','jpeg','png'))) $preview = $basename; 1096 | 1097 | // start image array 1098 | $image = array(); 1099 | foreach (array(0 => 'width', 1 => 'height', 2 => 'type', 'bits' => 'bits', 'channels' => 'channels', 'mime' => 'mime') as $key => $name) if(isset($imagesize[$key])) $image[$name] = $imagesize[$key]; 1100 | 1101 | // mime from image 1102 | if(!$mime && isset($image['mime'])) $item['mime'] = $image['mime']; 1103 | 1104 | // IPTC 1105 | $iptc = $info ? get_iptc($info) : false; 1106 | if(!empty($iptc)) $image['iptc'] = $iptc; 1107 | 1108 | // EXIF 1109 | $exif = get_exif($realpath); 1110 | if(!empty($exif)) { 1111 | $image['exif'] = $exif; 1112 | if(isset($exif['DateTimeOriginal'])) $item['DateTimeOriginal'] = $exif['DateTimeOriginal']; 1113 | // invert width/height if exif orientation 1114 | if(isset($exif['Orientation']) && $exif['Orientation'] > 4 && $exif['Orientation'] < 9){ 1115 | $image['width'] = $imagesize[1]; 1116 | $image['height'] = $imagesize[0]; 1117 | } 1118 | } 1119 | 1120 | // image resize cache direct 1121 | if(config::$image_resize_cache_direct){ 1122 | $resize1 = get_image_cache_path($realpath, config::$config['image_resize_dimensions'], $filesize, $filemtime); 1123 | if(file_exists($resize1)) $image['resize' . config::$config['image_resize_dimensions']] = get_url_path($resize1); 1124 | $retina = config::$image_resize_dimensions_retina; 1125 | if($retina){ 1126 | $resize2 = get_image_cache_path($realpath, $retina, $filesize, $filemtime); 1127 | if(file_exists($resize2)) $image['resize' . $retina] = get_url_path($resize2); 1128 | } 1129 | } 1130 | 1131 | // add image to item 1132 | $item['image'] = $image; 1133 | 1134 | // get real mime if getimagesize fails. Could be non-image disguised as image extension 1135 | } else if($is_readable && !$mime){ 1136 | $mime = get_mime($realpath); 1137 | if($mime) { 1138 | $item['mime'] = $mime; 1139 | if(strtok($mime, '/') !== 'image'){ // unset images_count and icon because is not image after all 1140 | $images_count --; 1141 | unset($item['icon']); 1142 | } 1143 | } 1144 | } 1145 | } 1146 | 1147 | // add to items with basename as key 1148 | $items[$basename] = $item; 1149 | } 1150 | 1151 | // Sort dirs on top and natural case sort / need to do in JS anyway 1152 | uasort($items, function($a, $b){ 1153 | if(!config::$config['sort_dirs_first'] || $a['is_dir'] === $b['is_dir']) return strnatcasecmp($a['basename'], $b['basename']); 1154 | return $b['is_dir'] ? 1 : -1; 1155 | }); 1156 | 1157 | // 1158 | return $items; 1159 | } 1160 | 1161 | // get files 1162 | function get_files($dir){ 1163 | 1164 | // invalid $dir 1165 | if(!$dir) json_error('Invalid directory'); 1166 | 1167 | // cache 1168 | $cache = get_dir_cache_path(real_path($dir)); 1169 | 1170 | // read cache or get dir and cache 1171 | if(!read_file($cache, 'application/json', 'files json served from cache')) { 1172 | json_cache(get_dir($dir, true), 'files json created' . ($cache ? ' and cached' : ''), $cache); 1173 | } 1174 | } 1175 | 1176 | /* start here */ 1177 | function post($param){ 1178 | return isset($_POST[$param]) && !empty($_POST[$param]) ? $_POST[$param] : false; 1179 | } 1180 | function get($param){ 1181 | return isset($_GET[$param]) && !empty($_GET[$param]) ? $_GET[$param] : false; 1182 | } 1183 | function json_cache($arr = array(), $msg = false, $cache = true){ 1184 | $json = empty($arr) ? '{}' : json_encode($arr, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_PARTIAL_OUTPUT_ON_ERROR); 1185 | if(empty($json)) json_error(json_last_error() ? json_last_error_msg() : 'json_encode() error'); 1186 | if($cache) @file_put_contents($cache, $json); 1187 | if($msg) header('files-msg: ' . $msg . ' [' . header_memory_time() . ']'); 1188 | header('content-type: application/json'); 1189 | echo $json; 1190 | } 1191 | function json_error($error = 'Error'){ 1192 | json_exit(array('error' => $error)); 1193 | } 1194 | function json_success($success = 'Success'){ 1195 | json_exit(array('success' => $success)); 1196 | } 1197 | function json_toggle($success, $error){ 1198 | json_exit(array_filter(array('success' => $success, 'error' => empty($success) ? $error : 0))); 1199 | } 1200 | function json_exit($arr = array()){ 1201 | header('content-type: application/json'); 1202 | exit(json_encode($arr)); 1203 | } 1204 | function error($msg, $code = false){ 1205 | // 400 Bad Request, 403 Forbidden, 401 Unauthorized, 404 Not Found, 500 Internal Server Error 1206 | if($code) http_response_code($code); 1207 | header('content-type: text/html'); 1208 | header('Expires: ' . gmdate('D, d M Y H:i:s') . ' GMT'); 1209 | header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0'); 1210 | header('Cache-Control: post-check=0, pre-check=0', false); 1211 | header('Pragma: no-cache'); 1212 | exit('

Error

' . $msg); 1213 | } 1214 | 1215 | // get valid menu cache 1216 | function get_valid_menu_cache($cache){ 1217 | if(!$cache || !file_exists($cache)) return; 1218 | $json = @file_get_contents($cache); 1219 | if(empty($json)) return; 1220 | if(!config::$config['menu_cache_validate']) return $json; 1221 | $arr = @json_decode($json, true); 1222 | if(empty($arr)) return; 1223 | foreach ($arr as $key => $val) { 1224 | $path = $val['path']; 1225 | if(strpos($path, '/') !== false && $val['mtime'] !== @filemtime(root_absolute($path))) return; // skip shallow 1st level dirs, and compare filemtime 1226 | } 1227 | return $json; 1228 | } 1229 | 1230 | // get root dirs 1231 | function get_root_dirs(){ 1232 | $root_dirs = glob(config::$root . '/*', GLOB_ONLYDIR|GLOB_NOSORT); 1233 | if(empty($root_dirs)) return array(); 1234 | return array_filter($root_dirs, function($dir){ 1235 | return !is_exclude($dir, true, is_link($dir)); 1236 | }); 1237 | } 1238 | 1239 | // get menu cache hash 1240 | function get_menu_cache_hash($root_dirs){ 1241 | $mtime_count = filemtime(config::$root); 1242 | foreach ($root_dirs as $root_dir) $mtime_count += filemtime($root_dir); 1243 | return substr(md5(config::$doc_root . config::$__dir__ . config::$root), 0, 6) . '.' . substr(md5(config::$version . config::$config['cache_key'] . config::$config['menu_max_depth'] . config::$config['menu_load_all'] . (config::$config['menu_load_all'] ? config::$config['files_exclude'] . config::$image_resize_cache_direct : '') . config::$has_login . config::$config['dirs_exclude'] . config::$config['menu_sort']), 0, 6) . '.' . $mtime_count; 1244 | } 1245 | 1246 | // get dirs 1247 | function dirs(){ 1248 | 1249 | // get menu_cache_hash 1250 | if(config::$config['cache']){ 1251 | $menu_cache_hash = post('menu_cache_hash'); // get menu cache hash 1252 | $menu_cache_arr = $menu_cache_hash ? explode('.', $menu_cache_hash) : false; 1253 | if(!$menu_cache_arr || 1254 | count($menu_cache_arr) !== 3 || 1255 | strlen($menu_cache_arr[0]) !== 6 || 1256 | strlen($menu_cache_arr[1]) !== 6 || 1257 | !is_numeric($menu_cache_arr[2]) 1258 | ) json_error('Invalid menu cache hash'); // early exit 1259 | } 1260 | $cache = config::$config['cache'] ? config::$cache_path . '/menu/' . $menu_cache_hash . '.json' : false; // get cache path 1261 | $json = $cache ? get_valid_menu_cache($cache) : false; // get valid json menu cache 1262 | 1263 | // $json is valid from menu cache file 1264 | if($json){ 1265 | header('content-type: application/json'); 1266 | header('files-msg: valid menu cache hash [' . $menu_cache_hash . ']' . (!config::$config['menu_cache_validate'] ? '[deep validation disabled]' : '') . '[' . header_memory_time() . ']'); 1267 | echo (post('localstorage') ? '{"localstorage":"1"}' : $json); 1268 | 1269 | // reload dirs 1270 | } else { 1271 | json_cache(get_dirs(config::$root), 'dirs reloaded' . ($cache ? ' and cached.' : ' [cache disabled]'), $cache); 1272 | } 1273 | } 1274 | 1275 | // include file html, php, css, js 1276 | function get_include($file){ 1277 | if(!config::$storage_path) return; 1278 | $path = config::$storage_path . '/' . $file; 1279 | if(!file_exists($path)) return; 1280 | $ext = pathinfo($path, PATHINFO_EXTENSION); 1281 | if(in_array($ext, ['html', 'php'])) return include $path; 1282 | if(!config::$storage_is_within_doc_root) return; 1283 | $src = get_url_path($path) . '?' . filemtime($path); 1284 | if($ext === 'js') echo ''; 1285 | if($ext === 'css') echo ''; 1286 | } 1287 | 1288 | // POST 1289 | if(post('action')){ 1290 | 1291 | // post action 1292 | $action = post('action'); 1293 | 1294 | // 1295 | new config(); 1296 | 1297 | // filemanager actions [beta] 1298 | if($action === 'fm') { 1299 | 1300 | // validate task 1301 | $task = post('task'); 1302 | if(empty($task) || !isset(config::$config['allow_' . $task]) || !config::$config['allow_' . $task]) json_error('invalid task'); 1303 | // demo_mode 1304 | if(config::$config['demo_mode']) json_error('Action not allowed in demo mode'); 1305 | // license required for file manager action 1306 | //if(!config::$config['license_key']) json_error('License required!'); 1307 | 1308 | // valid path / path must be inside assigned root 1309 | $is_dir = post('is_dir'); 1310 | $path = valid_root_path(post('path'), $is_dir); 1311 | if(empty($path)) json_error('invalid path ' . post('path')); 1312 | $path = real_path($path); // in case of symlink path 1313 | 1314 | // name_is_allowed / trim name, fail if empty or dodgy characters, mkfile, mkdir, rename, duplicate 1315 | function name_is_allowed($name){ 1316 | $name = $name ? trim($name) : false; // trim 1317 | // block empty / <>:"'/\|?*# chars / .. / endswith . 1318 | if(empty($name) || preg_match('/[<>:"\'\/\\\|?*#]|\.\.|\.$/', $name)) json_error('invalid name ' . $name); 1319 | return $name; // return valid trimmed name 1320 | } 1321 | 1322 | // filemanager json_toggle 1323 | function fm_json_toggle($success, $error){ 1324 | fm_json_exit($success, array_filter(array('success' => $success, 'error' => empty($success) ? $error : 0))); 1325 | } 1326 | // filemanager json_exit / includes feature to invalidate X3 cache if $x3_path 1327 | function fm_json_exit($success, $arr){ 1328 | if($success && config::$x3_path) touch(config::$x3_path . '/app/x3.inc.php'); 1329 | json_exit($arr); 1330 | } 1331 | 1332 | // UPLOAD 1333 | if($task === 'upload'){ 1334 | // upload path must be dir 1335 | if(!$is_dir) json_error('invalid dir ' . post('path')); 1336 | // upload path must be writeable 1337 | if(!is_writable($path)) json_error('upload dir ' . post('path') . ' is not writeable'); 1338 | // get $_FILES['file'] 1339 | $file = isset($_FILES) && isset($_FILES['file']) && is_array($_FILES['file']) ? $_FILES['file'] : false; 1340 | // invalid $_FILES['file'] 1341 | if(empty($file) || !isset($file['error']) || is_array($file['error'])) json_error('invalid $_FILES[]'); 1342 | // PHP meaningful file upload errors / https://www.php.net/manual/en/features.file-upload.errors.php 1343 | if($file['error'] !== 0) { 1344 | $upload_errors = array( 1345 | 1 => 'Uploaded file exceeds upload_max_filesize directive in php.ini', 1346 | 2 => 'Uploaded file exceeds MAX_FILE_SIZE directive specified in the HTML form', 1347 | 3 => 'The uploaded file was only partially uploaded', 1348 | 4 => 'No file was uploaded', 1349 | 6 => 'Missing a temporary folder', 1350 | 7 => 'Failed to write file to disk.', 1351 | 8 => 'A PHP extension stopped the file upload.' 1352 | ); 1353 | json_error(isset($upload_errors[$file['error']]) ? $upload_errors[$file['error']] : 'unknown error'); 1354 | } 1355 | // invalid $file['size'] 1356 | if(!isset($file['size']) || empty($file['size'])) json_error('invalid file size'); 1357 | // $file['size'] must not exceed $config['upload_max_filesize'] 1358 | if(config::$config['upload_max_filesize'] && $file['size'] > config::$config['upload_max_filesize']) json_error('File size [' . $file['size'] . '] exceeds upload_max_filesize option [' . config::$config['upload_max_filesize'] . ']'); 1359 | // filename 1360 | $filename = $file['name']; 1361 | // security: slashes are never ever allowed in filenames / always basenamed() but just in case 1362 | if(strpos($filename, '/') !== false || strpos($filename, '\\') !== false) json_error('Illegal \slash/ in filename ' . $filename); 1363 | // allow only valid file types from config::$config['upload_allowed_file_types'] / 'image/*, .pdf, .mp4' 1364 | $allowed_file_types = !empty(config::$config['upload_allowed_file_types']) ? array_filter(array_map('trim', explode(',', config::$config['upload_allowed_file_types']))) : false; 1365 | if(!empty($allowed_file_types)){ 1366 | $mime = get_mime($file['tmp_name']) ?: $file['type']; // mime from PHP or upload[type] 1367 | $ext = strrchr(strtolower($filename), '.'); 1368 | $is_valid = false; 1369 | // check if extension match || wildcard match mime type image/* 1370 | foreach ($allowed_file_types as $allowed_file_type) if($ext === ('.'.ltrim($allowed_file_type, '.')) || fnmatch($allowed_file_type, $mime)) { 1371 | $is_valid = true; 1372 | break; 1373 | } 1374 | if(!$is_valid) json_error('invalid file type ' . $filename); 1375 | // extra security: check if image is image 1376 | if(function_exists('exif_imagetype') && in_array($ext, ['.gif', '.jpeg', '.jpg', '.png', '.swf', '.psd', '.bmp', '.tif', '.tiff', 'webp']) && !@exif_imagetype($file['tmp_name'])) json_error('invalid image type ' . $filename); 1377 | } 1378 | 1379 | // file naming if !overwrite and file exists 1380 | if(config::$config['upload_exists'] !== 'overwrite' && file_exists("$path/$filename")){ 1381 | 1382 | // fail if !increment / 'upload_exists' => 'fail' || false || '' empty 1383 | if(config::$config['upload_exists'] !== 'increment') json_error("$filename already exists"); 1384 | 1385 | // increment filename / 'upload_exists' => 'increment' 1386 | $name = pathinfo($filename, PATHINFO_FILENAME); 1387 | $ext = pathinfo($filename, PATHINFO_EXTENSION); 1388 | $inc = 1; 1389 | while(file_exists($path . '/' . $name . '-' . $inc . '.' . $ext)) $inc ++; 1390 | $filename = $name . '-' . $inc . '.' . $ext; 1391 | } 1392 | 1393 | // all is well! attempt to move_uploaded_file() 1394 | if(@move_uploaded_file($file['tmp_name'], "$path/$filename")) fm_json_exit(true, array( 1395 | 'success' => true, 1396 | 'filename' => $filename, // return filename in case it was incremented or renamed 1397 | 'url' => get_url_path("$path/$filename") // for usage with showLinkToFileUploadResult 1398 | )); 1399 | 1400 | // error if failed to move uploaded file 1401 | json_error('failed to move_uploaded_file()'); 1402 | 1403 | // DELETE 1404 | } else if($task === 'delete'){ 1405 | 1406 | // dir recursive 1407 | if($is_dir){ 1408 | 1409 | // success/fail count 1410 | $success = 0; 1411 | $fail = 0; 1412 | 1413 | // recursive rmdir 1414 | function rrmdir($dir, &$success, &$fail) { 1415 | //global $success, $fail; 1416 | if(!is_readable($dir)) return $fail ++; 1417 | $files = array_diff(scandir($dir), array('.','..')); 1418 | if(!empty($files)) foreach ($files as $file) { 1419 | is_dir("$dir/$file") ? rrmdir("$dir/$file", $success, $fail) : (@unlink("$dir/$file") ? $success++ : $fail++); 1420 | } 1421 | @rmdir($dir) ? $success ++ : $fail ++; 1422 | } 1423 | 1424 | // recursive rmdir start 1425 | rrmdir($path, $success, $fail); 1426 | 1427 | // response with partial success/fail count or error if there is !$success 1428 | fm_json_exit($success, array_filter(array('success' => $success, 'fail' => $fail, 'error' => (empty($success) ? 'Failed to delete dir' : 0)))); 1429 | 1430 | // single file 1431 | } else { 1432 | fm_json_toggle(@unlink($path), 'PHP unlink() failed'); 1433 | } 1434 | 1435 | // new_folder || new_file 1436 | } else if($task === 'new_folder' || $task === 'new_file'){ 1437 | if(!$is_dir) json_error('invalid dir ' . post('path')); // parent path must be dir 1438 | if(!is_writable($path)) json_error(post('path') . ' is not writeable.'); // dir must be writeable 1439 | $name = name_is_allowed(post('name')); // trim and check valid 1440 | $file_path = $path . '/' . $name; 1441 | if(file_exists($file_path)) json_error($name . ' already exists'); 1442 | fm_json_toggle($task === 'new_folder' ? @mkdir($file_path) : @touch($file_path), $task . ' failed'); 1443 | 1444 | // rename $path (file or dir) 1445 | } else if($task === 'rename'){ 1446 | if(!is_writable($path)) json_error(post('path') . ' is not writeable.'); // path must be writeable 1447 | $name = name_is_allowed(post('name')); // trim and check valid 1448 | $new_path = dirname($path) . '/' . $name; 1449 | if(file_exists($new_path)) json_error("$name already exists."); // new name exists 1450 | // security: prevent renaming 'file.html' to 'file.php' / file must already be *.php when renaming 1451 | if(!$is_dir && stripos($path, '.php') === false && stripos($name, '.php') !== false) json_error('cannot rename files to .php'); 1452 | fm_json_toggle(@rename($path, $new_path), 'PHP rename() failed'); 1453 | 1454 | // duplicate file 1455 | } else if($task === 'duplicate'){ 1456 | if($is_dir) json_error('Can\'t duplicate dir'); 1457 | $parent_dir = dirname($path); 1458 | if(!is_writable($parent_dir)) json_error(basename($parent_dir) . ' is not writeable.'); // dir must be writeable 1459 | $name = name_is_allowed(post('name')); // trim and check valid 1460 | $copy_path = $parent_dir . '/' . $name; 1461 | if(file_exists($copy_path)) json_error($name . ' already exists.'); 1462 | fm_json_toggle(@copy($path, $copy_path), 'PHP copy() failed'); 1463 | 1464 | // text / code edit 1465 | } else if($task === 'text_edit'){ 1466 | if($is_dir) json_error('Can\'t write text to directory'); 1467 | if(!is_writeable($path) || !is_file($path)) json_error('File is not writeable'); 1468 | $success = isset($_POST['text']) && @file_put_contents($path, $_POST['text']) !== false ? 1 : 0; // text could be '' (empty) 1469 | if($success) @touch(dirname($path)); // invalidate any cache by updating parent dir mtime 1470 | fm_json_toggle($success, 'PHP file_put_contents() failed'); 1471 | } 1472 | 1473 | // dirs 1474 | } else if($action === 'dirs'){ 1475 | dirs(post('localstorage')); 1476 | 1477 | // files 1478 | } else if($action === 'files'){ 1479 | if(!isset($_POST['dir'])) json_error('Missing dir parameter'); 1480 | get_files(valid_root_path($_POST['dir'], true)); 1481 | 1482 | // file read 1483 | } else if($action === 'file'){ 1484 | 1485 | // valid path 1486 | $file = valid_root_path(post('file')); 1487 | if(!$file) error('Invalid file path'); 1488 | 1489 | // read text file 1490 | header('content-type:text/plain;charset=utf-8'); 1491 | if(@readfile(real_path($file)) === false) error('failed to read file ' . post('file'), 500); 1492 | 1493 | // check login 1494 | } else if($action === 'check_login'){ 1495 | json_success(true); 1496 | 1497 | // check updates 1498 | } else if($action === 'check_updates'){ 1499 | $json = @json_decode(@file_get_contents('https://data.jsdelivr.com/v1/package/npm/files.photo.gallery'), true); 1500 | $latest = !empty($json) && isset($json['versions'][0]) && version_compare($json['versions'][0], config::$version) > 0 ? $json['versions'][0] : false; 1501 | json_exit(array( 1502 | 'success' => $latest, 1503 | 'writeable' => $latest && is_writable(__FILE__) // only check writeable if $latest 1504 | )); 1505 | 1506 | // do update 1507 | } else if($action === 'do_update'){ 1508 | $version = post('version'); 1509 | if(!$version || version_compare($version, config::$version) <= 0 || !is_writable(__FILE__)) json_error(); // requirements 1510 | $get = @file_get_contents('https://cdn.jsdelivr.net/npm/files.photo.gallery@' . $version . '/index.php'); 1511 | if(empty($get) || strpos($get, ' @file_put_contents(__FILE__, $get))); 1513 | 1514 | // store license 1515 | } else if($action === 'license'){ 1516 | $key = post('key') ? trim(post('key')) : false; 1517 | json_exit(array( 1518 | 'success' => $key && config::$storage_config_realpath && config::save_config(array('license_key' => $key)), 1519 | 'md5' => $key ? md5($key) : false 1520 | )); 1521 | 1522 | // invalid action 1523 | } else { 1524 | json_error('invalid action: ' . $action); 1525 | } 1526 | 1527 | // GET 1528 | } else /*if($_SERVER['REQUEST_METHOD'] === 'GET')*/{ 1529 | 1530 | // download_dir_zip / download files in directory as zip file 1531 | if(get('download_dir_zip')) { 1532 | new config(); 1533 | 1534 | // check download_dir enabled 1535 | if(config::$config['download_dir'] !== 'zip') error('download_dir Zip disabled.', 403); 1536 | 1537 | // valid dir 1538 | $dir = valid_root_path(get('download_dir_zip'), true); 1539 | if(!$dir) error('Invalid download path ' . get('download_dir_zip') . '', 404); 1540 | $dir = real_path($dir); // in case of symlink path 1541 | 1542 | // create zip cache directly in dir (recommended, so that dir can be renamed while zip cache remains) 1543 | if(!config::$storage_path || config::$config['download_dir_cache'] === 'dir') { 1544 | if(!is_writable($dir)) error('Dir ' . basename($dir) . ' is not writeable.', 500); 1545 | $zip_file_name = '_files.zip'; 1546 | $zip_file = $dir . '/' . $zip_file_name; 1547 | 1548 | // create zip file in storage _files/zip/$dirname.$md5.zip / 1549 | } else { 1550 | mkdir_or_error(config::$storage_path . '/zip'); 1551 | $zip_file_name = basename($dir) . '.' . substr(md5($dir), 0, 6) . '.zip'; 1552 | $zip_file = config::$storage_path . '/zip/' . $zip_file_name; 1553 | } 1554 | 1555 | // cached / download_dir_cache && file_exists() && zip is not older than dir time 1556 | $cached = !empty(config::$config['download_dir_cache']) && file_exists($zip_file) && filemtime($zip_file) >= filemtime($dir); 1557 | 1558 | // create zip if !cached 1559 | if(!$cached){ 1560 | 1561 | // use shell zip command instead / probably faster and more robust than PHP / if use, comment out PHP ZipArchive method starting below 1562 | // exec('zip ' . $zip_file . ' ' . $dir . '/*.* -j -x _files*', $out, $res); 1563 | 1564 | // check that ZipArchive class exists 1565 | if(!class_exists('ZipArchive')) error('Missing PHP ZipArchive class.', 500); 1566 | 1567 | // glob files / must be readable / is_file / !symlink / !is_exclude 1568 | $files = array_filter(glob($dir. '/*', GLOB_NOSORT), function($file){ 1569 | return is_readable($file) && is_file($file) && !is_link($file) && !is_exclude($file, false); 1570 | }); 1571 | 1572 | // !no files available to zip 1573 | if(empty($files)) error('No files to zip!', 400); 1574 | 1575 | // new ZipArchive 1576 | $zip = new ZipArchive(); 1577 | 1578 | // create new $zip_file 1579 | if($zip->open($zip_file, ZipArchive::CREATE | ZIPARCHIVE::OVERWRITE) !== true) error('Failed to create ZIP file ' . $zip_file_name . '.', 500); 1580 | 1581 | // add files to zip / flatten with basename() 1582 | foreach($files as $file) $zip->addFile($file, basename($file)); 1583 | 1584 | // no files added (for some reason) 1585 | if(!$zip->numFiles) error('Could not add any files to ' . $zip_file_name . '.', 500); 1586 | 1587 | // close zip 1588 | $zip->close(); 1589 | 1590 | // make sure created zip file exists / just in case 1591 | if(!file_exists($zip_file)) error('Zip file ' . $zip_file_name . ' does not exist.', 500); 1592 | } 1593 | 1594 | // redirect instead of readfile() / might be useful if readfile() fails and/or for caching and performance 1595 | /*$zip_url = get_url_path($zip_file); 1596 | if($zip_url){ 1597 | header('Location:' . $zip_url . '?' . filemtime($dir), true, 302); 1598 | exit; 1599 | }*/ 1600 | 1601 | // output headers 1602 | if(config::$has_login) { 1603 | header('cache-control: must-revalidate, post-check=0, pre-check=0'); 1604 | header('cache-control: public'); 1605 | header('expires: 0'); 1606 | header('pragma: public'); 1607 | } else { 1608 | set_cache_headers(); 1609 | } 1610 | header('content-description: File Transfer'); 1611 | header('content-disposition: attachment; filename="' . addslashes(basename($dir)) . '.zip"'); 1612 | $content_length = filesize($zip_file); 1613 | header('content-length: ' . $content_length); 1614 | header('content-transfer-encoding: binary'); 1615 | header('content-type: application/zip'); 1616 | header('files-msg: [' . $zip_file_name . '][' . ($cached ? 'cached' : 'created') . ']'); 1617 | 1618 | // ignore user abort so we can delete file also on download cancel 1619 | if(empty(config::$config['download_dir_cache'])) @ignore_user_abort(true); 1620 | 1621 | // clear output buffer for large files 1622 | while (ob_get_level()) ob_end_clean(); 1623 | 1624 | // output zip readfile() 1625 | if(!readfile($zip_file)) error('Failed to readfile(' . $zip_file_name . ').', 500); 1626 | 1627 | // delete temp zip file if cache disable 1628 | if(empty(config::$config['download_dir_cache'])) @unlink($zip_file); 1629 | 1630 | 1631 | // folder preview image 1632 | } else if(get('preview')){ 1633 | new config(); 1634 | 1635 | // allow only if only if folder_preview_image + load_images + image_resize_enabled 1636 | foreach (['folder_preview_image', 'load_images', 'image_resize_enabled'] as $key) if(!config::$config[$key]) error('[' .$key . '] disabled.', 400); 1637 | 1638 | // get real path and validate 1639 | $path = valid_root_path(get('preview'), true); // make sure is valid dir 1640 | if(!$path) error('Invalid directory.', 404); 1641 | 1642 | 1643 | // 1. first check for default '_filespreview.jpg' inside dir 1644 | $default = config::$config['folder_preview_default'] ? $path . '/' . config::$config['folder_preview_default'] : false; 1645 | if($default && file_exists($default)) { 1646 | header('files-preview: folder_preview_default found [' . config::$config['folder_preview_default'] . ']'); 1647 | resize_image($default, config::$config['image_resize_dimensions']); 1648 | } 1649 | 1650 | 1651 | // 2. check preview cache 1652 | $cache = config::$cache_path . '/images/preview.' . substr(md5($path), 0, 6) . '.jpg'; 1653 | 1654 | // cache file exists 1655 | if(file_exists($cache)) { 1656 | 1657 | // make sure cache file is valid (must be newer than dir updated time) 1658 | if(filemtime($cache) >= filemtime($path)) read_file($cache, null, 'preview image served from cache', null, true); 1659 | 1660 | // delete expired cache file if is older than dir updated time [silent] 1661 | @unlink($cache); 1662 | } 1663 | 1664 | 1665 | // 3. glob images / GLOB_BRACE may fail on some non GNU systems, like Solaris. 1666 | $images = @glob($path . '/*.{jpg,JPG,jpeg,JPEG,png,PNG,gif,GIF}', GLOB_NOSORT|GLOB_BRACE); 1667 | 1668 | // loop images to locate first match that is not excluded 1669 | if(!empty($images)) foreach ($images as $image) { 1670 | if(!is_exclude($image, false)) { 1671 | header('files-preview: glob() found [' . basename($image) . ']'); 1672 | resize_image($image, config::$config['image_resize_dimensions'], $cache); // + clone into $cache 1673 | break; exit; // just in case 1674 | } 1675 | } 1676 | 1677 | 1678 | // 4. nothing found (no images in dir) 1679 | // create empty 1px in $cache, and output (so next check knows dir is empty or has no images, unless updated) 1680 | if(imagejpeg(imagecreate(1, 1), $cache)) read_file($cache, 'image/jpeg', '1px placeholder image created and cached', null, true); 1681 | 1682 | 1683 | // file/image 1684 | } else if(isset($_GET['file'])){ 1685 | new config(); 1686 | get_file(valid_root_path(get('file')), get('resize')); 1687 | 1688 | // download 1689 | } else if(isset($_GET['download'])){ 1690 | new config(); 1691 | 1692 | // valid download 1693 | $download = valid_root_path(get('download')); 1694 | if(!$download) error('Invalid download path ' . get('download') . '', 404); 1695 | $download = real_path($download); // in case of symlink path 1696 | 1697 | // required for some browsers 1698 | if(@ini_get('zlib.output_compression')) @ini_set('zlib.output_compression', 'Off'); 1699 | 1700 | // headers 1701 | header('Content-Description: File Transfer'); 1702 | header('Content-Type: application/octet-stream'); 1703 | header('Content-Disposition: attachment; filename="' . basename($download) . '"'); 1704 | header('Content-Transfer-Encoding: binary'); 1705 | header('Expires: 0'); 1706 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); 1707 | header('Pragma: public'); 1708 | header('Content-Length: ' . filesize($download)); 1709 | while (ob_get_level()) ob_end_clean(); 1710 | readfile($download); 1711 | 1712 | // tasks plugin 1713 | } else if(get('task')){ 1714 | 1715 | // new config with tests 1716 | new config(true); 1717 | 1718 | // get plugin 1719 | $tasks_path = config::$storage_path . '/plugins/tasks.php'; 1720 | if(!file_exists($tasks_path)) error("Tasks plugin does not exist at $tasks_path", 404); 1721 | include $tasks_path; 1722 | exit; 1723 | 1724 | // main document 1725 | } else { 1726 | 1727 | // new config, with tests 1728 | new config(true); 1729 | 1730 | // validate exclude regex 1731 | if(config::$config['files_exclude'] && @preg_match(config::$config['files_exclude'], '') === false) error('Invalid files_exclude regex ' . config::$config['files_exclude'] . ''); 1732 | if(config::$config['dirs_exclude'] && @preg_match(config::$config['dirs_exclude'], '') === false) error('Invalid dirs_exclude regex ' . config::$config['dirs_exclude'] . ''); 1733 | 1734 | // start path 1735 | $start_path = config::$config['start_path']; 1736 | if($start_path){ 1737 | $real_start_path = real_path($start_path); 1738 | if(!$real_start_path) error('start_path ' . $start_path . ' does not exist.'); 1739 | if(!is_within_root($real_start_path)) error('start_path ' . $start_path . ' is not within root dir ' . config::$config['root']); 1740 | $start_path = root_relative($real_start_path); 1741 | } 1742 | 1743 | // root dirs (if menu) 1744 | $root_dirs = config::$config['menu_enabled'] || config::$config['breadcrumbs'] ? get_root_dirs() : false; 1745 | $menu_enabled = config::$config['menu_enabled'] && !empty($root_dirs) ? true : false; 1746 | $breadcrumbs = config::$config['breadcrumbs'] && !empty($root_dirs) ? true : false; 1747 | 1748 | // get menu cache hash 1749 | $menu_cache_hash = false; 1750 | $menu_cache_file = false; 1751 | if($menu_enabled){ 1752 | $menu_cache_hash = get_menu_cache_hash($root_dirs); 1753 | // menu cache file (if cache, !menu_cache_validate, exists and is within doc root) 1754 | if(config::$storage_is_within_doc_root && config::$config['cache'] && !config::$config['menu_cache_validate']) { 1755 | $menu_cache_path = config::$cache_path . '/menu/' . $menu_cache_hash . '.json'; 1756 | $menu_cache_file = file_exists($menu_cache_path) ? get_url_path($menu_cache_path) : false; 1757 | if($menu_cache_file) $menu_cache_file .= '?' . filemtime($menu_cache_path); 1758 | } 1759 | } 1760 | 1761 | // init path 1762 | $query = config::$config['history'] && isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING']) ? explode('&', $_SERVER['QUERY_STRING']) : false; 1763 | $query_path = $query && strpos($query[0], '=') === false ? rtrim(rawurldecode($query[0]), '/') : false; 1764 | $query_path_valid = $query_path ? valid_root_path($query_path, true) : false; 1765 | $init_path = $query_path ?: $start_path ?: ''; 1766 | 1767 | // init dirs, with files if cache 1768 | function get_dir_init($dir){ 1769 | $cache = get_dir_cache_path(real_path($dir)); 1770 | if(file_exists($cache)) return json_decode(file_get_contents($cache), true); 1771 | return get_dir($dir); 1772 | } 1773 | 1774 | // get dirs for root and start path 1775 | $dirs = array('' => get_dir_init(config::$root)); 1776 | if($query_path){ 1777 | if($query_path_valid) $dirs[$query_path] = get_dir_init($query_path_valid); 1778 | } else if($start_path){ 1779 | $dirs[$start_path] = get_dir_init($real_start_path); 1780 | } 1781 | 1782 | // resize image types 1783 | $resize_image_types = array('jpeg', 'jpg', 'png', 'gif'); 1784 | if(version_compare(PHP_VERSION, '5.4.0') >= 0) { 1785 | $resize_image_types[] = 'webp'; 1786 | if(version_compare(PHP_VERSION, '7.2.0') >= 0) $resize_image_types[] = 'bmp'; 1787 | } 1788 | 1789 | // image resize memory limit 1790 | $image_resize_memory_limit = config::$config['image_resize_enabled'] && config::$config['image_resize_memory_limit'] && function_exists('ini_get') ? (int) @ini_get('memory_limit') : 0; 1791 | if($image_resize_memory_limit && function_exists('ini_set')) $image_resize_memory_limit = max($image_resize_memory_limit, config::$config['image_resize_memory_limit']); 1792 | 1793 | // wtc 1794 | $wtc = config::$config[base64_decode('bGljZW5zZV9rZXk')]; 1795 | 1796 | // look for custom language files _files/lang/*.json 1797 | function lang_custom() { 1798 | $dir = config::$storage_path ? config::$storage_path . '/lang' : false; 1799 | $files = $dir && file_exists($dir) ? glob($dir . '/*.json') : false; 1800 | if(empty($files)) return false; 1801 | $langs = array(); 1802 | foreach ($files as $path) { 1803 | $json = @file_get_contents($path); 1804 | $data = !empty($json) ? @json_decode($json, true) : false; 1805 | if(!empty($data)) $langs[strtok(basename($path), '.')] = $data; 1806 | } 1807 | return !empty($langs) ? $langs : false; 1808 | } 1809 | 1810 | // exclude some user settings from frontend 1811 | $exclude = array_diff_key(config::$config, array_flip(array('root', 'start_path', 'image_resize_cache', 'image_resize_quality', 'image_resize_function', 'image_resize_cache_direct', 'menu_sort', 'menu_load_all', 'cache_key', 'storage_path', 'files_exclude', 'dirs_exclude', 'username', 'password', 'breadcrumbs', 'allow_tasks', 'allow_symlinks', 'menu_recursive_symlinks', 'image_resize_sharpen', 'get_mime_type', 'license_key', 'video_thumbs', 'video_ffmpeg_path', 'folder_preview_default', 'image_resize_dimensions_allowed', 'download_dir_cache'))); 1812 | 1813 | // json config 1814 | $json_config = array_replace($exclude, array( 1815 | 'breadcrumbs' => $breadcrumbs, 1816 | 'script' => basename(__FILE__), 1817 | 'menu_enabled' => $menu_enabled, 1818 | 'menu_cache_hash' => $menu_cache_hash, 1819 | 'menu_cache_file' => $menu_cache_file, 1820 | 'query_path' => $query_path, 1821 | 'query_path_valid' => $query_path_valid ? true : false, 1822 | 'init_path' => $init_path, 1823 | 'dirs' => $dirs, 1824 | 'dirs_hash' => config::$dirs_hash, 1825 | 'resize_image_types' => $resize_image_types, 1826 | 'image_cache_hash' => config::$config['load_images'] ? substr(md5(config::$doc_root . config::$root . config::$config['image_resize_function'] . config::$config['image_resize_quality']), 0, 6) : false, 1827 | 'image_resize_dimensions_retina' => config::$image_resize_dimensions_retina, 1828 | 'location_hash' => md5(config::$root), 1829 | 'has_login' => config::$has_login, 1830 | 'version' => config::$version, 1831 | 'index_html' => intval(get('index_html')), 1832 | 'server_exif' => function_exists('exif_read_data'), 1833 | 'image_resize_memory_limit' => $image_resize_memory_limit, 1834 | 'qrx' => $wtc && is_string($wtc) ? substr(md5($wtc), 0, strlen($wtc)) : false, 1835 | 'video_thumbs_enabled' => config::$config['video_thumbs'] && config::$config['video_ffmpeg_path'] && config::$config['load_images'] && config::$config['image_resize_cache'] && @function_exists('exec') && @exec('type -P ' . config::$config['video_ffmpeg_path']), 1836 | 'lang_custom' => lang_custom(), 1837 | 'x3_path' => config::$x3_path ? get_url_path(config::$x3_path) : false 1838 | )); 1839 | 1840 | function php_directive_value_to_bytes($directive) { 1841 | $val = function_exists('ini_get') ? @ini_get($directive) : false; 1842 | if (empty($val) || !is_string($val)) return 0; 1843 | preg_match('/^(?\d+)(?