Ignore:
Timestamp:
04/01/11 14:24:09 (8 years ago)
Author:
gogo
Message:

Updates to http://www.github.com/sleemanj/mootools-filemanager from GerHoblett?

http://www.github.com/GerHoblett/

Changes to said updates by gogo (sleemanj @ github)

Modifications to MootoolsFileManager? to work with those updates, some courtesy of GerHoblett?, some sleemanj

GerHoblett? provided a large diff which accomplished the goal in a quite different way. It has merit, however I have opted for a less-affecting path in so far as Xinha's "way" is concerned, namely, not splitting the config for a single plugin into several calls to backend config passing functions which seemed a little cumbersome.

Instead I take the option of using POST to send backend data around, at the minor expense of an extra round trip when displaying thumbnails (for each one). This could be reduced by checking for thumbnail existence and returning the thumbnail name directly in "onView" rather than the backend request to generate said thumbnail.

Still to do, is to make the preview pane thumbnail also work.


File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/MootoolsFileManager-Update/plugins/MootoolsFileManager/mootools-filemanager/Assets/Connector/FileManager.php

    r1300 r1302  
    33 * Script: FileManager.php 
    44 *   MooTools FileManager - Backend for the FileManager Script 
    5  *  
     5 * 
    66 * Authors: 
    77 *  - Christoph Pojer (http://cpojer.net) (author) 
    88 *  - James Ehly (http://www.devtrench.com) 
    99 *  - Fabian Vogelsteller (http://frozeman.de) 
    10  *  
     10 *  - Ger Hobbelt (http://hebbut.net) 
     11 *  - James Sleeman (http://code.gogo.co.nz) 
     12 * 
    1113 * License: 
    1214 *   MIT-style license. 
    13  *  
     15 * 
    1416 * Copyright: 
    15  *   Copyright (c) 2009 [Christoph Pojer](http://cpojer.net) 
    16  *  
     17 *   Copyright (c) 2009-2011 [Christoph Pojer](http://cpojer.net) 
     18 *   Backend: FileManager & FMgr4Alias Copyright (c) 2011 [Ger Hobbelt](http://hobbelt.com) 
     19 * 
    1720 * Dependencies: 
    18  *   - Upload.php 
     21 *   - Tooling.php 
    1922 *   - Image.class.php 
    2023 *   - getId3 Library 
    21  *  
     24 * 
    2225 * Options: 
    23  *   - directory: (string) The base directory to be used for the FileManager 
    24  *   - assetBasePath: (string, optional) The path to all images and swf files used by the filemanager 
    25  *   - thumbnailPath: (string) The path where the thumbnails of the pictures will be saved 
    26  *   - mimeTypesPath: (string, optional) The path to the MimeTypes.ini file. 
     26 *   - directory: (string) The URI base directory to be used for the FileManager ('URI path' i.e. an absolute path here would be rooted at DocumentRoot: '/' == DocumentRoot) 
     27 *   - assetBasePath: (string, optional) The URI path to all images and swf files used by the filemanager 
     28 *   - thumbnailPath: (string) The URI path where the thumbnails of the pictures will be saved 
     29 *   - mimeTypesPath: (string, optional) The filesystem path to the MimeTypes.ini file. May exist in a place outside the DocumentRoot tree. 
    2730 *   - dateFormat: (string, defaults to *j M Y - H:i*) The format in which dates should be displayed 
    2831 *   - maxUploadSize: (integer, defaults to *20280000* bytes) The maximum file size for upload in bytes 
    29  *   - maxImageSize: (integer, default is 1024) The maximum number of pixels in both height and width an image can have, if the user enables "resize on upload" 
    30  *   - upload: (boolean, defaults to *true*) allow uploads, this is also set in the FileManager.js (this here is only for security protection when uploads should be deactivated) 
    31  *   - destroy: (boolean, defaults to *true*) allow files to get deleted, this is also set in the FileManager.js (this here is only for security protection when file/directory delete operations should be deactivated) 
    32  *   - create: (boolean, defaults to *true*) allow creating new subdirectories, this is also set in the FileManager.js (this here is only for security protection when dir creates should be deactivated) 
    33  *   - move: (boolean, defaults to *true*) allow file and directory move/rename and copy, this is also set in the FileManager.js (this here is only for security protection when rename/move/copy should be deactivated) 
    34  *   - download: (boolean, defaults to *true*) allow downloads, this is also set in the FileManager.js (this here is only for security protection when downloads should be deactivated) 
     32 *   - maxImageDimension: (array, defaults to *array('width' => 1024, 'height' => 768)*) The maximum number of pixels in height and width an image can have, if the user enables "resize on upload". 
     33 *   - upload: (boolean, defaults to *false*) allow uploads, this is also set in the FileManager.js (this here is only for security protection when uploads should be deactivated) 
     34 *   - destroy: (boolean, defaults to *false*) allow files to get deleted, this is also set in the FileManager.js (this here is only for security protection when file/directory delete operations should be deactivated) 
     35 *   - create: (boolean, defaults to *false*) allow creating new subdirectories, this is also set in the FileManager.js (this here is only for security protection when dir creates should be deactivated) 
     36 *   - move: (boolean, defaults to *false*) allow file and directory move/rename and copy, this is also set in the FileManager.js (this here is only for security protection when rename/move/copy should be deactivated) 
     37 *   - download: (boolean, defaults to *false*) allow downloads, this is also set in the FileManager.js (this here is only for security protection when downloads should be deactivated) 
    3538 *   - allowExtChange: (boolean, defaults to *false*) allow the file extension to be changed when performing a rename operation. 
    3639 *   - safe: (boolean, defaults to *true*) If true, disallows 'exe', 'dll', 'php', 'php3', 'php4', 'php5', 'phps' and saves them as 'txt' instead. 
    3740 *   - chmod: (integer, default is 0777) the permissions set to the uploaded files and created thumbnails (must have a leading "0", e.g. 0777) 
     41 *   - filter: (string, defaults to *null*) If not empty, this is a list of allowed mimetypes (overruled by the GET request 'filter' parameter: single requests can thus overrule the common setup in the constructor for this option) 
     42 *   - ViewIsAuthorized_cb (function/reference, default is *null*) authentication + authorization callback which can be used to determine whether the given directory may be viewed. 
     43 *     The parameter $action = 'view'. 
     44 *   - DetailIsAuthorized_cb (function/reference, default is *null*) authentication + authorization callback which can be used to determine whether the given file may be inspected (and the details listed). 
     45 *     The parameter $action = 'detail'. 
     46 *   - ThumbnailIsAuthorized_cb (function/reference, default is *null*) authentication + authorization callback which can be used to determine whether a thumbnail of the given file may be shown. 
     47 *     The parameter $action = 'thumbnail'. 
    3848 *   - UploadIsAuthorized_cb (function/reference, default is *null*) authentication + authorization callback which can be used to determine whether the given file may be uploaded. 
    3949 *     The parameter $action = 'upload'. 
     
    4757 *     Note that currently support for copying subdirectories is missing. 
    4858 *     The parameter $action = 'move'. 
    49  *  
     59 *   - URIpropagateData (array, default is *null*) the data elements which will be passed along as part of the generated request URIs, i.e. the thumbnail request URIs. Use this to pass custom data elements to the 
     60 *     handler which delivers the thumbnails to the front-end. 
     61 * 
     62 * Obsoleted options: 
     63 *   - maxImageSize: (integer, default is 1024) The maximum number of pixels in both height and width an image can have, if the user enables "resize on upload". (This option is obsoleted by the 'suggestedMaxImageDimension' option.) 
     64 * 
     65 * 
     66 * About the action permissions (upload|destroy|create|move|download): 
     67 * 
     68 *     All the option "permissions" are set to FALSE by default. Developers should always SPECIFICALLY enable a permission to have that permission, for two reasons: 
     69 * 
     70 *     1. Developers forget to disable permissions, they don't forget to enable them (because things don't work!) 
     71 * 
     72 *     2. Having open permissions by default leaves potential for security vulnerabilities where those open permissions are exploited. 
     73 * 
     74 * 
    5075 * For all authorization hooks (callback functions) the following applies: 
    51  *  
     76 * 
    5277 *     The callback should return TRUE for yes (permission granted), FALSE for no (permission denied). 
    5378 *     Parameters sent to the callback are: 
     
    5580 *     where $fileinfo is an array containing info about the file being uploaded, $action is a (string) identifying the current operation, $this is a reference to this FileManager instance. 
    5681 *     $action was included as a redundant parameter to each callback as a simple means to allow users to hook a single callback function to all the authorization hooks, without the need to create a wrapper function for each. 
    57  *  
    58  *     For more info about the hook parameter $fileinfo contents and a basic implementation, see Demos/manager.php and Demos/selectImage.php 
    59  *  
     82 * 
     83 *     For more info about the hook parameter $fileinfo contents and a basic implementation, see further below (section 'Hooks: Detailed Interface Specification') and the examples in 
     84 *     Demos/FM-common.php, Demos/manager.php and Demos/selectImage.php 
     85 * 
     86 * 
    6087 * Notes on relative paths and safety / security: 
    61  *  
     88 * 
    6289 *   If any option is specifying a relative path, e.g. '../Assets' or 'Media/Stuff/', this is assumed to be relative to the request URI path, 
    6390 *   i.e. dirname($_SERVER['SCRIPT_NAME']). 
    64  *  
     91 * 
    6592 *   Requests may post/submit relative paths as arguments to their FileManager events/actions in $_GET/$_POST, and those relative paths will be 
    6693 *   regarded as relative to the request URI handling script path, i.e. dirname($_SERVER['SCRIPT_NAME']) to make the most 
    6794 *   sense from bother server and client coding perspective. 
    68  *  
    69  *  
     95 * 
     96 * 
    7097 *   We also assume that any of the paths may be specified from the outside, so each path is processed and filtered to prevent malicious intent 
    7198 *   from succeeding. (An example of such would be an attacker posting his own 'destroy' event request requesting the destruction of 
    7299 *   '../../../../../../../../../etc/passwd' for example. In more complex rigs, the attack may be assisted through attacks at these options' paths, 
    73100 *   so these are subjected to the same scrutiny in here.) 
    74  *  
     101 * 
    75102 *   All paths, absolute or relative, as passed to the event handlers (see the onXXX methods of this class) are ENFORCED TO ABIDE THE RULE 
    76  *   'every path resides within the BASEDIR rooted tree' without exception. 
    77  *   When paths apparently don't, they are forcibly coerced into adherence to this rule. Because we can do without exceptions to important rules. ;-) 
    78  *  
     103 *   'every path resides within the options['directory'] a.k.a. BASEDIR rooted tree' without exception. 
     104 *   Because we can do without exceptions to important rules. ;-) 
     105 * 
     106 *   When paths apparently don't, they are coerced into adherence to this rule; when this fails, an exception is thrown internally and an error 
     107 *   will be reported and the action temrinated. 
     108 * 
     109 *  'LEGAL URL paths': 
     110 * 
     111 *   Paths which adhere to the aforementioned rule are so-called LEGAL URL paths; their 'root' equals BASEDIR. 
     112 * 
    79113 *   BASEDIR equals the path pointed at by the options['directory'] setting. It is therefore imperative that you ensure this value is 
    80114 *   correctly set up; worst case, this setting will equal DocumentRoot. 
    81115 *   In other words: you'll never be able to reach any file or directory outside this site's DocumentRoot directory tree, ever. 
    82  *  
    83  *  
     116 * 
     117 * 
     118 *  Path transformations: 
     119 * 
     120 *   To allow arbitrary directory/path mapping algorithms to be applied (e.g. when implementing Alias support such as available in the 
     121 *   derived class FileManagerWithAliasSupport), all paths are, on every change/edit, transformed from their LEGAL URL representation to 
     122 *   their 'absolute URI path' (which is suitable to be used in links and references in HTML output) and 'absolute physical filesystem path' 
     123 *   equivalents. 
     124 *   By enforcing such a unidirectional transformation we implicitly support non-reversible and hard-to-reverse path aliasing mechanisms, 
     125 *   e.g. complex regex+context based path manipulations in the server. 
     126 * 
     127 * 
    84128 *   When you need your paths to be restricted to the bounds of the options['directory'] tree (which is a subtree of the DocumentRoot based 
    85  *   tree), you may wish to use the CheckFile(), getPath() and getDir() methods instead of getRealPath() and getRealDir(), as the latter 
    86  *   restrict targets to within the DocumentRoot tree only. 
    87  *  
    88  *   getPath() and getRealPath() both deliver absolute paths relative to DocumentRoot, hence suitable for use in URIs and feeding to client side 
    89  *   scripts, while getRealDir() and getDir() both return absolute paths in the server filesystem perspective, i.e. the latter are suitable for 
    90  *   server side script based file operation functions. 
     129 *   tree), you may wish to use the 'legal' class of path transformation member functions: 
     130 * 
     131 *   - legal2abs_url_path() 
     132 *   - rel2abs_legal_url_path() 
     133 *   - legal_url_path2file_path() 
     134 * 
     135 *   When you have a 'absolute URI path' or a path relative in URI space (implicitly relative to dirname($_SERVER['SCRIPT_NAME']) ), you can 
     136 *   transform such a path to either a guaranteed-absolute URI space path or a filesystem path: 
     137 * 
     138 *   - rel2abs_url_path() 
     139 *   - url_path2file_path() 
     140 * 
     141 *   Any other path transformations are ILLEGAL and DANGEROUS. The only other possibly legal transformation is from absolute URI path to 
     142 *   BASEDIR-based LEGAL URL path, as the URI path space is assumed to be linear and contiguous. However, this operation is HIGHLY discouraged 
     143 *   as it is a very strong indicator of other faulty logic, so we do NOT offer a method for this. 
     144 * 
     145 * 
     146 * Hooks: Detailed Interface Specification: 
     147 * 
     148 *   All 'authorization' callback hooks share a common interface specification (function parameter set). This is by design, so one callback 
     149 *   function can be used to process any and all of these events: 
     150 * 
     151 *   Function prototype: 
     152 * 
     153 *       function CallbackFunction($mgr, $action, &$info) 
     154 * 
     155 *   where 
     156 * 
     157 *       $msg:      (object) reference to the current FileManager class instance. Can be used to invoke public FileManager methods inside 
     158 *                  the callback. 
     159 * 
     160 *       $action:   (string) identifies the event being processed. Can be one of these: 
     161 * 
     162 *                  'create'          create new directory 
     163 *                  'move'            move or copy a file or directory 
     164 *                  'destroy'         delete a file or directory 
     165 *                  'upload'          upload a single file (when performing a bulk upload, each file will be uploaded individually) 
     166 *                  'download'        download a file 
     167 *                  'view'            show a directory listing (in either 'list' or 'thumb' mode) 
     168 *                  'detail'          show detailed information about the file and, whn possible, provide a link to a (largish) thumbnail 
     169 *                  'thumbnail'       send the thumbnail to the client (done this way to allow JiT thumbnail creation) 
     170 * 
     171 *       $info      (array) carries all the details. Some of which can even be manipulated if your callbac is more than just an 
     172 *                  authentication / authorization checker. ;-) 
     173 *                  For more detail, see the next major section. 
     174 * 
     175 *   The callback should return a boolean, where TRUE means the session/client is authorized to execute the action, while FALSE 
     176 *   will cause the backend to report an authentication error and abort the action. 
     177 * 
     178 *  Exceptions throwing from the callback: 
     179 * 
     180 *   Note that you may choose to throw exceptions from inside the callback; those will be caught and transformed to proper error reports. 
     181 * 
     182 *   You may either throw any exceptions based on either the FileManagerException or Exception classes. When you format the exception 
     183 *   message as "XYZ:data", where 'XYZ' is a alphanumeric-only word, this will be transformed to a i18n-support string, where 
     184 *   'backend.XYZ' must map to a translation string (e.g. 'backend.nofile', see also the Language/Language.XX.js files) and the optional 
     185 *   'data' tail will be appended to the translated message. 
     186 * 
     187 * 
     188 * $info: the details: 
     189 * 
     190 *   Here is the list of $info members per $action event code: 
     191 * 
     192 *   'upload': 
     193 * 
     194 *           $info[] contains: 
     195 * 
     196 *               'legal_url'             (string) LEGAL URI path to the directory where the file is being uploaded. You may invoke 
     197 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     198 *                                       to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or 
     199 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     200 *                                       to obtain the absolute URI path for the given directory. 
     201 * 
     202 *               'dir'                   (string) physical filesystem path to the directory where the file is being uploaded. 
     203 * 
     204 *               'raw_filename'          (string) the raw, unprocessed filename of the file being being uploaded, as specified by the client. 
     205 * 
     206 *                                       WARNING: 'raw_filename' may contain anything illegal, such as directory paths instead of just a filename, 
     207 *                                                filesystem-illegal characters and what-not. Use 'name'+'extension' instead if you want to know 
     208 *                                                where the upload will end up. 
     209 * 
     210 *               'name'                  (string) the filename, sans extension, of the file being uploaded; this filename is ensured 
     211 *                                       to be both filesystem-legal, unique and not yet existing in the given directory. 
     212 * 
     213 *               'extension'             (string) the filename extension of the file being uploaded; this extension is ensured 
     214 *                                       to be filesystem-legal. 
     215 * 
     216 *                                       Note that the file name extension has already been cleaned, including 'safe' mode processing, 
     217 *                                       i.e. any uploaded binary executable will have been assigned the extension '.txt' already, when 
     218 *                                       FileManager's options['safe'] is enabled. 
     219 * 
     220 *               'tmp_filepath'          (string) filesystem path pointing at the temporary storage location of the uploaded file: you can 
     221 *                                       access the file data available here to optionally validate the uploaded content. 
     222 * 
     223 *               'mime'                  (string) the mime type as sniffed from the file 
     224 * 
     225 *               'mime_filter'           (optional, string) mime filter as specified by the client: a comma-separated string containing 
     226 *                                       full or partial mime types, where a 'partial' mime types is the part of a mime type before 
     227 *                                       and including the slash, e.g. 'image/' 
     228 * 
     229 *               'mime_filters'          (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. 
     230 * 
     231 *               'size'                  (integer) number of bytes of the uploaded file 
     232 * 
     233 *               'maxsize'               (integer) the configured maximum number of bytes for any single upload 
     234 * 
     235 *               'overwrite'             (boolean) FALSE: the uploaded file will not overwrite any existing file, it will fail instead. 
     236 * 
     237 *                                       Set to TRUE (and adjust the 'name' and 'extension' entries as you desire) when you wish to overwrite 
     238 *                                       an existing file. 
     239 * 
     240 *               'chmod'                 (integer) UNIX access rights (default: 0666) for the file-to-be-created (RW for user,group,world). 
     241 * 
     242 *                                       Note that the eXecutable bits have already been stripped before the callback was invoked. 
     243 * 
     244 * 
     245 *         Note that this request originates from a Macromedia Flash client: hence you'll need to use the 
     246 *         $_POST[session_name()] value to manually set the PHP session_id() before you start your your session 
     247 *         again. 
     248 * 
     249 *         The frontend-specified options.propagateData items will be available as $_GET[] items. 
     250 * 
     251 *         The frontend-specified options.uploadAuthData items will be available as $_POST[] items. 
     252 * 
     253 * 
     254 *  'download': 
     255 * 
     256 *           $info[] contains: 
     257 * 
     258 *               'legal_url'             (string) LEGAL URI path to the file to be downloaded. You may invoke 
     259 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     260 *                                       to obtain the physical filesystem path (also available in the 'file' $info entry, by the way!), or 
     261 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     262 *                                       to obtain the absolute URI path for the given file. 
     263 * 
     264 *               'file'                  (string) physical filesystem path to the file being downloaded. 
     265 * 
     266 *               'mime'                  (string) the mime type as sniffed from the file 
     267 * 
     268 *               'mime_filter'           (optional, string) mime filter as specified by the client: a comma-separated string containing 
     269 *                                       full or partial mime types, where a 'partial' mime types is the part of a mime type before 
     270 *                                       and including the slash, e.g. 'image/' 
     271 * 
     272 *               'mime_filters'          (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. 
     273 * 
     274 *         The frontend-specified options.propagateData items will be available as $_GET[] items. 
     275 * 
     276 * 
     277 *  'create': // create directory 
     278 * 
     279 *           $info[] contains: 
     280 * 
     281 *               'legal_url'             (string) LEGAL URI path to the parent directory of the directory being created. You may invoke 
     282 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     283 *                                       to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or 
     284 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     285 *                                       to obtain the absolute URI path for this parent directory. 
     286 * 
     287 *               'dir'                   (string) physical filesystem path to the parent directory of the directory being created. 
     288 * 
     289 *               'raw_name'              (string) the name of the directory to be created, as specified by the client (unfiltered!) 
     290 * 
     291 *               'uniq_name'             (string) the name of the directory to be created, filtered and ensured to be both unique and 
     292 *                                       not-yet-existing in the filesystem. 
     293 * 
     294 *               'newdir'                (string) the filesystem absolute path to the directory to be created; identical to: 
     295 *                                           $newdir = $mgr->legal_url_path2file_path($legal_url . $uniq_name); 
     296 *                                       Note the above: all paths are transformed from URI space to physical disk every time a change occurs; 
     297 *                                       this allows us to map even not-existing 'directories' to possibly disparate filesystem locations. 
     298 * 
     299 *               'chmod'                 (integer) UNIX access rights (default: 0777) for the directory-to-be-created (RWX for user,group,world) 
     300 * 
     301 *         The frontend-specified options.propagateData items will be available as $_GET[] items. 
     302 * 
     303 * 
     304 *  'destroy': 
     305 * 
     306 *           $info[] contains: 
     307 * 
     308 *               'legal_url'             (string) LEGAL URI path to the file/directory to be deleted. You may invoke 
     309 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     310 *                                       to obtain the physical filesystem path (also available in the 'file' $info entry, by the way!), or 
     311 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     312 *                                       to obtain the absolute URI path for the given file/directory. 
     313 * 
     314 *               'file'                  (string) physical filesystem path to the file/directory being deleted. 
     315 * 
     316 *               'mime'                  (string) the mime type as sniffed from the file / directory (directories are mime type: 'text/directory') 
     317 * 
     318 *               'mime_filter'           (optional, string) mime filter as specified by the client: a comma-separated string containing 
     319 *                                       full or partial mime types, where a 'partial' mime types is the part of a mime type before 
     320 *                                       and including the slash, e.g. 'image/' 
     321 * 
     322 *               'mime_filters'          (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. 
     323 * 
     324 *                                       Note that the 'mime_filters', if any, are applied to the 'delete' operation in a special way: only 
     325 *                                       files matching one of the mime types in this list will be deleted; anything else will remain intact. 
     326 *                                       This can be used to selectively clean a directory tree. 
     327 * 
     328 *                                       The design idea behind this approach is that you are only allowed what you can see ('view'), so 
     329 *                                       all 'view' restrictions should equally to the 'delete' operation. 
     330 * 
     331 *         The frontend-specified options.propagateData items will be available as $_GET[] items. 
     332 * 
     333 * 
     334 *  'move':  // move or copy! 
     335 * 
     336 *           $info[] contains: 
     337 * 
     338 *               'legal_url'             (string) LEGAL URI path to the source parent directory of the file/directory being moved/copied. You may invoke 
     339 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     340 *                                       to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or 
     341 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     342 *                                       to obtain the absolute URI path for the given directory. 
     343 * 
     344 *               'dir'                   (string) physical filesystem path to the source parent directory of the file/directory being moved/copied. 
     345 * 
     346 *               'path'                  (string) physical filesystem path to the file/directory being moved/copied itself; this is the full source path. 
     347 * 
     348 *               'name'                  (string) the name itself of the file/directory being moved/copied; this is the source name. 
     349 * 
     350 *               'legal_newurl'          (string) LEGAL URI path to the target parent directory of the file/directory being moved/copied. You may invoke 
     351 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     352 *                                       to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or 
     353 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     354 *                                       to obtain the absolute URI path for the given directory. 
     355 * 
     356 *               'newdir'                (string) physical filesystem path to the target parent directory of the file/directory being moved/copied; 
     357 *                                       this is the full path of the directory where the file/directory will be moved/copied to. (filesystem absolute) 
     358 * 
     359 *               'newpath'               (string) physical filesystem path to the target file/directory being moved/copied itself; this is the full destination path, 
     360 *                                       i.e. the full path of where the file/directory should be renamed/moved to. (filesystem absolute) 
     361 * 
     362 *               'newname'               (string) the target name itself of the file/directory being moved/copied; this is the destination name. 
     363 * 
     364 *                                       This filename is ensured to be both filesystem-legal, unique and not yet existing in the given target directory. 
     365 * 
     366 *               'rename'                (boolean) TRUE when a file/directory RENAME operation is requested (name change, staying within the same 
     367 *                                       parent directory). FALSE otherwise. 
     368 * 
     369 *               'is_dir'                (boolean) TRUE when the subject is a directory itself, FALSE when it is a regular file. 
     370 * 
     371 *               'function'              (string) PHP call which will perform the operation. ('rename' or 'copy') 
     372 * 
     373 *         The frontend-specified options.propagateData items will be available as $_GET[] items. 
     374 * 
     375 * 
     376 *  'view': 
     377 * 
     378 *           $info[] contains: 
     379 * 
     380 *               'legal_url'             (string) LEGAL URI path to the directory being viewed/scanned. You may invoke 
     381 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     382 *                                       to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or 
     383 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     384 *                                       to obtain the absolute URI path for the scanned directory. 
     385 * 
     386 *               'dir'                   (string) physical filesystem path to the directory being viewed/scanned. 
     387 * 
     388 *               'files'                 (array of strings) array of files and directories (including '..' entry at the top when this is a 
     389 *                                       subdirectory of the FM-managed tree): only names, not full paths. 
     390 * 
     391 *               'mime_filter'           (optional, string) mime filter as specified by the client: a comma-separated string containing 
     392 *                                       full or partial mime types, where a 'partial' mime types is the part of a mime type before 
     393 *                                       and including the slash, e.g. 'image/' 
     394 * 
     395 *               'mime_filters'          (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. 
     396 * 
     397 *               'guess_mime'            (boolean) TRUE when the mime type for each file in this directory will be determined using filename 
     398 *                                       extension sniffing only; FALSE means the mime type will be determined using content sniffing, which 
     399 *                                       is slower. 
     400 * 
     401 *               'list_type'             (string) the type of view requested: 'list' or 'thumb'. 
     402 * 
     403 *               'file_preselect'        (optional, string) filename of a file in this directory which should be located and selected. 
     404 *                                       When found, the backend will provide an index number pointing at the corresponding JSON files[] 
     405 *                                       entry to assist the front-end in jumping to that particular item in the view. 
     406 * 
     407 *               'preliminary_json'      (array) the JSON data collected so far; when ['status']==1, then we're performing a regular view 
     408 *                                       operation (possibly as the second half of a copy/move/delete operation), when the ['status']==0, 
     409 *                                       we are performing a view operation as the second part of another otherwise failed action, e.g. a 
     410 *                                       failed 'create directory'. 
     411 * 
     412 *         The frontend-specified options.propagateData items will be available as $_GET[] items. 
     413 * 
     414 * 
     415 *  'detail': 
     416 * 
     417 *           $info[] contains: 
     418 * 
     419 *               'legal_url'             (string) LEGAL URI path to the file/directory being inspected. You may invoke 
     420 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     421 *                                       to obtain the physical filesystem path (also available in the 'file' $info entry, by the way!), or 
     422 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     423 *                                       to obtain the absolute URI path for the given file. 
     424 * 
     425 *               'file'                  (string) physical filesystem path to the file being inspected. 
     426 * 
     427 *               'filename'              (string) the filename of the file being inspected. (Identical to 'basename($legal_url)') 
     428 * 
     429 *               'mime'                  (string) the mime type as sniffed from the file 
     430 * 
     431 *               'mime_filter'           (optional, string) mime filter as specified by the client: a comma-separated string containing 
     432 *                                       full or partial mime types, where a 'partial' mime types is the part of a mime type before 
     433 *                                       and including the slash, e.g. 'image/' 
     434 * 
     435 *               'mime_filters'          (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. 
     436 * 
     437 *         The frontend-specified options.propagateData items will be available as $_GET[] items. 
     438 * 
     439 * 
     440 *  'thumbnail': 
     441 * 
     442 *           $info[] contains: 
     443 * 
     444 *               'legal_url'             (string) LEGAL URI path to the file/directory being thumbnailed. You may invoke 
     445 *                                           $dir = $mgr->legal_url_path2file_path($legal_url); 
     446 *                                       to obtain the physical filesystem path (also available in the 'file' $info entry, by the way!), or 
     447 *                                           $url = $mgr->legal2abs_url_path($legal_url); 
     448 *                                       to obtain the absolute URI path for the given file. 
     449 * 
     450 *               'file'                  (string) physical filesystem path to the file being inspected. 
     451 * 
     452 *               'filename'              (string) the filename of the file being inspected. (Identical to 'basename($legal_url)') 
     453 * 
     454 *               'mime'                  (string) the mime type as sniffed from the file 
     455 * 
     456 *               'mime_filter'           (optional, string) mime filter as specified by the client: a comma-separated string containing 
     457 *                                       full or partial mime types, where a 'partial' mime types is the part of a mime type before 
     458 *                                       and including the slash, e.g. 'image/' 
     459 * 
     460 *               'mime_filters'          (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. 
     461 *The parameter $action = 'thumbnail'. 
     462 *   - UploadIsAuthorized_cb (function/reference, default is *null*) authentication + authorization callback which can be used to determine whether the given file may be uploaded. 
     463 *               'requested_size'        (integer) the size (maximum width and height) in pixels of the thumbnail to be produced. 
     464 * 
     465 *         The frontend-specified options.propagateData items will be available as $_GET[] items. 
     466 * 
     467 * 
     468 * 
     469 * Developer Notes: 
     470 * 
     471 * - member functions which have a commented out 'static' keyword have it removed by design: it makes for easier overloading through 
     472 *   inheritance that way and meanwhile there's no pressing need to have those (public) member functions acccessible from the outside world 
     473 *   without having an instance of the FileManager class itself round at the same time. 
    91474 */ 
    92475 
     
    94477if (version_compare(PHP_VERSION, '5.2.0') < 0) 
    95478{ 
    96     // die horribly: server does not match our requirements! 
    97     header('HTTP/1.0 500 FileManager requires PHP 5.2.0 or later', true, 500); // Internal server error 
    98     throw Exception('FileManager requires PHP 5.2.0 or later');   // this exception will most probably not be caught; that's our intent! 
     479        // die horribly: server does not match our requirements! 
     480        header('HTTP/1.0 500 FileManager requires PHP 5.2.0 or later', true, 500); // Internal server error 
     481        throw Exception('FileManager requires PHP 5.2.0 or later');   // this exception will most probably not be caught; that's our intent! 
    99482} 
    100483 
    101484if (function_exists('UploadIsAuthenticated')) 
    102485{ 
    103     // die horribly: user has not upgraded his callback hook(s)! 
    104     header('HTTP/1.0 500 FileManager callback has not been upgraded!', true, 500); // Internal server error 
    105     throw Exception('FileManager callback has not been upgraded!');   // this exception will most probably not be caught; that's our intent! 
     486        // die horribly: user has not upgraded his callback hook(s)! 
     487        header('HTTP/1.0 500 FileManager callback has not been upgraded!', true, 500); // Internal server error 
     488        throw Exception('FileManager callback has not been upgraded!');   // this exception will most probably not be caught; that's our intent! 
    106489} 
    107490 
    108491//------------------------------------------------------------------------------------------------------------- 
    109492 
    110  
    111 if (!defined('MTFM_PATH')) 
    112 { 
    113     $base = str_replace('\\','/',dirname(__FILE__)); 
    114     define('MTFM_PATH', $base); 
    115 } 
    116  
    117 require_once(MTFM_PATH . '/Upload.php'); 
    118 require_once(MTFM_PATH . '/Image.class.php'); 
     493if (!defined('DEVELOPMENT')) define('DEVELOPMENT', 0);   // make sure this #define is always known to us 
     494 
     495 
     496 
     497require_once(str_replace('\\', '/', dirname(__FILE__)) . '/Tooling.php'); 
     498require_once(str_replace('\\', '/', dirname(__FILE__)) . '/Image.class.php'); 
     499require_once(str_replace('\\', '/', dirname(__FILE__)) . '/Assets/getid3/getid3.php'); 
     500 
     501 
     502 
     503// the jpeg quality for the largest thumbnails (smaller ones are automatically done at increasingly higher quality) 
     504define('MTFM_THUMBNAIL_JPEG_QUALITY', 75); 
     505 
     506// the number of directory levels in the thumbnail cache; set to 2 when you expect to handle huge image collections. 
     507// 
     508// Note that each directory level distributes the files evenly across 256 directories; hence, you may set this 
     509// level count to 2 when you expect to handle more than 32K images in total -- as each image will have two thumbnails: 
     510// a 48px small one and a 250px large one. 
     511define('MTFM_NUMBER_OF_DIRLEVELS_FOR_CACHE', 1); 
     512 
    119513 
    120514class FileManager 
    121515{ 
    122   protected $path = null; 
    123   protected $basedir = null;                    // absolute path equivalent, filesystem-wise, for options['directory'] 
    124   protected $options; 
    125   protected $post; 
    126   protected $get; 
    127  
    128   public function __construct($options) 
    129   { 
    130     $this->options = array_merge(array( 
    131       /* 
    132        * Note that all default paths as listed below are transformed to DocumentRoot-based paths 
    133        * through the getRealPath() invocations further below: 
    134        */ 
    135       'directory' => MTFM_PATH . '/Files/', 
    136       'assetBasePath' => MTFM_PATH . '/../../Assets/', 
    137       'thumbnailPath' => MTFM_PATH . '/../../Assets/Thumbs/',  // written like this so we're completely clear on where the default thumbnails directory will be 
    138       'mimeTypesPath' => MTFM_PATH . '/MimeTypes.ini', 
    139       'dateFormat' => 'j M Y - H:i', 
    140       'maxUploadSize' => 2600 * 2600 * 3, 
    141       'maxImageSize' => 999999, // Xinha: We have separate X/Y dimension in 'suggestedMaxImageDimension', don't want this 
    142       'upload' => false, 
    143       'destroy' => false, 
    144       'create' => false, 
    145       'move' => false, 
    146       'download' => false, 
    147       /* ^^^ this last one is easily circumnavigated if it's about images: when you can view 'em, you can 'download' them anyway. 
    148        *     However, for other mime types which are not previewable / viewable 'in their full bluntal nugity' ;-) , this will 
    149        *     be a strong deterent. 
    150        * 
    151        *     Think Springer Verlag and PDFs, for instance. You can have 'em, but only /after/ you've ... 
    152        */ 
    153       'allowExtChange' => false, 
    154       'safe' => true, 
    155       'chmod' => 0777, 
    156       'UploadIsAuthorized_cb' => null, 
    157       'DownloadIsAuthorized_cb' => null, 
    158       'CreateIsAuthorized_cb' => null, 
    159       'DestroyIsAuthorized_cb' => null, 
    160       'MoveIsAuthorized_cb' => null, 
    161        
    162      // Xinha: Allow to specify the "Resize Large Images" tolerance level. 
    163      'suggestedMaxImageDimension' => array('width' => 1024, 'height' => 768), 
    164     ), (is_array($options) ? $options : array())); 
    165  
    166     $this->options['thumbnailPath'] = FileManagerUtility::getRealPath($this->options['thumbnailPath'], $this->options['chmod'], true); // create path if nonexistent 
    167     $this->options['assetBasePath'] = FileManagerUtility::getRealPath($this->options['assetBasePath']); 
    168     $this->options['mimeTypesPath'] = FileManagerUtility::getRealDir($this->options['mimeTypesPath'], 0, false, false); // filespec, not a dirspec! 
    169     $this->options['directory'] = FileManagerUtility::getRealPath($this->options['directory']); 
    170     $this->basedir = FileManagerUtility::getSiteRoot() . $this->options['directory']; 
    171  
    172     header('Expires: Fri, 01 Jan 1990 00:00:00 GMT'); 
    173     header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate'); 
    174  
    175     $this->get = $_GET; 
    176     $this->post = $_POST; 
    177   } 
    178  
    179   public function fireEvent($event) 
    180   { 
    181     $event = $event ? 'on' . ucfirst($event) : null; 
    182     if (!$event || !method_exists($this, $event)) $event = 'onView'; 
    183  
    184     $this->{$event}(); 
    185   } 
    186  
    187   /** 
    188    * @return array the FileManager options and settings. 
    189    */ 
    190   public function getSettings() 
    191   { 
    192     return array_merge(array( 
    193         'basedir' => $this->basedir 
    194     ), $this->options); 
    195   } 
    196  
    197   private function _onView($dir, $json, $mime_filter, $list_type) 
    198   { 
    199     $files = ($files = glob($dir . '*')) ? $files : array(); 
    200  
    201     $root = FileManagerUtility::getSiteRoot(); 
    202  
    203     if ($dir != $this->basedir) array_unshift($files, $dir . '..'); 
    204     natcasesort($files); 
    205     foreach ($files as $file) 
    206     { 
    207       $file = self::normalize($file); 
    208       $url = str_replace($root,'',$file); 
    209  
    210       $mime = $this->getMimeType($file); 
    211       if ($mime_filter && $mime != 'text/directory' && !FileManagerUtility::startsWith($mime, $mime_filter)) 
    212         continue; 
    213  
    214       /* 
    215        * each image we inspect may throw an exception due to a out of memory warning 
    216        * (which is far better than without those: a silent fatal abort!) 
    217        * 
    218        * However, now that we do have a way to check most memory failures occurring in here (due to large images 
    219        * and too little available RAM) we /still/ want a directory view; we just want to skip/ignore/mark those 
    220        * overly large ones. 
    221        */ 
    222       $thumb = false; 
    223       try 
     516        protected $options; 
     517 
     518        public function __construct($options) 
     519        { 
     520                $this->options = array_merge(array( 
     521                        /* 
     522                         * Note that all default paths as listed below are transformed to DocumentRoot-based paths 
     523                         * through the getRealPath() invocations further below: 
     524                         */ 
     525                        'directory' => null,                                       // MUST be in the DocumentRoot tree 
     526                        'assetBasePath' => null,                                   // may sit outside options['directory'] but MUST be in the DocumentRoot tree 
     527                        'thumbnailPath' => null,                                   // may sit outside options['directory'] but MUST be in the DocumentRoot tree 
     528                        'mimeTypesPath' => str_replace('\\', '/', dirname(__FILE__)) . '/MimeTypes.ini',   // an absolute filesystem path anywhere; when relative, it will be assumed to be against SERVER['SCRIPT_NAME'] 
     529                        'dateFormat' => 'j M Y - H:i', 
     530                        'maxUploadSize' => 2600 * 2600 * 3, 
     531                        // 'maxImageSize' => 99999,                                 // obsoleted, replaced by 'suggestedMaxImageDimension' 
     532                        // Xinha: Allow to specify the "Resize Large Images" tolerance level. 
     533                        'maxImageDimension' => array('width' => 1024, 'height' => 768), 
     534                        'upload' => false, 
     535                        'destroy' => false, 
     536                        'create' => false, 
     537                        'move' => false, 
     538                        'download' => false, 
     539                        /* ^^^ this last one is easily circumnavigated if it's about images: when you can view 'em, you can 'download' them anyway. 
     540                         *     However, for other mime types which are not previewable / viewable 'in their full bluntal nugity' ;-) , this will 
     541                         *     be a strong deterent. 
     542                         * 
     543                         *     Think Springer Verlag and PDFs, for instance. You can have 'em, but only /after/ you've ... 
     544                         */ 
     545                        'allowExtChange' => false, 
     546                        'safe' => true, 
     547                        'filter' => null, 
     548                        'chmod' => 0777, 
     549                        'ViewIsAuthorized_cb' => null, 
     550                        'DetailIsAuthorized_cb' => null, 
     551                        'ThumbnailIsAuthorized_cb' => null, 
     552                        'UploadIsAuthorized_cb' => null, 
     553                        'DownloadIsAuthorized_cb' => null, 
     554                        'CreateIsAuthorized_cb' => null, 
     555                        'DestroyIsAuthorized_cb' => null, 
     556                        'MoveIsAuthorized_cb' => null, 
     557                        'URIpropagateData' => null 
     558                ), (is_array($options) ? $options : array())); 
     559 
     560                // transform the obsoleted/deprecated options: 
     561                if (!empty($this->options['maxImageSize']) && $this->options['maxImageSize'] != 1024 && $this->options['maxImageDimension']['width'] == 1024 && $this->options['maxImageDimension']['height'] == 768) 
     562                { 
     563                        $this->options['maxImageDimension'] = array('width' => $this->options['maxImageSize'], 'height' => $this->options['maxImageSize']); 
     564                } 
     565 
     566                // only calculate the guestimated defaults when they are indeed required: 
     567                if ($this->options['directory'] == null || $this->options['assetBasePath'] == null || $this->options['thumbnailPath'] == null) 
     568                { 
     569                        $assumed_root = @realpath($_SERVER['DOCUMENT_ROOT']); 
     570                        $assumed_root = str_replace('\\', '/', $assumed_root); 
     571                        if (FileManagerUtility::endsWith($assumed_root, '/')) 
     572                        { 
     573                                $assumed_root = substr($assumed_root, 0, -1); 
     574                        } 
     575                        $my_path = @realpath(dirname(__FILE__)); 
     576                        $my_path = str_replace('\\', '/', $my_path); 
     577                        if (!FileManagerUtility::endsWith($my_path, '/')) 
     578                        { 
     579                                $my_path .= '/'; 
     580                        } 
     581                        $my_assumed_url_path = str_replace($assumed_root, '', $my_path); 
     582 
     583                        // we throw an Exception here because when these do not apply, the user should have specified all three these entries! 
     584                        if (empty($assumed_root) || empty($my_path) || !FileManagerUtility::startsWith($my_path, $assumed_root)) 
     585                                throw new FileManagerException('nofile'); 
     586 
     587                        if ($this->options['directory'] == null) 
     588                        { 
     589                                $this->options['directory'] = $my_assumed_url_path . '../../Demos/Files/'; 
     590                        } 
     591                        if ($this->options['assetBasePath'] == null) 
     592                        { 
     593                                $this->options['assetBasePath'] = $my_assumed_url_path . '../../Demos/Files/../../Assets/'; 
     594                        } 
     595                        if ($this->options['thumbnailPath'] == null) 
     596                        { 
     597                                $this->options['thumbnailPath'] = $my_assumed_url_path . '../../Demos/Files/../../Assets/Thumbs/'; 
     598                        } 
     599                } 
     600 
     601                /* 
     602                 * make sure we start with a very predictable and LEGAL options['directory'] setting, so that the checks applied to the 
     603                 * (possibly) user specified value for this bugger acvtually can check out okay AS LONG AS IT'S INSIDE the DocumentRoot-based 
     604                 * directory tree: 
     605                 */ 
     606                $new_root = $this->options['directory']; 
     607                $this->options['directory'] = '/';      // use DocumentRoot temporarily as THE root for this optional transform 
     608                $this->options['directory'] = self::enforceTrailingSlash($this->rel2abs_url_path($new_root)); 
     609 
     610                // now that the correct options['directory'] has been set up, go and check/clean the other paths in the options[]: 
     611 
     612                $this->options['thumbnailPath'] = self::enforceTrailingSlash($this->rel2abs_url_path($this->options['thumbnailPath'])); 
     613                $this->options['assetBasePath'] = self::enforceTrailingSlash($this->rel2abs_url_path($this->options['assetBasePath'])); 
     614 
     615                $this->options['mimeTypesPath'] = @realpath($this->options['mimeTypesPath']); 
     616                if (empty($this->options['mimeTypesPath'])) 
     617                        throw new FileManagerException('nofile'); 
     618                $this->options['mimeTypesPath'] = str_replace('\\', '/', $this->options['mimeTypesPath']); 
     619 
     620                if (!headers_sent()) 
     621                { 
     622                        header('Expires: Fri, 01 Jan 1990 00:00:00 GMT'); 
     623                        header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate'); 
     624                } 
     625        } 
     626 
     627        /** 
     628         * @return array the FileManager options and settings. 
     629         */ 
     630        public function getSettings() 
     631        { 
     632                return array_merge(array( 
     633                                'basedir' => $this->url_path2file_path($this->options['directory']) 
     634                ), $this->options); 
     635        } 
     636 
     637 
     638 
     639 
     640        /** 
     641         * Central entry point for any client side request. 
     642         */ 
     643        public function fireEvent($event = null) 
     644        { 
     645                $event = !empty($event) ? 'on' . ucfirst($event) : null; 
     646                if (!$event || !method_exists($this, $event)) $event = 'onView'; 
     647 
     648                $this->{$event}(); 
     649        } 
     650 
     651 
     652 
     653 
     654 
     655 
     656        /** 
     657         * Generalized 'view' handler, which produces a directory listing. 
     658         * 
     659         * Return the directory listing in a nested array, suitable for JSON encoding. 
     660         */ 
     661        protected function _onView($legal_url, $json, $mime_filter, $list_type, $file_preselect_arg = null, $filemask = '*') 
     662        { 
     663                $dir = $this->legal_url_path2file_path($legal_url); 
     664                if (!is_dir($dir)) 
     665                { 
     666                        throw new FileManagerException('nofile'); 
     667                } 
     668                $files = $this->scandir($dir, $filemask); 
     669 
     670                if ($files === false) 
     671                        throw new FileManagerException('nofile'); 
     672 
     673                /* 
     674                 * To ensure '..' ends up at the very top of the view, no matter what the other entries in $files[] are made of, 
     675                 * we pop the last element off the array, check whether it's the double-dot, and if so, keep it out while we 
     676                 * let the sort run. 
     677                 */ 
     678                $doubledot = array_pop($files); 
     679                if ($doubledot !== null && $doubledot !== '..') 
     680                { 
     681                        $files[] = $doubledot; 
     682                        $doubledot = null; 
     683                } 
     684                natcasesort($files); 
     685                if ($doubledot !== null) 
     686                { 
     687                        array_unshift($files, $doubledot); 
     688                } 
     689 
     690                $mime_filters = $this->getAllowedMimeTypes($mime_filter); 
     691 
     692                // remove the imageinfo() call overhead per file for very large directories; just guess at the mimetye from the filename alone. 
     693                // The real mimetype will show up in the 'details' view anyway! This is only for the 'filter' function: 
     694                $just_guess_mime = (count($files) > 100); 
     695 
     696                $fileinfo = array( 
     697                                'legal_url' => $legal_url, 
     698                                'dir' => $dir, 
     699                                'files' => $files, 
     700                                'mime_filter' => $mime_filter, 
     701                                'mime_filters' => $mime_filters, 
     702                                'guess_mime' => $just_guess_mime, 
     703                                'list_type' => $list_type, 
     704                                'file_preselect' => $file_preselect_arg, 
     705                                'preliminary_json' => $json 
     706                        ); 
     707 
     708                if (!empty($this->options['ViewIsAuthorized_cb']) && function_exists($this->options['ViewIsAuthorized_cb']) && !$this->options['ViewIsAuthorized_cb']($this, 'view', $fileinfo)) 
     709                        throw new FileManagerException('authorized'); 
     710 
     711                $legal_url = $fileinfo['legal_url']; 
     712                $dir = $fileinfo['dir']; 
     713                $files = $fileinfo['files']; 
     714                $mime_filter = $fileinfo['mime_filter']; 
     715                $mime_filters = $fileinfo['mime_filters']; 
     716                $just_guess_mime = $fileinfo['guess_mime']; 
     717                $list_type = $fileinfo['list_type']; 
     718                $file_preselect_arg = $fileinfo['file_preselect']; 
     719                $json = $fileinfo['preliminary_json']; 
     720 
     721                $file_preselect_index = -1; 
     722                $idx = array(0, 0); 
     723 
     724                foreach ($files as $filename) 
     725                { 
     726                        $url = $legal_url . $filename; 
     727                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     728                        $file = $this->legal_url_path2file_path($url); 
     729 
     730                        $isdir = (is_file($file) ? 0 : 1); 
     731                        if (!$isdir) 
     732                        { 
     733                                $mime = $this->getMimeType($file, $just_guess_mime); 
     734                                if (is_file($file)) 
     735                                { 
     736                                        if (!$this->IsAllowedMimeType($mime, $mime_filters)) 
     737                                                continue; 
     738                                } 
     739                                else 
     740                                { 
     741                                        continue; 
     742                                } 
     743                                $iconspec = $filename; 
     744 
     745                                if ($filename == $file_preselect_arg) 
     746                                { 
     747                                        $file_preselect_index = $idx[0]; 
     748                                } 
     749                        } 
     750                        else if (is_dir($file)) 
     751                        { 
     752                                $mime = 'text/directory'; 
     753                                $iconspec = ($filename == '..' ? 'is.dir_up' : 'is.dir'); 
     754                        } 
     755                        else 
     756                        { 
     757                                // simply do NOT list anything that we cannot cope with. 
     758                                // That includes clearly inaccessible files (and paths) with non-ASCII characters: 
     759                                // PHP5 and below are a real mess when it comes to handling Unicode filesystems 
     760                                // (see the php.net site too: readdir / glob / etc. user comments and the official 
     761                                // notice that PHP will support filesystem UTF-8/Unicode only when PHP6 is released. 
     762                                // 
     763                                // Big, fat bummer! 
     764                                continue; 
     765                        } 
     766 
     767                        if (FileManagerUtility::startsWith($mime, 'image/')) 
     768                        { 
     769                                /* 
     770                                 * offload the thumbnailing process to another event ('event=thumbnail') to be fired by the client 
     771                                 * when it's time to render the thumbnail: the offloading helps us tremendously in coping with large 
     772                                 * directories: 
     773                                 * WE simply assume the thumbnail will be there, so we don't even need to check for its existence 
     774                                 * (which saves us one more file_exists() per item at the very least). And when it doesn't, that's 
     775                                 * for the event=thumbnail handler to worry about (creating the thumbnail on demand or serving 
     776                                 * a generic icon image instead). 
     777                                 */ 
     778                                $thumb48 = $this->mkEventHandlerURL(array( 
     779                                                'event' => 'thumbnail', 
     780                                                // directory and filename of the ORIGINAL image should follow next: 
     781                                                'directory' => $legal_url, 
     782                                                'file' => $filename, 
     783                                                'size' => 48,          // thumbnail suitable for 'view/type=thumb' list views 
     784                                                'filter' => $mime_filter 
     785                                        )); 
     786                                $thumb250 = $this->mkEventHandlerURL(array( 
     787                                                'event' => 'thumbnail', 
     788                                                // directory and filename of the ORIGINAL image should follow next: 
     789                                                'directory' => $legal_url, 
     790                                                'file' => $filename, 
     791                                                'size' => 250,         // thumbnail suitable for 'view/type=thumb' list views 
     792                                                'filter' => $mime_filter 
     793                                        )); 
     794                        } 
     795                        else 
     796                        { 
     797                                $thumb48 = FileManagerUtility::rawurlencode_path($this->getIcon($iconspec, false)); 
     798                                $thumb250 = $thumb48; 
     799                        } 
     800                        $icon = FileManagerUtility::rawurlencode_path($this->getIcon($iconspec, true)); 
     801 
     802                        if ($list_type == 'thumb') 
     803                        { 
     804                                $thumb = $thumb48; 
     805                        } 
     806                        else 
     807                        { 
     808                                $thumb = $icon; 
     809                        } 
     810 
     811                        $out[$isdir][] = array( 
     812                                        'path' => FileManagerUtility::rawurlencode_path($url), 
     813                                        'name' => preg_replace('/[^ -~]/', '?', $filename),       // HACK/TWEAK: PHP5 and below are completely b0rked when it comes to international filenames   :-( 
     814                                        'date' => date($this->options['dateFormat'], @filemtime($file)), 
     815                                        'mime' => $mime, 
     816                                        'thumbnail' => $thumb, 
     817                                        'thumbnail48' => $thumb48, 
     818                                        'thumbnail250' => $thumb250, 
     819                                        'icon' => $icon, 
     820                                        'size' => @filesize($file) 
     821                                ); 
     822                        $idx[$isdir]++; 
     823 
     824                        if (0) 
     825                        { 
     826                                // help PHP when 'doing' large image directories: reset the timeout for each thumbnail / entry we produce: 
     827                                //   http://www.php.net/manual/en/info.configuration.php#ini.max-execution-time 
     828                                set_time_limit(max(30, ini_get('max_execution_time'))); 
     829                        } 
     830                } 
     831 
     832                $thumb48 = FileManagerUtility::rawurlencode_path($this->getIcon('is.dir', false)); 
     833                $icon = FileManagerUtility::rawurlencode_path($this->getIcon('is.dir', true)); 
     834                if ($list_type == 'thumb') 
     835                { 
     836                        $thumb = $thumb48; 
     837                } 
     838                else 
     839                { 
     840                        $thumb = $icon; 
     841                } 
     842                return array( 
     843                        'dirs' => (!empty($out[0]) ? $out[0] : array()), 
     844                        'files' => (!empty($out[1]) ? $out[1] : array()), 
     845                        'json' => array_merge((is_array($json) ? $json : array()), array( 
     846                                'root' => substr($this->options['directory'], 1), 
     847                                'path' => $legal_url,                                  // is relative to options['directory'] 
     848                                'dir' => array( 
     849                                        'path' => FileManagerUtility::rawurlencode_path($legal_url), 
     850                                        'name' => pathinfo($legal_url, PATHINFO_BASENAME), 
     851                                        'date' => date($this->options['dateFormat'], @filemtime($dir)), 
     852                                        'mime' => 'text/directory', 
     853                                        'thumbnail' => $thumb, 
     854                                        'thumbnail48' => $thumb48, 
     855                                        'thumbnail250' => $thumb48, 
     856                                        'icon' => $icon 
     857                                ), 
     858                                'preselect_index' => $file_preselect_index, 
     859                                'preselect_name' => ($file_preselect_index >= 0 ? $file_preselect_arg : null) 
     860                        )) 
     861                ); 
     862        } 
     863 
     864        /** 
     865         * Process the 'view' event (default event fired by fireEvent() method) 
     866         * 
     867         * Returns a JSON encoded directory view list. 
     868         * 
     869         * Expected parameters: 
     870         * 
     871         * $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
     872         * 
     873         * $_POST['file_preselect']     optional filename or path: 
     874         *                         when a filename, this is the filename of a file in this directory  
     875         *                         which should be located and selected. When found, the backend will  
     876         *                         provide an index number pointing at the corresponding JSON files[] 
     877         *                         entry to assist the front-end in jumping to that particular item  
     878         *                         in the view. 
     879         * 
     880         *                         when a path, it is either an absolute or a relative path: 
     881         *                         either is assumed to be a URI URI path, i.e. rooted at 
     882         *                           DocumentRoot. 
     883         *                         The path will be transformed to a LEGAL URI path and 
     884         *                         will OVERRIDE the $_POST['directory'] path. 
     885         *                         Otherwise, this mode acts as when only a filename was specified here. 
     886         *                         This mode is useful to help a frontend to quickly jump to a file 
     887         *                         pointed at by a URI. 
     888         * 
     889         *                         N.B.: This also the only entry which accepts absolute URI paths and  
     890         *                               transforms them to LEGAL URI paths. 
     891         * 
     892         *                         When the specified path is illegal, i.e. does not reside inside the 
     893         *                         options['directory']-rooted LEGAL URI subtree, it will be discarded 
     894         *                         entirely (as all file paths, whether they are absolute or relative, 
     895         *                         must end up inside the options['directory']-rooted subtree to be 
     896         *                         considered manageable files) and the process will continue as if  
     897         *                         the $_POST['file_preselect'] entry had not been set. 
     898         * 
     899         * $_POST['filter']        optional mimetype filter string, amy be the part up to and 
     900         *                         including the slash '/' or the full mimetype. Only files 
     901         *                         matching this (set of) mimetypes will be listed. 
     902         *                         Examples: 'image/' or 'application/zip' 
     903         * 
     904         * $_POST['type']          'thumb' will produce a list view including thumbnail and other 
     905         *                         information with each listed file; other values will produce 
     906         *                         a basic list view (similar to Windows Explorer 'list' view). 
     907         * 
     908         * Errors will produce a JSON encoded error report, including at least two fields: 
     909         * 
     910         * status                  0 for error; nonzero for success 
     911         * 
     912         * error                   error message 
     913         * 
     914         * Next to these, the JSON encoded output will, with high probability, include a 
     915         * list view of the parent or 'basedir' as a fast and easy fallback mechanism for client side 
     916         * viewing code. However, severe and repetitive errors may not produce this 
     917         * 'fallback view list' so proper client code should check the 'status' field in the 
     918         * JSON output. 
     919         */ 
     920        protected function onView() 
     921        { 
     922                // try to produce the view; if it b0rks, retry with the parent, until we've arrived at the basedir: 
     923                // then we fail more severely. 
     924 
     925                $emsg = null; 
     926                $jserr = array( 
     927                                'status' => 1 
     928                        ); 
     929 
     930                $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); 
     931                $list_type = ($this->getPOSTparam('type') != 'thumb' ? 'list' : 'thumb'); 
     932 
     933                try 
     934                { 
     935                        $dir_arg = $this->getPOSTparam('directory'); 
     936                        $legal_url = $this->rel2abs_legal_url_path($dir_arg); 
     937                        $legal_url = self::enforceTrailingSlash($legal_url); 
     938 
     939                        $file_preselect_arg = $this->getPOSTparam('file_preselect'); 
     940                        try 
     941                        { 
     942                                if (!empty($file_preselect_arg)) 
     943                                { 
     944                                        // check if this a path instead of just a basename, then convert to legal_url and split across filename and directory. 
     945                                        if (strpos($file_preselect_arg, '/') !== false) 
     946                                        { 
     947                                                // this will also convert a relative path to an absolute path before transforming it to a LEGAL URI path: 
     948                                                $legal_presel = $this->abs2legal_url_path($file_preselect_arg); 
     949                                                 
     950                                                $prseli = pathinfo($legal_presel); 
     951                                                $file_preselect_arg = $prseli['basename']; 
     952                                                // override the directory! 
     953                                                $legal_url = $prseli['dirname']; 
     954                                                $legal_url = self::enforceTrailingSlash($legal_url); 
     955                                        } 
     956                                        else 
     957                                        { 
     958                                                $file_preselect_arg = pathinfo($file_preselect_arg, PATHINFO_BASENAME); 
     959                                        } 
     960                                } 
     961                        } 
     962                        catch(FileManagerException $e) 
     963                        { 
     964                                // discard the preselect input entirely: 
     965                                $file_preselect_arg = null; 
     966                        } 
     967                } 
     968                catch(FileManagerException $e) 
     969                { 
     970                        $emsg = $e->getMessage(); 
     971                        $legal_url = '/'; 
     972                        $file_preselect_arg = null; 
     973                } 
     974                catch(Exception $e) 
     975                { 
     976                        // catching other severe failures; since this can be anything it may not be a translation keyword in the message... 
     977                        $emsg = $e->getMessage(); 
     978                        $legal_url = '/'; 
     979                        $file_preselect_arg = null; 
     980                } 
     981 
     982                // loop until we drop below the bottomdir; meanwhile getDir() above guarantees that $dir is a subdir of bottomdir, hence dir >= bottomdir. 
     983                do 
     984                { 
     985                        try 
     986                        { 
     987                                $rv = $this->_onView($legal_url, $jserr, $mime_filter, $list_type, $file_preselect_arg); 
     988 
     989                                if (!headers_sent()) header('Content-Type: application/json'); 
     990 
     991                                echo json_encode(array_merge($rv['json'], array('files' => array_merge(array(), $rv['dirs'], $rv['files'])))); 
     992                                return; 
     993                        } 
     994                        catch(FileManagerException $e) 
     995                        { 
     996                                if ($emsg === null) 
     997                                        $emsg = $e->getMessage(); 
     998                        } 
     999                        catch(Exception $e) 
     1000                        { 
     1001                                // catching other severe failures; since this can be anything it may not be a translation keyword in the message... 
     1002                                if ($emsg === null) 
     1003                                        $emsg = $e->getMessage(); 
     1004                        } 
     1005 
     1006                        // step down to the parent dir and retry: 
     1007                        $legal_url = self::getParentDir($legal_url); 
     1008                        $file_preselect_arg = null; 
     1009 
     1010                        $jserr['status']++; 
     1011 
     1012                } while ($legal_url !== false); 
     1013 
     1014                $this->modify_json4exception($jserr, $emsg . ' : path :: ' . $legal_url); 
     1015 
     1016                if (!headers_sent()) header('Content-Type: application/json'); 
     1017 
     1018                // when we fail here, it's pretty darn bad and nothing to it. 
     1019                // just push the error JSON as go. 
     1020                echo json_encode($jserr); 
     1021        } 
     1022 
     1023        /** 
     1024         * Process the 'detail' event 
     1025         * 
     1026         * Returns a JSON encoded HTML chunk describing the specified file (metadata such 
     1027         * as size, format and possibly a thumbnail image as well) 
     1028         * 
     1029         * Expected parameters: 
     1030         * 
     1031         * $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
     1032         * 
     1033         * $_POST['file']          filename (including extension, of course) of the file to 
     1034         *                         be detailed. 
     1035         * 
     1036         * $_POST['filter']        optional mimetype filter string, amy be the part up to and 
     1037         *                         including the slash '/' or the full mimetype. Only files 
     1038         *                         matching this (set of) mimetypes will be listed. 
     1039         *                         Examples: 'image/' or 'application/zip' 
     1040         * 
     1041         * Errors will produce a JSON encoded error report, including at least two fields: 
     1042         * 
     1043         * status                  0 for error; nonzero for success 
     1044         * 
     1045         * error                   error message 
     1046         */ 
     1047        protected function onDetail() 
     1048        { 
     1049                $emsg = null; 
     1050                $jserr = array( 
     1051                                'status' => 1 
     1052                        ); 
     1053 
     1054                try 
     1055                { 
     1056                        $file_arg = $this->getPOSTparam('file'); 
     1057                        if (empty($file_arg)) 
     1058                                throw new FileManagerException('nofile'); 
     1059 
     1060                        $dir_arg = $this->getPOSTparam('directory'); 
     1061                        $legal_url = $this->rel2abs_legal_url_path($dir_arg); 
     1062                        $legal_url = self::enforceTrailingSlash($legal_url); 
     1063 
     1064                        $filename = pathinfo($file_arg, PATHINFO_BASENAME); 
     1065                        $legal_url .= $filename; 
     1066                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     1067                        $file = $this->legal_url_path2file_path($legal_url); 
     1068 
     1069                        if (!is_readable($file)) 
     1070                                throw new FileManagerException('nofile'); 
     1071 
     1072                        $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); 
     1073                        $mime_filters = $this->getAllowedMimeTypes($mime_filter); 
     1074                        $mime = $this->getMimeType($file); 
     1075                        if (is_file($file)) 
     1076                        { 
     1077                                if (!$this->IsAllowedMimeType($mime, $mime_filters)) 
     1078                                        throw new FileManagerException('extension'); 
     1079                        } 
     1080                        else if (!is_dir($file)) 
     1081                        { 
     1082                                throw new FileManagerException('nofile'); 
     1083                        } 
     1084 
     1085                        $fileinfo = array( 
     1086                                        'legal_url' => $legal_url, 
     1087                                        'file' => $file, 
     1088                                        'filename' => $filename, 
     1089                                        'mime' => $mime, 
     1090                                        'mime_filter' => $mime_filter, 
     1091                                        'mime_filters' => $mime_filters 
     1092                                ); 
     1093 
     1094                        if (!empty($this->options['DetailIsAuthorized_cb']) && function_exists($this->options['DetailIsAuthorized_cb']) && !$this->options['DetailIsAuthorized_cb']($this, 'detail', $fileinfo)) 
     1095                                throw new FileManagerException('authorized'); 
     1096 
     1097                        $legal_url = $fileinfo['legal_url']; 
     1098                        $file = $fileinfo['file']; 
     1099                        $filename = $fileinfo['filename']; 
     1100                        $mime = $fileinfo['mime']; 
     1101                        $mime_filter = $fileinfo['mime_filter']; 
     1102                        $mime_filters = $fileinfo['mime_filters']; 
     1103 
     1104                        $jserr = $this->extractDetailInfo($jserr, $legal_url, $file, $mime, $mime_filter); 
     1105 
     1106                        if (!headers_sent()) header('Content-Type: application/json'); 
     1107 
     1108                        echo json_encode($jserr); 
     1109                        return; 
     1110                } 
     1111                catch(FileManagerException $e) 
     1112                { 
     1113                        $emsg = $e->getMessage(); 
     1114                } 
     1115                catch(Exception $e) 
     1116                { 
     1117                        // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
     1118                        $emsg = $e->getMessage(); 
     1119                } 
     1120 
     1121                $this->modify_json4exception($jserr, $emsg); 
     1122 
     1123                if (!headers_sent()) header('Content-Type: application/json'); 
     1124 
     1125                // when we fail here, it's pretty darn bad and nothing to it. 
     1126                // just push the error JSON as go. 
     1127                echo json_encode($jserr); 
     1128        } 
     1129 
     1130        /** 
     1131         * Process the 'thumbnail' event 
     1132         * 
     1133         * Returns either the binary content of the requested thumbnail or the binary content of a replacement image. 
     1134         * 
     1135         * Technical info: this function is assumed to be fired from a <img src="..."> URI or similar and must produce 
     1136         * the content of an image. 
     1137         * It is used in conjection with the 'view/list=thumb' view mode of the FM client: the 'view' list, as 
     1138         * produced by us, contains specially crafted URLs pointing back at us (the 'event=thumbnail' URLs) to 
     1139         * enable FM to cope much better with large image collections by having the entire thumbnail checking 
     1140         * and creation process offloaded to this Just-in-Time subevent. 
     1141         * 
     1142         * By not loading the 'view' event with the thumbnail precreation/checking effort, it can respond 
     1143         * much faster or at least not timeout in the backend for larger image sets in any directory. 
     1144         * ('view' simply assumes the thumbnail will be there, hence reducing its own workload with at least 
     1145         * 1 file_exists() plus worst-case one GD imageinfo + imageresample + extras per image in the 'view' list!) 
     1146         * 
     1147         * Expected parameters: 
     1148         * 
     1149         * $_GET['directory']      path relative to basedir a.k.a. options['directory'] root 
     1150         * 
     1151         * $_GET['file']           filename (including extension, of course) of the file to 
     1152         *                         be thumbnailed. 
     1153         * 
     1154         * $_GET['size']           the requested thumbnail maximum width / height (the bounding box is square). 
     1155         *                         Must be one of our 'authorized' sizes: 48, 250. 
     1156         * 
     1157         * $_GET['filter']         optional mimetype filter string, amy be the part up to and 
     1158         *                         including the slash '/' or the full mimetype. Only files 
     1159         *                         matching this (set of) mimetypes will be listed. 
     1160         *                         Examples: 'image/' or 'application/zip' 
     1161         * 
     1162         * $_GET['asJson']        return some JSON {status: 1, thumbnail: 'path/to/thumbnail.png' } 
     1163         * 
     1164         * Errors will produce a JSON encoded error report, including at least two fields: 
     1165         * 
     1166         * status                  0 for error; nonzero for success 
     1167         * 
     1168         * error                   error message 
     1169         * 
     1170         * Next to these, the JSON encoded output will, with high probability, include a 
     1171         * list view of the parent or 'basedir' as a fast and easy fallback mechanism for client side 
     1172         * viewing code. However, severe and repetitive errors may not produce this 
     1173         * 'fallback view list' so proper client code should check the 'status' field in the 
     1174         * JSON output. 
     1175         */ 
     1176        protected function onThumbnail() 
     1177        { 
     1178                // try to produce the view; if it b0rks, retry with the parent, until we've arrived at the basedir: 
     1179                // then we fail more severely. 
     1180 
     1181                $emsg = null; 
     1182                $img_filepath = null; 
     1183                $reqd_size = 48; 
     1184                $filename = null; 
     1185 
     1186                try 
     1187                { 
     1188                        $reqd_size = intval($this->getGETparam('size')); 
     1189                        if (empty($reqd_size)) 
     1190                                throw new FileManagerException('disabled'); 
     1191                        // and when not requesting one of our 'authorized' thumbnail sizes, you're gonna burn as well! 
     1192                        if (!in_array($reqd_size, array(16, 48, 250))) 
     1193                                throw new FileManagerException('disabled'); 
     1194 
     1195                        $file_arg = $this->getGETparam('file'); 
     1196                        if (empty($file_arg)) 
     1197                                throw new FileManagerException('nofile'); 
     1198 
     1199                        $dir_arg = $this->getGETparam('directory'); 
     1200                        $legal_url = $this->rel2abs_legal_url_path($dir_arg); 
     1201                        $legal_url = self::enforceTrailingSlash($legal_url); 
     1202 
     1203                        $filename = pathinfo($file_arg, PATHINFO_BASENAME); 
     1204                        $legal_url .= $filename; 
     1205                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     1206                        $file = $this->legal_url_path2file_path($legal_url); 
     1207 
     1208                        if (!is_readable($file)) 
     1209                                throw new FileManagerException('nofile'); 
     1210 
     1211                        $mime_filter = $this->getGETparam('filter', $this->options['filter']); 
     1212                        $mime_filters = $this->getAllowedMimeTypes($mime_filter); 
     1213                        $mime = $this->getMimeType($file); 
     1214                        if (is_file($file)) 
     1215                        { 
     1216                                if (!$this->IsAllowedMimeType($mime, $mime_filters)) 
     1217                                        throw new FileManagerException('extension'); 
     1218                        } 
     1219                        else 
     1220                        { 
     1221                                throw new FileManagerException('nofile'); 
     1222                        } 
     1223 
     1224                        $fileinfo = array( 
     1225                                        'legal_url' => $legal_url, 
     1226                                        'file' => $file, 
     1227                                        'filename' => $filename, 
     1228                                        'mime' => $mime, 
     1229                                        'mime_filter' => $mime_filter, 
     1230                                        'mime_filters' => $mime_filters, 
     1231                                        'requested_size' => $reqd_size 
     1232                                ); 
     1233 
     1234                        if (!empty($this->options['ThumbnailIsAuthorized_cb']) && function_exists($this->options['ThumbnailIsAuthorized_cb']) && !$this->options['ThumbnailIsAuthorized_cb']($this, 'thumbnail', $fileinfo)) 
     1235                                throw new FileManagerException('authorized'); 
     1236 
     1237                        $legal_url = $fileinfo['legal_url']; 
     1238                        $file = $fileinfo['file']; 
     1239                        $filename = $fileinfo['filename']; 
     1240                        $mime = $fileinfo['mime']; 
     1241                        $mime_filter = $fileinfo['mime_filter']; 
     1242                        $mime_filters = $fileinfo['mime_filters']; 
     1243                        $reqd_size = $fileinfo['requested_size']; 
     1244 
     1245                        /* 
     1246                         * each image we inspect may throw an exception due to a out of memory warning 
     1247                         * (which is far better than without those: a silent fatal abort!) 
     1248                         * 
     1249                         * However, now that we do have a way to check most memory failures occurring in here (due to large images 
     1250                         * and too little available RAM) we /still/ want to see that happen: for broken and overlarge images, we 
     1251                         * produce some alternative graphics instead! 
     1252                         */ 
     1253                        $thumb_path = null; 
     1254                        if (FileManagerUtility::startsWith($mime, 'image/')) 
     1255                        { 
     1256                                // access the image and create a thumbnail image; this can fail dramatically 
     1257                                $thumb_path = $this->getThumb($legal_url, $file, $reqd_size, $reqd_size); 
     1258                        } 
     1259 
     1260                        $img_filepath = (!empty($thumb_path) ? $thumb_path : $this->getIcon($filename, $reqd_size <= 16)); 
     1261                } 
     1262                catch(FileManagerException $e) 
     1263                { 
     1264                        $emsg = $e->getMessage(); 
     1265                } 
     1266                catch(Exception $e) 
     1267                { 
     1268                        // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
     1269                        $emsg = $e->getMessage(); 
     1270                } 
     1271 
     1272                // now go and serve the content of the thumbnail / icon image file (which we still need to determine /exactly/): 
     1273                try 
     1274                { 
     1275                        if (empty($img_filepath)) 
     1276                        { 
     1277                                $img_filepath = $this->getIconForError($emsg, $filename, $reqd_size <= 16); 
     1278                        } 
     1279 
     1280      if($this->getGETParam('asJson', 0)) 
    2241281      { 
    225         // access the image and create a thumbnail image; this can fail dramatically 
    226         if(strpos($mime,'image') !== false) 
    227           $thumb = $this->getThumb($file); 
     1282        $Response = array('status' => 1, 'thumbnail' => $img_filepath); 
     1283        if(@$emsg) 
     1284        { 
     1285          $Response['status'] = 0; 
     1286          $Response['error']  = $emsg; 
     1287        }         
     1288         
     1289        if (!headers_sent()) header('Content-Type: application/json'); 
     1290        echo json_encode($Response); 
     1291        return; 
    2281292      } 
    229       catch (Exception $e) 
    230       { 
    231          // do nothing, except mark image as 'not suitable for thumbnailing' 
    232       } 
    233  
    234       $icon = ($list_type == 'thumb' && $thumb) 
    235         ? $this->options['thumbnailPath'] . $thumb 
    236         : $this->getIcon($file, $list_type != 'thumb'); // TODO: add extra icons for those bad format and superlarge images with make us b0rk? 
    237  
    238       // list files, except the thumbnail folder itself or any file in it: 
    239       if(!FileManagerUtility::startswith($url, substr($this->options['thumbnailPath'],0,-1))) 
    240       { 
    241         $out[is_dir($file) ? 0 : 1][] = array( 
    242           'path' => FileManagerUtility::rawurlencode_path($url), 
    243           'name' => pathinfo($file, PATHINFO_BASENAME), 
    244           'date' => date($this->options['dateFormat'], @filemtime($file)), 
    245           'mime' => $mime, 
    246           'thumbnail' => FileManagerUtility::rawurlencode_path($icon), 
    247           'icon' => FileManagerUtility::rawurlencode_path($this->getIcon($file,true)), 
    248           'size' => @filesize($file) 
    249         ); 
    250       } 
    251     } 
    252     return array_merge((is_array($json) ? $json : array()), array( 
    253         //'assetBasePath' => $this->options['assetBasePath'], 
    254         //'thumbnailPath' => $this->options['thumbnailPath'], 
    255         //'ia_directory' => $this->options['directory'], 
    256         //'ia_dir' => $dir, 
    257         //'ia_root' => $root, 
    258         //'ia_basedir' => $this->basedir, 
    259         'root' => substr($this->options['directory'], 1), 
    260         'path' => str_replace($this->basedir,'',$dir),               // is relative to 'root' 
    261         'dir' => array( 
    262             'name' => pathinfo($dir, PATHINFO_BASENAME), 
    263             'date' => date($this->options['dateFormat'], @filemtime($dir)), 
    264             'mime' => 'text/directory', 
    265             'thumbnail' => $this->getIcon($dir), 
    266             'icon' => $this->getIcon($dir,true) 
    267           ), 
    268       'files' => array_merge(!empty($out[0]) ? $out[0] : array(), !empty($out[1]) ? $out[1] : array()) 
    269     )); 
    270   } 
    271  
    272   /** 
    273    * Process the 'view' event (default event fired by fireEvent() method) 
    274    * 
    275    * Returns a JSON encoded directory view list. 
    276    * 
    277    * Expected parameters: 
    278    * 
    279    * $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
    280    * 
    281    * $_POST['filter']        optional mimetype filter string, amy be the part up to and 
    282    *                         including the slash '/' or the full mimetype. Only files 
    283    *                         matching this (set of) mimetypes will be listed. 
    284    *                         Examples: 'image/' or 'application/zip' 
    285    * 
    286    * $_POST['type']          'thumb' will produce a list view including thumbnail and other 
    287    *                         information with each listed file; other values will produce 
    288    *                         a basic list view (similar to Windows Explorer 'list' view). 
    289    * 
    290    * Errors will produce a JSON encoded error report, including at least two fields: 
    291    * 
    292    * status                  0 for error; nonzero for success 
    293    * 
    294    * error                   error message 
    295    * 
    296    * Next to these, the JSON encoded output will, with high probability, include a 
    297    * list view of the parent or 'basedir' as a fast and easy fallback mechanism for client side 
    298    * viewing code. However, severe and repetitive errors may not produce this 
    299    * 'fallback view list' so proper client code should check the 'status' field in the 
    300    * JSON output. 
    301    */ 
    302   protected function onView() 
    303   { 
    304     // try to produce the view; if it b0rks, retry with the parent, until we've arrived at the basedir: 
    305     // then we fail more severely. 
    306  
    307     $mime_filter = null; 
    308     $list_type = null; 
    309     $emsg = null; 
    310     $jserr = array( 
    311             'status' => 1 
    312         ); 
    313     $bottomdir = $this->basedir; 
    314  
    315     try 
    316     { 
    317         $mime_filter = ((isset($_POST['filter']) && !empty($_POST['filter'])) ? $_POST['filter'].'/' : null); 
    318         $list_type = ((isset($_POST['type']) && $_POST['type'] == 'list') ? 'list' : 'thumb'); 
    319  
    320         $dir = $this->getDir(!empty($this->post['directory']) ? $this->post['directory'] : null); 
    321     } 
    322     catch(FileManagerException $e) 
    323     { 
    324         $emsg = $e->getMessage(); 
    325         $dir = $this->basedir; 
    326     } 
    327     catch(Exception $e) 
    328     { 
    329         // catching other severe failures; since this can be anything it may not be a translation keyword in the message... 
    330         $emsg = $e->getMessage(); 
    331         $dir = $this->basedir; 
    332     } 
    333  
    334     // loop until we drop below the bottomdir; meanwhile getDir() above guarantees that $dir is a subdir of bottomdir, hence dir >= bottomdir. 
    335     do 
    336     { 
    337         try 
    338         { 
    339             $rv = $this->_onView($dir, $jserr, $mime_filter, $list_type); 
    340             echo json_encode($rv); 
    341             return; 
    342         } 
    343         catch(FileManagerException $e) 
    344         { 
    345             $emsg = $e->getMessage(); 
    346         } 
    347         catch(Exception $e) 
    348         { 
    349             // catching other severe failures; since this can be anything it may not be a translation keyword in the message... 
    350             $emsg = $e->getMessage(); 
    351         } 
    352  
    353         // only set up the new json error report array when this is the first exception we got: 
    354         if ($jserr['status']) 
    355         { 
    356             // check the error message and see if it is a translation code word (with or without parameters) or just a generic error report string 
    357             $e = explode(':', $emsg, 2); 
    358             if (preg_match('/[^A-Za-z0-9_-]/', $e[0])) 
    359             { 
    360                 // generic message. ouch. 
    361                 $jserr = array( 
    362                         'status' => 0, 
    363                         'error' => $emsg 
    364                     ); 
    365             } 
    366             else 
    367             { 
    368                 $jserr = array( 
    369                         'status' => 0, 
    370                         'error' => '${backend.' . $e[0] . '}' . (isset($e[1]) ? $e[1] : '') 
    371                     ); 
    372             } 
    373         } 
    374  
    375         // step down to the parent dir and retry: 
    376         $dir = dirname($dir); 
    377         if (!FileManagerUtility::endsWith($dir, '/')) $dir .= '/'; 
    378  
    379     } while (strcmp($dir, $bottomdir) >= 0); 
    380  
    381     // when we fail here, it's pretty darn bad and nothing to it. 
    382     // just push the error JSON as go. 
    383     echo json_encode($jserr); 
    384   } 
    385  
    386   /** 
    387    * Process the 'detail' event 
    388    * 
    389    * Returns a JSON encoded HTML chunk describing the specified file (metadata such 
    390    * as size, format and possibly a thumbnail image as well) 
    391    * 
    392    * Expected parameters: 
    393    * 
    394    * $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
    395    * 
    396    * $_POST['file']          filename (including extension, of course) of the file to 
    397    *                         be detailed. 
    398    * 
    399    * Errors will produce a JSON encoded error report, including at least two fields: 
    400    * 
    401    * status                  0 for error; nonzero for success 
    402    * 
    403    * error                   error message 
    404    */ 
    405   protected function onDetail() 
    406   { 
    407   try 
    408   { 
    409     if (empty($this->post['file'])) 
    410         throw new FileManagerException('nofile'); 
    411  
    412     $url = $this->getPath(!empty($this->post['directory']) ? $this->post['directory'] : null); 
    413     $dir = FileManagerUtility::getSiteRoot() . $url; 
    414     $file = pathinfo($this->post['file'], PATHINFO_BASENAME); 
    415  
    416     $dir .= $file; 
    417     $url .= $file; 
    418  
    419     if (!$this->checkFile($dir)) 
    420         throw new FileManagerException('nofile'); 
    421  
    422     // spare the '/' dir separators from URL encoding: 
    423     $encoded_url = FileManagerUtility::rawurlencode_path($url); 
    424  
    425     $mime = $this->getMimeType($dir); 
    426     $content = null; 
    427     
    428     // Xinha: We want to get some more information about what has been selected in a way 
    429     // we can use it.  Effectively what gets put in here will be passed into the 
    430     // 'onDetails' event handler of your FileManager object (if any). 
    431     $extra_return_detail = array 
    432       ( 
    433         'url'  => $url, 
    434         'mime' => $mime 
    435       ); 
    436     
    437     // image 
    438     if (FileManagerUtility::startsWith($mime, 'image/')) 
    439     { 
    440       // generates a random number to put on the end of the image, to prevent caching 
    441       $randomImage = '?'.md5(uniqid(rand(),1)); 
    442       $size = @getimagesize($dir); 
    443       // check for badly formatted image files (corruption); we'll handle the overly large ones next 
    444       if (!$size) 
    445         throw new FileManagerException('corrupt_img:' . $url); 
    446     
    447         // Xinha: Return some information about the image which can be access  
    448         // from the onDetails event handler in FileManager 
    449        $extra_return_detail['width']  = $size[0]; 
    450        $extra_return_detail['height'] = $size[1];        
    451          
    452       $thumbfile = $this->options['thumbnailPath'] . $this->getThumb($dir); 
    453       $content = '<dl> 
    454           <dt>${width}</dt><dd>' . $size[0] . 'px</dd> 
    455           <dt>${height}</dt><dd>' . $size[1] . 'px</dd> 
    456         </dl> 
    457         <h2>${preview}</h2> 
    458         '; 
    459       try 
    460       { 
    461           $tnc = '<a href="'.$encoded_url.'" data-milkbox="preview" title="'.htmlentities($file, ENT_QUOTES, 'UTF-8').'"><img src="' . FileManagerUtility::rawurlencode_path($thumbfile) . $randomImage . '" class="preview" alt="preview" /></a>'; 
    462       } 
    463       catch (Exception $e) 
    464       { 
    465           $tnc = '<a href="'.$encoded_url.'" data-milkbox="preview" title="'.htmlentities($file, ENT_QUOTES, 'UTF-8').'"><img src="' . FileManagerUtility::rawurlencode_path($this->getIcon($dir)).$randomImage . '" class="preview" alt="preview" /></a>'; 
    466       } 
    467       $content .= $tnc; 
    468     // text preview 
    469     } 
    470     elseif (FileManagerUtility::startsWith($mime, 'text/') || $mime == 'application/x-javascript') 
    471     { 
    472       $filecontent = file_get_contents($dir, false, null, 0); 
    473       if (!FileManagerUtility::isBinary($filecontent)) 
    474       { 
    475         $content = '<div class="textpreview"><pre>' . str_replace(array('$', "\t"), array('&#36;', '&nbsp;&nbsp;'), htmlentities($filecontent,ENT_QUOTES,'UTF-8')) . '</pre></div>'; 
    476       } 
    477       // else: fall back to 'no preview available' 
    478     // zip 
    479     } 
    480     elseif ($mime == 'application/zip') 
    481     { 
    482       require_once(MTFM_PATH . '/Assets/getid3/getid3.php'); 
    483  
    484       $out = array(array(), array()); 
    485       $getid3 = new getID3(); 
    486       $getid3->Analyze($dir); 
    487       foreach ($getid3->info['zip']['files'] as $name => $size) 
    488       { 
    489         $isdir = is_array($size) ? true : false; 
    490         $out[($isdir) ? 0 : 1][$name] = '<li><a><img src="'.FileManagerUtility::rawurlencode_path($this->getIcon($dir,true)).'" alt="" /> ' . $name . '</a></li>'; 
    491       } 
    492       natcasesort($out[0]); 
    493       natcasesort($out[1]); 
    494       $content = '<ul>' . implode(array_merge($out[0], $out[1])) . '</ul>'; 
    495     // swf 
    496     } 
    497     elseif ($mime == 'application/x-shockwave-flash') 
    498     { 
    499       require_once(MTFM_PATH . '/Assets/getid3/getid3.php'); 
    500       $getid3 = new getID3(); 
    501       $getid3->Analyze($dir); 
    502  
    503       $content = '<dl> 
    504           <dt>${width}</dt><dd>' . $getid3->info['swf']['header']['frame_width']/10 . 'px</dd> 
    505           <dt>${height}</dt><dd>' . $getid3->info['swf']['header']['frame_height']/10 . 'px</dd> 
    506           <dt>${length}</dt><dd>' . round(($getid3->info['swf']['header']['length']/$getid3->info['swf']['header']['frame_count'])) . 's</dd> 
    507         </dl> 
    508         <h2>${preview}</h2> 
    509         <div class="object"> 
    510           <object type="application/x-shockwave-flash" data="'.FileManagerUtility::rawurlencode_path($url).'" width="500" height="400"> 
    511             <param name="scale" value="noscale" /> 
    512             <param name="movie" value="'.FileManagerUtility::rawurlencode_path($url).'" /> 
    513           </object> 
    514         </div>'; 
    515     // audio 
    516     } 
    517     elseif (FileManagerUtility::startsWith($mime, 'audio/')) 
    518     { 
    519       require_once(MTFM_PATH . '/Assets/getid3/getid3.php'); 
    520       $getid3 = new getID3(); 
    521       $getid3->Analyze($dir); 
    522       getid3_lib::CopyTagsToComments($getid3->info); 
    523  
    524       $dewplayer = FileManagerUtility::rawurlencode_path($this->options['assetBasePath'] . 'dewplayer.swf'); 
    525       $content = '<dl> 
    526           <dt>${title}</dt><dd>' . $getid3->info['comments']['title'][0] . '</dd> 
    527           <dt>${artist}</dt><dd>' . $getid3->info['comments']['artist'][0] . '</dd> 
    528           <dt>${album}</dt><dd>' . $getid3->info['comments']['album'][0] . '</dd> 
    529           <dt>${length}</dt><dd>' . $getid3->info['playtime_string'] . '</dd> 
    530           <dt>${bitrate}</dt><dd>' . round($getid3->info['bitrate']/1000) . 'kbps</dd> 
    531         </dl> 
    532         <h2>${preview}</h2> 
    533         <div class="object"> 
    534           <object type="application/x-shockwave-flash" data="' . $dewplayer . '" width="200" height="20" id="dewplayer" name="dewplayer"> 
    535             <param name="wmode" value="transparent" /> 
    536             <param name="movie" value="' . $dewplayer . '" /> 
    537             <param name="flashvars" value="mp3=' . FileManagerUtility::rawurlencode_path($url) . '&amp;volume=50&amp;showtime=1" /> 
    538           </object> 
    539         </div>'; 
    540     } 
    541     // else: fall back to 'no preview available' 
    542  
    543     echo json_encode(array_merge(array( 
    544       'status' => 1, 
    545       'content' => $content ? $content : '<div class="margin"> 
    546         ${nopreview} 
    547       </div>'                 //<br/><button value="' . $url . '">${download}</button> 
    548     ), $extra_return_detail)); 
    549     } 
    550     catch(FileManagerException $e) 
    551     { 
    552         $emsg = explode(':', $e->getMessage(), 2); 
    553         echo json_encode(array( 
    554                 'status' => 0, 
    555                 'content' => '<div class="margin"> 
    556                   ${nopreview} 
    557                   <div class="failure_notice"> 
    558                     <h3>${error}</h3> 
    559                     <p>mem usage: ' . number_format(memory_get_usage() / 1E6, 2) . ' MB : ' . number_format(memory_get_peak_usage() / 1E6, 2) . ' MB</p> 
    560                     <p>${backend.' . $emsg[0] . '}' . (isset($emsg[1]) ? $emsg[1] : '') . '</p> 
    561                   </div> 
    562                 </div>'       // <br/><button value="' . $url . '">${download}</button> 
    563             )); 
    564     } 
    565     catch(Exception $e) 
    566     { 
    567         // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
    568         echo json_encode(array( 
    569                 'status' => 0, 
    570                 'content' => '<div class="margin"> 
    571                   ${nopreview} 
    572                   <div class="failure_notice"> 
    573                     <h3>${error}</h3> 
    574                     <p>mem usage: ' . number_format(memory_get_usage() / 1E6, 2) . ' MB : ' . number_format(memory_get_peak_usage() / 1E6, 2) . ' MB</p> 
    575                     <p>' . $e->getMessage() . '</p> 
    576                   </div> 
    577                 </div>'       // <br/><button value="' . $url . '">${download}</button> 
    578             )); 
    579     } 
    580   } 
    581  
    582   /** 
    583    * Process the 'destroy' event 
    584    * 
    585    * Delete the specified file or directory and return a JSON encoded status of success 
    586    * or failure. 
    587    * 
    588    * Note that when images are deleted, so are their thumbnails. 
    589    * 
    590    * Expected parameters: 
    591    * 
    592    * $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
    593    * 
    594    * $_POST['file']          filename (including extension, of course) of the file to 
    595    *                         be detailed. 
    596    * 
    597    * Errors will produce a JSON encoded error report, including at least two fields: 
    598    * 
    599    * status                  0 for error; nonzero for success 
    600    * 
    601    * error                   error message 
    602    */ 
    603   protected function onDestroy() 
    604   { 
    605     try 
    606     { 
    607         if (!$this->options['destroy']) 
    608             throw new FileManagerException('disabled'); 
    609         if (empty($this->post['file'])) 
    610             throw new FileManagerException('nofile'); 
    611  
    612         $dir = $this->getDir(!empty($this->post['directory']) ? $this->post['directory'] : null); 
    613         $file = pathinfo($this->post['file'], PATHINFO_BASENAME); 
    614  
    615         $fileinfo = array( 
    616             'dir' => $dir, 
    617             'file' => $file 
    618         ); 
    619  
    620         if (!$this->checkFile($dir . $file)) 
    621             throw new FileManagerException('nofile'); 
    622  
    623         if (!empty($this->options['DestroyIsAuthorized_cb']) && function_exists($this->options['DestroyIsAuthorized_cb']) && !$this->options['DestroyIsAuthorized_cb']($this, 'destroy', $fileinfo)) 
    624             throw new FileManagerException('authorized'); 
    625  
    626         if (!$this->unlink($dir . $file)) 
    627             throw new FileManagerException('unlink_failed:' . $dir . $file); 
    628  
    629         echo json_encode(array( 
    630           'status' => 1, 
    631           'content' => 'destroyed' 
    632         )); 
    633     } 
    634     catch(FileManagerException $e) 
    635     { 
    636         $emsg = explode(':', $e->getMessage(), 2); 
    637         echo json_encode(array( 
    638                 'status' => 0, 
    639                 'error' => '${backend.' . $emsg[0] . '}' . (isset($emsg[1]) ? $emsg[1] : '') 
    640             )); 
    641     } 
    642     catch(Exception $e) 
    643     { 
    644         // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
    645         echo json_encode(array( 
    646                 'status' => 0, 
    647                 'error' => $e->getMessage() 
    648             )); 
    649     } 
    650   } 
    651  
    652   /** 
    653    * Process the 'create' event 
    654    * 
    655    * Create the specified subdirectory and give it the configured permissions 
    656    * (options['chmod'], default 0777) and return a JSON encoded status of success 
    657    * or failure. 
    658    * 
    659    * Expected parameters: 
    660    * 
    661    * $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
    662    * 
    663    * $_POST['file']          name of the subdirectory to be created 
    664    * 
    665    * Extra input parameters considered while producing the JSON encoded directory view. 
    666    * This may not seem relevant for an empty directory, but these parameters are also 
    667    * considered when providing the fallback directory view in case an error occurred 
    668    * and then the listed directory (either the parent or the basedir itself) may very 
    669    * likely not be empty! 
    670    * 
    671    * $_POST['filter']        optional mimetype filter string, amy be the part up to and 
    672    *                         including the slash '/' or the full mimetype. Only files 
    673    *                         matching this (set of) mimetypes will be listed. 
    674    *                         Examples: 'image/' or 'application/zip' 
    675    * 
    676    * $_POST['type']          'thumb' will produce a list view including thumbnail and other 
    677    *                         information with each listed file; other values will produce 
    678    *                         a basic list view (similar to Windows Explorer 'list' view). 
    679    * 
    680    * Errors will produce a JSON encoded error report, including at least two fields: 
    681    * 
    682    * status                  0 for error; nonzero for success 
    683    * 
    684    * error                   error message 
    685    */ 
    686   protected function onCreate() 
    687   { 
    688     try 
    689     { 
    690         $mime_filter = ((isset($_POST['filter']) && !empty($_POST['filter'])) ? $_POST['filter'].'/' : null); 
    691         $list_type = ((isset($_POST['type']) && $_POST['type'] == 'list') ? 'list' : 'thumb'); 
    692  
    693         if (!$this->options['create']) 
    694             throw new FileManagerException('disabled'); 
    695         if (empty($this->post['file'])) 
    696             throw new FileManagerException('nofile'); 
    697  
    698         $dir = $this->getDir(!empty($this->post['directory']) ? $this->post['directory'] : null); 
    699         $file = $this->getName(array('filename' => $this->post['file']), $dir);  // a directory has no 'extension'! 
    700         if (!$file) 
    701             throw new FileManagerException('nofile'); 
    702  
    703         $fileinfo = array( 
    704             'dir' => $dir, 
    705             'file' => $file, 
    706             'chmod' => $this->options['chmod'] 
    707         ); 
    708         if (!empty($this->options['CreateIsAuthorized_cb']) && function_exists($this->options['CreateIsAuthorized_cb']) && !$this->options['CreateIsAuthorized_cb']($this, 'create', $fileinfo)) 
    709             throw new FileManagerException('authorized'); 
    710  
    711         if (!@mkdir($file, $fileinfo['chmod'])) 
    712             throw new FileManagerException('mkdir_failed:' . $file); 
    713  
    714         // success, now show the new directory as a list view: 
    715         $jsok = array( 
    716                 'status' => 1 
    717             ); 
    718         $rv = $this->_onView($file . '/', $jsok, $mime_filter, $list_type); 
    719         echo json_encode($rv); 
    720     } 
    721     catch(FileManagerException $e) 
    722     { 
    723         $emsg = explode(':', $e->getMessage(), 2); 
    724         $jserr = array( 
    725                 'status' => 0, 
    726                 'error' => '${backend.' . $emsg[0] . '}' . (isset($emsg[1]) ? $emsg[1] : '') 
    727             ); 
    728         // and fall back to showing the PARENT directory 
    729         try 
    730         { 
    731             $rv = $this->_onView($dir, $jserr, $mime_filter, $list_type); 
    732             echo json_encode($rv); 
    733         } 
    734         catch (Exception $e) 
    735         { 
    736             // and fall back to showing the BASEDIR directory 
    737             try 
    738             { 
    739                 $dir = $this->getDir(); 
    740                 $rv = $this->_onView($dir, $jserr, $mime_filter, $list_type); 
    741                 echo json_encode($rv); 
    742             } 
    743             catch (Exception $e) 
    744             { 
    745                 // when we fail here, it's pretty darn bad and nothing to it. 
    746                 // just push the error JSON as go. 
    747                 echo json_encode($jserr); 
    748             } 
    749         } 
    750     } 
    751     catch(Exception $e) 
    752     { 
    753         // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
    754         $jserr = array( 
    755                 'status' => 0, 
    756                 'error' => $e->getMessage() 
    757             ); 
    758         // and fall back to showing the PARENT directory 
    759         try 
    760         { 
    761             $rv = $this->_onView($dir, $jserr, $mime_filter, $list_type); 
    762             echo json_encode($rv); 
    763         } 
    764         catch (Exception $e) 
    765         { 
    766             // and fall back to showing the BASEDIR directory 
    767             try 
    768             { 
    769                 $dir = $this->getDir(); 
    770                 $rv = $this->_onView($dir, $jserr, $mime_filter, $list_type); 
    771                 echo json_encode($rv); 
    772             } 
    773             catch (Exception $e) 
    774             { 
    775                 // when we fail here, it's pretty darn bad and nothing to it. 
    776                 // just push the error JSON as go. 
    777                 echo json_encode($jserr); 
    778             } 
    779         } 
    780     } 
    781   } 
    782  
    783   /** 
    784    * Process the 'download' event 
    785    * 
    786    * Send the file content of the specified file for download by the client. 
    787    * Only files residing within the directory tree rooted by the 
    788    * 'basedir' (options['directory']) will be allowed to be downloaded. 
    789    * 
    790    * Expected parameters: 
    791    * 
    792    * $_GET['file']          filepath of the file to be downloaded 
    793    * 
    794    * On errors a HTTP 403 error response will be sent instead. 
    795    */ 
    796   protected function onDownload() 
    797   { 
    798     try 
    799     { 
    800         if (!$this->options['download']) 
    801             throw new FileManagerException('disabled'); 
    802         if (empty($_GET['file'])) 
    803             throw new FileManagerException('nofile'); 
    804         // no need to check explicitly for '../' and './' here as getDir() will take care of it all! 
    805  
    806         // change the path to fit your websites document structure 
    807         $path = $this->getDir($_GET['file'], 0, false, false); 
    808         if (!is_file($path)) 
    809             throw new FileManagerException('nofile'); 
    810  
    811         $fileinfo = array( 
    812             'file' => $path 
    813         ); 
    814         if (!empty($this->options['DownloadIsAuthorized_cb']) && function_exists($this->options['DownloadIsAuthorized_cb']) && !$this->options['DownloadIsAuthorized_cb']($this, 'download', $fileinfo)) 
    815             throw new FileManagerException('authorized'); 
    816  
    817         if ($fd = fopen($path, "r")) 
    818         { 
    819             $fsize = filesize($path); 
    820             $path_parts = pathinfo($path); 
    821             $ext = strtolower($path_parts["extension"]); 
    822             switch ($ext) 
    823             { 
    824             case "pdf": 
    825                 header('Content-type: application/pdf'); 
    826                 header('Content-Disposition: attachment; filename="' . $path_parts["basename"] . '"'); // use 'attachment' to force a download 
    827                 break; 
    828  
    829              // add here more headers for diff. extensions 
    830  
    831             default; 
    832                 header('Content-type: application/octet-stream'); 
    833                 header('Content-Disposition: filename="' . $path_parts["basename"] . '"'); 
    834             } 
    835             header("Content-length: $fsize"); 
    836             header("Cache-control: private"); //use this to open files directly 
    837  
    838             fpassthru($fd); 
    839             fclose($fd); 
    840         } 
    841     } 
    842     catch(FileManagerException $e) 
    843     { 
    844         // we don't care whether it's a 404, a 403 or something else entirely: we feed 'em a 403 and that's final! 
    845         if (function_exists('send_response_status_header')) 
    846         { 
    847             send_response_status_header(403); 
    848             echo $e->getMessage(); 
    849         } 
    850         else 
    851         { 
    852             // no smarties detection whether we're running on fcgi or bare iron, we assume the latter: 
    853             header('HTTP/1.0 403 Forbidden', true, 403); 
    854             echo $e->getMessage(); 
    855         } 
    856     } 
    857     catch(Exception $e) 
    858     { 
    859         // we don't care whether it's a 404, a 403 or something else entirely: we feed 'em a 403 and that's final! 
    860         if (function_exists('send_response_status_header')) 
    861         { 
    862             send_response_status_header(403); 
    863             echo $e->getMessage(); 
    864         } 
    865         else 
    866         { 
    867             // no smarties detection whether we're running on fcgi or bare iron, we assume the latter: 
    868             header('HTTP/1.0 403 Forbidden', true, 403); 
    869             echo $e->getMessage(); 
    870         } 
    871     } 
    872   } 
    873  
    874   /** 
    875    * Process the 'upload' event 
    876    * 
    877    * Process and store the uploaded file in the designated location. 
    878    * Images will be resized when possible and applicable. A thumbnail image will also 
    879    * be preproduced when possible. 
    880    * Return a JSON encoded status of success or failure. 
    881    * 
    882    * Expected parameters: 
    883    * 
    884    * $_GET['directory']     path relative to basedir a.k.a. options['directory'] root 
    885    * 
    886    * $_FILES[]              the metadata for the uploaded file 
    887    * 
    888    * Errors will produce a JSON encoded error report, including at least two fields: 
    889    * 
    890    * status                  0 for error; nonzero for success 
    891    * 
    892    * error                   error message 
    893    */ 
    894   protected function onUpload() 
    895   { 
    896     try 
    897     { 
    898       if (!$this->options['upload']) 
    899         throw new FileManagerException('disabled'); 
    900       if (!Upload::exists('Filedata')) 
    901         throw new FileManagerException('nofile'); 
    902  
    903       $dir = $this->getDir(!empty($this->get['directory']) ? $this->get['directory'] : null); 
    904       $file = $this->getName($_FILES['Filedata']['name'], $dir); 
    905       if (!$file) 
    906         throw new FileManagerException('nofile'); 
    907       $fi = pathinfo($file); 
    908       if (!$fi['filename']) 
    909         throw new FileManagerException('nofile'); 
    910  
    911       /* 
    912       Security: 
    913  
    914       Upload::move() processes the unfiltered version of $_FILES[]['name'], at least to get the extension, 
    915       unless we ALWAYS override the filename and extension in the options array below. That's why we 
    916       calculate the extension at all times here. 
    917       */ 
    918       if (!is_string($fi['extension']) || strlen($fi['extension']) == 0) // can't use 'empty()' as "0" is a valid extension itself. 
    919       { 
    920         //enforce a mandatory extension, even when there isn't one (due to filtering or original input producing none) 
    921         $fi['extension'] = 'txt'; 
    922       } 
    923       else if ($this->options['safe'] && in_array(strtolower($fi['extension']), array('exe', 'dll', 'com', 'php', 'php3', 'php4', 'php5', 'phps'))) 
    924       { 
    925         $fi['extension'] = 'txt'; 
    926       } 
    927  
    928       $fileinfo = array( 
    929         'dir' => $dir, 
    930         'name' => $fi['filename'], 
    931         'extension' => $fi['extension'], 
    932         'size' => $_FILES['Filedata']['size'], 
    933         'maxsize' => $this->options['maxUploadSize'], 
    934         'mimes' => $this->getAllowedMimeTypes(), 
    935         'ext2mime_map' => $this->getMimeTypeDefinitions(), 
    936         'chmod' => $this->options['chmod'] & 0666   // security: never make those files 'executable'! 
    937       ); 
    938       if (!empty($this->options['UploadIsAuthorized_cb']) && function_exists($this->options['UploadIsAuthorized_cb']) && !$this->options['UploadIsAuthorized_cb']($this, 'upload', $fileinfo)) 
    939         throw new FileManagerException('authorized'); 
    940  
    941       $file = Upload::move('Filedata', $dir, $fileinfo); 
    942       $file = self::normalize($file); 
    943  
    944       /* 
    945        * NOTE: you /can/ (and should be able to, IMHO) upload 'overly large' image files to your site, but the thumbnailing process step 
    946        *       happening here will fail; we have memory usage estimators in place to make the fatal crash a non-silent one, i,e, one 
    947        *       where we still have a very high probability of NOT fatally crashing the PHP iunterpreter but catching a suitable exception 
    948        *       instead. 
    949        *       Having uploaded such huge images, a developer/somebody can always go in later and up the memory limit if the site admins 
    950        *       feel it is deserved. Until then, no thumbnails of such images (though you /should/ be able to milkbox-view the real thing!) 
    951        */ 
    952       if (FileManagerUtility::startsWith($this->getMimeType($file), 'image/') && !empty($this->get['resize'])) 
    953       { 
    954         $img = new Image($file); 
    955         $size = $img->getSize(); 
    956         // Xinha: We have separate width/height max dimensions, if these fail, the fallback of the 
    957         //   maxImageSize is used, which we set to some very high number by default for back compat 
    958         if ($size['width'] > $this->options['suggestedMaxImageDimension']['width'])  
    959           $img->resize( $this->options['suggestedMaxImageDimension']['width'])->save(); 
    960         elseif ($size['height'] > $this->options['suggestedMaxImageDimension']['height'])  
    961           $img->resize(null, $this->options['suggestedMaxImageDimension']['height'])->save();         
    962         else 
    963         // Image::resize() takes care to maintain the proper aspect ratio, so this is easy: 
    964         if ($size['width'] > $this->options['maxImageSize'] || $size['height'] > $this->options['maxImageSize']) 
    965           $img->resize($this->options['maxImageSize'], $this->options['maxImageSize'])->save(); 
    966         unset($img); 
    967       } 
    968  
    969       echo json_encode(array( 
    970         'status' => 1, 
    971         'name' => pathinfo($file, PATHINFO_BASENAME) 
    972       )); 
    973     } 
    974     catch(UploadException $e) 
    975     { 
    976       echo json_encode(array( 
    977         'status' => 0, 
    978         'error' => class_exists('ValidatorException') ? strip_tags($e->getMessage()) : '${backend.' . $e->getMessage() . '}' // This is for Styx :) 
    979       )); 
    980     } 
    981     catch(FileManagerException $e) 
    982     { 
    983         $emsg = explode(':', $e->getMessage(), 2); 
    984         echo json_encode(array( 
    985                 'status' => 0, 
    986                 'error' => '${backend.' . $emsg[0] . '}' . (isset($emsg[1]) ? $emsg[1] : '') 
    987             )); 
    988     } 
    989     catch(Exception $e) 
    990     { 
    991       // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
    992       echo json_encode(array( 
    993         'status' => 0, 
    994         'error' => $e->getMessage() 
    995       )); 
    996     } 
    997   } 
    998  
    999   /** 
    1000    * Process the 'move' event (with is used by both move/copy and rename client side actions) 
    1001    * 
    1002    * Copy or move/rename a given file or directory and return a JSON encoded status of success 
    1003    * or failure. 
    1004    * 
    1005    * Expected parameters: 
    1006    * 
    1007    * $_POST['copy']            nonzero value means copy, zero or nil for move/rename 
    1008    * 
    1009    * Source filespec: 
    1010    * 
    1011    *   $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
    1012    * 
    1013    *   $_POST['file']          original name of the file/subdirectory to be renamed/copied 
    1014    * 
    1015    * Destination filespec: 
    1016    * 
    1017    *   $_POST['newDirectory']  path relative to basedir a.k.a. options['directory'] root; 
    1018    *                           target directory where the file must be moved / copied 
    1019    * 
    1020    *   $_POST['name']          target name of the file/subdirectory to be renamed 
    1021    * 
    1022    * Errors will produce a JSON encoded error report, including at least two fields: 
    1023    * 
    1024    * status                    0 for error; nonzero for success 
    1025    * 
    1026    * error                     error message 
    1027    */ 
    1028   protected function onMove() 
    1029   { 
    1030     try 
    1031     { 
    1032         if (!$this->options['move']) 
    1033             throw new FileManagerException('disabled'); 
    1034         if (empty($this->post['file'])) 
    1035             throw new FileManagerException('nofile'); 
    1036  
    1037         $rename = empty($this->post['newDirectory']) && !empty($this->post['name']); 
    1038         $is_copy = (!empty($this->post['copy'])  && $this->post['copy']); 
    1039         $dir = $this->getDir(!empty($this->post['directory']) ? $this->post['directory'] : null); 
    1040         $file = pathinfo($this->post['file'], PATHINFO_BASENAME); 
    1041  
    1042         $is_dir = is_dir($dir . $file); 
    1043  
    1044         // note: we do not support copying entire directories, though directory rename/move is okay 
    1045         if (!$this->checkFile($dir . $file) || ($is_copy && $is_dir)) 
    1046             throw new FileManagerException('nofile'); 
    1047  
    1048         if($rename) 
    1049         { 
    1050             $fn = 'rename'; 
    1051             $newdir = null; 
    1052             if ($is_dir) 
    1053                 $newname = $this->getName(array('filename' => $this->post['name']), $dir);  // a directory has no 'extension' 
    1054             else 
    1055                 $newname = $this->getName($this->post['name'], $dir); 
    1056  
    1057             // when the new name seems to have a different extension, make sure the extension doesn't change after all: 
    1058             // Note: - if it's only 'case' we're changing here, then exchange the extension instead of appending it. 
    1059             //       - directories do not have extensions 
    1060             $extOld = pathinfo($file, PATHINFO_EXTENSION); 
    1061             $extNew = pathinfo($newname, PATHINFO_EXTENSION); 
    1062             if ((!$this->options['allowExtChange'] || (!$is_dir && empty($extNew))) && !empty($extOld) && strtolower($extOld) != strtolower($extNew)) 
    1063             { 
    1064                 $newname .= '.' . $extOld; 
    1065             } 
    1066         } 
    1067         else 
    1068         { 
    1069             $fn = ($is_copy ? 'copy' : 'rename' /* 'move' */); 
    1070             $newdir = $this->getDir(!empty($this->post['newDirectory']) ? $this->post['newDirectory'] : null); 
    1071             $newname = $this->getName($file, $newdir); 
    1072         } 
    1073  
    1074         if (!$newname) 
    1075             throw new FileManagerException('nonewfile'); 
    1076  
    1077         $fileinfo = array( 
    1078             'dir' => $dir, 
    1079             'file' => $file, 
    1080             'newdir' => $newdir, 
    1081             'newname' => $newname, 
    1082             'rename' => $rename, 
    1083             'is_dir' => $is_dir, 
    1084             'function' => $fn 
    1085         ); 
    1086  
    1087         if (!empty($this->options['MoveIsAuthorized_cb']) && function_exists($this->options['MoveIsAuthorized_cb']) && !$this->options['MoveIsAuthorized_cb']($this, 'move', $fileinfo)) 
    1088             throw new FileManagerException('authorized'); 
    1089  
    1090         if($rename) 
    1091         { 
    1092             // try to remove the thumbnail related to the original file; don't mind if it doesn't exist 
    1093             if(!$is_dir) 
    1094             { 
    1095                 if (!$this->deleteThumb($dir . $file)) 
    1096                     throw new FileManagerException('delete_thumbnail_failed'); 
    1097             } 
    1098         } 
    1099  
    1100         if (!@$fn($dir . $file, $newname)) 
    1101             throw new FileManagerException($fn . '_failed:' . $dir . $file . ':' . $newname); 
    1102  
    1103         echo json_encode(array( 
    1104             'status' => 1, 
    1105             'name' => pathinfo($newname, PATHINFO_BASENAME) 
    1106         )); 
    1107     } 
    1108     catch(FileManagerException $e) 
    1109     { 
    1110         $emsg = explode(':', $e->getMessage(), 2); 
    1111         echo json_encode(array( 
    1112                 'status' => 0, 
    1113                 'error' => '${backend.' . $emsg[0] . '}' . (isset($emsg[1]) ? $emsg[1] : '') 
    1114             )); 
    1115     } 
    1116     catch(Exception $e) 
    1117     { 
    1118         // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
    1119         echo json_encode(array( 
    1120                 'status' => 0, 
    1121                 'error' => $e->getMessage() 
    1122             )); 
    1123     } 
    1124   } 
    1125  
    1126  
    1127  
    1128   /** 
    1129    * Delete a file or directory, inclusing subdirectories and files. 
    1130    * 
    1131    * Return TRUE on success, FALSE when an error occurred. 
    1132    * 
    1133    * Note that the routine will try to percevere and keep deleting other subdirectories 
    1134    * and files, even when an error occurred for one or more of the subitems: this is 
    1135    * a best effort policy. 
    1136    */ 
    1137   protected function unlink($file) 
    1138   { 
    1139     if (!$file || !FileManagerUtility::startsWith($file, $this->basedir)) 
    1140         return false; 
    1141  
    1142     $rv = true; 
    1143     if(is_dir($file)) 
    1144     { 
    1145       $files = glob($file . '/*'); 
    1146       if (is_array($files)) 
    1147         foreach ($files as $f) 
    1148         { 
    1149           $rv &= $this->unlink($f); 
    1150           $rv &= $this->deleteThumb($f); 
    1151         } 
    1152  
    1153       $rv &= @rmdir($file); 
    1154     } 
    1155     else 
    1156     { 
    1157       if (file_exists($file)) 
    1158       { 
    1159         $rv &= @unlink($file); 
    1160         $rv &= $this->deleteThumb($file); 
    1161       } 
    1162     } 
    1163     return $rv; 
    1164   } 
    1165  
    1166   /** 
    1167    * Make a cleaned-up, unique filename 
    1168    * 
    1169    * Return the file (dir + name + ext), or a unique, yet non-existing, variant thereof, where the filename 
    1170    * is appended with a '_' and a number, e.g. '_1', when the file itself already exists in the given 
    1171    * directory. The directory part of the returned value equals $dir. 
    1172    * 
    1173    * Return NULL when $file is empty or when the specified directory does not reside within the 
    1174    * directory tree rooted by options['directory'] 
    1175    * 
    1176    * Note that the given filename will be converted to a legal filename, containing a filesystem-legal 
    1177    * subset of ASCII characters only, before being used and returned by this function. 
    1178    * 
    1179    * @param mixed $fileinfo     either a string containing a filename+ext or an array as produced by pathinfo(). 
    1180    * @daram string $dir         path pointing at where the given file may exist. 
    1181    * 
    1182    * @return a filepath consisting of $dir and the cleaned up and possibly sequenced filename and file extension 
    1183    *         as provided by $fileinfo. 
    1184    */ 
    1185   protected function getName($fileinfo, $dir) 
    1186   { 
    1187     if (!FileManagerUtility::endsWith($dir, '/')) $dir .= '/'; 
    1188  
    1189     if (is_string($fileinfo)) 
    1190     { 
    1191         $fileinfo = pathinfo($fileinfo); 
    1192     } 
    1193  
    1194     if (!is_array($fileinfo) || !$fileinfo['filename'] || !FileManagerUtility::startsWith($dir, $this->basedir)) return null; 
    1195  
    1196  
    1197     /* 
    1198      * since 'pagetitle()' is used to produce a unique, non-existing filename, we can forego the dirscan 
    1199      * and simply check whether the constructed filename/path exists or not and bump the suffix number 
    1200      * by 1 until it does not, thus quickly producing a unique filename. 
    1201      * 
    1202      * This is faster than using a dirscan to collect a set of existing filenames and feeding them as 
    1203      * an option array to pagetitle(), particularly for large directories. 
    1204      */ 
    1205     $filename = FileManagerUtility::pagetitle($fileinfo['filename'], null, '-_., []()~!@+' /* . '#&' */, '-_,~@+#&'); 
    1206     if (!$filename) 
    1207         return null; 
    1208  
    1209     // also clean up the extension: only allow alphanumerics in there! 
    1210     $ext = FileManagerUtility::pagetitle(!empty($fileinfo['extension']) ? $fileinfo['extension'] : null); 
    1211     $ext = (!empty($ext) ? '.' . $ext : null); 
    1212     // make sure the generated filename is SAFE: 
    1213     $file = $dir . $filename . $ext; 
    1214     if (file_exists($file)) 
    1215     { 
    1216         /* 
    1217          * make a unique name. Do this by postfixing the filename with '_X' where X is a sequential number. 
    1218          * 
    1219          * Note that when the input name is already such a 'sequenced' name, the sequence number is 
    1220          * extracted from it and sequencing continues from there, hence input 'file_5' would, if it already 
    1221          * existed, thus be bumped up to become 'file_6' and so on, until a filename is found which 
    1222          * does not yet exist in the designated directory. 
    1223          */ 
    1224         $i = 1; 
    1225         if (preg_match('/^(.*)_([1-9][0-9]*)$/', $filename, $matches)) 
    1226         { 
    1227             $i = intval($matches[2]); 
    1228             if ('P'.$i != 'P'.$matches[2] || $i > 100000) 
    1229             { 
    1230                 // very large number: not a sequence number! 
    1231                 $i = 1; 
    1232             } 
    1233             else 
    1234             { 
    1235                 $filename = $matches[1]; 
    1236             } 
    1237         } 
    1238         do 
    1239         { 
    1240             $file = $dir . $filename . ($i ? '_' . $i : '') . $ext; 
    1241             $i++; 
    1242         } while (file_exists($file)); 
    1243     } 
    1244  
    1245     // $file is now guaranteed to NOT exist 
    1246     return $file; 
    1247   } 
    1248  
    1249   protected function getIcon($file, $smallIcon = false) 
    1250   { 
    1251     if (FileManagerUtility::endsWith($file, '/..')) $ext = 'dir_up'; 
    1252     elseif (is_dir($file)) $ext = 'dir'; 
    1253     else $ext = pathinfo($file, PATHINFO_EXTENSION); 
    1254  
    1255     $largeDir = ($smallIcon === false ? 'Large/' : ''); 
    1256     $path = (is_file(FileManagerUtility::getSiteRoot() . $this->options['assetBasePath'] . 'Images/Icons/' .$largeDir.$ext.'.png')) 
    1257       ? $this->options['assetBasePath'] . 'Images/Icons/'.$largeDir.$ext.'.png' 
    1258       : $this->options['assetBasePath'] . 'Images/Icons/'.$largeDir.'default.png'; 
    1259  
    1260     return $path; 
    1261   } 
    1262  
    1263   protected function getThumb($file) 
    1264   { 
    1265     $thumb = $this->generateThumbName($file); 
    1266     $thumbPath = FileManagerUtility::getSiteRoot() . $this->options['thumbnailPath'] . $thumb; 
    1267     if (is_file($thumbPath)) 
    1268       return $thumb; 
    1269     elseif(is_file(FileManagerUtility::getSiteRoot() . $this->options['thumbnailPath'] . basename($file))) 
    1270       return basename($file); 
    1271     else 
    1272       return $this->generateThumb($file,$thumbPath); 
    1273   } 
    1274  
    1275   protected function generateThumbName($file) 
    1276   { 
    1277     return 'thumb_'.md5($file).'_'.str_replace('.','_',basename($file)).'.png'; 
    1278   } 
    1279  
    1280   protected function generateThumb($file,$thumbPath) 
    1281   { 
    1282     $img = new Image($file); 
    1283     $img->resize(250,250,true,false)->process('png',$thumbPath); // TODO: save as lossy / lower-Q jpeg to reduce filesize? 
    1284     unset($img); 
    1285     return basename($thumbPath); 
    1286   } 
    1287  
    1288   protected function deleteThumb($file) 
    1289   { 
    1290     $thumb = $this->generateThumbName($file); 
    1291     $thumbPath = FileManagerUtility::getSiteRoot() . $this->options['thumbnailPath'] . $thumb; 
    1292     if(is_file($thumbPath)) 
    1293       return @unlink($thumbPath); 
    1294     return true;   // when thumbnail does not exist, say it is succesfully removed: all that counts is it doesn't exist anymore when we're done here. 
    1295   } 
    1296  
    1297   public function getMimeType($file) 
    1298   { 
    1299     return is_dir($file) ? 'text/directory' : Upload::mime($file, null, $this->getMimeTypeDefinitions()); 
    1300   } 
    1301  
    1302   /** 
    1303    * Produce the absolute path equivalent, filesystem-wise, of the given $dir directory. 
    1304    * 
    1305    * The directory is enforced to sit within the directory tree rooted by options['directory'] 
    1306    * 
    1307    * When the directory does not exist or does not match this restricting criterium, the 
    1308    * basedir path (abs path eqv. to options['directory']) is returned instead. 
    1309    * 
    1310    * In short: getDir() will guarantee the returned path equals the options['directory'] path or 
    1311    *           a subdirectory thereof. The returned path is an absolute path in the filesystem. 
    1312    */ 
    1313   protected function getDir($dir = null, $chmod = 0777, $mkdir_if_notexist = false, $with_trailing_slash = true) 
    1314   { 
    1315     $dir = str_replace('\\','/', $dir); 
    1316     $basedir = $this->basedir; 
    1317     $root = FileManagerUtility::getSiteRoot(); 
    1318     $dir = (!FileManagerUtility::startsWith($dir, '/') ? $basedir : $root) . $dir; 
    1319     $dir = FileManagerUtility::getRealDir($dir, $chmod, $mkdir_if_notexist, $with_trailing_slash); 
    1320     return $this->checkFile($mkdir_if_notexist ? dirname($dir) : $dir) ? $dir : $this->basedir; 
    1321   } 
    1322  
    1323   /** 
    1324    * Identical to getDir() apart from the fact that this method returns a DocumentRoot based abolute one. 
    1325    * 
    1326    * This function assumes the specified path is located within the options['directory'] a.k.a. 
    1327    * 'basedir' based directory tree. 
    1328    */ 
    1329   protected function getPath($dir = null, $chmod = 0777, $mkdir_if_notexist = false, $with_trailing_slash = true) 
    1330   { 
    1331     $path = $this->getDir($dir, $chmod, $mkdir_if_notexist, $with_trailing_slash); 
    1332     $root = FileManagerUtility::getSiteRoot(); 
    1333     $path = str_replace($root,'',$path); 
    1334  
    1335     return $path; 
    1336   } 
    1337  
    1338   /** 
    1339    * Determine whether the specified file or directory is not nil, 
    1340    * exists within the directory tree rooted by options['directory'] and 
    1341    * matches the permitted mimetypes restriction (optional $mime_filter) 
    1342    * 
    1343    * @return TRUE when all criteria are met, FALSE otherwise. 
    1344    */ 
    1345   protected function checkFile($file, $mime_filter = null) 
    1346   { 
    1347     $mimes = $this->getAllowedMimeTypes($mime_filter); 
    1348  
    1349     $hasFilter = ($mime_filter && count($mimes)); 
    1350     if ($hasFilter) array_push($mimes, 'text/directory'); 
    1351     return !empty($file) && FileManagerUtility::startsWith($file, $this->basedir) && file_exists($file) && (!$hasFilter || in_array($this->getMimeType($file), $mimes)); 
    1352   } 
    1353  
    1354   /** 
    1355    * Normalize a path by converting all slashes '/' and/or backslashes '\' and any mix thereof in the 
    1356    * specified path to UNIX/MAC/Win compatible single forward slashes '/'. 
    1357    */ 
    1358   protected static function normalize($file) 
    1359   { 
    1360     return preg_replace('/(\\\|\/)+/', '/', $file); 
    1361   } 
    1362  
    1363   public function getAllowedMimeTypes($mime_filter = null) 
    1364   { 
    1365     $mimeTypes = array(); 
    1366  
    1367     if (!$mime_filter) return null; 
    1368     if (!FileManagerUtility::endsWith($mime_filter, '/')) return array($mime_filter); 
    1369  
    1370     $mimes = $this->getMimeTypeDefinitions(); 
    1371  
    1372     foreach ($mimes as $mime) 
    1373       if (FileManagerUtility::startsWith($mime, $mime_filter)) 
    1374         $mimeTypes[] = $mime; 
    1375  
    1376     return $mimeTypes; 
    1377   } 
    1378  
    1379   public function getMimeTypeDefinitions() 
    1380   { 
    1381     static $mimes; 
    1382  
    1383     if (!$mimes) $mimes = parse_ini_file($this->options['mimeTypesPath']); 
    1384     if (!$mimes) $mimes = array(); // prevent faulty mimetype ini file from b0rking other code sections. 
    1385     return $mimes; 
    1386   } 
     1293 
     1294                        $file = $this->url_path2file_path($img_filepath); 
     1295                        $mime = $this->getMimeType($file); 
     1296                        $fd = fopen($file, 'rb'); 
     1297                        if (!$fd) 
     1298                        { 
     1299                                // when the icon / thumbnail cannot be opened for whatever reason, fall back to the default error image: 
     1300                                $file = $this->url_path2file_path($this->getIcon('is.default-error', $reqd_size <= 16)); 
     1301                                $mime = $this->getMimeType($file); 
     1302                                $fd = fopen($file, 'rb'); 
     1303                                if (!$fd) 
     1304                                        throw new Exception('panic'); 
     1305                        } 
     1306                        $fsize = filesize($file); 
     1307                        if (!empty($mime)) 
     1308                        { 
     1309                                header('Content-Type: ' . $mime); 
     1310                        } 
     1311                        header('Content-Length: ' . $fsize); 
     1312 
     1313                        header("Cache-Control: private"); //use this to open files directly 
     1314 
     1315                        fpassthru($fd); 
     1316                        fclose($fd); 
     1317                        exit(); 
     1318                } 
     1319                catch(Exception $e) 
     1320                { 
     1321                        send_response_status_header(500); 
     1322                        echo 'Cannot produce thumbnail: ' . $emsg . ' :: ' . $img_filepath; 
     1323                } 
     1324        } 
     1325 
     1326 
     1327        /** 
     1328         * Process the 'destroy' event 
     1329         * 
     1330         * Delete the specified file or directory and return a JSON encoded status of success 
     1331         * or failure. 
     1332         * 
     1333         * Note that when images are deleted, so are their thumbnails. 
     1334         * 
     1335         * Expected parameters: 
     1336         * 
     1337         * $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
     1338         * 
     1339         * $_POST['file']          filename (including extension, of course) of the file to 
     1340         *                         be detailed. 
     1341         * 
     1342         * $_POST['filter']        optional mimetype filter string, amy be the part up to and 
     1343         *                         including the slash '/' or the full mimetype. Only files 
     1344         *                         matching this (set of) mimetypes will be listed. 
     1345         *                         Examples: 'image/' or 'application/zip' 
     1346         * 
     1347         * Errors will produce a JSON encoded error report, including at least two fields: 
     1348         * 
     1349         * status                  0 for error; nonzero for success 
     1350         * 
     1351         * error                   error message 
     1352         */ 
     1353        protected function onDestroy() 
     1354        { 
     1355                $emsg = null; 
     1356                $jserr = array( 
     1357                                'status' => 1 
     1358                        ); 
     1359 
     1360                try 
     1361                { 
     1362                        if (!$this->options['destroy']) 
     1363                                throw new FileManagerException('disabled'); 
     1364 
     1365                        $file_arg = $this->getPOSTparam('file'); 
     1366                        if (empty($file_arg)) 
     1367                                throw new FileManagerException('nofile'); 
     1368 
     1369                        $dir_arg = $this->getPOSTparam('directory'); 
     1370                        $legal_url = $this->rel2abs_legal_url_path($dir_arg); 
     1371                        $legal_url = self::enforceTrailingSlash($legal_url); 
     1372 
     1373                        $filename = pathinfo($file_arg, PATHINFO_BASENAME); 
     1374                        $legal_url .= $filename; 
     1375                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     1376                        $file = $this->legal_url_path2file_path($legal_url); 
     1377 
     1378                        if (!file_exists($file)) 
     1379                                throw new FileManagerException('nofile'); 
     1380 
     1381                        $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); 
     1382                        $mime = $this->getMimeType($file); 
     1383                        $mime_filters = $this->getAllowedMimeTypes($mime_filter); 
     1384                        if (is_file($file)) 
     1385                        { 
     1386                                if (!$this->IsAllowedMimeType($mime, $mime_filters)) 
     1387                                        throw new FileManagerException('extension'); 
     1388                        } 
     1389                        else if (!is_dir($file)) 
     1390                        { 
     1391                                throw new FileManagerException('nofile'); 
     1392                        } 
     1393 
     1394                        $fileinfo = array( 
     1395                                        'legal_url' => $legal_url, 
     1396                                        'file' => $file, 
     1397                                        'mime' => $mime, 
     1398                                        'mime_filter' => $mime_filter, 
     1399                                        'mime_filters' => $mime_filters 
     1400                                ); 
     1401 
     1402                        if (!empty($this->options['DestroyIsAuthorized_cb']) && function_exists($this->options['DestroyIsAuthorized_cb']) && !$this->options['DestroyIsAuthorized_cb']($this, 'destroy', $fileinfo)) 
     1403                                throw new FileManagerException('authorized'); 
     1404 
     1405                        $legal_url = $fileinfo['legal_url']; 
     1406                        $file = $fileinfo['file']; 
     1407                        $mime = $fileinfo['mime']; 
     1408                        $mime_filter = $fileinfo['mime_filter']; 
     1409                        $mime_filters = $fileinfo['mime_filters']; 
     1410 
     1411                        if (!$this->unlink($legal_url, $mime_filters)) 
     1412                                throw new FileManagerException('unlink_failed:' . $legal_url); 
     1413 
     1414                        if (!headers_sent()) header('Content-Type: application/json'); 
     1415 
     1416                        echo json_encode(array( 
     1417                                        'status' => 1, 
     1418                                        'content' => 'destroyed' 
     1419                                )); 
     1420                        return; 
     1421                } 
     1422                catch(FileManagerException $e) 
     1423                { 
     1424                        $emsg = $e->getMessage(); 
     1425                } 
     1426                catch(Exception $e) 
     1427                { 
     1428                        // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
     1429                        $emsg = $e->getMessage(); 
     1430                } 
     1431 
     1432                $this->modify_json4exception($jserr, $emsg); 
     1433 
     1434                if (!headers_sent()) header('Content-Type: application/json'); 
     1435 
     1436                // when we fail here, it's pretty darn bad and nothing to it. 
     1437                // just push the error JSON as go. 
     1438                echo json_encode($jserr); 
     1439        } 
     1440 
     1441        /** 
     1442         * Process the 'create' event 
     1443         * 
     1444         * Create the specified subdirectory and give it the configured permissions 
     1445         * (options['chmod'], default 0777) and return a JSON encoded status of success 
     1446         * or failure. 
     1447         * 
     1448         * Expected parameters: 
     1449         * 
     1450         * $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
     1451         * 
     1452         * $_POST['file']          name of the subdirectory to be created 
     1453         * 
     1454         * Extra input parameters considered while producing the JSON encoded directory view. 
     1455         * These may not seem relevant for an empty directory, but these parameters are also 
     1456         * considered when providing the fallback directory view in case an error occurred 
     1457         * and then the listed directory (either the parent or the basedir itself) may very 
     1458         * likely not be empty! 
     1459         * 
     1460         * $_POST['filter']        optional mimetype filter string, amy be the part up to and 
     1461         *                         including the slash '/' or the full mimetype. Only files 
     1462         *                         matching this (set of) mimetypes will be listed. 
     1463         *                         Examples: 'image/' or 'application/zip' 
     1464         * 
     1465         * $_POST['type']          'thumb' will produce a list view including thumbnail and other 
     1466         *                         information with each listed file; other values will produce 
     1467         *                         a basic list view (similar to Windows Explorer 'list' view). 
     1468         * 
     1469         * Errors will produce a JSON encoded error report, including at least two fields: 
     1470         * 
     1471         * status                  0 for error; nonzero for success 
     1472         * 
     1473         * error                   error message 
     1474         */ 
     1475        protected function onCreate() 
     1476        { 
     1477                $emsg = null; 
     1478                $jserr = array( 
     1479                                'status' => 1 
     1480                        ); 
     1481 
     1482                $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); 
     1483                $list_type = ($this->getPOSTparam('type') != 'thumb' ? 'list' : 'thumb'); 
     1484 
     1485                $legal_url = null; 
     1486 
     1487                try 
     1488                { 
     1489                        $dir_arg = $this->getPOSTparam('directory'); 
     1490                        $legal_url = $this->rel2abs_legal_url_path($dir_arg); 
     1491                        $legal_url = self::enforceTrailingSlash($legal_url); 
     1492 
     1493                        if (!$this->options['create']) 
     1494                                throw new FileManagerException('disabled'); 
     1495 
     1496                        $file_arg = $this->getPOSTparam('file'); 
     1497                        if (empty($file_arg)) 
     1498                                throw new FileManagerException('nofile'); 
     1499 
     1500                        $filename = pathinfo($file_arg, PATHINFO_BASENAME); 
     1501                        //$legal_url .= $filename; 
     1502                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     1503                        $dir = $this->legal_url_path2file_path($legal_url); 
     1504 
     1505                        if (!is_dir($dir)) 
     1506                                throw new FileManagerException('nofile'); 
     1507 
     1508                        $file = $this->getUniqueName(array('filename' => $filename), $dir);  // a directory has no 'extension'! 
     1509                        if (!$file) 
     1510                                throw new FileManagerException('nofile'); 
     1511                        $newdir = $this->legal_url_path2file_path($legal_url . $file); 
     1512 
     1513                        $fileinfo = array( 
     1514                                        'legal_url' => $legal_url, 
     1515                                        'dir' => $dir, 
     1516                                        'raw_name' => $filename, 
     1517                                        'uniq_name' => $file, 
     1518                                        'newdir' => $newdir, 
     1519                                        'chmod' => $this->options['chmod'] 
     1520                                ); 
     1521                        if (!empty($this->options['CreateIsAuthorized_cb']) && function_exists($this->options['CreateIsAuthorized_cb']) && !$this->options['CreateIsAuthorized_cb']($this, 'create', $fileinfo)) 
     1522                                throw new FileManagerException('authorized'); 
     1523 
     1524                        $legal_url = $fileinfo['legal_url']; 
     1525                        $dir = $fileinfo['dir']; 
     1526                        $filename = $fileinfo['raw_name']; 
     1527                        $file = $fileinfo['uniq_name']; 
     1528                        $newdir = $fileinfo['newdir']; 
     1529 
     1530                        if (!@mkdir($newdir, $fileinfo['chmod'], true)) 
     1531                                throw new FileManagerException('mkdir_failed:' . $this->legal2abs_url_path($legal_url) . $file); 
     1532 
     1533                        if (!headers_sent()) header('Content-Type: application/json'); 
     1534 
     1535                        // success, now show the new directory as a list view: 
     1536                        $rv = $this->_onView($legal_url . $file . '/', $jserr, $mime_filter, $list_type); 
     1537                        echo json_encode(array_merge($rv['json'], array('files' => array_merge(array(), $rv['dirs'], $rv['files'])))); 
     1538                        return; 
     1539                } 
     1540                catch(FileManagerException $e) 
     1541                { 
     1542                        $emsg = $e->getMessage(); 
     1543 
     1544                        $jserr['status'] = 0; 
     1545 
     1546                        // and fall back to showing the PARENT directory 
     1547                        try 
     1548                        { 
     1549                                $rv = $this->_onView($legal_url, $jserr, $mime_filter, $list_type); 
     1550                                $jserr = array_merge($rv['json'], array('files' => array_merge(array(), $rv['dirs'], $rv['files']))); 
     1551                        } 
     1552                        catch (Exception $e) 
     1553                        { 
     1554                                // and fall back to showing the BASEDIR directory 
     1555                                try 
     1556                                { 
     1557                                        $legal_url = $this->options['directory']; 
     1558                                        $rv = $this->_onView($legal_url, $jserr, $mime_filter, $list_type); 
     1559                                        $jserr = array_merge($rv['json'], array('files' => array_merge(array(), $rv['dirs'], $rv['files']))); 
     1560                                } 
     1561                                catch (Exception $e) 
     1562                                { 
     1563                                        // when we fail here, it's pretty darn bad and nothing to it. 
     1564                                        // just push the error JSON as go. 
     1565                                } 
     1566                        } 
     1567                } 
     1568                catch(Exception $e) 
     1569                { 
     1570                        // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
     1571                        $emsg = $e->getMessage(); 
     1572 
     1573                        $jserr['status'] = 0; 
     1574 
     1575                        // and fall back to showing the PARENT directory 
     1576                        try 
     1577                        { 
     1578                                $rv = $this->_onView($legal_url, $jserr, $mime_filter, $list_type); 
     1579                                $jserr = array_merge($rv['json'], array('files' => array_merge(array(), $rv['dirs'], $rv['files']))); 
     1580                        } 
     1581                        catch (Exception $e) 
     1582                        { 
     1583                                // and fall back to showing the BASEDIR directory 
     1584                                try 
     1585                                { 
     1586                                        $legal_url = $this->options['directory']; 
     1587                                        $rv = $this->_onView($legal_url, $jserr, $mime_filter, $list_type); 
     1588                                        $jserr = array_merge($rv['json'], array('files' => array_merge(array(), $rv['dirs'], $rv['files']))); 
     1589                                } 
     1590                                catch (Exception $e) 
     1591                                { 
     1592                                        // when we fail here, it's pretty darn bad and nothing to it. 
     1593                                        // just push the error JSON as go. 
     1594                                } 
     1595                        } 
     1596                } 
     1597 
     1598                $this->modify_json4exception($jserr, $emsg); 
     1599 
     1600                if (!headers_sent()) header('Content-Type: application/json'); 
     1601 
     1602                // when we fail here, it's pretty darn bad and nothing to it. 
     1603                // just push the error JSON as go. 
     1604                echo json_encode($jserr); 
     1605        } 
     1606 
     1607        /** 
     1608         * Process the 'download' event 
     1609         * 
     1610         * Send the file content of the specified file for download by the client. 
     1611         * Only files residing within the directory tree rooted by the 
     1612         * 'basedir' (options['directory']) will be allowed to be downloaded. 
     1613         * 
     1614         * Expected parameters: 
     1615         * 
     1616         * $_GET['file']          filepath of the file to be downloaded 
     1617         * 
     1618         * $_GET['filter']        optional mimetype filter string, amy be the part up to and 
     1619         *                        including the slash '/' or the full mimetype. Only files 
     1620         *                        matching this (set of) mimetypes will be listed. 
     1621         *                        Examples: 'image/' or 'application/zip' 
     1622         * 
     1623         * On errors a HTTP 403 error response will be sent instead. 
     1624         */ 
     1625        protected function onDownload() 
     1626        { 
     1627                try 
     1628                { 
     1629                        if (!$this->options['download']) 
     1630                                throw new FileManagerException('disabled'); 
     1631 
     1632                        $file_arg = $this->getGETparam('file'); 
     1633                        if (empty($file_arg)) 
     1634                                throw new FileManagerException('nofile'); 
     1635 
     1636                        $legal_url = $this->rel2abs_legal_url_path($file_arg); 
     1637                        //$legal_url = self::enforceTrailingSlash($legal_url); 
     1638 
     1639                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     1640                        $file = $this->legal_url_path2file_path($legal_url); 
     1641 
     1642                        if (!is_readable($file)) 
     1643                                throw new FileManagerException('nofile'); 
     1644 
     1645                        $mime_filter = $this->getGETparam('filter', $this->options['filter']); 
     1646                        $mime = $this->getMimeType($file); 
     1647                        $mime_filters = $this->getAllowedMimeTypes($mime_filter); 
     1648                        if (is_file($file)) 
     1649                        { 
     1650                                if (!$this->IsAllowedMimeType($mime, $mime_filters)) 
     1651                                        throw new FileManagerException('extension'); 
     1652                        } 
     1653                        else 
     1654                        { 
     1655                                throw new FileManagerException('nofile'); 
     1656                        } 
     1657 
     1658                        $fileinfo = array( 
     1659                                        'legal_url' => $legal_url, 
     1660                                        'file' => $file, 
     1661                                        'mime' => $mime, 
     1662                                        'mime_filter' => $mime_filter, 
     1663                                        'mime_filters' => $mime_filters 
     1664                                ); 
     1665                        if (!empty($this->options['DownloadIsAuthorized_cb']) && function_exists($this->options['DownloadIsAuthorized_cb']) && !$this->options['DownloadIsAuthorized_cb']($this, 'download', $fileinfo)) 
     1666                                throw new FileManagerException('authorized'); 
     1667 
     1668                        $legal_url = $fileinfo['legal_url']; 
     1669                        $file = $fileinfo['file']; 
     1670                        $mime = $fileinfo['mime']; 
     1671                        $mime_filter = $fileinfo['mime_filter']; 
     1672                        $mime_filters = $fileinfo['mime_filters']; 
     1673 
     1674                        if ($fd = fopen($file, 'rb')) 
     1675                        { 
     1676                                $fsize = filesize($file); 
     1677                                $path_parts = pathinfo($legal_url); 
     1678                                $ext = strtolower($path_parts["extension"]); 
     1679                                switch ($ext) 
     1680                                { 
     1681                                case "pdf": 
     1682                                        header('Content-Type: application/pdf'); 
     1683                                        header('Content-Disposition: attachment; filename="' . $path_parts["basename"] . '"'); // use 'attachment' to force a download 
     1684                                        break; 
     1685 
     1686                                // add here more headers for diff. extensions 
     1687 
     1688                                default; 
     1689                                        header('Content-Type: application/octet-stream'); 
     1690                                        header('Content-Disposition: filename="' . $path_parts["basename"] . '"'); 
     1691                                        break; 
     1692                                } 
     1693                                header("Content-length: $fsize"); 
     1694                                header("Cache-control: private"); //use this to open files directly 
     1695 
     1696                                fpassthru($fd); 
     1697                                fclose($fd); 
     1698                        } 
     1699                } 
     1700                catch(FileManagerException $e) 
     1701                { 
     1702                        // we don't care whether it's a 404, a 403 or something else entirely: we feed 'em a 403 and that's final! 
     1703                        send_response_status_header(403); 
     1704                        echo $e->getMessage(); 
     1705                } 
     1706                catch(Exception $e) 
     1707                { 
     1708                        // we don't care whether it's a 404, a 403 or something else entirely: we feed 'em a 403 and that's final! 
     1709                        send_response_status_header(403); 
     1710                        echo $e->getMessage(); 
     1711                } 
     1712        } 
     1713 
     1714        /** 
     1715         * Process the 'upload' event 
     1716         * 
     1717         * Process and store the uploaded file in the designated location. 
     1718         * Images will be resized when possible and applicable. A thumbnail image will also 
     1719         * be preproduced when possible. 
     1720         * Return a JSON encoded status of success or failure. 
     1721         * 
     1722         * Expected parameters: 
     1723         * 
     1724         * $_GET['directory']     path relative to basedir a.k.a. options['directory'] root 
     1725         * 
     1726         * $_GET['resize']        nonzero value indicates any uploaded image should be resized to the configured options['maxImageDimension'] width and height whenever possible 
     1727         * 
     1728         * $_GET['filter']        optional mimetype filter string, amy be the part up to and 
     1729         *                        including the slash '/' or the full mimetype. Only files 
     1730         *                        matching this (set of) mimetypes will be listed. 
     1731         *                        Examples: 'image/' or 'application/zip' 
     1732         * 
     1733         * $_FILES[]              the metadata for the uploaded file 
     1734         * 
     1735         * Errors will produce a JSON encoded error report, including at least two fields: 
     1736         * 
     1737         * status                  0 for error; nonzero for success 
     1738         * 
     1739         * error                   error message 
     1740         */ 
     1741        protected function onUpload() 
     1742        { 
     1743                $emsg = null; 
     1744                $jserr = array( 
     1745                                'status' => 1 
     1746                        ); 
     1747 
     1748                try 
     1749                { 
     1750                        if (!$this->options['upload']) 
     1751                                throw new FileManagerException('disabled'); 
     1752 
     1753                        if (!isset($_FILES) || empty($_FILES['Filedata']) || empty($_FILES['Filedata']['name']) || empty($_FILES['Filedata']['size'])) 
     1754                                throw new FileManagerException('nofile'); 
     1755 
     1756                        $file_arg = $_FILES['Filedata']['name']; 
     1757                        if (empty($file_arg)) 
     1758                                throw new FileManagerException('nofile'); 
     1759 
     1760                        $dir_arg = $this->getPOSTparam('directory'); 
     1761                        $legal_url = $this->rel2abs_legal_url_path($dir_arg); 
     1762                        $legal_url = self::enforceTrailingSlash($legal_url); 
     1763                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     1764                        $dir = $this->legal_url_path2file_path($legal_url); 
     1765 
     1766                        $filename = $this->getUniqueName($file_arg, $dir); 
     1767                        if (!$filename) 
     1768                                throw new FileManagerException('nofile'); 
     1769                        $fi = pathinfo($filename); 
     1770 
     1771 
     1772                        $mime_filter = $this->getGETparam('filter', $this->options['filter']); 
     1773                        $tmppath = $_FILES['Filedata']['tmp_name']; 
     1774                        $mime = $this->getMimeType($tmppath); 
     1775                        $mime_filters = $this->getAllowedMimeTypes($mime_filter); 
     1776                        if (!$this->IsAllowedMimeType($mime, $mime_filters)) 
     1777                                throw new FileManagerException('extension'); 
     1778 
     1779                        /* 
     1780                        Security: 
     1781 
     1782                        Upload::move() processes the unfiltered version of $_FILES[]['name'], at least to get the extension, 
     1783                        unless we ALWAYS override the filename and extension in the options array below. That's why we 
     1784                        calculate the extension at all times here. 
     1785                        */ 
     1786                        if (!is_string($fi['extension']) || strlen($fi['extension']) == 0) // can't use 'empty()' as "0" is a valid extension itself. 
     1787                        { 
     1788                                //enforce a mandatory extension, even when there isn't one (due to filtering or original input producing none) 
     1789                                $fi['extension'] = 'txt'; 
     1790                        } 
     1791                        else if ($this->options['safe'] && in_array(strtolower($fi['extension']), array('exe', 'dll', 'com', 'php', 'php3', 'php4', 'php5', 'phps'))) 
     1792                        { 
     1793                                $fi['extension'] = 'txt'; 
     1794                        } 
     1795 
     1796                        $fileinfo = array( 
     1797                                'legal_url' => $legal_url, 
     1798                                'dir' => $dir, 
     1799                                'raw_filename' => $file_arg, 
     1800                                'name' => $fi['filename'], 
     1801                                'extension' => $fi['extension'], 
     1802                                'mime' => $mime, 
     1803                                'mime_filter' => $mime_filter, 
     1804                                'mime_filters' => $mime_filters, 
     1805                                'tmp_filepath' => $tmppath, 
     1806                                'size' => $_FILES['Filedata']['size'], 
     1807                                'maxsize' => $this->options['maxUploadSize'], 
     1808                                'overwrite' => false, 
     1809                                'chmod' => $this->options['chmod'] & 0666   // security: never make those files 'executable'! 
     1810                        ); 
     1811                        if (!empty($this->options['UploadIsAuthorized_cb']) && function_exists($this->options['UploadIsAuthorized_cb']) && !$this->options['UploadIsAuthorized_cb']($this, 'upload', $fileinfo)) 
     1812                                throw new FileManagerException('authorized'); 
     1813 
     1814                        $legal_url = $fileinfo['legal_url']; 
     1815                        $dir = $fileinfo['dir']; 
     1816                        $file_arg = $fileinfo['raw_filename']; 
     1817                        $filename = $fileinfo['name'] . (!empty($fileinfo['extension']) ? '.' . $fileinfo['extension'] : ''); 
     1818                        $mime = $fileinfo['mime']; 
     1819                        $mime_filter = $fileinfo['mime_filter']; 
     1820                        $mime_filters = $fileinfo['mime_filters']; 
     1821                        //$tmppath = $fileinfo['tmp_filepath']; 
     1822 
     1823                        if($fileinfo['maxsize'] && $fileinfo['size'] > $fileinfo['maxsize']) 
     1824                                throw new FileManagerException('size'); 
     1825 
     1826                        if(!$fileinfo['extension']) 
     1827                                throw new FileManagerException('extension'); 
     1828 
     1829                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     1830                        $file = $this->legal_url_path2file_path($legal_url . $filename); 
     1831 
     1832                        if(!$fileinfo['overwrite'] && file_exists($file)) 
     1833                                throw new FileManagerException('exists'); 
     1834 
     1835                        if(!move_uploaded_file($_FILES['Filedata']['tmp_name'], $file)) 
     1836                                throw new FileManagerException(strtolower($_FILES['Filedata']['error'] <= 2 ? 'size' : ($_FILES['Filedata']['error'] == 3 ? 'partial' : 'path'))); 
     1837 
     1838                        @chmod($file, $fileinfo['chmod']); 
     1839 
     1840 
     1841                        /* 
     1842                         * NOTE: you /can/ (and should be able to, IMHO) upload 'overly large' image files to your site, but the resizing process step 
     1843                         *       happening here will fail; we have memory usage estimators in place to make the fatal crash a non-silent one, i,e, one 
     1844                         *       where we still have a very high probability of NOT fatally crashing the PHP iunterpreter but catching a suitable exception 
     1845                         *       instead. 
     1846                         *       Having uploaded such huge images, a developer/somebody can always go in later and up the memory limit if the site admins 
     1847                         *       feel it is deserved. Until then, no thumbnails of such images (though you /should/ be able to milkbox-view the real thing!) 
     1848                         */ 
     1849                        if (FileManagerUtility::startsWith($mime, 'image/') && $this->getGETparam('resize', 0)) 
     1850                        { 
     1851                                $img = new Image($file); 
     1852                                $size = $img->getSize(); 
     1853                                // Image::resize() takes care to maintain the proper aspect ratio, so this is easy 
     1854                                // (default quality is 100% for JPEG so we get the cleanest resized images here) 
     1855                                $img->resize($this->options['maxImageDimension']['width'], $this->options['maxImageDimension']['height'])->save(); 
     1856                                unset($img); 
     1857                        } 
     1858 
     1859                        if (!headers_sent()) header('Content-Type: application/json'); 
     1860 
     1861                        echo json_encode(array( 
     1862                                        'status' => 1, 
     1863                                        'name' => pathinfo($file, PATHINFO_BASENAME) 
     1864                                )); 
     1865                        return; 
     1866                } 
     1867                catch(FileManagerException $e) 
     1868                { 
     1869                        $emsg = $e->getMessage(); 
     1870                } 
     1871                catch(Exception $e) 
     1872                { 
     1873                        // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
     1874                        $emsg = $e->getMessage(); 
     1875                } 
     1876 
     1877                $this->modify_json4exception($jserr, $emsg); 
     1878 
     1879                if (!headers_sent()) header('Content-Type: application/json'); 
     1880 
     1881                // when we fail here, it's pretty darn bad and nothing to it. 
     1882                // just push the error JSON as go. 
     1883                echo json_encode($jserr); 
     1884        } 
     1885 
     1886        /** 
     1887         * Process the 'move' event (with is used by both move/copy and rename client side actions) 
     1888         * 
     1889         * Copy or move/rename a given file or directory and return a JSON encoded status of success 
     1890         * or failure. 
     1891         * 
     1892         * Expected parameters: 
     1893         * 
     1894         * $_POST['copy']            nonzero value means copy, zero or nil for move/rename 
     1895         * 
     1896         * Source filespec: 
     1897         * 
     1898         *   $_POST['directory']     path relative to basedir a.k.a. options['directory'] root 
     1899         * 
     1900         *   $_POST['file']          original name of the file/subdirectory to be renamed/copied 
     1901         * 
     1902         * Destination filespec: 
     1903         * 
     1904         *   $_POST['newDirectory']  path relative to basedir a.k.a. options['directory'] root; 
     1905         *                           target directory where the file must be moved / copied 
     1906         * 
     1907         *   $_POST['name']          target name of the file/subdirectory to be renamed 
     1908         * 
     1909         * Errors will produce a JSON encoded error report, including at least two fields: 
     1910         * 
     1911         * status                    0 for error; nonzero for success 
     1912         * 
     1913         * error                     error message 
     1914         */ 
     1915        protected function onMove() 
     1916        { 
     1917                $emsg = null; 
     1918                $jserr = array( 
     1919                                'status' => 1 
     1920                        ); 
     1921 
     1922                try 
     1923                { 
     1924                        if (!$this->options['move']) 
     1925                                throw new FileManagerException('disabled'); 
     1926 
     1927                        $file_arg = $this->getPOSTparam('file'); 
     1928                        if (empty($file_arg)) 
     1929                                throw new FileManagerException('nofile'); 
     1930 
     1931                        $dir_arg = $this->getPOSTparam('directory'); 
     1932                        $legal_url = $this->rel2abs_legal_url_path($dir_arg); 
     1933                        $legal_url = self::enforceTrailingSlash($legal_url); 
     1934 
     1935                        $filename = pathinfo($file_arg, PATHINFO_BASENAME); 
     1936                        //$legal_url .= $filename; 
     1937                        // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     1938                        $dir = $this->legal_url_path2file_path($legal_url); 
     1939                        $path = $this->legal_url_path2file_path($legal_url . $filename); 
     1940 
     1941                        if (!file_exists($path)) 
     1942                                throw new FileManagerException('nofile'); 
     1943 
     1944                        $is_dir = is_dir($path); 
     1945 
     1946                        $newdir_arg = $this->getPOSTparam('newDirectory'); 
     1947                        $newname_arg = $this->getPOSTparam('name'); 
     1948                        $rename = (empty($newdir_arg) && !empty($newname_arg)); 
     1949 
     1950                        $is_copy = !!$this->getPOSTparam('copy'); 
     1951 
     1952 
     1953                        // note: we do not support copying entire directories, though directory rename/move is okay 
     1954                        if ($is_copy && $is_dir) 
     1955                                throw new FileManagerException('disabled'); 
     1956 
     1957                        if($rename) 
     1958                        { 
     1959                                $fn = 'rename'; 
     1960                                $legal_newurl = $legal_url; 
     1961                                $newdir = $dir; 
     1962 
     1963                                $newname = pathinfo($newname_arg, PATHINFO_BASENAME); 
     1964                                if ($is_dir) 
     1965                                        $newname = $this->getUniqueName(array('filename' => $newname), $newdir);  // a directory has no 'extension' 
     1966                                else 
     1967                                        $newname = $this->getUniqueName($newname, $newdir); 
     1968 
     1969                                if (!$newname) 
     1970                                        throw new FileManagerException('nonewfile'); 
     1971 
     1972                                // when the new name seems to have a different extension, make sure the extension doesn't change after all: 
     1973                                // Note: - if it's only 'case' we're changing here, then exchange the extension instead of appending it. 
     1974                                //       - directories do not have extensions 
     1975                                $extOld = pathinfo($filename, PATHINFO_EXTENSION); 
     1976                                $extNew = pathinfo($newname, PATHINFO_EXTENSION); 
     1977                                if ((!$this->options['allowExtChange'] || (!$is_dir && empty($extNew))) && !empty($extOld) && strtolower($extOld) != strtolower($extNew)) 
     1978                                { 
     1979                                        $newname .= '.' . $extOld; 
     1980                                } 
     1981                        } 
     1982                        else 
     1983                        { 
     1984                                $fn = ($is_copy ? 'copy' : 'rename' /* 'move' */); 
     1985                                $legal_newurl = $this->rel2abs_legal_url_path($newdir_arg); 
     1986                                $legal_newurl = self::enforceTrailingSlash($legal_newurl); 
     1987                                $newdir = $this->legal_url_path2file_path($legal_newurl); 
     1988 
     1989                                if ($is_dir) 
     1990                                        $newname = $this->getUniqueName(array('filename' => $filename), $newdir);  // a directory has no 'extension' 
     1991                                else 
     1992                                        $newname = $this->getUniqueName($filename, $newdir); 
     1993 
     1994                                if (!$newname) 
     1995                                        throw new FileManagerException('nonewfile'); 
     1996                        } 
     1997 
     1998                        $newpath = $this->legal_url_path2file_path($legal_newurl . $newname); 
     1999 
     2000 
     2001                        $fileinfo = array( 
     2002                                        'legal_url' => $legal_url, 
     2003                                        'dir' => $dir, 
     2004                                        'path' => $path, 
     2005                                        'name' => $filename, 
     2006                                        'legal_newurl' => $legal_newurl, 
     2007                                        'newdir' => $newdir, 
     2008                                        'newpath' => $newpath, 
     2009                                        'newname' => $newname, 
     2010                                        'rename' => $rename, 
     2011                                        'is_dir' => $is_dir, 
     2012                                        'function' => $fn 
     2013                                ); 
     2014 
     2015                        if (!empty($this->options['MoveIsAuthorized_cb']) && function_exists($this->options['MoveIsAuthorized_cb']) && !$this->options['MoveIsAuthorized_cb']($this, 'move', $fileinfo)) 
     2016                                throw new FileManagerException('authorized'); 
     2017 
     2018                        $legal_url = $fileinfo['legal_url']; 
     2019                        $dir = $fileinfo['dir']; 
     2020                        $path = $fileinfo['path']; 
     2021                        $filename = $fileinfo['name']; 
     2022                        $legal_newurl = $fileinfo['legal_newurl']; 
     2023                        $newdir = $fileinfo['newdir']; 
     2024                        $newpath = $fileinfo['newpath']; 
     2025                        $newname = $fileinfo['newname']; 
     2026                        $rename = $fileinfo['rename']; 
     2027                        $is_dir = $fileinfo['is_dir']; 
     2028                        $fn = $fileinfo['function']; 
     2029 
     2030                        if($rename) 
     2031                        { 
     2032                                // try to remove the thumbnail related to the original file; don't mind if it doesn't exist 
     2033                                if(!$is_dir) 
     2034                                { 
     2035                                        if (!$this->deleteThumb($legal_url . $filename)) 
     2036                                                throw new FileManagerException('delete_thumbnail_failed'); 
     2037                                } 
     2038                        } 
     2039 
     2040                        if (!@$fn($path, $newpath)) 
     2041                                throw new FileManagerException($fn . '_failed:' . $legal_newurl . ':' . $newname); 
     2042 
     2043                        if (!headers_sent()) header('Content-Type: application/json'); 
     2044 
     2045                        echo json_encode(array( 
     2046                                'status' => 1, 
     2047                                'name' => $newname 
     2048                        )); 
     2049                        return; 
     2050                } 
     2051                catch(FileManagerException $e) 
     2052                { 
     2053                        $emsg = $e->getMessage(); 
     2054                } 
     2055                catch(Exception $e) 
     2056                { 
     2057                        // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating 
     2058                        $emsg = $e->getMessage(); 
     2059                } 
     2060 
     2061                $this->modify_json4exception($jserr, $emsg); 
     2062 
     2063                if (!headers_sent()) header('Content-Type: application/json'); 
     2064 
     2065                // when we fail here, it's pretty darn bad and nothing to it. 
     2066                // just push the error JSON as go. 
     2067                echo json_encode($jserr); 
     2068        } 
     2069 
     2070 
     2071 
     2072 
     2073 
     2074 
     2075 
     2076        /** 
     2077         * Convert a given file spec into a URL pointing at our JiT thumbnail creation/delivery event handler. 
     2078         * 
     2079         * The spec must be an array with these elements: 
     2080         *   'event':       'thumbnail' 
     2081         *   'directory':   URI path to directory of the ORIGINAL file 
     2082         *   'file':        filename of the ORIGINAL file 
     2083         *   'size':        requested thumbnail size (e.g. 48) 
     2084         *   'filter':      optional mime_filter as originally specified by the client 
     2085         *   'type':        'thumb' or 'list': the current type of directory view at the client 
     2086         * 
     2087         * Return the URL string. 
     2088         */ 
     2089        public function mkEventHandlerURL($spec) 
     2090        { 
     2091                // first determine how the client can reach us; assume that's the same URI as he went to right now. 
     2092                $our_handler_url = $_SERVER['SCRIPT_NAME']; 
     2093 
     2094    // Disabled the addition of propagateData here, this is done only in the client 
     2095                if (0 &&  is_array($this->options['URIpropagateData'])) 
     2096                { 
     2097                        // the items in 'spec' always win over any entries in 'URIpropagateData': 
     2098                        $spec = array_merge(array(), $this->options['URIpropagateData'], $spec); 
     2099                } 
     2100 
     2101                // next, construct the query part of the URI: 
     2102                $qstr = http_build_query_ex($spec, null, '&', null, PHP_QUERY_RFC3986); 
     2103 
     2104                return $our_handler_url . '?' . $qstr; 
     2105        } 
     2106 
     2107 
     2108 
     2109        /** 
     2110         * Produce a HTML snippet detailing the given file in the JSON 'content' element; place additional info 
     2111         * in the JSON elements 'thumbnail', 'thumbnail48', 'thumbnail250', 'width', 'height', ... 
     2112         * 
     2113         * Return an augmented JSON array. 
     2114         * 
     2115         * Throw an exception on error. 
     2116         */ 
     2117        public function extractDetailInfo($json, $legal_url, $file, $mime, $mime_filter) 
     2118        { 
     2119                $url = $this->legal2abs_url_path($legal_url); 
     2120                $filename = pathinfo($url, PATHINFO_BASENAME); 
     2121 
     2122                $isdir = !is_file($file); 
     2123                if (!$isdir) 
     2124                { 
     2125                        $iconspec = $filename; 
     2126                } 
     2127                else if (is_dir($file)) 
     2128                { 
     2129                        $mime = 'text/directory'; 
     2130                        $iconspec = ($filename == '..' ? 'is.dir_up' : 'is.dir'); 
     2131                } 
     2132                else 
     2133                { 
     2134                        // simply do NOT list anything that we cannot cope with. 
     2135                        // That includes clearly inaccessible files (and paths) with non-ASCII characters: 
     2136                        // PHP5 and below are a real mess when it comes to handling Unicode filesystems 
     2137                        // (see the php.net site too: readdir / glob / etc. user comments and the official 
     2138                        // notice that PHP will support filesystem UTF-8/Unicode only when PHP6 is released. 
     2139                        // 
     2140                        // Big, fat bummer! 
     2141                        throw new FileManagerException('nofile'); 
     2142                } 
     2143 
     2144                if (FileManagerUtility::startsWith($mime, 'image/')) 
     2145                { 
     2146                        /* 
     2147                         * offload the thumbnailing process to another event ('event=thumbnail') to be fired by the client 
     2148                         * when it's time to render the thumbnail: the offloading helps us tremendously in coping with large 
     2149                         * directories: 
     2150                         * WE simply assume the thumbnail will be there, so we don't even need to check for its existence 
     2151                         * (which saves us one more file_exists() per item at the very least). And when it doesn't, that's 
     2152                         * for the event=thumbnail handler to worry about (creating the thumbnail on demand or serving 
     2153                         * a generic icon image instead). 
     2154                         */ 
     2155                        $thumb48 = $this->mkEventHandlerURL(array( 
     2156                                        'event' => 'thumbnail', 
     2157                                        // directory and filename of the ORIGINAL image should follow next: 
     2158                                        'directory' => pathinfo($legal_url, PATHINFO_DIRNAME), 
     2159                                        'file' => pathinfo($legal_url, PATHINFO_BASENAME), 
     2160                                        'size' => 48,          // thumbnail suitable for 'view/type=thumb' list views 
     2161                                        'filter' => $mime_filter 
     2162                                )); 
     2163                        $thumb250 = $this->mkEventHandlerURL(array( 
     2164                                        'event' => 'thumbnail', 
     2165                                        // directory and filename of the ORIGINAL image should follow next: 
     2166                                        'directory' => pathinfo($legal_url, PATHINFO_DIRNAME), 
     2167                                        'file' => pathinfo($legal_url, PATHINFO_BASENAME), 
     2168                                        'size' => 250,         // thumbnail suitable for 'view/type=thumb' list views 
     2169                                        'filter' => $mime_filter 
     2170                                )); 
     2171                } 
     2172                else 
     2173                { 
     2174                        $thumb48 = $this->getIcon($iconspec, false); 
     2175                        $thumb250 = $thumb48; 
     2176                } 
     2177                $icon = $this->getIcon($iconspec, true); 
     2178 
     2179                $json = array_merge(array( 
     2180                                //'status' => 1, 
     2181                                //'mimetype' => $mime, 
     2182                                'content' => self::compressHTML('<div class="margin"> 
     2183                                        ${nopreview} 
     2184                                </div>') 
     2185                        ), 
     2186                        (is_array($json) ? $json : array()), 
     2187                        array( 
     2188                                'path' => FileManagerUtility::rawurlencode_path($url), 
     2189                                'name' => preg_replace('/[^ -~]/', '?', $filename),       // HACK/TWEAK: PHP5 and below are completely b0rked when it comes to international filenames   :-( 
     2190                                'date' => date($this->options['dateFormat'], @filemtime($file)), 
     2191                                'mime' => $mime, 
     2192                                //'thumbnail' => $thumb, 
     2193                                'thumbnail48' => $thumb48, 
     2194                                'thumbnail250' => $thumb250, 
     2195                                'icon' => FileManagerUtility::rawurlencode_path($icon), 
     2196                                'size' => @filesize($file) 
     2197                        )); 
     2198 
     2199 
     2200                // getID3 is slower as it *copies* the image to the temp dir before processing: see GetDataImageSize(). 
     2201                // This is done as getID3 can also analyze *embedded* images, for which this approach is required. 
     2202                $getid3 = new getID3(); 
     2203                $getid3->encoding = 'UTF-8'; 
     2204                $getid3->Analyze($file); 
     2205 
     2206                $content = null; 
     2207 
     2208                if (FileManagerUtility::startsWith($mime, 'image/')) 
     2209                { 
     2210                        // generates a random number to put on the end of the image, to prevent caching 
     2211                        //$randomImage = '?'.md5(uniqid(rand(),1)); 
     2212 
     2213                        //$size = @getimagesize($file); 
     2214                        //// check for badly formatted image files (corruption); we'll handle the overly large ones next 
     2215                        //if (!$size) 
     2216                        //  throw new FileManagerException('corrupt_img:' . $url); 
     2217 
     2218                        $sw_make = $this->getID3infoItem($getid3, null, 'jpg', 'exif', 'IFD0', 'Software'); 
     2219                        $time_make = $this->getID3infoItem($getid3, null, 'jpg', 'exif', 'IFD0', 'DateTime'); 
     2220 
     2221                        $width = $this->getID3infoItem($getid3, 0, 'video', 'resolution_x'); 
     2222                        $height = $this->getID3infoItem($getid3, 0, 'video', 'resolution_y'); 
     2223                        $json['width'] = $width; 
     2224                        $json['height'] = $height; 
     2225 
     2226                        $content = '<dl> 
     2227                                        <dt>${width}</dt><dd>' . $width . 'px</dd> 
     2228                                        <dt>${height}</dt><dd>' . $height . 'px</dd> 
     2229                                </dl>'; 
     2230                        if (!empty($sw_make) || !empty($time_make)) 
     2231                        { 
     2232                                $content .= '<p>Made with ' . (empty($sw_make) ? '???' : $sw_make) . ' @ ' . (empty($time_make) ? '???' : $time_make) . '</p>'; 
     2233                        } 
     2234                        $content .= ' 
     2235                                <h2>${preview}</h2> 
     2236                                '; 
     2237 
     2238                        $emsg = null; 
     2239                        try 
     2240                        { 
     2241                                $thumbfile = $this->getThumb($legal_url, $file, 250, 250); 
     2242                                /* 
     2243                                 * the thumbnail may be produced now, but we want to stay in control when the thumbnail is 
     2244                                 * fetched by the client, so we force them to travel through this backend. 
     2245                                 */ 
     2246                                $enc_thumbfile = $thumb250; 
     2247                        } 
     2248                        catch (Exception $e) 
     2249                        { 
     2250                                $emsg = $e->getMessage(); 
     2251                                $thumbfile = $this->getIconForError($emsg, $legal_url, false); 
     2252                                $enc_thumbfile = FileManagerUtility::rawurlencode_path($thumbfile); 
     2253 
     2254                                $json['thumbnail48'] = $thumbfile; 
     2255                                $json['thumbnail250'] = $thumbfile; 
     2256                        } 
     2257 
     2258                        // get the size of the thumbnail/icon: the <img> is styled with width and height to ensure the background 'loader' image is shown correctly: 
     2259                        $tnpath = $this->url_path2file_path($thumbfile); 
     2260                        $tninf = @getimagesize($tnpath); 
     2261 
     2262                        $json['tn_width'] = $tninf[0]; 
     2263                        $json['tn_height'] = $tninf[1]; 
     2264 
     2265                        $content .= '<a href="' . FileManagerUtility::rawurlencode_path($url) . '" data-milkbox="single" title="' . htmlentities($filename, ENT_QUOTES, 'UTF-8') . '"> 
     2266                                                   <img src="' . $enc_thumbfile . '" class="preview" alt="preview" style="width: ' . $tninf[0] . 'px; height: ' . $tninf[1] . 'px;" /> 
     2267                                                 </a>'; 
     2268                        if (!empty($emsg)) 
     2269                        { 
     2270                                // use the abilities of modify_json4exception() to munge/format the exception message: 
     2271                                $jsa = array(); 
     2272                                $this->modify_json4exception($jsa, $emsg); 
     2273                                $content .= "\n" . '<p class="err_info">' . $jsa['error'] . '</p>'; 
     2274                        } 
     2275                        if (!empty($emsg) && strpos($emsg, 'img_will_not_fit') !== false) 
     2276                        { 
     2277                                $earr = explode(':', $e->getMessage(), 2); 
     2278                                $content .= "\n" . '<p class="tech_info">Estimated minimum memory requirements to create thumbnails for this image: ' . $earr[1] . '</p>'; 
     2279                        } 
     2280 
     2281                        if (DEVELOPMENT) 
     2282                        { 
     2283                                $finfo = Image::guestimateRequiredMemorySpace($file); 
     2284                                if (!empty($finfo['usage_guestimate']) && !empty($finfo['usage_min_advised'])) 
     2285                                { 
     2286                                        $content .= "\n" . '<p class="tech_info">memory used: ' . number_format(memory_get_peak_usage() / 1E6, 1) . ' MB / estimated: ' . number_format($finfo['usage_guestimate'] / 1E6, 1) . ' MB / suggested: ' . number_format($finfo['usage_min_advised'] / 1E6, 1) . ' MB</p>'; 
     2287                                } 
     2288                        } 
     2289 
     2290                        $exif_data = $this->getID3infoItem($getid3, null, 'jpg', 'exif'); 
     2291                        try 
     2292                        { 
     2293                                if (!empty($exif_data)) 
     2294                                { 
     2295                                        /* 
     2296                                         * before dumping the EXIF data array (which may carry binary content and MAY CRASH the json_encode()r >:-(( 
     2297                                         * we filter it to prevent such crashes and oddly looking (diagnostic) presentation of values. 
     2298                                         */ 
     2299                                        self::clean_EXIF_results($exif_data); 
     2300                                        ob_start(); 
     2301                                                var_dump($exif_data); 
     2302                                        $dump = ob_get_clean(); 
     2303                                        $content .= $dump; 
     2304                                } 
     2305                        } 
     2306                        catch (Exception $e) 
     2307                        { 
     2308                                // use the abilities of modify_json4exception() to munge/format the exception message: 
     2309                                $jsa = array('error' => ''); 
     2310                                $this->modify_json4exception($jsa, $e->getMessage()); 
     2311                                $content .= "\n" . '<p class="err_info">' . $jsa['error'] . '</p>'; 
     2312                        } 
     2313                } 
     2314                elseif (FileManagerUtility::startsWith($mime, 'text/') || $mime == 'application/x-javascript') 
     2315                { 
     2316                        // text preview: 
     2317                        $filecontent = @file_get_contents($file, false, null, 0); 
     2318                        if ($filecontent === false) 
     2319                                throw new FileManagerException('nofile'); 
     2320 
     2321                        if (!FileManagerUtility::isBinary($filecontent)) 
     2322                        { 
     2323                                $content = '<div class="textpreview"><pre>' . str_replace(array('$', "\t"), array('&#36;', '&nbsp;&nbsp;'), htmlentities($filecontent, ENT_NOQUOTES, 'UTF-8')) . '</pre></div>'; 
     2324                        } 
     2325                        // else: fall back to 'no preview available' 
     2326                } 
     2327                elseif ($mime == 'application/zip') 
     2328                { 
     2329                        $out = array(array(), array()); 
     2330                        $info = $this->getID3infoItem($getid3, null, 'zip', 'files'); 
     2331                        if (is_array($info)) 
     2332                        { 
     2333                                foreach ($info as $name => $size) 
     2334                                { 
     2335                                        $isdir = is_array($size) ? true : false; 
     2336                                        $out[($isdir) ? 0 : 1][$name] = '<li><a><img src="' . FileManagerUtility::rawurlencode_path($this->getIcon($name, true)) . '" alt="" /> ' . $name . '</a></li>'; 
     2337                                } 
     2338                                natcasesort($out[0]); 
     2339                                natcasesort($out[1]); 
     2340                                $content = '<ul>' . implode(array_merge($out[0], $out[1])) . '</ul>'; 
     2341                        } 
     2342                } 
     2343                elseif ($mime == 'application/x-shockwave-flash') 
     2344                { 
     2345                        $info = $this->getID3infoItem($getid3, null, 'swf', 'header'); 
     2346                        if (is_array($info)) 
     2347                        { 
     2348                                // Note: preview data= urls were formatted like this in CCMS: 
     2349                                // $this->options['assetBasePath'] . 'dewplayer.swf?mp3=' . rawurlencode($url) . '&volume=30' 
     2350 
     2351                                $width = $this->getID3infoItem($getid3, 0, 'swf', 'header', 'frame_width') / 10; 
     2352                                $height = $this->getID3infoItem($getid3, 0, 'swf', 'header', 'frame_height') / 10; 
     2353                                $json['width'] = $width; 
     2354                                $json['height'] = $height; 
     2355 
     2356                                $content = '<dl> 
     2357                                                <dt>${width}</dt><dd>' . $width . 'px</dd> 
     2358                                                <dt>${height}</dt><dd>' . $height . 'px</dd> 
     2359                                                <dt>${length}</dt><dd>' . round($this->getID3infoItem($getid3, 0, 'swf', 'header', 'length') / $this->getID3infoItem($getid3, 25, 'swf', 'header', 'frame_count')) . 's</dd> 
     2360                                        </dl> 
     2361                                        <h2>${preview}</h2> 
     2362                                        <div class="object"> 
     2363                                                <object type="application/x-shockwave-flash" data="'.FileManagerUtility::rawurlencode_path($url).'" width="500" height="400"> 
     2364                                                        <param name="scale" value="noscale" /> 
     2365                                                        <param name="movie" value="'.FileManagerUtility::rawurlencode_path($url).'" /> 
     2366                                                </object> 
     2367                                        </div>'; 
     2368                        } 
     2369                } 
     2370                elseif (FileManagerUtility::startsWith($mime, 'audio/')) 
     2371                { 
     2372                        getid3_lib::CopyTagsToComments($getid3->info); 
     2373 
     2374                        $dewplayer = FileManagerUtility::rawurlencode_path($this->options['assetBasePath'] . 'dewplayer.swf'); 
     2375 
     2376                        $content = '<dl> 
     2377                                        <dt>${title}</dt><dd>' . $this->getID3infoItem($getid3, '???', 'comments', 'title', 0) . '</dd> 
     2378                                        <dt>${artist}</dt><dd>' . $this->getID3infoItem($getid3, '???', 'comments', 'artist', 0) . '</dd> 
     2379                                        <dt>${album}</dt><dd>' . $this->getID3infoItem($getid3, '???', 'comments', 'album', 0) . '</dd> 
     2380                                        <dt>${length}</dt><dd>' . $this->getID3infoItem($getid3, '???', 'playtime_string') . '</dd> 
     2381                                        <dt>${bitrate}</dt><dd>' . round($this->getID3infoItem($getid3, 0, 'bitrate') / 1000) . 'kbps</dd> 
     2382                                </dl> 
     2383                                <h2>${preview}</h2> 
     2384                                <div class="object"> 
     2385                                        <object type="application/x-shockwave-flash" data="' . $dewplayer . '" width="200" height="20" id="dewplayer" name="dewplayer"> 
     2386                                                <param name="wmode" value="transparent" /> 
     2387                                                <param name="movie" value="' . $dewplayer . '" /> 
     2388                                                <param name="flashvars" value="mp3=' . FileManagerUtility::rawurlencode_path($url) . '&amp;volume=50&amp;showtime=1" /> 
     2389                                        </object> 
     2390                                </div>'; 
     2391                } 
     2392                elseif (FileManagerUtility::startsWith($mime, 'video/')) 
     2393                { 
     2394                        $dewplayer = FileManagerUtility::rawurlencode_path($this->options['assetBasePath'] . 'dewplayer.swf'); 
     2395 
     2396                        $a_fmt = $this->getID3infoItem($getid3, '???', 'audio', 'dataformat'); 
     2397                        $a_samplerate = $this->getID3infoItem($getid3, 0, 'audio', 'sample_rate') / 1000; 
     2398                        $a_bitrate = round($this->getID3infoItem($getid3, 0, 'audio', 'bitrate') / 1000); 
     2399                        $a_bitrate_mode = $this->getID3infoItem($getid3, '???', 'audio', 'bitrate_mode'); 
     2400                        $a_channels = $this->getID3infoItem($getid3, 0, 'audio', 'channels'); 
     2401                        $a_codec = $this->getID3infoItem($getid3, '', 'audio', 'codec'); 
     2402                        $a_streams = $this->getID3infoItem($getid3, '???', 'audio', 'streams'); 
     2403                        $a_streamcount = (is_array($a_streams) ? count($a_streams) : 0); 
     2404 
     2405                        $v_fmt = $this->getID3infoItem($getid3, '???', 'video', 'dataformat'); 
     2406                        $v_bitrate = round($this->getID3infoItem($getid3, 0, 'video', 'bitrate') / 1000); 
     2407                        $v_bitrate_mode = $this->getID3infoItem($getid3, '???', 'video', 'bitrate_mode'); 
     2408                        $v_framerate = $this->getID3infoItem($getid3, '???', 'video', 'frame_rate'); 
     2409                        $v_width = $this->getID3infoItem($getid3, '???', 'video', 'resolution_x'); 
     2410                        $v_height = $this->getID3infoItem($getid3, '???', 'video', 'resolution_y'); 
     2411                        $v_par = $this->getID3infoItem($getid3, 1.0, 'video', 'pixel_aspect_ratio'); 
     2412                        $v_codec = $this->getID3infoItem($getid3, '', 'video', 'codec'); 
     2413 
     2414                        $g_bitrate = round($this->getID3infoItem($getid3, 0, 'bitrate') / 1000); 
     2415                        $g_playtime_str = $this->getID3infoItem($getid3, '???', 'playtime_string'); 
     2416 
     2417                        $content = '<dl> 
     2418                                        <dt>Audio</dt><dd>' . $a_fmt . (!empty($a_codec) ? ' (' . $a_codec . ')' : '') . 
     2419                                                                                (!empty($a_channels) ? ($a_channels === 1 ? ' (mono)' : $a_channels === 2 ? ' (stereo)' : ' (' . $a_channels . ' channels)') : '') . 
     2420                                                                                ': ' . $a_samplerate . ' kHz @ ' . $a_bitrate . ' kbps (' . strtoupper($a_bitrate_mode) . ')' . 
     2421                                                                                ($a_streamcount > 1 ? ' (' . $a_streamcount . ' streams)' : '') . 
     2422                                                                '</dd> 
     2423                                        <dt>Video</dt><dd>' . $v_fmt . (!empty($v_codec) ? ' (' . $v_codec . ')' : '') .  ': ' . $v_framerate . ' fps @ ' . $v_bitrate . ' kbps (' . strtoupper($v_bitrate_mode) . ')' . 
     2424                                                                                ($v_par != 1.0 ? ', PAR: ' . $v_par : '') . 
     2425                                                                '</dd> 
     2426                                        <dt>${width}</dt><dd>' . $v_width . 'px</dd> 
     2427                                        <dt>${height}</dt><dd>' . $v_height . 'px</dd> 
     2428                                        <dt>${length}</dt><dd>' . $g_playtime_str . '</dd> 
     2429                                        <dt>${bitrate}</dt><dd>' . $g_bitrate . 'kbps</dd> 
     2430                                </dl> 
     2431                                <h2>${preview}</h2> 
     2432                                <div class="object"> 
     2433                                        <object type="application/x-shockwave-flash" data="' . $dewplayer . '" width="200" height="20" id="dewplayer" name="dewplayer"> 
     2434                                                <param name="wmode" value="transparent" /> 
     2435                                                <param name="movie" value="' . $dewplayer . '" /> 
     2436                                                <param name="flashvars" value="mp3=' . FileManagerUtility::rawurlencode_path($url) . '&amp;volume=50&amp;showtime=1" /> 
     2437                                        </object> 
     2438                                </div>'; 
     2439                } 
     2440                else 
     2441                { 
     2442                        // else: fall back to 'no preview available' 
     2443                        if (!empty($getid3->info) && empty($getid3->info['error'])) 
     2444                        { 
     2445                                try 
     2446                                { 
     2447                                        ob_start(); 
     2448                                                var_dump($getid3->info); 
     2449                                        $dump = ob_get_clean(); 
     2450                                        // $dump may dump object IDs and other binary stuff, which will completely b0rk json_encode: make it palatable: 
     2451 
     2452                                        // strip the NULs out: 
     2453                                        $dump = str_replace('&#0;', '?', $dump); 
     2454                                        //$dump = html_entity_decode(strip_tags($dump), ENT_QUOTES, 'UTF-8'); 
     2455                                        //@file_put_contents('getid3.raw.log', $dump); 
     2456                                        // since the regex matcher leaves NUL bytes alone, we do those above in undecoded form; the rest is treated here 
     2457                                        $dump = preg_replace("/[^ -~\n\r\t]/", '?', $dump); // remove everything outside ASCII range; some of the high byte values seem to crash json_encode()! 
     2458                                        // and reduce long sequences of unknown charcodes: 
     2459                                        $dump = preg_replace('/\?{8,}/', '???????', $dump); 
     2460                                        //$dump = html_entity_encode(strip_tags($dump), ENT_NOQUOTES, 'UTF-8'); 
     2461 
     2462                                        $content = '<div class="margin"> 
     2463                                                                <h2>${preview}</h2> 
     2464                                                                <pre>' . "\n" . $dump . "\n" . '</pre></div>'; 
     2465                                        //@file_put_contents('getid3.log', $dump); 
     2466                                } 
     2467                                catch(Exception $e) 
     2468                                { 
     2469                                        // ignore 
     2470                                        $content = '<div class="margin"> 
     2471                                                                ${nopreview} 
     2472                                                                <p class="err_info">' . $e->getMessage() . '</p> 
     2473                                                        </div>'; 
     2474                                } 
     2475                        } 
     2476                        else 
     2477                        { 
     2478                                $content = '<div class="margin"> 
     2479                                                        ${nopreview} 
     2480                                                        <p class="err_info">' . implode(', ', $getid3->info['error']) . '</p> 
     2481                                                </div>'; 
     2482                        } 
     2483                } 
     2484 
     2485                if (!empty($content)) 
     2486                { 
     2487                        $json['content'] = self::compressHTML($content); 
     2488                } 
     2489 
     2490                return $json; 
     2491        } 
     2492 
     2493        /** 
     2494         * Traverse the getID3 info[] array tree and fetch the item pointed at by the variable number of indices specified 
     2495         * as additional parameters to this function. 
     2496         * 
     2497         * Return the default value when the indicated element does not exist in the info[] set; otherwise return the located item. 
     2498         * 
     2499         * The purpose of this method is to act as a safe go-in-between for the fileManager to collect arbitrary getID3 data and 
     2500         * not get a PHP error when some item in there does not exist. 
     2501         */ 
     2502        public /* static */ function getID3infoItem($getid3_obj, $default_value /* , ... */ ) 
     2503        { 
     2504                $rv = false; 
     2505                $o = $getid3_obj->info; 
     2506                $argc = func_num_args(); 
     2507 
     2508                for ($i = 2; $i < $argc; $i++) 
     2509                { 
     2510                        if (!is_array($o)) 
     2511                        { 
     2512                                return $default_value; 
     2513                        } 
     2514 
     2515                        $index = func_get_arg($i); 
     2516                        if (array_key_exists($index, $o)) 
     2517                        { 
     2518                                $o = $o[$index]; 
     2519                        } 
     2520                        else 
     2521                        { 
     2522                                return $default_value; 
     2523                        } 
     2524                } 
     2525                return $o; 
     2526        } 
     2527 
     2528        // helper function for clean_EXIF_results() as PHP < 5.3 lacks lambda functions 
     2529        protected static function __clean_EXIF_results(&$value, $key) 
     2530        { 
     2531                if (is_string($value)) 
     2532                { 
     2533                        if (FileManagerUtility::isBinary($value)) 
     2534                        { 
     2535                                $value = '(binary data... length = ' . strlen($value) . ')'; 
     2536                        } 
     2537                } 
     2538        } 
     2539 
     2540        protected static function clean_EXIF_results(&$arr) 
     2541        { 
     2542                // see http://nl2.php.net/manual/en/function.array-walk-recursive.php#81835 
     2543                // --> we don't mind about it because we're not worried about the references occurring in here, now or later. 
     2544                // Indeed, that does assume we (as in 'we' being this particular function!) know about how the 
     2545                // data we process will be used. Risky, but fine with me. Hence the 'protected'. 
     2546                array_walk_recursive($arr, 'self::__clean_EXIF_results'); 
     2547        } 
     2548 
     2549        /** 
     2550         * Delete a file or directory, inclusing subdirectories and files. 
     2551         * 
     2552         * Return TRUE on success, FALSE when an error occurred. 
     2553         * 
     2554         * Note that the routine will try to percevere and keep deleting other subdirectories 
     2555         * and files, even when an error occurred for one or more of the subitems: this is 
     2556         * a best effort policy. 
     2557         */ 
     2558        protected function unlink($legal_url, $mime_filters) 
     2559        { 
     2560                $rv = true; 
     2561 
     2562                // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: 
     2563                $file = $this->legal_url_path2file_path($legal_url); 
     2564 
     2565                if(is_dir($file)) 
     2566                { 
     2567                        $dir = self::enforceTrailingSlash($file); 
     2568                        $url = self::enforceTrailingSlash($legal_url); 
     2569                        $files = $this->scandir($dir); 
     2570                        foreach ($files as $f) 
     2571                        { 
     2572                                if(in_array($f, array('.','..'))) 
     2573                                        continue; 
     2574 
     2575                                $rv2 = $this->unlink($url . $f, $mime_filters); 
     2576                                if ($rv2) 
     2577                                        $rv &= $this->deleteThumb($url . $f); 
     2578                                else 
     2579                                        $rv = false; 
     2580                        } 
     2581 
     2582                        $rv &= @rmdir($file); 
     2583                } 
     2584                else if (file_exists($file)) 
     2585                { 
     2586                        if (is_file($file)) 
     2587                        { 
     2588                                $mime = $this->getMimeType($file); 
     2589                                if (!$this->IsAllowedMimeType($mime, $mime_filters)) 
     2590                                        return false; 
     2591                        } 
     2592 
     2593                        $rv2 = @unlink($file); 
     2594                        if ($rv2) 
     2595                                $rv &= $this->deleteThumb($legal_url); 
     2596                        else 
     2597                                $rv = false; 
     2598                } 
     2599                return $rv; 
     2600        } 
     2601 
     2602        /** 
     2603         * glob() wrapper: accepts the same options as Tooling.php::safe_glob() 
     2604         * 
     2605         * However, this method will also ensure the '..' directory entry is only returned, 
     2606         * even while asked for, when the parent directory can be legally traversed by the FileManager. 
     2607         * 
     2608         * Always return an array (possibly empty) 
     2609         * 
     2610         * IMPORTANT: this function GUARANTEES that, when present at all, the double-dot '..' 
     2611         *            entry is the very last entry in the array. 
     2612         *            This guarantee is important as onView() et al depend on it. 
     2613         */ 
     2614        protected function scandir($dir, $filemask = '*', $see_thumbnail_dir = false) 
     2615        { 
     2616                // list files, except the thumbnail folder itself or any file in it: 
     2617                $dir = self::enforceTrailingSlash($dir); 
     2618 
     2619                $just_below_thumbnail_dir = false; 
     2620                if (!$see_thumbnail_dir) 
     2621                { 
     2622                        $tnpath = $this->url_path2file_path($this->options['thumbnailPath']); 
     2623                        if (FileManagerUtility::startswith($dir, $tnpath)) 
     2624                                return false; 
     2625 
     2626                        $tnparent = $this->url_path2file_path(self::getParentDir($this->options['thumbnailPath'])); 
     2627                        $just_below_thumbnail_dir = ($dir == $tnparent); 
     2628 
     2629                        $tndir = basename(substr($this->options['thumbnailPath'], 0, -1)); 
     2630                } 
     2631 
     2632                $at_basedir = ($this->url_path2file_path($this->options['directory']) == $dir); 
     2633 
     2634 
     2635                $files = safe_glob($dir . $filemask, GLOB_NODOTS | GLOB_NOSORT); 
     2636 
     2637 
     2638                if ($just_below_thumbnail_dir) 
     2639                { 
     2640                        $f = array(); 
     2641                        foreach($files as $file) 
     2642                        { 
     2643                                if ($file !== $tndir) 
     2644                                        $f[] = $file; 
     2645                        } 
     2646                        unset($files); 
     2647                        $files = $f; 
     2648                } 
     2649 
     2650                if (!$at_basedir) 
     2651                { 
     2652                        $files[] = '..'; 
     2653                } 
     2654 
     2655                return $files; 
     2656        } 
     2657 
     2658        /** 
     2659         * Make a cleaned-up, unique filename 
     2660         * 
     2661         * Return the file (dir + name + ext), or a unique, yet non-existing, variant thereof, where the filename 
     2662         * is appended with a '_' and a number, e.g. '_1', when the file itself already exists in the given 
     2663         * directory. The directory part of the returned value equals $dir. 
     2664         * 
     2665         * Return NULL when $file is empty or when the specified directory does not reside within the 
     2666         * directory tree rooted by options['directory'] 
     2667         * 
     2668         * Note that the given filename will be converted to a legal filename, containing a filesystem-legal 
     2669         * subset of ASCII characters only, before being used and returned by this function. 
     2670         * 
     2671         * @param mixed $fileinfo     either a string containing a filename+ext or an array as produced by pathinfo(). 
     2672         * @daram string $dir         path pointing at where the given file may exist. 
     2673         * 
     2674         * @return a filepath consisting of $dir and the cleaned up and possibly sequenced filename and file extension 
     2675         *         as provided by $fileinfo. 
     2676         */ 
     2677        protected function getUniqueName($fileinfo, $dir) 
     2678        { 
     2679                $dir = self::enforceTrailingSlash($dir); 
     2680 
     2681                if (is_string($fileinfo)) 
     2682                { 
     2683                        $fileinfo = pathinfo($fileinfo); 
     2684                } 
     2685 
     2686                if (!is_array($fileinfo) || !$fileinfo['filename']) return null; 
     2687 
     2688 
     2689                /* 
     2690                 * since 'pagetitle()' is used to produce a unique, non-existing filename, we can forego the dirscan 
     2691                 * and simply check whether the constructed filename/path exists or not and bump the suffix number 
     2692                 * by 1 until it does not, thus quickly producing a unique filename. 
     2693                 * 
     2694                 * This is faster than using a dirscan to collect a set of existing filenames and feeding them as 
     2695                 * an option array to pagetitle(), particularly for large directories. 
     2696                 */ 
     2697                $filename = FileManagerUtility::pagetitle($fileinfo['filename'], null, '-_., []()~!@+' /* . '#&' */, '-_,~@+#&'); 
     2698                if (!$filename) 
     2699                        return null; 
     2700 
     2701                // also clean up the extension: only allow alphanumerics in there! 
     2702                $ext = FileManagerUtility::pagetitle(!empty($fileinfo['extension']) ? $fileinfo['extension'] : null); 
     2703                $ext = (!empty($ext) ? '.' . $ext : null); 
     2704                // make sure the generated filename is SAFE: 
     2705                $fname = $filename . $ext; 
     2706                $file = $dir . $fname; 
     2707                if (file_exists($file)) 
     2708                { 
     2709                        /* 
     2710                         * make a unique name. Do this by postfixing the filename with '_X' where X is a sequential number. 
     2711                         * 
     2712                         * Note that when the input name is already such a 'sequenced' name, the sequence number is 
     2713                         * extracted from it and sequencing continues from there, hence input 'file_5' would, if it already 
     2714                         * existed, thus be bumped up to become 'file_6' and so on, until a filename is found which 
     2715                         * does not yet exist in the designated directory. 
     2716                         */ 
     2717                        $i = 1; 
     2718                        if (preg_match('/^(.*)_([1-9][0-9]*)$/', $filename, $matches)) 
     2719                        { 
     2720                                $i = intval($matches[2]); 
     2721                                if ('P'.$i != 'P'.$matches[2] || $i > 100000) 
     2722                                { 
     2723                                        // very large number: not a sequence number! 
     2724                                        $i = 1; 
     2725                                } 
     2726                                else 
     2727                                { 
     2728                                        $filename = $matches[1]; 
     2729                                } 
     2730                        } 
     2731                        do 
     2732                        { 
     2733                                $fname = $filename . ($i ? '_' . $i : '') . $ext; 
     2734                                $file = $dir . $fname; 
     2735                                $i++; 
     2736                        } while (file_exists($file)); 
     2737                } 
     2738 
     2739                // $fname is now guaranteed to NOT exist in the given directory 
     2740                return $fname; 
     2741        } 
     2742 
     2743        /** 
     2744         * Returns the URI path to the apropriate icon image for the given file / directory. 
     2745         * 
     2746         * NOTES: 
     2747         * 
     2748         * 1) any $path with an 'extension' of '.dir' is assumed to be a directory. 
     2749         * 
     2750         * 2) This method specifically does NOT check whether the given path exists or not: it just looks at 
     2751         *    the filename extension passed to it, that's all. 
     2752         * 
     2753         * Note #2 is important as this enables this function to also serve as icon fetcher for ZIP content viewer, etc.: 
     2754         * after all, those files do not exist physically on disk themselves! 
     2755         */ 
     2756        public function getIcon($file, $smallIcon) 
     2757        { 
     2758                $ext = pathinfo($file, PATHINFO_EXTENSION); 
     2759 
     2760                $largeDir = (!$smallIcon ? 'Large/' : ''); 
     2761                $url_path = $this->options['assetBasePath'] . 'Images/Icons/' .$largeDir.$ext.'.png'; 
     2762                $path = (is_file($this->url_path2file_path($url_path))) 
     2763                        ? $url_path 
     2764                        : $this->options['assetBasePath'] . 'Images/Icons/'.$largeDir.'default.png'; 
     2765 
     2766                return $path; 
     2767        } 
     2768 
     2769        /** 
     2770         * Return the path to the thumbnail of the specified image, the thumbnail having its 
     2771         * width and height limited to $width pixels. 
     2772         * 
     2773         * When the thumbnail image does not exist yet, it will created on the fly. 
     2774         * 
     2775         * @param string $legal_url    the LEGAL URL path to the original image. Is used solely 
     2776         *                             to generate a suitable thumbnail filename. 
     2777         * 
     2778         * @param string $path         filesystem path to the original image. Is used to derive 
     2779         *                             the thumbnail content from. 
     2780         * 
     2781         * @param integer $width       the maximum number of pixels for width of the 
     2782         *                             thumbnail. 
     2783         * 
     2784         * @param integer $height      the maximum number of pixels for height of the 
     2785         *                             thumbnail. 
     2786         */ 
     2787        public function getThumb($legal_url, $path, $width, $height) 
     2788        { 
     2789                $thumb = $this->generateThumbName($legal_url, $width); 
     2790                $thumbPath = $this->url_path2file_path($this->options['thumbnailPath'] . $thumb); 
     2791                if (!is_file($thumbPath)) 
     2792                { 
     2793                        if (!file_exists(dirname($thumbPath))) 
     2794                        { 
     2795                                @mkdir(dirname($thumbPath), $this->options['chmod'], true); 
     2796                        } 
     2797                        $img = new Image($path); 
     2798                        // generally save as lossy / lower-Q jpeg to reduce filesize, unless orig is PNG/GIF, higher quality for smaller thumbnails: 
     2799                        $img->resize($width,$height)->save($thumbPath, min(98, max(MTFM_THUMBNAIL_JPEG_QUALITY, MTFM_THUMBNAIL_JPEG_QUALITY + 0.15 * (250 - min($width, $height)))), true); 
     2800                        unset($img); 
     2801                } 
     2802                return $this->options['thumbnailPath'] . $thumb; 
     2803        } 
     2804 
     2805        /** 
     2806         * Assitant function which produces the best possible icon image path for the given error/exception message. 
     2807         */ 
     2808        public function getIconForError($emsg, $original_filename, $small_icon) 
     2809        { 
     2810                if (empty($emsg)) 
     2811                { 
     2812                        // just go and pick the extension-related icon for this one; nothing is wrong today, it seems. 
     2813                        $thumb_path = (!empty($original_filename) ? $original_filename : 'is.default-missing'); 
     2814                } 
     2815                else 
     2816                { 
     2817                        $thumb_path = 'is.default-error'; 
     2818 
     2819                        if (strpos($emsg, 'img_will_not_fit') !== false) 
     2820                        { 
     2821                                $thumb_path = 'is.oversized_img'; 
     2822                        } 
     2823                        else if (strpos($emsg, 'nofile') !== false) 
     2824                        { 
     2825                                $thumb_path = 'is.default-missing'; 
     2826                        } 
     2827                        else if (strpos($emsg, 'unsupported_imgfmt') !== false) 
     2828                        { 
     2829                                // just go and pick the extension-related icon for this one; nothing seriously wrong here. 
     2830                                $thumb_path = (!empty($original_filename) ? $original_filename : $thumb_path); 
     2831                        } 
     2832                        else if (strpos($emsg, 'image') !== false) 
     2833                        { 
     2834                                $thumb_path = 'badly.broken_img'; 
     2835                        } 
     2836                } 
     2837 
     2838                $img_filepath = $this->getIcon($thumb_path, $small_icon); 
     2839 
     2840                return $img_filepath; 
     2841        } 
     2842 
     2843        /** 
     2844         * Make sure the generated thumbpath is unique for each file. To prevent 
     2845         * reduced performance for large file sets: all thumbnails derived from any files in the entire 
     2846         * FileManager-managed directory tree, rooted by options['directory'], can become a huge collection, 
     2847         * so we distribute them across a directory tree, which is created on demand. 
     2848         * 
     2849         * The thumbnails directory tree is determined by the MD5 of the full path to the image, 
     2850         * using the first two characters of the MD5, making for a span of 256. 
     2851         * 
     2852         * Note: when you expect to manage a really HUGE file collection from FM, you may dial up the 
     2853         *       $number_of_dir_levels to 2 here. 
     2854         */ 
     2855        protected function generateThumbName($legal_url, $width = 250, $number_of_dir_levels = MTFM_NUMBER_OF_DIRLEVELS_FOR_CACHE) 
     2856        { 
     2857                $fi = pathinfo($legal_url); 
     2858                $ext = strtolower(!empty($fi['extension']) ? $fi['extension'] : ''); 
     2859                switch ($ext) 
     2860                { 
     2861                case 'gif': 
     2862                case 'png': 
     2863                case 'jpg': 
     2864                case 'jpeg': 
     2865                        break; 
     2866 
     2867                default: 
     2868                        // default to PNG, as it'll handle transparancy and full color both: 
     2869                        $ext = 'png'; 
     2870                        break; 
     2871                } 
     2872 
     2873                // as the Thumbnail is generated, but NOT guaranteed from a safe filepath (FM may be visiting unsafe 
     2874                // image files when they exist in a preloaded directory tree!) we do the full safe-filename transform 
     2875                // on the name itself. 
     2876                // The MD5 is taken from the untrammeled original, though: 
     2877                $dircode = md5($legal_url); 
     2878 
     2879                $rv = ''; 
     2880                for ($i = 0; $i < $number_of_dir_levels; $i++) 
     2881                { 
     2882                        $rv .= substr($dircode, 0, 2) . '/'; 
     2883                        $dircode = substr($dircode, 2); 
     2884                } 
     2885 
     2886                $fn = '_' . $fi['filename']; 
     2887                $fn = substr($dircode, 0, 4) . preg_replace('/[^A-Za-z0-9]+/', '_', $fn); 
     2888                $fn = substr($fn . $dircode, 0, 38); 
     2889                $ext = preg_replace('/[^A-Za-z0-9_]+/', '_', $ext); 
     2890 
     2891                $rv .= $fn . '-' . $width . '.' . $ext; 
     2892                return $rv; 
     2893        } 
     2894 
     2895        protected function deleteThumb($legal_url) 
     2896        { 
     2897                // generate a thumbnail name with embedded wildcard for the size parameter: 
     2898                $thumb = $this->generateThumbName($legal_url, '*'); 
     2899                $tfi = pathinfo($thumb); 
     2900                $thumbnail_subdir = $tfi['dirname']; 
     2901                $thumbPath = $this->url_path2file_path($this->options['thumbnailPath'] . $thumbnail_subdir); 
     2902                $thumbPath = self::enforceTrailingSlash($thumbPath); 
     2903 
     2904                // remove thumbnails (any size) and any other related cached files (TODO: future version should cache getID3 metadata as well -- and delete it here!) 
     2905                $files = $this->scandir($thumbPath, $tfi['filename'] . '.*', true); 
     2906 
     2907                $rv = true; 
     2908                if (is_array($files)) 
     2909                { 
     2910                        foreach($files as $filename) 
     2911                        { 
     2912                                if(in_array($filename, array('.','..'))) 
     2913                                        continue; 
     2914 
     2915                                $file = $thumbPath . $filename; 
     2916                                if(is_file($file)) 
     2917                                        $rv &= @unlink($file); 
     2918                        } 
     2919                } 
     2920 
     2921                // as the thumbnail subdirectory may now be entirely empty, try to remove it as well, 
     2922                // but do NOT yack when we don't succeed: there may be other thumbnails, etc. in there still! 
     2923 
     2924                while ($thumbnail_subdir > '/') 
     2925                { 
     2926                        // try to NOT delete the thumbnails base directory itself; we MAY not be able to recreate it later on demand! 
     2927                        $thumbPath = $this->url_path2file_path($this->options['thumbnailPath'] . $thumbnail_subdir); 
     2928                        @rmdir($thumbPath); 
     2929 
     2930                        $thumbnail_subdir = self::getParentDir($thumbnail_subdir); 
     2931                } 
     2932 
     2933                return $rv;   // when thumbnail does not exist, say it is succesfully removed: all that counts is it doesn't exist anymore when we're done here. 
     2934        } 
     2935 
     2936 
     2937 
     2938 
     2939 
     2940 
     2941 
     2942 
     2943 
     2944 
     2945        /** 
     2946         * Safe replacement of dirname(); does not care whether the input has a trailing slash or not. 
     2947         * 
     2948         * Return FALSE when the path is attempting to get the parent of '/' 
     2949         */ 
     2950        public static function getParentDir($path) 
     2951        { 
     2952                /* 
     2953                 * on Windows, you get: 
     2954                 * 
     2955                 * dirname("/") = "\" 
     2956                 * dirname("y/") = "." 
     2957                 * dirname("/x") = "\" 
     2958                 * 
     2959                 * so we'd rather not use dirname()   :-( 
     2960                 */ 
     2961                if (!is_string($path)) 
     2962                        return false; 
     2963                $path = rtrim($path, '/'); 
     2964                // empty directory or a path with only 1 character in it cannot be a parent+child: that would be 2 at the very least when it's '/a': parent is root '/' then: 
     2965                if (strlen($path) <= 1) 
     2966                        return false; 
     2967 
     2968                $p2 = strrpos($path, '/' /* , -1 */ );  // -1 as extra offset is not good enough? Nope. At least not for my Win32 PHP 5.3.1. Yeah, sounds like a PHP bug to me. So we rtrim() now... 
     2969                if ($p2 === false) 
     2970                { 
     2971                        return false; // tampering! 
     2972                } 
     2973                $prev = substr($path, 0, $p2 + 1); 
     2974                return $prev; 
     2975        } 
     2976 
     2977        /** 
     2978         * Return the URI absolute path to the directory pointed at by the current URI request. 
     2979         * For example, if the request was 'http://site.org/dir1/dir2/script', then this method will 
     2980         * return '/dir1/dir2/'. 
     2981         * 
     2982         * Note that the path is returned WITH a trailing slash '/'. 
     2983         */ 
     2984        public /* static */ function getRequestPath() 
     2985        { 
     2986                // see also: http://php.about.com/od/learnphp/qt/_SERVER_PHP.htm 
     2987                $path = self::getParentDir(str_replace('\\', '/', $_SERVER['SCRIPT_NAME'])); 
     2988                $path = self::enforceTrailingSlash($path); 
     2989 
     2990                return $path; 
     2991        } 
     2992 
     2993        /** 
     2994         * Normalize an absolute path by converting all slashes '/' and/or backslashes '\' and any mix thereof in the 
     2995         * specified path to UNIX/MAC/Win compatible single forward slashes '/'. 
     2996         * 
     2997         * Also roll up any ./ and ../ directory elements in there. 
     2998         * 
     2999         * Throw an exception when the operation failed to produce a legal path. 
     3000         */ 
     3001        public /* static */ function normalize($path) 
     3002        { 
     3003                $path = preg_replace('/(\\\|\/)+/', '/', $path); 
     3004 
     3005                /* 
     3006                 * fold '../' directory parts to prevent malicious paths such as 'a/../../../../../../../../../etc/' 
     3007                 * from succeeding 
     3008                 * 
     3009                 * to prevent screwups in the folding code, we FIRST clean out the './' directories, to prevent 
     3010                 * 'a/./.././.././.././.././.././.././.././.././../etc/' from succeeding: 
     3011                 */ 
     3012                $path = preg_replace('#/(\./)+#', '/', $path); 
     3013 
     3014                // now temporarily strip off the leading part up to the colon to prevent entries like '../d:/dir' to succeed when the site root is 'c:/', for example: 
     3015                $lead = ''; 
     3016                // the leading part may NOT contain any directory separators, as it's for drive letters only. 
     3017                // So we must check in order to prevent malice like /../../../../../../../c:/dir from making it through. 
     3018                if (preg_match('#^([A-Za-z]:)?/(.*)$#', $path, $matches)) 
     3019                { 
     3020                        $lead = $matches[1]; 
     3021                        $path = '/' . $matches[2]; 
     3022                } 
     3023 
     3024                while (($pos = strpos($path, '/..')) !== false) 
     3025                { 
     3026                        $prev = substr($path, 0, $pos); 
     3027                        /* 
     3028                         * on Windows, you get: 
     3029                         * 
     3030                         * dirname("/") = "\" 
     3031                         * dirname("y/") = "." 
     3032                         * dirname("/x") = "\" 
     3033                         * 
     3034                         * so we'd rather not use dirname()   :-( 
     3035                         */ 
     3036                        $p2 = strrpos($prev, '/'); 
     3037                        if ($p2 === false) 
     3038                        { 
     3039                                throw new FileManagerException('path_tampering:' . $path); 
     3040                        } 
     3041                        $prev = substr($prev, 0, $p2); 
     3042                        $next = substr($path, $pos + 3); 
     3043                        if ($next && $next[0] != '/') 
     3044                        { 
     3045                                throw new FileManagerException('path_tampering:' . $path); 
     3046                        } 
     3047                        $path = $prev . $next; 
     3048                } 
     3049 
     3050                $path = $lead . $path; 
     3051 
     3052                /* 
     3053                 * iff there was such a '../../../etc/' attempt, we'll know because there'd be an exception thrown in the loop above. 
     3054                 */ 
     3055 
     3056                return $path; 
     3057        } 
     3058 
     3059 
     3060        /** 
     3061         * Accept a URI relative or absolute path and transform it to an absolute URI path, i.e. rooted against DocumentRoot. 
     3062         * 
     3063         * Relative paths are assumed to be relative to the current request path, i.e. the getRequestPath() produced path. 
     3064         * 
     3065         * Note: as it uses normalize(), any illegal path will throw an FileManagerException 
     3066         * 
     3067         * Returns a fully normalized URI absolute path. 
     3068         */ 
     3069        public function rel2abs_url_path($path) 
     3070        { 
     3071                $path = str_replace('\\', '/', $path); 
     3072                if (!FileManagerUtility::startsWith($path, '/')) 
     3073                { 
     3074                        $based = $this->getRequestPath(); 
     3075                        $path = $based . $path; 
     3076                } 
     3077                return $this->normalize($path); 
     3078        } 
     3079 
     3080        /** 
     3081         * Accept an absolute URI path, i.e. rooted against DocumentRoot, and transform it to a LEGAL URI absolute path, i.e. rooted against options['directory']. 
     3082         * 
     3083         * Relative paths are assumed to be relative to the current request path, i.e. the getRequestPath() produced path. 
     3084         * 
     3085         * Note: as it uses normalize(), any illegal path will throw a FileManagerException 
     3086         * 
     3087         * Returns a fully normalized LEGAL URI path. 
     3088         * 
     3089         * Throws a FileManagerException when the given path cannot be converted to a LEGAL URL, i.e. when it resides outside the options['directory'] subtree. 
     3090         */ 
     3091        public function abs2legal_url_path($path) 
     3092        { 
     3093                $root = $this->options['directory']; 
     3094 
     3095                $path = $this->rel2abs_url_path($path); 
     3096                //$path = $this->normalize($path);    -- taken care of by rel2abs_url_path already 
     3097 
     3098                // but we MUST make sure the path is still a LEGAL URI, i.e. sutting inside options['directory']: 
     3099                if (strlen($path) < strlen($root)) 
     3100                        $path = self::enforceTrailingSlash($path); 
     3101 
     3102                if (!FileManagerUtility::startsWith($path, $root)) 
     3103                { 
     3104                        throw new FileManagerException('path_tampering:' . $path); 
     3105                } 
     3106 
     3107                // clip the trailing '/' off the $root path before reduction: 
     3108                $path = str_replace(substr($root, 0, -1), '', $path); 
     3109                 
     3110                return $path; 
     3111        } 
     3112 
     3113        /** 
     3114         * Accept a URI relative or absolute LEGAL URI path and transform it to an absolute URI path, i.e. rooted against DocumentRoot. 
     3115         * 
     3116         * Relative paths are assumed to be relative to the current request path, i.e. the getRequestPath() produced path. 
     3117         * 
     3118         * Note: as it uses normalize(), any illegal path will throw a FileManagerException 
     3119         * 
     3120         * Returns a fully normalized URI absolute path. 
     3121         */ 
     3122        public function legal2abs_url_path($path) 
     3123        { 
     3124                $root = $this->options['directory']; 
     3125 
     3126                $path = str_replace('\\', '/', $path); 
     3127                if (FileManagerUtility::startsWith($path, '/')) 
     3128                { 
     3129                        // clip the trailing '/' off the $root path as $path has a leading '/' already: 
     3130                        $path = substr($root, 0, -1) . $path; 
     3131                } 
     3132 
     3133                $path = $this->rel2abs_url_path($path); 
     3134                //$path = $this->normalize($path);    -- taken care of by rel2abs_url_path already 
     3135 
     3136                // but we MUST make sure the path is still a LEGAL URI, i.e. sutting inside options['directory']: 
     3137                if (strlen($path) < strlen($root)) 
     3138                        $path = self::enforceTrailingSlash($path); 
     3139 
     3140                if (!FileManagerUtility::startsWith($path, $root)) 
     3141                { 
     3142                        throw new FileManagerException('path_tampering:' . $path); 
     3143                } 
     3144                return $path; 
     3145        } 
     3146 
     3147        /** 
     3148         * Accept a URI relative or absolute LEGAL URI path and transform it to an absolute LEGAL URI path, i.e. rooted against options['directory']. 
     3149         * 
     3150         * Relative paths are assumed to be relative to the options['directory'] directory. This makes them equivalent to absolute paths within 
     3151         * the LEGAL URI tree and this fact may seem odd. Alas, the FM frontend sends requests without the leading slash and it's those that 
     3152         * we wish to resolve here, after all. So, yes, this deviates from the general principle applied elesewhere in the code. :-( 
     3153         * Nevertheless, it's easier than scanning and tweaking the FM frontend everywhere. 
     3154         * 
     3155         * Note: as it uses normalize(), any illegal path will throw an FileManagerException 
     3156         * 
     3157         * Returns a fully normalized LEGAL URI absolute path. 
     3158         */ 
     3159        public function rel2abs_legal_url_path($path) 
     3160        { 
     3161                if (0) // TODO: remove the 'relative is based on options['directory']' hack when the frontend has been fixed... 
     3162                { 
     3163                        $path = $this->legal2abs_url_path($path); 
     3164 
     3165                        $root = $this->options['directory']; 
     3166 
     3167                        // clip the trailing '/' off the $root path before reduction: 
     3168                        $path = str_replace(substr($root, 0, -1), '', $path); 
     3169                } 
     3170                else 
     3171                { 
     3172                        $path = str_replace('\\', '/', $path); 
     3173                        if (!FileManagerUtility::startsWith($path, '/')) 
     3174                        { 
     3175                                $path = '/' . $path; 
     3176                        } 
     3177 
     3178                        $path = $this->normalize($path); 
     3179                } 
     3180 
     3181                return $path; 
     3182        } 
     3183 
     3184        /** 
     3185         * Return the filesystem absolute path for the relative or absolute URI path. 
     3186         * 
     3187         * Note: as it uses normalize(), any illegal path will throw an FileManagerException 
     3188         * 
     3189         * Returns a fully normalized filesystem absolute path. 
     3190         */ 
     3191        public function url_path2file_path($url_path) 
     3192        { 
     3193                $url_path = $this->rel2abs_url_path($url_path); 
     3194 
     3195                $root = str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']); 
     3196                if (FileManagerUtility::endsWith($root, '/')) 
     3197                { 
     3198                        $root = substr($root, 0, -1); 
     3199                } 
     3200                $path = $root . $url_path; 
     3201                //$path = $this->normalize($path);    -- taken care of by rel2abs_url_path already 
     3202                return $path; 
     3203        } 
     3204 
     3205        /** 
     3206         * Return the filesystem absolute path for the relative URI path or absolute LEGAL URI path. 
     3207         * 
     3208         * Note: as it uses normalize(), any illegal path will throw an FileManagerException 
     3209         * 
     3210         * Returns a fully normalized filesystem absolute path. 
     3211         */ 
     3212        public function legal_url_path2file_path($url_path) 
     3213        { 
     3214                $path = $this->rel2abs_legal_url_path($url_path); 
     3215 
     3216                $path = substr($this->options['directory'], 0, -1) . $path; 
     3217 
     3218                $path = $this->url_path2file_path($path); 
     3219 
     3220                return $path; 
     3221        } 
     3222 
     3223        public static function enforceTrailingSlash($string) 
     3224        { 
     3225                return (strrpos($string, '/') === strlen($string) - 1 ? $string : $string . '/'); 
     3226        } 
     3227 
     3228 
     3229 
     3230 
     3231 
     3232        /** 
     3233         * Produce minimized HTML output; used to cut don't on the content fed 
     3234         * to JSON_encode() and make it more readable in raw debug view. 
     3235         */ 
     3236        public static function compressHTML($str) 
     3237        { 
     3238                // brute force: replace tabs by spaces and reduce whitespace series to a single space. 
     3239                //$str = preg_replace('/\s+/', ' ', $str); 
     3240 
     3241                return $str; 
     3242        } 
     3243 
     3244 
     3245        protected /* static */ function modify_json4exception(&$jserr, $emsg, $mode = 0) 
     3246        { 
     3247                if (empty($emsg)) 
     3248                        return; 
     3249 
     3250                // only set up the new json error report array when this is the first exception we got: 
     3251                if (empty($jserr['error'])) 
     3252                { 
     3253                        // check the error message and see if it is a translation code word (with or without parameters) or just a generic error report string 
     3254                        $e = explode(':', $emsg, 2); 
     3255                        if (preg_match('/[^A-Za-z0-9_-]/', $e[0])) 
     3256                        { 
     3257                                // generic message. ouch. 
     3258                                $jserr['error'] = $emsg; 
     3259                        } 
     3260                        else 
     3261                        { 
     3262                                $jserr['error'] = $emsg = '${backend.' . $e[0] . '}' . (isset($e[1]) ? $e[1] : ''); 
     3263                        } 
     3264                        $jserr['status'] = 0; 
     3265 
     3266                        if ($mode == 1) 
     3267                        { 
     3268                                $jserr['content'] = self::compressHTML('<div class="margin"> 
     3269                                                ${nopreview} 
     3270                                                <div class="failure_notice"> 
     3271                                                        <h3>${error}</h3> 
     3272                                                        <p>mem usage: ' . number_format(memory_get_usage() / 1E6, 2) . ' MB : ' . number_format(memory_get_peak_usage() / 1E6, 2) . ' MB</p> 
     3273                                                        <p>' . $emsg . '</p> 
     3274                                                </div> 
     3275                                        </div>');       // <br/><button value="' . $url . '">${download}</button> 
     3276                        } 
     3277                } 
     3278        } 
     3279 
     3280 
     3281 
     3282 
     3283 
     3284 
     3285        public function getAllowedMimeTypes($mime_filter = null) 
     3286        { 
     3287                $mimeTypes = array(); 
     3288 
     3289                if (empty($mime_filter)) return null; 
     3290                $mset = explode(',', $mime_filter); 
     3291                for($i = count($mset) - 1; $i >= 0; $i--) 
     3292                { 
     3293                        if (strpos($mset[$i], '/') === false) $mset[$i] .= '/'; 
     3294                } 
     3295 
     3296                $mimes = $this->getMimeTypeDefinitions(); 
     3297 
     3298                foreach ($mimes as $mime) 
     3299                { 
     3300                        foreach($mset as $filter) 
     3301                        { 
     3302                                if (FileManagerUtility::startsWith($mime, $filter)) 
     3303                                        $mimeTypes[] = $mime; 
     3304                        } 
     3305                } 
     3306 
     3307                return $mimeTypes; 
     3308        } 
     3309 
     3310        public function getMimeTypeDefinitions() 
     3311        { 
     3312                static $mimes; 
     3313 
     3314                if (!$mimes) $mimes = parse_ini_file($this->options['mimeTypesPath']); 
     3315                if (!$mimes) $mimes = array(); // prevent faulty mimetype ini file from b0rking other code sections. 
     3316                return $mimes; 
     3317        } 
     3318 
     3319        public function IsAllowedMimeType($mime_type, $mime_filters) 
     3320        { 
     3321                if (empty($mime_type)) 
     3322                        return false; 
     3323                if (!is_array($mime_filters)) 
     3324                        return true; 
     3325 
     3326                return in_array($mime_type, $mime_filters); 
     3327        } 
     3328 
     3329        /** 
     3330         * Returns (if possible) the mimetype of the given file 
     3331         * 
     3332         * @param string $file 
     3333         * @param boolean $just_guess when TRUE, files are not 'sniffed' to derive their actual mimetype 
     3334         *                            but instead only the swift (and blunt) process of guestimating 
     3335         *                            the mime type from the file extension is performed. 
     3336         */ 
     3337        public function getMimeType($file, $just_guess = false) 
     3338        { 
     3339                if (is_dir($file)) 
     3340                        return 'text/directory'; 
     3341 
     3342                $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); 
     3343 
     3344                $mime = null; 
     3345                $ini = error_reporting(0); 
     3346                if (function_exists('finfo_open') && $f = finfo_open(FILEINFO_MIME, getenv('MAGIC'))) 
     3347                { 
     3348                        $mime = finfo_file($f, $file); 
     3349                        // some systems also produce the cracter encoding with the mime type; strip if off: 
     3350                        $ma = explode(';', $mime); 
     3351                        $mime = $ma[0]; 
     3352                        finfo_close($f); 
     3353                } 
     3354                error_reporting($ini); 
     3355 
     3356                if (!$mime && !$just_guess && in_array($ext, array('gif', 'jpg', 'jpeg', 'png'))) 
     3357                { 
     3358                        $image = @getimagesize($file); 
     3359                        if($image !== false && !empty($image['mime'])) 
     3360                                $mime = $image['mime']; 
     3361                } 
     3362 
     3363                if ((!$mime || $mime == 'application/octet-stream') && strlen($ext) > 0) 
     3364                { 
     3365                        $ext2mimetype_arr = $this->getMimeTypeDefinitions(); 
     3366 
     3367                        if (!empty($ext2mimetype_arr[$ext])) 
     3368                                return $ext2mimetype_arr[$ext]; 
     3369                } 
     3370 
     3371                if (!$mime) 
     3372                        $mime = 'application/octet-stream'; 
     3373 
     3374                return $mime; 
     3375        } 
     3376 
     3377 
     3378 
     3379 
     3380 
     3381        protected /* static */ function getGETparam($name, $default_value = null) 
     3382        { 
     3383                if (is_array($_GET) && !empty($_GET[$name])) 
     3384                { 
     3385                        $rv = $_GET[$name]; 
     3386 
     3387                        // see if there's any stuff in there which we don't like 
     3388                        if (!preg_match('/[^A-Za-z0-9\/~!@#$%^&*()_+{}[]\'",.?]/', $rv)) 
     3389                        { 
     3390                                return $rv; 
     3391                        } 
     3392                } 
     3393                return $default_value; 
     3394        } 
     3395 
     3396        protected /* static */ function getPOSTparam($name, $default_value = null) 
     3397        { 
     3398                if (is_array($_POST) && !empty($_POST[$name])) 
     3399                { 
     3400                        $rv = $_POST[$name]; 
     3401 
     3402                        // see if there's any stuff in there which we don't like 
     3403                        if (!preg_match('/[^A-Za-z0-9\/~!@#$%^&*()_+{}[]\'",.?]/', $rv)) 
     3404                        { 
     3405                                return $rv; 
     3406                        } 
     3407                } 
     3408                return $default_value; 
     3409        } 
    13873410} 
    13883411 
     3412 
     3413 
     3414 
     3415 
     3416 
    13893417class FileManagerException extends Exception {} 
     3418 
     3419 
     3420 
     3421 
    13903422 
    13913423/* Stripped-down version of some Styx PHP Framework-Functionality bundled with this FileBrowser. Styx is located at: http://styx.og5.net */ 
    13923424class FileManagerUtility 
    13933425{ 
    1394   public static function endsWith($string, $look){ 
    1395     return strrpos($string, $look)===strlen($string)-strlen($look); 
    1396   } 
    1397  
    1398   public static function startsWith($string, $look){ 
    1399     return strpos($string, $look)===0; 
    1400   } 
    1401  
    1402   /** 
    1403    * Cleanup and check against 'already known names' in optional $options array. 
    1404    * Return a uniquified name equal to or derived from the original ($data). 
    1405    * 
    1406    * First clean up the given name ($data): by default all characters not part of the 
    1407    * set [A-Za-z0-9_] are converted to an underscore '_'; series of these underscores 
    1408    * are reduced to a single one, and characters in the set [_.,&+ ] are stripped from 
    1409    * the lead and tail of the given name, e.g. '__name' would therefor be reduced to 
    1410    * 'name'. 
    1411    * 
    1412    * Next, check the now cleaned-up name $data against an optional set of names ($options array) 
    1413    * and return the name itself when it does not exist in the set, 
    1414    * otherwise return an augmented name such that it does not exist in the set 
    1415    * while having been constructed as name plus '_' plus an integer number, 
    1416    * starting at 1. 
    1417    * 
    1418    * Example: 
    1419    * If the set is {'file', 'file_1', 'file_3'} then $data = 'file' will return 
    1420    * the string 'file_2' instead, while $data = 'fileX' will return that same 
    1421    * value: 'fileX'. 
    1422    * 
    1423    * @param string $data     the name to be cleaned and checked/uniquified 
    1424    * @param array $options   an optional array of strings to check the given name $data against 
    1425    * @param string $extra_allowed_chars     optional set of additional characters which should pass 
    1426    *                                        unaltered through the cleanup stage. a dash '-' can be 
    1427    *                                        used to denote a character range, while the literal 
    1428    *                                        dash '-' itself, when included, should be positioned 
    1429    *                                        at the very start or end of the string. 
    1430    * 
    1431    *                                        Note that ] must NOT need to be escaped; we do this 
    1432    *                                        ourselves. 
    1433    * @param string $trim_chars              optional set of additional characters which are trimmed off the 
    1434    *                                        start and end of the name ($data); note that de dash 
    1435    *                                        '-' is always treated as a literal dash here; no 
    1436    *                                        range feature! 
    1437    *                                        The basic set of characters trimmed off the name is 
    1438    *                                        [. ]; this set cannot be reduced, only extended. 
    1439    * 
    1440    * @return cleaned-up and uniquified name derived from ($data). 
    1441    */ 
    1442   public static function pagetitle($data, $options = null, $extra_allowed_chars = null, $trim_chars = null) 
    1443   { 
    1444     static $regex; 
    1445     if (!$regex){ 
    1446       $regex = array( 
    1447         explode(' ', 'Æ Ê Œ œ ß Ü ÃŒ Ö ö Ä À À Á Â Ã Ä à
     3426        public static function endsWith($string, $look) 
     3427        { 
     3428                return strrpos($string, $look)===strlen($string)-strlen($look); 
     3429        } 
     3430 
     3431        public static function startsWith($string, $look) 
     3432        { 
     3433                return strpos($string, $look)===0; 
     3434        } 
     3435 
     3436 
     3437        /** 
     3438         * Cleanup and check against 'already known names' in optional $options array. 
     3439         * Return a uniquified name equal to or derived from the original ($data). 
     3440         * 
     3441         * First clean up the given name ($data): by default all characters not part of the 
     3442         * set [A-Za-z0-9_] are converted to an underscore '_'; series of these underscores 
     3443         * are reduced to a single one, and characters in the set [_.,&+ ] are stripped from 
     3444         * the lead and tail of the given name, e.g. '__name' would therefor be reduced to 
     3445         * 'name'. 
     3446         * 
     3447         * Next, check the now cleaned-up name $data against an optional set of names ($options array) 
     3448         * and return the name itself when it does not exist in the set, 
     3449         * otherwise return an augmented name such that it does not exist in the set 
     3450         * while having been constructed as name plus '_' plus an integer number, 
     3451         * starting at 1. 
     3452         * 
     3453         * Example: 
     3454         * If the set is {'file', 'file_1', 'file_3'} then $data = 'file' will return 
     3455         * the string 'file_2' instead, while $data = 'fileX' will return that same 
     3456         * value: 'fileX'. 
     3457         * 
     3458         * @param string $data     the name to be cleaned and checked/uniquified 
     3459         * @param array $options   an optional array of strings to check the given name $data against 
     3460         * @param string $extra_allowed_chars     optional set of additional characters which should pass 
     3461         *                                        unaltered through the cleanup stage. a dash '-' can be 
     3462         *                                        used to denote a character range, while the literal 
     3463         *                                        dash '-' itself, when included, should be positioned 
     3464         *                                        at the very start or end of the string. 
     3465         * 
     3466         *                                        Note that ] must NOT need to be escaped; we do this 
     3467         *                                        ourselves. 
     3468         * @param string $trim_chars              optional set of additional characters which are trimmed off the 
     3469         *                                        start and end of the name ($data); note that de dash 
     3470         *                                        '-' is always treated as a literal dash here; no 
     3471         *                                        range feature! 
     3472         *                                        The basic set of characters trimmed off the name is 
     3473         *                                        [. ]; this set cannot be reduced, only extended. 
     3474         * 
     3475         * @return cleaned-up and uniquified name derived from ($data). 
     3476         */ 
     3477        public static function pagetitle($data, $options = null, $extra_allowed_chars = null, $trim_chars = null) 
     3478        { 
     3479                static $regex; 
     3480                if (!$regex){ 
     3481                        $regex = array( 
     3482                                explode(' ', 'Æ Ê Œ œ ß Ü ÃŒ Ö ö Ä À À Á Â Ã Ä à
    14483483 &#260; &#258; Ç &#262; &#268; &#270; &#272; Ð È É Ê Ë &#280; &#282; &#286; Ì Í Î Ï &#304; &#321; &#317; &#313; Ñ &#323; &#327; Ò Ó Ô Õ Ö Ø &#336; &#340; &#344; Å  &#346; &#350; &#356; &#354; Ù Ú Û Ü &#366; &#368; Ý Åœ &#377; &#379; à á â ã À Ã¥ &#261; &#259; ç &#263; &#269; &#271; &#273; Ú é ê ë &#281; &#283; &#287; ì í î ï &#305; &#322; &#318; &#314; ñ &#324; &#328; ð ò ó ÃŽ õ ö Þ &#337; &#341; &#345; &#347; Å¡ &#351; &#357; &#355; ù ú û ÃŒ &#367; &#369; Ãœ ÿ ÅŸ &#378; &#380;'), 
    1449         explode(' ', 'Ae ae Oe oe ss Ue ue Oe oe Ae ae A A A A A A A A C C C D D D E E E E E E G I I I I I L L L N N N O O O O O O O R R S S S T T U U U U U U Y Z Z Z a a a a a a a a c c c d d e e e e e e g i i i i i l l l n n n o o o o o o o o r r s s s t t u u u u u u y y z z z'), 
    1450       ); 
    1451     } 
    1452  
    1453     if (empty($data)) 
    1454         return $data; 
    1455  
    1456     // fixup $extra_allowed_chars to ensure it's suitable as a character sequence for a set in a regex: 
    1457     // 
    1458     // Note: 
    1459     //   caller must ensure a dash '-', when to be treated as a separate character, is at the very end of the string 
    1460     if (is_string($extra_allowed_chars)) 
    1461     { 
    1462         $extra_allowed_chars = str_replace(']', '\]', $extra_allowed_chars); 
    1463         if (strpos($extra_allowed_chars, '-') === 0) 
    1464         { 
    1465             $extra_allowed_chars = substr($extra_allowed_chars, 1) . (strpos($extra_allowed_chars, '-') != strlen($extra_allowed_chars) - 1 ? '-' : ''); 
    1466         } 
    1467     } 
    1468     else 
    1469     { 
    1470         $extra_allowed_chars = ''; 
    1471     } 
    1472     // accepts dots and several other characters, but do NOT tolerate dots or underscores at the start or end, i.e. no 'hidden file names' accepted, for example! 
    1473     $data = preg_replace('/[^A-Za-z0-9' . $extra_allowed_chars . ']+/', '_', str_replace($regex[0], $regex[1], $data)); 
    1474     $data = trim($data, '_. ' . $trim_chars); 
    1475  
    1476     //$data = trim(substr(preg_replace('/(?:[^A-z0-9]|_|\^)+/i', '_', str_replace($regex[0], $regex[1], $data)), 0, 64), '_'); 
    1477     return !empty($options) ? self::checkTitle($data, $options) : $data; 
    1478   } 
    1479  
    1480   protected static function checkTitle($data, $options = array(), $i = 0){ 
    1481     if (!is_array($options)) return $data; 
    1482  
    1483     $lwr_data = strtolower($data); 
    1484  
    1485     foreach ($options as $content) 
    1486       if ($content && strtolower($content) == $lwr_data . ($i ? '_' . $i : '')) 
    1487         return self::checkTitle($data, $options, ++$i); 
    1488  
    1489     return $data.($i ? '_' . $i : ''); 
    1490   } 
    1491  
    1492   public static function isBinary($str){ 
    1493     $array = array(0, 255); 
    1494     for($i = 0; $i < strlen($str); $i++) 
    1495       if (in_array(ord($str[$i]), $array)) return true; 
    1496  
    1497     return false; 
    1498   } 
    1499  
    1500 // unused method: 
    1501 // 
    1502 //  public static function getPath(){ 
    1503 //    static $path; 
    1504 //    return $path ? $path : $path = pathinfo(str_replace('\\','/',__FILE__), PATHINFO_DIRNAME); 
    1505 //  } 
    1506  
    1507   /** 
    1508    * Return the filesystem absolute path to the directory pointed at by this site's DocumentRoot. 
    1509    * 
    1510    * Note that the path is returned WITHOUT a trailing slash '/'. 
    1511    */ 
    1512   public static function getSiteRoot() 
    1513   { 
    1514     $path = str_replace('\\','/',$_SERVER['DOCUMENT_ROOT']); 
    1515     $path = (FileManagerUtility::endsWith($path,'/')) ? substr($path, 0, -1) : $path; 
    1516  
    1517     return $path; 
    1518   } 
    1519  
    1520   /** 
    1521    * Return the filesystem absolute path to the directory pointed at by the current URI request. 
    1522    * For example, if the request was 'http://site.org/dir1/dir2/script', then this method will 
    1523    * return '<DocumentRoot>/dir1/dir2' where <DocumentRoot> is the filesystem path pointing 
    1524    * at this site's DocumentRoot. 
    1525    * 
    1526    * Note that the path is returned WITHOUT a trailing slash '/'. 
    1527    */ 
    1528   public static function getRequestDir() 
    1529   { 
    1530     // see also: http://php.about.com/od/learnphp/qt/_SERVER_PHP.htm 
    1531     $path = str_replace('\\','/', $_SERVER['SCRIPT_NAME']); 
    1532     $root = FileManagerUtility::getSiteRoot(); 
    1533     $path = dirname(!FileManagerUtility::startsWith($path, $root) ? $root . (!FileManagerUtility::startsWith($path, '/') ? '/' : '') . $path : $path); 
    1534     $path = (FileManagerUtility::endsWith($path,'/')) ? substr($path, 0, -1) : $path; 
    1535  
    1536     return $path; 
    1537   } 
    1538  
    1539   /** 
    1540    * Convert any relative or absolute path to a fully sanitized absolute path relative to DocumentRoot. 
    1541    * 
    1542    * When fed malicious paths (paths pointing outside the DocumentRoot tree) or paths which do not exist, a suitable 
    1543    * Exception will be thrown. 
    1544    * Note however that if and only if the parent directory of the given path does exist, is legal, and the 
    1545    * $mkdir_if_notexist argument is TRUE, then the last name in the path specification will be treated as a 
    1546    * subdirectory which will be created, while setting the directory permissions to the $chmod specified 
    1547    * value (default: 0777) 
    1548    */ 
    1549   public static function getRealPath($path, $chmod = 0777, $mkdir_if_notexist = false, $with_trailing_slash = true) 
    1550   { 
    1551     $path = preg_replace('/(\\\|\/)+/', '/', $path); 
    1552     $root = FileManagerUtility::getSiteRoot(); 
    1553     $path = str_replace($root,'',$path); 
    1554  
    1555     $path = (FileManagerUtility::startsWith($path,'/') ? $root . $path : FileManagerUtility::getRequestDir() . '/' . $path); /* do not base rel paths on FileManagerUtility::getRequestDir() root! */ 
    1556  
    1557     /* 
    1558      * fold '../' directory parts to prevent malicious paths such as 'a/../../../../../../../../../etc/' 
    1559      * from succeeding 
    1560      * 
    1561      * to prevent screwups in the folding code, we FIRST clean out the './' directories, to prevent 
    1562      * 'a/./.././.././.././.././.././.././.././.././../etc/' from succeeding: 
    1563      */ 
    1564     $path = preg_replace('#/(\./)+#','/',$path); 
    1565  
    1566     // now temporarily strip off the leading part up to the colon to prevent entries like '../d:/dir' to succeed when the site root is 'c:/', for example: 
    1567     $lead = ''; 
    1568     // the leading part may NOT contain any directory separators, as it's for drive letters only. 
    1569     // So we must check in order to prevent malice like /../../../../../../../c:/dir from making it through. 
    1570     if (preg_match('#^([A-Za-z]:)?/(.*)$#', $path, $matches)) 
    1571     { 
    1572         $lead = $matches[1]; 
    1573         $path = '/' . $matches[2]; 
    1574     } 
    1575  
    1576     while (($pos = strpos($path, '/..')) !== false) 
    1577     { 
    1578         $prev = substr($path, 0, $pos); 
    1579         /* 
    1580          * on Windows, you get: 
    1581          * 
    1582          * dirname("/") = "\" 
    1583          * dirname("y/") = "." 
    1584          * dirname("/x") = "\" 
    1585          * 
    1586          * so we'd rather not use dirname()   :-( 
    1587          */ 
    1588         $p2 = strrpos($prev, '/'); 
    1589         if ($p2 === false) 
    1590         { 
    1591             throw new FileManagerException('path_tampering'); 
    1592         } 
    1593         $prev = substr($prev, 0, $p2); 
    1594         $next = substr($path, $pos + 3); 
    1595         if ($next && $next[0] != '/') 
    1596         { 
    1597             throw new FileManagerException('path_tampering'); 
    1598         } 
    1599         $path = $prev . $next; 
    1600     } 
    1601  
    1602     $path = $lead . $path; 
    1603  
    1604     /* 
    1605      * iff there was such a '../../../etc/' attempt, we'll know because the resulting path does NOT have 
    1606      * the 'siteroot' prefix. We don't cover up such 'mishaps' but throw a tantrum instead, so upper level 
    1607      * logic can process this fact accordingly: 
    1608      */ 
    1609     if (!FileManagerUtility::startsWith($path, $root)) 
    1610     { 
    1611         throw new FileManagerException('path_tampering'); 
    1612     } 
    1613  
    1614     if(!is_dir($path) && is_dir(dirname($path)) && $mkdir_if_notexist) 
    1615     { 
    1616         $rv = @mkdir($path,$chmod); // create last folder if not existing 
    1617         if ($rv === false) 
    1618         { 
    1619             throw new FileManagerException('mkdir_failed:' . $path); 
    1620         } 
    1621     } 
    1622  
    1623     /* 
    1624      * now all there's left for realpath() to do is expand possible symbolic links in the path; don't make 
    1625      * that dependent on how the path looks: 
    1626      */ 
    1627     $rv = realpath($path); 
    1628     if ($rv === false) 
    1629     { 
    1630         throw new FileManagerException('realpath_failed:' . $path); 
    1631     } 
    1632     $path = str_replace('\\','/',$rv); 
    1633     $path = str_replace($root,'',$path); 
    1634     $path = ($with_trailing_slash ? (FileManagerUtility::endsWith($path,'/') ? $path : $path.'/') : ((FileManagerUtility::endsWith($path,'/') && strlen($path) > 1) ? substr($path, 0, -1) : $path)); 
    1635  
    1636     return $path; 
    1637   } 
    1638  
    1639   /** 
    1640    * Return the filesystem absolute path equivalent of the output of getRealPath(). 
    1641    */ 
    1642   public static function getRealDir($path, $chmod = 0777, $mkdir_if_notexist = false, $with_trailing_slash = true) 
    1643   { 
    1644     $path = self::getRealPath($path, $chmod, $mkdir_if_notexist, $with_trailing_slash); 
    1645     $path = FileManagerUtility::getSiteRoot() . $path; 
    1646     return $path; 
    1647   } 
    1648  
    1649   /** 
    1650    * Apply rawurlencode() to each of the elements of the given path 
    1651    * 
    1652    * @note 
    1653    *   this method is provided as rawurlencode() tself also encodes the '/' separators in a path/string 
    1654    *   and we do NOT want to 'revert' such change with the risk of also picking up other %2F bits in 
    1655    *   the string (this assumes crafted paths can be fed to us). 
    1656    */ 
    1657   public static function rawurlencode_path($path) 
    1658   { 
    1659     $encoded_path = explode('/', $path); 
    1660     array_walk($encoded_path, create_function('&$value', '$value = rawurlencode($value);')); 
    1661     return implode('/', $encoded_path); 
    1662   } 
    1663  
    1664   /** 
    1665    * Convert a number (representing number of bytes) to a formatted string representing GB .. bytes, 
    1666    * depending on the size of the value. 
    1667    */ 
    1668   public static function fmt_bytecount($val, $precision = 1) 
    1669   { 
    1670     $unit = array('TB', 'GB', 'MB', 'KB', 'bytes'); 
    1671     for ($x = count($unit) - 1; $val >= 1024 && $x > 0; $x--) 
    1672     { 
    1673         $val /= 1024.0; 
    1674     } 
    1675     $val = round($val, ($x > 0 ? $precision : 0)); 
    1676     return $val . '&#160;' . $unit[$x]; 
    1677   } 
     3484                                explode(' ', 'Ae ae Oe oe ss Ue ue Oe oe Ae ae A A A A A A A A C C C D D D E E E E E E G I I I I I L L L N N N O O O O O O O R R S S S T T U U U U U U Y Z Z Z a a a a a a a a c c c d d e e e e e e g i i i i i l l l n n n o o o o o o o o r r s s s t t u u u u u u y y z z z'), 
     3485                        ); 
     3486                } 
     3487 
     3488                if (empty($data)) 
     3489                                return $data; 
     3490 
     3491                // fixup $extra_allowed_chars to ensure it's suitable as a character sequence for a set in a regex: 
     3492                // 
     3493                // Note: 
     3494                //   caller must ensure a dash '-', when to be treated as a separate character, is at the very end of the string 
     3495                if (is_string($extra_allowed_chars)) 
     3496                { 
     3497                        $extra_allowed_chars = str_replace(']', '\]', $extra_allowed_chars); 
     3498                        if (strpos($extra_allowed_chars, '-') === 0) 
     3499                        { 
     3500                                $extra_allowed_chars = substr($extra_allowed_chars, 1) . (strpos($extra_allowed_chars, '-') != strlen($extra_allowed_chars) - 1 ? '-' : ''); 
     3501                        } 
     3502                } 
     3503                else 
     3504                { 
     3505                        $extra_allowed_chars = ''; 
     3506                } 
     3507                // accepts dots and several other characters, but do NOT tolerate dots or underscores at the start or end, i.e. no 'hidden file names' accepted, for example! 
     3508                $data = preg_replace('/[^A-Za-z0-9' . $extra_allowed_chars . ']+/', '_', str_replace($regex[0], $regex[1], $data)); 
     3509                $data = trim($data, '_. ' . $trim_chars); 
     3510 
     3511