diff --git a/Assets/Connector/FileManager.php b/Assets/Connector/FileManager.php old mode 100644 new mode 100755 index f9ef84b..c2d1df9 --- a/Assets/Connector/FileManager.php +++ b/Assets/Connector/FileManager.php @@ -15,7 +15,7 @@ * * Copyright: * Copyright (c) 2009-2011 [Christoph Pojer](http://cpojer.net) - * Backend: FileManager & FMgr4Alias Copyright (c) 2011 [Ger Hobbelt](http://hobbelt.com) + * Backend: FileManager & FileManagerWithAliasSupport Copyright (c) 2011 [Ger Hobbelt](http://hobbelt.com) * * Dependencies: * - Tooling.php @@ -23,23 +23,25 @@ * - getId3 Library * * Options: - * - 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) - * - assetBasePath: (string, optional) The URI path to all images and swf files used by the filemanager + * - documentRootPath: (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) + * - assetPath: (string, optional) The URI path to all images and swf files used by the filemanager * - thumbnailPath: (string) The URI path where the thumbnails of the pictures will be saved - * - mimeTypesPath: (string, optional) The filesystem path to the MimeTypes.ini file. May exist in a place outside the DocumentRoot tree. + * - thumbSmallSize: (integer) The (maximum) width / height in pixels of the thumb48 'small' thumbnails produced by this backend + * - thumbBigSize: (integer) The (maximum) width / height in pixels of the thumb250 'big' thumbnails produced by this backend + * - mimeTypesFilePath: (string, optional) The filesystem path to the MimeTypes.ini file. May exist in a place outside the DocumentRoot tree. * - dateFormat: (string, defaults to *j M Y - H:i*) The format in which dates should be displayed * - maxUploadSize: (integer, defaults to *20280000* bytes) The maximum file size for upload in bytes * - 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". - * - 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) - * - 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) - * - 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) - * - 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) - * - 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) - * - allowExtChange: (boolean, defaults to *false*) allow the file extension to be changed when performing a rename operation. - * - safe: (boolean, defaults to *true*) If true, disallows 'exe', 'dll', 'php', 'php3', 'php4', 'php5', 'phps' and saves them as 'txt' instead. + * - 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) + * - 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) + * - 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) + * - 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) + * - 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) + * - allowExtChange: (boolean, defaults to *FALSE*) allow the file extension to be changed when performing a rename operation. + * - safe: (boolean, defaults to *TRUE*) If TRUE, disallows 'exe', 'dll', 'php', 'php3', 'php4', 'php5', 'phps' and saves them as 'txt' instead. * - chmod: (integer, default is 0777) the permissions set to the uploaded files and created thumbnails (must have a leading "0", e.g. 0777) * - 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) - * - showHiddenFoldersAndFiles: (boolean, defaults to *false*) whether or not to show 'dotted' directories and files -- such files are considered 'hidden' on UNIX file systems + * - showHiddenFoldersAndFiles: (boolean, defaults to *FALSE*) whether or not to show 'dotted' directories and files -- such files are considered 'hidden' on UNIX file systems * - ViewIsAuthorized_cb (function/reference, default is *null*) authentication + authorization callback which can be used to determine whether the given directory may be viewed. * The parameter $action = 'view'. * - 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). @@ -56,480 +58,55 @@ * Note that currently support for copying subdirectories is missing. * The parameter $action = 'move'. * - * Obsoleted options: - * - 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.) * - * - * About the action permissions (upload|destroy|create|move|download): - * - * All the option "permissions" are set to FALSE by default. Developers should always SPECIFICALLY enable a permission to have that permission, for two reasons: - * - * 1. Developers forget to disable permissions, they don't forget to enable them (because things don't work!) - * - * 2. Having open permissions by default leaves potential for security vulnerabilities where those open permissions are exploited. - * - * - * For all authorization hooks (callback functions) the following applies: - * - * The callback should return TRUE for yes (permission granted), FALSE for no (permission denied). - * Parameters sent to the callback are: - * ($this, $action, $fileinfo) - * 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. - * $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. - * - * 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 - * Demos/FM-common.php, Demos/manager.php and Demos/selectImage.php - * - * - * Notes on relative paths and safety / security: - * - * 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, - * i.e. dirname($_SERVER['SCRIPT_NAME']). - * - * Requests may post/submit relative paths as arguments to their FileManager events/actions in $_GET/$_POST, and those relative paths will be - * regarded as relative to the request URI handling script path, i.e. dirname($_SERVER['SCRIPT_NAME']) to make the most - * sense from bother server and client coding perspective. - * - * - * 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 - * from succeeding. (An example of such would be an attacker posting his own 'destroy' event request requesting the destruction of - * '../../../../../../../../../etc/passwd' for example. In more complex rigs, the attack may be assisted through attacks at these options' paths, - * so these are subjected to the same scrutiny in here.) - * - * All paths, absolute or relative, as passed to the event handlers (see the onXXX methods of this class) are ENFORCED TO ABIDE THE RULE - * 'every path resides within the options['directory'] a.k.a. BASEDIR rooted tree' without exception. - * Because we can do without exceptions to important rules. ;-) - * - * When paths apparently don't, they are coerced into adherence to this rule; when this fails, an exception is thrown internally and an error - * will be reported and the action temrinated. - * - * 'LEGAL URL paths': - * - * Paths which adhere to the aforementioned rule are so-called LEGAL URL paths; their 'root' equals BASEDIR. - * - * BASEDIR equals the path pointed at by the options['directory'] setting. It is therefore imperative that you ensure this value is - * correctly set up; worst case, this setting will equal DocumentRoot. - * In other words: you'll never be able to reach any file or directory outside this site's DocumentRoot directory tree, ever. - * - * - * Path transformations: - * - * To allow arbitrary directory/path mapping algorithms to be applied (e.g. when implementing Alias support such as available in the - * derived class FileManagerWithAliasSupport), all paths are, on every change/edit, transformed from their LEGAL URL representation to - * their 'absolute URI path' (which is suitable to be used in links and references in HTML output) and 'absolute physical filesystem path' - * equivalents. - * By enforcing such a unidirectional transformation we implicitly support non-reversible and hard-to-reverse path aliasing mechanisms, - * e.g. complex regex+context based path manipulations in the server. - * - * - * When you need your paths to be restricted to the bounds of the options['directory'] tree (which is a subtree of the DocumentRoot based - * tree), you may wish to use the 'legal' class of path transformation member functions: - * - * - legal2abs_url_path() - * - rel2abs_legal_url_path() - * - legal_url_path2file_path() - * - * When you have a 'absolute URI path' or a path relative in URI space (implicitly relative to dirname($_SERVER['SCRIPT_NAME']) ), you can - * transform such a path to either a guaranteed-absolute URI space path or a filesystem path: - * - * - rel2abs_url_path() - * - url_path2file_path() - * - * Any other path transformations are ILLEGAL and DANGEROUS. The only other possibly legal transformation is from absolute URI path to - * BASEDIR-based LEGAL URL path, as the URI path space is assumed to be linear and contiguous. However, this operation is HIGHLY discouraged - * as it is a very strong indicator of other faulty logic, so we do NOT offer a method for this. - * - * - * Hooks: Detailed Interface Specification: - * - * All 'authorization' callback hooks share a common interface specification (function parameter set). This is by design, so one callback - * function can be used to process any and all of these events: - * - * Function prototype: - * - * function CallbackFunction($mgr, $action, &$info) - * - * where - * - * $msg: (object) reference to the current FileManager class instance. Can be used to invoke public FileManager methods inside - * the callback. - * - * $action: (string) identifies the event being processed. Can be one of these: - * - * 'create' create new directory - * 'move' move or copy a file or directory - * 'destroy' delete a file or directory - * 'upload' upload a single file (when performing a bulk upload, each file will be uploaded individually) - * 'download' download a file - * 'view' show a directory listing (in either 'list' or 'thumb' mode) - * 'detail' show detailed information about the file and, whn possible, provide a link to a (largish) thumbnail - * - * $info (array) carries all the details. Some of which can even be manipulated if your callbac is more than just an - * authentication / authorization checker. ;-) - * For more detail, see the next major section. - * - * The callback should return a boolean, where TRUE means the session/client is authorized to execute the action, while FALSE - * will cause the backend to report an authentication error and abort the action. - * - * Exceptions throwing from the callback: - * - * Note that you may choose to throw exceptions from inside the callback; those will be caught and transformed to proper error reports. - * - * You may either throw any exceptions based on either the FileManagerException or Exception classes. When you format the exception - * message as "XYZ:data", where 'XYZ' is a alphanumeric-only word, this will be transformed to a i18n-support string, where - * 'backend.XYZ' must map to a translation string (e.g. 'backend.nofile', see also the Language/Language.XX.js files) and the optional - * 'data' tail will be appended to the translated message. - * - * - * $info: the details: - * - * Here is the list of $info members per $action event code: - * - * 'upload': - * - * $info[] contains: - * - * 'legal_dir_url' (string) LEGAL URI path to the directory where the file is being uploaded. You may invoke - * $dir = $mgr->legal_url_path2file_path($legal_dir_url); - * to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or - * $dir_url = $mgr->legal2abs_url_path($legal_dir_url); - * to obtain the absolute URI path for the given directory. - * - * 'dir' (string) physical filesystem path to the directory where the file is being uploaded. - * - * 'raw_filename' (string) the raw, unprocessed filename of the file being being uploaded, as specified by the client. - * - * WARNING: 'raw_filename' may contain anything illegal, such as directory paths instead of just a filename, - * filesystem-illegal characters and what-not. Use 'name'+'extension' instead if you want to know - * where the upload will end up. - * - * 'filename' (string) the filename, plus extension, of the file being uploaded; this filename is ensured - * to be both filesystem-legal, unique and not yet existing in the given directory. - * - * Note that the file name extension has already been cleaned, including 'safe' mode processing, - * i.e. any uploaded binary executable will have been assigned the extension '.txt' already, when - * FileManager's options['safe'] is enabled. - * - * 'tmp_filepath' (string) filesystem path pointing at the temporary storage location of the uploaded file: you can - * access the file data available here to optionally validate the uploaded content. - * - * 'meta_data' (array) the content sniffed infor as produced by getID3 - * - * 'mime' (string) the mime type as sniffed from the file - * - * 'mime_filter' (optional, string) mime filter as specified by the client: a comma-separated string containing - * full or partial mime types, where a 'partial' mime types is the part of a mime type before - * and including the slash, e.g. 'image/' - * - * 'mime_filters' (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. - * - * 'size' (integer) number of bytes of the uploaded file - * - * 'maxsize' (integer) the configured maximum number of bytes for any single upload - * - * 'overwrite' (boolean) FALSE: the uploaded file will not overwrite any existing file, it will fail instead. - * - * Set to TRUE (and adjust the 'name' and 'extension' entries as you desire) when you wish to overwrite - * an existing file. - * - * 'resize' (boolean) TRUE: any uploaded images are resized to the configured maximum dimensions before they - * are stored on disk. - * - * 'chmod' (integer) UNIX access rights (default: 0666) for the file-to-be-created (RW for user,group,world). - * - * Note that the eXecutable bits have already been stripped before the callback was invoked. - * - * 'preliminary_json' (array) the JSON data collected so far; when ['status']==1, then we're performing a regular upload - * operation, when the ['status']==0, we are performing a defective upload operation. - * - * 'validation_failure' (string) NULL: no validation error has been detected before the callback was invoked; non-NULL, e.g. - * "nofile": the string passed as message parameter of the FileManagerException, which will be thrown - * after the callback has returned. (You may alter the 'validation_failure' string value to change the - * reported error, or set it to NULL to turn off the validation error report entirely -- we assume you - * will have corrected the other fileinfo[] items as well, when resetting the validation error. - * - * - * Note that this request originates from a Macromedia Flash client: hence you'll need to use the - * $_POST[session_name()] value to manually set the PHP session_id() before you start your your session - * again. - * - * The frontend-specified options.propagateData items will be available as $_POST[] items. - * - * The frontend-specified options.uploadAuthData items will be available as $_POST[] items. - * - * - * 'download': - * - * $info[] contains: - * - * 'legal_url' (string) LEGAL URI path to the file to be downloaded. You may invoke - * $dir = $mgr->legal_url_path2file_path($legal_url); - * to obtain the physical filesystem path (also available in the 'file' $info entry, by the way!), or - * $url = $mgr->legal2abs_url_path($legal_url); - * to obtain the absolute URI path for the given file. - * - * 'file' (string) physical filesystem path to the file being downloaded. - * - * 'meta_data' (array) the content sniffed infor as produced by getID3 - * - * 'mime' (string) the mime type as sniffed from the file - * - * 'mime_filter' (optional, string) mime filter as specified by the client: a comma-separated string containing - * full or partial mime types, where a 'partial' mime types is the part of a mime type before - * and including the slash, e.g. 'image/' - * - * 'mime_filters' (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. - * - * 'validation_failure' (string) NULL: no validation error has been detected before the callback was invoked; non-NULL, e.g. - * "nofile": the string passed as message parameter of the FileManagerException, which will be thrown - * after the callback has returned. (You may alter the 'validation_failure' string value to change the - * reported error, or set it to NULL to turn off the validation error report entirely -- we assume you - * will have corrected the other fileinfo[] items as well, when resetting the validation error. - * - * The frontend-specified options.propagateData items will be available as $_POST[] items. - * - * - * 'create': // create directory - * - * $info[] contains: - * - * 'legal_url' (string) LEGAL URI path to the parent directory of the directory being created. You may invoke - * $dir = $mgr->legal_url_path2file_path($legal_url); - * to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or - * $url = $mgr->legal2abs_url_path($legal_url); - * to obtain the absolute URI path for this parent directory. - * - * 'dir' (string) physical filesystem path to the parent directory of the directory being created. - * - * 'raw_name' (string) the name of the directory to be created, as specified by the client (unfiltered!) - * - * 'uniq_name' (string) the name of the directory to be created, filtered and ensured to be both unique and - * not-yet-existing in the filesystem. - * - * 'newdir' (string) the filesystem absolute path to the directory to be created; identical to: - * $newdir = $mgr->legal_url_path2file_path($legal_url . $uniq_name); - * Note the above: all paths are transformed from URI space to physical disk every time a change occurs; - * this allows us to map even not-existing 'directories' to possibly disparate filesystem locations. - * - * 'chmod' (integer) UNIX access rights (default: 0777) for the directory-to-be-created (RWX for user,group,world) - * - * 'preliminary_json' (array) the JSON data collected so far; when ['status']==1, then we're performing a regular 'create' - * operation, when the ['status']==0, we are performing a defective 'create' operation. - * - * 'validation_failure' (string) NULL: no validation error has been detected before the callback was invoked; non-NULL, e.g. - * "nofile": the string passed as message parameter of the FileManagerException, which will be thrown - * after the callback has returned. (You may alter the 'validation_failure' string value to change the - * reported error, or set it to NULL to turn off the validation error report entirely -- we assume you - * will have corrected the other fileinfo[] items as well, when resetting the validation error. - * - * The frontend-specified options.propagateData items will be available as $_POST[] items. - * - * - * 'destroy': - * - * $info[] contains: - * - * 'legal_url' (string) LEGAL URI path to the file/directory to be deleted. You may invoke - * $dir = $mgr->legal_url_path2file_path($legal_url); - * to obtain the physical filesystem path (also available in the 'file' $info entry, by the way!), or - * $url = $mgr->legal2abs_url_path($legal_url); - * to obtain the absolute URI path for the given file/directory. - * - * 'file' (string) physical filesystem path to the file/directory being deleted. - * - * 'meta_data' (array) the content sniffed infor as produced by getID3 - * - * 'mime' (string) the mime type as sniffed from the file / directory (directories are mime type: 'text/directory') - * - * 'mime_filter' (optional, string) mime filter as specified by the client: a comma-separated string containing - * full or partial mime types, where a 'partial' mime types is the part of a mime type before - * and including the slash, e.g. 'image/' - * - * 'mime_filters' (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. - * - * Note that the 'mime_filters', if any, are applied to the 'delete' operation in a special way: only - * files matching one of the mime types in this list will be deleted; anything else will remain intact. - * This can be used to selectively clean a directory tree. - * - * The design idea behind this approach is that you are only allowed what you can see ('view'), so - * all 'view' restrictions should equally to the 'delete' operation. - * - * 'preliminary_json' (array) the JSON data collected so far; when ['status']==1, then we're performing a regular 'destroy' - * operation, when the ['status']==0, we are performing a defective 'destroy' operation. - * - * 'validation_failure' (string) NULL: no validation error has been detected before the callback was invoked; non-NULL, e.g. - * "nofile": the string passed as message parameter of the FileManagerException, which will be thrown - * after the callback has returned. (You may alter the 'validation_failure' string value to change the - * reported error, or set it to NULL to turn off the validation error report entirely -- we assume you - * will have corrected the other fileinfo[] items as well, when resetting the validation error. - * - * The frontend-specified options.propagateData items will be available as $_POST[] items. - * - * - * 'move': // move or copy! - * - * $info[] contains: - * - * 'legal_url' (string) LEGAL URI path to the source parent directory of the file/directory being moved/copied. You may invoke - * $dir = $mgr->legal_url_path2file_path($legal_url); - * to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or - * $url = $mgr->legal2abs_url_path($legal_url); - * to obtain the absolute URI path for the given directory. - * - * 'dir' (string) physical filesystem path to the source parent directory of the file/directory being moved/copied. - * - * 'path' (string) physical filesystem path to the file/directory being moved/copied itself; this is the full source path. - * - * 'name' (string) the name itself of the file/directory being moved/copied; this is the source name. - * - * 'legal_newurl' (string) LEGAL URI path to the target parent directory of the file/directory being moved/copied. You may invoke - * $dir = $mgr->legal_url_path2file_path($legal_url); - * to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or - * $url = $mgr->legal2abs_url_path($legal_url); - * to obtain the absolute URI path for the given directory. - * - * 'newdir' (string) physical filesystem path to the target parent directory of the file/directory being moved/copied; - * this is the full path of the directory where the file/directory will be moved/copied to. (filesystem absolute) - * - * 'newpath' (string) physical filesystem path to the target file/directory being moved/copied itself; this is the full destination path, - * i.e. the full path of where the file/directory should be renamed/moved to. (filesystem absolute) - * - * 'newname' (string) the target name itself of the file/directory being moved/copied; this is the destination name. - * - * This filename is ensured to be both filesystem-legal, unique and not yet existing in the given target directory. - * - * 'rename' (boolean) TRUE when a file/directory RENAME operation is requested (name change, staying within the same - * parent directory). FALSE otherwise. - * - * 'is_dir' (boolean) TRUE when the subject is a directory itself, FALSE when it is a regular file. - * - * 'function' (string) PHP call which will perform the operation. ('rename' or 'copy') - * - * 'preliminary_json' (array) the JSON data collected so far; when ['status']==1, then we're performing a regular 'move' - * operation, when the ['status']==0, we are performing a defective 'move' operation. - * - * 'validation_failure' (string) NULL: no validation error has been detected before the callback was invoked; non-NULL, e.g. - * "nofile": the string passed as message parameter of the FileManagerException, which will be thrown - * after the callback has returned. (You may alter the 'validation_failure' string value to change the - * reported error, or set it to NULL to turn off the validation error report entirely -- we assume you - * will have corrected the other fileinfo[] items as well, when resetting the validation error. - * - * The frontend-specified options.propagateData items will be available as $_POST[] items. - * - * - * 'view': - * - * $info[] contains: - * - * 'legal_url' (string) LEGAL URI path to the directory being viewed/scanned. You may invoke - * $dir = $mgr->legal_url_path2file_path($legal_url); - * to obtain the physical filesystem path (also available in the 'dir' $info entry, by the way!), or - * $url = $mgr->legal2abs_url_path($legal_url); - * to obtain the absolute URI path for the scanned directory. - * - * 'dir' (string) physical filesystem path to the directory being viewed/scanned. - * - * 'collection' (dual array of strings) arrays of files and directories (including '..' entry at the top when this is a - * subdirectory of the FM-managed tree): only names, not full paths. The files array is located at the - * ['files'] index, while the directories are available at the ['dirs'] index. - * - * 'meta_data' (array) the content sniffed infor as produced by getID3 - * - * 'mime_filter' (optional, string) mime filter as specified by the client: a comma-separated string containing - * full or partial mime types, where a 'partial' mime types is the part of a mime type before - * and including the slash, e.g. 'image/' - * - * 'mime_filters' (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. - * - * 'file_preselect' (optional, string) filename of a file in this directory which should be located and selected. - * When found, the backend will provide an index number pointing at the corresponding JSON files[] - * entry to assist the front-end in jumping to that particular item in the view. - * - * 'preliminary_json' (array) the JSON data collected so far; when ['status']==1, then we're performing a regular view - * operation (possibly as the second half of a copy/move/delete operation), when the ['status']==0, - * we are performing a view operation as the second part of another otherwise failed action, e.g. a - * failed 'create directory'. - * - * 'validation_failure' (string) NULL: no validation error has been detected before the callback was invoked; non-NULL, e.g. - * "nofile": the string passed as message parameter of the FileManagerException, which will be thrown - * after the callback has returned. (You may alter the 'validation_failure' string value to change the - * reported error, or set it to NULL to turn off the validation error report entirely -- we assume you - * will have corrected the other fileinfo[] items as well, when resetting the validation error. - * - * The frontend-specified options.propagateData items will be available as $_POST[] items. - * - * - * 'detail': - * - * $info[] contains: - * - * 'legal_url' (string) LEGAL URI path to the file/directory being inspected. You may invoke - * $dir = $mgr->legal_url_path2file_path($legal_url); - * to obtain the physical filesystem path (also available in the 'file' $info entry, by the way!), or - * $url = $mgr->legal2abs_url_path($legal_url); - * to obtain the absolute URI path for the given file. - * - * 'file' (string) physical filesystem path to the file being inspected. - * - * 'meta_data' (array) the content sniffed infor as produced by getID3 - * - * 'mime' (string) the mime type as sniffed from the file - * - * 'mime_filter' (optional, string) mime filter as specified by the client: a comma-separated string containing - * full or partial mime types, where a 'partial' mime types is the part of a mime type before - * and including the slash, e.g. 'image/' - * - * 'mime_filters' (optional, array of strings) the set of allowed mime types, derived from the 'mime_filter' setting. - * - * 'preliminary_json' (array) the JSON data collected so far; when ['status']==1, then we're performing a regular 'detail' - * operation, when the ['status']==0, we are performing a defective 'detail' operation. - * - * 'validation_failure' (string) NULL: no validation error has been detected before the callback was invoked; non-NULL, e.g. - * "nofile": the string passed as message parameter of the FileManagerException, which will be thrown - * after the callback has returned. (You may alter the 'validation_failure' string value to change the - * reported error, or set it to NULL to turn off the validation error report entirely -- we assume you - * will have corrected the other fileinfo[] items as well, when resetting the validation error. - * - * The frontend-specified options.propagateData items will be available as $_POST[] items. - * - * - * - * Developer Notes: - * - * - member functions which have a commented out 'static' keyword have it removed by design: it makes for easier overloading through - * inheritance that way and meanwhile there's no pressing need to have those (public) member functions acccessible from the outside world - * without having an instance of the FileManager class itself round at the same time. */ // ----------- compatibility checks ---------------------------------------------------------------------------- if (version_compare(PHP_VERSION, '5.2.0') < 0) { // die horribly: server does not match our requirements! - header('HTTP/1.0 500 FileManager requires PHP 5.2.0 or later', true, 500); // Internal server error + header('HTTP/1.0 500 FileManager requires PHP 5.2.0 or later', TRUE, 500); // Internal server error throw Exception('FileManager requires PHP 5.2.0 or later'); // this exception will most probably not be caught; that's our intent! } -if (function_exists('UploadIsAuthenticated')) -{ - // die horribly: user has not upgraded his callback hook(s)! - header('HTTP/1.0 500 FileManager callback has not been upgraded!', true, 500); // Internal server error - throw Exception('FileManager callback has not been upgraded!'); // this exception will most probably not be caught; that's our intent! -} - //------------------------------------------------------------------------------------------------------------- if (!defined('DEVELOPMENT')) define('DEVELOPMENT', 0); // make sure this #define is always known to us - require(strtr(dirname(__FILE__), '\\', '/') . '/Tooling.php'); require(strtr(dirname(__FILE__), '\\', '/') . '/Image.class.php'); require(strtr(dirname(__FILE__), '\\', '/') . '/Assets/getid3/getid3.php'); +/* +require(strtr(dirname(__FILE__), '\\', '/') . '/Filemanager/Tooling.php'); +require(strtr(dirname(__FILE__), '\\', '/') . '/Filemanager/Image.class.php'); +require(strtr(dirname(__FILE__), '\\', '/') . '/getid3/getid3.php'); +*/ + + + + +# Should log to the same directory as this file +require(strtr(dirname(__FILE__), '\\', '/') . '/KLogger.php'); + +/* +$log->logInfo('Info Test'); +$log->logNotice('Notice Test'); +$log->logWarn('Warn Test'); +$log->logError('Error Test'); +$log->logFatal('Fatal Test'); +$log->logAlert('Alert Test'); +$log->logCrit('Crit test'); +$log->logEmerg('Emerg Test'); +*/ + + + // the jpeg quality for the largest thumbnails (smaller ones are automatically done at increasingly higher quality) -define('MTFM_THUMBNAIL_JPEG_QUALITY', 75); +define('MTFM_THUMBNAIL_JPEG_QUALITY', 80); // the number of directory levels in the thumbnail cache; set to 2 when you expect to handle huge image collections. // @@ -542,7 +119,7 @@ define('MTFM_MIN_GETID3_CACHESIZE', 16); // allow MTFM to use finfo_open() to help us produce mime types for files. This is slower than the basic file extension to mimetype mapping -define('MTFM_USE_FINFO_OPEN', false); +define('MTFM_USE_FINFO_OPEN', FALSE); @@ -552,13 +129,6 @@ // flags for clean_ID3info_results() define('MTFM_CLEAN_ID3_STRIP_EMBEDDED_IMAGES', 0x0001); -// 'UsageMode' bits; these work as bit flags: -define('MTFM_USAGE_ASYNC_THUMB250_PRODUCTION', 0x0001); -define('MTFM_USAGE_ASYNC_THUMB48_PRODUCTION', 0x0002); -define('MTFM_USAGE_AGGRESSIVE_META_INFO_CACHING', 0x0004); -// and some aggregates for ease of use: -define('MTFM_USAGE_BASIC', 0); -define('MTFM_USAGE_SPEED_FREAK', MTFM_USAGE_ASYNC_THUMB250_PRODUCTION | MTFM_USAGE_ASYNC_THUMB48_PRODUCTION | MTFM_USAGE_AGGRESSIVE_META_INFO_CACHING); @@ -571,7 +141,7 @@ * * Makes sure the generated (thumbpath) template is unique for each source file ('$legal_url'). We prevent * reduced performance for large file sets: all thumbnails/templates derived from any files in the entire - * FileManager-managed directory tree, rooted by options['directory'], can become a huge collection, + * FileManager-managed directory tree, rooted by options['documentRootPath'], can become a huge collection, * so we distribute them across a (thumbnail/cache) directory tree, which is created on demand. * * The thumbnails cache directory tree is determined by the MD5 of the full path to the source file ($legal_url), @@ -599,23 +169,28 @@ class MTFMCacheItem protected $cache_file; - public function __construct($fm_obj, $legal_url, $prefetch = false, $persistent_edits = true) + public function __construct($fm_obj, $legal_url, $prefetch = FALSE, $persistent_edits = TRUE) { $this->init($fm_obj, $legal_url, $prefetch, $persistent_edits); + + $this->log = KLogger::instance(dirname(__FILE__), KLogger::DEBUG); + + } - public function init($fm_obj, $legal_url, $prefetch = false, $persistent_edits) + public function init($fm_obj, $legal_url, $prefetch = FALSE, $persistent_edits) { - $this->dirty = false; + $this->dirty = FALSE; $this->persistent_edits = $persistent_edits; - $this->loaded = false; + $this->loaded = FALSE; $this->store = array(); $fmopts = $fm_obj->getSettings(); $this->legal_url = $legal_url; - $this->file = $fm_obj->legal_url_path2file_path($legal_url); - $this->fstat = null; +// $this->file = $fm_obj->legal_url_path2file_path($legal_url); + $this->file = $fm_obj->getAbsolutePath($legal_url); + $this->fstat = NULL; $fi = pathinfo($legal_url); if (is_dir($this->file)) @@ -667,7 +242,11 @@ public function init($fm_obj, $legal_url, $prefetch = false, $persistent_edits) $dircode = substr($dircode, 4); $fn = substr($fn . $dircode, 0, 38); - $this->cache_dir_url = $fmopts['thumbnailPath'] . $dir; + $this->cache_dir_url = $fmopts['thumbnailCacheUrl'] . $dir; + + + + $this->cache_dir = $fmopts['thumbnailCacheDir'] . $dir; $this->cache_dir_mode = $fmopts['chmod']; $this->cache_base = $fn; @@ -686,7 +265,7 @@ public function load() { if (!$this->loaded) { - $this->loaded = true; // always mark as loaded, even when the load fails + $this->loaded = TRUE; // always mark as loaded, even when the load fails if (!is_array($this->fstat) && file_exists($this->file)) { @@ -702,7 +281,7 @@ public function load() && $statdata[7] == $this->fstat[7] // size ) { - if (!DEVELOPMENT) + if ( ! DEVELOPMENT) { // mix disk cache data with items already existing in RAM cache: we use a delayed-load scheme which necessitates this. $this->store = array_merge($data, $this->store); @@ -717,9 +296,9 @@ public function load() } } - public function delete($every_ting_baby = false) + public function delete($every_ting_baby = FALSE) { - $rv = true; + $rv = TRUE; $dir = $this->cache_dir; $dir_exists = file_exists($dir); @@ -731,7 +310,7 @@ public function delete($every_ting_baby = false) $dir_and_mask = $dir . $this->cache_base . '*'; $coll = safe_glob($dir_and_mask, GLOB_NODOTS | GLOB_NOSORT); - if ($coll !== false) + if ($coll !== FALSE) { foreach($coll['files'] as $filename) { @@ -759,8 +338,8 @@ public function delete($every_ting_baby = false) } // also clear the data cached in RAM: - $this->dirty = false; - $this->loaded = true; // we know the cache file doesn't exist any longer, so don't bother trying to load it again later on! + $this->dirty = FALSE; + $this->loaded = TRUE; // we know the cache file doesn't exist any longer, so don't bother trying to load it again later on! $this->store = array(); return $rv; @@ -786,9 +365,9 @@ public function __destruct() // legal URL: ' . $this->legal_url . ' -$statdata = ' . var_export($this->fstat, true) . '; +$statdata = ' . var_export($this->fstat, TRUE) . '; -$data = ' . var_export($this->store, true) . ';' . PHP_EOL; +$data = ' . var_export($this->store, TRUE) . ';' . PHP_EOL; @file_put_contents($this->cache_file, $data); } @@ -797,7 +376,7 @@ public function __destruct() /* * @param boolean $persistent (default: TRUE) TRUE when we should also check the persistent cache storage for this item/key */ - public function fetch($key, $persistent = true) + public function fetch($key, $persistent = TRUE) { if (isset($this->store[$key])) { @@ -813,13 +392,13 @@ public function fetch($key, $persistent = true) } } - return null; + return NULL; } /* * @param boolean $persistent (default: TRUE) TRUE when we should also store this item/key in the persistent cache storage */ - public function store($key, $value, $persistent = true) + public function store($key, $value, $persistent = TRUE) { if (isset($this->store[$key])) { @@ -832,13 +411,13 @@ public function store($key, $value, $persistent = true) public function getThumbPath($dimensions) { - assert(!empty($dimensions)); + assert( ! empty($dimensions)); return $this->cache_dir . $this->cache_base . '-' . $dimensions . $this->cache_tnext; } public function getThumbURL($dimensions) { - assert(!empty($dimensions)); + assert( ! empty($dimensions)); return $this->cache_dir_url . $this->cache_base . '-' . $dimensions . $this->cache_tnext; } @@ -846,20 +425,20 @@ public function mkCacheDir() { if (!is_dir($this->cache_dir)) { - @mkdir($this->cache_dir, $this->cache_dir_mode, true); - return true; + @mkdir($this->cache_dir, $this->cache_dir_mode, TRUE); + return TRUE; } - return false; + return FALSE; } public function getMimeType() { - if (!empty($this->store['mime_type'])) + if ( ! empty($this->store['mime_type'])) { return $this->store['mime_type']; } //$mime = $fm_obj->getMimeFromExt($file); - return null; + return NULL; } } @@ -891,9 +470,9 @@ public function __construct($min_cache_size) * * You can store any arbitrary data in a cache slot: it doesn't have to be a MTFMCacheItem instance. */ - public function &pick($key, $fm_obj = null, $create_if_not_exist = true) + public function &pick($key, $fm_obj = NULL, $create_if_not_exist = TRUE) { - assert(!empty($key)); + assert( ! empty($key)); $age_limit = $this->store_lru_ts - $this->min_cache_size; @@ -955,20 +534,20 @@ public function &pick($key, $fm_obj = null, $create_if_not_exist = true) * add this slot (empty for now) to the cache. Only do this AFTER the pruning, so it won't risk being * picked by the random process in there. We _need_ this one right now. ;-) */ - $this->store[$key] = (!empty($fm_obj) ? new MTFMCacheItem($fm_obj, $key) : null); + $this->store[$key] = ( ! empty($fm_obj) ? new MTFMCacheItem($fm_obj, $key) : NULL); $this->store_ts[$key] = $this->store_lru_ts++; } else { // do not clutter the cache; all we're probably after this time is the assistance of a MTFMCacheItem: - // provide a dummy cache entry, nulled and all; we won't be saving the stored data, if any, anyhow. + // provide a dummy cache entry, NULLed and all; we won't be saving the stored data, if any, anyhow. if (isset($this->store['!']) && !empty($fm_obj)) { - $this->store['!']->init($fm_obj, $key, false, false); + $this->store['!']->init($fm_obj, $key, FALSE, FALSE); } else { - $this->store['!'] = (!empty($fm_obj) ? new MTFMCacheItem($fm_obj, $key, false, false) : null); + $this->store['!'] = ( ! empty($fm_obj) ? new MTFMCacheItem($fm_obj, $key, FALSE, FALSE) : NULL); } $this->store_ts['!'] = 0; $key = '!'; @@ -989,186 +568,168 @@ class FileManager protected $options; protected $getid3; protected $getid3_cache; - protected $icon_cache; // cache the icon paths per size (large/small) and file extension + protected $icon_cache; // cache the icon paths per size (large/small) and file extension + protected $documentRootPath; protected $thumbnailCacheDir; - protected $thumbnailCacheParentDir; // assistant precalculated value for scandir/view - protected $managedBaseDir; // precalculated filesystem path eqv. of options['directory'] - + protected $thumbnailCacheParentDir; // assistant precalculated value for scandir/view + protected $managedBaseDir; // precalculated filesystem path eqv. of options['documentRootPath'] + protected $assetPath; // Assets absolute filesystem path + + protected $mimeTypesFilePath; + + public function __construct($options) { + // LOG + $this->log = KLogger::instance(dirname(__FILE__), KLogger::DEBUG); + $this->options = array_merge(array( + /* * Note that all default paths as listed below are transformed to DocumentRoot-based paths * through the getRealPath() invocations further below: */ - 'directory' => null, // the root of the 'legal URI' directory tree, to be managed by MTFM. MUST be in the DocumentRoot tree. - 'assetBasePath' => null, // may sit outside options['directory'] but MUST be in the DocumentRoot tree - 'thumbnailPath' => null, // may sit outside options['directory'] but MUST be in the DocumentRoot tree - 'mimeTypesPath' => strtr(dirname(__FILE__), '\\', '/') . '/MimeTypes.ini', // an absolute filesystem path anywhere; when relative, it will be assumed to be against SERVER['SCRIPT_NAME'] - 'documentRootPath' => null, // an absolute filesystem path pointing at URI path '/'. Default: SERVER['DOCUMENT_ROOT'] + 'requestScript' => NULL, // default is $_SERVER['SCRIPT_NAME'] + + 'baseUrl' => NULL, // an absolute filesystem path pointing at URI path '/'. Default: SERVER['DOCUMENT_ROOT'] + 'documentRootPath' => NULL, // an absolute filesystem path pointing at URI path '/'. Default: SERVER['DOCUMENT_ROOT'] + 'directory' => NULL, // Relative to the the DocumentRoot path. + 'assetPath' => NULL, // may sit outside options['documentRootPath'] but MUST be in the DocumentRoot tree + 'thumbnailPath' => NULL, // may sit outside options['documentRootPath'] but MUST be in the DocumentRoot tree + + 'thumbSmallSize' => 48, // Used for thumb48 creation + 'thumbBigSize' => 250, // Used for thumb250 creation + 'mimeTypesFilePath' => strtr(dirname(__FILE__), '\\', '/') . '/MimeTypes.ini', // an absolute filesystem path anywhere; when relative, it will be assumed to be against options['requestScript'] + 'dateFormat' => 'j M Y - H:i', 'maxUploadSize' => 2600 * 2600 * 3, - // 'maxImageSize' => 99999, // OBSOLETED, replaced by 'suggestedMaxImageDimension' - 'maxImageDimension' => array('width' => 1024, 'height' => 768), // Allow to specify the "Resize Large Images" tolerance level. - 'upload' => false, - 'destroy' => false, - 'create' => false, - 'move' => false, - 'download' => false, - /* ^^^ this last one is easily circumnavigated if it's about images: when you can view 'em, you can 'download' them anyway. - * However, for other mime types which are not previewable / viewable 'in their full bluntal nugity' ;-) , this will - * be a strong deterent. - * - * Think Springer Verlag and PDFs, for instance. You can have 'em, but only /after/ you've ... - */ - 'allowExtChange' => false, - 'safe' => true, - 'filter' => null, + + 'maxImageDimension' => array('width' => 1024, 'height' => 768), // Allow to specify the "Resize Large Images" tolerance level. + 'upload' => FALSE, + 'destroy' => FALSE, + 'create' => FALSE, + 'move' => FALSE, + 'download' => FALSE, + + 'allowExtChange' => FALSE, + 'safe' => TRUE, + 'filter' => NULL, 'chmod' => 0777, - 'ViewIsAuthorized_cb' => null, - 'DetailIsAuthorized_cb' => null, - 'UploadIsAuthorized_cb' => null, - 'DownloadIsAuthorized_cb' => null, - 'CreateIsAuthorized_cb' => null, - 'DestroyIsAuthorized_cb' => null, - 'MoveIsAuthorized_cb' => null, - 'showHiddenFoldersAndFiles' => false, // Hide dot dirs/files ? - 'UsageMode' => MTFM_USAGE_BASIC + 'cleanFileName' => TRUE, + + 'ViewIsAuthorized_cb' => NULL, + 'DetailIsAuthorized_cb' => NULL, + 'UploadIsAuthorized_cb' => NULL, + 'DownloadIsAuthorized_cb' => NULL, + 'CreateIsAuthorized_cb' => NULL, + 'DestroyIsAuthorized_cb' => NULL, + 'MoveIsAuthorized_cb' => NULL, + 'showHiddenFoldersAndFiles' => FALSE // Hide dot dirs/files ? ), (is_array($options) ? $options : array())); - // transform the obsoleted/deprecated options: - if (!empty($this->options['maxImageSize']) && $this->options['maxImageSize'] != 1024 && $this->options['maxImageDimension']['width'] == 1024 && $this->options['maxImageDimension']['height'] == 768) - { - $this->options['maxImageDimension'] = array('width' => $this->options['maxImageSize'], 'height' => $this->options['maxImageSize']); - } - - $assumed_root = null; - if (!empty($this->options['documentRootPath'])) - { + $assumed_root = NULL; + if ( ! empty($this->options['documentRootPath'])) $assumed_root = realpath($this->options['documentRootPath']); - } + if (empty($assumed_root)) - { $assumed_root = realpath($_SERVER['DOCUMENT_ROOT']); - } + $assumed_root = strtr($assumed_root, '\\', '/'); $assumed_root = rtrim($assumed_root, '/'); - $this->options['documentRootPath'] = $assumed_root; - // only calculate the guestimated defaults when they are indeed required: - if ($this->options['directory'] == null || $this->options['assetBasePath'] == null || $this->options['thumbnailPath'] == null) - { - $my_path = @realpath(dirname(__FILE__)); - $my_path = strtr($my_path, '\\', '/'); - if (!FileManagerUtility::endsWith($my_path, '/')) - { - $my_path .= '/'; - } - $my_assumed_url_path = str_replace($assumed_root, '', $my_path); - // we throw an Exception here because when these do not apply, the user should have specified all three these entries! - if (empty($assumed_root) || empty($my_path) || !FileManagerUtility::startsWith($my_path, $assumed_root)) - { - throw new FileManagerException('nofile'); - } - - if ($this->options['directory'] == null) - { - $this->options['directory'] = $my_assumed_url_path . '../../Demos/Files/'; - } - if ($this->options['assetBasePath'] == null) - { - $this->options['assetBasePath'] = $my_assumed_url_path . '../../Demos/Files/../../Assets/'; - } - if ($this->options['thumbnailPath'] == null) - { - $this->options['thumbnailPath'] = $my_assumed_url_path . '../../Demos/Files/../../Assets/Thumbs/'; - } - } - - /* - * make sure we start with a very predictable and LEGAL options['directory'] setting, so that the checks applied to the - * (possibly) user specified value for this bugger acvtually can check out okay AS LONG AS IT'S INSIDE the DocumentRoot-based - * directory tree: - */ - $new_root = $this->options['directory']; - $this->options['directory'] = '/'; // use DocumentRoot temporarily as THE root for this optional transform - $this->options['directory'] = $this->rel2abs_url_path($new_root . '/'); + // apply default to requestScript: + if (empty($this->options['requestScript'])) + $this->options['requestScript'] = $this->getRequestURI(); - $this->managedBaseDir = $this->url_path2file_path($this->options['directory']); + $this->options['thumbnailPath'] = self::addTrailingSlash($this->options['thumbnailPath']); - // now that the correct options['directory'] has been set up, go and check/clean the other paths in the options[]: + $this->baseUrl = $this->options['baseUrl']; + + $this->documentRootPath = self::addTrailingSlash($assumed_root); + + // Assets + $this->options['assetPath'] = self::addTrailingSlash($this->options['assetPath']); + $this->assetPath = self::addTrailingSlash(realpath($this->documentRootPath . $this->options['assetPath'])); + $this->assetUrl = $this->baseUrl . trim($this->options['assetPath'], '/') .'/'; + + $this->managedBaseDir = $this->documentRootPath . trim($this->options['directory'], '/') .'/'; + $this->managedBaseUrl = $this->baseUrl . trim($this->options['directory'], '/') .'/'; - $this->options['thumbnailPath'] = $this->rel2abs_url_path($this->options['thumbnailPath'] . '/'); - $this->thumbnailCacheDir = $this->url_path2file_path($this->options['thumbnailPath']); // precalculate this value; safe as we can assume the entire cache dirtree maps 1:1 to filesystem. - $this->thumbnailCacheParentDir = $this->url_path2file_path(self::getParentDir($this->options['thumbnailPath'])); // precalculate this value as well; used by scandir/view + // By default for the moment + // This way of config allows to change easily the store folder of thumbs if wished + $this->thumbnailCacheDir = $this->documentRootPath . trim($this->options['thumbnailPath'], '/') .'/'; + $this->thumbnailCacheUrl = $this->baseUrl . trim($this->options['thumbnailPath'], '/') .'/'; + $this->thumbnailCacheParentDir = $this->documentRootPath . (self::getParentDir($this->options['thumbnailPath'])); - $this->options['assetBasePath'] = $this->rel2abs_url_path($this->options['assetBasePath'] . '/'); + // Mimes types file + $mimeTypesFilePath = @realpath($this->options['mimeTypesFilePath']); - $this->options['mimeTypesPath'] = @realpath($this->options['mimeTypesPath']); - if (empty($this->options['mimeTypesPath'])) - { + if (empty($mimeTypesFilePath)) throw new FileManagerException('nofile'); - } - $this->options['mimeTypesPath'] = strtr($this->options['mimeTypesPath'], '\\', '/'); + + $this->mimeTypesFilePath = strtr($mimeTypesFilePath, '\\', '/'); // getID3 is slower as it *copies* the image to the temp dir before processing: see GetDataImageSize(). // This is done as getID3 can also analyze *embedded* images, for which this approach is required. $this->getid3 = new getID3(); $this->getid3->setOption(array('encoding' => 'UTF-8')); - //$this->getid3->encoding = 'UTF-8'; - $this->getid3_cache = new MTFMCache(MTFM_MIN_GETID3_CACHESIZE); $this->icon_cache = array(array(), array()); + } - + + /** - * @return array the FileManager options and settings. + * Returns the FileManager options and settings. + * Used by MTFMCacheItem + * + * @return Array Array if settings + * */ public function getSettings() { return array_merge(array( - 'basedir' => $this->url_path2file_path($this->options['directory']), - 'thumbnailCacheDir' => $this->thumbnailCacheDir, - 'thumbnailCacheParentDir' => $this->thumbnailCacheParentDir, - 'managedBaseDir' => $this->managedBaseDir + 'thumbnailCacheUrl' => $this->thumbnailCacheUrl, + 'thumbnailCacheDir' => $this->thumbnailCacheDir, + 'thumbnailCacheParentDir' => $this->thumbnailCacheParentDir, + 'managedBaseDir' => $this->managedBaseDir, + 'chmod' => $this->options['chmod'] ), $this->options); } - - /** * Central entry point for any client side request. */ - public function fireEvent($event = null) + public function fireEvent($event = NULL) { - $event = (!empty($event) ? 'on' . ucfirst($event) : null); + $event = ( ! empty($event) ? 'on' . ucfirst($event) : NULL); if (!$event || !method_exists($this, $event)) $event = 'onView'; $this->{$event}(); } - - - - /** * Generalized 'view' handler, which produces a directory listing. * * Return the directory listing in a nested array, suitable for JSON encoding. */ - protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg = null, $filemask = '*') + protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg = NULL, $filemask = '*') { $v_ex_code = 'nofile'; - $dir = $this->legal_url_path2file_path($legal_url); - $doubledot = null; - $coll = null; +// $dir = $this->legal_url_path2file_path($legal_url); + + $dir = $this->managedBaseDir . $legal_url; + + $doubledot = NULL; + $coll = NULL; + if (is_dir($dir)) { /* @@ -1201,8 +762,8 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg * Code: TODO */ - $coll = $this->scandir($dir, $filemask, false, 0, ($this->options['showHiddenFoldersAndFiles'] ? ~GLOB_NOHIDDEN : ~0)); - if ($coll !== false) + $coll = $this->scandir($dir, $filemask, FALSE, 0, ($this->options['showHiddenFoldersAndFiles'] ? ~GLOB_NOHIDDEN : ~0)); + if ($coll !== FALSE) { /* * To ensure '..' ends up at the very top of the view, no matter what the other entries in $coll['dirs'][] are made of, @@ -1210,15 +771,15 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg * let the sort run. */ $doubledot = array_pop($coll['dirs']); - if ($doubledot !== null && $doubledot !== '..') + if ($doubledot !== NULL && $doubledot !== '..') { $coll['dirs'][] = $doubledot; - $doubledot = null; + $doubledot = NULL; } natcasesort($coll['dirs']); natcasesort($coll['files']); - $v_ex_code = null; + $v_ex_code = NULL; } } @@ -1235,12 +796,12 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg 'validation_failure' => $v_ex_code ); - if (!empty($this->options['ViewIsAuthorized_cb']) && function_exists($this->options['ViewIsAuthorized_cb']) && !$this->options['ViewIsAuthorized_cb']($this, 'view', $fileinfo)) + if ( ! empty($this->options['ViewIsAuthorized_cb']) && function_exists($this->options['ViewIsAuthorized_cb']) && !$this->options['ViewIsAuthorized_cb']($this, 'view', $fileinfo)) { $v_ex_code = $fileinfo['validation_failure']; if (empty($v_ex_code)) $v_ex_code = 'authorized'; } - if (!empty($v_ex_code)) + if ( ! empty($v_ex_code)) throw new FileManagerException($v_ex_code); $legal_url = $fileinfo['legal_url']; @@ -1255,9 +816,9 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg $out = array(array(), array()); $mime = 'text/directory'; - $iconspec = false; + $iconspec = FALSE; - if ($doubledot !== null) + if ($doubledot !== NULL) { $filename = '..'; @@ -1266,12 +827,17 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: $file = $this->legal_url_path2file_path($l_url); +/* +$this->log->logInfo('$l_url : ' . $l_url); +$this->log->logInfo('$file : ' . $file); +*/ + $iconspec = 'is.directory_up'; - $icon48 = $this->getIcon($iconspec, false); + $icon48 = $this->getIcon($iconspec, FALSE); $icon48_e = FileManagerUtility::rawurlencode_path($icon48); - $icon = $this->getIcon($iconspec, true); + $icon = $this->getIcon($iconspec, TRUE); $icon_e = FileManagerUtility::rawurlencode_path($icon); $out[1][] = array( @@ -1286,10 +852,10 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg // now precalc the directory-common items (a.k.a. invariant computation / common subexpression hoisting) $iconspec_d = 'is.directory'; - $icon48_d = $this->getIcon($iconspec_d, false); + $icon48_d = $this->getIcon($iconspec_d, FALSE); $icon48_de = FileManagerUtility::rawurlencode_path($icon48_d); - $icon_d = $this->getIcon($iconspec_d, true); + $icon_d = $this->getIcon($iconspec_d, TRUE); $icon_de = FileManagerUtility::rawurlencode_path($icon_d); foreach ($coll['dirs'] as $filename) @@ -1336,10 +902,10 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg * For now, simply assign a basic icon to any and all; the 'detail' event will replace this item in the frontend * when the time has arrives when that 'detail' request has been answered. */ - $icon48 = $this->getIcon($iconspec, false); + $icon48 = $this->getIcon($iconspec, FALSE); $icon48_e = FileManagerUtility::rawurlencode_path($icon48); - $icon = $this->getIcon($iconspec, true); + $icon = $this->getIcon($iconspec, TRUE); $icon_e = FileManagerUtility::rawurlencode_path($icon); $out[0][] = array( @@ -1347,7 +913,7 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg 'name' => $filename, 'mime' => $mime, // we don't know the thumbnail paths yet --> this will trigger deferred requests: (event=detail, mode=direct) - 'thumbs_deferred' => true, + 'thumbs_deferred' => TRUE, 'icon48' => $icon48_e, 'icon' => $icon_e ); @@ -1355,20 +921,20 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg } return array_merge((is_array($json) ? $json : array()), array( - 'root' => substr($this->options['directory'], 1), - 'this_dir' => array( - 'path' => $legal_url, - 'name' => basename($legal_url), - 'date' => date($this->options['dateFormat'], @filemtime($dir)), - 'mime' => 'text/directory', - 'icon48' => $icon48_de, - 'icon' => $icon_de - ), - 'preselect_index' => ($file_preselect_index >= 0 ? $file_preselect_index + count($out[1]) + 1 : 0), - 'preselect_name' => ($file_preselect_index >= 0 ? $file_preselect_arg : null), - 'dirs' => $out[1], - 'files' => $out[0] - )); + 'root' => substr($this->documentRootPath, 1), + 'this_dir' => array( + 'path' => $legal_url, + 'name' => basename($legal_url), + 'date' => date($this->options['dateFormat'], @filemtime($dir)), + 'mime' => 'text/directory', + 'icon48' => $icon48_de, + 'icon' => $icon_de + ), + 'preselect_index' => ($file_preselect_index >= 0 ? $file_preselect_index + count($out[1]) + 1 : 0), + 'preselect_name' => ($file_preselect_index >= 0 ? $file_preselect_arg : NULL), + 'dirs' => $out[1], + 'files' => $out[0] + )); } /** @@ -1378,7 +944,7 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg * * Expected parameters: * - * $_POST['directory'] path relative to basedir a.k.a. options['directory'] root + * $_POST['directory'] path relative to basedir a.k.a. options['documentRootPath'] root * * $_POST['file_preselect'] optional filename or path: * when a filename, this is the filename of a file in this directory @@ -1400,9 +966,9 @@ protected function _onView($legal_url, $json, $mime_filter, $file_preselect_arg * transforms them to LEGAL URI paths. * * When the specified path is illegal, i.e. does not reside inside the - * options['directory']-rooted LEGAL URI subtree, it will be discarded + * options['documentRootPath']-rooted LEGAL URI subtree, it will be discarded * entirely (as all file paths, whether they are absolute or relative, - * must end up inside the options['directory']-rooted subtree to be + * must end up inside the options['documentRootPath']-rooted subtree to be * considered manageable files) and the process will continue as if * the $_POST['file_preselect'] entry had not been set. * @@ -1427,14 +993,11 @@ protected function onView() { // try to produce the view; if it b0rks, retry with the parent, until we've arrived at the basedir: // then we fail more severely. - - $emsg = null; - $jserr = array( - 'status' => 1 - ); + $emsg = NULL; + $jserr = array('status' => 1); $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); - $legal_url = null; + $legal_url = NULL; try { @@ -1444,10 +1007,10 @@ protected function onView() $file_preselect_arg = $this->getPOSTparam('file_preselect'); try { - if (!empty($file_preselect_arg)) + if ( ! empty($file_preselect_arg)) { // check if this a path instead of just a basename, then convert to legal_url and split across filename and directory. - if (strpos($file_preselect_arg, '/') !== false) + if (strpos($file_preselect_arg, '/') !== FALSE) { // this will also convert a relative path to an absolute path before transforming it to a LEGAL URI path: $legal_presel = $this->abs2legal_url_path($file_preselect_arg); @@ -1456,7 +1019,7 @@ protected function onView() $file_preselect_arg = $prseli['basename']; // override the directory! $legal_url = $prseli['dirname']; - $legal_url = self::enforceTrailingSlash($legal_url); + $legal_url = self::addTrailingSlash($legal_url); } else { @@ -1467,63 +1030,63 @@ protected function onView() catch(FileManagerException $e) { // discard the preselect input entirely: - $file_preselect_arg = null; + $file_preselect_arg = NULL; } } catch(FileManagerException $e) { $emsg = $e->getMessage(); $legal_url = '/'; - $file_preselect_arg = null; + $file_preselect_arg = NULL; } catch(Exception $e) { // catching other severe failures; since this can be anything it may not be a translation keyword in the message... $emsg = $e->getMessage(); $legal_url = '/'; - $file_preselect_arg = null; + $file_preselect_arg = NULL; } // loop until we drop below the bottomdir; meanwhile getDir() above guarantees that $dir is a subdir of bottomdir, hence dir >= bottomdir. $original_legal_url = $legal_url; + do { try { $rv = $this->_onView($legal_url, $jserr, $mime_filter, $file_preselect_arg); - - $this->sendHttpHeaders('Content-Type: application/json'); - +// $this->sendHttpHeaders('Content-Type: application/json'); echo json_encode($rv); return; } catch(FileManagerException $e) { - if ($emsg === null) + if ($emsg === NULL) $emsg = $e->getMessage(); } catch(Exception $e) { // catching other severe failures; since this can be anything it may not be a translation keyword in the message... - if ($emsg === null) + if ($emsg === NULL) $emsg = $e->getMessage(); } // step down to the parent dir and retry: $legal_url = self::getParentDir($legal_url); - $file_preselect_arg = null; + $file_preselect_arg = NULL; $jserr['status']++; - } while ($legal_url !== false); + } while ($legal_url !== FALSE); $this->modify_json4exception($jserr, $emsg, 'path = ' . $original_legal_url); $this->sendHttpHeaders('Content-Type: application/json'); // when we fail here, it's pretty darn bad and nothing to it. - // just push the error JSON as go. + // just push the error JSON and go. echo json_encode($jserr); + die(); } /** @@ -1534,7 +1097,7 @@ protected function onView() * * Expected parameters: * - * $_POST['directory'] path relative to basedir a.k.a. options['directory'] root + * $_POST['directory'] path relative to basedir a.k.a. options['documentRootPath'] root * * $_POST['file'] filename (including extension, of course) of the file to * be detailed. @@ -1553,6 +1116,16 @@ protected function onView() * will neglect to provide those, expecting the frontend to * delay-load them through another 'event=detail / mode=direct' * request later on. + * 'metaHTML': show the metadata as extra HTML content in + * the preview pane (you can also turn that off using CSS: + * div.filemanager div.filemanager-diag-dump + * { + * display: none; + * } + * 'metaJSON': deliver the extra getID3 metadata in JSON format + * in the json['metadata'] field. + * + * Modes can be mixed by adding a '+' between them. * * Errors will produce a JSON encoded error report, including at least two fields: * @@ -1562,94 +1135,84 @@ protected function onView() */ protected function onDetail() { - $emsg = null; - $legal_url = null; - $file_arg = null; + $emsg = NULL; + $legal_url = NULL; + $file_arg = NULL; $jserr = array( - 'status' => 1 - ); + 'status' => 1 + ); try { $v_ex_code = 'nofile'; $mode = $this->getPOSTparam('mode'); + $mode = explode('+', $mode); + if (empty($mode)) $mode = array(); - $file_arg = $this->getPOSTparam('file'); - - $dir_arg = $this->getPOSTparam('directory'); - $legal_url = $this->rel2abs_legal_url_path($dir_arg . '/'); - - $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); + $file_arg = $this->getPOSTparam('file'); + $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); $mime_filters = $this->getAllowedMimeTypes($mime_filter); - $filename = null; - $file = null; - $mime = null; - $meta = null; - if (!empty($file_arg)) + $filename = NULL; + $file = NULL; + $mime = NULL; + $meta = NULL; + + if ( ! empty($file_arg)) { $filename = basename($file_arg); - // must normalize the combo as the user CAN legitimally request filename == '.' (directory detail view) for this event! - $path = $this->rel2abs_legal_url_path($legal_url . $filename); - //echo " path = $path, ($legal_url . $filename);\n"; - $legal_url = $path; - // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: - $file = $this->legal_url_path2file_path($legal_url); + + // relative to document root + $rel_path = $this->normalize($this->normalize($this->getPOSTparam('directory')) . '/'. $filename); + + // absolute system file path + $file = $this->getAbsolutePath($rel_path); if (is_readable($file)) { if (is_file($file)) { - $meta = $this->getFileInfo($file, $legal_url); + $meta = $this->getFileInfo($file, $rel_path); $mime = $meta->getMimeType(); + if (!$this->IsAllowedMimeType($mime, $mime_filters)) - { $v_ex_code = 'extension'; - } else - { - $v_ex_code = null; - } + $v_ex_code = NULL; } else if (is_dir($file)) { $mime = 'text/directory'; - $v_ex_code = null; + $v_ex_code = NULL; } } } $fileinfo = array( - 'legal_url' => $legal_url, - 'file' => $file, - 'mode' => $mode, - 'meta_data' => $meta, - 'mime' => $mime, - 'mime_filter' => $mime_filter, - 'mime_filters' => $mime_filters, - 'preliminary_json' => $jserr, - 'validation_failure' => $v_ex_code - ); + 'legal_url' => $rel_path, + 'file' => $file, + 'mode' => $mode, + 'meta_data' => $meta, + 'mime' => $mime, + 'mime_filter' => $mime_filter, + 'mime_filters' => $mime_filters, + 'preliminary_json' => $jserr, + 'validation_failure' => $v_ex_code + ); - if (!empty($this->options['DetailIsAuthorized_cb']) && function_exists($this->options['DetailIsAuthorized_cb']) && !$this->options['DetailIsAuthorized_cb']($this, 'detail', $fileinfo)) + if ( ! empty($this->options['DetailIsAuthorized_cb']) && function_exists($this->options['DetailIsAuthorized_cb']) && !$this->options['DetailIsAuthorized_cb']($this, 'detail', $fileinfo)) { + $v_ex_code = $fileinfo['validation_failure']; if (empty($v_ex_code)) $v_ex_code = 'authorized'; } - if (!empty($v_ex_code)) + if ( ! empty($v_ex_code)) + { throw new FileManagerException($v_ex_code); + } - $legal_url = $fileinfo['legal_url']; - //$file = $fileinfo['file']; - $mode = $fileinfo['mode']; - $meta = $fileinfo['meta_data']; - //$mime = $fileinfo['mime']; - $mime_filter = $fileinfo['mime_filter']; - $mime_filters = $fileinfo['mime_filters']; - $jserr = $fileinfo['preliminary_json']; - - $jserr = $this->extractDetailInfo($jserr, $legal_url, $meta, $mime_filter, $mime_filters, $mode); + $jserr = $this->extractDetailInfo($jserr, $rel_path, $meta, $mime_filter, $mime_filters, $mode); $this->sendHttpHeaders('Content-Type: application/json'); @@ -1668,31 +1231,31 @@ protected function onDetail() $this->modify_json4exception($jserr, $emsg, 'file = ' . $file_arg . ', path = ' . $legal_url); - $icon48 = $this->getIconForError($emsg, 'is.default-error', false); + $icon48 = $this->getIconForError($emsg, 'is.default-error', FALSE); $icon48_e = FileManagerUtility::rawurlencode_path($icon48); - $icon = $this->getIconForError($emsg, 'is.default-error', true); + $icon = $this->getIconForError($emsg, 'is.default-error', TRUE); $icon_e = FileManagerUtility::rawurlencode_path($icon); - $jserr['thumb250'] = $icon48_e; - $jserr['thumb48'] = $icon48_e; + $jserr['thumb250'] = NULL; + $jserr['thumb48'] = NULL; $jserr['icon48'] = $icon48_e; $jserr['icon'] = $icon_e; $postdiag_err_HTML = '

' . $emsg . '

'; $preview_HTML = '${nopreview}'; $content = ''; - //$content .= '

${preview}

'; + $content .= '
' . $preview_HTML . '
'; //$content .= '

Diagnostics

'; //$content .= '
; $content .= '
' . $postdiag_err_HTML . '
'; - //$content .= '
'; + $json['content'] = self::compressHTML($content); $this->sendHttpHeaders('Content-Type: application/json'); // when we fail here, it's pretty darn bad and nothing to it. - // just push the error JSON as go. + // just push the error JSON and go. echo json_encode($jserr); } @@ -1706,7 +1269,7 @@ protected function onDetail() * * Expected parameters: * - * $_POST['directory'] path relative to basedir a.k.a. options['directory'] root + * $_POST['directory'] path relative to basedir a.k.a. options['documentRootPath'] root * * $_POST['file'] filename (including extension, of course) of the file to * be detailed. @@ -1724,9 +1287,9 @@ protected function onDetail() */ protected function onDestroy() { - $emsg = null; - $file_arg = null; - $legal_url = null; + $emsg = NULL; + $file_arg = NULL; + $legal_url = NULL; $jserr = array( 'status' => 1 ); @@ -1734,7 +1297,7 @@ protected function onDestroy() try { if (!$this->options['destroy']) - throw new FileManagerException('disabled'); + throw new FileManagerException('disabled:destroy'); $v_ex_code = 'nofile'; @@ -1746,11 +1309,11 @@ protected function onDestroy() $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); $mime_filters = $this->getAllowedMimeTypes($mime_filter); - $filename = null; - $file = null; - $mime = null; - $meta = null; - if (!empty($file_arg)) + $filename = NULL; + $file = NULL; + $mime = NULL; + $meta = NULL; + if ( ! empty($file_arg)) { $filename = basename($file_arg); $legal_url .= $filename; @@ -1769,13 +1332,13 @@ protected function onDestroy() } else { - $v_ex_code = null; + $v_ex_code = NULL; } } else if (is_dir($file)) { $mime = 'text/directory'; - $v_ex_code = null; + $v_ex_code = NULL; } } } @@ -1791,12 +1354,12 @@ protected function onDestroy() 'validation_failure' => $v_ex_code ); - if (!empty($this->options['DestroyIsAuthorized_cb']) && function_exists($this->options['DestroyIsAuthorized_cb']) && !$this->options['DestroyIsAuthorized_cb']($this, 'destroy', $fileinfo)) + if ( ! empty($this->options['DestroyIsAuthorized_cb']) && function_exists($this->options['DestroyIsAuthorized_cb']) && !$this->options['DestroyIsAuthorized_cb']($this, 'destroy', $fileinfo)) { $v_ex_code = $fileinfo['validation_failure']; if (empty($v_ex_code)) $v_ex_code = 'authorized'; } - if (!empty($v_ex_code)) + if ( ! empty($v_ex_code)) throw new FileManagerException($v_ex_code); $legal_url = $fileinfo['legal_url']; @@ -1835,7 +1398,7 @@ protected function onDestroy() $this->sendHttpHeaders('Content-Type: application/json'); // when we fail here, it's pretty darn bad and nothing to it. - // just push the error JSON as go. + // just push the error JSON and go. echo json_encode($jserr); } @@ -1848,7 +1411,7 @@ protected function onDestroy() * * Expected parameters: * - * $_POST['directory'] path relative to basedir a.k.a. options['directory'] root + * $_POST['directory'] path relative to basedir a.k.a. options['documentRootPath'] root * * $_POST['file'] name of the subdirectory to be created * @@ -1871,20 +1434,20 @@ protected function onDestroy() */ protected function onCreate() { - $emsg = null; + $emsg = NULL; $jserr = array( 'status' => 1 ); $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); - $file_arg = null; - $legal_url = null; + $file_arg = NULL; + $legal_url = NULL; try { if (!$this->options['create']) - throw new FileManagerException('disabled'); + throw new FileManagerException('disabled:create'); $v_ex_code = 'nofile'; @@ -1896,12 +1459,13 @@ protected function onCreate() // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: $dir = $this->legal_url_path2file_path($legal_url); - $filename = null; - $file = null; - $newdir = null; - if (!empty($file_arg)) + $filename = NULL; + $file = NULL; + $newdir = NULL; + if ( ! empty($file_arg)) { $filename = basename($file_arg); + $filename = FileManagerUtility::cleanUrl($filename, array(), '_'); if (!$this->IsHiddenNameAllowed($file_arg)) { @@ -1912,10 +1476,10 @@ protected function onCreate() if (is_dir($dir)) { $file = $this->getUniqueName(array('filename' => $filename), $dir); // a directory has no 'extension'! - if ($file !== null) + if ($file !== NULL) { $newdir = $this->legal_url_path2file_path($legal_url . $file); - $v_ex_code = null; + $v_ex_code = NULL; } } } @@ -1931,12 +1495,12 @@ protected function onCreate() 'preliminary_json' => $jserr, 'validation_failure' => $v_ex_code ); - if (!empty($this->options['CreateIsAuthorized_cb']) && function_exists($this->options['CreateIsAuthorized_cb']) && !$this->options['CreateIsAuthorized_cb']($this, 'create', $fileinfo)) + if ( ! empty($this->options['CreateIsAuthorized_cb']) && function_exists($this->options['CreateIsAuthorized_cb']) && !$this->options['CreateIsAuthorized_cb']($this, 'create', $fileinfo)) { $v_ex_code = $fileinfo['validation_failure']; if (empty($v_ex_code)) $v_ex_code = 'authorized'; } - if (!empty($v_ex_code)) + if ( ! empty($v_ex_code)) throw new FileManagerException($v_ex_code); $legal_url = $fileinfo['legal_url']; @@ -1946,11 +1510,12 @@ protected function onCreate() $newdir = $fileinfo['newdir']; $jserr = $fileinfo['preliminary_json']; - if (!@mkdir($newdir, $fileinfo['chmod'], true)) + if (!@mkdir($newdir, $fileinfo['chmod'], TRUE)) { throw new FileManagerException('mkdir_failed:' . $this->legal2abs_url_path($legal_url) . $file); } - + @chmod($newdir, $fileinfo['chmod']); + $this->sendHttpHeaders('Content-Type: application/json'); // success, now show the new directory as a list view: @@ -1975,13 +1540,13 @@ protected function onCreate() // and fall back to showing the BASEDIR directory try { - $legal_url = $this->options['directory']; + $legal_url = $this->documentRootPath; $jserr = $this->_onView($legal_url, $jserr, $mime_filter); } catch (Exception $e) { // when we fail here, it's pretty darn bad and nothing to it. - // just push the error JSON as go. + // just push the error JSON and go. } } } @@ -2002,13 +1567,13 @@ protected function onCreate() // and fall back to showing the BASEDIR directory try { - $legal_url = $this->options['directory']; + $legal_url = $this->documentRootPath; $jserr = $this->_onView($legal_url, $jserr, $mime_filter); } catch (Exception $e) { // when we fail here, it's pretty darn bad and nothing to it. - // just push the error JSON as go. + // just push the error JSON and go. } } } @@ -2018,7 +1583,7 @@ protected function onCreate() $this->sendHttpHeaders('Content-Type: application/json'); // when we fail here, it's pretty darn bad and nothing to it. - // just push the error JSON as go. + // just push the error JSON and go. echo json_encode($jserr); } @@ -2027,7 +1592,7 @@ protected function onCreate() * * Send the file content of the specified file for download by the client. * Only files residing within the directory tree rooted by the - * 'basedir' (options['directory']) will be allowed to be downloaded. + * 'basedir' (options['documentRootPath']) will be allowed to be downloaded. * * Expected parameters: * @@ -2042,10 +1607,17 @@ protected function onCreate() */ protected function onDownload() { + $emsg = NULL; + $file_arg = NULL; + $file = NULL; + $jserr = array( + 'status' => 1 + ); + try { if (!$this->options['download']) - throw new FileManagerException('disabled'); + throw new FileManagerException('disabled:download'); $v_ex_code = 'nofile'; @@ -2054,11 +1626,11 @@ protected function onDownload() $mime_filter = $this->getPOSTparam('filter', $this->options['filter']); $mime_filters = $this->getAllowedMimeTypes($mime_filter); - $legal_url = null; - $file = null; - $mime = null; - $meta = null; - if (!empty($file_arg)) + $legal_url = NULL; + $file = NULL; + $mime = NULL; + $meta = NULL; + if ( ! empty($file_arg)) { $legal_url = $this->rel2abs_legal_url_path($file_arg); @@ -2077,7 +1649,7 @@ protected function onDownload() } else { - $v_ex_code = null; + $v_ex_code = NULL; } } else @@ -2096,12 +1668,12 @@ protected function onDownload() 'mime_filters' => $mime_filters, 'validation_failure' => $v_ex_code ); - if (!empty($this->options['DownloadIsAuthorized_cb']) && function_exists($this->options['DownloadIsAuthorized_cb']) && !$this->options['DownloadIsAuthorized_cb']($this, 'download', $fileinfo)) + if ( ! empty($this->options['DownloadIsAuthorized_cb']) && function_exists($this->options['DownloadIsAuthorized_cb']) && !$this->options['DownloadIsAuthorized_cb']($this, 'download', $fileinfo)) { $v_ex_code = $fileinfo['validation_failure']; if (empty($v_ex_code)) $v_ex_code = 'authorized'; } - if (!empty($v_ex_code)) + if ( ! empty($v_ex_code)) throw new FileManagerException($v_ex_code); $legal_url = $fileinfo['legal_url']; @@ -2140,20 +1712,31 @@ protected function onDownload() fpassthru($fd); fclose($fd); + return; } + + $emsg = 'read_error'; } catch(FileManagerException $e) { - // we don't care whether it's a 404, a 403 or something else entirely: we feed 'em a 403 and that's final! - send_response_status_header(403); - echo $e->getMessage(); + $emsg = $e->getMessage(); } catch(Exception $e) { - // we don't care whether it's a 404, a 403 or something else entirely: we feed 'em a 403 and that's final! - send_response_status_header(403); - echo $e->getMessage(); + // catching other severe failures; since this can be anything and should only happen in the direst of circumstances, we don't bother translating + $emsg = $e->getMessage(); } + + // we don't care whether it's a 404, a 403 or something else entirely: we feed 'em a 403 and that's final! + send_response_status_header(403); + + $this->modify_json4exception($jserr, $emsg, 'file = ' . $this->mkSafe4Display($file_arg . ', destination path = ' . $file)); + + $this->sendHttpHeaders('Content-Type: text/plain'); // Safer for iframes: the 'application/json' mime type would cause FF3.X to pop up a save/view dialog when transmitting these error reports! + + // when we fail here, it's pretty darn bad and nothing to it. + // just push the error JSON and go. + echo json_encode($jserr); } /** @@ -2166,7 +1749,7 @@ protected function onDownload() * * Expected parameters: * - * $_POST['directory'] path relative to basedir a.k.a. options['directory'] root + * $_POST['directory'] path relative to basedir a.k.a. options['documentRootPath'] root * * $_POST['resize'] nonzero value indicates any uploaded image should be resized to the configured * options['maxImageDimension'] width and height whenever possible @@ -2192,9 +1775,10 @@ protected function onDownload() */ protected function onUpload() { - $emsg = null; - $file_arg = null; - $legal_url = null; + $emsg = NULL; + $file_arg = NULL; + $file = NULL; + $legal_dir_url = NULL; $jserr = array( 'status' => 1 ); @@ -2202,7 +1786,7 @@ protected function onUpload() try { if (!$this->options['upload']) - throw new FileManagerException('disabled'); + throw new FileManagerException('disabled:upload'); // MAY upload zero length files! if (!isset($_FILES) || empty($_FILES['Filedata']) || empty($_FILES['Filedata']['name'])) @@ -2211,7 +1795,6 @@ protected function onUpload() $v_ex_code = 'nofile'; $file_size = (empty($_FILES['Filedata']['size']) ? 0 : $_FILES['Filedata']['size']); - $file_arg = $_FILES['Filedata']['name']; $dir_arg = $this->getPOSTparam('directory'); @@ -2226,11 +1809,11 @@ protected function onUpload() $resize_imgs = $this->getPOSTparam('resize', 0); - $filename = null; - $fi = array('filename' => null, 'extension' => null); - $mime = null; - $meta = null; - if (!empty($file_arg)) + $filename = NULL; + $fi = array('filename' => NULL, 'extension' => NULL); + $mime = NULL; + $meta = NULL; + if ( ! empty($file_arg)) { if (!$this->IsHiddenNameAllowed($file_arg)) { @@ -2239,7 +1822,7 @@ protected function onUpload() else { $filename = $this->getUniqueName($file_arg, $dir); - if ($filename !== null) + if ($filename !== NULL) { /* * Security: @@ -2269,7 +1852,7 @@ protected function onUpload() } else { - $v_ex_code = null; + $v_ex_code = NULL; } } } @@ -2287,18 +1870,18 @@ protected function onUpload() 'tmp_filepath' => $tmppath, 'size' => $file_size, 'maxsize' => $this->options['maxUploadSize'], - 'overwrite' => false, + 'overwrite' => FALSE, 'resize' => $resize_imgs, 'chmod' => $this->options['chmod'] & 0666, // security: never make those files 'executable'! 'preliminary_json' => $jserr, 'validation_failure' => $v_ex_code ); - if (!empty($this->options['UploadIsAuthorized_cb']) && function_exists($this->options['UploadIsAuthorized_cb']) && !$this->options['UploadIsAuthorized_cb']($this, 'upload', $fileinfo)) + if ( ! empty($this->options['UploadIsAuthorized_cb']) && function_exists($this->options['UploadIsAuthorized_cb']) && !$this->options['UploadIsAuthorized_cb']($this, 'upload', $fileinfo)) { $v_ex_code = $fileinfo['validation_failure']; if (empty($v_ex_code)) $v_ex_code = 'authorized'; } - if (!empty($v_ex_code)) + if ( ! empty($v_ex_code)) throw new FileManagerException($v_ex_code); $legal_dir_url = $fileinfo['legal_dir_url']; @@ -2318,7 +1901,13 @@ protected function onUpload() //if (!isset($fileinfo['extension'])) // throw new FileManagerException('extension'); - + + // Creates safe file names + if ($this->options['cleanFileName']) + { + $filename = FileManagerUtility::cleanUrl($filename, array(), '_'); + } + // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: $legal_url = $legal_dir_url . $filename; $file = $this->legal_url_path2file_path($legal_url); @@ -2355,7 +1944,7 @@ protected function onUpload() $emsg = 'filename_maybe_too_large'; } - if (!empty($_FILES['Filedata']['error'])) + if ( ! empty($_FILES['Filedata']['error'])) { $emsg .= ': error code = ' . strtolower($_FILES['Filedata']['error']) . ', ' . $emsg_add; } @@ -2374,13 +1963,13 @@ protected function onUpload() * Having uploaded such huge images, a developer/somebody can always go in later and up the memory limit if the site admins * feel it is deserved. Until then, no thumbnails of such images (though you /should/ be able to milkbox-view the real thing!) */ - $thumb250 = false; - $thumb250_e = false; - $thumb48 = false; - $thumb48_e = false; + $thumb250 = FALSE; + $thumb250_e = FALSE; + $thumb48 = FALSE; + $thumb48_e = FALSE; if (FileManagerUtility::startsWith($mime, 'image/')) { - if (!empty($resize_imgs)) + if ( ! empty($resize_imgs)) { $img = new Image($file); $size = $img->getSize(); @@ -2388,6 +1977,9 @@ protected function onUpload() // (default quality is 100% for JPEG so we get the cleanest resized images here) $img->resize($this->options['maxImageDimension']['width'], $this->options['maxImageDimension']['height'])->save(); unset($img); + + // source image has changed: nuke the cached metadata and then refetch the metadata = forced refetch + $meta = $this->getFileInfo($file, $legal_url, TRUE); } } @@ -2396,7 +1988,7 @@ protected function onUpload() * so we'll have a very fast performance viewing this file's details and thumbnails both from this point forward! */ $jsbogus = array('status' => 1); - $jsbogus = $this->extractDetailInfo($jsbogus, $legal_url, $meta, $mime_filter, $mime_filters, 'direct'); + $jsbogus = $this->extractDetailInfo($jsbogus, $legal_url, $meta, $mime_filter, $mime_filters, array('direct')); $this->sendHttpHeaders('Content-Type: ' . $this->getPOSTparam('reportContentType', 'application/json')); @@ -2421,7 +2013,7 @@ protected function onUpload() $this->sendHttpHeaders('Content-Type: ' . $this->getPOSTparam('reportContentType', 'application/json')); // when we fail here, it's pretty darn bad and nothing to it. - // just push the error JSON as go. + // just push the error JSON and go. echo json_encode(array_merge($jserr, $_FILES)); } @@ -2437,13 +2029,13 @@ protected function onUpload() * * Source filespec: * - * $_POST['directory'] path relative to basedir a.k.a. options['directory'] root + * $_POST['directory'] path relative to basedir a.k.a. options['documentRootPath'] root * * $_POST['file'] original name of the file/subdirectory to be renamed/copied * * Destination filespec: * - * $_POST['newDirectory'] path relative to basedir a.k.a. options['directory'] root; + * $_POST['newDirectory'] path relative to basedir a.k.a. options['documentRootPath'] root; * target directory where the file must be moved / copied * * $_POST['name'] target name of the file/subdirectory to be renamed @@ -2456,10 +2048,10 @@ protected function onUpload() */ protected function onMove() { - $emsg = null; - $file_arg = null; - $legal_url = null; - $newpath = null; + $emsg = NULL; + $file_arg = NULL; + $legal_url = NULL; + $newpath = NULL; $jserr = array( 'status' => 1 ); @@ -2467,7 +2059,7 @@ protected function onMove() try { if (!$this->options['move']) - throw new FileManagerException('disabled'); + throw new FileManagerException('disabled:rn_mv_cp'); $v_ex_code = 'nofile'; @@ -2485,21 +2077,21 @@ protected function onMove() $is_copy = !!$this->getPOSTparam('copy'); - $filename = null; - $path = null; - $fn = null; - $legal_newurl = null; - $newdir = null; - $newname = null; - $newpath = null; - $is_dir = false; + $filename = NULL; + $path = NULL; + $fn = NULL; + $legal_newurl = NULL; + $newdir = NULL; + $newname = NULL; + $newpath = NULL; + $is_dir = FALSE; if (!$this->IsHiddenPathAllowed($newdir_arg) || !$this->IsHiddenNameAllowed($newname_arg)) { $v_ex_code = 'authorized'; } else { - if (!empty($file_arg)) + if ( ! empty($file_arg)) { $filename = basename($file_arg); $path = $this->legal_url_path2file_path($legal_url . $filename); @@ -2511,7 +2103,7 @@ protected function onMove() // note: we do not support copying entire directories, though directory rename/move is okay if ($is_copy && $is_dir) { - $v_ex_code = 'disabled'; + $v_ex_code = 'disabled:rn_mv_cp'; } else if ($rename) { @@ -2525,7 +2117,7 @@ protected function onMove() else $newname = $this->getUniqueName($newname, $newdir); - if ($newname === null) + if ($newname === NULL) { $v_ex_code = 'nonewfile'; } @@ -2540,7 +2132,7 @@ protected function onMove() { $newname .= '.' . $extOld; } - $v_ex_code = null; + $v_ex_code = NULL; } } else @@ -2554,10 +2146,10 @@ protected function onMove() else $newname = $this->getUniqueName($filename, $newdir); - if ($newname === null) + if ($newname === NULL) $v_ex_code = 'nonewfile'; else - $v_ex_code = null; + $v_ex_code = NULL; } if (empty($v_ex_code)) @@ -2584,12 +2176,12 @@ protected function onMove() 'validation_failure' => $v_ex_code ); - if (!empty($this->options['MoveIsAuthorized_cb']) && function_exists($this->options['MoveIsAuthorized_cb']) && !$this->options['MoveIsAuthorized_cb']($this, 'move', $fileinfo)) + if ( ! empty($this->options['MoveIsAuthorized_cb']) && function_exists($this->options['MoveIsAuthorized_cb']) && !$this->options['MoveIsAuthorized_cb']($this, 'move', $fileinfo)) { $v_ex_code = $fileinfo['validation_failure']; if (empty($v_ex_code)) $v_ex_code = 'authorized'; } - if (!empty($v_ex_code)) + if ( ! empty($v_ex_code)) throw new FileManagerException($v_ex_code); $legal_url = $fileinfo['legal_url']; @@ -2609,9 +2201,9 @@ protected function onMove() { // try to remove the thumbnail & other cache entries related to the original file; don't mind if it doesn't exist $flurl = $legal_url . $filename; - $meta = &$this->getid3_cache->pick($flurl, $this, false); - assert($meta != null); - if (!$meta->delete(true)) + $meta = &$this->getid3_cache->pick($flurl, $this, FALSE); + assert($meta != NULL); + if (!$meta->delete(TRUE)) { throw new FileManagerException('delete_cache_entries_failed'); } @@ -2646,19 +2238,11 @@ protected function onMove() $this->sendHttpHeaders('Content-Type: application/json'); // when we fail here, it's pretty darn bad and nothing to it. - // just push the error JSON as go. + // just push the error JSON and go. echo json_encode($jserr); } - - - - - - - - /** * Send the listed headers when possible; the input parameter is an array of header strings or a single header string. * @@ -2683,9 +2267,6 @@ public function sendHttpHeaders($headers) } } - - - // derived from http://www.php.net/manual/en/function.filesize.php#100097 public function format_bytes($bytes) { @@ -2707,83 +2288,77 @@ public function format_bytes($bytes) * * Throw an exception on error. */ - public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $mime_filters, $thumbnail_gen_mode) + public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $mime_filters, $mode) { - $auto_thumb_gen_mode = ($thumbnail_gen_mode !== 'direct'); + $auto_thumb_gen_mode = !in_array('direct', $mode, TRUE); + $metaHTML_mode = in_array('metaHTML', $mode, TRUE); + $metaJSON_mode = in_array('metaJSON', $mode, TRUE); - $url = $this->legal2abs_url_path($legal_url); - $filename = basename($url); + $filename = basename($legal_url); // must transform here so alias/etc. expansions inside url_path2file_path() get a chance: - $file = $this->url_path2file_path($url); - - $isdir = !is_file($file); - $bad_ext = false; - $mime = null; + // $file = $this->url_path2file_path($url); + + // Absolute FS path to the file + $file = $this->getAbsolutePath($legal_url); + + // Absolute URL to the file + $url = $this->getAbsoluteUrl($legal_url); + + $bad_ext = FALSE; + $mime = NULL; + // only perform the (costly) getID3 scan when it hasn't been done before, i.e. can we re-use previously obtained data or not? - if (!is_object($meta)) + if ( ! is_object($meta)) { $meta = $this->getFileInfo($file, $legal_url); } - if (!$isdir) + + if ( is_file($file)) { $mime = $meta->getMimeType(); - $mime2 = $this->getMimeFromExt($file); $meta->store('mime_type from file extension', $mime2); - $bad_ext = ($mime2 != $mime); - if ($bad_ext) - { + // Bad extension + if ($mime2 != $mime) $iconspec = 'is.' + $this->getExtFromMime($mime); - } else - { $iconspec = $filename; - } - if (!$this->IsAllowedMimeType($mime, $mime_filters)) + if ( ! $this->IsAllowedMimeType($mime, $mime_filters)) throw new FileManagerException('extension'); } - else if (is_dir($file)) + else if ( is_dir($file) ) { $mime = $meta->getMimeType(); - // $mime = 'text/directory'; $iconspec = 'is.directory'; } else { - // simply do NOT list anything that we cannot cope with. - // That includes clearly inaccessible files (and paths) with non-ASCII characters: - // PHP5 and below are a real mess when it comes to handling Unicode filesystems - // (see the php.net site too: readdir / glob / etc. user comments and the official - // notice that PHP will support filesystem UTF-8/Unicode only when PHP6 is released. - // - // Big, fat bummer! + // Simply do NOT list anything that we cannot cope with. throw new FileManagerException('nofile'); } // as all the work below is quite costly, we check whether the already loaded cache entry got our number: // several chunks of work below may have been cached and when they have been, use the cached data. - // it's an internal error when this entry do not exist in the cache store by now! $fi = $meta->fetch('analysis'); - //assert(!empty($fi)); - - $icon48 = $this->getIcon($iconspec, false); - $icon = $this->getIcon($iconspec, true); + $icon48 = $this->getIcon($iconspec, FALSE); + $icon = $this->getIcon($iconspec, TRUE); $thumb250 = $meta->fetch('thumb250_direct'); $thumb48 = $meta->fetch('thumb48_direct'); - $thumb250_e = false; - $thumb48_e = false; + $thumb250_e = FALSE; + $thumb48_e = FALSE; + +// $this->log->logInfo('$thumb250 :::' . $thumb250); $tstamp_str = date($this->options['dateFormat'], @filemtime($file)); $fsize = @filesize($file); - $json = array_merge(array( - //'status' => 1, - //'mimetype' => $mime, + $json = array_merge( + array( 'content' => self::compressHTML('
${nopreview}
') @@ -2794,7 +2369,8 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m 'date' => $tstamp_str, 'mime' => $mime, 'size' => $fsize - )); + ) + ); if (empty($fsize)) { @@ -2813,13 +2389,13 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m
' . $mime . '
${size}
' . $fsize_str . '
'; - $content_dl_term = false; + $content_dl_term = FALSE; - $preview_HTML = null; + $preview_HTML = NULL; $postdiag_err_HTML = ''; $postdiag_dump_HTML = ''; - $thumbnails_done_or_deferred = false; // TRUE: mark our thumbnail work as 'done'; any NULL thumbnails represent deferred generation entries! - $check_for_embedded_img = false; + $thumbnails_done_or_deferred = FALSE; // TRUE: mark our thumbnail work as 'done'; any NULL thumbnails represent deferred generation entries! + $check_for_embedded_img = FALSE; $mime_els = explode('/', $mime); for(;;) // bogus loop; only meant to assist the [mime remapping] state machine in here @@ -2844,22 +2420,29 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m * That bit of code ASSUMES that the thumbnail will be generated from the file argument, while * the url argument is used to determine the thumbnail name/path. */ - $emsg = null; + $emsg = NULL; try { if (empty($thumb250)) { - $thumb250 = $this->getThumb($meta, $file, 250, 250, $auto_thumb_gen_mode); +// Store thumb 250 path in meta with getThumb +// or try to get it through +// $thumbPath = $meta->getThumbPath($width . 'x' . $height); + +$this->log->logInfo('line 2428 : ' . $file); + $thumb250 = $this->getThumb($meta, $file, $this->options['thumbBigSize'], $this->options['thumbBigSize'], $auto_thumb_gen_mode); +$this->log->logInfo('line 2430 : ' . $thumb250); + } - if (!empty($thumb250)) + if ( ! empty($thumb250)) { $thumb250_e = FileManagerUtility::rawurlencode_path($thumb250); } if (empty($thumb48)) { - $thumb48 = $this->getThumb($meta, (!empty($thumb250) ? $this->url_path2file_path($thumb250) : $file), 48, 48, $auto_thumb_gen_mode); + $thumb48 = $this->getThumb($meta, ( ! empty($thumb250) ? $this->url_path2file_path($thumb250) : $file), $this->options['thumbSmallSize'], $this->options['thumbSmallSize'], $auto_thumb_gen_mode); } - if (!empty($thumb48)) + if ( ! empty($thumb48)) { $thumb48_e = FileManagerUtility::rawurlencode_path($thumb48); } @@ -2872,13 +2455,13 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m */ $imginfo = Image::checkFileForProcessing($file); } - $thumbnails_done_or_deferred = true; + $thumbnails_done_or_deferred = TRUE; } catch (Exception $e) { $emsg = $e->getMessage(); - $icon48 = $this->getIconForError($emsg, $legal_url, false); - $icon = $this->getIconForError($emsg, $legal_url, true); + $icon48 = $this->getIconForError($emsg, $legal_url, FALSE); + $icon = $this->getIconForError($emsg, $legal_url, TRUE); // even cache the fail: that means next time around we don't suffer the failure but immediately serve the error icon instead. } @@ -2891,12 +2474,12 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m
${width}
' . $width . 'px
${height}
' . $height . 'px
'; - $content_dl_term = true; + $content_dl_term = TRUE; - $sw_make = $this->mkSafeUTF8($this->getID3infoItem($fi, null, 'jpg', 'exif', 'IFD0', 'Software')); - $time_make = $this->mkSafeUTF8($this->getID3infoItem($fi, null, 'jpg', 'exif', 'IFD0', 'DateTime')); + $sw_make = $this->mkSafeUTF8($this->getID3infoItem($fi, NULL, 'jpg', 'exif', 'IFD0', 'Software')); + $time_make = $this->mkSafeUTF8($this->getID3infoItem($fi, NULL, 'jpg', 'exif', 'IFD0', 'DateTime')); - if (!empty($sw_make) || !empty($time_make)) + if ( ! empty($sw_make) || !empty($time_make)) { $content .= '

Made with ' . (empty($sw_make) ? '???' : $sw_make) . ' @ ' . (empty($time_make) ? '???' : $time_make) . '

'; } @@ -2904,7 +2487,7 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m // are we delaying the thumbnail generation? When yes, then we need to infer the thumbnail dimensions *anyway*! if (empty($thumb48) && $thumbnails_done_or_deferred) { - $dims = $this->predictThumbDimensions($width, $height, 48, 48); + $dims = $this->predictThumbDimensions($width, $height, $this->options['thumbSmallSize'], $this->options['thumbSmallSize']); $json['thumb48_width'] = $dims['width']; $json['thumb48_height'] = $dims['height']; @@ -2916,10 +2499,10 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m // to show the loader.gif in the preview tag, we MUST set a width+height there, so we guestimate the thumbnail250 size as accurately as possible // // derive size from original: - $dims = $this->predictThumbDimensions($width, $height, 250, 250); + $dims = $this->predictThumbDimensions($width, $height, $this->options['thumbBigSize'], $this->options['thumbBigSize']); $preview_HTML = ' - preview + preview '; $json['thumb250_width'] = $dims['width']; @@ -2935,14 +2518,14 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m } // else: defer the $preview_HTML production until we're at the end of this and have fetched the actual thumbnail dimensions - if (!empty($emsg)) + if ( ! empty($emsg)) { // use the abilities of modify_json4exception() to munge/format the exception message: $jsa = array('error' => ''); $this->modify_json4exception($jsa, $emsg, 'path = ' . $url); $postdiag_err_HTML .= "\n" . '

' . $jsa['error'] . '

'; - if (strpos($emsg, 'img_will_not_fit') !== false) + if (strpos($emsg, 'img_will_not_fit') !== FALSE) { $earr = explode(':', $emsg, 2); $postdiag_err_HTML .= "\n" . '

Estimated minimum memory requirements to create thumbnails for this image: ' . $earr[1] . '

'; @@ -2954,13 +2537,15 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m switch ($mime_els[1]) { case 'directory': + $content = '
'; + $preview_HTML = ''; break; default: // text preview: - $filecontent = @file_get_contents($file, false, null, 0); - if ($filecontent === false) + $filecontent = @file_get_contents($file, FALSE, NULL, 0); + if ($filecontent === FALSE) throw new FileManagerException('nofile'); if (!FileManagerUtility::isBinary($filecontent)) @@ -2986,14 +2571,14 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m case 'zip': $out = array(array(), array()); - $info = $this->getID3infoItem($fi, null, 'zip', 'files'); + $info = $this->getID3infoItem($fi, NULL, 'zip', 'files'); if (is_array($info)) { foreach ($info as $name => $size) { $name = $this->mkSafeUTF8($name); $isdir = is_array($size); - $out[$isdir ? 0 : 1][$name] = '
  • ' . $name . '
  • '; + $out[$isdir ? 0 : 1][$name] = '
  • ' . $name . '
  • '; } natcasesort($out[0]); natcasesort($out[1]); @@ -3002,9 +2587,9 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m break; case 'x-shockwave-flash': - $check_for_embedded_img = true; + $check_for_embedded_img = TRUE; - $info = $this->getID3infoItem($fi, null, 'swf', 'header'); + $info = $this->getID3infoItem($fi, NULL, 'swf', 'header'); if (is_array($info)) { $width = round($this->getID3infoItem($fi, 0, 'swf', 'header', 'frame_width') / 10); @@ -3017,7 +2602,7 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m
    ${height}
    ' . $height . 'px
    ${length}
    ' . round($this->getID3infoItem($fi, 0, 'swf', 'header', 'length') / $this->getID3infoItem($fi, 25, 'swf', 'header', 'frame_count')) . 's
    '; - $content_dl_term = true; + $content_dl_term = TRUE; } break; @@ -3029,7 +2614,7 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m break; case 'audio': - $check_for_embedded_img = true; + $check_for_embedded_img = TRUE; $title = $this->mkSafeUTF8($this->getID3infoItem($fi, $this->getID3infoItem($fi, '???', 'tags', 'id3v1', 'title', 0), 'tags', 'id3v2', 'title', 0)); $artist = $this->mkSafeUTF8($this->getID3infoItem($fi, $this->getID3infoItem($fi, '???', 'tags', 'id3v1', 'artist', 0), 'tags', 'id3v2', 'artist', 0)); @@ -3042,11 +2627,11 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m
    ${length}
    ' . $this->mkSafeUTF8($this->getID3infoItem($fi, '???', 'playtime_string')) . '
    ${bitrate}
    ' . round($this->getID3infoItem($fi, 0, 'bitrate') / 1000) . 'kbps
    '; - $content_dl_term = true; + $content_dl_term = TRUE; break; case 'video': - $check_for_embedded_img = true; + $check_for_embedded_img = TRUE; $a_fmt = $this->mkSafeUTF8($this->getID3infoItem($fi, '???', 'audio', 'dataformat')); $a_samplerate = round($this->getID3infoItem($fi, 0, 'audio', 'sample_rate') / 1000, 1); @@ -3077,13 +2662,13 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m } else { - $content .= $a_fmt . (!empty($a_codec) ? ' (' . $a_codec . ')' : '') . - (!empty($a_channels) ? ($a_channels === 1 ? ' (mono)' : ($a_channels === 2 ? ' (stereo)' : ' (' . $a_channels . ' channels)')) : '') . + $content .= $a_fmt . ( ! empty($a_codec) ? ' (' . $a_codec . ')' : '') . + ( ! empty($a_channels) ? ($a_channels === 1 ? ' (mono)' : ($a_channels === 2 ? ' (stereo)' : ' (' . $a_channels . ' channels)')) : '') . ': ' . $a_samplerate . ' kHz @ ' . $a_bitrate . ' kbps (' . strtoupper($a_bitrate_mode) . ')' . ($a_streamcount > 1 ? ' (' . $a_streamcount . ' streams)' : ''); } $content .= ' -
    Video
    ' . $v_fmt . (!empty($v_codec) ? ' (' . $v_codec . ')' : '') . ': ' . $v_framerate . ' fps @ ' . $v_bitrate . ' kbps (' . strtoupper($v_bitrate_mode) . ')' . +
    Video
    ' . $v_fmt . ( ! empty($v_codec) ? ' (' . $v_codec . ')' : '') . ': ' . $v_framerate . ' fps @ ' . $v_bitrate . ' kbps (' . strtoupper($v_bitrate_mode) . ')' . ($v_par != 1.0 ? ', PAR: ' . $v_par : '') . '
    ${width}
    ' . $v_width . 'px
    @@ -3091,7 +2676,7 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m
    ${length}
    ' . $g_playtime_str . '
    ${bitrate}
    ' . $g_bitrate . 'kbps
    '; - $content_dl_term = true; + $content_dl_term = TRUE; break; default: @@ -3101,26 +2686,28 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m break; } - if (!$content_dl_term) + if ( ! $content_dl_term) { $content .= ''; } - if (!empty($fi['error'])) + if ( ! empty($fi['error'])) { $postdiag_err_HTML .= '

    ' . $this->mkSafeUTF8(implode(', ', $fi['error'])) . '

    '; } - $emsgX = null; + $emsgX = NULL; + if (empty($thumb250)) { if (!$thumbnails_done_or_deferred) { // check if we have stored a thumbnail for this file anyhow: - $thumb250 = $this->getThumb($meta, $file, 250, 250, true); + $thumb250 = $this->getThumb($meta, $file, $this->options['thumbBigSize'], $this->options['thumbBigSize'], TRUE); + if (empty($thumb250)) { - if (!empty($fi) && $check_for_embedded_img) + if ( ! empty($fi) && $check_for_embedded_img) { /* * No thumbnail available yet, so find me one! @@ -3132,7 +2719,7 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m * low cost. */ $embed = $this->extract_ID3info_embedded_image($fi); - //@file_put_contents(dirname(__FILE__) . '/extract_embedded_img.log', print_r(array('html' => $preview_HTML, 'json' => $json, 'thumb250_e' => $thumb250_e, 'thumb250' => $thumb250, 'embed' => $embed, 'fileinfo' => $fi), true)); + //@file_put_contents(dirname(__FILE__) . '/extract_embedded_img.log', print_r(array('html' => $preview_HTML, 'json' => $json, 'thumb250_e' => $thumb250_e, 'thumb250' => $thumb250, 'embed' => $embed, 'fileinfo' => $fi), TRUE)); if (is_object($embed)) { $thumbX = $meta->getThumbURL('embed'); @@ -3144,29 +2731,29 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m // as we've spent some effort to dig out the embedded thumbnail, and 'knowing' (assuming) that generally // embedded thumbnails are not too large, we don't concern ourselves with delaying the thumbnail generation (the // source file mapping is not bidirectional, either!) and go straight ahead and produce the 250px thumbnail at least. - $thumb250 = false; - $thumb250_e = false; - $thumb48 = false; - $thumb48_e = false; + $thumb250 = FALSE; + $thumb250_e = FALSE; + $thumb48 = FALSE; + $thumb48_e = FALSE; $meta->mkCacheDir(); - if (false === file_put_contents($thumbX_f, $embed->imagedata)) + if (FALSE === file_put_contents($thumbX_f, $embed->imagedata)) { @unlink($thumbX_f); $emsgX = 'Cannot save embedded image data to cache.'; - $icon48 = $this->getIcon('is.default-error', false); - $icon = $this->getIcon('is.default-error', true); + $icon48 = $this->getIcon('is.default-error', FALSE); + $icon = $this->getIcon('is.default-error', TRUE); } else { try { - $thumb250 = $this->getThumb($meta, $thumbX_f, 250, 250, false); - if (!empty($thumb250)) + $thumb250 = $this->getThumb($meta, $thumbX_f, $this->options['thumbBigSize'], $this->options['thumbBigSize'], FALSE); + if ( ! empty($thumb250)) { $thumb250_e = FileManagerUtility::rawurlencode_path($thumb250); } - $thumb48 = $this->getThumb($meta, (!empty($thumb250) ? $this->url_path2file_path($thumb250) : $thumbX_f), 48, 48, false); - if (!empty($thumb48)) + $thumb48 = $this->getThumb($meta, ( ! empty($thumb250) ? $this->url_path2file_path($thumb250) : $thumbX_f), $this->options['thumbSmallSize'], $this->options['thumbSmallSize'], FALSE); + if ( ! empty($thumb48)) { $thumb48_e = FileManagerUtility::rawurlencode_path($thumb48); } @@ -3174,35 +2761,36 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m catch (Exception $e) { $emsgX = $e->getMessage(); - $icon48 = $this->getIconForError($emsgX, $legal_url, false); - $icon = $this->getIconForError($emsgX, $legal_url, true); + $icon48 = $this->getIconForError($emsgX, $legal_url, FALSE); + $icon = $this->getIconForError($emsgX, $legal_url, TRUE); } } } } } + // $thumb250 isn't empty else { - // !empty($thumb250) $thumb250_e = FileManagerUtility::rawurlencode_path($thumb250); try { - $thumb48 = $this->getThumb($meta, $this->url_path2file_path($thumb250), 48, 48, false); - assert(!empty($thumb48)); + $thumb48 = $this->getThumb($meta, $this->url_path2file_path($thumb250), $this->options['thumbSmallSize'], $this->options['thumbSmallSize'], FALSE); + assert( ! empty($thumb48)); $thumb48_e = FileManagerUtility::rawurlencode_path($thumb48); } catch (Exception $e) { $emsgX = $e->getMessage(); - $icon48 = $this->getIconForError($emsgX, $legal_url, false); - $icon = $this->getIconForError($emsgX, $legal_url, true); - $thumb48 = false; - $thumb48_e = false; + $icon48 = $this->getIconForError($emsgX, $legal_url, FALSE); + $icon = $this->getIconForError($emsgX, $legal_url, TRUE); + $thumb48 = FALSE; + $thumb48_e = FALSE; } } } } - else // if (!empty($thumb250)) + // if ( ! empty($thumb250)) + else { if (empty($thumb250_e)) { @@ -3212,17 +2800,22 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m { try { - $thumb48 = $this->getThumb($meta, $this->url_path2file_path($thumb250), 48, 48, false); - assert(!empty($thumb48)); + +// $this->log->logInfo('$thumb250 : ' . $this->url_path2file_path($thumb250)); +// $meta->store('mime_type from file extension', $mime2); + + + $thumb48 = $this->getThumb($meta, $this->url_path2file_path($thumb250), $this->options['thumbSmallSize'], $this->options['thumbSmallSize'], FALSE); + assert( ! empty($thumb48)); $thumb48_e = FileManagerUtility::rawurlencode_path($thumb48); } catch (Exception $e) { $emsgX = $e->getMessage(); - $icon48 = $this->getIconForError($emsgX, $legal_url, false); - $icon = $this->getIconForError($emsgX, $legal_url, true); - $thumb48 = false; - $thumb48_e = false; + $icon48 = $this->getIconForError($emsgX, $legal_url, FALSE); + $icon = $this->getIconForError($emsgX, $legal_url, TRUE); + $thumb48 = FALSE; + $thumb48_e = FALSE; } } if (empty($thumb48_e)) @@ -3232,15 +2825,32 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m } // also provide X/Y size info with each direct-access thumbnail file: - if (!empty($thumb250)) + if ( ! empty($thumb250)) { $json['thumb250'] = $thumb250_e; + $meta->store('thumb250_direct', $thumb250); $tnsize = $meta->fetch('thumb250_info'); + if (empty($tnsize)) { - $tnsize = getimagesize($this->url_path2file_path($thumb250)); +// $this->log->logInfo('thumb250 : ' . $this->url_path2file_path($thumb250)); + +// $this->log->logInfo(print_r($meta, true)); + +// $tnsize = getimagesize($this->url_path2file_path($thumb250)); + $thumbPath = $meta->getThumbPath('250x250'); + +// $tnsize = getimagesize($thumb250); + $tnsize = getimagesize($thumbPath); + + $this->log->logInfo(print_r($tnsize, true)); + + +// $this->log->logInfo('$tnsize : ' .$this->url_path2file_path($thumb250), true); + + $meta->store('thumb250_info', $tnsize); } if (is_array($tnsize)) @@ -3251,13 +2861,13 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m if (empty($preview_HTML)) { $preview_HTML = ' - ' . (!empty($emsgX) ? $this->mkSafe4HTMLattr($emsgX) : 'preview') . ' '; } } } - if (!empty($thumb48)) + if ( ! empty($thumb48)) { $json['thumb48'] = $thumb48_e; $meta->store('thumb48_direct', $thumb48); @@ -3276,25 +2886,26 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m } if ($thumbnails_done_or_deferred && (empty($thumbs250) || empty($thumbs48))) { - $json['thumbs_deferred'] = true; + $json['thumbs_deferred'] = TRUE; } else { - $json['thumbs_deferred'] = false; + $json['thumbs_deferred'] = FALSE; } - if (!empty($icon48)) + if ( ! empty($icon48)) { $icon48_e = FileManagerUtility::rawurlencode_path($icon48); $json['icon48'] = $icon48_e; } - if (!empty($icon)) + if ( ! empty($icon)) { $icon_e = FileManagerUtility::rawurlencode_path($icon); $json['icon'] = $icon_e; } - if (!empty($fi)) + $fi4dump = NULL; + if ( ! empty($fi)) { try { @@ -3306,10 +2917,10 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m $meta->store('file_info_dump', $fi4dump); } - $dump = FileManagerUtility::table_var_dump($fi4dump, false); + $dump = FileManagerUtility::table_var_dump($fi4dump, FALSE); $postdiag_dump_HTML .= "\n" . $dump . "\n"; - //@file_put_contents(dirname(__FILE__) . '/getid3.log', print_r(array('html' => $preview_HTML, 'json' => $json, 'thumb250_e' => $thumb250_e, 'thumb250' => $thumb250, 'embed' => $embed, 'fileinfo' => $fi), true)); + //@file_put_contents(dirname(__FILE__) . '/getid3.log', print_r(array('html' => $preview_HTML, 'json' => $json, 'thumb250_e' => $thumb250_e, 'thumb250' => $thumb250, 'embed' => $embed, 'fileinfo' => $fi), TRUE)); } catch(Exception $e) { @@ -3317,32 +2928,27 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m } } - if ($preview_HTML === null) + if ($preview_HTML === NULL) { $preview_HTML = '${nopreview}'; } - if (!empty($preview_HTML)) + if ( ! empty($preview_HTML)) { //$content .= '

    ${preview}

    '; $content .= '
    ' . $preview_HTML . '
    '; } - if (!empty($postdiag_err_HTML) || !empty($postdiag_dump_HTML)) + if ( ! empty($postdiag_err_HTML)) { - //$content .= '

    Diagnostics

    '; - //$content .= '
    '; - if (!empty($postdiag_err_HTML)) - { - $content .= '
    ' . $postdiag_err_HTML . '
    '; - } - if (!empty($postdiag_dump_HTML)) - { - $content .= '
    ' . $postdiag_dump_HTML . '
    '; - } - //$content .= '
    '; + $content .= '
    ' . $postdiag_err_HTML . '
    '; + } + if ( ! empty($postdiag_dump_HTML) && $metaHTML_mode) + { + $content .= '
    ' . $postdiag_dump_HTML . '
    '; } $json['content'] = self::compressHTML($content); + $json['metadata'] = ($metaJSON_mode ? $fi4dump : NULL); return array_merge((is_array($json_in) ? $json_in : array()), $json); } @@ -3358,7 +2964,7 @@ public function extractDetailInfo($json_in, $legal_url, &$meta, $mime_filter, $m */ public /* static */ function getID3infoItem($getid3_info_obj, $default_value /* , ... */ ) { - $rv = false; + $rv = FALSE; $argc = func_num_args(); for ($i = 2; $i < $argc; $i++) @@ -3408,18 +3014,18 @@ protected function extract_ID3info_embedded_image(&$arr) else if (is_array($value)) { $rv = $this->extract_ID3info_embedded_image($value); - if ($rv !== false) + if ($rv !== FALSE) return $rv; } } } - return false; + return FALSE; } protected function fold_quicktime_subatoms(&$arr, &$inject, $key_prefix) { - $satms = false; - if (!empty($arr['subatoms']) && is_array($arr['subatoms']) && !empty($arr['hierarchy']) && !empty($arr['name']) && $arr['hierarchy'] == $arr['name']) + $satms = FALSE; + if ( ! empty($arr['subatoms']) && is_array($arr['subatoms']) && !empty($arr['hierarchy']) && !empty($arr['name']) && $arr['hierarchy'] == $arr['name']) { // fold these up all the way to the root: $key_prefix .= '.' . $arr['hierarchy']; @@ -3437,7 +3043,7 @@ protected function fold_quicktime_subatoms(&$arr, &$inject, $key_prefix) } } - if ($satms !== false) + if ($satms !== FALSE) { $this->fold_quicktime_subatoms($inject[$key_prefix], $inject, $key_prefix); } @@ -3561,10 +3167,10 @@ protected function clean_ID3info_results_r(&$arr, $flags) unset($newarr); } - $activity = true; + $activity = TRUE; while ($activity) { - $activity = false; // assume there's nothing to do anymore. Prove us wrong now... + $activity = FALSE; // assume there's nothing to do anymore. Prove us wrong now... // heuristic #2: when the number of items in the array which are themselves arrays is 80%, contract the set $todo = array(); @@ -3622,7 +3228,7 @@ protected function clean_ID3info_results_r(&$arr, $flags) } } $arr = $inject; - $activity = true; + $activity = TRUE; } } @@ -3676,7 +3282,7 @@ protected function clean_ID3info_results_r(&$arr, $flags) $str = $this->mkSafeUTF8(trim(strtr(getid3_lib::iconv_fallback($arr['encoding'], 'UTF-8', $value), "\x00", ' '))); $temp = unpack('H*', $value); $temp = str_split($temp[1], 8); - $value = new BinaryDataContainer(implode(' ', $temp) . (!empty($str) ? ' (' . $str . ')' : '')); + $value = new BinaryDataContainer(implode(' ', $temp) . ( ! empty($str) ? ' (' . $str . ')' : '')); } else if ( ($key === 'data' && is_string($value) && isset($arr['offset']) && isset($arr['size'])) // AVI offset/size/data items: data = binary || ($key === 'type_specific_data' && is_string($value) /* && isset($arr['type_specific_len']) */ ) // munch WMV/RM 'type specific data': binary ('type specific len' will occur alongside, but not in WMV) @@ -3699,7 +3305,7 @@ protected function clean_ID3info_results_r(&$arr, $flags) $temp = unpack('H*', $value); $temp = str_split($temp[1], 8); $temp = implode(' ', $temp); - $value = new BinaryDataContainer($temp . (!empty($str) > 0 ? ' (' . $str . ')' : '')); + $value = new BinaryDataContainer($temp . ( ! empty($str) > 0 ? ' (' . $str . ')' : '')); } else { @@ -3717,12 +3323,12 @@ protected function clean_ID3info_results_r(&$arr, $flags) else if (is_array($value)) { // heuristic #3: when the value is an array of integers, implode them to a comma-separated list (string) instead: - $is_all_ints = true; + $is_all_ints = TRUE; for ($sk = count($value) - 1; $sk >= 0; --$sk) { if (!array_key_exists($sk, $value) || !is_int($value[$sk])) { - $is_all_ints = false; + $is_all_ints = FALSE; break; } } @@ -3755,7 +3361,7 @@ protected function clean_ID3info_results_r(&$arr, $flags) // check if we can turn it into something UTF8-LEGAL; when not, we hexdump! $im = str_replace('?', '&QMaRK;', $value); $dst = $this->mkSafeUTF8($im); - if (strpos($dst, '?') === false) + if (strpos($dst, '?') === FALSE) { // it's a UTF-8 legal string now! $arr = str_replace('&QMaRK;', '?', $dst); @@ -3786,15 +3392,15 @@ protected function clean_ID3info_results_r(&$arr, $flags) } else if (is_object($arr) && !isset($arr->id3_procsupport_obj)) { - $arr = new BinaryDataContainer('(object) ' . $this->mkSafeUTF8(print_r($arr, true))); + $arr = new BinaryDataContainer('(object) ' . $this->mkSafeUTF8(print_r($arr, TRUE))); } else if (is_resource($arr)) { - $arr = new BinaryDataContainer('(resource) ' . $this->mkSafeUTF8(print_r($arr, true))); + $arr = new BinaryDataContainer('(resource) ' . $this->mkSafeUTF8(print_r($arr, TRUE))); } else { - $arr = new BinaryDataContainer('(unidentified type: ' . gettype($arr) . ') ' . $this->mkSafeUTF8(print_r($arr, true))); + $arr = new BinaryDataContainer('(unidentified type: ' . gettype($arr) . ') ' . $this->mkSafeUTF8(print_r($arr, TRUE))); } } @@ -3834,17 +3440,17 @@ protected function clean_ID3info_results(&$arr, $flags = 0) */ protected function unlink($legal_url, $mime_filters) { - $rv = true; + $rv = TRUE; // must transform here so alias/etc. expansions inside legal_url_path2file_path() get a chance: $file = $this->legal_url_path2file_path($legal_url); if (is_dir($file)) { - $dir = self::enforceTrailingSlash($file); - $url = self::enforceTrailingSlash($legal_url); - $coll = $this->scandir($dir, '*', false, 0, ~GLOB_NOHIDDEN); - if ($coll !== false) + $dir = self::addTrailingSlash($file); + $url = self::addTrailingSlash($legal_url); + $coll = $this->scandir($dir, '*', FALSE, 0, ~GLOB_NOHIDDEN); + if ($coll !== FALSE) { foreach ($coll['dirs'] as $f) { @@ -3860,7 +3466,7 @@ protected function unlink($legal_url, $mime_filters) } else { - $rv = false; + $rv = FALSE; } $rv &= @rmdir($file); @@ -3871,14 +3477,14 @@ protected function unlink($legal_url, $mime_filters) { $mime = $this->getMimeFromExt($file); // take the fast track to mime type sniffing; we'll live with the (rather low) risk of being inacurate due to accidental/intentional misnaming of the files if (!$this->IsAllowedMimeType($mime, $mime_filters)) - return false; + return FALSE; } - $meta = &$this->getid3_cache->pick($legal_url, $this, false); - assert($meta != null); + $meta = &$this->getid3_cache->pick($legal_url, $this, FALSE); + assert($meta != NULL); $rv &= @unlink($file); - $rv &= $meta->delete(true); + $rv &= $meta->delete(TRUE); unset($meta); } @@ -3900,14 +3506,14 @@ protected function unlink($legal_url, $mime_filters) public function scandir($dir, $filemask, $see_thumbnail_dir, $glob_flags_or, $glob_flags_and) { // list files, except the thumbnail folder itself or any file in it: - $dir = self::enforceTrailingSlash($dir); + $dir = self::addTrailingSlash($dir); - $just_below_thumbnail_dir = false; + $just_below_thumbnail_dir = FALSE; if (!$see_thumbnail_dir) { $tnpath = $this->thumbnailCacheDir; if (FileManagerUtility::startswith($dir, $tnpath)) - return false; + return FALSE; $tnparent = $this->thumbnailCacheParentDir; $just_below_thumbnail_dir = ($dir == $tnparent); @@ -3922,7 +3528,7 @@ public function scandir($dir, $filemask, $see_thumbnail_dir, $glob_flags_or, $gl $flags |= $glob_flags_or; $coll = safe_glob($dir . $filemask, $flags); - if ($coll !== false) + if ($coll !== FALSE) { if ($just_below_thumbnail_dir) { @@ -3955,7 +3561,7 @@ public function getSafeExtension($extension, $safe_extension = 'txt', $mandatory if (!is_string($extension) || $extension === '') // can't use 'empty($extension)' as "0" is a valid extension itself. { //enforce a mandatory extension, even when there isn't one (due to filtering or original input producing none) - return (!empty($mandatory_extension) ? $mandatory_extension : (!empty($safe_extension) ? $safe_extension : $extension)); + return ( ! empty($mandatory_extension) ? $mandatory_extension : ( ! empty($safe_extension) ? $safe_extension : $extension)); } $extension = strtolower($extension); switch ($extension) @@ -3972,7 +3578,7 @@ public function getSafeExtension($extension, $safe_extension = 'txt', $mandatory case 'php4': case 'php5': case 'phps': - return (!empty($safe_extension) ? $safe_extension : $extension); + return ( ! empty($safe_extension) ? $safe_extension : $extension); default: return $extension; @@ -3988,10 +3594,10 @@ public function IsHiddenNameAllowed($file) { if ($file !== '.' && $file !== '..' && $file[0] === '.') { - return false; + return FALSE; } } - return true; + return TRUE; } public function IsHiddenPathAllowed($path) @@ -4004,11 +3610,11 @@ public function IsHiddenPathAllowed($path) { if (!$this->IsHiddenNameAllowed($file)) { - return false; + return FALSE; } } } - return true; + return TRUE; } @@ -4020,7 +3626,7 @@ public function IsHiddenPathAllowed($path) * directory. The directory part of the returned value equals $dir. * * Return NULL when $file is empty or when the specified directory does not reside within the - * directory tree rooted by options['directory'] + * directory tree rooted by options['documentRootPath'] * * Note that the given filename will be converted to a legal filename, containing a filesystem-legal * subset of ASCII characters only, before being used and returned by this function. @@ -4033,7 +3639,7 @@ public function IsHiddenPathAllowed($path) */ public function getUniqueName($fileinfo, $dir) { - $dir = self::enforceTrailingSlash($dir); + $dir = self::addTrailingSlash($dir); if (is_string($fileinfo)) { @@ -4042,7 +3648,7 @@ public function getUniqueName($fileinfo, $dir) if (!is_array($fileinfo)) { - return null; + return NULL; } $dotfile = (strlen($fileinfo['filename']) == 0); @@ -4054,13 +3660,13 @@ public function getUniqueName($fileinfo, $dir) * This is faster than using a dirscan to collect a set of existing filenames and feeding them as * an option array to pagetitle(), particularly for large directories. */ - $filename = FileManagerUtility::pagetitle($fileinfo['filename'], null, '-_., []()~!@+' /* . '#&' */, '-_,~@+#&'); + $filename = FileManagerUtility::pagetitle($fileinfo['filename'], NULL, '-_., []()~!@+' /* . '#&' */, '-_,~@+#&'); if (!$filename && !$dotfile) - return null; + return NULL; // also clean up the extension: only allow alphanumerics in there! - $ext = FileManagerUtility::pagetitle(isset($fileinfo['extension']) ? $fileinfo['extension'] : null); - $ext = (strlen($ext) > 0 ? '.' . $ext : null); + $ext = FileManagerUtility::pagetitle(isset($fileinfo['extension']) ? $fileinfo['extension'] : NULL); + $ext = (strlen($ext) > 0 ? '.' . $ext : NULL); // make sure the generated filename is SAFE: $fname = $filename . $ext; $file = $dir . $fname; @@ -4113,7 +3719,7 @@ public function getUniqueName($fileinfo, $dir) * * Note: exists as a method in this class, so you can override it when you override getThumb(). */ - public function predictThumbDimensions($orig_x, $orig_y, $max_x = null, $max_y = null, $ratio = true, $resizeWhenSmaller = false) + public function predictThumbDimensions($orig_x, $orig_y, $max_x = NULL, $max_y = NULL, $ratio = TRUE, $resizeWhenSmaller = FALSE) { return Image::calculate_resize_dimensions($orig_x, $orig_y, $max_x, $max_y, $ratio, $resizeWhenSmaller); } @@ -4142,14 +3748,24 @@ public function getIcon($file, $smallIcon) } $largeDir = (!$smallIcon ? 'Large/' : ''); - $url_path = $this->options['assetBasePath'] . 'Images/Icons/' . $largeDir . $ext . '.png'; - $path = (is_file($this->url_path2file_path($url_path))) - ? $url_path - : $this->options['assetBasePath'] . 'Images/Icons/' . $largeDir . 'default.png'; - $this->icon_cache[!$smallIcon][$ext] = $path; + $icon_path = $this->assetPath . 'Images/Icons/' . $largeDir . $ext . '.png'; + $icon_url = $this->options['assetPath'] . 'Images/Icons/' . $largeDir . $ext . '.png'; - return $path; + + $url = (is_file($icon_path)) + ? $icon_url + : $this->options['assetPath'] . 'Images/Icons/' . $largeDir . 'default.png'; + +/* + $this->log->logInfo('$icon_path : ' . $icon_path); + $this->log->logInfo('ICON url : ' . $url); +*/ + + + $this->icon_cache[!$smallIcon][$ext] = $url; + + return $url; } /** @@ -4171,26 +3787,28 @@ public function getIcon($file, $smallIcon) * @param integer $height the maximum number of pixels for height of the * thumbnail. */ - public function getThumb($meta, $path, $width, $height, $onlyIfExistsInCache = false) + public function getThumb($meta, $path, $width, $height, $onlyIfExistsInCache = FALSE) { + // @notice : Correct $thumbPath = $meta->getThumbPath($width . 'x' . $height); - if (!is_file($thumbPath)) + + if ( ! is_file($thumbPath)) { if ($onlyIfExistsInCache) - return false; + return FALSE; // make sure the cache subdirectory exists where we are going to store the thumbnail: $meta->mkCacheDir(); $img = new Image($path); + // generally save as lossy / lower-Q jpeg to reduce filesize, unless orig is PNG/GIF, higher quality for smaller thumbnails: - $img->resize($width, $height)->save($thumbPath, min(98, max(MTFM_THUMBNAIL_JPEG_QUALITY, MTFM_THUMBNAIL_JPEG_QUALITY + 0.15 * (250 - min($width, $height)))), true); + $img->resize($width, $height)->save($thumbPath, min(98, max(MTFM_THUMBNAIL_JPEG_QUALITY, MTFM_THUMBNAIL_JPEG_QUALITY + 0.15 * (250 - min($width, $height)))), TRUE); if (DEVELOPMENT) { $imginfo = $img->getMetaInfo(); $meta->store('img_info', $imginfo); - $meta->store('memory used', number_format(memory_get_peak_usage() / 1E6, 1) . ' MB'); $meta->store('memory estimated', number_format(@$imginfo['fileinfo']['usage_guestimate'] / 1E6, 1) . ' MB'); $meta->store('memory suggested', number_format(@$imginfo['fileinfo']['usage_min_advised'] / 1E6, 1) . ' MB'); @@ -4209,26 +3827,26 @@ public function getIconForError($emsg, $original_filename, $small_icon) if (empty($emsg)) { // just go and pick the extension-related icon for this one; nothing is wrong today, it seems. - $thumb_path = (!empty($original_filename) ? $original_filename : 'is.default-missing'); + $thumb_path = ( ! empty($original_filename) ? $original_filename : 'is.default-missing'); } else { $thumb_path = 'is.default-error'; - if (strpos($emsg, 'img_will_not_fit') !== false) + if (strpos($emsg, 'img_will_not_fit') !== FALSE) { $thumb_path = 'is.oversized_img'; } - else if (strpos($emsg, 'nofile') !== false) + else if (strpos($emsg, 'nofile') !== FALSE) { $thumb_path = 'is.default-missing'; } - else if (strpos($emsg, 'unsupported_imgfmt') !== false) + else if (strpos($emsg, 'unsupported_imgfmt') !== FALSE) { // just go and pick the extension-related icon for this one; nothing seriously wrong here. - $thumb_path = (!empty($original_filename) ? $original_filename : $thumb_path); + $thumb_path = ( ! empty($original_filename) ? $original_filename : $thumb_path); } - else if (strpos($emsg, 'image') !== false) + else if (strpos($emsg, 'image') !== FALSE) { $thumb_path = 'badly.broken_img'; } @@ -4288,9 +3906,9 @@ public function mkSafeUTF8($str) if (!mb_check_encoding($str, 'UTF-8') || $str !== mb_convert_encoding(mb_convert_encoding($str, 'UTF-32', 'UTF-8'), 'UTF-8', 'UTF-32')) { - $encoding = mb_detect_encoding($str, 'auto, ISO-8859-1', true); + $encoding = mb_detect_encoding($str, 'auto, ISO-8859-1', TRUE); $im = str_replace('?', '&qmark;', $str); - if ($encoding !== false) + if ($encoding !== FALSE) { $dst = mb_convert_encoding($im, 'UTF-8', $encoding); } @@ -4301,14 +3919,14 @@ public function mkSafeUTF8($str) //$dst = utf8_encode($im); //$dst = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-8', $im); - if (!mb_check_encoding($dst, 'UTF-8') || $dst !== mb_convert_encoding(mb_convert_encoding($dst, 'UTF-32', 'UTF-8'), 'UTF-8', 'UTF-32') || strpos($dst, '?') !== false) + if (!mb_check_encoding($dst, 'UTF-8') || $dst !== mb_convert_encoding(mb_convert_encoding($dst, 'UTF-32', 'UTF-8'), 'UTF-8', 'UTF-32') || strpos($dst, '?') !== FALSE) { // not UTF8 yet... try them all $encs = mb_list_encodings(); foreach ($encs as $encoding) { $dst = mb_convert_encoding($im, 'UTF-8', $encoding); - if (mb_check_encoding($dst, 'UTF-8') && $dst === mb_convert_encoding(mb_convert_encoding($dst, 'UTF-32', 'UTF-8'), 'UTF-8', 'UTF-32') && strpos($dst, '?') === false) + if (mb_check_encoding($dst, 'UTF-8') && $dst === mb_convert_encoding(mb_convert_encoding($dst, 'UTF-32', 'UTF-8'), 'UTF-8', 'UTF-32') && strpos($dst, '?') === FALSE) { return str_replace('&qmark;', '?', $dst); } @@ -4348,16 +3966,16 @@ public static function getParentDir($path) * so we'd rather not use dirname() :-( */ if (!is_string($path)) - return false; + return FALSE; $path = rtrim($path, '/'); // 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: if (strlen($path) <= 1) - return false; + return FALSE; $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... - if ($p2 === false) + if ($p2 === FALSE) { - return false; // tampering! + return FALSE; // tampering! } $prev = substr($path, 0, $p2 + 1); return $prev; @@ -4368,16 +3986,29 @@ public static function getParentDir($path) * For example, if the request was 'http://site.org/dir1/dir2/script.php', then this method will * return '/dir1/dir2/script.php'. * - * This is equivalent to $_SERVER['SCRIPT_NAME'] + * By default, this is equivalent to $_SERVER['SCRIPT_NAME']. + * + * This default can be overridden by specifying the options['requestScript'] when invoking the constructor. */ - public /* static */ function getRequestScriptURI() + + public function getRequestURI() { + return strtr($_SERVER['SCRIPT_NAME'], '\\', '/'); + +/* + if ( ! empty($this->options['requestScript'])) + { + return $this->options['requestScript']; + } + // see also: http://php.about.com/od/learnphp/qt/_SERVER_PHP.htm $path = strtr($_SERVER['SCRIPT_NAME'], '\\', '/'); return $path; +*/ } + /** * Return the URI absolute path to the directory pointed at by the current URI request. * For example, if the request was 'http://site.org/dir1/dir2/script', then this method will @@ -4385,11 +4016,12 @@ public static function getParentDir($path) * * Note that the path is returned WITH a trailing slash '/'. */ - public /* static */ function getRequestPath() + public function getRequestPath() { - // see also: http://php.about.com/od/learnphp/qt/_SERVER_PHP.htm - $path = self::getParentDir($this->getRequestScriptURI()); - $path = self::enforceTrailingSlash($path); + $path = self::getParentDir($this->getRequestURI()); + $path = self::addTrailingSlash($path); + +//echo('path : ' . $path); return $path; } @@ -4402,10 +4034,14 @@ public static function getParentDir($path) * * Throw an exception when the operation failed to produce a legal path. */ - public /* static */ function normalize($path) + public function normalize($path) { + $path = preg_replace('/(\\\|\/)+/', '/', $path); + $path = '/' . ltrim($path, '/'); + + /* * fold '../' directory parts to prevent malicious paths such as 'a/../../../../../../../../../etc/' * from succeeding @@ -4427,7 +4063,7 @@ public static function getParentDir($path) $path = '/' . $matches[2]; } - while (($pos = strpos($path, '/..')) !== false) + while (($pos = strpos($path, '/..')) !== FALSE) { $prev = substr($path, 0, $pos); /* @@ -4440,7 +4076,7 @@ public static function getParentDir($path) * so we'd rather not use dirname() :-( */ $p2 = strrpos($prev, '/'); - if ($p2 === false) + if ($p2 === FALSE) { throw new FileManagerException('path_tampering:' . $path); } @@ -4484,7 +4120,7 @@ public function rel2abs_url_path($path) } /** - * 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']. + * Accept an absolute URI path, i.e. rooted against DocumentRoot, and transform it to a LEGAL URI absolute path, i.e. rooted against options['documentRootPath']. * * Relative paths are assumed to be relative to the current request path, i.e. the getRequestPath() produced path. * @@ -4492,17 +4128,17 @@ public function rel2abs_url_path($path) * * Returns a fully normalized LEGAL URI path. * - * Throws a FileManagerException when the given path cannot be converted to a LEGAL URL, i.e. when it resides outside the options['directory'] subtree. + * Throws a FileManagerException when the given path cannot be converted to a LEGAL URL, i.e. when it resides outside the options['documentRootPath'] subtree. */ public function abs2legal_url_path($path) { - $root = $this->options['directory']; + $root = $this->documentRootPath; $path = $this->rel2abs_url_path($path); - // but we MUST make sure the path is still a LEGAL URI, i.e. sitting inside options['directory']: + // but we MUST make sure the path is still a LEGAL URI, i.e. sitting inside options['documentRootPath']: if (strlen($path) < strlen($root)) - $path = self::enforceTrailingSlash($path); + $path = self::addTrailingSlash($path); if (!FileManagerUtility::startsWith($path, $root)) { @@ -4515,9 +4151,12 @@ public function abs2legal_url_path($path) } /** - * Accept a URI relative or absolute LEGAL URI path and transform it to an absolute URI path, i.e. rooted against DocumentRoot. + * Accept a relative or absolute LEGAL URI path and transform it to an absolute URI path, i.e. rooted against DocumentRoot. * - * Relative paths are assumed to be relative to the current request path, i.e. the getRequestPath() produced path. + * Relative paths are assumed to be relative to the options['documentRootPath'] directory. This makes them equivalent to absolute paths within + * 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 + * we wish to resolve here, after all. So, yes, this deviates from the general principle applied elesewhere in the code. :-( + * Nevertheless, it's easier than scanning and tweaking the FM frontend everywhere. * * Note: as it uses normalize(), any illegal path will throw a FileManagerException * @@ -4525,32 +4164,20 @@ public function abs2legal_url_path($path) */ public function legal2abs_url_path($path) { - $root = $this->options['directory']; - - $path = strtr($path, '\\', '/'); - if (FileManagerUtility::startsWith($path, '/')) - { - // clip the trailing '/' off the $root path as $path has a leading '/' already: - $path = substr($root, 0, -1) . $path; - } + $path = $this->rel2abs_legal_url_path($path); - $path = $this->rel2abs_url_path($path); + $root = $this->documentRootPath; - // but we MUST make sure the path is still a LEGAL URI, i.e. sutting inside options['directory']: - if (strlen($path) < strlen($root)) - $path = self::enforceTrailingSlash($path); + // clip the trailing '/' off the $root path as $path has a leading '/' already: + $path = substr($root, 0, -1) . $path; - if (!FileManagerUtility::startsWith($path, $root)) - { - throw new FileManagerException('path_tampering:' . $path); - } return $path; } /** - * Accept a URI relative or absolute LEGAL URI path and transform it to an absolute LEGAL URI path, i.e. rooted against options['directory']. + * Accept a relative or absolute LEGAL URI path and transform it to an absolute LEGAL URI path, i.e. rooted against options['documentRootPath']. * - * Relative paths are assumed to be relative to the options['directory'] directory. This makes them equivalent to absolute paths within + * Relative paths are assumed to be relative to the options['documentRootPath'] directory. This makes them equivalent to absolute paths within * 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 * we wish to resolve here, after all. So, yes, this deviates from the general principle applied elesewhere in the code. :-( * Nevertheless, it's easier than scanning and tweaking the FM frontend everywhere. @@ -4561,25 +4188,11 @@ public function legal2abs_url_path($path) */ public function rel2abs_legal_url_path($path) { - if (0) // TODO: remove the 'relative is based on options['directory']' hack when the frontend has been fixed... - { - $path = $this->legal2abs_url_path($path); - - $root = $this->options['directory']; - - // clip the trailing '/' off the $root path before reduction: - $path = str_replace(substr($root, 0, -1), '', $path); - } - else - { - $path = strtr($path, '\\', '/'); - if (!FileManagerUtility::startsWith($path, '/')) - { - $path = '/' . $path; - } + $path = strtr($path, '\\', '/'); + + $path = '/' . ltrim($path, '/'); - $path = $this->normalize($path); - } + $path = $this->normalize($path); return $path; } @@ -4594,14 +4207,15 @@ public function rel2abs_legal_url_path($path) public function url_path2file_path($url_path) { $url_path = $this->rel2abs_url_path($url_path); +//log_message('error', 'url_path2file_path :' . $url_path); +//log_message('error', 'FileSystemPath4SiteDocumentRoot :' . $this->options['FileSystemPath4SiteDocumentRoot']); + $path = $this->documentRootPath . $url_path; - $path = $this->options['documentRootPath'] . $url_path; - //$path = $this->normalize($path); -- taken care of by rel2abs_url_path already return $path; } /** - * Return the filesystem absolute path for the relative URI path or absolute LEGAL URI path. + * Return the filesystem absolute path for the relative or absolute LEGAL URI path. * * Note: as it uses normalize(), any illegal path will throw an FileManagerException * @@ -4616,12 +4230,24 @@ public function legal_url_path2file_path($url_path) return $path; } - public static function enforceTrailingSlash($string) + public static function addTrailingSlash($string) { - return (strrpos($string, '/') === strlen($string) - 1 ? $string : $string . '/'); + return rtrim($string, '/') . '/'; } + + + public function getAbsolutePath($path) + { + $path = $this->normalize($path); + return rtrim($this->managedBaseDir, '/') . $path; + } + public function getAbsoluteUrl($path) + { + $path = $this->normalize($path); + return rtrim($this->managedBaseUrl, '/') . $path; + } @@ -4638,7 +4264,7 @@ public static function compressHTML($str) } - protected /* static */ function modify_json4exception(&$jserr, $emsg, $target_info = null) + protected /* static */ function modify_json4exception(&$jserr, $emsg, $target_info = NULL) { if (empty($emsg)) return; @@ -4655,9 +4281,18 @@ public static function compressHTML($str) } else { - $extra1 = (!empty($e[1]) ? $this->mkSafe4Display($e[1]) : ''); - $extra2 = (!empty($target_info) ? ' (' . $this->mkSafe4Display($target_info) . ')' : ''); - $jserr['error'] = $emsg = '${backend.' . $e[0] . '}' . $extra1 . $extra2; + $extra1 = ( ! empty($e[1]) ? $this->mkSafe4Display($e[1]) : ''); + $extra2 = ( ! empty($target_info) ? ' (' . $this->mkSafe4Display($target_info) . ')' : ''); + $jserr['error'] = $emsg = '${backend.' . $e[0] . '}'; + if ($e[0] != 'disabled') + { + // only append the extra data when it's NOT the 'disabled on this server' message! + $jserr['error'] .= $extra1 . $extra2; + } + else + { + $jserr['error'] .= ' (${' . $extra1 . '})'; + } } $jserr['status'] = 0; } @@ -4668,15 +4303,15 @@ public static function compressHTML($str) - public function getAllowedMimeTypes($mime_filter = null) + public function getAllowedMimeTypes($mime_filter = NULL) { $mimeTypes = array(); - if (empty($mime_filter)) return null; + if (empty($mime_filter)) return NULL; $mset = explode(',', $mime_filter); for($i = count($mset) - 1; $i >= 0; $i--) { - if (strpos($mset[$i], '/') === false) + if (strpos($mset[$i], '/') === FALSE) $mset[$i] .= '/'; } @@ -4705,7 +4340,7 @@ public function getMimeTypeDefinitions() if (!$mimes) { - $mimes = parse_ini_file($this->options['mimeTypesPath']); + $mimes = parse_ini_file($this->mimeTypesFilePath); if (is_array($mimes)) { @@ -4713,8 +4348,8 @@ public function getMimeTypeDefinitions() { $m = explode(',', (string)$v); $mimes[$k] = $m[0]; - $p = null; - if (!empty($m[1])) + $p = NULL; + if ( ! empty($m[1])) { $p = trim($m[1]); } @@ -4730,7 +4365,7 @@ public function getMimeTypeDefinitions() } else { - $mimes = false; + $mimes = FALSE; } } @@ -4742,9 +4377,9 @@ public function getMimeTypeDefinitions() public function IsAllowedMimeType($mime_type, $mime_filters) { if (empty($mime_type)) - return false; + return FALSE; if (!is_array($mime_filters)) - return true; + return TRUE; return in_array($mime_type, $mime_filters); } @@ -4758,10 +4393,10 @@ public function getMimeFromExt($file) { $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); - $mime = null; + $mime = NULL; if (MTFM_USE_FINFO_OPEN) { - $ini = error_reporting(0); +// $ini = error_reporting(0); if (function_exists('finfo_open') && $f = finfo_open(FILEINFO_MIME, getenv('MAGIC'))) { $mime = finfo_file($f, $file); @@ -4770,7 +4405,7 @@ public function getMimeFromExt($file) $mime = $ma[0]; finfo_close($f); } - error_reporting($ini); +// error_reporting($ini); } if ((empty($mime) || $mime === 'application/octet-stream') && strlen($ext) > 0) @@ -4802,13 +4437,13 @@ public function getExtFromMime($mime) if (array_key_exists($mime, $mime2ext_arr)) return $mime2ext_arr[$mime]; - return null; + return NULL; } /** * Returns (if possible) all info about the given file, mimetype, dimensions, the works * - * @param string $file physical filesystem path to the file we want to know all about + * @param string $file physical filesystem path to the file we want to get info * * @param string $legal_url * 'legal URL path' to the file; used as the key to the corresponding @@ -4818,19 +4453,19 @@ public function getExtFromMime($mime) * * @return the info array as produced by getID3::analyze(), as part of a MTFMCacheEntry reference */ - public function getFileInfo($file, $legal_url) + public function getFileInfo($file, $legal_url, $force_recheck = FALSE) { // when hash exists in cache, return that one: $meta = &$this->getid3_cache->pick($legal_url, $this); - assert($meta != null); + assert($meta != NULL); $mime_check = $meta->fetch('mime_type'); - if (empty($mime_check)) + if (empty($mime_check) || $force_recheck) { // cache entry is not yet filled: we'll have to do the hard work now and store it. if (is_dir($file)) { - $meta->store('mime_type', 'text/directory', false); - $meta->store('analysis', null, false); + $meta->store('mime_type', 'text/directory', FALSE); + $meta->store('analysis', NULL, FALSE); } else { @@ -4857,7 +4492,7 @@ public function getFileInfo($file, $legal_url) - protected /* static */ function getGETparam($name, $default_value = null) + protected function getGETparam($name, $default_value = NULL) { if (is_array($_GET) && !empty($_GET[$name])) { @@ -4872,7 +4507,7 @@ public function getFileInfo($file, $legal_url) return $default_value; } - protected /* static */ function getPOSTparam($name, $default_value = null) + protected function getPOSTparam($name, $default_value = NULL) { if (is_array($_POST) && !empty($_POST[$name])) { @@ -4953,7 +4588,7 @@ public static function startsWith($string, $look) * * @return cleaned-up and uniquified name derived from ($data). */ - public static function pagetitle($data, $options = null, $extra_allowed_chars = null, $trim_chars = null) + public static function pagetitle($data, $options = NULL, $extra_allowed_chars = NULL, $trim_chars = NULL) { static $regex; if (!$regex){ @@ -5011,12 +4646,31 @@ public static function isBinary($str) { $c = ord($str[$i]); // do not accept ANY codes below SPACE, except TAB, CR and LF. - if ($c == 255 || ($c < 32 /* SPACE */ && $c != 9 && $c != 10 && $c != 13)) return true; + if ($c == 255 || ($c < 32 /* SPACE */ && $c != 9 && $c != 10 && $c != 13)) return TRUE; } - return false; + return FALSE; } + + public static function cleanUrl($str, $replace=array(), $delimiter='-') + { + setlocale(LC_ALL, 'en_US.UTF8'); + + if( !empty($replace) ) { + $str = str_replace((array)$replace, ' ', $str); + } + + $clean = iconv('UTF-8', 'ASCII//TRANSLIT', $str); + $clean = preg_replace("/[^a-zA-Z0-9\/_.|+ -]/", '', $clean); + $clean = strtolower(trim($clean, '-. ')); + $clean = preg_replace("/[\/_|+ -]+/", $delimiter, $clean); + return $clean; + } + + + + /** * Apply rawurlencode() to each of the elements of the given path * @@ -5053,7 +4707,7 @@ public static function fmt_bytecount($val, $precision = 1) * * Attempts some 'intelligent' conversions for better readability and information compacting. */ - public static function table_var_dump(&$variable, $wrap_in_td = false, $show_types = false, $level = 0) + public static function table_var_dump(&$variable, $wrap_in_td = FALSE, $show_types = FALSE, $level = 0) { $returnstring = ''; if (is_array($variable)) @@ -5062,8 +4716,20 @@ public static function table_var_dump(&$variable, $wrap_in_td = false, $show_typ $returnstring .= ''; $returnstring .= ($wrap_in_td ? '' : ''); @@ -5145,24 +4811,24 @@ public static function table_var_dump(&$variable, $wrap_in_td = false, $show_typ $returnstring .= ($wrap_in_td ? '' : ''); if ($variable->binarydata_mode == 'procd') { - $returnstring .= '' . self::table_var_dump($variable->binarydata, false, false, $level + 1) . ''; + $returnstring .= '' . self::table_var_dump($variable->binarydata, FALSE, FALSE, $level + 1) . ''; } else { $temp = unpack('H*', $variable->binarydata); $temp = str_split($temp[1], 8); - $returnstring .= '' . self::table_var_dump(implode(' ', $temp), false, false, $level + 1) . ''; + $returnstring .= '' . self::table_var_dump(implode(' ', $temp), FALSE, FALSE, $level + 1) . ''; } $returnstring .= ($wrap_in_td ? '' : ''); } else { - $returnstring .= ($wrap_in_td ? '' : '').print_r($variable, true).($wrap_in_td ? '' : ''); + $returnstring .= ($wrap_in_td ? '' : '').print_r($variable, TRUE).($wrap_in_td ? '' : ''); } } else if (is_object($variable)) { - $returnstring .= ($wrap_in_td ? '' : '').print_r($variable, true).($wrap_in_td ? '' : ''); + $returnstring .= ($wrap_in_td ? '' : '').print_r($variable, TRUE).($wrap_in_td ? '' : ''); } else if (is_null($variable)) { @@ -5199,7 +4865,7 @@ public function __construct($meta, $img) { $this->metadata = $meta; $this->imagedata = $img; - $this->id3_procsupport_obj = true; + $this->id3_procsupport_obj = TRUE; } public static function __set_state($arr) @@ -5219,7 +4885,7 @@ public function __construct($data, $mode = 'procd') { $this->binarydata_mode = $mode; $this->binarydata = $data; - $this->id3_procsupport_obj = true; + $this->id3_procsupport_obj = TRUE; } public static function __set_state($arr) diff --git a/Assets/Connector/KLogger.php b/Assets/Connector/KLogger.php new file mode 100755 index 0000000..00ce4e5 --- /dev/null +++ b/Assets/Connector/KLogger.php @@ -0,0 +1,395 @@ +logInfo('Returned a million search results'); //Prints to the log file + * $log->logFatal('Oh dear.'); //Prints to the log file + * $log->logDebug('x = 5'); //Prints nothing due to current severity threshhold + * + * @author Kenny Katzgrau + * @since July 26, 2008 + * @link http://codefury.net + * @version 0.1 + */ + +/** + * Class documentation + */ +class KLogger +{ + /** + * Error severity, from low to high. From BSD syslog RFC, secion 4.1.1 + * @link http://www.faqs.org/rfcs/rfc3164.html + */ + const EMERG = 0; // Emergency: system is unusable + const ALERT = 1; // Alert: action must be taken immediately + const CRIT = 2; // Critical: critical conditions + const ERR = 3; // Error: error conditions + const WARN = 4; // Warning: warning conditions + const NOTICE = 5; // Notice: normal but significant condition + const INFO = 6; // Informational: informational messages + const DEBUG = 7; // Debug: debug messages + + //custom logging level + /** + * Log nothing at all + */ + const OFF = 8; + /** + * Alias for CRIT + * @deprecated + */ + const FATAL = 2; + + /** + * Internal status codes + */ + const STATUS_LOG_OPEN = 1; + const STATUS_OPEN_FAILED = 2; + const STATUS_LOG_CLOSED = 3; + + /** + * Current status of the log file + * @var integer + */ + private $_logStatus = self::STATUS_LOG_CLOSED; + /** + * Holds messages generated by the class + * @var array + */ + private $_messageQueue = array(); + /** + * Path to the log file + * @var string + */ + private $_logFilePath = null; + /** + * Current minimum logging threshold + * @var integer + */ + private $_severityThreshold = self::INFO; + /** + * This holds the file handle for this instance's log file + * @var resource + */ + private $_fileHandle = null; + + /** + * Standard messages produced by the class. Can be modified for il8n + * @var array + */ + private $_messages = array( + //'writefail' => 'The file exists, but could not be opened for writing. Check that appropriate permissions have been set.', + 'writefail' => 'The file could not be written to. Check that appropriate permissions have been set.', + 'opensuccess' => 'The log file was opened successfully.', + 'openfail' => 'The file could not be opened. Check permissions.', + ); + + /** + * Default severity of log messages, if not specified + * @var integer + */ + private static $_defaultSeverity = self::DEBUG; + /** + * Valid PHP date() format string for log timestamps + * @var string + */ + private static $_dateFormat = 'Y-m-d G:i:s'; + /** + * Octal notation for default permissions of the log file + * @var integer + */ + private static $_defaultPermissions = 0777; + /** + * Array of KLogger instances, part of Singleton pattern + * @var array + */ + private static $instances = array(); + + /** + * Partially implements the Singleton pattern. Each $logDirectory gets one + * instance. + * + * @param string $logDirectory File path to the logging directory + * @param integer $severity One of the pre-defined severity constants + * @return KLogger + */ + public static function instance($logDirectory = false, $severity = false) + { + if ($severity === false) { + $severity = self::$_defaultSeverity; + } + + if ($logDirectory === false) { + if (count(self::$instances) > 0) { + return current(self::$instances); + } else { + $logDirectory = dirname(__FILE__); + } + } + + if (in_array($logDirectory, self::$instances)) { + return self::$instances[$logDirectory]; + } + + self::$instances[$logDirectory] = new self($logDirectory, $severity); + + return self::$instances[$logDirectory]; + } + + /** + * Class constructor + * + * @param string $logDirectory File path to the logging directory + * @param integer $severity One of the pre-defined severity constants + * @return void + */ + public function __construct($logDirectory, $severity) + { + $logDirectory = rtrim($logDirectory, '\\/'); + + if ($severity === self::OFF) { + return; + } + + $this->_logFilePath = $logDirectory + . DIRECTORY_SEPARATOR + . 'log_' + . date('Y-m-d') + . '.txt'; + + $this->_severityThreshold = $severity; + if (!file_exists($logDirectory)) { + mkdir($logDirectory, self::$_defaultPermissions, true); + } + + if (file_exists($this->_logFilePath) && !is_writable($this->_logFilePath)) { + $this->_logStatus = self::STATUS_OPEN_FAILED; + $this->_messageQueue[] = $this->_messages['writefail']; + return; + } + + if (($this->_fileHandle = fopen($this->_logFilePath, 'a'))) { + $this->_logStatus = self::STATUS_LOG_OPEN; + $this->_messageQueue[] = $this->_messages['opensuccess']; + } else { + $this->_logStatus = self::STATUS_OPEN_FAILED; + $this->_messageQueue[] = $this->_messages['openfail']; + } + } + + /** + * Class destructor + */ + public function __destruct() + { + if ($this->_fileHandle) { + fclose($this->_fileHandle); + } + } + /** + * Writes a $line to the log with a severity level of DEBUG + * + * @param string $line Information to log + * @return void + */ + public function logDebug($line) + { + $this->log($line, self::DEBUG); + } + + /** + * Returns (and removes) the last message from the queue. + * @return string + */ + public function getMessage() + { + return array_pop($this->_messageQueue); + } + + /** + * Returns the entire message queue (leaving it intact) + * @return array + */ + public function getMessages() + { + return $this->_messageQueue; + } + + /** + * Empties the message queue + * @return void + */ + public function clearMessages() + { + $this->_messageQueue = array(); + } + + /** + * Sets the date format used by all instances of KLogger + * + * @param string $dateFormat Valid format string for date() + */ + public static function setDateFormat($dateFormat) + { + self::$_dateFormat = $dateFormat; + } + + /** + * Writes a $line to the log with a severity level of INFO. Any information + * can be used here, or it could be used with E_STRICT errors + * + * @param string $line Information to log + * @return void + */ + public function logInfo($line) + { + $this->log($line, self::INFO); + } + + /** + * Writes a $line to the log with a severity level of NOTICE. Generally + * corresponds to E_STRICT, E_NOTICE, or E_USER_NOTICE errors + * + * @param string $line Information to log + * @return void + */ + public function logNotice($line) + { + $this->log($line, self::NOTICE); + } + + /** + * Writes a $line to the log with a severity level of WARN. Generally + * corresponds to E_WARNING, E_USER_WARNING, E_CORE_WARNING, or + * E_COMPILE_WARNING + * + * @param string $line Information to log + * @return void + */ + public function logWarn($line) + { + $this->log($line, self::WARN); + } + + /** + * Writes a $line to the log with a severity level of ERR. Most likely used + * with E_RECOVERABLE_ERROR + * + * @param string $line Information to log + * @return void + */ + public function logError($line) + { + $this->log($line, self::ERR); + } + + /** + * Writes a $line to the log with a severity level of FATAL. Generally + * corresponds to E_ERROR, E_USER_ERROR, E_CORE_ERROR, or E_COMPILE_ERROR + * + * @param string $line Information to log + * @return void + * @deprecated Use logCrit + */ + public function logFatal($line) + { + $this->log($line, self::FATAL); + } + + /** + * Writes a $line to the log with a severity level of ALERT. + * + * @param string $line Information to log + * @return void + */ + public function logAlert($line) + { + $this->log($line, self::ALERT); + } + + /** + * Writes a $line to the log with a severity level of CRIT. + * + * @param string $line Information to log + * @return void + */ + public function logCrit($line) + { + $this->log($line, self::CRIT); + } + + /** + * Writes a $line to the log with a severity level of EMERG. + * + * @param string $line Information to log + * @return void + */ + public function logEmerg($line) + { + $this->log($line, self::EMERG); + } + + /** + * Writes a $line to the log with the given severity + * + * @param string $line Text to add to the log + * @param integer $severity Severity level of log message (use constants) + */ + public function log($line, $severity) + { + if ($this->_severityThreshold >= $severity) { + $status = $this->_getTimeLine($severity); + $this->writeFreeFormLine("$status $line \n"); + } + } + + /** + * Writes a line to the log without prepending a status or timestamp + * + * @param string $line Line to write to the log + * @return void + */ + public function writeFreeFormLine($line) + { + if ($this->_logStatus == self::STATUS_LOG_OPEN + && $this->_severityThreshold != self::OFF) { + if (fwrite($this->_fileHandle, $line) === false) { + $this->_messageQueue[] = $this->_messages['writefail']; + } + } + } + + private function _getTimeLine($level) + { + $time = date(self::$_dateFormat); + + switch ($level) { + case self::EMERG: + return "$time - EMERG -->"; + case self::ALERT: + return "$time - ALERT -->"; + case self::CRIT: + return "$time - CRIT -->"; + case self::FATAL: # FATAL is an alias of CRIT + return "$time - FATAL -->"; + case self::NOTICE: + return "$time - NOTICE -->"; + case self::INFO: + return "$time - INFO -->"; + case self::WARN: + return "$time - WARN -->"; + case self::DEBUG: + return "$time - DEBUG -->"; + case self::ERR: + return "$time - ERROR -->"; + default: + return "$time - LOG -->"; + } + } +} diff --git a/Assets/Css/Additions.css b/Assets/Css/Additions.css old mode 100644 new mode 100755 index 9af5aea..831919e --- a/Assets/Css/Additions.css +++ b/Assets/Css/Additions.css @@ -119,4 +119,22 @@ div.filemanager-dialog-engine-trident4 button, div.filemanager-dialog-engine-tri div.filemanager-dialog button.filemanager-dialog-decline { right: auto; left: 15px; -} \ No newline at end of file +} + +/* NoFlash.upload */ +div.filemanager #filemanager_upload_Filedata +{ + width: auto; + font-size: 11px; +} + +div.filemanager label.filemanager-resize +{ + width: auto; +} + +div.filemanager label.filemanager-resize span +{ + width: 20%; + overflow: hidden; +} diff --git a/Assets/Css/FileManager.css b/Assets/Css/FileManager.css old mode 100644 new mode 100755 index 85b5e6c..60ac58f --- a/Assets/Css/FileManager.css +++ b/Assets/Css/FileManager.css @@ -372,7 +372,6 @@ div.filemanager-container span.fi span, div.filemanager-container span.thumb { white-space: nowrap; display: block; - overflow: hidden; vertical-align: middle; /* pretty useless, but keep it in? */ overflow: visible; /* ^^^ since the floats are all images, this does a nice graphic effect for very long filenames: @@ -838,7 +837,7 @@ div.filemanager button.filemanager-serialize { div.filemanager-gallery { position: absolute; /* z-index: 1000; */ - width: 778px; /* 9 images wide; old 750px value was ~ 8.5 so pretty useless */ + width: 798px; /* 9 images wide + optional scrollbar; old 750px value was ~ 8.5 so pretty useless */ height: 144px; display: none; @@ -1034,8 +1033,11 @@ div.filemanager-filelist .fi:hover .name { div.filemanager-filelist .fi .dir-gal-thumb-bg { background-repeat: no-repeat; background-position: center center; + /* + -- Size depends on the user's choice for gallery thumbs sizes width: 100%; height: 48px; + */ } div.filemanager-filelist .fi .name { overflow: hidden; @@ -1046,6 +1048,12 @@ div.filemanager-filelist .fi .name { /* Extra (Tech.) Info / Diagnostics in preview pane */ +/* +div.filemanager div.filemanager-diag-dump { + display: none; +} +*/ + div.filemanager div.filemanager-diag-dump ul { width: auto; height: auto; @@ -1068,7 +1076,12 @@ div.filemanager div.filemanager-diag-dump li span.key { font-weight: bold; } -div.filemanager div.filemanager-diag-dump li span.key.overlarge { +/* + * overlarger series ~ golden ratio: 0010, 0016, 0025, 0040, ... + * + * Use these classes to tweak the look of the detail info dump. + */ +div.filemanager div.filemanager-diag-dump li span.key.overlarger0016 { width: auto; /* prevent key text from overlapping the data; that looks really ugly. */ } @@ -1083,3 +1096,68 @@ div.filemanager div.filemanager-diag-dump li img { + + +/* the thumbnails gallery displayed in the preview/detail pane */ + +#gallery-tn-container +{ + margin: 15px; + padding: 5px; + border: 1px solid #b2a0a0; + background: #fffafa; + box-shadow: rgba(255, 0, 0, 0.3) 0 0 10px; + -moz-box-shadow: rgba(255, 0, 0, 0.3) 0 0 10px; + -webkit-box-shadow: rgba(255, 0, 0, 0.3) 0 0 10px; +} + +#gallery-tn-container div +{ + float: left; + + /* border: green solid 1px; */ + margin: 5px; + +} + +#gallery-tn-container div a +{ + display: block; + /* + width: 250px; + height: 250px; + + -- The (and the within!) are 'sized' in the JS code + */ + border: 1px solid #b2a0a0; + background: #eee2e2; + padding: 0; + margin: 0; + + /* horizontal centering of the contained image */ + text-align: center; + margin-left: auto; + margin-right: auto; +} + +#gallery-tn-container div a img +{ + padding: 0; + margin: 0; + border: 0 none; +} + +/* + * http://www.smashingmagazine.com/2007/05/01/css-float-theory-things-you-should-know/ + * --> + * http://www.positioniseverything.net/easyclearing.html + * --> + * http://blogs.sitepoint.com/simple-clearing-of-floats/ + */ +#gallery-tn-container +{ + overflow: auto; +} + + + diff --git a/Assets/Css/FileManager_ie7.css b/Assets/Css/FileManager_ie7.css new file mode 100755 index 0000000..4485f83 --- /dev/null +++ b/Assets/Css/FileManager_ie7.css @@ -0,0 +1,39 @@ +div.filemanager div.filemanager-browserscroll { + position: relative; +} + +div.filemanager-infos { + width: auto; +} + +div.filemanager dl { + margin: 5px 0 10px 0; + width: 100%; + clear: both; + overflow: auto; + position: relative; +} + +div.filemanager-details { + position: relative; + overflow-x: hidden; + height: 100%; +} + +#mootools-filemanager div.filemanager div.filemanager-menu { + width: 350px; + background-color: transparent; +} + +#mootools-filemanager div.filemanager-menu label { +} + +div.filemanager button, +div.filemanager-dialog button{ + display: inline; + overflow: visible; +} + +div.filemanager-container span.fi span { + overflow:hidden; +} diff --git a/Assets/Css/FileManager_ionize.css b/Assets/Css/FileManager_ionize.css new file mode 100644 index 0000000..4ef37d0 --- /dev/null +++ b/Assets/Css/FileManager_ionize.css @@ -0,0 +1,98 @@ +div.filemanager div.filemanager-menu { + left: 30% !important; + width: 70% !important; + + box-shadow: none !important; + -moz-box-shadow: none !important; + -webkit-box-shadow: none !important; + -o-box-shadow: none !important; + + border-radius: 0 !important; + -moz-border-radius: 0 !important; + -webkit-border-radius: 0 !important; + -o-border-radius: 0 !important; +} +div.filemanager div.filemanager-browsercontainer { + background: #dee6f0 url("../Images/filelist.png") repeat-x bottom left !important; + width: 30% !important; + + box-shadow: none !important; + -moz-box-shadow: none !important; + -webkit-box-shadow: none !important; + -o-box-shadow: none !important; + + border-radius: 0 !important; + -moz-border-radius: 0 !important; + -webkit-border-radius: 0 !important; + -o-border-radius: 0 !important; +} +div.filemanager-infos { + background-color: #fff; + left: 30% !important; + width: 68% !important; +} + +div.filemanager button { + background: url("../../../../images/button-bg.jpg") no-repeat scroll 0 50% transparent; + border: 1px solid #FFFFFF; + color: #FFFFFF; + cursor: pointer; + font-size: 11px; + height: 24px; + line-height: normal; + margin: 4px 5px; + padding: 2px 6px; + vertical-align: middle; + + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + -o-border-radius: 0px; + border-radius: 0px; + + text-shadow: none; +} + +div.filemanager button:hover, +div.filemanager button.hover, +div.filemanager button:focus { + background: url(../../../../images/button-bg.jpg) no-repeat -200px 50%; + border: 1px solid #FFFFFF; +} + +div.filemanager button.disabled, +div.filemanager button.disabled:hover, +div.filemanager button.disabled:focus, +div.filemanager button.filemanager-open.disabled, +div.filemanager button.filemanager-open.disabled:hover, +div.filemanager button.filemanager-open.disabled:focus { + background: none repeat scroll 0 0 #DDDDDD; + color: #666666; + cursor: default; + text-shadow: none; + border: 1px solid #FFFFFF; +} +div.filemanager button.filemanager-open { + background: url("../../../../images/submit-bg.jpg") no-repeat scroll 0 50% transparent; + margin-right: 10px; + font-weight: normal; +} + +div.filemanager-filelist .fi, div.gallery-image { + background-color:#f2f2f2; +} + +/* Browser list */ +div.filemanager div.filemanager-browserscroll li.list { + background-color:transparent; + border:none; + padding:0; +} + +/* Preview description correction (against .panel dd) */ +div.filemanager dd{ + margin:0 0 0 10px; + width:auto; +} +div.filemanager-menu form { + width:auto; +} diff --git a/Assets/Images/cancel.png b/Assets/Images/cancel.png index c149c2b..7fe6d18 100644 Binary files a/Assets/Images/cancel.png and b/Assets/Images/cancel.png differ diff --git a/Assets/Images/destroy.png b/Assets/Images/destroy.png old mode 100644 new mode 100755 index dc35a89..7404ecc Binary files a/Assets/Images/destroy.png and b/Assets/Images/destroy.png differ diff --git a/Assets/Images/filelist.png b/Assets/Images/filelist.png index e8af3cb..5833d21 100644 Binary files a/Assets/Images/filelist.png and b/Assets/Images/filelist.png differ diff --git a/Assets/Images/filelist_selected.png b/Assets/Images/filelist_selected.png index f129115..f06f6cb 100644 Binary files a/Assets/Images/filelist_selected.png and b/Assets/Images/filelist_selected.png differ diff --git a/Assets/js/jsGET.js b/Assets/js/jsGET.js index b23c92c..52087fd 100644 --- a/Assets/js/jsGET.js +++ b/Assets/js/jsGET.js @@ -156,7 +156,6 @@ var jsGET = { if (typeof remove !== 'object') { removes = [remove]; - //removes[0] = remove; } else { removes = remove; } diff --git a/Assets/js/milkbox/milkbox.js b/Assets/js/milkbox/milkbox.js index 163a700..4c20a14 100644 --- a/Assets/js/milkbox/milkbox.js +++ b/Assets/js/milkbox/milkbox.js @@ -31,6 +31,9 @@ this.Milkbox = new Class({ removeTitle:true, autoSize:true, autoSizeMaxHeight:0,//only if autoSize==true + autoSizeMaxWidth:0,//only if autoSize==true + autoSizeMinHeight:0,//only if autoSize==true + autoSizeMinWidth:0,//only if autoSize==true centered:false, imageOfText:'of', onXmlGalleries:function(){}, @@ -252,17 +255,25 @@ this.Milkbox = new Class({ //to prevent the loader to show if the file is cached startLoadingCheck:function(){ var t = 0; - this.loadCheckerId = (function(){ - t+=1; - if(t > 5){ - this.display.show_loader(); - this.stopLoadingCheck(); - } - }).periodical(100,this); + if (!this.loadCheckerId) + { + this.loadCheckerId = (function(){ + t+=1; + if(t > 5){ + if (this.loadCheckerId) + { + // only show the loader when the timer has not been cleared yet! + this.display.show_loader(); + } + this.stopLoadingCheck(); + } + }).periodical(100,this); + } }, stopLoadingCheck:function(){ clearInterval(this.loadCheckerId); + this.loadCheckerId = null; }, preloadFiles:function(preloads){ @@ -545,6 +556,9 @@ this.Milkbox = new Class({ centered:this.options.centered, auto_size:this.options.autoSize, autosize_max_height:this.options.autoSizeMaxHeight, + autosize_max_width:this.options.autoSizeMaxWidth, + autosize_min_height:this.options.autoSizeMinHeight, + autosize_min_width:this.options.autoSizeMinWidth, image_of_text:this.options.imageOfText }); }, @@ -723,6 +737,9 @@ var MilkboxDisplay= new Class({ centered:false, auto_size:false, autosize_max_height:0, + autosize_max_width:0, + autosize_min_height:0, + autosize_min_width:0, fixup_dimension:true, image_of_text:'of', zIndex: 410000, // required to be a high number > 400000 as the 'filemanager as tinyMCE plugin' sits at z-index 400K+ @@ -890,8 +907,14 @@ var MilkboxDisplay= new Class({ //console.log('resize_fx start'); }, onComplete:function(){ + var w, h; + w = this.current_file.width; + h = this.current_file.height; + if (w < this.options.autosize_min_width) w = this.options.autosize_min_width; + if (h < this.options.autosize_min_height) h = this.options.autosize_min_height; this.show_bottom(); - this.filebox.setStyle('height',this.current_file.height+'px'); + this.filebox.setStyle('width', w+'px'); + this.filebox.setStyle('height', h+'px'); this.filebox.setStyle('opacity',0).grab(this.current_file).tween('opacity',1); this.fireEvent('resizeComplete'); }.bind(this) @@ -944,6 +967,26 @@ var MilkboxDisplay= new Class({ }; file.set({ 'width':file_size.w.toInt(), 'height':file_size.h.toInt() }); } + + if(this.options.autosize_min_height > file_size.h){ + var mt = Math.round((this.options.autosize_min_height - file_size.h) / 2); + var mb = this.options.autosize_min_height - file_size.h - mt; + file.setStyles({ + 'margin-top': mt, // no need to say "+'px'": mootools takes care of that! + 'margin-bottom': mb + }); + file_size.h = this.options.autosize_min_height; + } + if(this.options.autosize_min_width > file_size.w){ + var ml = Math.round((this.options.autosize_min_width - file_size.w) / 2); + var mr = this.options.autosize_min_width - file_size.w - ml; + file.setStyles({ + 'margin-left': ml, + 'margin-right': mr + }); + file_size.w = this.options.autosize_min_width; + } + file_size = Object.map(file_size,function(value){ return value.toInt(); }); @@ -1005,22 +1048,28 @@ var MilkboxDisplay= new Class({ ratio = (ratio <= 1 ? ratio : 1); i_size = Object.map(i_size,function(value){ - return Math.floor(value*ratio); + return value*ratio; }); ratio = (max_size[check]/i_size[check] <= 1 ? max_size[check]/i_size[check] : 1); i_size = Object.map(i_size,function(value){ - return Math.floor(value*ratio); + return value*ratio; }); if(this.options.autosize_max_height > 0){ - ratio = (this.options.autosize_max_height/i_size.height < 1 ? this.options.autosize_max_height/i_size.height : 1); + ratio = (this.options.autosize_max_height/i_size.h < 1 ? this.options.autosize_max_height/i_size.h : 1); + i_size = Object.map(i_size,function(value){ + return value*ratio; + }); + } + if(this.options.autosize_max_width > 0){ + ratio = (this.options.autosize_max_width/i_size.w < 1 ? this.options.autosize_max_width/i_size.w : 1); i_size = Object.map(i_size,function(value){ - return Math.floor(value*ratio); + return value*ratio; }); } - image.set({ 'width':i_size.w, 'height':i_size.h }); + image.set({ 'width': Math.round(i_size.w), 'height': Math.round(i_size.h) }); return image; },//get_resized_image @@ -1284,13 +1333,15 @@ var MilkboxGallery = new Class({ -//Creating Milkbox instance: you can comment this code out and instantiate Milkbox somewhere else instead. -window.addEvent('domready', function(){ - this.milkbox = new Milkbox({ - centered:false +// Creating Milkbox instance: set __MILKBOX_NO_AUTOINIT__ to instantiate Milkbox somewhere else instead. +if (typeof __MILKBOX_NO_AUTOINIT__ === 'undefined') +{ + window.addEvent('domready', function(){ + this.milkbox = new Milkbox({ + centered:false + }); }); -}); - +} /* jsLint settings: diff --git a/Demos/CKEditor.php b/Demos/CKEditor.php index 0d03db0..dc2976b 100644 --- a/Demos/CKEditor.php +++ b/Demos/CKEditor.php @@ -24,79 +24,76 @@ ?> - - - - - MooTools FileManager CKEditor example - - - - - - - - - - - - - - - - - + + + + + MooTools FileManager CKEditor example + + + + + + + + + + + + + + + diff --git a/Demos/FM-common.php b/Demos/FM-common.php deleted file mode 100644 index 86b918c..0000000 --- a/Demos/FM-common.php +++ /dev/null @@ -1,790 +0,0 @@ -'; - } - $type = gettype($value); - $rv .= $type; - - switch ($type) - { - case 'string': - $rv .= '(' . strlen($value) . ')'; - $value = var_dump_ex($value, -1, 0, $show_whitespace, $max_subitems); - break; - - case 'boolean': - $value = ($value ? 'true' : 'false'); - break; - - case 'object': - $props = get_object_vars($value); - if ($sort_before_dump > $level) - { - ksort($props); - } - $rv .= '(' . count($props) . ') ' . get_class($value) . ''; - foreach($props as $key => $val) - { - $rv .= "\n" . str_repeat("\t", $level + 1) . var_dump_ex($key, -1, 0, $show_whitespace, $max_subitems) . ' => '; - $rv .= var_dump_ex($value->{$key}, $level + 1, $sort_before_dump, $show_whitespace, $max_subitems); - } - $value = ''; - break; - - case 'array': - if ($sort_before_dump > $level) - { - $value = array_merge($value); // fastest way to clone the input array - ksort($value); - } - $rv .= '(' . count($value) . ')'; - $count = 0; - foreach($value as $key => $val) - { - $rv .= "\n" . str_repeat("\t", $level + 1) . var_dump_ex($key, -1, 0, $show_whitespace, $max_subitems) . ' => '; - $rv .= var_dump_ex($val, $level + 1, $sort_before_dump, $show_whitespace, $max_subitems); - $count++; - if ($count >= $max_subitems) - { - $rv .= "\n" . str_repeat("\t", $level + 1) . '(' . (count($value) - $count) . ' more entries ...)'; - break; - } - } - $value = ''; - break; - - default: - break; - } - $rv .= ' ' . $value . ''; - if ($level == 0) - { - $rv .= ''; - } - return $rv; -} - - - - -/** - * Generate a dump of the optional $extra values and/or the global variables $ccms[], $cfg[] and the superglobals. - * - * @param array $filename_options (optional) specifies a few pieces of the filename which will be generated to write - * the dump to: - * - * 'namebase': the leading part of the filename, - * 'origin-section': follows the timestamp encoded in the filename, - * 'extension': the desired filename extension (default: 'html' for HTML dumps, 'log' for plain dumps) - * - * @return the generated dump in the format and carrying the content as specified by the $dump_options. - */ -define('__DUMP2LOG_DEFAULT_OPTIONS', -1 ^ DUMP2LOG_WRITE_TO_STDOUT); -function dump_request_to_logfile($extra = null, $dump_options = __DUMP2LOG_DEFAULT_OPTIONS, $filename_options = null) -{ - global $_SERVER; - global $_ENV; - global $_COOKIE; - global $_SESSION; - static $sequence_number; - - if (!$sequence_number) - { - $sequence_number = 1; - } - else - { - $sequence_number++; - } - - $sorting = ($dump_options & DUMP2LOG_SORT); - $show_WS = ($dump_options & DUMP2LOG_FORMAT_AS_HTML); - - $rv = ''; - - if (!empty($_SESSION['dbg_last_dump']) && ($dump_options & DUMP2LOG_FORMAT_AS_HTML)) - { - $rv .= '

    Go to previous dump

    ' . "\n"; - } - - $now = microtime(true); - if (!empty($_SERVER['REQUEST_TIME'])) - { - $start = $_SERVER['REQUEST_TIME']; - $diff = $now - $start; - - $rv .= '

    Time elapses since request start: ' . number_format($diff, 3) . ' seconds

    ' . "\n"; - } - - if (!empty($extra)) - { - $rv .= '

    EXTRA

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($extra, 0, $sorting, $show_WS, 500);
    -		$rv .= "
    "; - } - - if ($dump_options & DUMP2LOG_ENV_GLOBALS) - { - $rv .= '

    $_ENV

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($_ENV, 0, $sorting, $show_WS);
    -		$rv .= "
    "; - } - if ($dump_options & DUMP2LOG_SESSION_GLOBALS) - { - $rv .= '

    $_SESSION

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($_SESSION, 0, $sorting, $show_WS);
    -		$rv .= "
    "; - } - if ($dump_options & DUMP2LOG_POST_GLOBALS) - { - $rv .= '

    $_POST

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($_POST, 0, $sorting, $show_WS);
    -		$rv .= "
    "; - } - if ($dump_options & DUMP2LOG_GET_GLOBALS) - { - $rv .= '

    $_GET

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($_GET, 0, $sorting, $show_WS);
    -		$rv .= "
    "; - } - if ($dump_options & DUMP2LOG_FILES_GLOBALS) - { - $rv .= '

    $_FILES

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($_FILES, 0, $sorting, $show_WS);
    -		$rv .= "
    "; - } - if ($dump_options & DUMP2LOG_COOKIE_GLOBALS) - { - $rv .= '

    $_COOKIE

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($_COOKIE, 0, $sorting, $show_WS);
    -		$rv .= "
    "; - } - if ($dump_options & DUMP2LOG_REQUEST_GLOBALS) - { - $rv .= '

    $_REQUEST

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($_REQUEST, 0, $sorting, $show_WS);
    -		$rv .= "
    "; - } - - if ($dump_options & DUMP2LOG_SERVER_GLOBALS) - { - $rv .= '

    $_SERVER

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($_SERVER, 0, $sorting, $show_WS);
    -		$rv .= "
    "; - } - - if ($dump_options & DUMP2LOG_STACKTRACE) - { - $st = debug_backtrace(false); - $rv .= '

    Stack Trace:

    '; - $rv .= "
    ";
    -		$rv .= var_dump_ex($st, 0, 0, $show_WS);
    -		$rv .= "
    "; - } - - $rv .= ''; - - $tstamp = date('Y-m-d.His') . '.' . sprintf('%07d', fmod($now, 1) * 1E6); - - $filename_options = array_merge(array( - 'namebase' => 'LOG-', - 'origin-section' => substr($_SERVER['REQUEST_URI'], 0, -42), - 'extension' => (($dump_options & DUMP2LOG_FORMAT_AS_HTML) ? 'html' : 'log') - ), (is_array($filename_options) ? $filename_options : array())); - - $fname = $filename_options['namebase'] . $tstamp . '.' . sprintf('%03u', $sequence_number) . '-' . $filename_options['origin-section']; - $fname = substr(preg_replace('/[^A-Za-z0-9_.-]+/', '_', $fname), 0, 46) . '.' . substr(preg_replace('/[^A-Za-z0-9_.-]+/', '_', $filename_options['extension']), 0, 9); // make suitable for filesystem - if (isset($_SESSION)) - { - $_SESSION['dbg_last_dump'] = $fname; - } - - if (!($dump_options & DUMP2LOG_FORMAT_AS_HTML)) - { - $rv = preg_replace('/^.*?(.+)<\/body>.*?$/sD', '\\1', $rv); - - $trans['

    '] = "\n\n*** "; - $trans['

    '] = " ***\n"; - $rv = strtr($rv, $trans); - - $rv = html_entity_decode(strip_tags($rv), ENT_NOQUOTES, 'UTF-8'); - } - - if ($dump_options & DUMP2LOG_WRITE_TO_FILE) - { - $fname = strtr(dirname(__FILE__), '\\', '/') . '/' . $fname; - - if (@file_put_contents($fname, $rv) === false) - { - throw new Exception('b0rk at ' . $fname); - } - } - - if ($dump_options & DUMP2LOG_FORMAT_AS_HTML) - { - $rv = preg_replace('/^.*?(.+)<\/body>.*?$/sD', '\\1', $rv); - } - - if ($dump_options & DUMP2LOG_WRITE_TO_STDOUT) - { - echo $rv; - } - - return array('filename' => $fname, 'content' => $rv); -} - - - - - - - - - - - - - - - -/** - * dumper useful in development - */ -define('__MTFM_VARDUMP_DEFAULT_OPTIONS', (0 - | DUMP2LOG_SERVER_GLOBALS - | DUMP2LOG_ENV_GLOBALS - | DUMP2LOG_SESSION_GLOBALS - | DUMP2LOG_POST_GLOBALS - | DUMP2LOG_GET_GLOBALS - | DUMP2LOG_REQUEST_GLOBALS - | DUMP2LOG_FILES_GLOBALS - | DUMP2LOG_COOKIE_GLOBALS - //| DUMP2LOG_STACKTRACE - | DUMP2LOG_SORT - //| DUMP2LOG_FORMAT_AS_HTML - | DUMP2LOG_WRITE_TO_FILE - //| DUMP2LOG_WRITE_TO_STDOUT - )); -function FM_vardumper($mgr = null, $action = null, $info = null, $extra = null, $dump_options = __MTFM_VARDUMP_DEFAULT_OPTIONS) -{ - if (DEVELOPMENT) - { - if ($mgr) - $settings = $mgr->getSettings(); - else - $settings = null; - - //$mimetdefs = $mgr->getMimeTypeDefinitions(); - - // log request data: - $data = array( - "FileManager::action" => $action, - "FileManager::info" => $info, - "FileManager::settings" => $settings - ); - if (!empty($extra)) - { - $data['extra'] = $extra; - } - - dump_request_to_logfile($data, $dump_options, array( - 'origin-section' => basename($_SERVER['REQUEST_URI']) . '-' . $action - )); - } -} - - - - - - - - - - - - - - - -/** - * Just a simple wrapper around the FileManager class constructor. Assumes a series of option defaults for the Demos, - * which you may override by providing your own in $options. - * - * Returns an instantiated FileManager instance, which you can use to process the incoming request. - */ -function mkNewFileManager($options = null) -{ - $Aliases = array(); - - if (SITE_USES_ALIASES) - { - // - // http://httpd.apache.org/docs/2.2/mod/mod_alias.html -- we emulate the Alias statement. Sort of. - // - // In principle each entry in this array should copy a Alias/VhostAlias/... web server configuration line. - // - // When filesystem paths are 'real time constructed', e.g. through complex regex manipulations, you will need - // to derive your own class from FileManagerWithAliasSupport or FileManager and implement/override - // the offending member functions in there, using the FileManagerWithAliasSupport implementation as a guide. - // - // NOTE that the above caveat applies to very complex rigs only, e.g. where a single URL points at different - // physical locations, depending on who's logged in, or where the request is originating from. - // - // As long as you can construct a static URI path to disk mapping, you are good to go using the Aliases[] - // array below! - // - $Aliases = array( - '/c/lib/includes/js/mootools-filemanager/Demos/Files/alias' => "D:/xxx", - '/c/lib/includes/js/mootools-filemanager/Demos/Files/d' => "D:/xxx.tobesorted", - '/c/lib/includes/js/mootools-filemanager/Demos/Files/u' => "D:/websites-uploadarea", - - '/c/lib/includes/js/mootools-filemanager/Demos/Files' => "D:/experiment" - ); - } - - $options = array_merge(array( - //'directory' => $fm_basedir . 'Files/', // absolute paths: as the relative ones, they sit in URI space, i.e. assume DocumentRoot is root '/' - - 'directory' => 'Files/', // relative paths: are relative to the URI request script path, i.e. dirname(__FILE__) or rather: $_SERVER['SCRIPT_NAME'] - 'thumbnailPath' => 'Files/Thumbnails/', - 'assetBasePath' => '../Assets', - 'chmod' => 0777, - //'maxUploadSize' => 1024 * 1024 * 5, - //'upload' => false, - //'destroy' => false, - //'create' => false, - //'move' => false, - //'download' => false, - //'filter' => 'image/', - 'allowExtChange' => true, // allow file name extensions to be changed; the default however is: NO (FALSE) - 'UploadIsAuthorized_cb' => 'FM_IsAuthorized', - 'DownloadIsAuthorized_cb' => 'FM_IsAuthorized', - 'CreateIsAuthorized_cb' => 'FM_IsAuthorized', - 'DestroyIsAuthorized_cb' => 'FM_IsAuthorized', - 'MoveIsAuthorized_cb' => 'FM_IsAuthorized', - 'ViewIsAuthorized_cb' => 'FM_IsAuthorized', - 'DetailIsAuthorized_cb' => 'FM_IsAuthorized', - 'ThumbnailIsAuthorized_cb' => 'FM_IsAuthorized', - - // FileManagerWithAliasSupport-specific options: - 'Aliases' => $Aliases, - 'RequestScriptURI' => strtr($_SERVER['SCRIPT_NAME'], '\\', '/') // or whatever URL you fancy. As long as the run-time ends up invoking the $browser class instantiated below on each request - ), (is_array($options) ? $options : array())); - - if (SITE_USES_ALIASES) - { - $browser = new FileManagerWithAliasSupport($options); - } - else - { - $browser = new FileManager($options); - } - return $browser; -} - - - - - - - - - - -/** -Start the session, whether the session ID is passed through the URL query section or as a cookie. -*/ -function session_start_ex($override = false) -{ - /* - * Load session if not already done by CCMS! - */ - if (empty($_SESSION)) - { - $sesid = session_id(); - $sesname = session_name(); - // the SWF.Upload / FancyUpload FLASH components do pass along the cookies, but as extra URL query entities: - if (!empty($_POST[$sesname])) - { - // legalize the sessionID; just a precaution for malicious intent - $alt_sesid = preg_replace('/[^A-Za-z0-9]/', 'X', $_POST[$sesname]); - - /* - * Before we set the sessionID, we'd better make darn sure it's a legitimate request instead of a hacker trying to get in: - * - * however, before we can access any $_SESSION[] variables do we have to load the session for the given ID. - */ - session_id($alt_sesid); - if (!session_start()) - { - header('HTTP/1.0 403 Forbidden', true, 403); - die('session_start_ex() failed'); - } - - /* - check the 'secret' value to doublecheck the legality of the session: did it originate from one of the demo entry pages? - */ - if (empty($_SESSION['FileManager']) || $_SESSION['FileManager'] !== 'DemoMagick') - { - //echo " :: illegal session override! IGNORED! \n"; - - // do not nuke the first session; this might have been a interloper trying a attack... let it all run its natural course. - if (0) - { - if (ini_get('session.use_cookies')) - { - $params = session_get_cookie_params(); - if (!empty($params['ccms_userID'])) - { - setcookie(session_name(), '', time() - 42000, - $params['path'], $params['domain'], - $params['secure'], $params['httponly'] - ); - } - } - - // Generate a new session_id - session_regenerate_id(); - - // Finally, destroy the session. - if(session_destroy()) - { - header('HTTP/1.0 403 Forbidden', true, 403); - die('session_start_ex() failed'); - } - } - session_write_close(); - session_regenerate_id(); - session_id($sesid); - } - } - else - { - if (!session_start()) - { - header('HTTP/1.0 403 Forbidden', true, 403); - die('session_start_ex(ALT) failed'); - } - } - } - else if ($override) - { - $sesid = session_id(); - $sesname = session_name(); - // the SWF.Upload / FancyUpload FLASH components do pass along the cookies, but as extra URL query entities: - if (!empty($_POST[$sesname])) - { - // legalize the sessionID; just a precaution for malicious intent - $alt_sesid = preg_replace('/[^A-Za-z0-9]/', 'X', $_POST[$sesname]); - - // did we already activate this session? - if ($sesid === $alt_sesid) - { - // yep. We're done! - return; - } - else - { - // close running session: - session_regenerate_id(); - //session_destroy(); - session_write_close(); - unset($_SESSION); - - /* - * Before we set the sessionID, we'd better make darn sure it's a legitimate request instead of a hacker trying to get in: - * - * however, before we can access any $_SESSION[] variables do we have to load the session for the given ID. - */ - session_id($alt_sesid); - if (!session_start()) - { - header('HTTP/1.0 403 Forbidden', true, 403); - die('session_start_ex() failed'); - } - - /* - check the 'secret' value to doublecheck the legality of the session: did it originate from one of the demo entry pages? - */ - if (empty($_SESSION['FileManager']) || $_SESSION['FileManager'] !== 'DemoMagick') - { - //echo " :: illegal session override! IGNORED! \n"; - - // do not nuke the first session; this might have been a interloper trying a attack... let it all run its natural course. - if (0) - { - if (ini_get('session.use_cookies')) - { - $params = session_get_cookie_params(); - if (!empty($params['ccms_userID'])) - { - setcookie(session_name(), '', time() - 42000, - $params['path'], $params['domain'], - $params['secure'], $params['httponly'] - ); - } - } - - // Generate a new session_id - session_regenerate_id(); - - // Finally, destroy the session. - if(session_destroy()) - { - header('HTTP/1.0 403 Forbidden', true, 403); - die('session_start_ex() failed'); - } - } - session_write_close(); - session_regenerate_id(); - session_id($sesid); - } - } - } - } -} - - - - - - - - - - - -/* - * FileManager event callback: Please add your own authentication / authorization here. - * - * Note that this function serves as a custom callback for all FileManager - * authentication/authorization requests, but you may of course provide - * different functions for each of the FM callbacks. - * - * Return TRUE when the session/client is authorized to execute the action, FALSE - * otherwise. - * - * NOTE: the customer code in here may edit the $fileinfo items and have those edits picked up by FM. - * E.g. changing the filename on write/move, fixing filename extensions based on file content sniffed mimetype, etc. - * - * See the Assets/Connector/FileManager.php file for extended info about this callback and the parameters passed into it. - * - * - * Notes: - * - * Just for the sake of the demo, did we include exceptions being thrown in here; in a real situation you wouldn't - * need those in the circumstances they are used right now. You /may/ use exceptions to signal other faults, though. - */ -function FM_IsAuthorized($mgr, $action, &$info) -{ - // Start session, if not already started - session_name('alt_session_name'); - session_start_ex(); - - //$settings = $mgr->getSettings(); - //$mimetdefs = $mgr->getMimeTypeDefinitions(); - - // log request data: - FM_vardumper($mgr, $action, $info); - - // when the session, started in the demo entry pages, doesn't exist or is not valid, we do not allow ANYTHING any more: - if (empty($_SESSION)) - { - session_write_close(); - throw new FileManagerException('authorized: The session is non-existent.'); - return false; - } - - if (empty($_SESSION['FileManager']) || $_SESSION['FileManager'] !== 'DemoMagick') - { - session_write_close(); - throw new FileManagerException('authorized: The session is illegal, as it does not contain the mandatory magic value set up by the demo entry pages.'); - return false; - } - - - /* - * authenticate / authorize: - * this sample is a bogus authorization, but you can perform simple to highly - * sophisticated authentications / authorizations here, e.g. even ones which also check permissions - * related to what is being uploaded right now (different permissions required for file mimetypes, - * e.g. images: any authorized user; while other file types which are more susceptible to carrying - * illicit payloads requiring at least 'power/trusted user' permissions, ...) - */ - - $rv = false; - switch ($action) - { - case 'upload': - /* - * Note that the TinyMCE demo currently has this sestting set to 'NO' to simulate an UNauthorized user, for the sake of the demo. - */ - $rv = ($_SESSION['UploadAuth'] == 'yes'); - break; - - case 'download': - $rv = true; - break; - - case 'create': // create directory - case 'destroy': - case 'move': // move or copy! - case 'view': - $rv = true; - break; - - case 'detail': - /* - * For the demo, we deny generation of thumbnails for images in a certain size range: 500KB - 2MB, jpeg only. - * - * To showcase the nasty/cool (depending on your disposition) things you can do in this callback, we - * force the thumbnail to become a thumbnail of the 'nuke': - */ - $fsize = @filesize($info['file']); - /* - * When the thumbnail request is made, the demo will error on - * bison-head-with-horns (Ray Rauch, U.S. Fish and Wildlife Service).jpg - * fruits-vegetables-milk-and-yogurt (Peggy Greb, U.S. Department of Agriculture).jpg - * intentionally with the next bit of code; just to give you an idea what can be done in here. - * - * you can do a similar thing for any other request and have a good file fail or a bad file recover and succeed, - * simply by patching the $info[] items. - */ - if (SHOW_CUSTOM_CALLBACK_WORK && $info['mime'] == 'image/jpeg' && $fsize >= 180 * 1024 && $fsize <= 200 * 1024) - { - // force the manager to fetch the 'nuke' icon: - $info['filename'] = 'is.default-error'; - - // and nuke the mimetype to make sure it does go for the icon, always: - $info['mime'] = 'icon/icon'; - - // and act as if we authorized the action. Meanwhile, we just nuked it. - } - $rv = true; - break; - - default: - // unknown operation. Internal server error. - $rv = false; - break; - } - - // make sure the session is closed (and unlocked) before the bulk of the work is performed: better parallelism server-side. - session_write_close(); - - return $rv; -} - - - - - - - -// Do *NOT* add a close tag here! Any whitespace after that makes PHP output both a Content-Type: test/html header AND the whitespace as content. -// This BREAKS any operation (such as mootools-filemanager::event=thumbnail) which outputs BINARY DATA (in that particular case, PHP spits out an image) -// The safest way to prevent ANY PHP file from producing undesirable [whitespace] output is to never add that ?-> close tag. diff --git a/Demos/demos.css b/Demos/demos.css deleted file mode 100644 index ed31687..0000000 --- a/Demos/demos.css +++ /dev/null @@ -1,172 +0,0 @@ - -body -{ - font-size: 11px; - font-family: Tahoma, sans-serif; -} - -h1, h2 -{ - margin: 0 0 10px 0; - padding: 0; - - color: #666; - font-weight: normal; - font-size: 24px; - letter-spacing: 1px; - word-spacing: 2px; - line-height: 22px; - min-height: 25px; -} - -h2 -{ - margin-top: 3em; -} - -h1 span -{ - font-size: 11px; - letter-spacing: 0; - word-spacing: 0; - text-shadow: none; -} - -.blue -{ - color: #1f52b0; -} - -div.content -{ - min-width: 650px; - min-height: 200px; - margin: 23px 34px; - padding: 10px 17px; - border: 1px solid #b2b2b2; - background: #fff; - box-shadow: rgba(0, 0, 0, 0.3) 0 0 10px; - -moz-box-shadow: rgba(0, 0, 0, 0.3) 0 0 10px; - -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 0 10px; -} - -div.content div.example -{ - float: left; - clear: both; - margin: 10px 0; -} - -div.selected-file div -{ - padding: 3px; - - line-height: 1.5em; -} - -div.selected-file div img.preview -{ - padding: 3px; -} - -button -{ - margin: 5px 0; -} - - -/* http://labnol.blogspot.com/2006/10/html-css-trick-for-displaying-code.html */ -pre -{ - white-space: pre-wrap; /* css-3 */ - white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ - word-wrap: break-word; /* Internet Explorer 5.5+ */ -} - -div.go_home -{ - float: right; -} - - -#gallery-tn-container -{ - margin: 15px; - padding: 5px; - border: 1px solid #b2a0a0; - background: #fffafa; - box-shadow: rgba(255, 0, 0, 0.3) 0 0 10px; - -moz-box-shadow: rgba(255, 0, 0, 0.3) 0 0 10px; - -webkit-box-shadow: rgba(255, 0, 0, 0.3) 0 0 10px; -} - -#gallery-tn-container div -{ - float: left; - - /* border: green solid 1px; */ - margin: 5px; - -} - -#gallery-tn-container div a -{ - display: block; - /* - width: 250px; - height: 250px; - */ - border: 1px solid #b2a0a0; - background: #eee2e2; - padding: 0; - margin: 0; - - text-align: center; - margin-left: auto; - margin-right: auto; -} - -#gallery-tn-container div a img -{ - padding: 0; - margin: 0; -} - -/* - * http://www.smashingmagazine.com/2007/05/01/css-float-theory-things-you-should-know/ - * --> - * http://www.positioniseverything.net/easyclearing.html - * --> - * http://blogs.sitepoint.com/simple-clearing-of-floats/ - */ -#gallery-tn-container -{ - overflow: auto; -} - - - -/* -http://jsfiddle.net/api/post/mootools/1.3/dependencies/more,art/ -*/ - -.slider -{ - background: #CCC; - height: 16px; - width: 300px; -} -.slider .knob -{ - background: #000; - width: 16px; - height: 16px; - - cursor: pointer; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border-radius: 6px; -} - diff --git a/Demos/home_16x16.png b/Demos/home_16x16.png deleted file mode 100644 index 604b50c..0000000 Binary files a/Demos/home_16x16.png and /dev/null differ diff --git a/Demos/index.html b/Demos/index.html new file mode 100644 index 0000000..97c1a61 --- /dev/null +++ b/Demos/index.html @@ -0,0 +1,252 @@ + + + + MooTools FileManager Testground + + + + + + + + + + + + + + + + + +
    +

    FileManager Demo

    + + + +
    + +
    + + +
    + + +
    +
    + +

    In a container (can be a window, a div..)

    + +
    +
    +
    + +
    +
    + + \ No newline at end of file diff --git a/Demos/index.php b/Demos/index.php index 1c3dcd6..f9bf880 100644 --- a/Demos/index.php +++ b/Demos/index.php @@ -19,6 +19,19 @@ /* the remainder of the code does not need access to the session data. */ session_write_close(); +if (0) +{ + // and add a couple other, slightly malicious cookies to check whether Flash will crash on it, or not. + setcookie("ASP.NET_SessionId", 'ASP.NET: b0rk b0rk b0rk & ... b0rk!', time() + 600, + $params['path'], $params['domain'], + $params['secure'], $params['httponly'] + ); + setcookie('.1!#$%20X', 'b0rk b0rk b0rk & ... b0rk!', time() + 600, + $params['path'], $params['domain'], + $params['secure'], $params['httponly'] + ); +} + ?> @@ -26,10 +39,69 @@ MooTools FileManager Testground - + + + + + @@ -53,11 +125,8 @@ hideOnClick: true, assetBasePath: '../Assets', // uploadAuthData is deprecated; use propagateData instead. The session cookie(s) are passed through Flash automatically, these days... - // - // and a couple of extra user defined parameters sent with EVERY request: - propagateData: { - origin: 'demo-FM-1', - extra_data: 'ExtraData' + uploadAuthData: { + session: 'MySessionData' }, upload: true, download: true, @@ -67,62 +136,217 @@ createFolders: true, // selectable: true, hideQonDelete: false, // DO ask 'are you sure' when the user hits the 'delete' button - verbose: true, // log a lot of activity to console (when it exists) onComplete: function(path, file, mgr) { - if (typeof console !== 'undefined' && console.log) console.log('MFM.onComplete: ', path, file, mgr); + if (typeof console !== 'undefined' && console.log) console.log('MFM.onComplete: ' + path + ', ' + debug.dump(file) + ', ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); }, onModify: function(file, json, mode, mgr) { - if (typeof console !== 'undefined' && console.log) console.log('MFM.onModify: ', mode, file, json, mgr); + if (typeof console !== 'undefined' && console.log) console.log('MFM.onModify: ' + mode + ', ' + debug.dump(file) + ', ' + debug.dump(json) + ', ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); }, onShow: function(mgr) { - if (typeof console !== 'undefined' && console.log) console.log('MFM.onShow: ', mgr); + if (typeof console !== 'undefined' && console.log) console.log('MFM.onShow: ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); }, onHide: function(mgr) { - if (typeof console !== 'undefined' && console.log) console.log('MFM.onHide: ', mgr); + if (typeof console !== 'undefined' && console.log) console.log('MFM.onHide: ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); }, onScroll: function(e, mgr) { - if (typeof console !== 'undefined' && console.log) console.log('MFM.onScroll: ', e, mgr); + if (typeof console !== 'undefined' && console.log) console.log('MFM.onScroll: ' + debug.dump(e) + ', ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); }, onPreview: function(src, mgr, el) { - if (typeof console !== 'undefined' && console.log) console.log('MFM.onPreview: ', src, el, mgr); + if (typeof console !== 'undefined' && console.log) console.log('MFM.onPreview: ' + debug.dump(src) + ', ' + debug.dump(el) + ', ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); }, onDetails: function(json, mgr) { - if (typeof console !== 'undefined' && console.log) console.log('MFM.onDetails: ', json, mgr); + if (typeof console !== 'undefined' && console.log) console.log('MFM.onDetails: ' + debug.dump(json) + ', ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); }, onHidePreview: function(mgr) { - if (typeof console !== 'undefined' && console.log) console.log('MFM.onHidePreview: ', mgr); + if (typeof console !== 'undefined' && console.log) console.log('MFM.onHidePreview: ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); + }, + // and a couple of extra user defined parameters sent with EVERY request: + propagateData: { + origin: 'demo-FM-1' } }); $('example1').addEvent('click', manager1.show.bind(manager1)); + /* Select a file */ + var el = $('example2'); + var div, manager2; + var complete = function(path, file, mgr) { + el.set('value', path); + if (div) { + div.destroy(); + } + var icon = new Asset.image( + mgr.assetBasePath+'Images/cancel.png', + { + 'class': 'file-cancel', + title: 'deselect' + }).addEvent('click', function(e){ + e.stop(); + el.set('value', ''); + var self = this; + div.fade(0).get('tween').chain(function(){ + div.destroy(); + mgr.tips.hide(); + mgr.tips.detach(self); + }); + }); + mgr.tips.attach(icon); + + var img = null; + var mimetype = file.mime; + if (mimetype && mimetype.contains('image/')) + { + img = new Element('div', { + 'text': 'Click on the thumbnail to view the image/file in a lightbox (milkbox)' + }).adopt( + new Element('br'), + new Element('a', { + 'data-milkbox': 'single', + 'title': file.name, + 'href': path // no need to URLencode the path as FM does it already: encodeURI(path) // see also: http://www.javascripter.net/faq/escape.htm + }).adopt(new Element('img', { + 'src': (file.thumb250 ? file.thumb250 : file.icon), + 'class': 'preview', + 'alt': 'preview (picked)' + })) + ); + } + + div = new Element('div', {'class': 'selected-file', text: 'Selected file: '}).adopt( + new Asset.image(file.icon, {'class': 'mime-icon'}), + new Element('span', {text: file.name}), + icon, + img + ).inject(el, 'after'); + + if (img && typeof milkbox != 'undefined') + { + milkbox.reloadPageGalleries(); + } + }; + + manager2 = new FileManager({ + url: 'selectImage.php', + language: 'en', + filter: 'image', + hideOnClick: true, + assetBasePath: '../Assets', + // uploadAuthData is deprecated; use propagateData instead. The session cookie(s) are passed through Flash automatically, these days... + uploadAuthData: { + session: 'MySessionData' + }, + selectable: true, + upload: true, + destroy: true, + rename: true, + move_or_copy: true, + createFolders: true, + onComplete: complete, + // and a couple of extra user defined parameters sent with EVERY request: + propagateData: { + origin: 'demo-selectFile' + } + }); + + el.setStyle('display', 'none'); + var val = el.get('value'); + if (val) { + var file_ext = val.split('.').getLast(); + complete.apply(manager2, [val, { + name: val.split('/').getLast(), + mime: 'image/' + file_ext, + icon: '../Assets/Images/Icons/'+file_ext+'.png' + }, manager2]); + } + + new Element('button', {'class': 'browser', text: 'Select an image'}).addEvent('click', manager2.show.bind(manager2)).inject(el, 'before'); + + /* Localized Example */ + var manager3 = new FileManager({ + url: 'manager.php', + language: 'de', + hideOnClick: true, + assetBasePath: '../Assets', + // uploadAuthData is deprecated; use propagateData instead. The session cookie(s) are passed through Flash automatically, these days... + uploadAuthData: { + session: 'MySessionData' + }, + upload: true, + destroy: true, + rename: true, + move_or_copy: true, + createFolders: true, + // and a couple of extra user defined parameters sent with EVERY request: + propagateData: { + origin: 'demo-clickedLink' + } + }); + $('example3').addEvent('click', manager3.show.bind(manager3)); + + + /* Gallery Example */ + var global = this; + var example4 = $('myGallery'); + var manager4 = new FileManager.Gallery({ + url: 'selectImage.php?exhibit=A', // 'manager.php', but with a bogus query parameter included: latest FM can cope with such an URI + assetBasePath: '../Assets', + filter: 'image', + hideOnClick: true, + // uploadAuthData is deprecated; use propagateData instead. The session cookie(s) are passed through Flash automatically, these days... + uploadAuthData: { + session: 'MySessionData' + }, + propagateData: { + origin: 'demo-Gallery' + }, + onShow: function(mgr) { + if (typeof console !== 'undefined' && console.log) console.log('GALLERY.onShow: ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); + var obj; + Function.attempt(function(){ + var gallist = example4.get('value'); + if (typeof console !== 'undefined' && console.log) console.log('GALLERY list: ' + debug.dump(gallist, 0, 1, 60, 'function')); + obj = JSON.decode(gallist); + }); + this.populate(obj); + }, + onComplete: function(serialized, files, mgr){ + if (typeof console !== 'undefined' && console.log) console.log('GALLERY.onComplete: ' + debug.dump(serialized) + ', ' + debug.dump(files) + ', ' + debug.dump(mgr, 0, 1, 60, 'object,function,string:empty')); + + example4.set('value', JSON.encode(serialized)); + } + }); + $('example4').addEvent('click', manager4.show.bind(manager4)); });
    -

    Basic FileManager Demo

    +

    FileManager Demo

    - +
    diff --git a/Demos/manager.php b/Demos/manager.php index c2b1f39..aa8309a 100644 --- a/Demos/manager.php +++ b/Demos/manager.php @@ -1,72 +1,51 @@ $fm_basedir . 'Files/', // absolute paths: as the relative ones, they sit in URI space, i.e. assume DocumentRoot is root '/' - - 'directory' => 'Files/', // relative paths: are relative to the URI request script path, i.e. dirname(__FILE__) or rather: $_SERVER['SCRIPT_NAME'] - //'filter' => 'image/', +baseUrl : http://filemanager.local/Demos/ + +documentRootPath realpath($_SERVER['DOCUMENT_ROOT']) +directory: Demos/Files/ +thumbnailPath Demos/Files/Thumbnails/ +assetPath ../Assets + +*/ + + +$browser = new FileManager(array +( + 'baseUrl' => $baseUrl, + + // Must be set if the 'directory' value is in a subfolder of the DOCUMENT_ROOT + 'documentRootPath' => realpath($_SERVER['DOCUMENT_ROOT']) . '/Demos', + + // Relative to documentRootPath but also to baseUrl + 'directory' => 'Files/', + 'thumbnailPath' => 'Files/Thumbnails/', + 'assetPath' => '../Assets', + + 'chmod' => 0777, + //'maxUploadSize' => 1024 * 1024 * 5, 'upload' => true, - 'destroy' => true, - 'create' => true, - 'move' => true, - 'download' => true, - 'allowExtChange' => true // allow file name extensions to be changed; the default however is: NO (FALSE) + + 'allowExtChange' => true, // ? + 'UploadIsAuthorized_cb' => 'FM_IsAuthorized', + 'DownloadIsAuthorized_cb' => 'FM_IsAuthorized', + 'CreateIsAuthorized_cb' => 'FM_IsAuthorized', + 'DestroyIsAuthorized_cb' => 'FM_IsAuthorized', + 'MoveIsAuthorized_cb' => 'FM_IsAuthorized' )); - -$event_cmd = (!empty($_GET['event']) ? $_GET['event'] : null); - -// log request data: -FM_vardumper($browser, 'init' . $event_cmd); - - - -// and process the request: -$browser->fireEvent($event_cmd); - - - - - - -// Do *NOT* add a close tag here! Any whitespace after that makes PHP output both a Content-Type: test/html header AND the whitespace as content. -// This BREAKS any operation (such as mootools-filemanager::event=thumbnail) which outputs BINARY DATA (in that particular case, PHP spits out an image) -// The safest way to prevent ANY PHP file from producing undesirable [whitespace] output is to never add that ?-> close tag. +$browser->fireEvent(!empty($_GET['event']) ? $_GET['event'] : null); diff --git a/Demos/mootools-core.js b/Demos/mootools-core-1.3.2-full-nocompat.js similarity index 93% rename from Demos/mootools-core.js rename to Demos/mootools-core-1.3.2-full-nocompat.js index a847852..96af6c6 100644 --- a/Demos/mootools-core.js +++ b/Demos/mootools-core-1.3.2-full-nocompat.js @@ -1,5 +1,15 @@ /* --- +MooTools: the javascript framework + +web build: + - http://mootools.net/core/7c56cfef9dddcf170a5d68e3fb61cfd7 + +packager build: + - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady Core/Swiff + +/* +--- name: Core @@ -23,8 +33,8 @@ provides: [Core, MooTools, Type, typeOf, instanceOf, Native] (function(){ this.MooTools = { - version: '1.3.3dev', - build: '%build%' + version: '1.3.2', + build: 'c9f1ff10e9e7facb65e9481049ed1b450959d587' }; // typeOf, instanceOf @@ -157,9 +167,7 @@ var Type = this.Type = function(name, object){ object.prototype.$family = (function(){ return lower; }).hide(); - //<1.2compat> - object.type = typeCheck; - // + } } @@ -195,7 +203,7 @@ var implement = function(name, method){ if (typeOf(hook) == 'type') implement.call(hook, name, method); else hook.call(this, name, method); } - + var previous = this.prototype[name]; if (previous == null || !previous.$protected) this.prototype[name] = method; @@ -388,125 +396,7 @@ String.extend('uniqueID', function(){ return (UID++).toString(36); }); -//<1.2compat> - -var Hash = this.Hash = new Type('Hash', function(object){ - if (typeOf(object) == 'hash') object = Object.clone(object.getClean()); - for (var key in object) this[key] = object[key]; - return this; -}); - -Hash.implement({ - - forEach: function(fn, bind){ - Object.forEach(this, fn, bind); - }, - - getClean: function(){ - var clean = {}; - for (var key in this){ - if (this.hasOwnProperty(key)) clean[key] = this[key]; - } - return clean; - }, - - getLength: function(){ - var length = 0; - for (var key in this){ - if (this.hasOwnProperty(key)) length++; - } - return length; - } - -}); - -Hash.alias('each', 'forEach'); - -Object.type = Type.isObject; - -var Native = this.Native = function(properties){ - return new Type(properties.name, properties.initialize); -}; - -Native.type = Type.type; - -Native.implement = function(objects, methods){ - for (var i = 0; i < objects.length; i++) objects[i].implement(methods); - return Native; -}; - -var arrayType = Array.type; -Array.type = function(item){ - return instanceOf(item, Array) || arrayType(item); -}; - -this.$A = function(item){ - return Array.from(item).slice(); -}; - -this.$arguments = function(i){ - return function(){ - return arguments[i]; - }; -}; - -this.$chk = function(obj){ - return !!(obj || obj === 0); -}; - -this.$clear = function(timer){ - clearTimeout(timer); - clearInterval(timer); - return null; -}; - -this.$defined = function(obj){ - return (obj != null); -}; - -this.$each = function(iterable, fn, bind){ - var type = typeOf(iterable); - ((type == 'arguments' || type == 'collection' || type == 'array' || type == 'elements') ? Array : Object).each(iterable, fn, bind); -}; - -this.$empty = function(){}; - -this.$extend = function(original, extended){ - return Object.append(original, extended); -}; - -this.$H = function(object){ - return new Hash(object); -}; - -this.$merge = function(){ - var args = Array.slice(arguments); - args.unshift({}); - return Object.merge.apply(null, args); -}; - -this.$lambda = Function.from; -this.$mixin = Object.merge; -this.$random = Number.random; -this.$splat = Array.from; -this.$time = Date.now; - -this.$type = function(object){ - var type = typeOf(object); - if (type == 'elements') return 'array'; - return (type == 'null') ? false : type; -}; - -this.$unlink = function(object){ - switch (typeOf(object)){ - case 'object': return Object.clone(object); - case 'array': return Array.clone(object); - case 'hash': return new Hash(object); - default: return object; - } -}; -// })(); @@ -680,15 +570,7 @@ Array.implement({ }); -//<1.2compat> -Array.alias('extend', 'append'); - -var $pick = function(){ - return Array.from(arguments).pick(); -}; - -// /* @@ -864,7 +746,7 @@ Function.implement({ try { return this.apply(bind, Array.from(args)); } catch (e){} - + return null; }, @@ -872,7 +754,7 @@ Function.implement({ bind: function(bind){ var self = this, args = (arguments.length > 1) ? Array.slice(arguments, 1) : null; - + return function(){ if (!args && !arguments.length) return self.call(bind); if (args && arguments.length) return self.apply(bind, args.concat(Array.from(arguments))); @@ -899,54 +781,7 @@ Function.implement({ }); -//<1.2compat> - -delete Function.prototype.bind; - -Function.implement({ - - create: function(options){ - var self = this; - options = options || {}; - return function(event){ - var args = options.arguments; - args = (args != null) ? Array.from(args) : Array.slice(arguments, (options.event) ? 1 : 0); - if (options.event) args = [event || window.event].extend(args); - var returns = function(){ - return self.apply(options.bind || null, args); - }; - if (options.delay) return setTimeout(returns, options.delay); - if (options.periodical) return setInterval(returns, options.periodical); - if (options.attempt) return Function.attempt(returns); - return returns(); - }; - }, - - bind: function(bind, args){ - var self = this; - if (args != null) args = Array.from(args); - return function(){ - return self.apply(bind, args || arguments); - }; - }, - - bindWithEvent: function(bind, args){ - var self = this; - if (args != null) args = Array.from(args); - return function(event){ - return self.apply(bind, (args == null) ? arguments : [event].concat(args)); - }; - }, - - run: function(args, bind){ - return this.apply(bind, Array.from(args)); - } - -}); - -var $try = Function.attempt; -// /* @@ -1069,95 +904,7 @@ Object.extend({ })(); -//<1.2compat> - -Hash.implement({ - - has: Object.prototype.hasOwnProperty, - - keyOf: function(value){ - return Object.keyOf(this, value); - }, - - hasValue: function(value){ - return Object.contains(this, value); - }, - - extend: function(properties){ - Hash.each(properties || {}, function(value, key){ - Hash.set(this, key, value); - }, this); - return this; - }, - - combine: function(properties){ - Hash.each(properties || {}, function(value, key){ - Hash.include(this, key, value); - }, this); - return this; - }, - - erase: function(key){ - if (this.hasOwnProperty(key)) delete this[key]; - return this; - }, - - get: function(key){ - return (this.hasOwnProperty(key)) ? this[key] : null; - }, - - set: function(key, value){ - if (!this[key] || this.hasOwnProperty(key)) this[key] = value; - return this; - }, - - empty: function(){ - Hash.each(this, function(value, key){ - delete this[key]; - }, this); - return this; - }, - - include: function(key, value){ - if (this[key] == null) this[key] = value; - return this; - }, - - map: function(fn, bind){ - return new Hash(Object.map(this, fn, bind)); - }, - - filter: function(fn, bind){ - return new Hash(Object.filter(this, fn, bind)); - }, - - every: function(fn, bind){ - return Object.every(this, fn, bind); - }, - - some: function(fn, bind){ - return Object.some(this, fn, bind); - }, - - getKeys: function(){ - return Object.keys(this); - }, - - getValues: function(){ - return Object.values(this); - }, - - toQueryString: function(base){ - return Object.toQueryString(this, base); - } -}); - -Hash.extend = Object.append; - -Hash.alias({indexOf: 'keyOf', contains: 'hasValue'}); - -// /* @@ -1361,67 +1108,7 @@ try { } /**/ -//<1.2compat> - -if (Browser.Platform.ios) Browser.Platform.ipod = true; - -Browser.Engine = {}; - -var setEngine = function(name, version){ - Browser.Engine.name = name; - Browser.Engine[name + version] = true; - Browser.Engine.version = version; -}; -if (Browser.ie){ - Browser.Engine.trident = true; - - switch (Browser.version){ - case 6: setEngine('trident', 4); break; - case 7: setEngine('trident', 5); break; - case 8: setEngine('trident', 6); - } -} - -if (Browser.firefox){ - Browser.Engine.gecko = true; - - if (Browser.version >= 3) setEngine('gecko', 19); - else setEngine('gecko', 18); -} - -if (Browser.safari || Browser.chrome){ - Browser.Engine.webkit = true; - - switch (Browser.version){ - case 2: setEngine('webkit', 419); break; - case 3: setEngine('webkit', 420); break; - case 4: setEngine('webkit', 525); - } -} - -if (Browser.opera){ - Browser.Engine.presto = true; - - if (Browser.version >= 9.6) setEngine('presto', 960); - else if (Browser.version >= 9.5) setEngine('presto', 950); - else setEngine('presto', 925); -} - -if (Browser.name == 'unknown'){ - switch ((ua.match(/(?:webkit|khtml|gecko)/) || [])[0]){ - case 'webkit': - case 'khtml': - Browser.Engine.webkit = true; - break; - case 'gecko': - Browser.Engine.gecko = true; - } -} - -this.$exec = Browser.exec; - -// })(); @@ -1536,11 +1223,7 @@ Event.Keys = { 'delete': 46 }; -//<1.2compat> -Event.Keys = new Hash(Event.Keys); - -// Event.implement({ @@ -1731,9 +1414,7 @@ this.Events = new Class({ addEvent: function(type, fn, internal){ type = removeOn(type); - /*<1.2compat>*/ - if (fn == $empty) return this; - /**/ + this.$events[type] = (this.$events[type] || []).include(fn); if (internal) fn.internal = true; @@ -1811,7 +1492,7 @@ provides: Slick.Parser ... */ -(function(){ +;(function(){ var parsed, separatorIndex, @@ -2044,7 +1725,7 @@ requires: Slick.Parser ... */ -(function(){ +;(function(){ var local = {}, featuresCache = {}, @@ -2111,7 +1792,7 @@ local.setDocument = function(document){ var selected, id = 'slick_uniqueid'; var testNode = document.createElement('div'); - + var testRoot = document.body || document.getElementsByTagName('body')[0] || root; testRoot.appendChild(testNode); @@ -2162,7 +1843,7 @@ local.setDocument = function(document){ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN; } - + if (testNode.querySelectorAll){ // IE 8 returns closed nodes (EG:"") for querySelectorAll('*') for some documents try { @@ -2288,7 +1969,7 @@ var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/, local.search = function(context, expression, append, first){ var found = this.found = (first) ? null : (append || []); - + if (!context) return found; else if (context.navigator) context = context.document; // Convert the node from a window to a document else if (!context.nodeType) return found; @@ -2391,11 +2072,7 @@ local.search = function(context, expression, append, first){ } try { - if (context == null) { - qsaFailExpCache[expression] = 1; - break querySelector; - } - else if (first) return context.querySelector(_expression) || null; + if (first) return context.querySelector(_expression) || null; else nodes = context.querySelectorAll(_expression); } catch(e) { qsaFailExpCache[expression] = 1; @@ -2598,7 +2275,7 @@ local.matchNode = function(node, selector){ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]')); } catch(matchError) {} } - + var parsed = this.Slick.parse(selector); if (!parsed) return true; @@ -2677,7 +2354,7 @@ var combinators = { this.push(item, tag, null, classes, attributes, pseudos); break; } - } + } return; } if (!item){ @@ -2886,7 +2563,7 @@ var pseudos = { 'root': function(node){ return (node === this.root); }, - + 'selected': function(node){ return node.selected; } @@ -2915,7 +2592,7 @@ local.attributeGetters = { 'style': function(){ return (this.style) ? this.style.cssText : this.getAttribute('style'); }, - + 'tabindex': function(){ var attributeNode = this.getAttributeNode('tabindex'); return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; @@ -3084,11 +2761,7 @@ if (!Browser.Element){ Element.Constructors = {}; -//<1.2compat> -Element.Constructors = new Hash; - -// var IFrame = new Type('IFrame', function(){ var params = Array.link(arguments, { @@ -3179,11 +2852,7 @@ new Type('Elements', Elements).implement({ }); -//<1.2compat> - -Elements.alias('extend', 'append'); -// (function(){ @@ -3315,52 +2984,7 @@ Window.implement({ }); -//<1.2compat> -(function(search, find, match){ - - this.Selectors = {}; - var pseudos = this.Selectors.Pseudo = new Hash(); - - var addSlickPseudos = function(){ - for (var name in pseudos) if (pseudos.hasOwnProperty(name)){ - Slick.definePseudo(name, pseudos[name]); - delete pseudos[name]; - } - }; - - Slick.search = function(context, expression, append){ - addSlickPseudos(); - return search.call(this, context, expression, append); - }; - - Slick.find = function(context, expression){ - addSlickPseudos(); - return find.call(this, context, expression); - }; - - Slick.match = function(node, selector){ - addSlickPseudos(); - return match.call(this, node, selector); - }; - -})(Slick.search, Slick.find, Slick.match); - -if (window.$$ == null) Window.implement('$$', function(selector){ - var elements = new Elements; - if (arguments.length == 1 && typeof selector == 'string') return Slick.search(this.document, selector, elements); - var args = Array.flatten(arguments); - for (var i = 0, l = args.length; i < l; i++){ - var item = args[i]; - switch (typeOf(item)){ - case 'element': elements.push(item); break; - case 'string': Slick.search(this.document, item, elements); - } - } - return elements; -}); - -// if (window.$$ == null) Window.implement('$$', function(selector){ if (arguments.length == 1){ @@ -3394,7 +3018,7 @@ var camels = ['defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpa 'rowSpan', 'tabIndex', 'useMap' ]; var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readOnly', 'multiple', 'selected', - 'noresize', 'defer', 'defaultChecked', 'autofocus' + 'noresize', 'defer', 'defaultChecked' ]; var attributes = { 'html': 'innerHTML', @@ -3439,29 +3063,7 @@ var inserters = { inserters.inside = inserters.bottom; -//<1.2compat> - -Object.each(inserters, function(inserter, where){ - - where = where.capitalize(); - var methods = {}; - - methods['inject' + where] = function(el){ - inserter(this, document.id(el, true)); - return this; - }; - - methods['grab' + where] = function(el){ - inserter(document.id(el, true), this); - return this; - }; - - Element.implement(methods); - -}); - -// var injectCombinator = function(expression, combinator){ if (!expression) return combinator; @@ -3732,13 +3334,7 @@ var contains = {contains: function(element){ if (!document.contains) Document.implement(contains); if (!document.createElement('div').contains) Element.implement(contains); -//<1.2compat> -Element.implement('hasChild', function(element){ - return this !== element && this.contains(element); -}); - -// [Element, Window, Document].invoke('implement', { @@ -3794,11 +3390,7 @@ if (window.attachEvent && !window.addEventListener) window.addListener('unload', Element.Properties = {}; -//<1.2compat> - -Element.Properties = new Hash; -// Element.Properties.style = { @@ -3970,19 +3562,17 @@ Element.implement({ } else if (value == String(Number(value))){ value = Math.round(value); } - if ((property === 'width' || property === 'height') && value.toString().indexOf('-') >= 0) - { - // [i_a] don't set a negative width or height: MSIE6 croaks! - this.style[property] = '0px'; - return this; + + this.style[property] = value; +// Partikule +/* try { + this.style[property] = value; } - else if (property === 'zIndex' && value === 'auto') + catch(e) { - // [i_a] MSIE6 croaks on this one; set to nil instead - this.style[property] = ''; - return this; + alert( this.id + ' : ' + property + ':' + value); } - this.style[property] = value; +*/ return this; }, @@ -4046,11 +3636,7 @@ Element.Styles = { zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@' }; -//<1.2compat> - -Element.Styles = new Hash(Element.Styles); -// Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; @@ -4241,11 +3827,7 @@ Element.Events = { }; -//<1.2compat> -Element.Events = new Hash(Element.Events); - -// })(); @@ -4396,7 +3978,7 @@ Element.implement({ x: offset.x - scroll.x, y: offset.y - scroll.y }; - + if (relative && (relative = document.id(relative))){ var relativePosition = relative.getPosition(); return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)}; @@ -4860,11 +4442,7 @@ Fx.CSS.Parsers = { }; -//<1.2compat> - -Fx.CSS.Parsers = new Hash(Fx.CSS.Parsers); -// /* @@ -5109,11 +4687,7 @@ Fx.Transitions = { }; -//<1.2compat> -Fx.Transitions = new Hash(Fx.Transitions); - -// Fx.Transitions.extend = function(transitions){ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); @@ -5548,14 +5122,7 @@ provides: JSON if (typeof JSON == 'undefined') this.JSON = {}; -//<1.2compat> - -JSON = new Hash({ - stringify: JSON.stringify, - parse: JSON.parse -}); -// (function(){ diff --git a/Demos/mootools-more-1.3.2.1-yc.js b/Demos/mootools-more-1.3.2.1-yc.js new file mode 100644 index 0000000..8562b17 --- /dev/null +++ b/Demos/mootools-more-1.3.2.1-yc.js @@ -0,0 +1,743 @@ +// MooTools: the javascript framework. +// Load this file's selection again by visiting: http://mootools.net/more/2b6c361008664ec367a7bb2debd9aa1a +// Or build this file again with packager using: packager build More/More More/Events.Pseudos More/Class.Refactor More/Class.Binds More/Class.Occlude More/Chain.Wait More/Array.Extras More/Date More/Date.Extras More/Number.Format More/Object.Extras More/String.Extras More/String.QueryString More/URI More/URI.Relative More/Hash More/Hash.Extras More/Element.Forms More/Elements.From More/Element.Event.Pseudos More/Element.Event.Pseudos.Keys More/Element.Delegation More/Element.Measure More/Element.Pin More/Element.Position More/Element.Shortcuts More/Form.Request More/Form.Request.Append More/Form.Validator More/Form.Validator.Inline More/Form.Validator.Extras More/OverText More/Fx.Elements More/Fx.Accordion More/Fx.Move More/Fx.Reveal More/Fx.Scroll More/Fx.Slide More/Fx.SmoothScroll More/Fx.Sort More/Drag More/Drag.Move More/Slider More/Sortables More/Request.JSONP More/Request.Queue More/Request.Periodical More/Assets More/Color More/Group More/Hash.Cookie More/IframeShim More/Table More/HtmlTable More/HtmlTable.Zebra More/HtmlTable.Sort More/HtmlTable.Select More/Keyboard More/Keyboard.Extras More/Mask More/Scroller More/Tips More/Spinner More/Locale More/Locale.en-US.Date More/Locale.en-US.Form.Validator More/Locale.en-US.Number +/* +--- +copyrights: + - [MooTools](http://mootools.net) + +licenses: + - [MIT License](http://mootools.net/license.txt) +... +*/ +MooTools.More={version:"1.3.2.1",build:"e586bcd2496e9b22acfde32e12f84d49ce09e59d"};Events.Pseudos=function(g,c,e){var b="monitorEvents:";var a=function(h){return{store:h.store?function(i,j){h.store(b+i,j); +}:function(i,j){(h.$monitorEvents||(h.$monitorEvents={}))[i]=j;},retrieve:h.retrieve?function(i,j){return h.retrieve(b+i,j);}:function(i,j){if(!h.$monitorEvents){return j; +}return h.$monitorEvents[i]||j;}};};var f=function(j){if(j.indexOf(":")==-1||!g){return null;}var i=Slick.parse(j).expressions[0][0],m=i.pseudos,h=m.length,k=[]; +while(h--){if(g[m[h].key]){k.push({event:i.tag,value:m[h].value,pseudo:m[h].key,original:j});}}return k.length?k:null;};var d=function(h){return Object.merge.apply(this,h.map(function(i){return g[i.pseudo].options||{}; +}));};return{addEvent:function(m,p,j){var n=f(m);if(!n){return c.call(this,m,p,j);}var k=a(this),s=k.retrieve(m,[]),h=n[0].event,t=d(n),o=p,i=t[h]||{},l=Array.slice(arguments,2),r=this,q; +if(i.args){l.append(Array.from(i.args));}if(i.base){h=i.base;}if(i.onAdd){i.onAdd(this);}n.each(function(u){var v=o;o=function(){(i.listener||g[u.pseudo].listener).call(r,u,v,arguments,q,t); +};});q=o.bind(this);s.include({event:p,monitor:q});k.store(m,s);c.apply(this,[m,p].concat(l));return c.apply(this,[h,q].concat(l));},removeEvent:function(l,n){var m=f(l); +if(!m){return e.call(this,l,n);}var j=a(this),o=j.retrieve(l);if(!o){return this;}var h=m[0].event,p=d(m),i=p[h]||{},k=Array.slice(arguments,2);if(i.args){k.append(Array.from(i.args)); +}if(i.base){h=i.base;}if(i.onRemove){i.onRemove(this);}e.apply(this,[l,n].concat(k));o.each(function(q,r){if(!n||q.event==n){e.apply(this,[h,q.monitor].concat(k)); +}delete o[r];},this);j.store(l,o);return this;}};};(function(){var b={once:{listener:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f); +}},throttle:{listener:function(d,e,c){if(!e._throttled){e.apply(this,c);e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}}},pause:{listener:function(d,e,c){clearTimeout(e._pause); +e._pause=e.delay(d.value||250,this,c);}}};Events.definePseudo=function(c,d){b[c]=Type.isFunction(d)?{listener:d}:d;return this;};Events.lookupPseudo=function(c){return b[c]; +};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype); +}});})();Class.refactor=function(b,a){Object.each(a,function(e,d){var c=b.prototype[d];c=(c&&c.$origin)||c||function(){};b.implement(d,(typeof e=="function")?function(){var f=this.previous; +this.previous=c;var g=e.apply(this,arguments);this.previous=f;return g;}:e);});return b;};Class.Mutators.Binds=function(a){if(!this.prototype.initialize){this.implement("initialize",function(){}); +}return Array.from(a).concat(this.prototype.Binds||[]);};Class.Mutators.initialize=function(a){return function(){Array.from(this.Binds).each(function(b){var c=this[b]; +if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Class.Occlude=new Class({occlude:function(c,b){b=document.id(b||this.element);var a=b.retrieve(c||this.property); +if(a&&!this.occluded){return(this.occluded=a);}this.occluded=false;b.store(c||this.property,this);return this.occluded;}});(function(){var a={wait:function(b){return this.chain(function(){this.callChain.delay(b==null?500:b,this); +return this;}.bind(this));}};Chain.implement(a);if(this.Fx){Fx.implement(a);}if(this.Element&&Element.implement&&this.Fx){Element.implement({chains:function(b){Array.from(b||["tween","morph","reveal"]).each(function(c){c=this.get(c); +if(!c){return;}c.setOptions({link:"chain"});},this);return this;},pauseFx:function(c,b){this.chains(b).get(b||"tween").wait(c);return this;}});}})();(function(a){Array.implement({min:function(){return Math.min.apply(null,this); +},max:function(){return Math.max.apply(null,this);},average:function(){return this.length?this.sum()/this.length:0;},sum:function(){var b=0,c=this.length; +if(c){while(c--){b+=this[c];}}return b;},unique:function(){return[].combine(this);},shuffle:function(){for(var c=this.length;c&&--c;){var b=this[c],d=Math.floor(Math.random()*(c+1)); +this[c]=this[d];this[d]=b;}return this;},reduce:function(d,e){for(var c=0,b=this.length;c3&&a<21)?"th":["th","st","nd","rd","th"][Math.min(a%10,4)]; +},lessThanMinuteAgo:"less than a minute ago",minuteAgo:"about a minute ago",minutesAgo:"{delta} minutes ago",hourAgo:"about an hour ago",hoursAgo:"about {delta} hours ago",dayAgo:"1 day ago",daysAgo:"{delta} days ago",weekAgo:"1 week ago",weeksAgo:"{delta} weeks ago",monthAgo:"1 month ago",monthsAgo:"{delta} months ago",yearAgo:"1 year ago",yearsAgo:"{delta} years ago",lessThanMinuteUntil:"less than a minute from now",minuteUntil:"about a minute from now",minutesUntil:"{delta} minutes from now",hourUntil:"about an hour from now",hoursUntil:"about {delta} hours from now",dayUntil:"1 day from now",daysUntil:"{delta} days from now",weekUntil:"1 week from now",weeksUntil:"{delta} weeks from now",monthUntil:"1 month from now",monthsUntil:"{delta} months from now",yearUntil:"1 year from now",yearsUntil:"{delta} years from now"}); +(function(){var a=this.Date;var f=a.Methods={ms:"Milliseconds",year:"FullYear",min:"Minutes",mo:"Month",sec:"Seconds",hr:"Hours"};["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds","Time","TimezoneOffset","Week","Timezone","GMTOffset","DayOfYear","LastMonth","LastDayOfMonth","UTCDate","UTCDay","UTCFullYear","AMPM","Ordinal","UTCHours","UTCMilliseconds","UTCMinutes","UTCMonth","UTCSeconds","UTCMilliseconds"].each(function(t){a.Methods[t.toLowerCase()]=t; +});var p=function(v,u,t){if(u==1){return v;}return v28){return 1;}if(z==0&&t<-2){y=new a(y).decrement("day",v); +v=0;}x=new a(y.get("year"),0,1).get("day")||7;if(x>4){u=-7;}}else{x=new a(y.get("year"),0,1).get("day");}u+=y.get("dayofyear");u+=6-v;u+=(7+x-w)%7;return(u/7); +},getOrdinal:function(t){return a.getMsg("ordinal",t||this.get("date"));},getTimezone:function(){return this.toString().replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/,"$1").replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/,"$1$2$3"); +},getGMTOffset:function(){var t=this.get("timezoneOffset");return((t>0)?"-":"+")+p((t.abs()/60).floor(),2)+p(t%60,2);},setAMPM:function(t){t=t.toUpperCase(); +var u=this.get("hr");if(u>11&&t=="AM"){return this.decrement("hour",12);}else{if(u<12&&t=="PM"){return this.increment("hour",12);}}return this;},getAMPM:function(){return(this.get("hr")<12)?"AM":"PM"; +},parse:function(t){this.set("time",a.parse(t));return this;},isValid:function(t){return !isNaN((t||this).valueOf());},format:function(u){if(!this.isValid()){return"invalid date"; +}if(!u){u="%x %X";}var t=u.toLowerCase();if(s[t]){return s[t](this);}u=g[t]||u;var v=this;return u.replace(/%([a-z%])/gi,function(x,w){switch(w){case"a":return a.getMsg("days_abbr")[v.get("day")]; +case"A":return a.getMsg("days")[v.get("day")];case"b":return a.getMsg("months_abbr")[v.get("month")];case"B":return a.getMsg("months")[v.get("month")]; +case"c":return v.format("%a %b %d %H:%M:%S %Y");case"d":return p(v.get("date"),2);case"e":return p(v.get("date"),2," ");case"H":return p(v.get("hr"),2); +case"I":return p((v.get("hr")%12)||12,2);case"j":return p(v.get("dayofyear"),3);case"k":return p(v.get("hr"),2," ");case"l":return p((v.get("hr")%12)||12,2," "); +case"L":return p(v.get("ms"),3);case"m":return p((v.get("mo")+1),2);case"M":return p(v.get("min"),2);case"o":return v.get("ordinal");case"p":return a.getMsg(v.get("ampm")); +case"s":return Math.round(v/1000);case"S":return p(v.get("seconds"),2);case"T":return v.format("%H:%M:%S");case"U":return p(v.get("week"),2);case"w":return v.get("day"); +case"x":return v.format(a.getMsg("shortDate"));case"X":return v.format(a.getMsg("shortTime"));case"y":return v.get("year").toString().substr(2);case"Y":return v.get("year"); +case"z":return v.get("GMTOffset");case"Z":return v.get("Timezone");}return w;});},toISOString:function(){return this.format("iso8601");}}).alias({toJSON:"toISOString",compare:"diff",strftime:"format"}); +var g={db:"%Y-%m-%d %H:%M:%S",compact:"%Y%m%dT%H%M%S","short":"%d %b %H:%M","long":"%B %d, %Y %H:%M"};var k=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],h=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; +var s={rfc822:function(t){return k[t.get("day")]+t.format(", %d ")+h[t.get("month")]+t.format(" %Y %H:%M:%S %Z");},rfc2822:function(t){return k[t.get("day")]+t.format(", %d ")+h[t.get("month")]+t.format(" %Y %H:%M:%S %z"); +},iso8601:function(t){return(t.getUTCFullYear()+"-"+p(t.getUTCMonth()+1,2)+"-"+p(t.getUTCDate(),2)+"T"+p(t.getUTCHours(),2)+":"+p(t.getUTCMinutes(),2)+":"+p(t.getUTCSeconds(),2)+"."+p(t.getUTCMilliseconds(),3)+"Z"); +}};var c=[],n=a.parse;var r=function(w,y,v){var u=-1,x=a.getMsg(w+"s");switch(typeOf(y)){case"object":u=x[y.get(w)];break;case"number":u=x[y];if(!u){throw new Error("Invalid "+w+" index: "+y); +}break;case"string":var t=x.filter(function(z){return this.test(z);},new RegExp("^"+y,"i"));if(!t.length){throw new Error("Invalid "+w+" string");}if(t.length>1){throw new Error("Ambiguous "+w); +}u=t[0];}return(v)?x.indexOf(u):u;};var i=1900,o=70;a.extend({getMsg:function(u,t){return Locale.get("Date."+u,t);},units:{ms:Function.from(1),second:Function.from(1000),minute:Function.from(60000),hour:Function.from(3600000),day:Function.from(86400000),week:Function.from(608400000),month:function(u,t){var v=new a; +return a.daysInMonth(u!=null?u:v.get("mo"),t!=null?t:v.get("year"))*86400000;},year:function(t){t=t||new a().get("year");return a.isLeapYear(t)?31622400000:31536000000; +}},daysInMonth:function(u,t){return[31,a.isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][u];},isLeapYear:function(t){return((t%4===0)&&(t%100!==0))||(t%400===0); +},parse:function(w){var v=typeOf(w);if(v=="number"){return new a(w);}if(v!="string"){return w;}w=w.clean();if(!w.length){return null;}var u;c.some(function(x){var t=x.re.exec(w); +return(t)?(u=x.handler(t)):false;});if(!(u&&u.isValid())){u=new a(n(w));if(!(u&&u.isValid())){u=new a(w.toInt());}}return u;},parseDay:function(t,u){return r("day",t,u); +},parseMonth:function(u,t){return r("month",u,t);},parseUTC:function(u){var t=new a(u);var v=a.UTC(t.get("year"),t.get("mo"),t.get("date"),t.get("hr"),t.get("min"),t.get("sec"),t.get("ms")); +return new a(v);},orderIndex:function(t){return a.getMsg("dateOrder").indexOf(t)+1;},defineFormat:function(t,u){g[t]=u;return this;},defineFormats:function(t){for(var u in t){a.defineFormat(u,t[u]); +}return this;},defineParser:function(t){c.push((t.re&&t.handler)?t:l(t));return this;},defineParsers:function(){Array.flatten(arguments).each(a.defineParser); +return this;},define2DigitYearStart:function(t){o=t%100;i=t-o;return this;}});var d=function(t){return new RegExp("(?:"+a.getMsg(t).map(function(u){return u.substr(0,3); +}).join("|")+")[a-z]*");};var m=function(t){switch(t){case"T":return"%H:%M:%S";case"x":return((a.orderIndex("month")==1)?"%m[-./]%d":"%d[-./]%m")+"([-./]%y)?"; +case"X":return"%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?";}return null;};var j={d:/[0-2]?[0-9]|3[01]/,H:/[01]?[0-9]|2[0-3]/,I:/0?[1-9]|1[0-2]/,M:/[0-5]?\d/,s:/\d+/,o:/[a-z]*/,p:/[ap]\.?m\.?/,y:/\d{2}|\d{4}/,Y:/\d{4}/,z:/Z|[+-]\d{2}(?::?\d{2})?/}; +j.m=j.I;j.S=j.M;var e;var b=function(t){e=t;j.a=j.A=d("days");j.b=j.B=d("months");c.each(function(v,u){if(v.format){c[u]=l(v.format);}});};var l=function(v){if(!e){return{format:v}; +}var t=[];var u=(v.source||v).replace(/%([a-z])/gi,function(x,w){return m(w)||x;}).replace(/\((?!\?)/g,"(?:").replace(/ (?!\?|\*)/g,",? ").replace(/%([a-z%])/gi,function(x,w){var y=j[w]; +if(!y){return w;}t.push(w);return"("+y.source+")";}).replace(/\[a-z\]/gi,"[a-z\\u00c0-\\uffff;&]");return{format:v,re:new RegExp("^"+u+"$","i"),handler:function(z){z=z.slice(1).associate(t); +var w=new a().clearTime(),y=z.y||z.Y;if(y!=null){q.call(w,"y",y);}if("d" in z){q.call(w,"d",1);}if("m" in z||z.b||z.B){q.call(w,"m",1);}for(var x in z){q.call(w,x,z[x]); +}return w;}};};var q=function(t,u){if(!u){return this;}switch(t){case"a":case"A":return this.set("day",a.parseDay(u,true));case"b":case"B":return this.set("mo",a.parseMonth(u,true)); +case"d":return this.set("date",u);case"H":case"I":return this.set("hr",u);case"m":return this.set("mo",u-1);case"M":return this.set("min",u);case"p":return this.set("ampm",u.replace(/\./g,"")); +case"S":return this.set("sec",u);case"s":return this.set("ms",("0."+u)*1000);case"w":return this.set("day",u);case"Y":return this.set("year",u);case"y":u=+u; +if(u<100){u+=i+(u0.75*a){e=c;}break;}f/=a;e=c+"s";}f=f.round();return Date.getMsg(e+d,f).substitute({delta:f});}}).defineParsers({re:/^(?:tod|tom|yes)/i,handler:function(a){var b=new Date().clearTime(); +switch(a[0]){case"tom":return b.increment();case"yes":return b.decrement();default:return b;}}},{re:/^(next|last) ([a-z]+)$/i,handler:function(e){var f=new Date().clearTime(); +var b=f.getDay();var c=Date.parseDay(e[2],true);var a=c-b;if(c<=b){a+=7;}if(e[1]=="last"){a-=7;}return f.set("date",f.getDate()+a);}}).alias("timeAgoInWords","timeDiffInWords"); +Locale.define("en-US","Number",{decimal:".",group:",",currency:{prefix:"$ "}});Number.implement({format:function(q){var n=this;q=q?Object.clone(q):{};var a=function(i){if(q[i]!=null){return q[i]; +}return Locale.get("Number."+i);};var f=n<0,h=a("decimal"),k=a("precision"),o=a("group"),c=a("decimals");if(f){var e=a("negative")||{};if(e.prefix==null&&e.suffix==null){e.prefix="-"; +}["prefix","suffix"].each(function(i){if(e[i]){q[i]=a(i)+e[i];}});n=-n;}var l=a("prefix"),p=a("suffix");if(c!==""&&c>=0&&c<=20){n=n.toFixed(c);}if(k>=1&&k<=21){n=(+n).toPrecision(k); +}n+="";var m;if(a("scientific")===false&&n.indexOf("e")>-1){var j=n.split("e"),b=+j[1];n=j[0].replace(".","");if(b<0){b=-b-1;m=j[0].indexOf(".");if(m>-1){b-=m-1; +}while(b--){n="0"+n;}n="0."+n;}else{m=j[0].lastIndexOf(".");if(m>-1){b-=j[0].length-m-1;}while(b--){n+="0";}}}if(h!="."){n=n.replace(".",h);}if(o){m=n.lastIndexOf(h); +m=(m>-1)?m:n.length;var d=n.substring(m),g=m;while(g--){if((m-g-1)%3==0&&g!=(m-1)){d=o+d;}d=n.charAt(g)+d;}n=d;}if(l){n=l+n;}if(p){n+=p;}return n;},formatCurrency:function(){var a=Locale.get("Number.currency")||{}; +if(a.scientific==null){a.scientific=false;}if(a.decimals==null){a.decimals=2;}return this.format(a);},formatPercentage:function(){var a=Locale.get("Number.percentage")||{}; +if(a.suffix==null){a.suffix="%";}if(a.decimals==null){a.decimals=2;}return this.format(a);}});(function(){var c={a:/[àáâãäåăą]/g,A:/[ÀÁÂÃÄÅĂĄ]/g,c:/[ćčç]/g,C:/[ĆČÇ]/g,d:/[ďđ]/g,D:/[ĎÐ]/g,e:/[èéêëěę]/g,E:/[ÈÉÊËĚĘ]/g,g:/[ğ]/g,G:/[Ğ]/g,i:/[ìíîï]/g,I:/[ÌÍÎÏ]/g,l:/[ĺľł]/g,L:/[ĹĽŁ]/g,n:/[ñňń]/g,N:/[ÑŇŃ]/g,o:/[òóôõöøő]/g,O:/[ÒÓÔÕÖØ]/g,r:/[řŕ]/g,R:/[ŘŔ]/g,s:/[ššş]/g,S:/[ŠŞŚ]/g,t:/[ťţ]/g,T:/[ŤŢ]/g,ue:/[ü]/g,UE:/[Ü]/g,u:/[ùúûůµ]/g,U:/[ÙÚÛŮ]/g,y:/[ÿý]/g,Y:/[ŸÝ]/g,z:/[žźż]/g,Z:/[ŽŹŻ]/g,th:/[þ]/g,TH:/[Þ]/g,dh:/[ð]/g,DH:/[Ð]/g,ss:/[ß]/g,oe:/[œ]/g,OE:/[Œ]/g,ae:/[æ]/g,AE:/[Æ]/g},b={" ":/[\xa0\u2002\u2003\u2009]/g,"*":/[\xb7]/g,"'":/[\u2018\u2019]/g,'"':/[\u201c\u201d]/g,"...":/[\u2026]/g,"-":/[\u2013]/g,"»":/[\uFFFD]/g}; +var a=function(f,h){var e=f,g;for(g in h){e=e.replace(h[g],g);}return e;};var d=function(e,g){e=e||"";var h=g?"<"+e+"(?!\\w)[^>]*>([\\s\\S]*?)":"]+)?>",f=new RegExp(h,"gi"); +return f;};String.implement({standardize:function(){return a(this,c);},repeat:function(e){return new Array(e+1).join(this);},pad:function(e,h,g){if(this.length>=e){return this; +}var f=(h==null?" ":""+h).repeat(e-this.length).substr(0,e-this.length);if(!g||g=="right"){return this+f;}if(g=="left"){return f+this;}return f.substr(0,(f.length/2).floor())+this+f.substr(0,(f.length/2).ceil()); +},getTags:function(e,f){return this.match(d(e,f))||[];},stripTags:function(e,f){return this.replace(d(e,f),"");},tidy:function(){return a(this,b);},truncate:function(e,f,i){var h=this; +if(f==null&&arguments.length==1){f="…";}if(h.length>e){h=h.substring(0,e);if(i){var g=h.lastIndexOf(i);if(g!=-1){h=h.substr(0,g);}}if(f){h+=f;}}return h; +}});})();String.implement({parseQueryString:function(d,a){if(d==null){d=true;}if(a==null){a=true;}var c=this.split(/[&;]/),b={};if(!c.length){return b; +}c.each(function(i){var e=i.indexOf("=")+1,g=e?i.substr(e):"",f=e?i.substr(0,e-1).match(/([^\]\[]+|(\B)(?=\]))/g):[i],h=b;if(!f){return;}if(a){g=decodeURIComponent(g); +}f.each(function(k,j){if(d){k=decodeURIComponent(k);}var l=h[k];if(j0){c.pop(); +}else{if(f!="."){c.push(f);}}});return c.join("/")+"/";},combine:function(c){return c.value||c.scheme+"://"+(c.user?c.user+(c.password?":"+c.password:"")+"@":"")+(c.host||"")+(c.port&&c.port!=this.schemes[c.scheme]?":"+c.port:"")+(c.directory||"/")+(c.file||"")+(c.query?"?"+c.query:"")+(c.fragment?"#"+c.fragment:""); +},set:function(d,f,e){if(d=="value"){var c=f.match(a.regs.scheme);if(c){c=c[1];}if(c&&this.schemes[c.toLowerCase()]==null){this.parsed={scheme:c,value:f}; +}else{this.parsed=this.parse(f,(e||this).parsed)||(c?{scheme:c,value:f}:{value:f});}}else{if(d=="data"){this.setData(f);}else{this.parsed[d]=f;}}return this; +},get:function(c,d){switch(c){case"value":return this.combine(this.parsed,d?d.parsed:false);case"data":return this.getData();}return this.parsed[c]||""; +},go:function(){document.location.href=this.toString();},toURI:function(){return this;},getData:function(e,d){var c=this.get(d||"query");if(!(c||c===0)){return e?null:{}; +}var f=c.parseQueryString();return e?f[e]:f;},setData:function(c,f,d){if(typeof c=="string"){var e=this.getData();e[arguments[0]]=arguments[1];c=e;}else{if(f){c=Object.merge(this.getData(),c); +}}return this.set(d||"query",Object.toQueryString(c));},clearData:function(c){return this.set(c||"query","");},toString:b,valueOf:b});a.regs={endSlash:/\/$/,scheme:/^(\w+):/,directoryDot:/\.\/|\.$/}; +a.base=new a(Array.from(document.getElements("base[href]",true)).getLast(),{base:document.location});String.implement({toURI:function(c){return new a(this,c); +}});})();URI=Class.refactor(URI,{combine:function(f,e){if(!e||f.scheme!=e.scheme||f.host!=e.host||f.port!=e.port){return this.previous.apply(this,arguments); +}var a=f.file+(f.query?"?"+f.query:"")+(f.fragment?"#"+f.fragment:"");if(!e.directory){return(f.directory||(f.file?"":"./"))+a;}var d=e.directory.split("/"),c=f.directory.split("/"),g="",h; +var b=0;for(h=0;h=0||g.parentPositioned||d.allowNegative)?c.x:0).toInt(); +c.top=((c.y>=0||g.parentPositioned||d.allowNegative)?c.y:0).toInt();a.toMinMax(c,d);if(d.relFixedPosition||f.getStyle("position")=="fixed"){a.toRelFixedPosition(f,c); +}if(d.ignoreScroll){a.toIgnoreScroll(f,c);}if(d.ignoreMargins){a.toIgnoreMargins(c,d);}c.left=Math.ceil(c.left);c.top=Math.ceil(c.top);delete c.x;delete c.y; +return c;},setPositionCoordinates:function(k,g,d){var f=k.offset.y,h=k.offset.x,e=(d==document.body)?window.getScroll():d.getPosition(),j=e.y,c=e.x,i=window.getSize(); +switch(k.position.x){case"left":g.x=c+h;break;case"right":g.x=c+h+d.offsetWidth;break;default:g.x=c+((d==document.body?i.x:d.offsetWidth)/2)+h;break;}switch(k.position.y){case"top":g.y=j+f; +break;case"bottom":g.y=j+f+d.offsetHeight;break;default:g.y=j+((d==document.body?i.y:d.offsetHeight)/2)+f;break;}},toMinMax:function(c,d){var f={left:"x",top:"y"},e; +["minimum","maximum"].each(function(g){["left","top"].each(function(h){e=d[g]?d[g][f[h]]:null;if(e!=null&&((g=="minimum")?c[h]e)){c[h]=e;}});}); +},toRelFixedPosition:function(e,c){var d=window.getScroll();c.top+=d.y;c.left+=d.x;},toIgnoreScroll:function(e,d){var c=e.getScroll();d.top-=c.y;d.left-=c.x; +},toIgnoreMargins:function(c,d){c.left+=d.edge.x=="right"?d.dimensions["margin-right"]:(d.edge.x!="center"?-d.dimensions["margin-left"]:-d.dimensions["margin-left"]+((d.dimensions["margin-right"]+d.dimensions["margin-left"])/2)); +c.top+=d.edge.y=="bottom"?d.dimensions["margin-bottom"]:(d.edge.y!="center"?-d.dimensions["margin-top"]:-d.dimensions["margin-top"]+((d.dimensions["margin-bottom"]+d.dimensions["margin-top"])/2)); +},toEdge:function(c,d){var e={},g=d.dimensions,f=d.edge;switch(f.x){case"left":e.x=0;break;case"right":e.x=-g.x-g.computedRight-g.computedLeft;break;default:e.x=-(Math.round(g.totalWidth/2)); +break;}switch(f.y){case"top":e.y=0;break;case"bottom":e.y=-g.y-g.computedTop-g.computedBottom;break;default:e.y=-(Math.round(g.totalHeight/2));break;}c.x+=e.x; +c.y+=e.y;},getCoordinateFromValue:function(c){if(typeOf(c)!="string"){return c;}c=c.toLowerCase();return{x:c.test("left")?"left":(c.test("right")?"right":"center"),y:c.test(/upper|top/)?"top":(c.test("bottom")?"bottom":"center")}; +}};Element.implement({position:function(d){if(d&&(d.x!=null||d.y!=null)){return(b?b.apply(this,arguments):this);}var c=this.setStyle("position","absolute").calculatePosition(d); +return(d&&d.returnPos)?c:this.setStyles(c);},calculatePosition:function(c){return a.getPosition(this,c);}});})(Element.prototype.position);Element.implement({isDisplayed:function(){return this.getStyle("display")!="none"; +},isVisible:function(){var a=this.offsetWidth,b=this.offsetHeight;return(a==0&&b==0)?false:(a>0&&b>0)?true:this.style.display!="none";},toggle:function(){return this[this.isDisplayed()?"hide":"show"](); +},hide:function(){var b;try{b=this.getStyle("display");}catch(a){}if(b=="none"){return this;}return this.store("element:_originalDisplay",b||"").setStyle("display","none"); +},show:function(a){if(!a&&this.isDisplayed()){return this;}a=a||this.retrieve("element:_originalDisplay")||"block";return this.setStyle("display",(a=="none")?"block":a); +},swapClass:function(a,b){return this.removeClass(a).addClass(b);}});Document.implement({clearSelection:function(){if(window.getSelection){var a=window.getSelection(); +if(a&&a.removeAllRanges){a.removeAllRanges();}}else{if(document.selection&&document.selection.empty){try{document.selection.empty();}catch(b){}}}}});var IframeShim=new Class({Implements:[Options,Events,Class.Occlude],options:{className:"iframeShim",src:'javascript:false;document.write("");',display:false,zIndex:null,margin:0,offset:{x:0,y:0},browsers:(Browser.ie6||(Browser.firefox&&Browser.version<3&&Browser.Platform.mac))},property:"IframeShim",initialize:function(b,a){this.element=document.id(b); +if(this.occlude()){return this.occluded;}this.setOptions(a);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var c=this.element.getStyle("zIndex").toInt(); +if(!c){c=1;var b=this.element.getStyle("position");if(b=="static"||!b){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",c); +}c=((this.options.zIndex!=null||this.options.zIndex===0)&&c>this.options.zIndex)?this.options.zIndex:c-1;if(c<0){c=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:c,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this); +var a=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",a); +}else{a();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this; +}var a=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){a.x=a.x-(this.options.margin*2);a.y=a.y-(this.options.margin*2); +this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:a.x,height:a.y}).position({relativeTo:this.element,offset:this.options.offset}); +return this;},hide:function(){if(this.shim){this.shim.setStyle("display","none");}return this;},show:function(){if(this.shim){this.shim.setStyle("display","block"); +}return this.position();},dispose:function(){if(this.shim){this.shim.dispose();}return this;},destroy:function(){if(this.shim){this.shim.destroy();}return this; +}});window.addEvent("load",function(){IframeShim.ready=true;});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(b,a){this.target=document.id(b)||document.id(document.body); +this.target.store("mask",this);this.setOptions(a);this.render();this.inject();},render:function(){this.element=new Element("div",{"class":this.options["class"],id:this.options.id||"mask-"+String.uniqueID(),styles:Object.merge({},this.options.style,{display:"none"}),events:{click:function(a){this.fireEvent("click",a); +if(this.options.hideOnClick){this.hide();}}.bind(this)}});this.hidden=true;},toElement:function(){return this.element;},inject:function(b,a){a=a||(this.options.inject?this.options.inject.where:"")||this.target==document.body?"inside":"after"; +b=b||(this.options.inject&&this.options.inject.target)||this.target;this.element.inject(b,a);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions); +this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)});}},position:function(){this.resize(this.options.width,this.options.height); +this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this; +},resize:function(a,e){var b={styles:["padding","border"]};if(this.options.maskMargins){b.styles.push("margin");}var d=this.target.getComputedSize(b);if(this.target==document.body){this.element.setStyles({width:0,height:0}); +var c=window.getScrollSize();if(d.totalHeight=0&&a.options[a.selectedIndex].value!=""); +}else{return((a.get("value")==null)||(a.get("value").length==0));}}});Form.Validator.addAllThese([["required",{errorMsg:function(){return Form.Validator.getMsg("required"); +},test:function(a){return !Form.Validator.getValidator("IsEmpty").test(a);}}],["minLength",{errorMsg:function(a,b){if(typeOf(b.minLength)!="null"){return Form.Validator.getMsg("minLength").substitute({minLength:b.minLength,length:a.get("value").length}); +}else{return"";}},test:function(a,b){if(typeOf(b.minLength)!="null"){return(a.get("value").length>=(b.minLength||0));}else{return true;}}}],["maxLength",{errorMsg:function(a,b){if(typeOf(b.maxLength)!="null"){return Form.Validator.getMsg("maxLength").substitute({maxLength:b.maxLength,length:a.get("value").length}); +}else{return"";}},test:function(a,b){return a.get("value").length<=(b.maxLength||10000);}}],["validate-integer",{errorMsg:Form.Validator.getMsg.pass("integer"),test:function(a){return Form.Validator.getValidator("IsEmpty").test(a)||(/^(-?[1-9]\d*|0)$/).test(a.get("value")); +}}],["validate-numeric",{errorMsg:Form.Validator.getMsg.pass("numeric"),test:function(a){return Form.Validator.getValidator("IsEmpty").test(a)||(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(a.get("value")); +}}],["validate-digits",{errorMsg:Form.Validator.getMsg.pass("digits"),test:function(a){return Form.Validator.getValidator("IsEmpty").test(a)||(/^[\d() .:\-\+#]+$/.test(a.get("value"))); +}}],["validate-alpha",{errorMsg:Form.Validator.getMsg.pass("alpha"),test:function(a){return Form.Validator.getValidator("IsEmpty").test(a)||(/^[a-zA-Z]+$/).test(a.get("value")); +}}],["validate-alphanum",{errorMsg:Form.Validator.getMsg.pass("alphanum"),test:function(a){return Form.Validator.getValidator("IsEmpty").test(a)||!(/\W/).test(a.get("value")); +}}],["validate-date",{errorMsg:function(a,b){if(Date.parse){var c=b.dateFormat||"%x";return Form.Validator.getMsg("dateSuchAs").substitute({date:new Date().format(c)}); +}else{return Form.Validator.getMsg("dateInFormatMDY");}},test:function(e,g){if(Form.Validator.getValidator("IsEmpty").test(e)){return true;}var a=Locale.getCurrent().sets.Date,b=new RegExp([a.days,a.days_abbr,a.months,a.months_abbr].flatten().join("|"),"i"),i=e.get("value"),f=i.match(/[a-z]+/gi); +if(f&&!f.every(b.exec,b)){return false;}var c=Date.parse(i),h=g.dateFormat||"%x",d=c.format(h);if(d!="invalid date"){e.set("value",d);}return c.isValid(); +}}],["validate-email",{errorMsg:Form.Validator.getMsg.pass("email"),test:function(a){return Form.Validator.getValidator("IsEmpty").test(a)||(/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(a.get("value")); +}}],["validate-url",{errorMsg:Form.Validator.getMsg.pass("url"),test:function(a){return Form.Validator.getValidator("IsEmpty").test(a)||(/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(a.get("value")); +}}],["validate-currency-dollar",{errorMsg:Form.Validator.getMsg.pass("currencyDollar"),test:function(a){return Form.Validator.getValidator("IsEmpty").test(a)||(/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(a.get("value")); +}}],["validate-one-required",{errorMsg:Form.Validator.getMsg.pass("oneRequired"),test:function(a,b){var c=document.id(b["validate-one-required"])||a.getParent(b["validate-one-required"]); +return c.getElements("input").some(function(d){if(["checkbox","radio"].contains(d.get("type"))){return d.get("checked");}return d.get("value");});}}]]); +Element.Properties.validator={set:function(a){this.get("validator").setOptions(a);},get:function(){var a=this.retrieve("validator");if(!a){a=new Form.Validator(this); +this.store("validator",a);}return a;}};Element.implement({validate:function(a){if(a){this.set("validator",a);}return this.get("validator").validate();}}); +Form.Validator.Inline=new Class({Extends:Form.Validator,options:{showError:function(a){if(a.reveal){a.reveal();}else{a.setStyle("display","block");}},hideError:function(a){if(a.dissolve){a.dissolve(); +}else{a.setStyle("display","none");}},scrollToErrorsOnSubmit:true,scrollToErrorsOnBlur:false,scrollToErrorsOnChange:false,scrollFxOptions:{transition:"quad:out",offset:{y:-20}}},initialize:function(b,a){this.parent(b,a); +this.addEvent("onElementValidate",function(g,f,e,h){var d=this.getValidator(e);if(!g&&d.getError(f)){if(h){f.addClass("warning");}var c=this.makeAdvice(e,f,d.getError(f),h); +this.insertAdvice(c,f);this.showAdvice(e,f);}else{this.hideAdvice(e,f);}});},makeAdvice:function(d,f,c,g){var e=(g)?this.warningPrefix:this.errorPrefix; +e+=(this.options.useTitles)?f.title||c:c;var a=(g)?"warning-advice":"validation-advice";var b=this.getAdvice(d,f);if(b){b=b.set("html",e);}else{b=new Element("div",{html:e,styles:{display:"none"},id:"advice-"+d.split(":")[0]+"-"+this.getFieldId(f)}).addClass(a); +}f.store("$moo:advice-"+d,b);return b;},getFieldId:function(a){return a.id?a.id:a.id="input_"+a.name;},showAdvice:function(b,c){var a=this.getAdvice(b,c); +if(a&&!c.retrieve("$moo:"+this.getPropName(b))&&(a.getStyle("display")=="none"||a.getStyle("visiblity")=="hidden"||a.getStyle("opacity")==0)){c.store("$moo:"+this.getPropName(b),true); +this.options.showError(a);this.fireEvent("showAdvice",[c,a,b]);}},hideAdvice:function(b,c){var a=this.getAdvice(b,c);if(a&&c.retrieve("$moo:"+this.getPropName(b))){c.store("$moo:"+this.getPropName(b),false); +this.options.hideError(a);this.fireEvent("hideAdvice",[c,a,b]);}},getPropName:function(a){return"advice"+a;},resetField:function(a){a=document.id(a);if(!a){return this; +}this.parent(a);a.get("validators").each(function(b){this.hideAdvice(b,a);},this);return this;},getAllAdviceMessages:function(d,c){var b=[];if(d.hasClass("ignoreValidation")&&!c){return b; +}var a=d.get("validators").some(function(g){var e=g.test("^warn-")||d.hasClass("warnOnly");if(e){g=g.replace(/^warn-/,"");}var f=this.getValidator(g);if(!f){return; +}b.push({message:f.getError(d),warnOnly:e,passed:f.test(),validator:f});},this);return b;},getAdvice:function(a,b){return b.retrieve("$moo:advice-"+a); +},insertAdvice:function(a,c){var b=c.get("validatorProps");if(!b.msgPos||!document.id(b.msgPos)){if(c.type&&c.type.toLowerCase()=="radio"){c.getParent().adopt(a); +}else{a.inject(document.id(c),"after");}}else{document.id(b.msgPos).grab(a);}},validateField:function(g,f,b){var a=this.parent(g,f);if(((this.options.scrollToErrorsOnSubmit&&b==null)||b)&&!a){var c=document.id(this).getElement(".validation-failed"); +var d=document.id(this).getParent();while(d!=document.body&&d.getScrollSize().y==d.getSize().y){d=d.getParent();}var e=d.retrieve("$moo:fvScroller");if(!e&&window.Fx&&Fx.Scroll){e=new Fx.Scroll(d,this.options.scrollFxOptions); +d.store("$moo:fvScroller",e);}if(c){if(e){e.toElement(c);}else{d.scrollTo(d.getScroll().x,c.getPosition(d).y-20);}}}return a;},watchFields:function(a){a.each(function(b){if(this.options.evaluateFieldsOnBlur){b.addEvent("blur",this.validationMonitor.pass([b,false,this.options.scrollToErrorsOnBlur],this)); +}if(this.options.evaluateFieldsOnChange){b.addEvent("change",this.validationMonitor.pass([b,true,this.options.scrollToErrorsOnChange],this));}},this);}}); +Form.Validator.addAllThese([["validate-enforce-oncheck",{test:function(a,b){var c=a.getParent("form").retrieve("validator");if(!c){return true;}(b.toEnforce||document.id(b.enforceChildrenOf).getElements("input, select, textarea")).map(function(d){if(a.checked){c.enforceField(d); +}else{c.ignoreField(d);c.resetField(d);}});return true;}}],["validate-ignore-oncheck",{test:function(a,b){var c=a.getParent("form").retrieve("validator"); +if(!c){return true;}(b.toIgnore||document.id(b.ignoreChildrenOf).getElements("input, select, textarea")).each(function(d){if(a.checked){c.ignoreField(d); +c.resetField(d);}else{c.enforceField(d);}});return true;}}],["validate-nospace",{errorMsg:function(){return Form.Validator.getMsg("noSpace");},test:function(a,b){return !a.get("value").test(/\s/); +}}],["validate-toggle-oncheck",{test:function(b,c){var d=b.getParent("form").retrieve("validator");if(!d){return true;}var a=c.toToggle||document.id(c.toToggleChildrenOf).getElements("input, select, textarea"); +if(!b.checked){a.each(function(e){d.ignoreField(e);d.resetField(e);});}else{a.each(function(e){d.enforceField(e);});}return true;}}],["validate-reqchk-bynode",{errorMsg:function(){return Form.Validator.getMsg("reqChkByNode"); +},test:function(a,b){return(document.id(b.nodeId).getElements(b.selector||"input[type=checkbox], input[type=radio]")).some(function(c){return c.checked; +});}}],["validate-required-check",{errorMsg:function(a,b){return b.useTitle?a.get("title"):Form.Validator.getMsg("requiredChk");},test:function(a,b){return !!a.checked; +}}],["validate-reqchk-byname",{errorMsg:function(a,b){return Form.Validator.getMsg("reqChkByName").substitute({label:b.label||a.get("type")});},test:function(b,d){var c=d.groupName||b.get("name"); +var a=$$(document.getElementsByName(c)).some(function(g,f){return g.checked;});var e=b.getParent("form").retrieve("validator");if(a&&e){e.resetField(b); +}return a;}}],["validate-match",{errorMsg:function(a,b){return Form.Validator.getMsg("match").substitute({matchName:b.matchName||document.id(b.matchInput).get("name")}); +},test:function(b,c){var d=b.get("value");var a=document.id(c.matchInput)&&document.id(c.matchInput).get("value");return d&&a?d==a:true;}}],["validate-after-date",{errorMsg:function(a,b){return Form.Validator.getMsg("afterDate").substitute({label:b.afterLabel||(b.afterElement?Form.Validator.getMsg("startDate"):Form.Validator.getMsg("currentDate"))}); +},test:function(b,c){var d=document.id(c.afterElement)?Date.parse(document.id(c.afterElement).get("value")):new Date();var a=Date.parse(b.get("value")); +return a&&d?a>=d:true;}}],["validate-before-date",{errorMsg:function(a,b){return Form.Validator.getMsg("beforeDate").substitute({label:b.beforeLabel||(b.beforeElement?Form.Validator.getMsg("endDate"):Form.Validator.getMsg("currentDate"))}); +},test:function(b,c){var d=Date.parse(b.get("value"));var a=document.id(c.beforeElement)?Date.parse(document.id(c.beforeElement).get("value")):new Date(); +return a&&d?a>=d:true;}}],["validate-custom-required",{errorMsg:function(){return Form.Validator.getMsg("required");},test:function(a,b){return a.get("value")!=b.emptyValue; +}}],["validate-same-month",{errorMsg:function(a,b){var c=document.id(b.sameMonthAs)&&document.id(b.sameMonthAs).get("value");var d=a.get("value");if(d!=""){return Form.Validator.getMsg(c?"sameMonth":"startMonth"); +}},test:function(a,b){var d=Date.parse(a.get("value"));var c=Date.parse(document.id(b.sameMonthAs)&&document.id(b.sameMonthAs).get("value"));return d&&c?d.format("%B")==c.format("%B"):true; +}}],["validate-cc-num",{errorMsg:function(a){var b=a.get("value").replace(/[^0-9]/g,"");return Form.Validator.getMsg("creditcard").substitute({length:b.length}); +},test:function(c){if(Form.Validator.getValidator("IsEmpty").test(c)){return true;}var g=c.get("value");g=g.replace(/[^0-9]/g,"");var a=false;if(g.test(/^4[0-9]{12}([0-9]{3})?$/)){a="Visa"; +}else{if(g.test(/^5[1-5]([0-9]{14})$/)){a="Master Card";}else{if(g.test(/^3[47][0-9]{13}$/)){a="American Express";}else{if(g.test(/^6011[0-9]{12}$/)){a="Discover"; +}}}}if(a){var d=0;var e=0;for(var b=g.length-1;b>=0;--b){e=g.charAt(b).toInt();if(e==0){continue;}if((g.length-b)%2==0){e+=e;}if(e>9){e=e.toString().charAt(0).toInt()+e.toString().charAt(1).toInt(); +}d+=e;}if((d%10)==0){return true;}}var f="";while(g!=""){f+=" "+g.substr(0,4);g=g.substr(4);}c.getParent("form").retrieve("validator").ignoreField(c);c.set("value",f.clean()); +c.getParent("form").retrieve("validator").enforceField(c);return false;}}]]);var OverText=new Class({Implements:[Options,Events,Class.Occlude],Binds:["reposition","assert","focus","hide"],options:{element:"label",labelClass:"overTxtLabel",positionOptions:{position:"upperLeft",edge:"upperLeft",offset:{x:4,y:2}},poll:false,pollInterval:250,wrap:false},property:"OverText",initialize:function(b,a){b=this.element=document.id(b); +if(this.occlude()){return this.occluded;}this.setOptions(a);this.attach(b);OverText.instances.push(this);if(this.options.poll){this.poll();}},toElement:function(){return this.element; +},attach:function(){var b=this.element,a=this.options,c=a.textOverride||b.get("alt")||b.get("title");if(!c){return this;}var d=this.text=new Element(a.element,{"class":a.labelClass,styles:{lineHeight:"normal",position:"absolute",cursor:"text"},html:c,events:{click:this.hide.pass(a.element=="label",this)}}).inject(b,"after"); +if(a.element=="label"){if(!b.get("id")){b.set("id","input_"+String.uniqueID());}d.set("for",b.get("id"));}if(a.wrap){this.textHolder=new Element("div.overTxtWrapper",{styles:{lineHeight:"normal",position:"relative"}}).grab(d).inject(b,"before"); +}return this.enable();},destroy:function(){this.element.eliminate(this.property);this.disable();if(this.text){this.text.destroy();}if(this.textHolder){this.textHolder.destroy(); +}return this;},disable:function(){this.element.removeEvents({focus:this.focus,blur:this.assert,change:this.assert});window.removeEvent("resize",this.reposition); +this.hide(true,true);return this;},enable:function(){this.element.addEvents({focus:this.focus,blur:this.assert,change:this.assert});window.addEvent("resize",this.reposition); +this.assert(true);this.reposition();return this;},wrap:function(){if(this.options.element=="label"){if(!this.element.get("id")){this.element.set("id","input_"+String.uniqueID()); +}this.text.set("for",this.element.get("id"));}},startPolling:function(){this.pollingPaused=false;return this.poll();},poll:function(a){if(this.poller&&!a){return this; +}if(a){clearInterval(this.poller);}else{this.poller=(function(){if(!this.pollingPaused){this.assert(true);}}).periodical(this.options.pollInterval,this); +}return this;},stopPolling:function(){this.pollingPaused=true;return this.poll(true);},focus:function(){if(this.text&&(!this.text.isDisplayed()||this.element.get("disabled"))){return this; +}return this.hide();},hide:function(c,a){if(this.text&&(this.text.isDisplayed()&&(!this.element.get("disabled")||a))){this.text.hide();this.fireEvent("textHide",[this.text,this.element]); +this.pollingPaused=true;if(!c){try{this.element.fireEvent("focus");this.element.focus();}catch(b){}}}return this;},show:function(){if(this.text&&!this.text.isDisplayed()){this.text.show(); +this.reposition();this.fireEvent("textShow",[this.text,this.element]);this.pollingPaused=false;}return this;},test:function(){return !this.element.get("value"); +},assert:function(a){return this[this.test()?"show":"hide"](a);},reposition:function(){this.assert(true);if(!this.element.isVisible()){return this.stopPolling().hide(); +}if(this.text&&this.test()){this.text.position(Object.merge(this.options.positionOptions,{relativeTo:this.element}));}return this;}});OverText.instances=[]; +Object.append(OverText,{each:function(a){return OverText.instances.each(function(c,b){if(c.element&&c.text){a.call(OverText,c,b);}});},update:function(){return OverText.each(function(a){return a.reposition(); +});},hideAll:function(){return OverText.each(function(a){return a.hide(true,true);});},showAll:function(){return OverText.each(function(a){return a.show(); +});}});Fx.Elements=new Class({Extends:Fx.CSS,initialize:function(b,a){this.elements=this.subject=$$(b);this.parent(a);},compute:function(g,h,j){var c={}; +for(var d in g){var a=g[d],e=h[d],f=c[d]={};for(var b in a){f[b]=this.parent(a[b],e[b],j);}}return c;},set:function(b){for(var c in b){if(!this.elements[c]){continue; +}var a=b[c];for(var d in a){this.render(this.elements[c],d,a[d],this.options.unit);}}return this;},start:function(c){if(!this.check(c)){return this;}var h={},j={}; +for(var d in c){if(!this.elements[d]){continue;}var f=c[d],a=h[d]={},g=j[d]={};for(var b in f){var e=this.prepare(this.elements[d],b,f[b]);a[b]=e.from; +g[b]=e.to;}}return this.parent(h,j);}});Fx.Accordion=new Class({Extends:Fx.Elements,options:{fixedHeight:false,fixedWidth:false,display:0,show:false,height:true,width:false,opacity:true,alwaysHide:false,trigger:"click",initialDisplayFx:true,resetHeight:true},initialize:function(){var g=function(h){return h!=null; +};var f=Array.link(arguments,{container:Type.isElement,options:Type.isObject,togglers:g,elements:g});this.parent(f.elements,f.options);var b=this.options,e=this.togglers=$$(f.togglers); +this.previous=-1;this.internalChain=new Chain();if(b.alwaysHide){this.options.link="chain";}if(b.show||this.options.show===0){b.display=false;this.previous=b.show; +}if(b.start){b.display=false;b.show=false;}var d=this.effects={};if(b.opacity){d.opacity="fullOpacity";}if(b.width){d.width=b.fixedWidth?"fullWidth":"offsetWidth"; +}if(b.height){d.height=b.fixedHeight?"fullHeight":"scrollHeight";}for(var c=0,a=e.length;c=0?a-1:0)).chain(d);}else{d();}return this;},detach:function(b){var a=function(c){c.removeEvent(this.options.trigger,c.retrieve("accordion:display")); +}.bind(this);if(!b){this.togglers.each(a);}else{a(b);}return this;},display:function(b,c){if(!this.check(b,c)){return this;}var h={},g=this.elements,a=this.options,f=this.effects; +if(c==null){c=true;}if(typeOf(b)=="element"){b=g.indexOf(b);}if(b==this.previous&&!a.alwaysHide){return this;}if(a.resetHeight){var e=g[this.previous]; +if(e&&!this.selfHidden){for(var d in f){e.setStyle(d,e[f[d]]);}}}if((this.timer&&a.link=="chain")||(b===this.previous&&!a.alwaysHide)){return this;}this.previous=b; +this.selfHidden=false;g.each(function(l,k){h[k]={};var j;if(k!=b){j=true;}else{if(a.alwaysHide&&((l.offsetHeight>0&&a.height)||l.offsetWidth>0&&a.width)){j=true; +this.selfHidden=true;}}this.fireEvent(j?"background":"active",[this.togglers[k],l]);for(var m in f){h[k][m]=j?0:l[f[m]];}if(!c&&!j&&a.resetHeight){h[k].height="auto"; +}},this);this.internalChain.clearChain();this.internalChain.chain(function(){if(a.resetHeight&&!this.selfHidden){var i=g[b];if(i){i.setStyle("height","auto"); +}}}.bind(this));return c?this.start(h):this.set(h).internalChain.callChain();}});Fx.Move=new Class({Extends:Fx.Morph,options:{relativeTo:document.body,position:"center",edge:false,offset:{x:0,y:0}},start:function(a){var b=this.element,c=b.getStyles("top","left"); +if(c.top=="auto"||c.left=="auto"){b.setPosition(b.getPosition(b.getOffsetParent()));}return this.parent(b.position(Object.merge({},this.options,a,{returnPos:true}))); +}});Element.Properties.move={set:function(a){this.get("move").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("move");if(!a){a=new Fx.Move(this,{link:"cancel"}); +this.store("move",a);}return a;}};Element.implement({move:function(a){this.get("move").start(a);return this;}});(function(){Fx.Scroll=new Class({Extends:Fx,options:{offset:{x:0,y:0},wheelStops:true},initialize:function(c,b){this.element=this.subject=document.id(c); +this.parent(b);if(typeOf(this.element)!="element"){this.element=document.id(this.element.getDocument().body);}if(this.options.wheelStops){var d=this.element,e=this.cancel.pass(false,this); +this.addEvent("start",function(){d.addEvent("mousewheel",e);},true);this.addEvent("complete",function(){d.removeEvent("mousewheel",e);},true);}},set:function(){var b=Array.flatten(arguments); +if(Browser.firefox){b=[Math.round(b[0]),Math.round(b[1])];}this.element.scrollTo(b[0],b[1]);return this;},compute:function(d,c,b){return[0,1].map(function(e){return Fx.compute(d[e],c[e],b); +});},start:function(c,d){if(!this.check(c,d)){return this;}var b=this.element.getScroll();return this.parent([b.x,b.y],[c,d]);},calculateScroll:function(g,f){var d=this.element,b=d.getScrollSize(),h=d.getScroll(),j=d.getSize(),c=this.options.offset,i={x:g,y:f}; +for(var e in i){if(!i[e]&&i[e]!==0){i[e]=h[e];}if(typeOf(i[e])!="number"){i[e]=b[e]-j[e];}i[e]+=c[e];}return[i.x,i.y];},toTop:function(){return this.start.apply(this,this.calculateScroll(false,0)); +},toLeft:function(){return this.start.apply(this,this.calculateScroll(0,false));},toRight:function(){return this.start.apply(this,this.calculateScroll("right",false)); +},toBottom:function(){return this.start.apply(this,this.calculateScroll(false,"bottom"));},toElement:function(d,e){e=e?Array.from(e):["x","y"];var c=a(this.element)?{x:0,y:0}:this.element.getScroll(); +var b=Object.map(document.id(d).getPosition(this.element),function(g,f){return e.contains(f)?g+c[f]:false;});return this.start.apply(this,this.calculateScroll(b.x,b.y)); +},toElementEdge:function(d,g,e){g=g?Array.from(g):["x","y"];d=document.id(d);var i={},f=d.getPosition(this.element),j=d.getSize(),h=this.element.getScroll(),b=this.element.getSize(),c={x:f.x+j.x,y:f.y+j.y}; +["x","y"].each(function(k){if(g.contains(k)){if(c[k]>h[k]+b[k]){i[k]=c[k]-b[k];}if(f[k]this.elements.length){e.splice(this.elements.length-1,e.length-this.elements.length); +}}var b=0;i=a=0;e.each(function(k){var j={};if(d){j.top=i-f[k].top-b;i+=f[k].height;}else{j.left=a-f[k].left;a+=f[k].width;}b=b+f[k].margin;c[k]=j;},this); +var g={};Array.clone(e).sort().each(function(j){g[j]=c[j];});this.start(g);this.currentOrder=e;return this;},rearrangeDOM:function(a){a=a||this.currentOrder; +var b=this.elements[0].getParent();var c=[];this.elements.setStyle("opacity",0);a.each(function(d){c.push(this.elements[d].inject(b).setStyles({top:0,left:0})); +},this);this.elements.setStyle("opacity",1);this.elements=$$(c);this.setDefaultOrder();return this;},getDefaultOrder:function(){return this.elements.map(function(b,a){return a; +});},getCurrentOrder:function(){return this.currentOrder;},forward:function(){return this.sort(this.getDefaultOrder());},backward:function(){return this.sort(this.getDefaultOrder().reverse()); +},reverse:function(){return this.sort(this.currentOrder.reverse());},sortByElements:function(a){return this.sort(a.map(function(b){return this.elements.indexOf(b); +},this));},swap:function(c,b){if(typeOf(c)=="element"){c=this.elements.indexOf(c);}if(typeOf(b)=="element"){b=this.elements.indexOf(b);}var a=Array.clone(this.currentOrder); +a[this.currentOrder.indexOf(c)]=b;a[this.currentOrder.indexOf(b)]=c;return this.sort(a);}});var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,element:function(c){return c!=null; +}});this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=typeOf(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element; +this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection=(Browser.ie)?"selectstart":"mousedown";if(Browser.ie&&!Drag.ondragstartFixed){document.ondragstart=Function.from(false); +Drag.ondragstartFixed=true;}this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:Function.from(false)}; +this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start); +return this;},start:function(a){var j=this.options;if(a.rightClick){return;}if(j.preventDefault){a.preventDefault();}if(j.stopPropagation){a.stopPropagation(); +}this.mouse.start=a.page;this.fireEvent("beforeStart",this.element);var c=j.limit;this.limit={x:[],y:[]};var e,g;for(e in j.modifiers){if(!j.modifiers[e]){continue; +}var b=this.element.getStyle(j.modifiers[e]);if(b&&!b.match(/px$/)){if(!g){g=this.element.getCoordinates(this.element.getOffsetParent());}b=g[j.modifiers[e]]; +}if(j.style){this.value.now[e]=(b||0).toInt();}else{this.value.now[e]=this.element[j.modifiers[e]];}if(j.invert){this.value.now[e]*=-1;}this.mouse.pos[e]=a.page[e]-this.value.now[e]; +if(c&&c[e]){var d=2;while(d--){var f=c[e][d];if(f||f===0){this.limit[e][d]=(typeof f=="function")?f():f;}}}}if(typeOf(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid}; +}var h={mousemove:this.bound.check,mouseup:this.bound.cancel};h[this.selection]=this.bound.eventStop;this.document.addEvents(h);},check:function(a){if(this.options.preventDefault){a.preventDefault(); +}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop}); +this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(b){var a=this.options;if(a.preventDefault){b.preventDefault(); +}this.mouse.now=b.page;for(var c in a.modifiers){if(!a.modifiers[c]){continue;}this.value.now[c]=this.mouse.now[c]-this.mouse.pos[c];if(a.invert){this.value.now[c]*=-1; +}if(a.limit&&this.limit[c]){if((this.limit[c][1]||this.limit[c][1]===0)&&(this.value.now[c]>this.limit[c][1])){this.value.now[c]=this.limit[c][1];}else{if((this.limit[c][0]||this.limit[c][0]===0)&&(this.value.now[c]d.left&&b.xd.top);},this).getLast();if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]); +}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables(); +}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a); +this.store("dragger",b);return b;}});var Slider=new Class({Implements:[Events,Options],Binds:["clickedElement","draggedKnob","scrolledElement"],options:{onTick:function(a){this.setKnobPosition(a); +},initialStep:0,snap:false,offset:0,range:false,wheel:false,steps:100,mode:"horizontal"},initialize:function(f,a,e){this.setOptions(e);e=this.options;this.element=document.id(f); +a=this.knob=document.id(a);this.previousChange=this.previousEnd=this.step=-1;var b={},d={x:false,y:false};switch(e.mode){case"vertical":this.axis="y";this.property="top"; +this.offset="offsetHeight";break;case"horizontal":this.axis="x";this.property="left";this.offset="offsetWidth";}this.setSliderDimensions();this.setRange(e.range); +if(a.getStyle("position")=="static"){a.setStyle("position","relative");}a.setStyle(this.property,-e.offset);d[this.axis]=this.property;b[this.axis]=[-e.offset,this.full-e.offset]; +var c={snap:0,limit:b,modifiers:d,onDrag:this.draggedKnob,onStart:this.draggedKnob,onBeforeStart:(function(){this.isDragging=true;}).bind(this),onCancel:function(){this.isDragging=false; +}.bind(this),onComplete:function(){this.isDragging=false;this.draggedKnob();this.end();}.bind(this)};if(e.snap){this.setSnap(c);}this.drag=new Drag(a,c); +this.attach();if(e.initialStep!=null){this.set(e.initialStep);}},attach:function(){this.element.addEvent("mousedown",this.clickedElement);if(this.options.wheel){this.element.addEvent("mousewheel",this.scrolledElement); +}this.drag.attach();return this;},detach:function(){this.element.removeEvent("mousedown",this.clickedElement).removeEvent("mousewheel",this.scrolledElement); +this.drag.detach();return this;},autosize:function(){this.setSliderDimensions().setKnobPosition(this.toPosition(this.step));this.drag.options.limit[this.axis]=[-this.options.offset,this.full-this.options.offset]; +if(this.options.snap){this.setSnap();}return this;},setSnap:function(a){if(!a){a=this.drag.options;}a.grid=Math.ceil(this.stepWidth);a.limit[this.axis][1]=this.full; +return this;},setKnobPosition:function(a){if(this.options.snap){a=this.toPosition(this.step);}this.knob.setStyle(this.property,a);return this;},setSliderDimensions:function(){this.full=this.element.measure(function(){this.half=this.knob[this.offset]/2; +return this.element[this.offset]-this.knob[this.offset]+(this.options.offset*2);}.bind(this));return this;},set:function(a){if(!((this.range>0)^(a0)^(a>this.max))){a=this.max;}this.step=Math.round(a);return this.checkStep().fireEvent("tick",this.toPosition(this.step)).end();},setRange:function(a,b){this.min=Array.pick([a[0],0]); +this.max=Array.pick([a[1],this.options.steps]);this.range=this.max-this.min;this.steps=this.options.steps||this.full;this.stepSize=Math.abs(this.range)/this.steps; +this.stepWidth=this.stepSize*this.full/Math.abs(this.range);if(a){this.set(Array.pick([b,this.step]).floor(this.min).max(this.max));}return this;},clickedElement:function(c){if(this.isDragging||c.target==this.knob){return; +}var b=this.range<0?-1:1,a=c.page[this.axis]-this.element.getPosition()[this.axis]-this.half;a=a.limit(-this.options.offset,this.full-this.options.offset); +this.step=Math.round(this.min+b*this.toStep(a));this.checkStep().fireEvent("tick",a).end();},scrolledElement:function(a){var b=(this.options.mode=="horizontal")?(a.wheel<0):(a.wheel>0); +this.set(this.step+(b?-1:1)*this.stepSize);a.stop();},draggedKnob:function(){var b=this.range<0?-1:1,a=this.drag.value.now[this.axis];a=a.limit(-this.options.offset,this.full-this.options.offset); +this.step=Math.round(this.min+b*this.toStep(a));this.checkStep();},checkStep:function(){var a=this.step;if(this.previousChange!=a){this.previousChange=a; +this.fireEvent("change",a);}return this;},end:function(){var a=this.step;if(this.previousEnd!==a){this.previousEnd=a;this.fireEvent("complete",a+"");}return this; +},toStep:function(a){var b=(a+this.options.offset)*this.stepSize/this.full*this.steps;return this.options.steps?Math.round(b-=b%this.stepSize):b;},toPosition:function(a){return(this.full*Math.abs(this.min-a))/(this.steps*this.stepSize)-this.options.offset; +}});var Sortables=new Class({Implements:[Events,Options],options:{opacity:1,clone:false,revert:false,handle:false,dragOptions:{}},initialize:function(a,b){this.setOptions(b); +this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,Object.merge({duration:250,link:"cancel"},this.options.revert)); +}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a); +var b=a.retrieve("sortables:start",function(c){this.start.call(this,c,a);}.bind(this));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b); +},this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.include(a);this.addItems(a.getChildren());},this);return this; +},removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b); +return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a; +},this));},getClone:function(b,a){if(!this.options.clone){return new Element(a.tagName).inject(document.body);}if(typeOf(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list); +}var c=a.clone(true).setStyles({margin:0,position:"absolute",visibility:"hidden",width:a.getStyle("width")}).addEvent("mousedown",function(d){a.fireEvent("mousedown",d); +});if(c.get("html").test("radio")){c.getElements("input[type=radio]").each(function(d,e){d.set("name","clone_"+e);if(d.get("checked")){a.getElements("input[type=radio]")[e].set("checked",true); +}});}return c.inject(this.list).setPosition(a.getPosition(a.getOffsetParent()));},getDroppables:function(){var a=this.list.getChildren().erase(this.clone).erase(this.element); +if(!this.options.constrain){a.append(this.lists).erase(this.list);}return a;},insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b; +this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";}this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]); +},start:function(b,a){if(!this.idle||b.rightClick||["button","input","a"].contains(b.target.get("tag"))){return;}this.idle=false;this.element=a;this.opacity=a.get("opacity"); +this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,Object.merge({droppables:this.getDroppables()},this.options.dragOptions)).addEvents({onSnap:function(){b.stop(); +this.clone.setStyle("visibility","visible");this.element.set("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);}.bind(this),onEnter:this.insert.bind(this),onCancel:this.end.bind(this),onComplete:this.end.bind(this)}); +this.clone.inject(this.element,"before");this.drag.start(b);},end:function(){this.drag.detach();this.element.set("opacity",this.opacity);if(this.effect){var b=this.element.getStyles("width","height"),d=this.clone,c=d.computePosition(this.element.getPosition(this.clone.getOffsetParent())); +var a=function(){this.removeEvent("cancel",a);d.destroy();};this.effect.element=d;this.effect.start({top:c.top,left:c.left,width:b.width,height:b.height,opacity:0.25}).addEvent("cancel",a).chain(a); +}else{this.clone.destroy();}this.reset();},reset:function(){this.idle=true;this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Type.isFunction,index:function(d){return d!=null; +}});var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0; +}return(a||a===0)&&a>=0&&a2083){this.fireEvent("error",f);}Request.JSONP.request_map["request_"+b]=function(){this.success(arguments,b);}.bind(this);var a=this.getScript(f).inject(c.injectScript); +this.fireEvent("request",[f,a]);if(c.timeout){this.timeout.delay(c.timeout,this);}return this;},getScript:function(a){if(!this.script){this.script=new Element("script",{type:"text/javascript",async:true,src:a}); +}return this.script;},success:function(b,a){if(!this.running){return;}this.clear().fireEvent("complete",b).fireEvent("success",b).callChain();},cancel:function(){if(this.running){this.clear().fireEvent("cancel"); +}return this;},isRunning:function(){return !!this.running;},clear:function(){this.running=false;if(this.script){this.script.destroy();this.script=null; +}return this;},timeout:function(){if(this.running){this.running=false;this.fireEvent("timeout",[this.script.get("src"),this.script]).fireEvent("failure").cancel(); +}return this;}});Request.JSONP.counter=0;Request.JSONP.request_map={};Request.Queue=new Class({Implements:[Options,Events],Binds:["attach","request","complete","cancel","success","failure","exception"],options:{stopOnFailure:true,autoAdvance:true,concurrent:1,requests:{}},initialize:function(a){var b; +if(a){b=a.requests;delete a.requests;}this.setOptions(a);this.requests={};this.queue=[];this.reqBinders={};if(b){this.addRequests(b);}},addRequest:function(a,b){this.requests[a]=b; +this.attach(a,b);return this;},addRequests:function(a){Object.each(a,function(c,b){this.addRequest(b,c);},this);return this;},getName:function(a){return Object.keyOf(this.requests,a); +},attach:function(a,b){if(b._groupSend){return this;}["request","complete","cancel","success","failure","exception"].each(function(c){if(!this.reqBinders[a]){this.reqBinders[a]={}; +}this.reqBinders[a][c]=function(){this["on"+c.capitalize()].apply(this,[a,b].append(arguments));}.bind(this);b.addEvent(c,this.reqBinders[a][c]);},this); +b._groupSend=b.send;b.send=function(c){this.send(a,c);return b;}.bind(this);return this;},removeRequest:function(b){var a=typeOf(b)=="object"?this.getName(b):b; +if(!a&&typeOf(a)!="string"){return this;}b=this.requests[a];if(!b){return this;}["request","complete","cancel","success","failure","exception"].each(function(c){b.removeEvent(c,this.reqBinders[a][c]); +},this);b.send=b._groupSend;delete b._groupSend;return this;},getRunning:function(){return Object.filter(this.requests,function(a){return a.running;}); +},isRunning:function(){return !!(Object.keys(this.getRunning()).length);},send:function(b,a){var c=function(){this.requests[b]._groupSend(a);this.queue.erase(c); +}.bind(this);c.name=b;if(Object.keys(this.getRunning()).length>=this.options.concurrent||(this.error&&this.options.stopOnFailure)){this.queue.push(c);}else{c(); +}return this;},hasNext:function(a){return(!a)?!!this.queue.length:!!this.queue.filter(function(b){return b.name==a;}).length;},resume:function(){this.error=false; +(this.options.concurrent-Object.keys(this.getRunning()).length).times(this.runNext,this);return this;},runNext:function(a){if(!this.queue.length){return this; +}if(!a){this.queue[0]();}else{var b;this.queue.each(function(c){if(!b&&c.name==a){b=true;c();}});}return this;},runAll:function(){this.queue.each(function(a){a(); +});return this;},clear:function(a){if(!a){this.queue.empty();}else{this.queue=this.queue.map(function(b){if(b.name!=a){return b;}else{return false;}}).filter(function(b){return b; +});}return this;},cancel:function(a){this.requests[a].cancel();return this;},onRequest:function(){this.fireEvent("request",arguments);},onComplete:function(){this.fireEvent("complete",arguments); +if(!this.queue.length){this.fireEvent("end");}},onCancel:function(){if(this.options.autoAdvance&&!this.error){this.runNext();}this.fireEvent("cancel",arguments); +},onSuccess:function(){if(this.options.autoAdvance&&!this.error){this.runNext();}this.fireEvent("success",arguments);},onFailure:function(){this.error=true; +if(!this.options.stopOnFailure&&this.options.autoAdvance){this.runNext();}this.fireEvent("failure",arguments);},onException:function(){this.error=true; +if(!this.options.stopOnFailure&&this.options.autoAdvance){this.runNext();}this.fireEvent("exception",arguments);}});Request.implement({options:{initialDelay:5000,delay:5000,limit:60000},startTimer:function(b){var a=function(){if(!this.running){this.send({data:b}); +}};this.lastDelay=this.options.initialDelay;this.timer=a.delay(this.lastDelay,this);this.completeCheck=function(c){clearTimeout(this.timer);this.lastDelay=(c)?this.options.delay:(this.lastDelay+this.options.delay).min(this.options.limit); +this.timer=a.delay(this.lastDelay,this);};return this.addEvent("complete",this.completeCheck);},stopTimer:function(){clearTimeout(this.timer);return this.removeEvent("complete",this.completeCheck); +}});var Asset={javascript:function(f,c){if(!c){c={};}var a=new Element("script",{src:f,type:"text/javascript"}),g=c.document||document,b=0,d=c.onload||c.onLoad; +var e=d?function(){if(++b==1){d.call(this);}}:function(){};delete c.onload;delete c.onLoad;delete c.document;return a.addEvents({load:e,readystatechange:function(){if(["loaded","complete"].contains(this.readyState)){e.call(this); +}}}).set(c).inject(g.head);},css:function(d,a){if(!a){a={};}var b=new Element("link",{rel:"stylesheet",media:"screen",type:"text/css",href:d});var c=a.onload||a.onLoad,e=a.document||document; +delete a.onload;delete a.onLoad;delete a.document;if(c){b.addEvent("load",c);}return b.set(a).inject(e.head);},image:function(c,b){if(!b){b={};}var d=new Image(),a=document.id(d)||new Element("img"); +["load","abort","error"].each(function(e){var g="on"+e,f="on"+e.capitalize(),h=b[g]||b[f]||function(){};delete b[f];delete b[g];d[g]=function(){if(!d){return; +}if(!a.parentNode){a.width=d.width;a.height=d.height;}d=d.onload=d.onabort=d.onerror=null;h.delay(1,a,a);a.fireEvent(e,a,1);};});d.src=a.src=c;if(d&&d.complete){d.onload.delay(1); +}return a.set(b);},images:function(c,b){c=Array.from(c);var d=function(){},a=0;b=Object.merge({onComplete:d,onProgress:d,onError:d,properties:{}},b);return new Elements(c.map(function(f,e){return Asset.image(f,Object.append(b.properties,{onload:function(){a++; +b.onProgress.call(this,a,e,f);if(a==c.length){b.onComplete();}},onerror:function(){a++;b.onError.call(this,a,e,f);if(a==c.length){b.onComplete();}}})); +}));}};(function(){var a=this.Color=new Type("Color",function(c,d){if(arguments.length>=3){d="rgb";c=Array.slice(arguments,0,3);}else{if(typeof c=="string"){if(c.match(/rgb/)){c=c.rgbToHex().hexToRgb(true); +}else{if(c.match(/hsb/)){c=c.hsbToRgb();}else{c=c.hexToRgb(true);}}}}d=d||"rgb";switch(d){case"hsb":var b=c;c=c.hsbToRgb();c.hsb=b;break;case"hex":c=c.hexToRgb(true); +break;}c.rgb=c.slice(0,3);c.hsb=c.hsb||c.rgbToHsb();c.hex=c.rgbToHex();return Object.append(c,this);});a.implement({mix:function(){var b=Array.slice(arguments); +var d=(typeOf(b.getLast())=="number")?b.pop():50;var c=this.slice();b.each(function(e){e=new a(e);for(var f=0;f<3;f++){c[f]=Math.round((c[f]/100*(100-d))+(e[f]/100*d)); +}});return new a(c,"rgb");},invert:function(){return new a(this.map(function(b){return 255-b;}));},setHue:function(b){return new a([b,this.hsb[1],this.hsb[2]],"hsb"); +},setSaturation:function(b){return new a([this.hsb[0],b,this.hsb[2]],"hsb");},setBrightness:function(b){return new a([this.hsb[0],this.hsb[1],b],"hsb"); +}});this.$RGB=function(e,d,c){return new a([e,d,c],"rgb");};this.$HSB=function(e,d,c){return new a([e,d,c],"hsb");};this.$HEX=function(b){return new a(b,"hex"); +};Array.implement({rgbToHsb:function(){var c=this[0],d=this[1],k=this[2],h=0;var j=Math.max(c,d,k),f=Math.min(c,d,k);var l=j-f;var i=j/255,g=(j!=0)?l/j:0; +if(g!=0){var e=(j-c)/l;var b=(j-d)/l;var m=(j-k)/l;if(c==j){h=m-b;}else{if(d==j){h=2+e-m;}else{h=4+b-e;}}h/=6;if(h<0){h++;}}return[Math.round(h*360),Math.round(g*100),Math.round(i*100)]; +},hsbToRgb:function(){var d=Math.round(this[2]/100*255);if(this[1]==0){return[d,d,d];}else{var b=this[0]%360;var g=b%60;var h=Math.round((this[2]*(100-this[1]))/10000*255); +var e=Math.round((this[2]*(6000-this[1]*g))/600000*255);var c=Math.round((this[2]*(6000-this[1]*(60-g)))/600000*255);switch(Math.floor(b/60)){case 0:return[d,c,h]; +case 1:return[e,d,h];case 2:return[h,d,c];case 3:return[h,e,d];case 4:return[c,h,d];case 5:return[d,h,e];}}return false;}});String.implement({rgbToHsb:function(){var b=this.match(/\d{1,3}/g); +return(b)?b.rgbToHsb():null;},hsbToRgb:function(){var b=this.match(/\d{1,3}/g);return(b)?b.hsbToRgb():null;}});})();(function(){this.Group=new Class({initialize:function(){this.instances=Array.flatten(arguments); +this.events={};this.checker={};},addEvent:function(b,a){this.checker[b]=this.checker[b]||{};this.events[b]=this.events[b]||[];if(this.events[b].contains(a)){return false; +}else{this.events[b].push(a);}this.instances.each(function(c,d){c.addEvent(b,this.check.pass([b,c,d],this));},this);return this;},check:function(c,a,b){this.checker[c][b]=true; +var d=this.instances.every(function(f,e){return this.checker[c][e]||false;},this);if(!d){return;}this.checker[c]={};this.events[c].each(function(e){e.call(this,this.instances,a); +},this);}});})();Hash.Cookie=new Class({Extends:Cookie,options:{autoSave:true},initialize:function(b,a){this.parent(b,a);this.load();},save:function(){var a=JSON.encode(this.hash); +if(!a||a.length>4096){return false;}if(a=="{}"){this.dispose();}else{this.write(a);}return true;},load:function(){this.hash=new Hash(JSON.decode(this.read(),true)); +return this;}});Hash.each(Hash.prototype,function(b,a){if(typeof b=="function"){Hash.Cookie.implement(a,function(){var c=b.apply(this.hash,arguments);if(this.options.autoSave){this.save(); +}return c;});}});(function(){var a=this.Table=function(){this.length=0;var c=[],b=[];this.set=function(e,g){var d=c.indexOf(e);if(d==-1){var f=c.length; +c[f]=e;b[f]=g;this.length++;}else{b[d]=g;}return this;};this.get=function(e){var d=c.indexOf(e);return(d==-1)?null:b[d];};this.erase=function(e){var d=c.indexOf(e); +if(d!=-1){this.length--;c.splice(d,1);return b.splice(d,1)[0];}return null;};this.each=this.forEach=function(f,g){for(var e=0,d=this.length;e1?$$(a):a.length?document.id(a[0]):false;},setHeaders:function(a){this.set("headers",a); +return this;},setFooters:function(a){this.set("footers",a);return this;},push:function(f,c,e,a,b){if(typeOf(f)=="element"&&f.get("tag")=="tr"){f.inject(e||this.body,b); +return{tr:f,tds:f.getChildren("td")};}var d=f.map(function(i){var j=new Element(a||"td",i?i.properties:{}),h=(i?i.content:"")||i,g=typeOf(h);if(["element","array","collection","elements"].contains(g)){j.adopt(h); +}else{j.set("html",h);}return j;});return{tr:new Element("tr",c).inject(e||this.body,b).adopt(d),tds:d};}});["adopt","inject","wraps","grab","replaces","dispose"].each(function(a){HtmlTable.implement(a,function(){this.element[a].apply(this.element,arguments); +return this;});});HtmlTable=Class.refactor(HtmlTable,{options:{classZebra:"table-tr-odd",zebra:true},initialize:function(){this.previous.apply(this,arguments); +if(this.occluded){return this.occluded;}if(this.options.zebra){this.updateZebras();}},updateZebras:function(){Array.each(this.body.rows,this.zebra,this); +},setRowStyle:function(b,a){if(this.previous){this.previous(b,a);}this.zebra(b,a);},zebra:function(b,a){return b[((a%2)?"remove":"add")+"Class"](this.options.classZebra); +},push:function(){var a=this.previous.apply(this,arguments);if(this.options.zebra){this.updateZebras();}return a;}});HtmlTable=Class.refactor(HtmlTable,{options:{sortIndex:0,sortReverse:false,parsers:[],defaultParser:"string",classSortable:"table-sortable",classHeadSort:"table-th-sort",classHeadSortRev:"table-th-sort-rev",classNoSort:"table-th-nosort",classGroupHead:"table-tr-group-head",classGroup:"table-tr-group",classCellSort:"table-td-sort",classSortSpan:"table-th-sort-span",sortable:false,thSelector:"th"},initialize:function(){this.previous.apply(this,arguments); +if(this.occluded){return this.occluded;}this.sorted={index:null,dir:1};this.bound={headClick:this.headClick.bind(this)};this.sortSpans=new Elements();if(this.options.sortable){this.enableSort(); +if(this.options.sortIndex!=null){this.sort(this.options.sortIndex,this.options.sortReverse);}}},attachSorts:function(a){this.detachSorts();if(a!==false){this.element.addEvent("click:relay("+this.options.thSelector+")",this.bound.headClick); +}},detachSorts:function(){this.element.removeEvents("click:relay("+this.options.thSelector+")");},setHeaders:function(){this.previous.apply(this,arguments); +if(this.sortEnabled){this.setParsers();}},setParsers:function(){this.parsers=this.detectParsers();},detectParsers:function(){return this.head&&this.head.getElements(this.options.thSelector).flatten().map(this.detectParser,this); +},detectParser:function(a,b){if(a.hasClass(this.options.classNoSort)||a.retrieve("htmltable-parser")){return a.retrieve("htmltable-parser");}var c=new Element("div"); +c.adopt(a.childNodes).inject(a);var f=new Element("span",{"class":this.options.classSortSpan}).inject(c,"top");this.sortSpans.push(f);var g=this.options.parsers[b],e=this.body.rows,d; +switch(typeOf(g)){case"function":g={convert:g};d=true;break;case"string":g=g;d=true;break;}if(!d){HtmlTable.ParserPriority.some(function(k){var o=HtmlTable.Parsers[k],m=o.match; +if(!m){return false;}for(var n=0,l=e.length;nc){b[f].position--;}}}},setRowStyle:function(b,a){this.previous(b,a);b.cells[this.sorted.index].addClass(this.options.classCellSort); +},setGroupSort:function(b,c,a){if(b==a.value){c.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);}else{c.removeClass(this.options.classGroup).addClass(this.options.classGroupHead); +}return a.value;},getParser:function(){var a=this.parsers[this.sorted.index];return typeOf(a)=="string"?HtmlTable.Parsers[a]:a;},sort:function(c,b,e){if(!this.head){return; +}if(!e){this.clearSort();this.setSortedState(c,b);this.setHeadSort(true);}var f=this.getParser();if(!f){return;}var a;if(!Browser.ie){a=this.body.getParent(); +this.body.dispose();}var d=this.parseData(f).sort(function(h,g){if(h.value===g.value){return 0;}return h.value>g.value?1:-1;});if(this.sorted.reverse==(f==HtmlTable.Parsers["input-checked"])){d.reverse(true); +}this.setRowSort(d,e);if(a){a.grab(this.body);}this.fireEvent("stateChanged");return this.fireEvent("sort",[this.body,this.sorted.index]);},parseData:function(a){return Array.map(this.body.rows,function(d,b){var c=a.convert.call(document.id(d.cells[this.sorted.index])); +return{position:b,value:c};},this);},clearSort:function(){this.setHeadSort(false);this.body.getElements("td").removeClass(this.options.classCellSort);},reSort:function(){if(this.sortEnabled){this.sort.call(this,this.sorted.index,this.sorted.reverse); +}return this;},enableSort:function(){this.element.addClass(this.options.classSortable);this.attachSorts(true);this.setParsers();this.sortEnabled=true;return this; +},disableSort:function(){this.element.removeClass(this.options.classSortable);this.attachSorts(false);this.sortSpans.each(function(a){a.destroy();});this.sortSpans.empty(); +this.sortEnabled=false;return this;}});HtmlTable.ParserPriority=["date","input-checked","input-value","float","number"];HtmlTable.Parsers={date:{match:/^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,convert:function(){var a=Date.parse(this.get("text").stripTags()); +return(typeOf(a)=="date")?a.format("db"):"";},type:"date"},"input-checked":{match:/ type="(radio|checkbox)" /,convert:function(){return this.getElement("input").checked; +}},"input-value":{match:/=b.length){a=null;}return a;},attachSelects:function(d){d=d!=null?d:true; +var g=d?"addEvents":"removeEvents";this.element[g]({mouseleave:this.bound.mouseleave,click:this.bound.activateKeyboard});this.body[g]({"click:relay(tr)":this.bound.clickRow,"contextmenu:relay(tr)":this.bound.clickRow}); +if(this.options.useKeyboard||this.keyboard){if(!this.keyboard){this.keyboard=new Keyboard();}if(!this.selectKeysDefined){this.selectKeysDefined=true;var f,e; +var c=function(i){var h=function(j){clearTimeout(f);j.preventDefault();var k=this.body.rows[this.getRowByOffset(i)];if(j.shift&&k&&this.isSelected(k)){this.deselectRow(this.focused); +this.focused=k;}else{if(k&&(!this.options.allowMultiSelect||!j.shift)){this.selectNone();}this.shiftFocus(i,j);}if(e){f=h.delay(100,this,j);}else{f=(function(){e=true; +h(j);}).delay(400);}}.bind(this);return h;}.bind(this);var b=function(){clearTimeout(f);e=false;};this.keyboard.addEvents({"keydown:shift+up":c(-1),"keydown:shift+down":c(1),"keyup:shift+up":b,"keyup:shift+down":b,"keyup:up":b,"keyup:down":b}); +var a="";if(this.options.allowMultiSelect&&this.options.shiftForMultiSelect&&this.options.useKeyboard){a=" (Shift multi-selects).";}this.keyboard.addShortcuts({"Select Previous Row":{keys:"up",shortcut:"up arrow",handler:c(-1),description:"Select the previous row in the table."+a},"Select Next Row":{keys:"down",shortcut:"down arrow",handler:c(1),description:"Select the next row in the table."+a}}); +}this.keyboard[d?"activate":"deactivate"]();}this.updateSelects();},mouseleave:function(){if(this.hovered){this.leaveRow(this.hovered);}}});var Scroller=new Class({Implements:[Events,Options],options:{area:20,velocity:1,onChange:function(a,b){this.element.scrollTo(a,b); +},fps:50},initialize:function(b,a){this.setOptions(a);this.element=document.id(b);this.docBody=document.id(this.element.getDocument().body);this.listener=(typeOf(this.element)!="element")?this.docBody:this.element; +this.timer=null;this.bound={attach:this.attach.bind(this),detach:this.detach.bind(this),getCoords:this.getCoords.bind(this)};},start:function(){this.listener.addEvents({mouseover:this.bound.attach,mouseleave:this.bound.detach}); +return this;},stop:function(){this.listener.removeEvents({mouseover:this.bound.attach,mouseleave:this.bound.detach});this.detach();this.timer=clearInterval(this.timer); +return this;},attach:function(){this.listener.addEvent("mousemove",this.bound.getCoords);},detach:function(){this.listener.removeEvent("mousemove",this.bound.getCoords); +this.timer=clearInterval(this.timer);},getCoords:function(a){this.page=(this.listener.get("tag")=="body")?a.client:a.page;if(!this.timer){this.timer=this.scroll.periodical(Math.round(1000/this.options.fps),this); +}},scroll:function(){var c=this.element.getSize(),a=this.element.getScroll(),h=this.element!=this.docBody?this.element.getOffsets():{x:0,y:0},d=this.element.getScrollSize(),g={x:0,y:0},e=this.options.area.top||this.options.area,b=this.options.area.bottom||this.options.area; +for(var f in this.page){if(this.page[f]<(e+h[f])&&a[f]!=0){g[f]=(this.page[f]-e-h[f])*this.options.velocity;}else{if(this.page[f]+b>(c[f]+h[f])&&a[f]+c[f]!=d[f]){g[f]=(this.page[f]-c[f]+b-h[f])*this.options.velocity; +}}g[f]=g[f].round();}if(g.y||g.x){this.fireEvent("change",[a.x+g.x,a.y+g.y]);}}});(function(){var a=function(c,b){return(c)?(typeOf(c)=="function"?c(b):b.get(c)):""; +};this.Tips=new Class({Implements:[Events,Options],options:{onShow:function(){this.tip.setStyle("display","block");},onHide:function(){this.tip.setStyle("display","none"); +},title:"title",text:function(b){return b.get("rel")||b.get("href");},showDelay:100,hideDelay:100,className:"tip-wrap",offset:{x:16,y:16},windowPadding:{x:0,y:0},fixed:false},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,elements:function(c){return c!=null; +}});this.setOptions(b.options);if(b.elements){this.attach(b.elements);}this.container=new Element("div",{"class":"tip"});},toElement:function(){if(this.tip){return this.tip; +}this.tip=new Element("div",{"class":this.options.className,styles:{position:"absolute",top:0,left:0}}).adopt(new Element("div",{"class":"tip-top"}),this.container,new Element("div",{"class":"tip-bottom"})); +return this.tip;},attach:function(b){$$(b).each(function(d){var f=a(this.options.title,d),e=a(this.options.text,d);d.set("title","").store("tip:native",f).retrieve("tip:title",f); +d.retrieve("tip:text",e);this.fireEvent("attach",[d]);var c=["enter","leave"];if(!this.options.fixed){c.push("move");}c.each(function(h){var g=d.retrieve("tip:"+h); +if(!g){g=function(i){this["element"+h.capitalize()].apply(this,[i,d]);}.bind(this);}d.store("tip:"+h,g).addEvent("mouse"+h,g);},this);},this);return this; +},detach:function(b){$$(b).each(function(d){["enter","leave","move"].each(function(e){d.removeEvent("mouse"+e,d.retrieve("tip:"+e)).eliminate("tip:"+e); +});this.fireEvent("detach",[d]);if(this.options.title=="title"){var c=d.retrieve("tip:native");if(c){d.set("title",c);}}},this);return this;},elementEnter:function(c,b){clearTimeout(this.timer); +this.timer=(function(){this.container.empty();["title","text"].each(function(e){var d=b.retrieve("tip:"+e);var f=this["_"+e+"Element"]=new Element("div",{"class":"tip-"+e}).inject(this.container); +if(d){this.fill(f,d);}},this);this.show(b);this.position((this.options.fixed)?{page:b.getPosition()}:c);}).delay(this.options.showDelay,this);},elementLeave:function(c,b){clearTimeout(this.timer); +this.timer=this.hide.delay(this.options.hideDelay,this,b);this.fireForParent(c,b);},setTitle:function(b){if(this._titleElement){this._titleElement.empty(); +this.fill(this._titleElement,b);}return this;},setText:function(b){if(this._textElement){this._textElement.empty();this.fill(this._textElement,b);}return this; +},fireForParent:function(c,b){b=b.getParent();if(!b||b==document.body){return;}if(b.retrieve("tip:enter")){b.fireEvent("mouseenter",c);}else{this.fireForParent(c,b); +}},elementMove:function(c,b){this.position(c);},position:function(f){if(!this.tip){document.id(this);}var c=window.getSize(),b=window.getScroll(),g={x:this.tip.offsetWidth,y:this.tip.offsetHeight},d={x:"left",y:"top"},e={y:false,x2:false,y2:false,x:false},h={}; +for(var i in d){h[d[i]]=f.page[i]+this.options.offset[i];if(h[d[i]]<0){e[i]=true;}if((h[d[i]]+g[i]-b[i])>c[i]-this.options.windowPadding[i]){h[d[i]]=f.page[i]-this.options.offset[i]-g[i]; +e[i+"2"]=true;}}this.fireEvent("bound",e);this.tip.setStyles(h);},fill:function(b,c){if(typeof c=="string"){b.set("html",c);}else{b.adopt(c);}},show:function(b){if(!this.tip){document.id(this); +}if(!this.tip.getParent()){this.tip.inject(document.body);}this.fireEvent("show",[this.tip,b]);},hide:function(b){if(!this.tip){document.id(this);}this.fireEvent("hide",[this.tip,b]); +}});})(); \ No newline at end of file diff --git a/Demos/mootools-more.js b/Demos/mootools-more.js deleted file mode 100644 index 0d8df04..0000000 --- a/Demos/mootools-more.js +++ /dev/null @@ -1,13543 +0,0 @@ -/* ---- - -script: More.js - -name: More - -description: MooTools More - -license: MIT-style license - -authors: - - Guillermo Rauch - - Thomas Aylott - - Scott Kyle - - Arian Stolwijk - - Tim Wienk - - Christoph Pojer - - Aaron Newton - - Jacob Thornton - -requires: - - Core/MooTools - -provides: [MooTools.More] - -... -*/ - -MooTools.More = { - 'version': '1.3.2.2dev', - 'build': 'b6dcbdcc821fbf62404ef43f332f5c594ac76a3a' -}; - - -/* ---- - -name: Events.Pseudos - -description: Adds the functionality to add pseudo events - -license: MIT-style license - -authors: - - Arian Stolwijk - -requires: [Core/Class.Extras, Core/Slick.Parser, More/MooTools.More] - -provides: [Events.Pseudos] - -... -*/ - -Events.Pseudos = function(pseudos, addEvent, removeEvent){ - - var storeKey = 'monitorEvents:'; - - var storageOf = function(object){ - return { - store: object.store ? function(key, value){ - object.store(storeKey + key, value); - } : function(key, value){ - (object.$monitorEvents || (object.$monitorEvents = {}))[key] = value; - }, - retrieve: object.retrieve ? function(key, dflt){ - return object.retrieve(storeKey + key, dflt); - } : function(key, dflt){ - if (!object.$monitorEvents) return dflt; - return object.$monitorEvents[key] || dflt; - } - }; - }; - - var splitType = function(type){ - if (type.indexOf(':') == -1 || !pseudos) return null; - - var parsed = Slick.parse(type).expressions[0][0], - parsedPseudos = parsed.pseudos, - l = parsedPseudos.length, - splits = []; - - while (l--) if (pseudos[parsedPseudos[l].key]){ - splits.push({ - event: parsed.tag, - value: parsedPseudos[l].value, - pseudo: parsedPseudos[l].key, - original: type - }); - } - - return splits.length ? splits : null; - }; - - var mergePseudoOptions = function(split){ - return Object.merge.apply(this, split.map(function(item){ - return pseudos[item.pseudo].options || {}; - })); - }; - - return { - - addEvent: function(type, fn, internal){ - var split = splitType(type); - if (!split) return addEvent.call(this, type, fn, internal); - - var storage = storageOf(this), - events = storage.retrieve(type, []), - eventType = split[0].event, - options = mergePseudoOptions(split), - stack = fn, - eventOptions = options[eventType] || {}, - args = Array.slice(arguments, 2), - self = this, - monitor; - - if (eventOptions.args) args.append(Array.from(eventOptions.args)); - if (eventOptions.base) eventType = eventOptions.base; - if (eventOptions.onAdd) eventOptions.onAdd(this); - - split.each(function(item){ - var stackFn = stack; - stack = function(){ - (eventOptions.listener || pseudos[item.pseudo].listener).call(self, item, stackFn, arguments, monitor, options); - }; - }); - monitor = stack.bind(this); - - events.include({event: fn, monitor: monitor}); - storage.store(type, events); - - addEvent.apply(this, [type, fn].concat(args)); - return addEvent.apply(this, [eventType, monitor].concat(args)); - }, - - removeEvent: function(type, fn){ - var split = splitType(type); - if (!split) return removeEvent.call(this, type, fn); - - var storage = storageOf(this), - events = storage.retrieve(type); - if (!events) return this; - - var eventType = split[0].event, - options = mergePseudoOptions(split), - eventOptions = options[eventType] || {}, - args = Array.slice(arguments, 2); - - if (eventOptions.args) args.append(Array.from(eventOptions.args)); - if (eventOptions.base) eventType = eventOptions.base; - if (eventOptions.onRemove) eventOptions.onRemove(this); - - removeEvent.apply(this, [type, fn].concat(args)); - events.each(function(monitor, i){ - if (!fn || monitor.event == fn) removeEvent.apply(this, [eventType, monitor.monitor].concat(args)); - delete events[i]; - }, this); - - storage.store(type, events); - return this; - } - - }; - -}; - -(function(){ - -var pseudos = { - - once: { - listener: function(split, fn, args, monitor){ - fn.apply(this, args); - this.removeEvent(split.event, monitor) - .removeEvent(split.original, fn); - } - }, - - throttle: { - listener: function(split, fn, args){ - if (!fn._throttled){ - fn.apply(this, args); - fn._throttled = setTimeout(function(){ - fn._throttled = false; - }, split.value || 250); - } - } - }, - - pause: { - listener: function(split, fn, args){ - clearTimeout(fn._pause); - fn._pause = fn.delay(split.value || 250, this, args); - } - } - -}; - -Events.definePseudo = function(key, listener){ - pseudos[key] = Type.isFunction(listener) ? {listener: listener} : listener; - return this; -}; - -Events.lookupPseudo = function(key){ - return pseudos[key]; -}; - -var proto = Events.prototype; -Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); - -['Request', 'Fx'].each(function(klass){ - if (this[klass]) this[klass].implement(Events.prototype); -}); - -})(); - - -/* ---- - -script: Class.Refactor.js - -name: Class.Refactor - -description: Extends a class onto itself with new property, preserving any items attached to the class's namespace. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Class - - /MooTools.More - -# Some modules declare themselves dependent on Class.Refactor -provides: [Class.refactor, Class.Refactor] - -... -*/ - -Class.refactor = function(original, refactors){ - - Object.each(refactors, function(item, name){ - var origin = original.prototype[name]; - origin = (origin && origin.$origin) || origin || function(){}; - original.implement(name, (typeof item == 'function') ? function(){ - var old = this.previous; - this.previous = origin; - var value = item.apply(this, arguments); - this.previous = old; - return value; - } : item); - }); - - return original; - -}; - - -/* ---- - -script: Class.Binds.js - -name: Class.Binds - -description: Automagically binds specified methods in a class to the instance of the class. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Class - - /MooTools.More - -provides: [Class.Binds] - -... -*/ - -Class.Mutators.Binds = function(binds){ - if (!this.prototype.initialize) this.implement('initialize', function(){}); - return Array.from(binds).concat(this.prototype.Binds || []); -}; - -Class.Mutators.initialize = function(initialize){ - return function(){ - Array.from(this.Binds).each(function(name){ - var original = this[name]; - if (original) this[name] = original.bind(this); - }, this); - return initialize.apply(this, arguments); - }; -}; - - -/* ---- - -script: Class.Occlude.js - -name: Class.Occlude - -description: Prevents a class from being applied to a DOM element twice. - -license: MIT-style license. - -authors: - - Aaron Newton - -requires: - - Core/Class - - Core/Element - - /MooTools.More - -provides: [Class.Occlude] - -... -*/ - -Class.Occlude = new Class({ - - occlude: function(property, element){ - element = document.id(element || this.element); - var instance = element.retrieve(property || this.property); - if (instance && !this.occluded) - return (this.occluded = instance); - - this.occluded = false; - element.store(property || this.property, this); - return this.occluded; - } - -}); - - -/* ---- - -script: Chain.Wait.js - -name: Chain.Wait - -description: value, Adds a method to inject pauses between chained events. - -license: MIT-style license. - -authors: - - Aaron Newton - -requires: - - Core/Chain - - Core/Element - - Core/Fx - - /MooTools.More - -provides: [Chain.Wait] - -... -*/ - -(function(){ - - var wait = { - wait: function(duration){ - return this.chain(function(){ - this.callChain.delay(duration == null ? 500 : duration, this); - return this; - }.bind(this)); - } - }; - - Chain.implement(wait); - - if (this.Fx) Fx.implement(wait); - - if (this.Element && Element.implement && this.Fx){ - Element.implement({ - - chains: function(effects){ - Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){ - effect = this.get(effect); - if (!effect) return; - effect.setOptions({ - link:'chain' - }); - }, this); - return this; - }, - - pauseFx: function(duration, effect){ - this.chains(effect).get(effect || 'tween').wait(duration); - return this; - } - - }); - } - -})(); - - -/* ---- - -script: Array.Extras.js - -name: Array.Extras - -description: Extends the Array native object to include useful methods to work with arrays. - -license: MIT-style license - -authors: - - Christoph Pojer - - Sebastian Markbåge - -requires: - - Core/Array - - MooTools.More - -provides: [Array.Extras] - -... -*/ - -(function(nil){ - -Array.implement({ - - min: function(){ - return Math.min.apply(null, this); - }, - - max: function(){ - return Math.max.apply(null, this); - }, - - average: function(){ - return this.length ? this.sum() / this.length : 0; - }, - - sum: function(){ - var result = 0, l = this.length; - if (l){ - while (l--) result += this[l]; - } - return result; - }, - - unique: function(){ - return [].combine(this); - }, - - shuffle: function(){ - for (var i = this.length; i && --i;){ - var temp = this[i], r = Math.floor(Math.random() * ( i + 1 )); - this[i] = this[r]; - this[r] = temp; - } - return this; - }, - - reduce: function(fn, value){ - for (var i = 0, l = this.length; i < l; i++){ - if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); - } - return value; - }, - - reduceRight: function(fn, value){ - var i = this.length; - while (i--){ - if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); - } - return value; - } - -}); - -})(); - - -/* ---- - -script: Object.Extras.js - -name: Object.Extras - -description: Extra Object generics, like getFromPath which allows a path notation to child elements. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Object - - /MooTools.More - -provides: [Object.Extras] - -... -*/ - -(function(){ - -var defined = function(value){ - return value != null; -}; - -var hasOwnProperty = Object.prototype.hasOwnProperty; - -Object.extend({ - - getFromPath: function(source, parts){ - if (typeof parts == 'string') parts = parts.split('.'); - for (var i = 0, l = parts.length; i < l; i++){ - if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]]; - else return null; - } - return source; - }, - - cleanValues: function(object, method){ - method = method || defined; - for (var key in object) if (!method(object[key])){ - delete object[key]; - } - return object; - }, - - erase: function(object, key){ - if (hasOwnProperty.call(object, key)) delete object[key]; - return object; - }, - - run: function(object){ - var args = Array.slice(arguments, 1); - for (var key in object) if (object[key].apply){ - object[key].apply(object, args); - } - return object; - } - -}); - -})(); - - -/* ---- - -script: Locale.js - -name: Locale - -description: Provides methods for localization. - -license: MIT-style license - -authors: - - Aaron Newton - - Arian Stolwijk - -requires: - - Core/Events - - /Object.Extras - - /MooTools.More - -provides: [Locale, Lang] - -... -*/ - -(function(){ - -var current = null, - locales = {}, - inherits = {}; - -var getSet = function(set){ - if (instanceOf(set, Locale.Set)) return set; - else return locales[set]; -}; - -var Locale = this.Locale = { - - define: function(locale, set, key, value){ - var name; - if (instanceOf(locale, Locale.Set)){ - name = locale.name; - if (name) locales[name] = locale; - } else { - name = locale; - if (!locales[name]) locales[name] = new Locale.Set(name); - locale = locales[name]; - } - - if (set) locale.define(set, key, value); - - /*<1.2compat>*/ - if (set == 'cascade') return Locale.inherit(name, key); - /**/ - - if (!current) current = locale; - - return locale; - }, - - use: function(locale){ - locale = getSet(locale); - - if (locale){ - current = locale; - - this.fireEvent('change', locale); - - /*<1.2compat>*/ - this.fireEvent('langChange', locale.name); - /**/ - } - - return this; - }, - - getCurrent: function(){ - return current; - }, - - get: function(key, args){ - return (current) ? current.get(key, args) : ''; - }, - - inherit: function(locale, inherits, set){ - locale = getSet(locale); - - if (locale) locale.inherit(inherits, set); - return this; - }, - - list: function(){ - return Object.keys(locales); - } - -}; - -Object.append(Locale, new Events); - -Locale.Set = new Class({ - - sets: {}, - - inherits: { - locales: [], - sets: {} - }, - - initialize: function(name){ - this.name = name || ''; - }, - - define: function(set, key, value){ - var defineData = this.sets[set]; - if (!defineData) defineData = {}; - - if (key){ - if (typeOf(key) == 'object') defineData = Object.merge(defineData, key); - else defineData[key] = value; - } - this.sets[set] = defineData; - - return this; - }, - - get: function(key, args, _base){ - var value = Object.getFromPath(this.sets, key); - if (value != null){ - var type = typeOf(value); - if (type == 'function') value = value.apply(null, Array.from(args)); - else if (type == 'object') value = Object.clone(value); - return value; - } - - // get value of inherited locales - var index = key.indexOf('.'), - set = index < 0 ? key : key.substr(0, index), - names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US'); - if (!_base) _base = []; - - for (var i = 0, l = names.length; i < l; i++){ - if (_base.contains(names[i])) continue; - _base.include(names[i]); - - var locale = locales[names[i]]; - if (!locale) continue; - - value = locale.get(key, args, _base); - if (value != null) return value; - } - - return ''; - }, - - inherit: function(names, set){ - names = Array.from(names); - - if (set && !this.inherits.sets[set]) this.inherits.sets[set] = []; - - var l = names.length; - while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]); - - return this; - } - -}); - -/*<1.2compat>*/ -var lang = MooTools.lang = {}; - -Object.append(lang, Locale, { - setLanguage: Locale.use, - getCurrentLanguage: function(){ - var current = Locale.getCurrent(); - return (current) ? current.name : null; - }, - set: function(){ - Locale.define.apply(this, arguments); - return this; - }, - get: function(set, key, args){ - if (key) set += '.' + key; - return Locale.get(set, args); - } -}); -/**/ - -})(); - - -/* ---- - -name: Locale.en-US.Date - -description: Date messages for US English. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - /Locale - -provides: [Locale.en-US.Date] - -... -*/ - -Locale.define('en-US', 'Date', { - - months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], - - // Culture's date order: MM/DD/YYYY - dateOrder: ['month', 'date', 'year'], - shortDate: '%m/%d/%Y', - shortTime: '%I:%M%p', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 0, - - // Date.Extras - ordinal: function(dayOfMonth){ - // 1st, 2nd, 3rd, etc. - return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)]; - }, - - lessThanMinuteAgo: 'less than a minute ago', - minuteAgo: 'about a minute ago', - minutesAgo: '{delta} minutes ago', - hourAgo: 'about an hour ago', - hoursAgo: 'about {delta} hours ago', - dayAgo: '1 day ago', - daysAgo: '{delta} days ago', - weekAgo: '1 week ago', - weeksAgo: '{delta} weeks ago', - monthAgo: '1 month ago', - monthsAgo: '{delta} months ago', - yearAgo: '1 year ago', - yearsAgo: '{delta} years ago', - - lessThanMinuteUntil: 'less than a minute from now', - minuteUntil: 'about a minute from now', - minutesUntil: '{delta} minutes from now', - hourUntil: 'about an hour from now', - hoursUntil: 'about {delta} hours from now', - dayUntil: '1 day from now', - daysUntil: '{delta} days from now', - weekUntil: '1 week from now', - weeksUntil: '{delta} weeks from now', - monthUntil: '1 month from now', - monthsUntil: '{delta} months from now', - yearUntil: '1 year from now', - yearsUntil: '{delta} years from now' - -}); - - -/* ---- - -script: Date.js - -name: Date - -description: Extends the Date native object to include methods useful in managing dates. - -license: MIT-style license - -authors: - - Aaron Newton - - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/ - - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de - - Scott Kyle - scott [at] appden.com; http://appden.com - -requires: - - Core/Array - - Core/String - - Core/Number - - MooTools.More - - Locale - - Locale.en-US.Date - -provides: [Date] - -... -*/ - -(function(){ - -var Date = this.Date; - -var DateMethods = Date.Methods = { - ms: 'Milliseconds', - year: 'FullYear', - min: 'Minutes', - mo: 'Month', - sec: 'Seconds', - hr: 'Hours' -}; - -['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset', - 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear', - 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){ - Date.Methods[method.toLowerCase()] = method; -}); - -var pad = function(n, digits, string){ - if (digits == 1) return n; - return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n; -}; - -Date.implement({ - - set: function(prop, value){ - prop = prop.toLowerCase(); - var method = DateMethods[prop] && 'set' + DateMethods[prop]; - if (method && this[method]) this[method](value); - return this; - }.overloadSetter(), - - get: function(prop){ - prop = prop.toLowerCase(); - var method = DateMethods[prop] && 'get' + DateMethods[prop]; - if (method && this[method]) return this[method](); - return null; - }.overloadGetter(), - - clone: function(){ - return new Date(this.get('time')); - }, - - increment: function(interval, times){ - interval = interval || 'day'; - times = times != null ? times : 1; - - switch (interval){ - case 'year': - return this.increment('month', times * 12); - case 'month': - var d = this.get('date'); - this.set('date', 1).set('mo', this.get('mo') + times); - return this.set('date', d.min(this.get('lastdayofmonth'))); - case 'week': - return this.increment('day', times * 7); - case 'day': - return this.set('date', this.get('date') + times); - } - - if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval'); - - return this.set('time', this.get('time') + times * Date.units[interval]()); - }, - - decrement: function(interval, times){ - return this.increment(interval, -1 * (times != null ? times : 1)); - }, - - isLeapYear: function(){ - return Date.isLeapYear(this.get('year')); - }, - - clearTime: function(){ - return this.set({hr: 0, min: 0, sec: 0, ms: 0}); - }, - - diff: function(date, resolution){ - if (typeOf(date) == 'string') date = Date.parse(date); - - return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month - }, - - getLastDayOfMonth: function(){ - return Date.daysInMonth(this.get('mo'), this.get('year')); - }, - - getDayOfYear: function(){ - return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) - - Date.UTC(this.get('year'), 0, 1)) / Date.units.day(); - }, - - setDay: function(day, firstDayOfWeek){ - if (firstDayOfWeek == null){ - firstDayOfWeek = Date.getMsg('firstDayOfWeek'); - if (firstDayOfWeek === '') firstDayOfWeek = 1; - } - - day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7; - var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7; - - return this.increment('day', day - currentDay); - }, - - getWeek: function(firstDayOfWeek){ - if (firstDayOfWeek == null){ - firstDayOfWeek = Date.getMsg('firstDayOfWeek'); - if (firstDayOfWeek === '') firstDayOfWeek = 1; - } - - var date = this, - dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7, - dividend = 0, - firstDayOfYear; - - if (firstDayOfWeek == 1){ - // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week) - var month = date.get('month'), - startOfWeek = date.get('date') - dayOfWeek; - - if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year - - if (month == 0 && startOfWeek < -2){ - // Use a date from last year to determine the week - date = new Date(date).decrement('day', dayOfWeek); - dayOfWeek = 0; - } - - firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7; - if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1 - } else { - // In other cultures the first week of the year is always week 1 and the last week always 53 or 54. - // Days in the same week can have a different weeknumber if the week spreads across two years. - firstDayOfYear = new Date(date.get('year'), 0, 1).get('day'); - } - - dividend += date.get('dayofyear'); - dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week - dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week - - return (dividend / 7); - }, - - getOrdinal: function(day){ - return Date.getMsg('ordinal', day || this.get('date')); - }, - - getTimezone: function(){ - return this.toString() - .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1') - .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3'); - }, - - getGMTOffset: function(){ - var off = this.get('timezoneOffset'); - return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2); - }, - - setAMPM: function(ampm){ - ampm = ampm.toUpperCase(); - var hr = this.get('hr'); - if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12); - else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12); - return this; - }, - - getAMPM: function(){ - return (this.get('hr') < 12) ? 'AM' : 'PM'; - }, - - parse: function(str){ - this.set('time', Date.parse(str)); - return this; - }, - - isValid: function(date){ - return !isNaN((date || this).valueOf()); - }, - - format: function(f){ - if (!this.isValid()) return 'invalid date'; - if (!f) f = '%x %X'; - - var formatLower = f.toLowerCase(); - if (formatters[formatLower]) return formatters[formatLower](this); // it's a formatter! - f = formats[formatLower] || f; // replace short-hand with actual format - - var d = this; - return f.replace(/%([a-z%])/gi, - function($0, $1){ - switch ($1){ - case 'a': return Date.getMsg('days_abbr')[d.get('day')]; - case 'A': return Date.getMsg('days')[d.get('day')]; - case 'b': return Date.getMsg('months_abbr')[d.get('month')]; - case 'B': return Date.getMsg('months')[d.get('month')]; - case 'c': return d.format('%a %b %d %H:%M:%S %Y'); - case 'd': return pad(d.get('date'), 2); - case 'e': return pad(d.get('date'), 2, ' '); - case 'H': return pad(d.get('hr'), 2); - case 'I': return pad((d.get('hr') % 12) || 12, 2); - case 'j': return pad(d.get('dayofyear'), 3); - case 'k': return pad(d.get('hr'), 2, ' '); - case 'l': return pad((d.get('hr') % 12) || 12, 2, ' '); - case 'L': return pad(d.get('ms'), 3); - case 'm': return pad((d.get('mo') + 1), 2); - case 'M': return pad(d.get('min'), 2); - case 'o': return d.get('ordinal'); - case 'p': return Date.getMsg(d.get('ampm')); - case 's': return Math.round(d / 1000); - case 'S': return pad(d.get('seconds'), 2); - case 'T': return d.format('%H:%M:%S'); - case 'U': return pad(d.get('week'), 2); - case 'w': return d.get('day'); - case 'x': return d.format(Date.getMsg('shortDate')); - case 'X': return d.format(Date.getMsg('shortTime')); - case 'y': return d.get('year').toString().substr(2); - case 'Y': return d.get('year'); - case 'z': return d.get('GMTOffset'); - case 'Z': return d.get('Timezone'); - } - return $1; - } - ); - }, - - toISOString: function(){ - return this.format('iso8601'); - } - -}).alias({ - toJSON: 'toISOString', - compare: 'diff', - strftime: 'format' -}); - -var formats = { - db: '%Y-%m-%d %H:%M:%S', - compact: '%Y%m%dT%H%M%S', - 'short': '%d %b %H:%M', - 'long': '%B %d, %Y %H:%M' -}; - -// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized -var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], - rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - -var formatters = { - rfc822: function(date){ - return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z'); - }, - rfc2822: function(date){ - return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z'); - }, - iso8601: function(date){ - return ( - date.getUTCFullYear() + '-' + - pad(date.getUTCMonth() + 1, 2) + '-' + - pad(date.getUTCDate(), 2) + 'T' + - pad(date.getUTCHours(), 2) + ':' + - pad(date.getUTCMinutes(), 2) + ':' + - pad(date.getUTCSeconds(), 2) + '.' + - pad(date.getUTCMilliseconds(), 3) + 'Z' - ); - } -}; - - -var parsePatterns = [], - nativeParse = Date.parse; - -var parseWord = function(type, word, num){ - var ret = -1, - translated = Date.getMsg(type + 's'); - switch (typeOf(word)){ - case 'object': - ret = translated[word.get(type)]; - break; - case 'number': - ret = translated[word]; - if (!ret) throw new Error('Invalid ' + type + ' index: ' + word); - break; - case 'string': - var match = translated.filter(function(name){ - return this.test(name); - }, new RegExp('^' + word, 'i')); - if (!match.length) throw new Error('Invalid ' + type + ' string'); - if (match.length > 1) throw new Error('Ambiguous ' + type); - ret = match[0]; - } - - return (num) ? translated.indexOf(ret) : ret; -}; - -var startCentury = 1900, - startYear = 70; - -Date.extend({ - - getMsg: function(key, args){ - return Locale.get('Date.' + key, args); - }, - - units: { - ms: Function.from(1), - second: Function.from(1000), - minute: Function.from(60000), - hour: Function.from(3600000), - day: Function.from(86400000), - week: Function.from(608400000), - month: function(month, year){ - var d = new Date; - return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000; - }, - year: function(year){ - year = year || new Date().get('year'); - return Date.isLeapYear(year) ? 31622400000 : 31536000000; - } - }, - - daysInMonth: function(month, year){ - return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; - }, - - isLeapYear: function(year){ - return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0); - }, - - parse: function(from){ - var t = typeOf(from); - if (t == 'number') return new Date(from); - if (t != 'string') return from; - from = from.clean(); - if (!from.length) return null; - - var parsed; - parsePatterns.some(function(pattern){ - var bits = pattern.re.exec(from); - return (bits) ? (parsed = pattern.handler(bits)) : false; - }); - - if (!(parsed && parsed.isValid())){ - parsed = new Date(nativeParse(from)); - if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt()); - } - return parsed; - }, - - parseDay: function(day, num){ - return parseWord('day', day, num); - }, - - parseMonth: function(month, num){ - return parseWord('month', month, num); - }, - - parseUTC: function(value){ - var localDate = new Date(value); - var utcSeconds = Date.UTC( - localDate.get('year'), - localDate.get('mo'), - localDate.get('date'), - localDate.get('hr'), - localDate.get('min'), - localDate.get('sec'), - localDate.get('ms') - ); - return new Date(utcSeconds); - }, - - orderIndex: function(unit){ - return Date.getMsg('dateOrder').indexOf(unit) + 1; - }, - - defineFormat: function(name, format){ - formats[name] = format; - return this; - }, - - defineFormats: function(formats){ - for (var name in formats) Date.defineFormat(name, formats[name]); - return this; - }, - - //<1.2compat> - parsePatterns: parsePatterns, - // - - defineParser: function(pattern){ - parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern)); - return this; - }, - - defineParsers: function(){ - Array.flatten(arguments).each(Date.defineParser); - return this; - }, - - define2DigitYearStart: function(year){ - startYear = year % 100; - startCentury = year - startYear; - return this; - } - -}); - -var regexOf = function(type){ - return new RegExp('(?:' + Date.getMsg(type).map(function(name){ - return name.substr(0, 3); - }).join('|') + ')[a-z]*'); -}; - -var replacers = function(key){ - switch (key){ - case 'T': - return '%H:%M:%S'; - case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first - return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?'; - case 'X': - return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?'; - } - return null; -}; - -var keys = { - d: /[0-2]?[0-9]|3[01]/, - H: /[01]?[0-9]|2[0-3]/, - I: /0?[1-9]|1[0-2]/, - M: /[0-5]?\d/, - s: /\d+/, - o: /[a-z]*/, - p: /[ap]\.?m\.?/, - y: /\d{2}|\d{4}/, - Y: /\d{4}/, - z: /Z|[+-]\d{2}(?::?\d{2})?/ -}; - -keys.m = keys.I; -keys.S = keys.M; - -var currentLanguage; - -var recompile = function(language){ - currentLanguage = language; - - keys.a = keys.A = regexOf('days'); - keys.b = keys.B = regexOf('months'); - - parsePatterns.each(function(pattern, i){ - if (pattern.format) parsePatterns[i] = build(pattern.format); - }); -}; - -var build = function(format){ - if (!currentLanguage) return {format: format}; - - var parsed = []; - var re = (format.source || format) // allow format to be regex - .replace(/%([a-z])/gi, - function($0, $1){ - return replacers($1) || $0; - } - ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing - .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas - .replace(/%([a-z%])/gi, - function($0, $1){ - var p = keys[$1]; - if (!p) return $1; - parsed.push($1); - return '(' + p.source + ')'; - } - ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words - - return { - format: format, - re: new RegExp('^' + re + '$', 'i'), - handler: function(bits){ - bits = bits.slice(1).associate(parsed); - var date = new Date().clearTime(), - year = bits.y || bits.Y; - - if (year != null) handle.call(date, 'y', year); // need to start in the right year - if ('d' in bits) handle.call(date, 'd', 1); - if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1); - - for (var key in bits) handle.call(date, key, bits[key]); - return date; - } - }; -}; - -var handle = function(key, value){ - if (!value) return this; - - switch (key){ - case 'a': case 'A': return this.set('day', Date.parseDay(value, true)); - case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true)); - case 'd': return this.set('date', value); - case 'H': case 'I': return this.set('hr', value); - case 'm': return this.set('mo', value - 1); - case 'M': return this.set('min', value); - case 'p': return this.set('ampm', value.replace(/\./g, '')); - case 'S': return this.set('sec', value); - case 's': return this.set('ms', ('0.' + value) * 1000); - case 'w': return this.set('day', value); - case 'Y': return this.set('year', value); - case 'y': - value = +value; - if (value < 100) value += startCentury + (value < startYear ? 100 : 0); - return this.set('year', value); - case 'z': - if (value == 'Z') value = '+00'; - var offset = value.match(/([+-])(\d{2}):?(\d{2})?/); - offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset(); - return this.set('time', this - offset * 60000); - } - - return this; -}; - -Date.defineParsers( - '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601 - '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact - '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM" - '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm" - '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched - '%Y %b( %d%o( %X)?)?', // Same as above with year coming first - '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009" - '%T', // %H:%M:%S - '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05" -); - -Locale.addEvent('change', function(language){ - if (Locale.get('Date')) recompile(language); -}).fireEvent('change', Locale.getCurrent()); - -})(); - - -/* ---- - -script: Date.Extras.js - -name: Date.Extras - -description: Extends the Date native object to include extra methods (on top of those in Date.js). - -license: MIT-style license - -authors: - - Aaron Newton - - Scott Kyle - -requires: - - /Date - -provides: [Date.Extras] - -... -*/ - -Date.implement({ - - timeDiffInWords: function(to){ - return Date.distanceOfTimeInWords(this, to || new Date); - }, - - timeDiff: function(to, separator){ - if (to == null) to = new Date; - var delta = ((to - this) / 1000).floor().abs(); - - var vals = [], - durations = [60, 60, 24, 365, 0], - names = ['s', 'm', 'h', 'd', 'y'], - value, duration; - - for (var item = 0; item < durations.length; item++){ - if (item && !delta) break; - value = delta; - if ((duration = durations[item])){ - value = (delta % duration); - delta = (delta / duration).floor(); - } - vals.unshift(value + (names[item] || '')); - } - - return vals.join(separator || ':'); - } - -}).extend({ - - distanceOfTimeInWords: function(from, to){ - return Date.getTimePhrase(((to - from) / 1000).toInt()); - }, - - getTimePhrase: function(delta){ - var suffix = (delta < 0) ? 'Until' : 'Ago'; - if (delta < 0) delta *= -1; - - var units = { - minute: 60, - hour: 60, - day: 24, - week: 7, - month: 52 / 12, - year: 12, - eon: Infinity - }; - - var msg = 'lessThanMinute'; - - for (var unit in units){ - var interval = units[unit]; - if (delta < 1.5 * interval){ - if (delta > 0.75 * interval) msg = unit; - break; - } - delta /= interval; - msg = unit + 's'; - } - - delta = delta.round(); - return Date.getMsg(msg + suffix, delta).substitute({delta: delta}); - } - -}).defineParsers( - - { - // "today", "tomorrow", "yesterday" - re: /^(?:tod|tom|yes)/i, - handler: function(bits){ - var d = new Date().clearTime(); - switch (bits[0]){ - case 'tom': return d.increment(); - case 'yes': return d.decrement(); - default: return d; - } - } - }, - - { - // "next Wednesday", "last Thursday" - re: /^(next|last) ([a-z]+)$/i, - handler: function(bits){ - var d = new Date().clearTime(); - var day = d.getDay(); - var newDay = Date.parseDay(bits[2], true); - var addDays = newDay - day; - if (newDay <= day) addDays += 7; - if (bits[1] == 'last') addDays -= 7; - return d.set('date', d.getDate() + addDays); - } - } - -).alias('timeAgoInWords', 'timeDiffInWords'); - - -/* ---- - -name: Locale.en-US.Number - -description: Number messages for US English. - -license: MIT-style license - -authors: - - Arian Stolwijk - -requires: - - /Locale - -provides: [Locale.en-US.Number] - -... -*/ - -Locale.define('en-US', 'Number', { - - decimal: '.', - group: ',', - -/* Commented properties are the defaults for Number.format - decimals: 0, - precision: 0, - scientific: null, - - prefix: null, - suffic: null, - - // Negative/Currency/percentage will mixin Number - negative: { - prefix: '-' - },*/ - - currency: { -// decimals: 2, - prefix: '$ ' - }/*, - - percentage: { - decimals: 2, - suffix: '%' - }*/ - -}); - - - - -/* ---- -name: Number.Format -description: Extends the Number Type object to include a number formatting method. -license: MIT-style license -authors: [Arian Stolwijk] -requires: [Core/Number, Locale.en-US.Number] -# Number.Extras is for compatibility -provides: [Number.Format, Number.Extras] -... -*/ - - -Number.implement({ - - format: function(options){ - // Thanks dojo and YUI for some inspiration - var value = this; - options = options ? Object.clone(options) : {}; - var getOption = function(key){ - if (options[key] != null) return options[key]; - return Locale.get('Number.' + key); - }; - - var negative = value < 0, - decimal = getOption('decimal'), - precision = getOption('precision'), - group = getOption('group'), - decimals = getOption('decimals'); - - if (negative){ - var negativeLocale = getOption('negative') || {}; - if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-'; - ['prefix', 'suffix'].each(function(key){ - if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key]; - }); - - value = -value; - } - - var prefix = getOption('prefix'), - suffix = getOption('suffix'); - - if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals); - if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision); - - value += ''; - var index; - if (getOption('scientific') === false && value.indexOf('e') > -1){ - var match = value.split('e'), - zeros = +match[1]; - value = match[0].replace('.', ''); - - if (zeros < 0){ - zeros = -zeros - 1; - index = match[0].indexOf('.'); - if (index > -1) zeros -= index - 1; - while (zeros--) value = '0' + value; - value = '0.' + value; - } else { - index = match[0].lastIndexOf('.'); - if (index > -1) zeros -= match[0].length - index - 1; - while (zeros--) value += '0'; - } - } - - if (decimal != '.') value = value.replace('.', decimal); - - if (group){ - index = value.lastIndexOf(decimal); - index = (index > -1) ? index : value.length; - var newOutput = value.substring(index), - i = index; - - while (i--){ - if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput; - newOutput = value.charAt(i) + newOutput; - } - - value = newOutput; - } - - if (prefix) value = prefix + value; - if (suffix) value += suffix; - - return value; - }, - - formatCurrency: function(){ - var locale = Locale.get('Number.currency') || {}; - if (locale.scientific == null) locale.scientific = false; - if (locale.decimals == null) locale.decimals = 2; - - return this.format(locale); - }, - - formatPercentage: function(){ - var locale = Locale.get('Number.percentage') || {}; - if (locale.suffix == null) locale.suffix = '%'; - if (locale.decimals == null) locale.decimals = 2; - - return this.format(locale); - } - -}); - - -/* ---- - -script: String.Extras.js - -name: String.Extras - -description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc). - -license: MIT-style license - -authors: - - Aaron Newton - - Guillermo Rauch - - Christopher Pitt - -requires: - - Core/String - - Core/Array - - MooTools.More - -provides: [String.Extras] - -... -*/ - -(function(){ - -var special = { - 'a': /[àáâãäåăą]/g, - 'A': /[ÀÁÂÃÄÅĂĄ]/g, - 'c': /[ćčç]/g, - 'C': /[ĆČÇ]/g, - 'd': /[ďđ]/g, - 'D': /[ĎÐ]/g, - 'e': /[èéêëěę]/g, - 'E': /[ÈÉÊËĚĘ]/g, - 'g': /[ğ]/g, - 'G': /[Ğ]/g, - 'i': /[ìíîï]/g, - 'I': /[ÌÍÎÏ]/g, - 'l': /[ĺľł]/g, - 'L': /[ĹĽŁ]/g, - 'n': /[ñňń]/g, - 'N': /[ÑŇŃ]/g, - 'o': /[òóôõöøő]/g, - 'O': /[ÒÓÔÕÖØ]/g, - 'r': /[řŕ]/g, - 'R': /[ŘŔ]/g, - 's': /[ššş]/g, - 'S': /[ŠŞŚ]/g, - 't': /[ťţ]/g, - 'T': /[ŤŢ]/g, - 'ue': /[ü]/g, - 'UE': /[Ü]/g, - 'u': /[ùúûůµ]/g, - 'U': /[ÙÚÛŮ]/g, - 'y': /[ÿý]/g, - 'Y': /[ŸÝ]/g, - 'z': /[žźż]/g, - 'Z': /[ŽŹŻ]/g, - 'th': /[þ]/g, - 'TH': /[Þ]/g, - 'dh': /[ð]/g, - 'DH': /[Ð]/g, - 'ss': /[ß]/g, - 'oe': /[œ]/g, - 'OE': /[Œ]/g, - 'ae': /[æ]/g, - 'AE': /[Æ]/g -}, - -tidy = { - ' ': /[\xa0\u2002\u2003\u2009]/g, - '*': /[\xb7]/g, - '\'': /[\u2018\u2019]/g, - '"': /[\u201c\u201d]/g, - '...': /[\u2026]/g, - '-': /[\u2013]/g, -// '--': /[\u2014]/g, - '»': /[\uFFFD]/g -}; - -var walk = function(string, replacements){ - var result = string, key; - for (key in replacements) result = result.replace(replacements[key], key); - return result; -}; - -var getRegexForTag = function(tag, contents){ - tag = tag || ''; - var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>", - reg = new RegExp(regstr, "gi"); - return reg; -}; - -String.implement({ - - standardize: function(){ - return walk(this, special); - }, - - repeat: function(times){ - return new Array(times + 1).join(this); - }, - - pad: function(length, str, direction){ - if (this.length >= length) return this; - - var pad = (str == null ? ' ' : '' + str) - .repeat(length - this.length) - .substr(0, length - this.length); - - if (!direction || direction == 'right') return this + pad; - if (direction == 'left') return pad + this; - - return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil()); - }, - - getTags: function(tag, contents){ - return this.match(getRegexForTag(tag, contents)) || []; - }, - - stripTags: function(tag, contents){ - return this.replace(getRegexForTag(tag, contents), ''); - }, - - tidy: function(){ - return walk(this, tidy); - }, - - truncate: function(max, trail, atChar){ - var string = this; - if (trail == null && arguments.length == 1) trail = '…'; - if (string.length > max){ - string = string.substring(0, max); - if (atChar){ - var index = string.lastIndexOf(atChar); - if (index != -1) string = string.substr(0, index); - } - if (trail) string += trail; - } - return string; - } - -}); - -})(); - - -/* ---- - -script: String.QueryString.js - -name: String.QueryString - -description: Methods for dealing with URI query strings. - -license: MIT-style license - -authors: - - Sebastian Markbåge - - Aaron Newton - - Lennart Pilon - - Valerio Proietti - -requires: - - Core/Array - - Core/String - - /MooTools.More - -provides: [String.QueryString] - -... -*/ - -String.implement({ - - parseQueryString: function(decodeKeys, decodeValues){ - if (decodeKeys == null) decodeKeys = true; - if (decodeValues == null) decodeValues = true; - - var vars = this.split(/[&;]/), - object = {}; - if (!vars.length) return object; - - vars.each(function(val){ - var index = val.indexOf('=') + 1, - value = index ? val.substr(index) : '', - keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val], - obj = object; - if (!keys) return; - if (decodeValues) value = decodeURIComponent(value); - keys.each(function(key, i){ - if (decodeKeys) key = decodeURIComponent(key); - var current = obj[key]; - - if (i < keys.length - 1) obj = obj[key] = current || {}; - else if (typeOf(current) == 'array') current.push(value); - else obj[key] = current != null ? [current, value] : value; - }); - }); - - return object; - }, - - cleanQueryString: function(method){ - return this.split('&').filter(function(val){ - var index = val.indexOf('='), - key = index < 0 ? '' : val.substr(0, index), - value = val.substr(index + 1); - - return method ? method.call(null, key, value) : (value || value === 0); - }).join('&'); - } - -}); - - -/* ---- - -script: URI.js - -name: URI - -description: Provides methods useful in managing the window location and uris. - -license: MIT-style license - -authors: - - Sebastian Markbåge - - Aaron Newton - -requires: - - Core/Object - - Core/Class - - Core/Class.Extras - - Core/Element - - /String.QueryString - -provides: [URI] - -... -*/ - -(function(){ - -var toString = function(){ - return this.get('value'); -}; - -var URI = this.URI = new Class({ - - Implements: Options, - - options: { - /*base: false*/ - }, - - regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/, - parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'], - schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0}, - - initialize: function(uri, options){ - this.setOptions(options); - var base = this.options.base || URI.base; - if (!uri) uri = base; - - if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed); - else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false); - }, - - parse: function(value, base){ - var bits = value.match(this.regex); - if (!bits) return false; - bits.shift(); - return this.merge(bits.associate(this.parts), base); - }, - - merge: function(bits, base){ - if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false; - if (base){ - this.parts.every(function(part){ - if (bits[part]) return false; - bits[part] = base[part] || ''; - return true; - }); - } - bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()]; - bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/'; - return bits; - }, - - parseDirectory: function(directory, baseDirectory){ - directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory; - if (!directory.test(URI.regs.directoryDot)) return directory; - var result = []; - directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){ - if (dir == '..' && result.length > 0) result.pop(); - else if (dir != '.') result.push(dir); - }); - return result.join('/') + '/'; - }, - - combine: function(bits){ - return bits.value || bits.scheme + '://' + - (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') + - (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') + - (bits.directory || '/') + (bits.file || '') + - (bits.query ? '?' + bits.query : '') + - (bits.fragment ? '#' + bits.fragment : ''); - }, - - set: function(part, value, base){ - if (part == 'value'){ - var scheme = value.match(URI.regs.scheme); - if (scheme) scheme = scheme[1]; - if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value }; - else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value }); - } else if (part == 'data'){ - this.setData(value); - } else { - this.parsed[part] = value; - } - return this; - }, - - get: function(part, base){ - switch (part){ - case 'value': return this.combine(this.parsed, base ? base.parsed : false); - case 'data' : return this.getData(); - } - return this.parsed[part] || ''; - }, - - go: function(){ - document.location.href = this.toString(); - }, - - toURI: function(){ - return this; - }, - - getData: function(key, part){ - var qs = this.get(part || 'query'); - if (!(qs || qs === 0)) return key ? null : {}; - var obj = qs.parseQueryString(); - return key ? obj[key] : obj; - }, - - setData: function(values, merge, part){ - if (typeof values == 'string'){ - var data = this.getData(); - data[arguments[0]] = arguments[1]; - values = data; - } else if (merge){ - values = Object.merge(this.getData(), values); - } - return this.set(part || 'query', Object.toQueryString(values)); - }, - - clearData: function(part){ - return this.set(part || 'query', ''); - }, - - toString: toString, - valueOf: toString - -}); - -URI.regs = { - endSlash: /\/$/, - scheme: /^(\w+):/, - directoryDot: /\.\/|\.$/ -}; - -URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location}); - -String.implement({ - - toURI: function(options){ - return new URI(this, options); - } - -}); - -})(); - - -/* ---- - -script: URI.Relative.js - -name: URI.Relative - -description: Extends the URI class to add methods for computing relative and absolute urls. - -license: MIT-style license - -authors: - - Sebastian Markbåge - - -requires: - - /Class.refactor - - /URI - -provides: [URI.Relative] - -... -*/ - -URI = Class.refactor(URI, { - - combine: function(bits, base){ - if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port) - return this.previous.apply(this, arguments); - var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : ''); - - if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end; - - var baseDir = base.directory.split('/'), - relDir = bits.directory.split('/'), - path = '', - offset; - - var i = 0; - for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++); - for (i = 0; i < baseDir.length - offset - 1; i++) path += '../'; - for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/'; - - return (path || (bits.file ? '' : './')) + end; - }, - - toAbsolute: function(base){ - base = new URI(base); - if (base) base.set('directory', '').set('file', ''); - return this.toRelative(base); - }, - - toRelative: function(base){ - return this.get('value', new URI(base)); - } - -}); - - -/* ---- - -name: Hash - -description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. - -license: MIT-style license. - -requires: - - Core/Object - - /MooTools.More - -provides: [Hash] - -... -*/ - -(function(){ - -if (this.Hash) return; - -var Hash = this.Hash = new Type('Hash', function(object){ - if (typeOf(object) == 'hash') object = Object.clone(object.getClean()); - for (var key in object) this[key] = object[key]; - return this; -}); - -this.$H = function(object){ - return new Hash(object); -}; - -Hash.implement({ - - forEach: function(fn, bind){ - Object.forEach(this, fn, bind); - }, - - getClean: function(){ - var clean = {}; - for (var key in this){ - if (this.hasOwnProperty(key)) clean[key] = this[key]; - } - return clean; - }, - - getLength: function(){ - var length = 0; - for (var key in this){ - if (this.hasOwnProperty(key)) length++; - } - return length; - } - -}); - -Hash.alias('each', 'forEach'); - -Hash.implement({ - - has: Object.prototype.hasOwnProperty, - - keyOf: function(value){ - return Object.keyOf(this, value); - }, - - hasValue: function(value){ - return Object.contains(this, value); - }, - - extend: function(properties){ - Hash.each(properties || {}, function(value, key){ - Hash.set(this, key, value); - }, this); - return this; - }, - - combine: function(properties){ - Hash.each(properties || {}, function(value, key){ - Hash.include(this, key, value); - }, this); - return this; - }, - - erase: function(key){ - if (this.hasOwnProperty(key)) delete this[key]; - return this; - }, - - get: function(key){ - return (this.hasOwnProperty(key)) ? this[key] : null; - }, - - set: function(key, value){ - if (!this[key] || this.hasOwnProperty(key)) this[key] = value; - return this; - }, - - empty: function(){ - Hash.each(this, function(value, key){ - delete this[key]; - }, this); - return this; - }, - - include: function(key, value){ - if (this[key] == undefined) this[key] = value; - return this; - }, - - map: function(fn, bind){ - return new Hash(Object.map(this, fn, bind)); - }, - - filter: function(fn, bind){ - return new Hash(Object.filter(this, fn, bind)); - }, - - every: function(fn, bind){ - return Object.every(this, fn, bind); - }, - - some: function(fn, bind){ - return Object.some(this, fn, bind); - }, - - getKeys: function(){ - return Object.keys(this); - }, - - getValues: function(){ - return Object.values(this); - }, - - toQueryString: function(base){ - return Object.toQueryString(this, base); - } - -}); - -Hash.alias({indexOf: 'keyOf', contains: 'hasValue'}); - - -})(); - - - -/* ---- - -script: Hash.Extras.js - -name: Hash.Extras - -description: Extends the Hash Type to include getFromPath which allows a path notation to child elements. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - /Hash - - /Object.Extras - -provides: [Hash.Extras] - -... -*/ - -Hash.implement({ - - getFromPath: function(notation){ - return Object.getFromPath(this, notation); - }, - - cleanValues: function(method){ - return new Hash(Object.cleanValues(this, method)); - }, - - run: function(){ - Object.run(arguments); - } - -}); - - -/* ---- - -script: Element.Forms.js - -name: Element.Forms - -description: Extends the Element native object to include methods useful in managing inputs. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Element - - /String.Extras - - /MooTools.More - -provides: [Element.Forms] - -... -*/ - -Element.implement({ - - tidy: function(){ - this.set('value', this.get('value').tidy()); - }, - - getTextInRange: function(start, end){ - return this.get('value').substring(start, end); - }, - - getSelectedText: function(){ - if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd()); - return document.selection.createRange().text; - }, - - getSelectedRange: function(){ - if (this.selectionStart != null){ - return { - start: this.selectionStart, - end: this.selectionEnd - }; - } - - var pos = { - start: 0, - end: 0 - }; - var range = this.getDocument().selection.createRange(); - if (!range || range.parentElement() != this) return pos; - var duplicate = range.duplicate(); - - if (this.type == 'text'){ - pos.start = 0 - duplicate.moveStart('character', -100000); - pos.end = pos.start + range.text.length; - } else { - var value = this.get('value'); - var offset = value.length; - duplicate.moveToElementText(this); - duplicate.setEndPoint('StartToEnd', range); - if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length; - pos.end = offset - duplicate.text.length; - duplicate.setEndPoint('StartToStart', range); - pos.start = offset - duplicate.text.length; - } - return pos; - }, - - getSelectionStart: function(){ - return this.getSelectedRange().start; - }, - - getSelectionEnd: function(){ - return this.getSelectedRange().end; - }, - - setCaretPosition: function(pos){ - if (pos == 'end') pos = this.get('value').length; - this.selectRange(pos, pos); - return this; - }, - - getCaretPosition: function(){ - return this.getSelectedRange().start; - }, - - selectRange: function(start, end){ - if (this.setSelectionRange){ - this.focus(); - this.setSelectionRange(start, end); - } else { - var value = this.get('value'); - var diff = value.substr(start, end - start).replace(/\r/g, '').length; - start = value.substr(0, start).replace(/\r/g, '').length; - var range = this.createTextRange(); - range.collapse(true); - range.moveEnd('character', start + diff); - range.moveStart('character', start); - range.select(); - } - return this; - }, - - insertAtCursor: function(value, select){ - var pos = this.getSelectedRange(); - var text = this.get('value'); - this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length)); - if (select !== false) this.selectRange(pos.start, pos.start + value.length); - else this.setCaretPosition(pos.start + value.length); - return this; - }, - - insertAroundCursor: function(options, select){ - options = Object.append({ - before: '', - defaultMiddle: '', - after: '' - }, options); - - var value = this.getSelectedText() || options.defaultMiddle; - var pos = this.getSelectedRange(); - var text = this.get('value'); - - if (pos.start == pos.end){ - this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length)); - this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length); - } else { - var current = text.substring(pos.start, pos.end); - this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length)); - var selStart = pos.start + options.before.length; - if (select !== false) this.selectRange(selStart, selStart + current.length); - else this.setCaretPosition(selStart + text.length); - } - return this; - } - -}); - - -/* ---- - -script: Elements.From.js - -name: Elements.From - -description: Returns a collection of elements from a string of html. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/String - - Core/Element - - /MooTools.More - -provides: [Elements.from, Elements.From] - -... -*/ - -Elements.from = function(text, excludeScripts){ - if (excludeScripts || excludeScripts == null) text = text.stripScripts(); - - var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i); - - if (match){ - container = new Element('table'); - var tag = match[1].toLowerCase(); - if (['td', 'th', 'tr'].contains(tag)){ - container = new Element('tbody').inject(container); - if (tag != 'tr') container = new Element('tr').inject(container); - } - } - - return (container || new Element('div')).set('html', text).getChildren(); -}; - - -/* ---- - -name: Element.Event.Pseudos - -description: Adds the functionality to add pseudo events for Elements - -license: MIT-style license - -authors: - - Arian Stolwijk - -requires: [Core/Element.Event, Events.Pseudos] - -provides: [Element.Event.Pseudos] - -... -*/ - -(function(){ - -var pseudos = {}, - copyFromEvents = ['once', 'throttle', 'pause'], - count = copyFromEvents.length; - -while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]); - -Event.definePseudo = function(key, listener){ - pseudos[key] = Type.isFunction(listener) ? {listener: listener} : listener; - return this; -}; - -var proto = Element.prototype; -[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); - -})(); - - -/* ---- - -name: Element.Event.Pseudos.Keys - -description: Adds functionality fire events if certain keycombinations are pressed - -license: MIT-style license - -authors: - - Arian Stolwijk - -requires: [Element.Event.Pseudos] - -provides: [Element.Event.Pseudos.Keys] - -... -*/ - -(function(){ - -var keysStoreKey = '$moo:keys-pressed', - keysKeyupStoreKey = '$moo:keys-keyup'; - - -Event.definePseudo('keys', function(split, fn, args){ - - var event = args[0], - keys = [], - pressed = this.retrieve(keysStoreKey, []); - - keys.append(split.value.replace('++', function(){ - keys.push('+'); // shift++ and shift+++a - return ''; - }).split('+')); - - pressed.include(event.key); - - if (keys.every(function(key){ - return pressed.contains(key); - })) fn.apply(this, args); - - this.store(keysStoreKey, pressed); - - if (!this.retrieve(keysKeyupStoreKey)){ - var keyup = function(event){ - (function(){ - pressed = this.retrieve(keysStoreKey, []).erase(event.key); - this.store(keysStoreKey, pressed); - }).delay(0, this); // Fix for IE - }; - this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup); - } - -}); - -Object.append(Event.Keys, { - 'shift': 16, - 'control': 17, - 'alt': 18, - 'capslock': 20, - 'pageup': 33, - 'pagedown': 34, - 'end': 35, - 'home': 36, - 'numlock': 144, - 'scrolllock': 145, - ';': 186, - '=': 187, - ',': 188, - '-': Browser.firefox ? 109 : 189, - '.': 190, - '/': 191, - '`': 192, - '[': 219, - '\\': 220, - ']': 221, - "'": 222, - '+': 107 -}); - -})(); - - -/* ---- - -script: Element.Delegation.js - -name: Element.Delegation - -description: Extends the Element native object to include the delegate method for more efficient event management. - -credits: - - "Event checking based on the work of Daniel Steigerwald. License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz" - -license: MIT-style license - -authors: - - Aaron Newton - - Daniel Steigerwald - -requires: [/MooTools.More, Element.Event.Pseudos] - -provides: [Element.Delegation] - -... -*/ - -(function(){ - -var eventListenerSupport = !(window.attachEvent && !window.addEventListener), - nativeEvents = Element.NativeEvents; - -nativeEvents.focusin = 2; -nativeEvents.focusout = 2; - -var check = function(split, target, event){ - var elementEvent = Element.Events[split.event], condition; - if (elementEvent) condition = elementEvent.condition; - return Slick.match(target, split.value) && (!condition || condition.call(target, event)); -}; - -var bubbleUp = function(split, event, fn){ - for (var target = event.target; target && target != this; target = document.id(target.parentNode)){ - if (target && check(split, target, event)) return fn.call(target, event, target); - } -}; - -var formObserver = function(eventName){ - - var $delegationKey = '$delegation:'; - - return { - base: 'focusin', - - onRemove: function(element){ - element.retrieve($delegationKey + 'forms', []).each(function(el){ - el.retrieve($delegationKey + 'listeners', []).each(function(listener){ - el.removeEvent(eventName, listener); - }); - el.eliminate($delegationKey + eventName + 'listeners') - .eliminate($delegationKey + eventName + 'originalFn'); - }); - }, - - listener: function(split, fn, args, monitor, options){ - var event = args[0], - forms = this.retrieve($delegationKey + 'forms', []), - target = event.target, - form = (target.get('tag') == 'form') ? target : event.target.getParent('form'); - - if (!form) return; - - var formEvents = form.retrieve($delegationKey + 'originalFn', []), - formListeners = form.retrieve($delegationKey + 'listeners', []), - self = this; - - forms.include(form); - this.store($delegationKey + 'forms', forms); - - if (!formEvents.contains(fn)){ - var formListener = function(event){ - bubbleUp.call(self, split, event, fn); - }; - form.addEvent(eventName, formListener); - - formEvents.push(fn); - formListeners.push(formListener); - - form.store($delegationKey + eventName + 'originalFn', formEvents) - .store($delegationKey + eventName + 'listeners', formListeners); - } - } - }; -}; - -var inputObserver = function(eventName){ - return { - base: 'focusin', - listener: function(split, fn, args){ - var events = {blur: function(){ - this.removeEvents(events); - }}, self = this; - events[eventName] = function(event){ - bubbleUp.call(self, split, event, fn); - }; - args[0].target.addEvents(events); - } - }; -}; - -var eventOptions = { - mouseenter: { - base: 'mouseover' - }, - mouseleave: { - base: 'mouseout' - }, - focus: { - base: 'focus' + (eventListenerSupport ? '' : 'in'), - args: [true] - }, - blur: { - base: eventListenerSupport ? 'blur' : 'focusout', - args: [true] - } -}; - -if (!eventListenerSupport) Object.append(eventOptions, { - submit: formObserver('submit'), - reset: formObserver('reset'), - change: inputObserver('change'), - select: inputObserver('select') -}); - -Event.definePseudo('relay', { - listener: function(split, fn, args){ - bubbleUp.call(this, split, args[0], fn); - }, - options: eventOptions -}); - -})(); - - -/* ---- - -script: Element.Measure.js - -name: Element.Measure - -description: Extends the Element native object to include methods useful in measuring dimensions. - -credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz" - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Element.Style - - Core/Element.Dimensions - - /MooTools.More - -provides: [Element.Measure] - -... -*/ - -(function(){ - -var getStylesList = function(styles, planes){ - var list = []; - Object.each(planes, function(directions){ - Object.each(directions, function(edge){ - styles.each(function(style){ - list.push(style + '-' + edge + (style == 'border' ? '-width' : '')); - }); - }); - }); - return list; -}; - -var calculateEdgeSize = function(edge, styles){ - var total = 0; - Object.each(styles, function(value, style){ - if (style.test(edge)) total = total + value.toInt(); - }); - return total; -}; - -var isVisible = function(el){ - return !!(!el || el.offsetHeight || el.offsetWidth); -}; - - -Element.implement({ - - measure: function(fn){ - if (isVisible(this)) return fn.call(this); - var parent = this.getParent(), - toMeasure = []; - while (!isVisible(parent) && parent != document.body){ - toMeasure.push(parent.expose()); - parent = parent.getParent(); - } - var restore = this.expose(), - result = fn.call(this); - restore(); - toMeasure.each(function(restore){ - restore(); - }); - return result; - }, - - expose: function(){ - if (this.getStyle('display') != 'none') return function(){}; - var before = this.style.cssText; - this.setStyles({ - display: 'block', - position: 'absolute', - visibility: 'hidden' - }); - return function(){ - this.style.cssText = before; - }.bind(this); - }, - - getDimensions: function(options){ - options = Object.merge({computeSize: false}, options); - var dim = {x: 0, y: 0}; - - var getSize = function(el, options){ - return (options.computeSize) ? el.getComputedSize(options) : el.getSize(); - }; - - var parent = this.getParent('body'); - - if (parent && this.getStyle('display') == 'none'){ - dim = this.measure(function(){ - return getSize(this, options); - }); - } else if (parent){ - try { //safari sometimes crashes here, so catch it - dim = getSize(this, options); - }catch(e){} - } - - return Object.append(dim, (dim.x || dim.x === 0) ? { - width: dim.x, - height: dim.y - } : { - x: dim.width, - y: dim.height - } - ); - }, - - getComputedSize: function(options){ - //<1.2compat> - //legacy support for my stupid spelling error - if (options && options.plains) options.planes = options.plains; - // - - options = Object.merge({ - styles: ['padding','border'], - planes: { - height: ['top','bottom'], - width: ['left','right'] - }, - mode: 'both' - }, options); - - var styles = {}, - size = {width: 0, height: 0}, - dimensions; - - if (options.mode == 'vertical'){ - delete size.width; - delete options.planes.width; - } else if (options.mode == 'horizontal'){ - delete size.height; - delete options.planes.height; - } - - getStylesList(options.styles, options.planes).each(function(style){ - styles[style] = this.getStyle(style).toInt(); - }, this); - - Object.each(options.planes, function(edges, plane){ - - var capitalized = plane.capitalize(), - style = this.getStyle(plane); - - if (style == 'auto' && !dimensions) dimensions = this.getDimensions(); - - style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt(); - size['total' + capitalized] = style; - - edges.each(function(edge){ - var edgesize = calculateEdgeSize(edge, styles); - size['computed' + edge.capitalize()] = edgesize; - size['total' + capitalized] += edgesize; - }); - - }, this); - - return Object.append(size, styles); - } - -}); - -})(); - - -/* ---- - -script: Element.Pin.js - -name: Element.Pin - -description: Extends the Element native object to include the pin method useful for fixed positioning for elements. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Element.Event - - Core/Element.Dimensions - - Core/Element.Style - - /MooTools.More - -provides: [Element.Pin] - -... -*/ - -(function(){ - var supportsPositionFixed = false, - supportTested = false; - - var testPositionFixed = function(){ - var test = new Element('div').setStyles({ - position: 'fixed', - top: 0, - right: 0 - }).inject(document.body); - supportsPositionFixed = (test.offsetTop === 0); - test.dispose(); - supportTested = true; - }; - - Element.implement({ - - pin: function(enable, forceScroll){ - if (!supportTested) testPositionFixed(); - if (this.getStyle('display') == 'none') return this; - - var pinnedPosition, - scroll = window.getScroll(), - parent, - scrollFixer; - - if (enable !== false){ - pinnedPosition = this.getPosition(supportsPositionFixed ? document.body : this.getOffsetParent()); - if (!this.retrieve('pin:_pinned')){ - var currentPosition = { - top: pinnedPosition.y - scroll.y, - left: pinnedPosition.x - scroll.x - }; - - if (supportsPositionFixed && !forceScroll){ - this.setStyle('position', 'fixed').setStyles(currentPosition); - } else { - - parent = this.getOffsetParent(); - var position = this.getPosition(parent), - styles = this.getStyles('left', 'top'); - - if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position); - if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute'); - - position = { - x: styles.left.toInt() - scroll.x, - y: styles.top.toInt() - scroll.y - }; - - scrollFixer = function(){ - if (!this.retrieve('pin:_pinned')) return; - var scroll = window.getScroll(); - this.setStyles({ - left: position.x + scroll.x, - top: position.y + scroll.y - }); - }.bind(this); - - this.store('pin:_scrollFixer', scrollFixer); - window.addEvent('scroll', scrollFixer); - } - this.store('pin:_pinned', true); - } - - } else { - if (!this.retrieve('pin:_pinned')) return this; - - parent = this.getParent(); - var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent()); - - pinnedPosition = this.getPosition(offsetParent); - - this.store('pin:_pinned', false); - scrollFixer = this.retrieve('pin:_scrollFixer'); - if (!scrollFixer){ - this.setStyles({ - position: 'absolute', - top: pinnedPosition.y + scroll.y, - left: pinnedPosition.x + scroll.x - }); - } else { - this.store('pin:_scrollFixer', null); - window.removeEvent('scroll', scrollFixer); - } - this.removeClass('isPinned'); - } - return this; - }, - - unpin: function(){ - return this.pin(false); - }, - - togglePin: function(){ - return this.pin(!this.retrieve('pin:_pinned')); - } - - }); - -//<1.2compat> -Element.alias('togglepin', 'togglePin'); -// - -})(); - - -/* ---- - -script: Element.Position.js - -name: Element.Position - -description: Extends the Element native object to include methods useful positioning elements relative to others. - -license: MIT-style license - -authors: - - Aaron Newton - - Jacob Thornton - -requires: - - Core/Options - - Core/Element.Dimensions - - Element.Measure - -provides: [Element.Position] - -... -*/ - -(function(original){ - -var local = Element.Position = { - - options: {/* - edge: false, - returnPos: false, - minimum: {x: 0, y: 0}, - maximum: {x: 0, y: 0}, - relFixedPosition: false, - ignoreMargins: false, - ignoreScroll: false, - allowNegative: false,*/ - relativeTo: document.body, - position: { - x: 'center', //left, center, right - y: 'center' //top, center, bottom - }, - offset: {x: 0, y: 0} - }, - - getOptions: function(element, options){ - options = Object.merge({}, local.options, options); - local.setPositionOption(options); - local.setEdgeOption(options); - local.setOffsetOption(element, options); - local.setDimensionsOption(element, options); - return options; - }, - - setPositionOption: function(options){ - options.position = local.getCoordinateFromValue(options.position); - }, - - setEdgeOption: function(options){ - var edgeOption = local.getCoordinateFromValue(options.edge); - options.edge = edgeOption ? edgeOption : - (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} : - {x: 'left', y: 'top'}; - }, - - setOffsetOption: function(element, options){ - var parentOffset = {x: 0, y: 0}, - offsetParent = element.measure(function(){ - return document.id(this.getOffsetParent()); - }), - parentScroll = offsetParent.getScroll(); - - if (!offsetParent || offsetParent == element.getDocument().body) return; - parentOffset = offsetParent.measure(function(){ - var position = this.getPosition(); - if (this.getStyle('position') == 'fixed'){ - var scroll = window.getScroll(); - position.x += scroll.x; - position.y += scroll.y; - } - return position; - }); - - options.offset = { - parentPositioned: offsetParent != document.id(options.relativeTo), - x: options.offset.x - parentOffset.x + parentScroll.x, - y: options.offset.y - parentOffset.y + parentScroll.y - }; - }, - - setDimensionsOption: function(element, options){ - options.dimensions = element.getDimensions({ - computeSize: true, - styles: ['padding', 'border', 'margin'] - }); - }, - - getPosition: function(element, options){ - var position = {}; - options = local.getOptions(element, options); - var relativeTo = document.id(options.relativeTo) || document.body; - - local.setPositionCoordinates(options, position, relativeTo); - if (options.edge) local.toEdge(position, options); - - var offset = options.offset; - position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt(); - position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt(); - - local.toMinMax(position, options); - - if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position); - if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position); - if (options.ignoreMargins) local.toIgnoreMargins(position, options); - - position.left = Math.ceil(position.left); - position.top = Math.ceil(position.top); - delete position.x; - delete position.y; - - return position; - }, - - setPositionCoordinates: function(options, position, relativeTo){ - var offsetY = options.offset.y, - offsetX = options.offset.x, - calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(), - top = calc.y, - left = calc.x, - winSize = window.getSize(); - - switch(options.position.x){ - case 'left': position.x = left + offsetX; break; - case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break; - default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break; - } - - switch(options.position.y){ - case 'top': position.y = top + offsetY; break; - case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break; - default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break; - } - }, - - toMinMax: function(position, options){ - var xy = {left: 'x', top: 'y'}, value; - ['minimum', 'maximum'].each(function(minmax){ - ['left', 'top'].each(function(lr){ - value = options[minmax] ? options[minmax][xy[lr]] : null; - if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value; - }); - }); - }, - - toRelFixedPosition: function(relativeTo, position){ - var winScroll = window.getScroll(); - position.top += winScroll.y; - position.left += winScroll.x; - }, - - toIgnoreScroll: function(relativeTo, position){ - var relScroll = relativeTo.getScroll(); - position.top -= relScroll.y; - position.left -= relScroll.x; - }, - - toIgnoreMargins: function(position, options){ - position.left += options.edge.x == 'right' - ? options.dimensions['margin-right'] - : (options.edge.x != 'center' - ? -options.dimensions['margin-left'] - : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2)); - - position.top += options.edge.y == 'bottom' - ? options.dimensions['margin-bottom'] - : (options.edge.y != 'center' - ? -options.dimensions['margin-top'] - : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2)); - }, - - toEdge: function(position, options){ - var edgeOffset = {}, - dimensions = options.dimensions, - edge = options.edge; - - switch(edge.x){ - case 'left': edgeOffset.x = 0; break; - case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break; - // center - default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break; - } - - switch(edge.y){ - case 'top': edgeOffset.y = 0; break; - case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break; - // center - default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break; - } - - position.x += edgeOffset.x; - position.y += edgeOffset.y; - }, - - getCoordinateFromValue: function(option){ - if (typeOf(option) != 'string') return option; - option = option.toLowerCase(); - - return { - x: option.test('left') ? 'left' - : (option.test('right') ? 'right' : 'center'), - y: option.test(/upper|top/) ? 'top' - : (option.test('bottom') ? 'bottom' : 'center') - }; - } - -}; - -Element.implement({ - - position: function(options){ - if (options && (options.x != null || options.y != null)) { - return (original ? original.apply(this, arguments) : this); - } - var position = this.setStyle('position', 'absolute').calculatePosition(options); - return (options && options.returnPos) ? position : this.setStyles(position); - }, - - calculatePosition: function(options){ - return local.getPosition(this, options); - } - -}); - -})(Element.prototype.position); - - -/* ---- - -script: Element.Shortcuts.js - -name: Element.Shortcuts - -description: Extends the Element native object to include some shortcut methods. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Element.Style - - /MooTools.More - -provides: [Element.Shortcuts] - -... -*/ - -Element.implement({ - - isDisplayed: function(){ - return this.getStyle('display') != 'none'; - }, - - isVisible: function(){ - var w = this.offsetWidth, - h = this.offsetHeight; - return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none'; - }, - - toggle: function(){ - return this[this.isDisplayed() ? 'hide' : 'show'](); - }, - - hide: function(){ - var d; - try { - //IE fails here if the element is not in the dom - d = this.getStyle('display'); - } catch(e){} - if (d == 'none') return this; - return this.store('element:_originalDisplay', d || '').setStyle('display', 'none'); - }, - - show: function(display){ - if (!display && this.isDisplayed()) return this; - display = display || this.retrieve('element:_originalDisplay') || 'block'; - return this.setStyle('display', (display == 'none') ? 'block' : display); - }, - - swapClass: function(remove, add){ - return this.removeClass(remove).addClass(add); - } - -}); - -Document.implement({ - - clearSelection: function(){ - if (window.getSelection){ - var selection = window.getSelection(); - if (selection && selection.removeAllRanges) selection.removeAllRanges(); - } else if (document.selection && document.selection.empty){ - try { - //IE fails here if selected element is not in dom - document.selection.empty(); - } catch(e){} - } - } - -}); - - -/* ---- - -script: IframeShim.js - -name: IframeShim - -description: Defines IframeShim, a class for obscuring select lists and flash objects in IE. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Element.Event - - Core/Element.Style - - Core/Options - - Core/Events - - /Element.Position - - /Class.Occlude - -provides: [IframeShim] - -... -*/ - -var IframeShim = new Class({ - - Implements: [Options, Events, Class.Occlude], - - options: { - className: 'iframeShim', - src: 'javascript:false;document.write("");', - display: false, - zIndex: null, - margin: 0, - offset: {x: 0, y: 0}, - browsers: (Browser.ie6 || (Browser.firefox && Browser.version < 3 && Browser.Platform.mac)) - }, - - property: 'IframeShim', - - initialize: function(element, options){ - this.element = document.id(element); - if (this.occlude()) return this.occluded; - this.setOptions(options); - this.makeShim(); - return this; - }, - - makeShim: function(){ - if (this.options.browsers){ - var zIndex = this.element.getStyle('zIndex').toInt(); - - if (!zIndex){ - zIndex = 1; - var pos = this.element.getStyle('position'); - if (pos == 'static' || !pos) this.element.setStyle('position', 'relative'); - this.element.setStyle('zIndex', zIndex); - } - zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1; - if (zIndex < 0) zIndex = 1; - this.shim = new Element('iframe', { - src: this.options.src, - scrolling: 'no', - frameborder: 0, - styles: { - zIndex: zIndex, - position: 'absolute', - border: 'none', - filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)' - }, - 'class': this.options.className - }).store('IframeShim', this); - var inject = (function(){ - this.shim.inject(this.element, 'after'); - this[this.options.display ? 'show' : 'hide'](); - this.fireEvent('inject'); - }).bind(this); - if (!IframeShim.ready) window.addEvent('load', inject); - else inject(); - } else { - this.position = this.hide = this.show = this.dispose = Function.from(this); - } - }, - - position: function(){ - if (!IframeShim.ready || !this.shim) return this; - var size = this.element.measure(function(){ - return this.getSize(); - }); - if (this.options.margin != undefined){ - size.x = size.x - (this.options.margin * 2); - size.y = size.y - (this.options.margin * 2); - this.options.offset.x += this.options.margin; - this.options.offset.y += this.options.margin; - } - this.shim.set({width: size.x, height: size.y}).position({ - relativeTo: this.element, - offset: this.options.offset - }); - return this; - }, - - hide: function(){ - if (this.shim) this.shim.setStyle('display', 'none'); - return this; - }, - - show: function(){ - if (this.shim) this.shim.setStyle('display', 'block'); - return this.position(); - }, - - dispose: function(){ - if (this.shim) this.shim.dispose(); - return this; - }, - - destroy: function(){ - if (this.shim) this.shim.destroy(); - return this; - } - -}); - -window.addEvent('load', function(){ - IframeShim.ready = true; -}); - - -/* ---- - -script: Mask.js - -name: Mask - -description: Creates a mask element to cover another. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Options - - Core/Events - - Core/Element.Event - - /Class.Binds - - /Element.Position - - /IframeShim - -provides: [Mask] - -... -*/ - -var Mask = new Class({ - - Implements: [Options, Events], - - Binds: ['position'], - - options: {/* - onShow: function(){}, - onHide: function(){}, - onDestroy: function(){}, - onClick: function(event){}, - inject: { - where: 'after', - target: null, - }, - hideOnClick: false, - id: null, - destroyOnHide: false,*/ - style: {}, - 'class': 'mask', - maskMargins: false, - useIframeShim: true, - iframeShimOptions: {} - }, - - initialize: function(target, options){ - this.target = document.id(target) || document.id(document.body); - this.target.store('mask', this); - this.setOptions(options); - this.render(); - this.inject(); - }, - - render: function(){ - this.element = new Element('div', { - 'class': this.options['class'], - id: this.options.id || 'mask-' + String.uniqueID(), - styles: Object.merge({}, this.options.style, { - display: 'none' - }), - events: { - click: function(event){ - this.fireEvent('click', event); - if (this.options.hideOnClick) this.hide(); - }.bind(this) - } - }); - - this.hidden = true; - }, - - toElement: function(){ - return this.element; - }, - - inject: function(target, where){ - where = where || (this.options.inject ? this.options.inject.where : '') || this.target == document.body ? 'inside' : 'after'; - target = target || (this.options.inject && this.options.inject.target) || this.target; - - this.element.inject(target, where); - - if (this.options.useIframeShim){ - this.shim = new IframeShim(this.element, this.options.iframeShimOptions); - - this.addEvents({ - show: this.shim.show.bind(this.shim), - hide: this.shim.hide.bind(this.shim), - destroy: this.shim.destroy.bind(this.shim) - }); - } - }, - - position: function(){ - this.resize(this.options.width, this.options.height); - - this.element.position({ - relativeTo: this.target, - position: 'topLeft', - ignoreMargins: !this.options.maskMargins, - ignoreScroll: this.target == document.body - }); - - return this; - }, - - resize: function(x, y){ - var opt = { - styles: ['padding', 'border'] - }; - if (this.options.maskMargins) opt.styles.push('margin'); - - var dim = this.target.getComputedSize(opt); - if (this.target == document.body){ - this.element.setStyles({width: 0, height: 0}); - var win = window.getScrollSize(); - if (dim.totalHeight < win.y) dim.totalHeight = win.y; - if (dim.totalWidth < win.x) dim.totalWidth = win.x; - } - this.element.setStyles({ - width: Array.pick([x, dim.totalWidth, dim.x]), - height: Array.pick([y, dim.totalHeight, dim.y]) - }); - - return this; - }, - - show: function(){ - if (!this.hidden) return this; - - window.addEvent('resize', this.position); - this.position(); - this.showMask.apply(this, arguments); - - return this; - }, - - showMask: function(){ - this.element.setStyle('display', 'block'); - this.hidden = false; - this.fireEvent('show'); - }, - - hide: function(){ - if (this.hidden) return this; - - window.removeEvent('resize', this.position); - this.hideMask.apply(this, arguments); - if (this.options.destroyOnHide) return this.destroy(); - - return this; - }, - - hideMask: function(){ - this.element.setStyle('display', 'none'); - this.hidden = true; - this.fireEvent('hide'); - }, - - toggle: function(){ - this[this.hidden ? 'show' : 'hide'](); - }, - - destroy: function(){ - this.hide(); - this.element.destroy(); - this.fireEvent('destroy'); - this.target.eliminate('mask'); - } - -}); - -Element.Properties.mask = { - - set: function(options){ - var mask = this.retrieve('mask'); - if (mask) mask.destroy(); - return this.eliminate('mask').store('mask:options', options); - }, - - get: function(){ - var mask = this.retrieve('mask'); - if (!mask){ - mask = new Mask(this, this.retrieve('mask:options')); - this.store('mask', mask); - } - return mask; - } - -}; - -Element.implement({ - - mask: function(options){ - if (options) this.set('mask', options); - this.get('mask').show(); - return this; - }, - - unmask: function(){ - this.get('mask').hide(); - return this; - } - -}); - - -/* ---- - -script: Spinner.js - -name: Spinner - -description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Fx.Tween - - Core/Request - - /Class.refactor - - /Mask - -provides: [Spinner] - -... -*/ - -var Spinner = new Class({ - - Extends: Mask, - - Implements: Chain, - - options: {/* - message: false,*/ - 'class': 'spinner', - containerPosition: {}, - content: { - 'class': 'spinner-content' - }, - messageContainer: { - 'class': 'spinner-msg' - }, - img: { - 'class': 'spinner-img' - }, - fxOptions: { - link: 'chain' - } - }, - - initialize: function(target, options){ - this.target = document.id(target) || document.id(document.body); - this.target.store('spinner', this); - this.setOptions(options); - this.render(); - this.inject(); - - // Add this to events for when noFx is true; parent methods handle hide/show. - var deactivate = function(){ this.active = false; }.bind(this); - this.addEvents({ - hide: deactivate, - show: deactivate - }); - }, - - render: function(){ - this.parent(); - - this.element.set('id', this.options.id || 'spinner-' + String.uniqueID()); - - this.content = document.id(this.options.content) || new Element('div', this.options.content); - this.content.inject(this.element); - - if (this.options.message){ - this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message); - this.msg.inject(this.content); - } - - if (this.options.img){ - this.img = document.id(this.options.img) || new Element('div', this.options.img); - this.img.inject(this.content); - } - - this.element.set('tween', this.options.fxOptions); - }, - - show: function(noFx){ - if (this.active) return this.chain(this.show.bind(this)); - if (!this.hidden){ - this.callChain.delay(20, this); - return this; - } - - this.active = true; - - return this.parent(noFx); - }, - - showMask: function(noFx){ - var pos = function(){ - this.content.position(Object.merge({ - relativeTo: this.element - }, this.options.containerPosition)); - }.bind(this); - - if (noFx){ - this.parent(); - pos(); - } else { - if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat(); - this.element.setStyles({ - display: 'block', - opacity: 0 - }).tween('opacity', this.options.style.opacity); - pos(); - this.hidden = false; - this.fireEvent('show'); - this.callChain(); - } - }, - - hide: function(noFx){ - if (this.active) return this.chain(this.hide.bind(this)); - if (this.hidden){ - this.callChain.delay(20, this); - return this; - } - this.active = true; - return this.parent(noFx); - }, - - hideMask: function(noFx){ - if (noFx) return this.parent(); - this.element.tween('opacity', 0).get('tween').chain(function(){ - this.element.setStyle('display', 'none'); - this.hidden = true; - this.fireEvent('hide'); - this.callChain(); - }.bind(this)); - }, - - destroy: function(){ - this.content.destroy(); - this.parent(); - this.target.eliminate('spinner'); - } - -}); - -Request = Class.refactor(Request, { - - options: { - useSpinner: false, - spinnerOptions: {}, - spinnerTarget: false - }, - - initialize: function(options){ - this._send = this.send; - this.send = function(options){ - var spinner = this.getSpinner(); - if (spinner) spinner.chain(this._send.pass(options, this)).show(); - else this._send(options); - return this; - }; - this.previous(options); - }, - - getSpinner: function(){ - if (!this.spinner){ - var update = document.id(this.options.spinnerTarget) || document.id(this.options.update); - if (this.options.useSpinner && update){ - update.set('spinner', this.options.spinnerOptions); - var spinner = this.spinner = update.get('spinner'); - ['complete', 'exception', 'cancel'].each(function(event){ - this.addEvent(event, spinner.hide.bind(spinner)); - }, this); - } - } - return this.spinner; - } - -}); - -Element.Properties.spinner = { - - set: function(options){ - var spinner = this.retrieve('spinner'); - if (spinner) spinner.destroy(); - return this.eliminate('spinner').store('spinner:options', options); - }, - - get: function(){ - var spinner = this.retrieve('spinner'); - if (!spinner){ - spinner = new Spinner(this, this.retrieve('spinner:options')); - this.store('spinner', spinner); - } - return spinner; - } - -}; - -Element.implement({ - - spin: function(options){ - if (options) this.set('spinner', options); - this.get('spinner').show(); - return this; - }, - - unspin: function(){ - this.get('spinner').hide(); - return this; - } - -}); - - -/* ---- - -script: Form.Request.js - -name: Form.Request - -description: Handles the basic functionality of submitting a form and updating a dom element with the result. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Request.HTML - - /Class.Binds - - /Class.Occlude - - /Spinner - - /String.QueryString - - /Element.Delegation - -provides: [Form.Request] - -... -*/ - -if (!window.Form) window.Form = {}; - -(function(){ - - Form.Request = new Class({ - - Binds: ['onSubmit', 'onFormValidate'], - - Implements: [Options, Events, Class.Occlude], - - options: {/* - onFailure: function(){}, - onSuccess: function(){}, // aliased to onComplete, - onSend: function(){}*/ - requestOptions: { - evalScripts: true, - useSpinner: true, - emulation: false, - link: 'ignore' - }, - sendButtonClicked: true, - extraData: {}, - resetForm: true - }, - - property: 'form.request', - - initialize: function(form, target, options){ - this.element = document.id(form); - if (this.occlude()) return this.occluded; - this.setOptions(options) - .setTarget(target) - .attach(); - }, - - setTarget: function(target){ - this.target = document.id(target); - if (!this.request){ - this.makeRequest(); - } else { - this.request.setOptions({ - update: this.target - }); - } - return this; - }, - - toElement: function(){ - return this.element; - }, - - makeRequest: function(){ - var self = this; - this.request = new Request.HTML(Object.merge({ - update: this.target, - emulation: false, - spinnerTarget: this.element, - method: this.element.get('method') || 'post' - }, this.options.requestOptions)).addEvents({ - success: function(tree, elements, html, javascript){ - ['complete', 'success'].each(function(evt){ - self.fireEvent(evt, [self.target, tree, elements, html, javascript]); - }); - }, - failure: function(){ - self.fireEvent('complete', arguments).fireEvent('failure', arguments); - }, - exception: function(){ - self.fireEvent('failure', arguments); - } - }); - return this.attachReset(); - }, - - attachReset: function(){ - if (!this.options.resetForm) return this; - this.request.addEvent('success', function(){ - Function.attempt(function(){ - this.element.reset(); - }.bind(this)); - if (window.OverText) OverText.update(); - }.bind(this)); - return this; - }, - - attach: function(attach){ - var method = (attach != false) ? 'addEvent' : 'removeEvent'; - this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this)); - - var fv = this.element.retrieve('validator'); - if (fv) fv[method]('onFormValidate', this.onFormValidate); - else this.element[method]('submit', this.onSubmit); - - return this; - }, - - detach: function(){ - return this.attach(false); - }, - - //public method - enable: function(){ - return this.attach(); - }, - - //public method - disable: function(){ - return this.detach(); - }, - - onFormValidate: function(valid, form, event){ - //if there's no event, then this wasn't a submit event - if (!event) return; - var fv = this.element.retrieve('validator'); - if (valid || (fv && !fv.options.stopOnFailure)){ - event.stop(); - this.send(); - } - }, - - onSubmit: function(event){ - var fv = this.element.retrieve('validator'); - if (fv){ - //form validator was created after Form.Request - this.element.removeEvent('submit', this.onSubmit); - fv.addEvent('onFormValidate', this.onFormValidate); - this.element.validate(); - return; - } - if (event) event.stop(); - this.send(); - }, - - saveClickedButton: function(event, target){ - var targetName = target.get('name'); - if (!targetName || !this.options.sendButtonClicked) return; - this.options.extraData[targetName] = target.get('value') || true; - this.clickedCleaner = function(){ - delete this.options.extraData[targetName]; - this.clickedCleaner = function(){}; - }.bind(this); - }, - - clickedCleaner: function(){}, - - send: function(){ - var str = this.element.toQueryString().trim(), - data = Object.toQueryString(this.options.extraData); - - if (str) str += "&" + data; - else str = data; - - this.fireEvent('send', [this.element, str.parseQueryString()]); - this.request.send({ - data: str, - url: this.options.requestOptions.url || this.element.get('action') - }); - this.clickedCleaner(); - return this; - } - - }); - - Element.implement('formUpdate', function(update, options){ - var fq = this.retrieve('form.request'); - if (!fq) { - fq = new Form.Request(this, update, options); - } else { - if (update) fq.setTarget(update); - if (options) fq.setOptions(options).makeRequest(); - } - fq.send(); - return this; - }); - -})(); - - -/* ---- - -script: Fx.Reveal.js - -name: Fx.Reveal - -description: Defines Fx.Reveal, a class that shows and hides elements with a transition. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Fx.Morph - - /Element.Shortcuts - - /Element.Measure - -provides: [Fx.Reveal] - -... -*/ - -(function(){ - - -var hideTheseOf = function(object){ - var hideThese = object.options.hideInputs; - if (window.OverText){ - var otClasses = [null]; - OverText.each(function(ot){ - otClasses.include('.' + ot.options.labelClass); - }); - if (otClasses) hideThese += otClasses.join(', '); - } - return (hideThese) ? object.element.getElements(hideThese) : null; -}; - - -Fx.Reveal = new Class({ - - Extends: Fx.Morph, - - options: {/* - onShow: function(thisElement){}, - onHide: function(thisElement){}, - onComplete: function(thisElement){}, - heightOverride: null, - widthOverride: null,*/ - link: 'cancel', - styles: ['padding', 'border', 'margin'], - transitionOpacity: !Browser.ie6, - mode: 'vertical', - display: function(){ - return this.element.get('tag') != 'tr' ? 'block' : 'table-row'; - }, - opacity: 1, - hideInputs: Browser.ie ? 'select, input, textarea, object, embed' : null - }, - - dissolve: function(){ - if (!this.hiding && !this.showing){ - if (this.element.getStyle('display') != 'none'){ - this.hiding = true; - this.showing = false; - this.hidden = true; - this.cssText = this.element.style.cssText; - - var startStyles = this.element.getComputedSize({ - styles: this.options.styles, - mode: this.options.mode - }); - if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity; - - var zero = {}; - Object.each(startStyles, function(style, name){ - zero[name] = [style, 0]; - }); - - this.element.setStyles({ - display: Function.from(this.options.display).call(this), - overflow: 'hidden' - }); - - var hideThese = hideTheseOf(this); - if (hideThese) hideThese.setStyle('visibility', 'hidden'); - - this.$chain.unshift(function(){ - if (this.hidden){ - this.hiding = false; - this.element.style.cssText = this.cssText; - this.element.setStyle('display', 'none'); - if (hideThese) hideThese.setStyle('visibility', 'visible'); - } - this.fireEvent('hide', this.element); - this.callChain(); - }.bind(this)); - - this.start(zero); - } else { - this.callChain.delay(10, this); - this.fireEvent('complete', this.element); - this.fireEvent('hide', this.element); - } - } else if (this.options.link == 'chain'){ - this.chain(this.dissolve.bind(this)); - } else if (this.options.link == 'cancel' && !this.hiding){ - this.cancel(); - this.dissolve(); - } - return this; - }, - - reveal: function(){ - if (!this.showing && !this.hiding){ - if (this.element.getStyle('display') == 'none'){ - this.hiding = false; - this.showing = true; - this.hidden = false; - this.cssText = this.element.style.cssText; - - var startStyles; - this.element.measure(function(){ - startStyles = this.element.getComputedSize({ - styles: this.options.styles, - mode: this.options.mode - }); - }.bind(this)); - if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt(); - if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt(); - if (this.options.transitionOpacity){ - this.element.setStyle('opacity', 0); - startStyles.opacity = this.options.opacity; - } - - var zero = { - height: 0, - display: Function.from(this.options.display).call(this) - }; - Object.each(startStyles, function(style, name){ - zero[name] = 0; - }); - zero.overflow = 'hidden'; - - this.element.setStyles(zero); - - var hideThese = hideTheseOf(this); - if (hideThese) hideThese.setStyle('visibility', 'hidden'); - - this.$chain.unshift(function(){ - this.element.style.cssText = this.cssText; - this.element.setStyle('display', Function.from(this.options.display).call(this)); - if (!this.hidden) this.showing = false; - if (hideThese) hideThese.setStyle('visibility', 'visible'); - this.callChain(); - this.fireEvent('show', this.element); - }.bind(this)); - - this.start(startStyles); - } else { - this.callChain(); - this.fireEvent('complete', this.element); - this.fireEvent('show', this.element); - } - } else if (this.options.link == 'chain'){ - this.chain(this.reveal.bind(this)); - } else if (this.options.link == 'cancel' && !this.showing){ - this.cancel(); - this.reveal(); - } - return this; - }, - - toggle: function(){ - if (this.element.getStyle('display') == 'none'){ - this.reveal(); - } else { - this.dissolve(); - } - return this; - }, - - cancel: function(){ - this.parent.apply(this, arguments); - if (this.cssText != null) this.element.style.cssText = this.cssText; - this.hiding = false; - this.showing = false; - return this; - } - -}); - -Element.Properties.reveal = { - - set: function(options){ - this.get('reveal').cancel().setOptions(options); - return this; - }, - - get: function(){ - var reveal = this.retrieve('reveal'); - if (!reveal){ - reveal = new Fx.Reveal(this); - this.store('reveal', reveal); - } - return reveal; - } - -}; - -Element.Properties.dissolve = Element.Properties.reveal; - -Element.implement({ - - reveal: function(options){ - this.get('reveal').setOptions(options).reveal(); - return this; - }, - - dissolve: function(options){ - this.get('reveal').setOptions(options).dissolve(); - return this; - }, - - nix: function(options){ - var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject}); - this.get('reveal').setOptions(options).dissolve().chain(function(){ - this[params.destroy ? 'destroy' : 'dispose'](); - }.bind(this)); - return this; - }, - - wink: function(){ - var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject}); - var reveal = this.get('reveal').setOptions(params.options); - reveal.reveal().chain(function(){ - (function(){ - reveal.dissolve(); - }).delay(params.duration || 2000); - }); - } - -}); - -})(); - - -/* ---- - -script: Form.Request.Append.js - -name: Form.Request.Append - -description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - /Form.Request - - /Fx.Reveal - - /Elements.from - -provides: [Form.Request.Append] - -... -*/ - -Form.Request.Append = new Class({ - - Extends: Form.Request, - - options: { - //onBeforeEffect: function(){}, - useReveal: true, - revealOptions: {}, - inject: 'bottom' - }, - - makeRequest: function(){ - this.request = new Request.HTML(Object.merge({ - url: this.element.get('action'), - method: this.element.get('method') || 'post', - spinnerTarget: this.element - }, this.options.requestOptions, { - evalScripts: false - }) - ).addEvents({ - success: function(tree, elements, html, javascript){ - var container; - var kids = Elements.from(html); - if (kids.length == 1){ - container = kids[0]; - } else { - container = new Element('div', { - styles: { - display: 'none' - } - }).adopt(kids); - } - container.inject(this.target, this.options.inject); - if (this.options.requestOptions.evalScripts) Browser.exec(javascript); - this.fireEvent('beforeEffect', container); - var finish = function(){ - this.fireEvent('success', [container, this.target, tree, elements, html, javascript]); - }.bind(this); - if (this.options.useReveal){ - container.set('reveal', this.options.revealOptions).get('reveal').chain(finish); - container.reveal(); - } else { - finish(); - } - }.bind(this), - failure: function(xhr){ - this.fireEvent('failure', xhr); - }.bind(this) - }); - this.attachReset(); - } - -}); - - -/* ---- - -name: Locale.en-US.Form.Validator - -description: Form Validator messages for English. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - /Locale - -provides: [Locale.en-US.Form.Validator] - -... -*/ - -Locale.define('en-US', 'FormValidator', { - - required: 'This field is required.', - minLength: 'Please enter at least {minLength} characters (you entered {length} characters).', - maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).', - integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.', - numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").', - digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).', - alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.', - alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.', - dateSuchAs: 'Please enter a valid date such as {date}', - dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")', - email: 'Please enter a valid email address. For example "fred@domain.com".', - url: 'Please enter a valid URL such as http://www.example.com.', - currencyDollar: 'Please enter a valid $ amount. For example $100.00 .', - oneRequired: 'Please enter something for at least one of these inputs.', - errorPrefix: 'Error: ', - warningPrefix: 'Warning: ', - - // Form.Validator.Extras - noSpace: 'There can be no spaces in this input.', - reqChkByNode: 'No items are selected.', - requiredChk: 'This field is required.', - reqChkByName: 'Please select a {label}.', - match: 'This field needs to match the {matchName} field', - startDate: 'the start date', - endDate: 'the end date', - currendDate: 'the current date', - afterDate: 'The date should be the same or after {label}.', - beforeDate: 'The date should be the same or before {label}.', - startMonth: 'Please select a start month', - sameMonth: 'These two dates must be in the same month - you must change one or the other.', - creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.' - -}); - - -/* ---- - -script: Form.Validator.js - -name: Form.Validator - -description: A css-class based form validation system. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Options - - Core/Events - - Core/Slick.Finder - - Core/Element.Event - - Core/Element.Style - - Core/JSON - - /Locale - - /Class.Binds - - /Date - - /Element.Forms - - /Locale.en-US.Form.Validator - - /Element.Shortcuts - -provides: [Form.Validator, InputValidator, FormValidator.BaseValidators] - -... -*/ -if (!window.Form) window.Form = {}; - -var InputValidator = this.InputValidator = new Class({ - - Implements: [Options], - - options: { - errorMsg: 'Validation failed.', - test: Function.from(true) - }, - - initialize: function(className, options){ - this.setOptions(options); - this.className = className; - }, - - test: function(field, props){ - field = document.id(field); - return (field) ? this.options.test(field, props || this.getProps(field)) : false; - }, - - getError: function(field, props){ - field = document.id(field); - var err = this.options.errorMsg; - if (typeOf(err) == 'function') err = err(field, props || this.getProps(field)); - return err; - }, - - getProps: function(field){ - field = document.id(field); - return (field) ? field.get('validatorProps') : {}; - } - -}); - -Element.Properties.validators = { - - get: function(){ - return (this.get('data-validators') || this.className).clean().split(' '); - } - -}; - -Element.Properties.validatorProps = { - - set: function(props){ - return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props); - }, - - get: function(props){ - if (props) this.set(props); - if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps'); - if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){ - try { - this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'))); - }catch(e){ - return {}; - } - } else { - var vals = this.get('validators').filter(function(cls){ - return cls.test(':'); - }); - if (!vals.length){ - this.store('$moo:validatorProps', {}); - } else { - props = {}; - vals.each(function(cls){ - var split = cls.split(':'); - if (split[1]){ - try { - props[split[0]] = JSON.decode(split[1]); - } catch(e){} - } - }); - this.store('$moo:validatorProps', props); - } - } - return this.retrieve('$moo:validatorProps'); - } - -}; - -Form.Validator = new Class({ - - Implements: [Options, Events], - - Binds: ['onSubmit'], - - options: {/* - onFormValidate: function(isValid, form, event){}, - onElementValidate: function(isValid, field, className, warn){}, - onElementPass: function(field){}, - onElementFail: function(field, validatorsFailed){}, */ - fieldSelectors: 'input, select, textarea', - ignoreHidden: true, - ignoreDisabled: true, - useTitles: false, - evaluateOnSubmit: true, - evaluateFieldsOnBlur: true, - evaluateFieldsOnChange: true, - serial: true, - stopOnFailure: true, - warningPrefix: function(){ - return Form.Validator.getMsg('warningPrefix') || 'Warning: '; - }, - errorPrefix: function(){ - return Form.Validator.getMsg('errorPrefix') || 'Error: '; - } - }, - - initialize: function(form, options){ - this.setOptions(options); - this.element = document.id(form); - this.element.store('validator', this); - this.warningPrefix = Function.from(this.options.warningPrefix)(); - this.errorPrefix = Function.from(this.options.errorPrefix)(); - if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit); - if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields()); - }, - - toElement: function(){ - return this.element; - }, - - getFields: function(){ - return (this.fields = this.element.getElements(this.options.fieldSelectors)); - }, - - watchFields: function(fields){ - fields.each(function(el){ - if (this.options.evaluateFieldsOnBlur) - el.addEvent('blur', this.validationMonitor.pass([el, false], this)); - if (this.options.evaluateFieldsOnChange) - el.addEvent('change', this.validationMonitor.pass([el, true], this)); - }, this); - }, - - validationMonitor: function(){ - clearTimeout(this.timer); - this.timer = this.validateField.delay(50, this, arguments); - }, - - onSubmit: function(event){ - if (this.validate(event)) this.reset(); - }, - - reset: function(){ - this.getFields().each(this.resetField, this); - return this; - }, - - validate: function(event){ - var result = this.getFields().map(function(field){ - return this.validateField(field, true); - }, this).every(function(v){ - return v; - }); - this.fireEvent('formValidate', [result, this.element, event]); - if (this.options.stopOnFailure && !result && event) event.preventDefault(); - return result; - }, - - validateField: function(field, force){ - if (this.paused) return true; - field = document.id(field); - var passed = !field.hasClass('validation-failed'); - var failed, warned; - if (this.options.serial && !force){ - failed = this.element.getElement('.validation-failed'); - warned = this.element.getElement('.warning'); - } - if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){ - var validationTypes = field.get('validators'); - var validators = validationTypes.some(function(cn){ - return this.getValidator(cn); - }, this); - var validatorsFailed = []; - validationTypes.each(function(className){ - if (className && !this.test(className, field)) validatorsFailed.include(className); - }, this); - passed = validatorsFailed.length === 0; - if (validators && !this.hasValidator(field, 'warnOnly')){ - if (passed){ - field.addClass('validation-passed').removeClass('validation-failed'); - this.fireEvent('elementPass', [field]); - } else { - field.addClass('validation-failed').removeClass('validation-passed'); - this.fireEvent('elementFail', [field, validatorsFailed]); - } - } - if (!warned){ - var warnings = validationTypes.some(function(cn){ - if (cn.test('^warn')) - return this.getValidator(cn.replace(/^warn-/,'')); - else return null; - }, this); - field.removeClass('warning'); - var warnResult = validationTypes.map(function(cn){ - if (cn.test('^warn')) - return this.test(cn.replace(/^warn-/,''), field, true); - else return null; - }, this); - } - } - return passed; - }, - - test: function(className, field, warn){ - field = document.id(field); - if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true; - var validator = this.getValidator(className); - if (warn != null) warn = false; - if (this.hasValidator(field, 'warnOnly')) warn = true; - var isValid = this.hasValidator(field, 'ignoreValidation') || (validator ? validator.test(field) : true); - if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]); - if (warn) return true; - return isValid; - }, - - hasValidator: function(field, value){ - return field.get('validators').contains(value); - }, - - resetField: function(field){ - field = document.id(field); - if (field){ - field.get('validators').each(function(className){ - if (className.test('^warn-')) className = className.replace(/^warn-/, ''); - field.removeClass('validation-failed'); - field.removeClass('warning'); - field.removeClass('validation-passed'); - }, this); - } - return this; - }, - - stop: function(){ - this.paused = true; - return this; - }, - - start: function(){ - this.paused = false; - return this; - }, - - ignoreField: function(field, warn){ - field = document.id(field); - if (field){ - this.enforceField(field); - if (warn) field.addClass('warnOnly'); - else field.addClass('ignoreValidation'); - } - return this; - }, - - enforceField: function(field){ - field = document.id(field); - if (field) field.removeClass('warnOnly').removeClass('ignoreValidation'); - return this; - } - -}); - -Form.Validator.getMsg = function(key){ - return Locale.get('FormValidator.' + key); -}; - -Form.Validator.adders = { - - validators:{}, - - add : function(className, options){ - this.validators[className] = new InputValidator(className, options); - //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace) - //extend these validators into it - //this allows validators to be global and/or per instance - if (!this.initialize){ - this.implement({ - validators: this.validators - }); - } - }, - - addAllThese : function(validators){ - Array.from(validators).each(function(validator){ - this.add(validator[0], validator[1]); - }, this); - }, - - getValidator: function(className){ - return this.validators[className.split(':')[0]]; - } - -}; - -Object.append(Form.Validator, Form.Validator.adders); - -Form.Validator.implement(Form.Validator.adders); - -Form.Validator.add('IsEmpty', { - - errorMsg: false, - test: function(element){ - if (element.type == 'select-one' || element.type == 'select') - return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != ''); - else - return ((element.get('value') == null) || (element.get('value').length == 0)); - } - -}); - -Form.Validator.addAllThese([ - - ['required', { - errorMsg: function(){ - return Form.Validator.getMsg('required'); - }, - test: function(element){ - return !Form.Validator.getValidator('IsEmpty').test(element); - } - }], - - ['minLength', { - errorMsg: function(element, props){ - if (typeOf(props.minLength) != 'null') - return Form.Validator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length }); - else return ''; - }, - test: function(element, props){ - if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0)); - else return true; - } - }], - - ['maxLength', { - errorMsg: function(element, props){ - //props is {maxLength:10} - if (typeOf(props.maxLength) != 'null') - return Form.Validator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length }); - else return ''; - }, - test: function(element, props){ - return element.get('value').length <= (props.maxLength || 10000); - } - }], - - ['validate-integer', { - errorMsg: Form.Validator.getMsg.pass('integer'), - test: function(element){ - return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value')); - } - }], - - ['validate-numeric', { - errorMsg: Form.Validator.getMsg.pass('numeric'), - test: function(element){ - return Form.Validator.getValidator('IsEmpty').test(element) || - (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value')); - } - }], - - ['validate-digits', { - errorMsg: Form.Validator.getMsg.pass('digits'), - test: function(element){ - return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value'))); - } - }], - - ['validate-alpha', { - errorMsg: Form.Validator.getMsg.pass('alpha'), - test: function(element){ - return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value')); - } - }], - - ['validate-alphanum', { - errorMsg: Form.Validator.getMsg.pass('alphanum'), - test: function(element){ - return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value')); - } - }], - - ['validate-date', { - errorMsg: function(element, props){ - if (Date.parse){ - var format = props.dateFormat || '%x'; - return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)}); - } else { - return Form.Validator.getMsg('dateInFormatMDY'); - } - }, - test: function(element, props){ - if (Form.Validator.getValidator('IsEmpty').test(element)) return true; - var dateLocale = Locale.getCurrent().sets.Date, - dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr].flatten().join('|'), 'i'), - value = element.get('value'), - wordsInValue = value.match(/[a-z]+/gi); - - if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false; - - var date = Date.parse(value), - format = props.dateFormat || '%x', - formatted = date.format(format); - - if (formatted != 'invalid date') element.set('value', formatted); - return date.isValid(); - } - }], - - ['validate-email', { - errorMsg: Form.Validator.getMsg.pass('email'), - test: function(element){ - /* - var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]", - local = '(?:' + chars + '\\.?){0,63}' + chars, - - label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', - hostname = '(?:' + label + '\\.)*' + label; - - octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)', - ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]', - - domain = '(?:' + hostname + '|' + ipv4 + ')'; - - var regex = new RegExp('^' + local + '@' + domain + '$', 'i'); - */ - return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value')); - } - }], - - ['validate-url', { - errorMsg: Form.Validator.getMsg.pass('url'), - test: function(element){ - return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value')); - } - }], - - ['validate-currency-dollar', { - errorMsg: Form.Validator.getMsg.pass('currencyDollar'), - test: function(element){ - return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); - } - }], - - ['validate-one-required', { - errorMsg: Form.Validator.getMsg.pass('oneRequired'), - test: function(element, props){ - var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']); - return p.getElements('input').some(function(el){ - if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked'); - return el.get('value'); - }); - } - }] - -]); - -Element.Properties.validator = { - - set: function(options){ - this.get('validator').setOptions(options); - }, - - get: function(){ - var validator = this.retrieve('validator'); - if (!validator){ - validator = new Form.Validator(this); - this.store('validator', validator); - } - return validator; - } - -}; - -Element.implement({ - - validate: function(options){ - if (options) this.set('validator', options); - return this.get('validator').validate(); - } - -}); - - -//<1.2compat> -//legacy -var FormValidator = Form.Validator; -// - - - - -/* ---- - -script: Form.Validator.Inline.js - -name: Form.Validator.Inline - -description: Extends Form.Validator to add inline messages. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - /Form.Validator - -provides: [Form.Validator.Inline] - -... -*/ - -Form.Validator.Inline = new Class({ - - Extends: Form.Validator, - - options: { - showError: function(errorElement){ - if (errorElement.reveal) errorElement.reveal(); - else errorElement.setStyle('display', 'block'); - }, - hideError: function(errorElement){ - if (errorElement.dissolve) errorElement.dissolve(); - else errorElement.setStyle('display', 'none'); - }, - scrollToErrorsOnSubmit: true, - scrollToErrorsOnBlur: false, - scrollToErrorsOnChange: false, - scrollFxOptions: { - transition: 'quad:out', - offset: { - y: -20 - } - } - }, - - initialize: function(form, options){ - this.parent(form, options); - this.addEvent('onElementValidate', function(isValid, field, className, warn){ - var validator = this.getValidator(className); - if (!isValid && validator.getError(field)){ - if (warn) field.addClass('warning'); - var advice = this.makeAdvice(className, field, validator.getError(field), warn); - this.insertAdvice(advice, field); - this.showAdvice(className, field); - } else { - this.hideAdvice(className, field); - } - }); - }, - - makeAdvice: function(className, field, error, warn){ - var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix; - errorMsg += (this.options.useTitles) ? field.title || error:error; - var cssClass = (warn) ? 'warning-advice' : 'validation-advice'; - var advice = this.getAdvice(className, field); - if (advice){ - advice = advice.set('html', errorMsg); - } else { - advice = new Element('div', { - html: errorMsg, - styles: { display: 'none' }, - id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field) - }).addClass(cssClass); - } - field.store('$moo:advice-' + className, advice); - return advice; - }, - - getFieldId : function(field){ - return field.id ? field.id : field.id = 'input_' + field.name; - }, - - showAdvice: function(className, field){ - var advice = this.getAdvice(className, field); - if ( - advice && - !field.retrieve('$moo:' + this.getPropName(className)) && - ( - advice.getStyle('display') == 'none' || - advice.getStyle('visiblity') == 'hidden' || - advice.getStyle('opacity') == 0 - ) - ){ - field.store('$moo:' + this.getPropName(className), true); - this.options.showError(advice); - this.fireEvent('showAdvice', [field, advice, className]); - } - }, - - hideAdvice: function(className, field){ - var advice = this.getAdvice(className, field); - if (advice && field.retrieve('$moo:' + this.getPropName(className))){ - field.store('$moo:' + this.getPropName(className), false); - this.options.hideError(advice); - this.fireEvent('hideAdvice', [field, advice, className]); - } - }, - - getPropName: function(className){ - return 'advice' + className; - }, - - resetField: function(field){ - field = document.id(field); - if (!field) return this; - this.parent(field); - field.get('validators').each(function(className){ - this.hideAdvice(className, field); - }, this); - return this; - }, - - getAllAdviceMessages: function(field, force){ - var advice = []; - if (field.hasClass('ignoreValidation') && !force) return advice; - var validators = field.get('validators').some(function(cn){ - var warner = cn.test('^warn-') || field.hasClass('warnOnly'); - if (warner) cn = cn.replace(/^warn-/, ''); - var validator = this.getValidator(cn); - if (!validator) return; - advice.push({ - message: validator.getError(field), - warnOnly: warner, - passed: validator.test(), - validator: validator - }); - }, this); - return advice; - }, - - getAdvice: function(className, field){ - return field.retrieve('$moo:advice-' + className); - }, - - insertAdvice: function(advice, field){ - //Check for error position prop - var props = field.get('validatorProps'); - //Build advice - if (!props.msgPos || !document.id(props.msgPos)){ - if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice); - else advice.inject(document.id(field), 'after'); - } else { - document.id(props.msgPos).grab(advice); - } - }, - - validateField: function(field, force, scroll){ - var result = this.parent(field, force); - if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){ - var failed = document.id(this).getElement('.validation-failed'); - var par = document.id(this).getParent(); - while (par != document.body && par.getScrollSize().y == par.getSize().y){ - par = par.getParent(); - } - var fx = par.retrieve('$moo:fvScroller'); - if (!fx && window.Fx && Fx.Scroll){ - fx = new Fx.Scroll(par, this.options.scrollFxOptions); - par.store('$moo:fvScroller', fx); - } - if (failed){ - if (fx) fx.toElement(failed); - else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20); - } - } - return result; - }, - - watchFields: function(fields){ - fields.each(function(el){ - if (this.options.evaluateFieldsOnBlur){ - el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this)); - } - if (this.options.evaluateFieldsOnChange){ - el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this)); - } - }, this); - } - -}); - - -/* ---- - -script: Form.Validator.Extras.js - -name: Form.Validator.Extras - -description: Additional validators for the Form.Validator class. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - /Form.Validator - -provides: [Form.Validator.Extras] - -... -*/ -Form.Validator.addAllThese([ - - ['validate-enforce-oncheck', { - test: function(element, props){ - var fv = element.getParent('form').retrieve('validator'); - if (!fv) return true; - (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){ - if (element.checked){ - fv.enforceField(item); - } else { - fv.ignoreField(item); - fv.resetField(item); - } - }); - return true; - } - }], - - ['validate-ignore-oncheck', { - test: function(element, props){ - var fv = element.getParent('form').retrieve('validator'); - if (!fv) return true; - (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){ - if (element.checked){ - fv.ignoreField(item); - fv.resetField(item); - } else { - fv.enforceField(item); - } - }); - return true; - } - }], - - ['validate-nospace', { - errorMsg: function(){ - return Form.Validator.getMsg('noSpace'); - }, - test: function(element, props){ - return !element.get('value').test(/\s/); - } - }], - - ['validate-toggle-oncheck', { - test: function(element, props){ - var fv = element.getParent('form').retrieve('validator'); - if (!fv) return true; - var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea'); - if (!element.checked){ - eleArr.each(function(item){ - fv.ignoreField(item); - fv.resetField(item); - }); - } else { - eleArr.each(function(item){ - fv.enforceField(item); - }); - } - return true; - } - }], - - ['validate-reqchk-bynode', { - errorMsg: function(){ - return Form.Validator.getMsg('reqChkByNode'); - }, - test: function(element, props){ - return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){ - return item.checked; - }); - } - }], - - ['validate-required-check', { - errorMsg: function(element, props){ - return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk'); - }, - test: function(element, props){ - return !!element.checked; - } - }], - - ['validate-reqchk-byname', { - errorMsg: function(element, props){ - return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')}); - }, - test: function(element, props){ - var grpName = props.groupName || element.get('name'); - var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){ - return item.checked; - }); - var fv = element.getParent('form').retrieve('validator'); - if (oneCheckedItem && fv) fv.resetField(element); - return oneCheckedItem; - } - }], - - ['validate-match', { - errorMsg: function(element, props){ - return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')}); - }, - test: function(element, props){ - var eleVal = element.get('value'); - var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value'); - return eleVal && matchVal ? eleVal == matchVal : true; - } - }], - - ['validate-after-date', { - errorMsg: function(element, props){ - return Form.Validator.getMsg('afterDate').substitute({ - label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate')) - }); - }, - test: function(element, props){ - var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date(); - var end = Date.parse(element.get('value')); - return end && start ? end >= start : true; - } - }], - - ['validate-before-date', { - errorMsg: function(element, props){ - return Form.Validator.getMsg('beforeDate').substitute({ - label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate')) - }); - }, - test: function(element, props){ - var start = Date.parse(element.get('value')); - var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date(); - return end && start ? end >= start : true; - } - }], - - ['validate-custom-required', { - errorMsg: function(){ - return Form.Validator.getMsg('required'); - }, - test: function(element, props){ - return element.get('value') != props.emptyValue; - } - }], - - ['validate-same-month', { - errorMsg: function(element, props){ - var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'); - var eleVal = element.get('value'); - if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth'); - }, - test: function(element, props){ - var d1 = Date.parse(element.get('value')); - var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value')); - return d1 && d2 ? d1.format('%B') == d2.format('%B') : true; - } - }], - - - ['validate-cc-num', { - errorMsg: function(element){ - var ccNum = element.get('value').replace(/[^0-9]/g, ''); - return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length}); - }, - test: function(element){ - // required is a different test - if (Form.Validator.getValidator('IsEmpty').test(element)) return true; - - // Clean number value - var ccNum = element.get('value'); - ccNum = ccNum.replace(/[^0-9]/g, ''); - - var valid_type = false; - - if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa'; - else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card'; - else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express'; - else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover'; - - if (valid_type){ - var sum = 0; - var cur = 0; - - for (var i=ccNum.length-1; i>=0; --i){ - cur = ccNum.charAt(i).toInt(); - if (cur == 0) continue; - - if ((ccNum.length-i) % 2 == 0) cur += cur; - if (cur > 9){ - cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); - } - - sum += cur; - } - if ((sum % 10) == 0) return true; - } - - var chunks = ''; - while (ccNum != ''){ - chunks += ' ' + ccNum.substr(0,4); - ccNum = ccNum.substr(4); - } - - element.getParent('form').retrieve('validator').ignoreField(element); - element.set('value', chunks.clean()); - element.getParent('form').retrieve('validator').enforceField(element); - return false; - } - }] - - -]); - - -/* ---- - -script: OverText.js - -name: OverText - -description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Options - - Core/Events - - Core/Element.Event - - Class.Binds - - Class.Occlude - - Element.Position - - Element.Shortcuts - -provides: [OverText] - -... -*/ - -var OverText = new Class({ - - Implements: [Options, Events, Class.Occlude], - - Binds: ['reposition', 'assert', 'focus', 'hide'], - - options: {/* - textOverride: null, - onFocus: function(){}, - onTextHide: function(textEl, inputEl){}, - onTextShow: function(textEl, inputEl){}, */ - element: 'label', - labelClass: 'overTxtLabel', - positionOptions: { - position: 'upperLeft', - edge: 'upperLeft', - offset: { - x: 4, - y: 2 - } - }, - poll: false, - pollInterval: 250, - wrap: false - }, - - property: 'OverText', - - initialize: function(element, options){ - element = this.element = document.id(element); - - if (this.occlude()) return this.occluded; - this.setOptions(options); - - this.attach(element); - OverText.instances.push(this); - - if (this.options.poll) this.poll(); - }, - - toElement: function(){ - return this.element; - }, - - attach: function(){ - var element = this.element, - options = this.options, - value = options.textOverride || element.get('alt') || element.get('title'); - - if (!value) return this; - - var text = this.text = new Element(options.element, { - 'class': options.labelClass, - styles: { - lineHeight: 'normal', - position: 'absolute', - cursor: 'text' - }, - html: value, - events: { - click: this.hide.pass(options.element == 'label', this) - } - }).inject(element, 'after'); - - if (options.element == 'label'){ - if (!element.get('id')) element.set('id', 'input_' + String.uniqueID()); - text.set('for', element.get('id')); - } - - if (options.wrap){ - this.textHolder = new Element('div.overTxtWrapper', { - styles: { - lineHeight: 'normal', - position: 'relative' - } - }).grab(text).inject(element, 'before'); - } - - return this.enable(); - }, - - destroy: function(){ - this.element.eliminate(this.property); // Class.Occlude storage - this.disable(); - if (this.text) this.text.destroy(); - if (this.textHolder) this.textHolder.destroy(); - return this; - }, - - disable: function(){ - this.element.removeEvents({ - focus: this.focus, - blur: this.assert, - change: this.assert - }); - window.removeEvent('resize', this.reposition); - this.hide(true, true); - return this; - }, - - enable: function(){ - this.element.addEvents({ - focus: this.focus, - blur: this.assert, - change: this.assert - }); - window.addEvent('resize', this.reposition); - this.assert(true); - this.reposition(); - return this; - }, - - wrap: function(){ - if (this.options.element == 'label'){ - if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID()); - this.text.set('for', this.element.get('id')); - } - }, - - startPolling: function(){ - this.pollingPaused = false; - return this.poll(); - }, - - poll: function(stop){ - //start immediately - //pause on focus - //resumeon blur - if (this.poller && !stop) return this; - if (stop){ - clearInterval(this.poller); - } else { - this.poller = (function(){ - if (!this.pollingPaused) this.assert(true); - }).periodical(this.options.pollInterval, this); - } - - return this; - }, - - stopPolling: function(){ - this.pollingPaused = true; - return this.poll(true); - }, - - focus: function(){ - if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this; - return this.hide(); - }, - - hide: function(suppressFocus, force){ - if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){ - this.text.hide(); - this.fireEvent('textHide', [this.text, this.element]); - this.pollingPaused = true; - if (!suppressFocus){ - try { - this.element.fireEvent('focus'); - this.element.focus(); - } catch(e){} //IE barfs if you call focus on hidden elements - } - } - return this; - }, - - show: function(){ - if (this.text && !this.text.isDisplayed()){ - this.text.show(); - this.reposition(); - this.fireEvent('textShow', [this.text, this.element]); - this.pollingPaused = false; - } - return this; - }, - - test: function(){ - return !this.element.get('value'); - }, - - assert: function(suppressFocus){ - return this[this.test() ? 'show' : 'hide'](suppressFocus); - }, - - reposition: function(){ - this.assert(true); - if (!this.element.isVisible()) return this.stopPolling().hide(); - if (this.text && this.test()){ - this.text.position(Object.merge(this.options.positionOptions, { - relativeTo: this.element - })); - } - return this; - } - -}); - -OverText.instances = []; - -Object.append(OverText, { - - each: function(fn){ - return OverText.instances.each(function(ot, i){ - if (ot.element && ot.text) fn.call(OverText, ot, i); - }); - }, - - update: function(){ - - return OverText.each(function(ot){ - return ot.reposition(); - }); - - }, - - hideAll: function(){ - - return OverText.each(function(ot){ - return ot.hide(true, true); - }); - - }, - - showAll: function(){ - return OverText.each(function(ot){ - return ot.show(); - }); - } - -}); - - - -/* ---- - -script: Fx.Elements.js - -name: Fx.Elements - -description: Effect to change any number of CSS properties of any number of Elements. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Fx.CSS - - /MooTools.More - -provides: [Fx.Elements] - -... -*/ - -Fx.Elements = new Class({ - - Extends: Fx.CSS, - - initialize: function(elements, options){ - this.elements = this.subject = $$(elements); - this.parent(options); - }, - - compute: function(from, to, delta){ - var now = {}; - - for (var i in from){ - var iFrom = from[i], iTo = to[i], iNow = now[i] = {}; - for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta); - } - - return now; - }, - - set: function(now){ - for (var i in now){ - if (!this.elements[i]) continue; - - var iNow = now[i]; - for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit); - } - - return this; - }, - - start: function(obj){ - if (!this.check(obj)) return this; - var from = {}, to = {}; - - for (var i in obj){ - if (!this.elements[i]) continue; - - var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {}; - - for (var p in iProps){ - var parsed = this.prepare(this.elements[i], p, iProps[p]); - iFrom[p] = parsed.from; - iTo[p] = parsed.to; - } - } - - return this.parent(from, to); - } - -}); - - -/* ---- - -script: Fx.Accordion.js - -name: Fx.Accordion - -description: An Fx.Elements extension which allows you to easily create accordion type controls. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Element.Event - - /Fx.Elements - -provides: [Fx.Accordion] - -... -*/ - -Fx.Accordion = new Class({ - - Extends: Fx.Elements, - - options: {/* - onActive: function(toggler, section){}, - onBackground: function(toggler, section){},*/ - fixedHeight: false, - fixedWidth: false, - display: 0, - show: false, - height: true, - width: false, - opacity: true, - alwaysHide: false, - trigger: 'click', - initialDisplayFx: true, - resetHeight: true - }, - - initialize: function(){ - var defined = function(obj){ - return obj != null; - }; - - var params = Array.link(arguments, { - 'container': Type.isElement, //deprecated - 'options': Type.isObject, - 'togglers': defined, - 'elements': defined - }); - this.parent(params.elements, params.options); - - var options = this.options, - togglers = this.togglers = $$(params.togglers); - - this.previous = -1; - this.internalChain = new Chain(); - - if (options.alwaysHide) this.options.link = 'chain'; - - if (options.show || this.options.show === 0){ - options.display = false; - this.previous = options.show; - } - - if (options.start){ - options.display = false; - options.show = false; - } - - var effects = this.effects = {}; - - if (options.opacity) effects.opacity = 'fullOpacity'; - if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth'; - if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight'; - - for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]); - - this.elements.each(function(el, i){ - if (options.show === i){ - this.fireEvent('active', [togglers[i], el]); - } else { - for (var fx in effects) el.setStyle(fx, 0); - } - }, this); - - if (options.display || options.display === 0 || options.initialDisplayFx === false){ - this.display(options.display, options.initialDisplayFx); - } - - if (options.fixedHeight !== false) options.resetHeight = false; - this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain)); - }, - - addSection: function(toggler, element){ - toggler = document.id(toggler); - element = document.id(element); - this.togglers.include(toggler); - this.elements.include(element); - - var togglers = this.togglers, - options = this.options, - test = togglers.contains(toggler), - idx = togglers.indexOf(toggler), - displayer = this.display.pass(idx, this); - - toggler.store('accordion:display', displayer) - .addEvent(options.trigger, displayer); - - if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'}); - if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'}); - - element.fullOpacity = 1; - if (options.fixedWidth) element.fullWidth = options.fixedWidth; - if (options.fixedHeight) element.fullHeight = options.fixedHeight; - element.setStyle('overflow', 'hidden'); - - if (!test) for (var fx in this.effects){ - element.setStyle(fx, 0); - } - return this; - }, - - removeSection: function(toggler, displayIndex){ - var togglers = this.togglers, - idx = togglers.indexOf(toggler), - element = this.elements[idx]; - - var remover = function(){ - togglers.erase(toggler); - this.elements.erase(element); - this.detach(toggler); - }.bind(this); - - if (this.now == idx || displayIndex != null){ - this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover); - } else { - remover(); - } - return this; - }, - - detach: function(toggler){ - var remove = function(toggler){ - toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display')); - }.bind(this); - - if (!toggler) this.togglers.each(remove); - else remove(toggler); - return this; - }, - - display: function(index, useFx){ - if (!this.check(index, useFx)) return this; - - var obj = {}, - elements = this.elements, - options = this.options, - effects = this.effects; - - if (useFx == null) useFx = true; - if (typeOf(index) == 'element') index = elements.indexOf(index); - if (index == this.previous && !options.alwaysHide) return this; - - if (options.resetHeight){ - var prev = elements[this.previous]; - if (prev && !this.selfHidden){ - for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]); - } - } - - if ((this.timer && options.link == 'chain') || (index === this.previous && !options.alwaysHide)) return this; - - this.previous = index; - this.selfHidden = false; - - elements.each(function(el, i){ - obj[i] = {}; - var hide; - if (i != index){ - hide = true; - } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){ - hide = true; - this.selfHidden = true; - } - this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]); - for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]]; - if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto'; - }, this); - - this.internalChain.clearChain(); - this.internalChain.chain(function(){ - if (options.resetHeight && !this.selfHidden){ - var el = elements[index]; - if (el) el.setStyle('height', 'auto'); - } - }.bind(this)); - - return useFx ? this.start(obj) : this.set(obj).internalChain.callChain(); - } - -}); - -/*<1.2compat>*/ -/* - Compatibility with 1.2.0 -*/ -var Accordion = new Class({ - - Extends: Fx.Accordion, - - initialize: function(){ - this.parent.apply(this, arguments); - var params = Array.link(arguments, {'container': Type.isElement}); - this.container = params.container; - }, - - addSection: function(toggler, element, pos){ - toggler = document.id(toggler); - element = document.id(element); - - var test = this.togglers.contains(toggler); - var len = this.togglers.length; - if (len && (!test || pos)){ - pos = pos != null ? pos : len - 1; - toggler.inject(this.togglers[pos], 'before'); - element.inject(toggler, 'after'); - } else if (this.container && !test){ - toggler.inject(this.container); - element.inject(this.container); - } - return this.parent.apply(this, arguments); - } - -}); -/**/ - - -/* ---- - -script: Fx.Move.js - -name: Fx.Move - -description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Fx.Morph - - /Element.Position - -provides: [Fx.Move] - -... -*/ - -Fx.Move = new Class({ - - Extends: Fx.Morph, - - options: { - relativeTo: document.body, - position: 'center', - edge: false, - offset: {x: 0, y: 0} - }, - - start: function(destination){ - var element = this.element, - topLeft = element.getStyles('top', 'left'); - if (topLeft.top == 'auto' || topLeft.left == 'auto'){ - element.setPosition(element.getPosition(element.getOffsetParent())); - } - return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true}))); - } - -}); - -Element.Properties.move = { - - set: function(options){ - this.get('move').cancel().setOptions(options); - return this; - }, - - get: function(){ - var move = this.retrieve('move'); - if (!move){ - move = new Fx.Move(this, {link: 'cancel'}); - this.store('move', move); - } - return move; - } - -}; - -Element.implement({ - - move: function(options){ - this.get('move').start(options); - return this; - } - -}); - - -/* ---- - -script: Fx.Scroll.js - -name: Fx.Scroll - -description: Effect to smoothly scroll any element, including the window. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Fx - - Core/Element.Event - - Core/Element.Dimensions - - /MooTools.More - -provides: [Fx.Scroll] - -... -*/ - -(function(){ - -Fx.Scroll = new Class({ - - Extends: Fx, - - options: { - offset: {x: 0, y: 0}, - wheelStops: true - }, - - initialize: function(element, options){ - this.element = this.subject = document.id(element); - this.parent(options); - - if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body); - - if (this.options.wheelStops){ - var stopper = this.element, - cancel = this.cancel.pass(false, this); - this.addEvent('start', function(){ - stopper.addEvent('mousewheel', cancel); - }, true); - this.addEvent('complete', function(){ - stopper.removeEvent('mousewheel', cancel); - }, true); - } - }, - - set: function(){ - var now = Array.flatten(arguments); - if (Browser.firefox) now = [Math.round(now[0]), Math.round(now[1])]; // not needed anymore in newer firefox versions - this.element.scrollTo(now[0], now[1]); - return this; - }, - - compute: function(from, to, delta){ - return [0, 1].map(function(i){ - return Fx.compute(from[i], to[i], delta); - }); - }, - - start: function(x, y){ - if (!this.check(x, y)) return this; - var scroll = this.element.getScroll(); - return this.parent([scroll.x, scroll.y], [x, y]); - }, - - calculateScroll: function(x, y){ - var element = this.element, - scrollSize = element.getScrollSize(), - scroll = element.getScroll(), - size = element.getSize(), - offset = this.options.offset, - values = {x: x, y: y}; - - for (var z in values){ - if (!values[z] && values[z] !== 0) values[z] = scroll[z]; - if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z]; - values[z] += offset[z]; - } - - return [values.x, values.y]; - }, - - toTop: function(){ - return this.start.apply(this, this.calculateScroll(false, 0)); - }, - - toLeft: function(){ - return this.start.apply(this, this.calculateScroll(0, false)); - }, - - toRight: function(){ - return this.start.apply(this, this.calculateScroll('right', false)); - }, - - toBottom: function(){ - return this.start.apply(this, this.calculateScroll(false, 'bottom')); - }, - - toElement: function(el, axes){ - axes = axes ? Array.from(axes) : ['x', 'y']; - var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll(); - var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){ - return axes.contains(axis) ? value + scroll[axis] : false; - }); - return this.start.apply(this, this.calculateScroll(position.x, position.y)); - }, - - toElementEdge: function(el, axes, offset){ - axes = axes ? Array.from(axes) : ['x', 'y']; - el = document.id(el); - var to = {}, - position = el.getPosition(this.element), - size = el.getSize(), - scroll = this.element.getScroll(), - containerSize = this.element.getSize(), - edge = { - x: position.x + size.x, - y: position.y + size.y - }; - - ['x', 'y'].each(function(axis){ - if (axes.contains(axis)){ - if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis]; - if (position[axis] < scroll[axis]) to[axis] = position[axis]; - } - if (to[axis] == null) to[axis] = scroll[axis]; - if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; - }, this); - - if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); - return this; - }, - - toElementCenter: function(el, axes, offset){ - axes = axes ? Array.from(axes) : ['x', 'y']; - el = document.id(el); - var to = {}, - position = el.getPosition(this.element), - size = el.getSize(), - scroll = this.element.getScroll(), - containerSize = this.element.getSize(); - - ['x', 'y'].each(function(axis){ - if (axes.contains(axis)){ - to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2; - } - if (to[axis] == null) to[axis] = scroll[axis]; - if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; - }, this); - - if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); - return this; - } - -}); - -//<1.2compat> -Fx.Scroll.implement({ - scrollToCenter: function(){ - return this.toElementCenter.apply(this, arguments); - }, - scrollIntoView: function(){ - return this.toElementEdge.apply(this, arguments); - } -}); -// - -function isBody(element){ - return (/^(?:body|html)$/i).test(element.tagName); -} - -})(); - - -/* ---- - -script: Fx.Slide.js - -name: Fx.Slide - -description: Effect to slide an element in and out of view. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Fx - - Core/Element.Style - - /MooTools.More - -provides: [Fx.Slide] - -... -*/ - -Fx.Slide = new Class({ - - Extends: Fx, - - options: { - mode: 'vertical', - wrapper: false, - hideOverflow: true, - resetHeight: false - }, - - initialize: function(element, options){ - element = this.element = this.subject = document.id(element); - this.parent(options); - options = this.options; - - var wrapper = element.retrieve('wrapper'), - styles = element.getStyles('margin', 'position', 'overflow'); - - if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'}); - if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles); - - if (!wrapper) wrapper = new Element('div', { - styles: styles - }).wraps(element); - - element.store('wrapper', wrapper).setStyle('margin', 0); - if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden'); - - this.now = []; - this.open = true; - this.wrapper = wrapper; - - this.addEvent('complete', function(){ - this.open = (wrapper['offset' + this.layout.capitalize()] != 0); - if (this.open && this.options.resetHeight) wrapper.setStyle('height', ''); - }, true); - }, - - vertical: function(){ - this.margin = 'margin-top'; - this.layout = 'height'; - this.offset = this.element.offsetHeight; - }, - - horizontal: function(){ - this.margin = 'margin-left'; - this.layout = 'width'; - this.offset = this.element.offsetWidth; - }, - - set: function(now){ - this.element.setStyle(this.margin, now[0]); - this.wrapper.setStyle(this.layout, now[1]); - return this; - }, - - compute: function(from, to, delta){ - return [0, 1].map(function(i){ - return Fx.compute(from[i], to[i], delta); - }); - }, - - start: function(how, mode){ - if (!this.check(how, mode)) return this; - this[mode || this.options.mode](); - - var margin = this.element.getStyle(this.margin).toInt(), - layout = this.wrapper.getStyle(this.layout).toInt(), - caseIn = [[margin, layout], [0, this.offset]], - caseOut = [[margin, layout], [-this.offset, 0]], - start; - - switch (how){ - case 'in': start = caseIn; break; - case 'out': start = caseOut; break; - case 'toggle': start = (layout == 0) ? caseIn : caseOut; - } - return this.parent(start[0], start[1]); - }, - - slideIn: function(mode){ - return this.start('in', mode); - }, - - slideOut: function(mode){ - return this.start('out', mode); - }, - - hide: function(mode){ - this[mode || this.options.mode](); - this.open = false; - return this.set([-this.offset, 0]); - }, - - show: function(mode){ - this[mode || this.options.mode](); - this.open = true; - return this.set([0, this.offset]); - }, - - toggle: function(mode){ - return this.start('toggle', mode); - } - -}); - -Element.Properties.slide = { - - set: function(options){ - this.get('slide').cancel().setOptions(options); - return this; - }, - - get: function(){ - var slide = this.retrieve('slide'); - if (!slide){ - slide = new Fx.Slide(this, {link: 'cancel'}); - this.store('slide', slide); - } - return slide; - } - -}; - -Element.implement({ - - slide: function(how, mode){ - how = how || 'toggle'; - var slide = this.get('slide'), toggle; - switch (how){ - case 'hide': slide.hide(mode); break; - case 'show': slide.show(mode); break; - case 'toggle': - var flag = this.retrieve('slide:flag', slide.open); - slide[flag ? 'slideOut' : 'slideIn'](mode); - this.store('slide:flag', !flag); - toggle = true; - break; - default: slide.start(how, mode); - } - if (!toggle) this.eliminate('slide:flag'); - return this; - } - -}); - - -/* ---- - -script: Fx.SmoothScroll.js - -name: Fx.SmoothScroll - -description: Class for creating a smooth scrolling effect to all internal links on the page. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Slick.Finder - - /Fx.Scroll - -provides: [Fx.SmoothScroll] - -... -*/ - -/*<1.2compat>*/var SmoothScroll = /**/Fx.SmoothScroll = new Class({ - - Extends: Fx.Scroll, - - options: { - axes: ['x', 'y'] - }, - - initialize: function(options, context){ - context = context || document; - this.doc = context.getDocument(); - this.parent(this.doc, options); - - var win = context.getWindow(), - location = win.location.href.match(/^[^#]*/)[0] + '#', - links = $$(this.options.links || this.doc.links); - - links.each(function(link){ - if (link.href.indexOf(location) != 0) return; - var anchor = link.href.substr(location.length); - if (anchor) this.useLink(link, anchor); - }, this); - - this.addEvent('complete', function(){ - win.location.hash = this.anchor; - this.element.scrollTo(this.to[0], this.to[1]); - }, true); - }, - - useLink: function(link, anchor){ - - link.addEvent('click', function(event){ - var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']'); - if (!el) return; - - event.preventDefault(); - this.toElement(el, this.options.axes).chain(function(){ - this.fireEvent('scrolledTo', [link, el]); - }.bind(this)); - - this.anchor = anchor; - - }.bind(this)); - - return this; - } -}); - - -/* ---- - -script: Fx.Sort.js - -name: Fx.Sort - -description: Defines Fx.Sort, a class that reorders lists with a transition. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Element.Dimensions - - /Fx.Elements - - /Element.Measure - -provides: [Fx.Sort] - -... -*/ - -Fx.Sort = new Class({ - - Extends: Fx.Elements, - - options: { - mode: 'vertical' - }, - - initialize: function(elements, options){ - this.parent(elements, options); - this.elements.each(function(el){ - if (el.getStyle('position') == 'static') el.setStyle('position', 'relative'); - }); - this.setDefaultOrder(); - }, - - setDefaultOrder: function(){ - this.currentOrder = this.elements.map(function(el, index){ - return index; - }); - }, - - sort: function(){ - if (!this.check(arguments)) return this; - var newOrder = Array.flatten(arguments); - - var top = 0, - left = 0, - next = {}, - zero = {}, - vert = this.options.mode == 'vertical'; - - var current = this.elements.map(function(el, index){ - var size = el.getComputedSize({styles: ['border', 'padding', 'margin']}); - var val; - if (vert){ - val = { - top: top, - margin: size['margin-top'], - height: size.totalHeight - }; - top += val.height - size['margin-top']; - } else { - val = { - left: left, - margin: size['margin-left'], - width: size.totalWidth - }; - left += val.width; - } - var plane = vert ? 'top' : 'left'; - zero[index] = {}; - var start = el.getStyle(plane).toInt(); - zero[index][plane] = start || 0; - return val; - }, this); - - this.set(zero); - newOrder = newOrder.map(function(i){ return i.toInt(); }); - if (newOrder.length != this.elements.length){ - this.currentOrder.each(function(index){ - if (!newOrder.contains(index)) newOrder.push(index); - }); - if (newOrder.length > this.elements.length) - newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length); - } - var margin = 0; - top = left = 0; - newOrder.each(function(item){ - var newPos = {}; - if (vert){ - newPos.top = top - current[item].top - margin; - top += current[item].height; - } else { - newPos.left = left - current[item].left; - left += current[item].width; - } - margin = margin + current[item].margin; - next[item]=newPos; - }, this); - var mapped = {}; - Array.clone(newOrder).sort().each(function(index){ - mapped[index] = next[index]; - }); - this.start(mapped); - this.currentOrder = newOrder; - - return this; - }, - - rearrangeDOM: function(newOrder){ - newOrder = newOrder || this.currentOrder; - var parent = this.elements[0].getParent(); - var rearranged = []; - this.elements.setStyle('opacity', 0); - //move each element and store the new default order - newOrder.each(function(index){ - rearranged.push(this.elements[index].inject(parent).setStyles({ - top: 0, - left: 0 - })); - }, this); - this.elements.setStyle('opacity', 1); - this.elements = $$(rearranged); - this.setDefaultOrder(); - return this; - }, - - getDefaultOrder: function(){ - return this.elements.map(function(el, index){ - return index; - }); - }, - - getCurrentOrder: function(){ - return this.currentOrder; - }, - - forward: function(){ - return this.sort(this.getDefaultOrder()); - }, - - backward: function(){ - return this.sort(this.getDefaultOrder().reverse()); - }, - - reverse: function(){ - return this.sort(this.currentOrder.reverse()); - }, - - sortByElements: function(elements){ - return this.sort(elements.map(function(el){ - return this.elements.indexOf(el); - }, this)); - }, - - swap: function(one, two){ - if (typeOf(one) == 'element') one = this.elements.indexOf(one); - if (typeOf(two) == 'element') two = this.elements.indexOf(two); - - var newOrder = Array.clone(this.currentOrder); - newOrder[this.currentOrder.indexOf(one)] = two; - newOrder[this.currentOrder.indexOf(two)] = one; - - return this.sort(newOrder); - } - -}); - - -/* ---- - -script: Drag.js - -name: Drag - -description: The base Drag Class. Can be used to drag and resize Elements using mouse events. - -license: MIT-style license - -authors: - - Valerio Proietti - - Tom Occhinno - - Jan Kassens - -requires: - - Core/Events - - Core/Options - - Core/Element.Event - - Core/Element.Style - - Core/Element.Dimensions - - /MooTools.More - -provides: [Drag] -... - -*/ - -var Drag = new Class({ - - Implements: [Events, Options], - - options: {/* - onBeforeStart: function(thisElement){}, - onStart: function(thisElement, event){}, - onSnap: function(thisElement){}, - onDrag: function(thisElement, event){}, - onCancel: function(thisElement){}, - onComplete: function(thisElement, event){},*/ - snap: 6, - unit: 'px', - grid: false, - style: true, - limit: false, - handle: false, - invert: false, - preventDefault: false, - stopPropagation: false, - modifiers: {x: 'left', y: 'top'} - }, - - initialize: function(){ - var params = Array.link(arguments, { - 'options': Type.isObject, - 'element': function(obj){ - return obj != null; - } - }); - - this.element = document.id(params.element); - this.document = this.element.getDocument(); - this.setOptions(params.options || {}); - var htype = typeOf(this.options.handle); - this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element; - this.mouse = {'now': {}, 'pos': {}}; - this.value = {'start': {}, 'now': {}}; - - this.selection = (Browser.ie) ? 'selectstart' : 'mousedown'; - - - if (Browser.ie && !Drag.ondragstartFixed){ - document.ondragstart = Function.from(false); - Drag.ondragstartFixed = true; - } - - this.bound = { - start: this.start.bind(this), - check: this.check.bind(this), - drag: this.drag.bind(this), - stop: this.stop.bind(this), - cancel: this.cancel.bind(this), - eventStop: Function.from(false) - }; - this.attach(); - }, - - attach: function(){ - this.handles.addEvent('mousedown', this.bound.start); - return this; - }, - - detach: function(){ - this.handles.removeEvent('mousedown', this.bound.start); - return this; - }, - - start: function(event){ - var options = this.options; - - if (event.rightClick) return; - - if (options.preventDefault) event.preventDefault(); - if (options.stopPropagation) event.stopPropagation(); - this.mouse.start = event.page; - - this.fireEvent('beforeStart', this.element); - - var limit = options.limit; - this.limit = {x: [], y: []}; - - var z, coordinates; - for (z in options.modifiers){ - if (!options.modifiers[z]) continue; - - var style = this.element.getStyle(options.modifiers[z]); - - // Some browsers (IE and Opera) don't always return pixels. - if (style && !style.match(/px$/)){ - if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent()); - style = coordinates[options.modifiers[z]]; - } - - if (options.style) this.value.now[z] = (style || 0).toInt(); - else this.value.now[z] = this.element[options.modifiers[z]]; - - if (options.invert) this.value.now[z] *= -1; - - this.mouse.pos[z] = event.page[z] - this.value.now[z]; - - if (limit && limit[z]){ - var i = 2; - while (i--){ - var limitZI = limit[z][i]; - if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI; - } - } - } - - if (typeOf(this.options.grid) == 'number') this.options.grid = { - x: this.options.grid, - y: this.options.grid - }; - - var events = { - mousemove: this.bound.check, - mouseup: this.bound.cancel - }; - events[this.selection] = this.bound.eventStop; - this.document.addEvents(events); - }, - - check: function(event){ - if (this.options.preventDefault) event.preventDefault(); - var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2))); - if (distance > this.options.snap){ - this.cancel(); - this.document.addEvents({ - mousemove: this.bound.drag, - mouseup: this.bound.stop - }); - this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element); - } - }, - - drag: function(event){ - var options = this.options; - - if (options.preventDefault) event.preventDefault(); - this.mouse.now = event.page; - - for (var z in options.modifiers){ - if (!options.modifiers[z]) continue; - this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z]; - - if (options.invert) this.value.now[z] *= -1; - - if (options.limit && this.limit[z]){ - if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){ - this.value.now[z] = this.limit[z][1]; - } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){ - this.value.now[z] = this.limit[z][0]; - } - } - - if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]); - - if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit); - else this.element[options.modifiers[z]] = this.value.now[z]; - } - - this.fireEvent('drag', [this.element, event]); - }, - - cancel: function(event){ - this.document.removeEvents({ - mousemove: this.bound.check, - mouseup: this.bound.cancel - }); - if (event){ - this.document.removeEvent(this.selection, this.bound.eventStop); - this.fireEvent('cancel', this.element); - } - }, - - stop: function(event){ - var events = { - mousemove: this.bound.drag, - mouseup: this.bound.stop - }; - events[this.selection] = this.bound.eventStop; - this.document.removeEvents(events); - if (event) this.fireEvent('complete', [this.element, event]); - } - -}); - -Element.implement({ - - makeResizable: function(options){ - var drag = new Drag(this, Object.merge({ - modifiers: { - x: 'width', - y: 'height' - } - }, options)); - - this.store('resizer', drag); - return drag.addEvent('drag', function(){ - this.fireEvent('resize', drag); - }.bind(this)); - } - -}); - - -/* ---- - -script: Drag.Move.js - -name: Drag.Move - -description: A Drag extension that provides support for the constraining of draggables to containers and droppables. - -license: MIT-style license - -authors: - - Valerio Proietti - - Tom Occhinno - - Jan Kassens - - Aaron Newton - - Scott Kyle - -requires: - - Core/Element.Dimensions - - /Drag - -provides: [Drag.Move] - -... -*/ - -Drag.Move = new Class({ - - Extends: Drag, - - options: {/* - onEnter: function(thisElement, overed){}, - onLeave: function(thisElement, overed){}, - onDrop: function(thisElement, overed, event){},*/ - droppables: [], - container: false, - precalculate: false, - includeMargins: true, - checkDroppables: true - }, - - initialize: function(element, options){ - this.parent(element, options); - element = this.element; - - this.droppables = $$(this.options.droppables); - this.container = document.id(this.options.container); - - if (this.container && typeOf(this.container) != 'element') - this.container = document.id(this.container.getDocument().body); - - if (this.options.style){ - if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){ - var parent = element.getOffsetParent(), - styles = element.getStyles('left', 'top'); - if (parent && (styles.left == 'auto' || styles.top == 'auto')){ - element.setPosition(element.getPosition(parent)); - } - } - - if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute'); - } - - this.addEvent('start', this.checkDroppables, true); - this.overed = null; - }, - - start: function(event){ - if (this.container) this.options.limit = this.calculateLimit(); - - if (this.options.precalculate){ - this.positions = this.droppables.map(function(el){ - return el.getCoordinates(); - }); - } - - this.parent(event); - }, - - calculateLimit: function(){ - var element = this.element, - container = this.container, - - offsetParent = document.id(element.getOffsetParent()) || document.body, - containerCoordinates = container.getCoordinates(offsetParent), - elementMargin = {}, - elementBorder = {}, - containerMargin = {}, - containerBorder = {}, - offsetParentPadding = {}; - - ['top', 'right', 'bottom', 'left'].each(function(pad){ - elementMargin[pad] = element.getStyle('margin-' + pad).toInt(); - elementBorder[pad] = element.getStyle('border-' + pad).toInt(); - containerMargin[pad] = container.getStyle('margin-' + pad).toInt(); - containerBorder[pad] = container.getStyle('border-' + pad).toInt(); - offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt(); - }, this); - - var width = element.offsetWidth + elementMargin.left + elementMargin.right, - height = element.offsetHeight + elementMargin.top + elementMargin.bottom, - left = 0, - top = 0, - right = containerCoordinates.right - containerBorder.right - width, - bottom = containerCoordinates.bottom - containerBorder.bottom - height; - - if (this.options.includeMargins){ - left += elementMargin.left; - top += elementMargin.top; - } else { - right += elementMargin.right; - bottom += elementMargin.bottom; - } - - if (element.getStyle('position') == 'relative'){ - var coords = element.getCoordinates(offsetParent); - coords.left -= element.getStyle('left').toInt(); - coords.top -= element.getStyle('top').toInt(); - - left -= coords.left; - top -= coords.top; - if (container.getStyle('position') != 'relative'){ - left += containerBorder.left; - top += containerBorder.top; - } - right += elementMargin.left - coords.left; - bottom += elementMargin.top - coords.top; - - if (container != offsetParent){ - left += containerMargin.left + offsetParentPadding.left; - top += ((Browser.ie6 || Browser.ie7) ? 0 : containerMargin.top) + offsetParentPadding.top; - } - } else { - left -= elementMargin.left; - top -= elementMargin.top; - if (container != offsetParent){ - left += containerCoordinates.left + containerBorder.left; - top += containerCoordinates.top + containerBorder.top; - } - } - - return { - x: [left, right], - y: [top, bottom] - }; - }, - - getDroppableCoordinates: function(element){ - var position = element.getCoordinates(); - if (element.getStyle('position') == 'fixed'){ - var scroll = window.getScroll(); - position.left += scroll.x; - position.right += scroll.x; - position.top += scroll.y; - position.bottom += scroll.y; - } - return position; - }, - - checkDroppables: function(){ - var overed = this.droppables.filter(function(el, i){ - el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el); - var now = this.mouse.now; - return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top); - }, this).getLast(); - - if (this.overed != overed){ - if (this.overed) this.fireEvent('leave', [this.element, this.overed]); - if (overed) this.fireEvent('enter', [this.element, overed]); - this.overed = overed; - } - }, - - drag: function(event){ - this.parent(event); - if (this.options.checkDroppables && this.droppables.length) this.checkDroppables(); - }, - - stop: function(event){ - this.checkDroppables(); - this.fireEvent('drop', [this.element, this.overed, event]); - this.overed = null; - return this.parent(event); - } - -}); - -Element.implement({ - - makeDraggable: function(options){ - var drag = new Drag.Move(this, options); - this.store('dragger', drag); - return drag; - } - -}); - - -/* ---- - -script: Slider.js - -name: Slider - -description: Class for creating horizontal and vertical slider controls. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Element.Dimensions - - /Class.Binds - - /Drag - - /Element.Measure - -provides: [Slider] - -... -*/ - -var Slider = new Class({ - - Implements: [Events, Options], - - Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'], - - options: {/* - onTick: function(intPosition){}, - onChange: function(intStep){}, - onComplete: function(strStep){},*/ - onTick: function(position){ - this.setKnobPosition(position); - }, - initialStep: 0, - snap: false, - offset: 0, - range: false, - wheel: false, - steps: 100, - mode: 'horizontal' - }, - - initialize: function(element, knob, options){ - this.setOptions(options); - options = this.options; - this.element = document.id(element); - knob = this.knob = document.id(knob); - this.previousChange = this.previousEnd = this.step = -1; - - var limit = {}, - modifiers = {x: false, y: false}; - - switch (options.mode){ - case 'vertical': - this.axis = 'y'; - this.property = 'top'; - this.offset = 'offsetHeight'; - break; - case 'horizontal': - this.axis = 'x'; - this.property = 'left'; - this.offset = 'offsetWidth'; - } - - this.setSliderDimensions(); - this.setRange(options.range); - - if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative'); - knob.setStyle(this.property, -options.offset); - modifiers[this.axis] = this.property; - limit[this.axis] = [-options.offset, this.full - options.offset]; - - var dragOptions = { - snap: 0, - limit: limit, - modifiers: modifiers, - onDrag: this.draggedKnob, - onStart: this.draggedKnob, - onBeforeStart: (function(){ - this.isDragging = true; - }).bind(this), - onCancel: function(){ - this.isDragging = false; - }.bind(this), - onComplete: function(){ - this.isDragging = false; - this.draggedKnob(); - this.end(); - }.bind(this) - }; - if (options.snap) this.setSnap(dragOptions); - - this.drag = new Drag(knob, dragOptions); - this.attach(); - if (options.initialStep != null) this.set(options.initialStep); - }, - - attach: function(){ - this.element.addEvent('mousedown', this.clickedElement); - if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement); - this.drag.attach(); - return this; - }, - - detach: function(){ - this.element.removeEvent('mousedown', this.clickedElement) - .removeEvent('mousewheel', this.scrolledElement); - this.drag.detach(); - return this; - }, - - autosize: function(){ - this.setSliderDimensions() - .setKnobPosition(this.toPosition(this.step)); - this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset]; - if (this.options.snap) this.setSnap(); - return this; - }, - - setSnap: function(options){ - if (!options) options = this.drag.options; - options.grid = Math.ceil(this.stepWidth); - options.limit[this.axis][1] = this.full; - return this; - }, - - setKnobPosition: function(position){ - if (this.options.snap) position = this.toPosition(this.step); - this.knob.setStyle(this.property, position); - return this; - }, - - setSliderDimensions: function(){ - this.full = this.element.measure(function(){ - this.half = this.knob[this.offset] / 2; - return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2); - }.bind(this)); - return this; - }, - - set: function(step){ - if (!((this.range > 0) ^ (step < this.min))) step = this.min; - if (!((this.range > 0) ^ (step > this.max))) step = this.max; - - this.step = Math.round(step); - return this.checkStep() - .fireEvent('tick', this.toPosition(this.step)) - .end(); - }, - - setRange: function(range, pos){ - this.min = Array.pick([range[0], 0]); - this.max = Array.pick([range[1], this.options.steps]); - this.range = this.max - this.min; - this.steps = this.options.steps || this.full; - this.stepSize = Math.abs(this.range) / this.steps; - this.stepWidth = this.stepSize * this.full / Math.abs(this.range); - if (range) this.set(Array.pick([pos, this.step]).floor(this.min).max(this.max)); - return this; - }, - - clickedElement: function(event){ - if (this.isDragging || event.target == this.knob) return; - - var dir = this.range < 0 ? -1 : 1, - position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half; - - position = position.limit(-this.options.offset, this.full - this.options.offset); - - this.step = Math.round(this.min + dir * this.toStep(position)); - - this.checkStep() - .fireEvent('tick', position) - .end(); - }, - - scrolledElement: function(event){ - var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0); - this.set(this.step + (mode ? -1 : 1) * this.stepSize); - event.stop(); - }, - - draggedKnob: function(){ - var dir = this.range < 0 ? -1 : 1, - position = this.drag.value.now[this.axis]; - - position = position.limit(-this.options.offset, this.full -this.options.offset); - - this.step = Math.round(this.min + dir * this.toStep(position)); - this.checkStep(); - }, - - checkStep: function(){ - var step = this.step; - if (this.previousChange != step){ - this.previousChange = step; - this.fireEvent('change', step); - } - return this; - }, - - end: function(){ - var step = this.step; - if (this.previousEnd !== step){ - this.previousEnd = step; - this.fireEvent('complete', step + ''); - } - return this; - }, - - toStep: function(position){ - var step = (position + this.options.offset) * this.stepSize / this.full * this.steps; - return this.options.steps ? Math.round(step -= step % this.stepSize) : step; - }, - - toPosition: function(step){ - return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset; - } - -}); - - -/* ---- - -script: Sortables.js - -name: Sortables - -description: Class for creating a drag and drop sorting interface for lists of items. - -license: MIT-style license - -authors: - - Tom Occhino - -requires: - - Core/Fx.Morph - - /Drag.Move - -provides: [Sortables] - -... -*/ - -var Sortables = new Class({ - - Implements: [Events, Options], - - options: {/* - onSort: function(element, clone){}, - onStart: function(element, clone){}, - onComplete: function(element){},*/ - opacity: 1, - clone: false, - revert: false, - handle: false, - dragOptions: {}/*<1.2compat>*/, - snap: 4, - constrain: false, - preventDefault: false - /**/ - }, - - initialize: function(lists, options){ - this.setOptions(options); - - this.elements = []; - this.lists = []; - this.idle = true; - - this.addLists($$(document.id(lists) || lists)); - - if (!this.options.clone) this.options.revert = false; - if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({ - duration: 250, - link: 'cancel' - }, this.options.revert)); - }, - - attach: function(){ - this.addLists(this.lists); - return this; - }, - - detach: function(){ - this.lists = this.removeLists(this.lists); - return this; - }, - - addItems: function(){ - Array.flatten(arguments).each(function(element){ - this.elements.push(element); - var start = element.retrieve('sortables:start', function(event){ - this.start.call(this, event, element); - }.bind(this)); - (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start); - }, this); - return this; - }, - - addLists: function(){ - Array.flatten(arguments).each(function(list){ - this.lists.include(list); - this.addItems(list.getChildren()); - }, this); - return this; - }, - - removeItems: function(){ - return $$(Array.flatten(arguments).map(function(element){ - this.elements.erase(element); - var start = element.retrieve('sortables:start'); - (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start); - - return element; - }, this)); - }, - - removeLists: function(){ - return $$(Array.flatten(arguments).map(function(list){ - this.lists.erase(list); - this.removeItems(list.getChildren()); - - return list; - }, this)); - }, - - getClone: function(event, element){ - if (!this.options.clone) return new Element(element.tagName).inject(document.body); - if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list); - var clone = element.clone(true).setStyles({ - margin: 0, - position: 'absolute', - visibility: 'hidden', - width: element.getStyle('width') - }).addEvent('mousedown', function(event){ - element.fireEvent('mousedown', event); - }); - //prevent the duplicated radio inputs from unchecking the real one - if (clone.get('html').test('radio')){ - clone.getElements('input[type=radio]').each(function(input, i){ - input.set('name', 'clone_' + i); - if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true); - }); - } - - return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent())); - }, - - getDroppables: function(){ - var droppables = this.list.getChildren().erase(this.clone).erase(this.element); - if (!this.options.constrain) droppables.append(this.lists).erase(this.list); - return droppables; - }, - - insert: function(dragging, element){ - var where = 'inside'; - if (this.lists.contains(element)){ - this.list = element; - this.drag.droppables = this.getDroppables(); - } else { - where = this.element.getAllPrevious().contains(element) ? 'before' : 'after'; - } - this.element.inject(element, where); - this.fireEvent('sort', [this.element, this.clone]); - }, - - start: function(event, element){ - if ( - !this.idle || - event.rightClick || - ['button', 'input', 'a'].contains(event.target.get('tag')) - ) return; - - this.idle = false; - this.element = element; - this.opacity = element.get('opacity'); - this.list = element.getParent(); - this.clone = this.getClone(event, element); - - this.drag = new Drag.Move(this.clone, Object.merge({ - /*<1.2compat>*/ - preventDefault: this.options.preventDefault, - snap: this.options.snap, - container: this.options.constrain && this.element.getParent(), - /**/ - droppables: this.getDroppables() - }, this.options.dragOptions)).addEvents({ - onSnap: function(){ - event.stop(); - this.clone.setStyle('visibility', 'visible'); - this.element.set('opacity', this.options.opacity || 0); - this.fireEvent('start', [this.element, this.clone]); - }.bind(this), - onEnter: this.insert.bind(this), - onCancel: this.end.bind(this), - onComplete: this.end.bind(this) - }); - - this.clone.inject(this.element, 'before'); - this.drag.start(event); - }, - - end: function(){ - this.drag.detach(); - this.element.set('opacity', this.opacity); - if (this.effect){ - var dim = this.element.getStyles('width', 'height'), - clone = this.clone, - pos = clone.computePosition(this.element.getPosition(this.clone.getOffsetParent())); - - var destroy = function(){ - this.removeEvent('cancel', destroy); - clone.destroy(); - }; - - this.effect.element = clone; - this.effect.start({ - top: pos.top, - left: pos.left, - width: dim.width, - height: dim.height, - opacity: 0.25 - }).addEvent('cancel', destroy).chain(destroy); - } else { - this.clone.destroy(); - } - this.reset(); - }, - - reset: function(){ - this.idle = true; - this.fireEvent('complete', this.element); - }, - - serialize: function(){ - var params = Array.link(arguments, { - modifier: Type.isFunction, - index: function(obj){ - return obj != null; - } - }); - var serial = this.lists.map(function(list){ - return list.getChildren().map(params.modifier || function(element){ - return element.get('id'); - }, this); - }, this); - - var index = params.index; - if (this.lists.length == 1) index = 0; - return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial; - } - -}); - - -/* ---- - -script: Request.JSONP.js - -name: Request.JSONP - -description: Defines Request.JSONP, a class for cross domain javascript via script injection. - -license: MIT-style license - -authors: - - Aaron Newton - - Guillermo Rauch - - Arian Stolwijk - -requires: - - Core/Element - - Core/Request - - MooTools.More - -provides: [Request.JSONP] - -... -*/ - -Request.JSONP = new Class({ - - Implements: [Chain, Events, Options], - - options: {/* - onRequest: function(src, scriptElement){}, - onComplete: function(data){}, - onSuccess: function(data){}, - onCancel: function(){}, - onTimeout: function(){}, - onError: function(){}, */ - onRequest: function(src){ - if (this.options.log && window.console && console.log){ - console.log('JSONP retrieving script with url:' + src); - } - }, - onError: function(src){ - if (this.options.log && window.console && console.warn){ - console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs'); - } - }, - url: '', - callbackKey: 'callback', - injectScript: document.head, - data: '', - link: 'ignore', - timeout: 0, - log: false - }, - - initialize: function(options){ - this.setOptions(options); - }, - - send: function(options){ - if (!Request.prototype.check.call(this, options)) return this; - this.running = true; - - var type = typeOf(options); - if (type == 'string' || type == 'element') options = {data: options}; - options = Object.merge(this.options, options || {}); - - var data = options.data; - switch (typeOf(data)){ - case 'element': data = document.id(data).toQueryString(); break; - case 'object': case 'hash': data = Object.toQueryString(data); - } - - var index = this.index = Request.JSONP.counter++; - - var src = options.url + - (options.url.test('\\?') ? '&' :'?') + - (options.callbackKey) + - '=Request.JSONP.request_map.request_'+ index + - (data ? '&' + data : ''); - - if (src.length > 2083) this.fireEvent('error', src); - - Request.JSONP.request_map['request_' + index] = function(){ - this.success(arguments, index); - }.bind(this); - - var script = this.getScript(src).inject(options.injectScript); - this.fireEvent('request', [src, script]); - - if (options.timeout) this.timeout.delay(options.timeout, this); - - return this; - }, - - getScript: function(src){ - if (!this.script) this.script = new Element('script', { - type: 'text/javascript', - async: true, - src: src - }); - return this.script; - }, - - success: function(args, index){ - if (!this.running) return; - this.clear() - .fireEvent('complete', args).fireEvent('success', args) - .callChain(); - }, - - cancel: function(){ - if (this.running) this.clear().fireEvent('cancel'); - return this; - }, - - isRunning: function(){ - return !!this.running; - }, - - clear: function(){ - this.running = false; - if (this.script){ - this.script.destroy(); - this.script = null; - } - return this; - }, - - timeout: function(){ - if (this.running){ - this.running = false; - this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel(); - } - return this; - } - -}); - -Request.JSONP.counter = 0; -Request.JSONP.request_map = {}; - - -/* ---- - -script: Request.Queue.js - -name: Request.Queue - -description: Controls several instances of Request and its variants to run only one request at a time. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Element - - Core/Request - - /Class.Binds - -provides: [Request.Queue] - -... -*/ - -Request.Queue = new Class({ - - Implements: [Options, Events], - - Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'], - - options: {/* - onRequest: function(argsPassedToOnRequest){}, - onSuccess: function(argsPassedToOnSuccess){}, - onComplete: function(argsPassedToOnComplete){}, - onCancel: function(argsPassedToOnCancel){}, - onException: function(argsPassedToOnException){}, - onFailure: function(argsPassedToOnFailure){}, - onEnd: function(){}, - */ - stopOnFailure: true, - autoAdvance: true, - concurrent: 1, - requests: {} - }, - - initialize: function(options){ - var requests; - if (options){ - requests = options.requests; - delete options.requests; - } - this.setOptions(options); - this.requests = {}; - this.queue = []; - this.reqBinders = {}; - - if (requests) this.addRequests(requests); - }, - - addRequest: function(name, request){ - this.requests[name] = request; - this.attach(name, request); - return this; - }, - - addRequests: function(obj){ - Object.each(obj, function(req, name){ - this.addRequest(name, req); - }, this); - return this; - }, - - getName: function(req){ - return Object.keyOf(this.requests, req); - }, - - attach: function(name, req){ - if (req._groupSend) return this; - ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ - if (!this.reqBinders[name]) this.reqBinders[name] = {}; - this.reqBinders[name][evt] = function(){ - this['on' + evt.capitalize()].apply(this, [name, req].append(arguments)); - }.bind(this); - req.addEvent(evt, this.reqBinders[name][evt]); - }, this); - req._groupSend = req.send; - req.send = function(options){ - this.send(name, options); - return req; - }.bind(this); - return this; - }, - - removeRequest: function(req){ - var name = typeOf(req) == 'object' ? this.getName(req) : req; - if (!name && typeOf(name) != 'string') return this; - req = this.requests[name]; - if (!req) return this; - ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ - req.removeEvent(evt, this.reqBinders[name][evt]); - }, this); - req.send = req._groupSend; - delete req._groupSend; - return this; - }, - - getRunning: function(){ - return Object.filter(this.requests, function(r){ - return r.running; - }); - }, - - isRunning: function(){ - return !!(Object.keys(this.getRunning()).length); - }, - - send: function(name, options){ - var q = function(){ - this.requests[name]._groupSend(options); - this.queue.erase(q); - }.bind(this); - - q.name = name; - if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q); - else q(); - return this; - }, - - hasNext: function(name){ - return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length; - }, - - resume: function(){ - this.error = false; - (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this); - return this; - }, - - runNext: function(name){ - if (!this.queue.length) return this; - if (!name){ - this.queue[0](); - } else { - var found; - this.queue.each(function(q){ - if (!found && q.name == name){ - found = true; - q(); - } - }); - } - return this; - }, - - runAll: function(){ - this.queue.each(function(q){ - q(); - }); - return this; - }, - - clear: function(name){ - if (!name){ - this.queue.empty(); - } else { - this.queue = this.queue.map(function(q){ - if (q.name != name) return q; - else return false; - }).filter(function(q){ - return q; - }); - } - return this; - }, - - cancel: function(name){ - this.requests[name].cancel(); - return this; - }, - - onRequest: function(){ - this.fireEvent('request', arguments); - }, - - onComplete: function(){ - this.fireEvent('complete', arguments); - if (!this.queue.length) this.fireEvent('end'); - }, - - onCancel: function(){ - if (this.options.autoAdvance && !this.error) this.runNext(); - this.fireEvent('cancel', arguments); - }, - - onSuccess: function(){ - if (this.options.autoAdvance && !this.error) this.runNext(); - this.fireEvent('success', arguments); - }, - - onFailure: function(){ - this.error = true; - if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); - this.fireEvent('failure', arguments); - }, - - onException: function(){ - this.error = true; - if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); - this.fireEvent('exception', arguments); - } - -}); - - -/* ---- - -script: Request.Periodical.js - -name: Request.Periodical - -description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load - -license: MIT-style license - -authors: - - Christoph Pojer - -requires: - - Core/Request - - /MooTools.More - -provides: [Request.Periodical] - -... -*/ - -Request.implement({ - - options: { - initialDelay: 5000, - delay: 5000, - limit: 60000 - }, - - startTimer: function(data){ - var fn = function(){ - if (!this.running) this.send({data: data}); - }; - this.lastDelay = this.options.initialDelay; - this.timer = fn.delay(this.lastDelay, this); - this.completeCheck = function(response){ - clearTimeout(this.timer); - this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit); - this.timer = fn.delay(this.lastDelay, this); - }; - return this.addEvent('complete', this.completeCheck); - }, - - stopTimer: function(){ - clearTimeout(this.timer); - return this.removeEvent('complete', this.completeCheck); - } - -}); - - -/* ---- - -script: Assets.js - -name: Assets - -description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Element.Event - - /MooTools.More - -provides: [Assets] - -... -*/ - -var Asset = { - - javascript: function(source, properties){ - if (!properties) properties = {}; - - var script = new Element('script', {src: source, type: 'text/javascript'}), - doc = properties.document || document, - loaded = 0, - loadEvent = properties.onload || properties.onLoad; - - var load = loadEvent ? function(){ // make sure we only call the event once - if (++loaded == 1) loadEvent.call(this); - } : function(){}; - - delete properties.onload; - delete properties.onLoad; - delete properties.document; - - return script.addEvents({ - load: load, - readystatechange: function(){ - if (['loaded', 'complete'].contains(this.readyState)) load.call(this); - } - }).set(properties).inject(doc.head); - }, - - css: function(source, properties){ - if (!properties) properties = {}; - - var link = new Element('link', { - rel: 'stylesheet', - media: 'screen', - type: 'text/css', - href: source - }); - - var load = properties.onload || properties.onLoad, - doc = properties.document || document; - - delete properties.onload; - delete properties.onLoad; - delete properties.document; - - if (load) link.addEvent('load', load); - return link.set(properties).inject(doc.head); - }, - - image: function(source, properties){ - if (!properties) properties = {}; - - var image = new Image(), - element = document.id(image) || new Element('img'); - - ['load', 'abort', 'error'].each(function(name){ - var type = 'on' + name, - cap = 'on' + name.capitalize(), - event = properties[type] || properties[cap] || function(){}; - - delete properties[cap]; - delete properties[type]; - - image[type] = function(){ - if (!image) return; - if (!element.parentNode){ - element.width = image.width; - element.height = image.height; - } - image = image.onload = image.onabort = image.onerror = null; - event.delay(1, element, element); - element.fireEvent(name, element, 1); - }; - }); - - image.src = element.src = source; - if (image && image.complete) image.onload.delay(1); - return element.set(properties); - }, - - images: function(sources, options){ - sources = Array.from(sources); - - var fn = function(){}, - counter = 0; - - options = Object.merge({ - onComplete: fn, - onProgress: fn, - onError: fn, - properties: {} - }, options); - - return new Elements(sources.map(function(source, index){ - return Asset.image(source, Object.append(options.properties, { - onload: function(){ - counter++; - options.onProgress.call(this, counter, index, source); - if (counter == sources.length) options.onComplete(); - }, - onerror: function(){ - counter++; - options.onError.call(this, counter, index, source); - if (counter == sources.length) options.onComplete(); - } - })); - })); - } - -}; - - -/* ---- - -script: Color.js - -name: Color - -description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Array - - Core/String - - Core/Number - - Core/Hash - - Core/Function - - MooTools.More - -provides: [Color] - -... -*/ - -(function(){ - -var Color = this.Color = new Type('Color', function(color, type){ - if (arguments.length >= 3){ - type = 'rgb'; color = Array.slice(arguments, 0, 3); - } else if (typeof color == 'string'){ - if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true); - else if (color.match(/hsb/)) color = color.hsbToRgb(); - else color = color.hexToRgb(true); - } - type = type || 'rgb'; - switch (type){ - case 'hsb': - var old = color; - color = color.hsbToRgb(); - color.hsb = old; - break; - case 'hex': color = color.hexToRgb(true); break; - } - color.rgb = color.slice(0, 3); - color.hsb = color.hsb || color.rgbToHsb(); - color.hex = color.rgbToHex(); - return Object.append(color, this); -}); - -Color.implement({ - - mix: function(){ - var colors = Array.slice(arguments); - var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50; - var rgb = this.slice(); - colors.each(function(color){ - color = new Color(color); - for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha)); - }); - return new Color(rgb, 'rgb'); - }, - - invert: function(){ - return new Color(this.map(function(value){ - return 255 - value; - })); - }, - - setHue: function(value){ - return new Color([value, this.hsb[1], this.hsb[2]], 'hsb'); - }, - - setSaturation: function(percent){ - return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb'); - }, - - setBrightness: function(percent){ - return new Color([this.hsb[0], this.hsb[1], percent], 'hsb'); - } - -}); - -this.$RGB = function(r, g, b){ - return new Color([r, g, b], 'rgb'); -}; - -this.$HSB = function(h, s, b){ - return new Color([h, s, b], 'hsb'); -}; - -this.$HEX = function(hex){ - return new Color(hex, 'hex'); -}; - -Array.implement({ - - rgbToHsb: function(){ - var red = this[0], - green = this[1], - blue = this[2], - hue = 0; - var max = Math.max(red, green, blue), - min = Math.min(red, green, blue); - var delta = max - min; - var brightness = max / 255, - saturation = (max != 0) ? delta / max : 0; - if (saturation != 0){ - var rr = (max - red) / delta; - var gr = (max - green) / delta; - var br = (max - blue) / delta; - if (red == max) hue = br - gr; - else if (green == max) hue = 2 + rr - br; - else hue = 4 + gr - rr; - hue /= 6; - if (hue < 0) hue++; - } - return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)]; - }, - - hsbToRgb: function(){ - var br = Math.round(this[2] / 100 * 255); - if (this[1] == 0){ - return [br, br, br]; - } else { - var hue = this[0] % 360; - var f = hue % 60; - var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255); - var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255); - var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255); - switch (Math.floor(hue / 60)){ - case 0: return [br, t, p]; - case 1: return [q, br, p]; - case 2: return [p, br, t]; - case 3: return [p, q, br]; - case 4: return [t, p, br]; - case 5: return [br, p, q]; - } - } - return false; - } - -}); - -String.implement({ - - rgbToHsb: function(){ - var rgb = this.match(/\d{1,3}/g); - return (rgb) ? rgb.rgbToHsb() : null; - }, - - hsbToRgb: function(){ - var hsb = this.match(/\d{1,3}/g); - return (hsb) ? hsb.hsbToRgb() : null; - } - -}); - -})(); - - - -/* ---- - -script: Group.js - -name: Group - -description: Class for monitoring collections of events - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Events - - /MooTools.More - -provides: [Group] - -... -*/ - -(function(){ - -this.Group = new Class({ - - initialize: function(){ - this.instances = Array.flatten(arguments); - this.events = {}; - this.checker = {}; - }, - - addEvent: function(type, fn){ - this.checker[type] = this.checker[type] || {}; - this.events[type] = this.events[type] || []; - if (this.events[type].contains(fn)) return false; - else this.events[type].push(fn); - this.instances.each(function(instance, i){ - instance.addEvent(type, this.check.pass([type, instance, i], this)); - }, this); - return this; - }, - - check: function(type, instance, i){ - this.checker[type][i] = true; - var every = this.instances.every(function(current, j){ - return this.checker[type][j] || false; - }, this); - if (!every) return; - this.checker[type] = {}; - this.events[type].each(function(event){ - event.call(this, this.instances, instance); - }, this); - } - -}); - -})(); - - - -/* ---- - -script: Hash.Cookie.js - -name: Hash.Cookie - -description: Class for creating, reading, and deleting Cookies in JSON format. - -license: MIT-style license - -authors: - - Valerio Proietti - - Aaron Newton - -requires: - - Core/Cookie - - Core/JSON - - /MooTools.More - - /Hash - -provides: [Hash.Cookie] - -... -*/ - -Hash.Cookie = new Class({ - - Extends: Cookie, - - options: { - autoSave: true - }, - - initialize: function(name, options){ - this.parent(name, options); - this.load(); - }, - - save: function(){ - var value = JSON.encode(this.hash); - if (!value || value.length > 4096) return false; //cookie would be truncated! - if (value == '{}') this.dispose(); - else this.write(value); - return true; - }, - - load: function(){ - this.hash = new Hash(JSON.decode(this.read(), true)); - return this; - } - -}); - -Hash.each(Hash.prototype, function(method, name){ - if (typeof method == 'function') Hash.Cookie.implement(name, function(){ - var value = method.apply(this.hash, arguments); - if (this.options.autoSave) this.save(); - return value; - }); -}); - - -/* ---- -name: Table -description: LUA-Style table implementation. -license: MIT-style license -authors: - - Valerio Proietti -requires: [Core/Array] -provides: [Table] -... -*/ - -(function(){ - -var Table = this.Table = function(){ - - this.length = 0; - var keys = [], - values = []; - - this.set = function(key, value){ - var index = keys.indexOf(key); - if (index == -1){ - var length = keys.length; - keys[length] = key; - values[length] = value; - this.length++; - } else { - values[index] = value; - } - return this; - }; - - this.get = function(key){ - var index = keys.indexOf(key); - return (index == -1) ? null : values[index]; - }; - - this.erase = function(key){ - var index = keys.indexOf(key); - if (index != -1){ - this.length--; - keys.splice(index, 1); - return values.splice(index, 1)[0]; - } - return null; - }; - - this.each = this.forEach = function(fn, bind){ - for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this); - }; - -}; - -if (this.Type) new Type('Table', Table); - -})(); - - -/* ---- - -script: HtmlTable.js - -name: HtmlTable - -description: Builds table elements with methods to add rows. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - Core/Options - - Core/Events - - /Class.Occlude - -provides: [HtmlTable] - -... -*/ - -var HtmlTable = new Class({ - - Implements: [Options, Events, Class.Occlude], - - options: { - properties: { - cellpadding: 0, - cellspacing: 0, - border: 0 - }, - rows: [], - headers: [], - footers: [] - }, - - property: 'HtmlTable', - - initialize: function(){ - var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString}); - this.setOptions(params.options); - if (!params.table && params.id) params.table = document.id(params.id); - this.element = params.table || new Element('table', this.options.properties); - if (this.occlude()) return this.occluded; - this.build(); - }, - - build: function(){ - this.element.store('HtmlTable', this); - - this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element); - $$(this.body.rows); - - if (this.options.headers.length) this.setHeaders(this.options.headers); - else this.thead = document.id(this.element.tHead); - - if (this.thead) this.head = this.getHead(); - if (this.options.footers.length) this.setFooters(this.options.footers); - - this.tfoot = document.id(this.element.tFoot); - if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]); - - this.options.rows.each(function(row){ - this.push(row); - }, this); - }, - - toElement: function(){ - return this.element; - }, - - empty: function(){ - this.body.empty(); - return this; - }, - - set: function(what, items){ - var target = (what == 'headers') ? 'tHead' : 'tFoot', - lower = target.toLowerCase(); - - this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty(); - var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td'); - - if (what == 'headers') this.head = this.getHead(); - else this.foot = this.getHead(); - - return data; - }, - - getHead: function(){ - var rows = this.thead.rows; - return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false; - }, - - setHeaders: function(headers){ - this.set('headers', headers); - return this; - }, - - setFooters: function(footers){ - this.set('footers', footers); - return this; - }, - - push: function(row, rowProperties, target, tag, where){ - if (typeOf(row) == 'element' && row.get('tag') == 'tr'){ - row.inject(target || this.body, where); - return { - tr: row, - tds: row.getChildren('td') - }; - } - - var tds = row.map(function(data){ - var td = new Element(tag || 'td', data ? data.properties : {}), - content = (data ? data.content : '') || data, - type = typeOf(content); - - if (['element', 'array', 'collection', 'elements'].contains(type)) td.adopt(content); - else td.set('html', content); - - return td; - }); - - return { - tr: new Element('tr', rowProperties).inject(target || this.body, where).adopt(tds), - tds: tds - }; - } - -}); - - -['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){ - HtmlTable.implement(method, function(){ - this.element[method].apply(this.element, arguments); - return this; - }); -}); - - - - -/* ---- - -script: HtmlTable.Zebra.js - -name: HtmlTable.Zebra - -description: Builds a stripy table with methods to add rows. - -license: MIT-style license - -authors: - - Harald Kirschner - - Aaron Newton - -requires: - - /HtmlTable - - /Class.refactor - -provides: [HtmlTable.Zebra] - -... -*/ - -HtmlTable = Class.refactor(HtmlTable, { - - options: { - classZebra: 'table-tr-odd', - zebra: true - }, - - initialize: function(){ - this.previous.apply(this, arguments); - if (this.occluded) return this.occluded; - if (this.options.zebra) this.updateZebras(); - }, - - updateZebras: function(){ - Array.each(this.body.rows, this.zebra, this); - }, - - setRowStyle: function(row, i){ - if (this.previous) this.previous(row, i); - this.zebra(row, i); - }, - - zebra: function(row, i){ - return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra); - }, - - push: function(){ - var pushed = this.previous.apply(this, arguments); - if (this.options.zebra) this.updateZebras(); - return pushed; - } - -}); - - -/* ---- - -script: HtmlTable.Sort.js - -name: HtmlTable.Sort - -description: Builds a stripy, sortable table with methods to add rows. - -license: MIT-style license - -authors: - - Harald Kirschner - - Aaron Newton - - Jacob Thornton - -requires: - - Core/Hash - - /HtmlTable - - /Class.refactor - - /Element.Delegation - - /String.Extras - - /Date - -provides: [HtmlTable.Sort] - -... -*/ - -HtmlTable = Class.refactor(HtmlTable, { - - options: {/* - onSort: function(){}, */ - sortIndex: 0, - sortReverse: false, - parsers: [], - defaultParser: 'string', - classSortable: 'table-sortable', - classHeadSort: 'table-th-sort', - classHeadSortRev: 'table-th-sort-rev', - classNoSort: 'table-th-nosort', - classGroupHead: 'table-tr-group-head', - classGroup: 'table-tr-group', - classCellSort: 'table-td-sort', - classSortSpan: 'table-th-sort-span', - sortable: false, - thSelector: 'th' - }, - - initialize: function (){ - this.previous.apply(this, arguments); - if (this.occluded) return this.occluded; - this.sorted = {index: null, dir: 1}; - this.bound = { - headClick: this.headClick.bind(this) - }; - this.sortSpans = new Elements(); - if (this.options.sortable){ - this.enableSort(); - if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse); - } - }, - - attachSorts: function(attach){ - this.detachSorts(); - if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick); - }, - - detachSorts: function(){ - this.element.removeEvents('click:relay(' + this.options.thSelector + ')'); - }, - - setHeaders: function(){ - this.previous.apply(this, arguments); - if (this.sortEnabled) this.setParsers(); - }, - - setParsers: function(){ - this.parsers = this.detectParsers(); - }, - - detectParsers: function(){ - return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this); - }, - - detectParser: function(cell, index){ - if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser'); - var thDiv = new Element('div'); - thDiv.adopt(cell.childNodes).inject(cell); - var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top'); - this.sortSpans.push(sortSpan); - var parser = this.options.parsers[index], - rows = this.body.rows, - cancel; - switch (typeOf(parser)){ - case 'function': parser = {convert: parser}; cancel = true; break; - case 'string': parser = parser; cancel = true; break; - } - if (!cancel){ - HtmlTable.ParserPriority.some(function(parserName){ - var current = HtmlTable.Parsers[parserName], - match = current.match; - if (!match) return false; - for (var i = 0, j = rows.length; i < j; i++){ - var cell = document.id(rows[i].cells[index]), - text = cell ? cell.get('html').clean() : ''; - if (text && match.test(text)){ - parser = current; - return true; - } - } - }); - } - if (!parser) parser = this.options.defaultParser; - cell.store('htmltable-parser', parser); - return parser; - }, - - headClick: function(event, el){ - if (!this.head || el.hasClass(this.options.classNoSort)) return; - return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length); - }, - - serialize: function() { - var previousSerialization = this.previous.apply(this, arguments) || {}; - if (this.options.sortable) { - previousSerialization.sortIndex = this.sorted.index; - previousSerialization.sortReverse = this.sorted.reverse; - } - return previousSerialization; - }, - - restore: function(tableState) { - if(this.options.sortable && tableState.sortIndex) { - this.sort(tableState.sortIndex, tableState.sortReverse); - } - this.previous.apply(this, arguments); - }, - - setSortedState: function(index, reverse){ - if (reverse != null) this.sorted.reverse = reverse; - else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse; - else this.sorted.reverse = this.sorted.index == null; - - if (index != null) this.sorted.index = index; - }, - - setHeadSort: function(sorted){ - var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){ - return row.getElements(this.options.thSelector)[this.sorted.index]; - }, this).clean()); - if (!head.length) return; - if (sorted){ - head.addClass(this.options.classHeadSort); - if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev); - else head.removeClass(this.options.classHeadSortRev); - } else { - head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev); - } - }, - - setRowSort: function(data, pre){ - var count = data.length, - body = this.body, - group, - rowIndex; - - while (count){ - var item = data[--count], - position = item.position, - row = body.rows[position]; - - if (row.disabled) continue; - if (!pre){ - group = this.setGroupSort(group, row, item); - this.setRowStyle(row, count); - } - body.appendChild(row); - - for (rowIndex = 0; rowIndex < count; rowIndex++){ - if (data[rowIndex].position > position) data[rowIndex].position--; - } - } - }, - - setRowStyle: function(row, i){ - this.previous(row, i); - row.cells[this.sorted.index].addClass(this.options.classCellSort); - }, - - setGroupSort: function(group, row, item){ - if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup); - else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead); - return item.value; - }, - - getParser: function(){ - var parser = this.parsers[this.sorted.index]; - return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser; - }, - - sort: function(index, reverse, pre){ - if (!this.head) return; - - if (!pre){ - this.clearSort(); - this.setSortedState(index, reverse); - this.setHeadSort(true); - } - - var parser = this.getParser(); - if (!parser) return; - - var rel; - if (!Browser.ie){ - rel = this.body.getParent(); - this.body.dispose(); - } - - var data = this.parseData(parser).sort(function(a, b){ - if (a.value === b.value) return 0; - return a.value > b.value ? 1 : -1; - }); - - if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true); - this.setRowSort(data, pre); - - if (rel) rel.grab(this.body); - this.fireEvent('stateChanged'); - return this.fireEvent('sort', [this.body, this.sorted.index]); - }, - - parseData: function(parser){ - return Array.map(this.body.rows, function(row, i){ - var value = parser.convert.call(document.id(row.cells[this.sorted.index])); - return { - position: i, - value: value - }; - }, this); - }, - - clearSort: function(){ - this.setHeadSort(false); - this.body.getElements('td').removeClass(this.options.classCellSort); - }, - - reSort: function(){ - if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse); - return this; - }, - - enableSort: function(){ - this.element.addClass(this.options.classSortable); - this.attachSorts(true); - this.setParsers(); - this.sortEnabled = true; - return this; - }, - - disableSort: function(){ - this.element.removeClass(this.options.classSortable); - this.attachSorts(false); - this.sortSpans.each(function(span){ - span.destroy(); - }); - this.sortSpans.empty(); - this.sortEnabled = false; - return this; - } - -}); - -HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number']; - -HtmlTable.Parsers = { - - 'date': { - match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/, - convert: function(){ - var d = Date.parse(this.get('text').stripTags()); - return (typeOf(d) == 'date') ? d.format('db') : ''; - }, - type: 'date' - }, - 'input-checked': { - match: / type="(radio|checkbox)" /, - convert: function(){ - return this.getElement('input').checked; - } - }, - 'input-value': { - match: / -HtmlTable.Parsers = new Hash(HtmlTable.Parsers); -// - -HtmlTable.defineParsers = function(parsers){ - HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers); - for (var parser in parsers){ - HtmlTable.ParserPriority.unshift(parser); - } -}; - - -/* ---- - -script: Keyboard.js - -name: Keyboard - -description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c. - -license: MIT-style license - -authors: - - Perrin Westrich - - Aaron Newton - - Scott Kyle - -requires: - - Core/Events - - Core/Options - - Core/Element.Event - - Element.Event.Pseudos.Keys - -provides: [Keyboard] - -... -*/ - -(function(){ - - var Keyboard = this.Keyboard = new Class({ - - Extends: Events, - - Implements: [Options], - - options: {/* - onActivate: function(){}, - onDeactivate: function(){},*/ - defaultEventType: 'keydown', - active: false, - manager: null, - events: {}, - nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged'] - }, - - initialize: function(options){ - if (options && options.manager){ - this._manager = options.manager; - delete options.manager; - } - this.setOptions(options); - this._setup(); - }, - - addEvent: function(type, fn, internal){ - return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal); - }, - - removeEvent: function(type, fn){ - return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn); - }, - - toggleActive: function(){ - return this[this.isActive() ? 'deactivate' : 'activate'](); - }, - - activate: function(instance){ - if (instance){ - if (instance.isActive()) return this; - //if we're stealing focus, store the last keyboard to have it so the relinquish command works - if (this._activeKB && instance != this._activeKB){ - this.previous = this._activeKB; - this.previous.fireEvent('deactivate'); - } - //if we're enabling a child, assign it so that events are now passed to it - this._activeKB = instance.fireEvent('activate'); - Keyboard.manager.fireEvent('changed'); - } else if (this._manager){ - //else we're enabling ourselves, we must ask our parent to do it for us - this._manager.activate(this); - } - return this; - }, - - isActive: function(){ - return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this); - }, - - deactivate: function(instance){ - if (instance){ - if (instance === this._activeKB){ - this._activeKB = null; - instance.fireEvent('deactivate'); - Keyboard.manager.fireEvent('changed'); - } - } else if (this._manager){ - this._manager.deactivate(this); - } - return this; - }, - - relinquish: function(){ - if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous); - else this.deactivate(); - return this; - }, - - //management logic - manage: function(instance){ - if (instance._manager) instance._manager.drop(instance); - this._instances.push(instance); - instance._manager = this; - if (!this._activeKB) this.activate(instance); - return this; - }, - - drop: function(instance){ - instance.relinquish(); - this._instances.erase(instance); - if (this._activeKB == instance){ - if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous); - else this._activeKB = this._instances[0]; - } - return this; - }, - - trace: function(){ - Keyboard.trace(this); - }, - - each: function(fn){ - Keyboard.each(this, fn); - }, - - /* - PRIVATE METHODS - */ - - _instances: [], - - _disable: function(instance){ - if (this._activeKB == instance) this._activeKB = null; - }, - - _setup: function(){ - this.addEvents(this.options.events); - //if this is the root manager, nothing manages it - if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this); - if (this.options.active) this.activate(); - else this.relinquish(); - }, - - _handle: function(event, type){ - //Keyboard.stop(event) prevents key propagation - if (event.preventKeyboardPropagation) return; - - var bubbles = !!this._manager; - if (bubbles && this._activeKB){ - this._activeKB._handle(event, type); - if (event.preventKeyboardPropagation) return; - } - this.fireEvent(type, event); - - if (!bubbles && this._activeKB) this._activeKB._handle(event, type); - } - - }); - - var parsed = {}; - var modifiers = ['shift', 'control', 'alt', 'meta']; - var regex = /^(?:shift|control|ctrl|alt|meta)$/; - - Keyboard.parse = function(type, eventType, ignore){ - if (ignore && ignore.contains(type.toLowerCase())) return type; - - type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){ - eventType = $1; - return ''; - }); - - if (!parsed[type]){ - var key, mods = {}; - type.split('+').each(function(part){ - if (regex.test(part)) mods[part] = true; - else key = part; - }); - - mods.control = mods.control || mods.ctrl; // allow both control and ctrl - - var keys = []; - modifiers.each(function(mod){ - if (mods[mod]) keys.push(mod); - }); - - if (key) keys.push(key); - parsed[type] = keys.join('+'); - } - - return eventType + ':keys(' + parsed[type] + ')'; - }; - - Keyboard.each = function(keyboard, fn){ - var current = keyboard || Keyboard.manager; - while (current){ - fn.run(current); - current = current._activeKB; - } - }; - - Keyboard.stop = function(event){ - event.preventKeyboardPropagation = true; - }; - - Keyboard.manager = new Keyboard({ - active: true - }); - - Keyboard.trace = function(keyboard){ - keyboard = keyboard || Keyboard.manager; - var hasConsole = window.console && console.log; - if (hasConsole) console.log('the following items have focus: '); - Keyboard.each(keyboard, function(current){ - if (hasConsole) console.log(document.id(current.widget) || current.wiget || current); - }); - }; - - var handler = function(event){ - var keys = []; - modifiers.each(function(mod){ - if (event[mod]) keys.push(mod); - }); - - if (!regex.test(event.key)) keys.push(event.key); - Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')'); - }; - - document.addEvents({ - 'keyup': handler, - 'keydown': handler - }); - -})(); - - -/* ---- - -script: Keyboard.Extras.js - -name: Keyboard.Extras - -description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys. - -license: MIT-style license - -authors: - - Perrin Westrich - -requires: - - /Keyboard - - /MooTools.More - -provides: [Keyboard.Extras] - -... -*/ -Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']); - -Keyboard.implement({ - - /* - shortcut should be in the format of: - { - 'keys': 'shift+s', // the default to add as an event. - 'description': 'blah blah blah', // a brief description of the functionality. - 'handler': function(){} // the event handler to run when keys are pressed. - } - */ - addShortcut: function(name, shortcut){ - this._shortcuts = this._shortcuts || []; - this._shortcutIndex = this._shortcutIndex || {}; - - shortcut.getKeyboard = Function.from(this); - shortcut.name = name; - this._shortcutIndex[name] = shortcut; - this._shortcuts.push(shortcut); - if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler); - return this; - }, - - addShortcuts: function(obj){ - for (var name in obj) this.addShortcut(name, obj[name]); - return this; - }, - - removeShortcut: function(name){ - var shortcut = this.getShortcut(name); - if (shortcut && shortcut.keys){ - this.removeEvent(shortcut.keys, shortcut.handler); - delete this._shortcutIndex[name]; - this._shortcuts.erase(shortcut); - } - return this; - }, - - removeShortcuts: function(names){ - names.each(this.removeShortcut, this); - return this; - }, - - getShortcuts: function(){ - return this._shortcuts || []; - }, - - getShortcut: function(name){ - return (this._shortcutIndex || {})[name]; - } - -}); - -Keyboard.rebind = function(newKeys, shortcuts){ - Array.from(shortcuts).each(function(shortcut){ - shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler); - shortcut.getKeyboard().addEvent(newKeys, shortcut.handler); - shortcut.keys = newKeys; - shortcut.getKeyboard().fireEvent('rebound'); - }); -}; - - -Keyboard.getActiveShortcuts = function(keyboard){ - var activeKBS = [], activeSCS = []; - Keyboard.each(keyboard, [].push.bind(activeKBS)); - activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); }); - return activeSCS; -}; - -Keyboard.getShortcut = function(name, keyboard, opts){ - opts = opts || {}; - var shortcuts = opts.many ? [] : null, - set = opts.many ? function(kb){ - var shortcut = kb.getShortcut(name); - if (shortcut) shortcuts.push(shortcut); - } : function(kb){ - if (!shortcuts) shortcuts = kb.getShortcut(name); - }; - Keyboard.each(keyboard, set); - return shortcuts; -}; - -Keyboard.getShortcuts = function(name, keyboard){ - return Keyboard.getShortcut(name, keyboard, { many: true }); -}; - - -/* ---- - -script: HtmlTable.Select.js - -name: HtmlTable.Select - -description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation. - -license: MIT-style license - -authors: - - Harald Kirschner - - Aaron Newton - -requires: - - /Keyboard - - /Keyboard.Extras - - /HtmlTable - - /Class.refactor - - /Element.Delegation - - /Element.Shortcuts - -provides: [HtmlTable.Select] - -... -*/ - -HtmlTable = Class.refactor(HtmlTable, { - - options: { - /*onRowFocus: function(){}, - onRowUnfocus: function(){},*/ - useKeyboard: true, - classRowSelected: 'table-tr-selected', - classRowHovered: 'table-tr-hovered', - classSelectable: 'table-selectable', - shiftForMultiSelect: true, - allowMultiSelect: true, - selectable: false - }, - - initialize: function(){ - this.previous.apply(this, arguments); - if (this.occluded) return this.occluded; - - this.selectedRows = new Elements(); - - this.bound = { - mouseleave: this.mouseleave.bind(this), - clickRow: this.clickRow.bind(this), - activateKeyboard: function() { - if (this.keyboard && this.selectEnabled) this.keyboard.activate(); - }.bind(this) - }; - - if (this.options.selectable) this.enableSelect(); - }, - - empty: function(){ - this.selectNone(); - return this.previous(); - }, - - enableSelect: function(){ - this.selectEnabled = true; - this.attachSelects(); - this.element.addClass(this.options.classSelectable); - return this; - }, - - disableSelect: function(){ - this.selectEnabled = false; - this.attachSelects(false); - this.element.removeClass(this.options.classSelectable); - return this; - }, - - push: function(){ - var ret = this.previous.apply(this, arguments); - this.updateSelects(); - return ret; - }, - - isSelected: function(row){ - return this.selectedRows.contains(row); - }, - - toggleRow: function(row){ - return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row); - }, - - selectRow: function(row, _nocheck){ - //private variable _nocheck: boolean whether or not to confirm the row is in the table body - //added here for optimization when selecting ranges - if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; - if (!this.options.allowMultiSelect) this.selectNone(); - - if (!this.isSelected(row)){ - this.selectedRows.push(row); - row.addClass(this.options.classRowSelected); - this.fireEvent('rowFocus', [row, this.selectedRows]); - this.fireEvent('stateChanged'); - } - - this.focused = row; - document.clearSelection(); - - return this; - }, - - getSelected: function(){ - return this.selectedRows; - }, - - serialize: function() { - var previousSerialization = this.previous.apply(this, arguments) || {}; - if (this.options.selectable) { - previousSerialization.selectedRows = this.selectedRows.map(function(row) { - return Array.indexOf(this.body.rows, row); - }.bind(this)); - } - return previousSerialization; - }, - - restore: function(tableState) { - if(this.options.selectable && tableState.selectedRows) { - tableState.selectedRows.each(function(index) { - this.selectRow(this.body.rows[index]); - }.bind(this)); - } - this.previous.apply(this, arguments); - }, - - deselectRow: function(row, _nocheck){ - if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; - - this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row)); - row.removeClass(this.options.classRowSelected); - this.fireEvent('rowUnfocus', [row, this.selectedRows]); - this.fireEvent('stateChanged'); - return this; - }, - - selectAll: function(selectNone){ - if (!selectNone && !this.options.allowMultiSelect) return; - this.selectRange(0, this.body.rows.length, selectNone); - return this; - }, - - selectNone: function(){ - return this.selectAll(true); - }, - - selectRange: function(startRow, endRow, _deselect){ - if (!this.options.allowMultiSelect && !_deselect) return; - var method = _deselect ? 'deselectRow' : 'selectRow', - rows = Array.clone(this.body.rows); - - if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow); - if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow); - endRow = endRow < rows.length - 1 ? endRow : rows.length - 1; - - if (endRow < startRow){ - var tmp = startRow; - startRow = endRow; - endRow = tmp; - } - - for (var i = startRow; i <= endRow; i++) this[method](rows[i], true); - - return this; - }, - - deselectRange: function(startRow, endRow){ - this.selectRange(startRow, endRow, true); - }, - - getSelected: function(){ - return this.selectedRows; - }, - -/* - Private methods: -*/ - - enterRow: function(row){ - if (this.hovered) this.hovered = this.leaveRow(this.hovered); - this.hovered = row.addClass(this.options.classRowHovered); - }, - - leaveRow: function(row){ - row.removeClass(this.options.classRowHovered); - }, - - updateSelects: function(){ - Array.each(this.body.rows, function(row){ - var binders = row.retrieve('binders'); - if (!binders && !this.selectEnabled) return; - if (!binders){ - binders = { - mouseenter: this.enterRow.pass([row], this), - mouseleave: this.leaveRow.pass([row], this) - }; - row.store('binders', binders); - } - if (this.selectEnabled) row.addEvents(binders); - else row.removeEvents(binders); - }, this); - }, - - shiftFocus: function(offset, event){ - if (!this.focused) return this.selectRow(this.body.rows[0], event); - var to = this.getRowByOffset(offset); - if (to === null || this.focused == this.body.rows[to]) return this; - this.toggleRow(this.body.rows[to], event); - }, - - clickRow: function(event, row){ - var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect; - if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone(); - - if (event.rightClick) this.selectRow(row); - else this.toggleRow(row); - - if (event.shift){ - this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true); - this.focused = row; - } - this.rangeStart = row; - }, - - getRowByOffset: function(offset){ - if (!this.focused) return 0; - var rows = Array.clone(this.body.rows), - index = rows.indexOf(this.focused) + offset; - - if (index < 0) index = null; - if (index >= rows.length) index = null; - - return index; - }, - - attachSelects: function(attach){ - attach = attach != null ? attach : true; - - var method = attach ? 'addEvents' : 'removeEvents'; - this.element[method]({ - mouseleave: this.bound.mouseleave, - click: this.bound.activateKeyboard - }); - - this.body[method]({ - 'click:relay(tr)': this.bound.clickRow, - 'contextmenu:relay(tr)': this.bound.clickRow - }); - - if (this.options.useKeyboard || this.keyboard){ - if (!this.keyboard) this.keyboard = new Keyboard(); - if (!this.selectKeysDefined) { - this.selectKeysDefined = true; - var timer, held; - - var move = function(offset){ - var mover = function(e){ - clearTimeout(timer); - e.preventDefault(); - - var to = this.body.rows[this.getRowByOffset(offset)]; - if (e.shift && to && this.isSelected(to)){ - this.deselectRow(this.focused); - this.focused = to; - } else { - if (to && (!this.options.allowMultiSelect || !e.shift)){ - this.selectNone(); - } - this.shiftFocus(offset, e); - } - - if (held){ - timer = mover.delay(100, this, e); - } else { - timer = (function(){ - held = true; - mover(e); - }).delay(400); - } - }.bind(this); - return mover; - }.bind(this); - - var clear = function(){ - clearTimeout(timer); - held = false; - }; - - this.keyboard.addEvents({ - 'keydown:shift+up': move(-1), - 'keydown:shift+down': move(1), - 'keyup:shift+up': clear, - 'keyup:shift+down': clear, - 'keyup:up': clear, - 'keyup:down': clear - }); - - var shiftHint = ''; - if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){ - shiftHint = " (Shift multi-selects)."; - } - - this.keyboard.addShortcuts({ - 'Select Previous Row': { - keys: 'up', - shortcut: 'up arrow', - handler: move(-1), - description: 'Select the previous row in the table.' + shiftHint - }, - 'Select Next Row': { - keys: 'down', - shortcut: 'down arrow', - handler: move(1), - description: 'Select the next row in the table.' + shiftHint - } - }); - - } - this.keyboard[attach ? 'activate' : 'deactivate'](); - } - this.updateSelects(); - }, - - mouseleave: function(){ - if (this.hovered) this.leaveRow(this.hovered); - } - -}); - - -/* ---- - -script: Scroller.js - -name: Scroller - -description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries. - -license: MIT-style license - -authors: - - Valerio Proietti - -requires: - - Core/Events - - Core/Options - - Core/Element.Event - - Core/Element.Dimensions - - MooTools.More - -provides: [Scroller] - -... -*/ - -var Scroller = new Class({ - - Implements: [Events, Options], - - options: { - area: 20, - velocity: 1, - onChange: function(x, y){ - this.element.scrollTo(x, y); - }, - fps: 50 - }, - - initialize: function(element, options){ - this.setOptions(options); - this.element = document.id(element); - this.docBody = document.id(this.element.getDocument().body); - this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element; - this.timer = null; - this.bound = { - attach: this.attach.bind(this), - detach: this.detach.bind(this), - getCoords: this.getCoords.bind(this) - }; - }, - - start: function(){ - this.listener.addEvents({ - mouseover: this.bound.attach, - mouseleave: this.bound.detach - }); - return this; - }, - - stop: function(){ - this.listener.removeEvents({ - mouseover: this.bound.attach, - mouseleave: this.bound.detach - }); - this.detach(); - this.timer = clearInterval(this.timer); - return this; - }, - - attach: function(){ - this.listener.addEvent('mousemove', this.bound.getCoords); - }, - - detach: function(){ - this.listener.removeEvent('mousemove', this.bound.getCoords); - this.timer = clearInterval(this.timer); - }, - - getCoords: function(event){ - this.page = (this.listener.get('tag') == 'body') ? event.client : event.page; - if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this); - }, - - scroll: function(){ - var size = this.element.getSize(), - scroll = this.element.getScroll(), - pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0}, - scrollSize = this.element.getScrollSize(), - change = {x: 0, y: 0}, - top = this.options.area.top || this.options.area, - bottom = this.options.area.bottom || this.options.area; - for (var z in this.page){ - if (this.page[z] < (top + pos[z]) && scroll[z] != 0){ - change[z] = (this.page[z] - top - pos[z]) * this.options.velocity; - } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){ - change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity; - } - change[z] = change[z].round(); - } - if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]); - } - -}); - - -/* ---- - -script: Tips.js - -name: Tips - -description: Class for creating nice tips that follow the mouse cursor when hovering an element. - -license: MIT-style license - -authors: - - Valerio Proietti - - Christoph Pojer - - Luis Merino - -requires: - - Core/Options - - Core/Events - - Core/Element.Event - - Core/Element.Style - - Core/Element.Dimensions - - /MooTools.More - -provides: [Tips] - -... -*/ - -(function(){ - -var read = function(option, element){ - return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : ''; -}; - -this.Tips = new Class({ - - Implements: [Events, Options], - - options: {/* - onAttach: function(element){}, - onDetach: function(element){}, - onBound: function(coords){},*/ - onShow: function(){ - this.tip.setStyle('display', 'block'); - }, - onHide: function(){ - this.tip.setStyle('display', 'none'); - }, - title: 'title', - text: function(element){ - return element.get('rel') || element.get('href'); - }, - showDelay: 100, - hideDelay: 100, - className: 'tip-wrap', - offset: {x: 16, y: 16}, - windowPadding: {x:0, y:0}, - fixed: false - }, - - initialize: function(){ - var params = Array.link(arguments, { - options: Type.isObject, - elements: function(obj){ - return obj != null; - } - }); - this.setOptions(params.options); - if (params.elements) this.attach(params.elements); - this.container = new Element('div', {'class': 'tip'}); - }, - - toElement: function(){ - if (this.tip) return this.tip; - - this.tip = new Element('div', { - 'class': this.options.className, - styles: { - position: 'absolute', - top: 0, - left: 0 - } - }).adopt( - new Element('div', {'class': 'tip-top'}), - this.container, - new Element('div', {'class': 'tip-bottom'}) - ); - - return this.tip; - }, - - attach: function(elements){ - $$(elements).each(function(element){ - var title = read(this.options.title, element), - text = read(this.options.text, element); - - element.set('title', '').store('tip:native', title).retrieve('tip:title', title); - element.retrieve('tip:text', text); - this.fireEvent('attach', [element]); - - var events = ['enter', 'leave']; - if (!this.options.fixed) events.push('move'); - - events.each(function(value){ - var event = element.retrieve('tip:' + value); - if (!event) event = function(event){ - this['element' + value.capitalize()].apply(this, [event, element]); - }.bind(this); - - element.store('tip:' + value, event).addEvent('mouse' + value, event); - }, this); - }, this); - - return this; - }, - - detach: function(elements){ - $$(elements).each(function(element){ - ['enter', 'leave', 'move'].each(function(value){ - element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value); - }); - - this.fireEvent('detach', [element]); - - if (this.options.title == 'title'){ // This is necessary to check if we can revert the title - var original = element.retrieve('tip:native'); - if (original) element.set('title', original); - } - }, this); - - return this; - }, - - elementEnter: function(event, element){ - clearTimeout(this.timer); - this.timer = (function(){ - this.container.empty(); - - ['title', 'text'].each(function(value){ - var content = element.retrieve('tip:' + value); - var div = this['_' + value + 'Element'] = new Element('div', { - 'class': 'tip-' + value - }).inject(this.container); - if (content) this.fill(div, content); - }, this); - this.show(element); - this.position((this.options.fixed) ? {page: element.getPosition()} : event); - }).delay(this.options.showDelay, this); - }, - - elementLeave: function(event, element){ - clearTimeout(this.timer); - this.timer = this.hide.delay(this.options.hideDelay, this, element); - this.fireForParent(event, element); - }, - - setTitle: function(title){ - if (this._titleElement){ - this._titleElement.empty(); - this.fill(this._titleElement, title); - } - return this; - }, - - setText: function(text){ - if (this._textElement){ - this._textElement.empty(); - this.fill(this._textElement, text); - } - return this; - }, - - fireForParent: function(event, element){ - element = element.getParent(); - if (!element || element == document.body) return; - if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event); - else this.fireForParent(event, element); - }, - - elementMove: function(event, element){ - this.position(event); - }, - - position: function(event){ - if (!this.tip) document.id(this); - - var size = window.getSize(), scroll = window.getScroll(), - tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight}, - props = {x: 'left', y: 'top'}, - bounds = {y: false, x2: false, y2: false, x: false}, - obj = {}; - - for (var z in props){ - obj[props[z]] = event.page[z] + this.options.offset[z]; - if (obj[props[z]] < 0) bounds[z] = true; - if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){ - obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z]; - bounds[z+'2'] = true; - } - } - - this.fireEvent('bound', bounds); - this.tip.setStyles(obj); - }, - - fill: function(element, contents){ - if (typeof contents == 'string') element.set('html', contents); - else element.adopt(contents); - }, - - show: function(element){ - if (!this.tip) document.id(this); - if (!this.tip.getParent()) this.tip.inject(document.body); - this.fireEvent('show', [this.tip, element]); - }, - - hide: function(element){ - if (!this.tip) document.id(this); - this.fireEvent('hide', [this.tip, element]); - } - -}); - -})(); - - -/* ---- - -script: Locale.Set.From.js - -name: Locale.Set.From - -description: Provides an alternative way to create Locale.Set objects. - -license: MIT-style license - -authors: - - Tim Wienk - -requires: - - Core/JSON - - /Locale - -provides: Locale.Set.From - -... -*/ - -(function(){ - -var parsers = { - 'json': JSON.decode -}; - -Locale.Set.defineParser = function(name, fn){ - parsers[name] = fn; -}; - -Locale.Set.from = function(set, type){ - if (instanceOf(set, Locale.Set)) return set; - - if (!type && typeOf(set) == 'string') type = 'json'; - if (parsers[type]) set = parsers[type](set); - - var locale = new Locale.Set; - - locale.sets = set.sets || {}; - - if (set.inherits){ - locale.inherits.locales = Array.from(set.inherits.locales); - locale.inherits.sets = set.inherits.sets || {}; - } - - return locale; -}; - -})(); - - -/* ---- - -name: Locale.ar.Date - -description: Date messages for Arabic. - -license: MIT-style license - -authors: - - Chafik Barbar - -requires: - - /Locale - -provides: [Locale.ar.Date] - -... -*/ - -Locale.define('ar', 'Date', { - - // Culture's date order: DD/MM/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d/%m/%Y', - shortTime: '%H:%M' - -}); - - -/* ---- - -name: Locale.ar.Form.Validator - -description: Form Validator messages for Arabic. - -license: MIT-style license - -authors: - - Chafik Barbar - -requires: - - /Locale - -provides: [Locale.ar.Form.Validator] - -... -*/ - -Locale.define('ar', 'FormValidator', { - - required: 'هذا الحقل مطلوب.', - minLength: 'رجاءً إدخال {minLength} أحرف على الأقل (تم إدخال {length} أحرف).', - maxLength: 'الرجاء عدم إدخال أكثر من {maxLength} أحرف (تم إدخال {length} أحرف).', - integer: 'الرجاء إدخال عدد صحيح في هذا الحقل. أي رقم ذو كسر عشري أو مئوي (مثال 1.25 ) غير مسموح.', - numeric: 'الرجاء إدخال قيم رقمية في هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").', - digits: 'الرجاء أستخدام قيم رقمية وعلامات ترقيمية فقط في هذا الحقل (مثال, رقم هاتف مع نقطة أو شحطة)', - alpha: 'الرجاء أستخدام أحرف فقط (ا-ي) في هذا الحقل. أي فراغات أو علامات غير مسموحة.', - alphanum: 'الرجاء أستخدام أحرف فقط (ا-ي) أو أرقام (0-9) فقط في هذا الحقل. أي فراغات أو علامات غير مسموحة.', - dateSuchAs: 'الرجاء إدخال تاريخ صحيح كالتالي {date}', - dateInFormatMDY: 'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)', - email: 'الرجاء إدخال بريد إلكتروني صحيح.', - url: 'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.example.com', - currencyDollar: 'الرجاء إدخال قيمة $ صحيحة. مثال, 100.00$', - oneRequired: 'الرجاء إدخال قيمة في أحد هذه الحقول على الأقل.', - errorPrefix: 'خطأ: ', - warningPrefix: 'تحذير: ' - -}); - - -/* ---- - -name: Locale.ca-CA.Date - -description: Date messages for Catalan. - -license: MIT-style license - -authors: - - Ãlfons Sanchez - -requires: - - /Locale - -provides: [Locale.ca-CA.Date] - -... -*/ - -Locale.define('ca-CA', 'Date', { - - months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'], - months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], - days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'], - days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], - - // Culture's date order: DD/MM/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d/%m/%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 0, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: 'fa menys d`un minut', - minuteAgo: 'fa un minut', - minutesAgo: 'fa {delta} minuts', - hourAgo: 'fa un hora', - hoursAgo: 'fa unes {delta} hores', - dayAgo: 'fa un dia', - daysAgo: 'fa {delta} dies', - - lessThanMinuteUntil: 'menys d`un minut des d`ara', - minuteUntil: 'un minut des d`ara', - minutesUntil: '{delta} minuts des d`ara', - hourUntil: 'un hora des d`ara', - hoursUntil: 'unes {delta} hores des d`ara', - dayUntil: '1 dia des d`ara', - daysUntil: '{delta} dies des d`ara' - -}); - - -/* ---- - -name: Locale.ca-CA.Form.Validator - -description: Form Validator messages for Catalan. - -license: MIT-style license - -authors: - - Miquel Hudin - - Ãlfons Sanchez - -requires: - - /Locale - -provides: [Locale.ca-CA.Form.Validator] - -... -*/ - -Locale.define('ca-CA', 'FormValidator', { - - required: 'Aquest camp es obligatori.', - minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).', - maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).', - integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.', - numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").', - digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).', - alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No s´admiteixen espais ni altres caracters.', - alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No s´admiteixen espais ni altres caracters.', - dateSuchAs: 'Per favor introdueix una data valida com {date}', - dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")', - email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".', - url: 'Per favor introdueix una URL valida com http://www.example.com.', - currencyDollar: 'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .', - oneRequired: 'Per favor introdueix alguna cosa per al menys una d´aquestes entrades.', - errorPrefix: 'Error: ', - warningPrefix: 'Avis: ', - - // Form.Validator.Extras - noSpace: 'No poden haver espais en aquesta entrada.', - reqChkByNode: 'No hi han elements seleccionats.', - requiredChk: 'Aquest camp es obligatori.', - reqChkByName: 'Per favor selecciona una {label}.', - match: 'Aquest camp necessita coincidir amb el camp {matchName}', - startDate: 'la data de inici', - endDate: 'la data de fi', - currendDate: 'la data actual', - afterDate: 'La data deu ser igual o posterior a {label}.', - beforeDate: 'La data deu ser igual o anterior a {label}.', - startMonth: 'Per favor selecciona un mes d´orige', - sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.' - -}); - - -/* ---- - -name: Locale.cs-CZ.Date - -description: Date messages for Czech. - -license: MIT-style license - -authors: - - Jan Černý chemiX - - Christopher Zukowski - -requires: - - /Locale - -provides: [Locale.cs-CZ.Date] - -... -*/ -(function(){ - -// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html -// one -> n is 1; 1 -// few -> n in 2..4; 2-4 -// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... -var pluralize = function (n, one, few, other){ - if (n == 1) return one; - else if (n == 2 || n == 3 || n == 4) return few; - else return other; -}; - -Locale.define('cs-CZ', 'Date', { - - months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'], - months_abbr: ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'], - days: ['Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'], - days_abbr: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'], - - // Culture's date order: DD.MM.YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d.%m.%Y', - shortTime: '%H:%M', - AM: 'dop.', - PM: 'odp.', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '.', - - lessThanMinuteAgo: 'před chvílí', - minuteAgo: 'přibližně před minutou', - minutesAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); }, - hourAgo: 'přibližně před hodinou', - hoursAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, - dayAgo: 'před dnem', - daysAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); }, - weekAgo: 'před týdnem', - weeksAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'týdnem', 'týdny', 'týdny'); }, - monthAgo: 'před měsícem', - monthsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'měsícem', 'měsíci', 'měsíci'); }, - yearAgo: 'před rokem', - yearsAgo: function(delta){ return 'před {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); }, - - lessThanMinuteUntil: 'za chvíli', - minuteUntil: 'přibližně za minutu', - minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); }, - hourUntil: 'přibližně za hodinu', - hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); }, - dayUntil: 'za den', - daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); }, - weekUntil: 'za týden', - weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'týden', 'týdny', 'týdnů'); }, - monthUntil: 'za měsíc', - monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'měsíc', 'měsíce', 'měsíců'); }, - yearUntil: 'za rok', - yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); } -}); - -})(); - - -/* ---- - -name: Locale.cs-CZ.Form.Validator - -description: Form Validator messages for Czech. - -license: MIT-style license - -authors: - - Jan Černý chemiX - -requires: - - /Locale - -provides: [Locale.cs-CZ.Form.Validator] - -... -*/ - -Locale.define('cs-CZ', 'FormValidator', { - - required: 'Tato položka je povinná.', - minLength: 'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).', - maxLength: 'Zadejte prosím méně než {maxLength} znaků (nápsáno {length} znaků).', - integer: 'Zadejte prosím celé číslo. Desetinná čísla (např. 1.25) nejsou povolena.', - numeric: 'Zadejte jen číselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").', - digits: 'Zadejte prosím pouze čísla a interpunkční znaménka(například telefonní číslo s pomlčkami nebo tečkami je povoleno).', - alpha: 'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.', - alphanum: 'Zadejte prosím pouze písmena (a-z) nebo číslice (0-9). Mezery nebo jiné znaky nejsou povoleny.', - dateSuchAs: 'Zadejte prosím platné datum jako {date}', - dateInFormatMDY: 'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")', - email: 'Zadejte prosím platnou e-mailovou adresu. Například "fred@domain.com".', - url: 'Zadejte prosím platnou URL adresu jako http://www.example.com.', - currencyDollar: 'Zadejte prosím platnou částku. Například $100.00.', - oneRequired: 'Zadejte prosím alespoň jednu hodnotu pro tyto položky.', - errorPrefix: 'Chyba: ', - warningPrefix: 'Upozornění: ', - - // Form.Validator.Extras - noSpace: 'V této položce nejsou povoleny mezery', - reqChkByNode: 'Nejsou vybrány žádné položky.', - requiredChk: 'Tato položka je vyžadována.', - reqChkByName: 'Prosím vyberte {label}.', - match: 'Tato položka se musí shodovat s položkou {matchName}', - startDate: 'datum zahájení', - endDate: 'datum ukončení', - currendDate: 'aktuální datum', - afterDate: 'Datum by mělo být stejné nebo větší než {label}.', - beforeDate: 'Datum by mělo být stejné nebo menší než {label}.', - startMonth: 'Vyberte počáteční měsíc.', - sameMonth: 'Tyto dva datumy musí být ve stejném měsíci - změňte jeden z nich.', - creditcard: 'Zadané číslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} čísel.' - -}); - - -/* ---- - -name: Locale.da-DK.Date - -description: Date messages for Danish. - -license: MIT-style license - -authors: - - Martin Overgaard - - Henrik Hansen - -requires: - - /Locale - -provides: [Locale.da-DK.Date] - -... -*/ - -Locale.define('da-DK', 'Date', { - - months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'], - months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], - days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'], - days_abbr: ['søn', 'man', 'tir', 'ons', 'tor', 'fre', 'lør'], - - // Culture's date order: DD-MM-YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d-%m-%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '.', - - lessThanMinuteAgo: 'mindre end et minut siden', - minuteAgo: 'omkring et minut siden', - minutesAgo: '{delta} minutter siden', - hourAgo: 'omkring en time siden', - hoursAgo: 'omkring {delta} timer siden', - dayAgo: '1 dag siden', - daysAgo: '{delta} dage siden', - weekAgo: '1 uge siden', - weeksAgo: '{delta} uger siden', - monthAgo: '1 måned siden', - monthsAgo: '{delta} måneder siden', - yearAgo: '1 år siden', - yearsAgo: '{delta} år siden', - - lessThanMinuteUntil: 'mindre end et minut fra nu', - minuteUntil: 'omkring et minut fra nu', - minutesUntil: '{delta} minutter fra nu', - hourUntil: 'omkring en time fra nu', - hoursUntil: 'omkring {delta} timer fra nu', - dayUntil: '1 dag fra nu', - daysUntil: '{delta} dage fra nu', - weekUntil: '1 uge fra nu', - weeksUntil: '{delta} uger fra nu', - monthUntil: '1 måned fra nu', - monthsUntil: '{delta} måneder fra nu', - yearUntil: '1 år fra nu', - yearsUntil: '{delta} år fra nu' - -}); - - -/* ---- - -name: Locale.da-DK.Form.Validator - -description: Form Validator messages for Danish. - -license: MIT-style license - -authors: - - Martin Overgaard - -requires: - - /Locale - -provides: [Locale.da-DK.Form.Validator] - -... -*/ - -Locale.define('da-DK', 'FormValidator', { - - required: 'Feltet skal udfyldes.', - minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).', - maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).', - integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.', - numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").', - digits: 'Skriv kun tal og tegnsætning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).', - alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.', - alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.', - dateSuchAs: 'Skriv en gyldig dato som {date}', - dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")', - email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".', - url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".', - currencyDollar: 'Skriv et gldigt beløb. F.eks Kr.100.00 .', - oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.', - errorPrefix: 'Fejl: ', - warningPrefix: 'Advarsel: ', - - // Form.Validator.Extras - noSpace: 'Der må ikke benyttes mellemrum i dette felt.', - reqChkByNode: 'Foretag et valg.', - requiredChk: 'Dette felt skal udfyldes.', - reqChkByName: 'Vælg en {label}.', - match: 'Dette felt skal matche {matchName} feltet', - startDate: 'start dato', - endDate: 'slut dato', - currendDate: 'dags dato', - afterDate: 'Datoen skal være større end eller lig med {label}.', - beforeDate: 'Datoen skal være mindre end eller lig med {label}.', - startMonth: 'Vælg en start måned', - sameMonth: 'De valgte datoer skal være i samme måned - skift en af dem.' - -}); - - -/* ---- - -name: Locale.de-DE.Date - -description: Date messages for German. - -license: MIT-style license - -authors: - - Christoph Pojer - - Frank Rossi - - Ulrich Petri - - Fabian Beiner - -requires: - - /Locale - -provides: [Locale.de-DE.Date] - -... -*/ - -Locale.define('de-DE', 'Date', { - - months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], - months_abbr: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], - days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], - days_abbr: ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], - - // Culture's date order: DD.MM.YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d.%m.%Y', - shortTime: '%H:%M', - AM: 'vormittags', - PM: 'nachmittags', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '.', - - lessThanMinuteAgo: 'vor weniger als einer Minute', - minuteAgo: 'vor einer Minute', - minutesAgo: 'vor {delta} Minuten', - hourAgo: 'vor einer Stunde', - hoursAgo: 'vor {delta} Stunden', - dayAgo: 'vor einem Tag', - daysAgo: 'vor {delta} Tagen', - weekAgo: 'vor einer Woche', - weeksAgo: 'vor {delta} Wochen', - monthAgo: 'vor einem Monat', - monthsAgo: 'vor {delta} Monaten', - yearAgo: 'vor einem Jahr', - yearsAgo: 'vor {delta} Jahren', - - lessThanMinuteUntil: 'in weniger als einer Minute', - minuteUntil: 'in einer Minute', - minutesUntil: 'in {delta} Minuten', - hourUntil: 'in ca. einer Stunde', - hoursUntil: 'in ca. {delta} Stunden', - dayUntil: 'in einem Tag', - daysUntil: 'in {delta} Tagen', - weekUntil: 'in einer Woche', - weeksUntil: 'in {delta} Wochen', - monthUntil: 'in einem Monat', - monthsUntil: 'in {delta} Monaten', - yearUntil: 'in einem Jahr', - yearsUntil: 'in {delta} Jahren' - -}); - - -/* ---- - -name: Locale.de-CH.Date - -description: Date messages for German (Switzerland). - -license: MIT-style license - -authors: - - Michael van der Weg - -requires: - - /Locale - - /Locale.de-DE.Date - -provides: [Locale.de-CH.Date] - -... -*/ - -Locale.define('de-CH').inherit('de-DE', 'Date'); - - -/* ---- - -name: Locale.de-CH.Form.Validator - -description: Form Validator messages for German (Switzerland). - -license: MIT-style license - -authors: - - Michael van der Weg - -requires: - - /Locale - -provides: [Locale.de-CH.Form.Validator] - -... -*/ - -Locale.define('de-CH', 'FormValidator', { - - required: 'Dieses Feld ist obligatorisch.', - minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', - maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', - integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.', - numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. "1", "1.1", "-1" oder "-1.1").', - digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).', - alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.', - alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.', - dateSuchAs: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel {date}', - dateInFormatMDY: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. "31.12.1999")', - email: 'Geben Sie bitte eine gültige E-Mail Adresse ein. Wie zum Beispiel "maria@bernasconi.ch".', - url: 'Geben Sie bitte eine gültige URL ein. Wie zum Beispiel http://www.example.com.', - currencyDollar: 'Geben Sie bitte einen gültigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .', - oneRequired: 'Machen Sie für mindestens eines der Eingabefelder einen Eintrag.', - errorPrefix: 'Fehler: ', - warningPrefix: 'Warnung: ', - - // Form.Validator.Extras - noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.', - reqChkByNode: 'Es wurden keine Elemente gewählt.', - requiredChk: 'Dieses Feld ist obligatorisch.', - reqChkByName: 'Bitte wählen Sie ein {label}.', - match: 'Dieses Eingabefeld muss mit dem Feld {matchName} übereinstimmen.', - startDate: 'Das Anfangsdatum', - endDate: 'Das Enddatum', - currendDate: 'Das aktuelle Datum', - afterDate: 'Das Datum sollte zur gleichen Zeit oder später sein {label}.', - beforeDate: 'Das Datum sollte zur gleichen Zeit oder früher sein {label}.', - startMonth: 'Wählen Sie bitte einen Anfangsmonat', - sameMonth: 'Diese zwei Datumsangaben müssen im selben Monat sein - Sie müssen eine von beiden verändern.', - creditcard: 'Die eingegebene Kreditkartennummer ist ungültig. Bitte überprüfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' - -}); - - -/* ---- - -name: Locale.de-DE.Form.Validator - -description: Form Validator messages for German. - -license: MIT-style license - -authors: - - Frank Rossi - - Ulrich Petri - - Fabian Beiner - -requires: - - /Locale - -provides: [Locale.de-DE.Form.Validator] - -... -*/ - -Locale.define('de-DE', 'FormValidator', { - - required: 'Dieses Eingabefeld muss ausgefüllt werden.', - minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).', - maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', - integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.', - numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.', - digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).', - alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.', - alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.', - dateSuchAs: 'Geben Sie bitte ein gültiges Datum ein (z.B. "{date}").', - dateInFormatMDY: 'Geben Sie bitte ein gültiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").', - email: 'Geben Sie bitte eine gültige E-Mail-Adresse ein (z.B. "max@mustermann.de").', - url: 'Geben Sie bitte eine gültige URL ein (z.B. "http://www.example.com").', - currencyDollar: 'Geben Sie bitte einen gültigen Betrag in EURO ein (z.B. 100.00€).', - oneRequired: 'Bitte füllen Sie mindestens ein Eingabefeld aus.', - errorPrefix: 'Fehler: ', - warningPrefix: 'Warnung: ', - - // Form.Validator.Extras - noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.', - reqChkByNode: 'Es wurden keine Elemente gewählt.', - requiredChk: 'Dieses Feld muss ausgefüllt werden.', - reqChkByName: 'Bitte wählen Sie ein {label}.', - match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld übereinstimmen.', - startDate: 'Das Anfangsdatum', - endDate: 'Das Enddatum', - currendDate: 'Das aktuelle Datum', - afterDate: 'Das Datum sollte zur gleichen Zeit oder später sein als {label}.', - beforeDate: 'Das Datum sollte zur gleichen Zeit oder früher sein als {label}.', - startMonth: 'Wählen Sie bitte einen Anfangsmonat', - sameMonth: 'Diese zwei Datumsangaben müssen im selben Monat sein - Sie müssen eines von beiden verändern.', - creditcard: 'Die eingegebene Kreditkartennummer ist ungültig. Bitte überprüfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' - -}); - - -/* ---- - -name: Locale.EU.Number - -description: Number messages for Europe. - -license: MIT-style license - -authors: - - Arian Stolwijk - -requires: - - /Locale - -provides: [Locale.EU.Number] - -... -*/ - -Locale.define('EU', 'Number', { - - decimal: ',', - group: '.', - - currency: { - prefix: '€ ' - } - -}); - - -/* ---- - -name: Locale.de-DE.Number - -description: Number messages for German. - -license: MIT-style license - -authors: - - Christoph Pojer - -requires: - - /Locale - - /Locale.EU.Number - -provides: [Locale.de-DE.Number] - -... -*/ - -Locale.define('de-DE').inherit('EU', 'Number'); - - -/* ---- - -name: Locale.en-GB.Date - -description: Date messages for British English. - -license: MIT-style license - -authors: - - Aaron Newton - -requires: - - /Locale - - /Locale.en-US.Date - -provides: [Locale.en-GB.Date] - -... -*/ - -Locale.define('en-GB', 'Date', { - - // Culture's date order: DD/MM/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d/%m/%Y', - shortTime: '%H:%M' - -}).inherit('en-US', 'Date'); - - -/* ---- - -name: Locale.es-ES.Date - -description: Date messages for Spanish. - -license: MIT-style license - -authors: - - Ãlfons Sanchez - -requires: - - /Locale - -provides: [Locale.es-ES.Date] - -... -*/ - -Locale.define('es-ES', 'Date', { - - months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'], - months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], - days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'], - days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'], - - // Culture's date order: DD/MM/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d/%m/%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: 'hace menos de un minuto', - minuteAgo: 'hace un minuto', - minutesAgo: 'hace {delta} minutos', - hourAgo: 'hace una hora', - hoursAgo: 'hace unas {delta} horas', - dayAgo: 'hace un día', - daysAgo: 'hace {delta} días', - weekAgo: 'hace una semana', - weeksAgo: 'hace unas {delta} semanas', - monthAgo: 'hace un mes', - monthsAgo: 'hace {delta} meses', - yearAgo: 'hace un año', - yearsAgo: 'hace {delta} años', - - lessThanMinuteUntil: 'menos de un minuto desde ahora', - minuteUntil: 'un minuto desde ahora', - minutesUntil: '{delta} minutos desde ahora', - hourUntil: 'una hora desde ahora', - hoursUntil: 'unas {delta} horas desde ahora', - dayUntil: 'un día desde ahora', - daysUntil: '{delta} días desde ahora', - weekUntil: 'una semana desde ahora', - weeksUntil: 'unas {delta} semanas desde ahora', - monthUntil: 'un mes desde ahora', - monthsUntil: '{delta} meses desde ahora', - yearUntil: 'un año desde ahora', - yearsUntil: '{delta} años desde ahora' - -}); - - -/* ---- - -name: Locale.es-AR.Date - -description: Date messages for Spanish (Argentina). - -license: MIT-style license - -authors: - - Ãlfons Sanchez - - Diego Massanti - -requires: - - /Locale - - /Locale.es-ES.Date - -provides: [Locale.es-AR.Date] - -... -*/ - -Locale.define('es-AR').inherit('es-ES', 'Date'); - - -/* ---- - -name: Locale.es-AR.Form.Validator - -description: Form Validator messages for Spanish (Argentina). - -license: MIT-style license - -authors: - - Diego Massanti - -requires: - - /Locale - -provides: [Locale.es-AR.Form.Validator] - -... -*/ - -Locale.define('es-AR', 'FormValidator', { - - required: 'Este campo es obligatorio.', - minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).', - maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).', - integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', - numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', - digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).', - alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.', - alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.', - dateSuchAs: 'Por favor ingrese una fecha válida como {date}', - dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")', - email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".', - url: 'Por favor ingrese una URL válida como http://www.example.com.', - currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .', - oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.', - errorPrefix: 'Error: ', - warningPrefix: 'Advertencia: ', - - // Form.Validator.Extras - noSpace: 'No se permiten espacios en este campo.', - reqChkByNode: 'No hay elementos seleccionados.', - requiredChk: 'Este campo es obligatorio.', - reqChkByName: 'Por favor selecciona una {label}.', - match: 'Este campo necesita coincidir con el campo {matchName}', - startDate: 'la fecha de inicio', - endDate: 'la fecha de fin', - currendDate: 'la fecha actual', - afterDate: 'La fecha debe ser igual o posterior a {label}.', - beforeDate: 'La fecha debe ser igual o anterior a {label}.', - startMonth: 'Por favor selecciona un mes de origen', - sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' - -}); - - -/* ---- - -name: Locale.es-ES.Form.Validator - -description: Form Validator messages for Spanish. - -license: MIT-style license - -authors: - - Ãlfons Sanchez - -requires: - - /Locale - -provides: [Locale.es-ES.Form.Validator] - -... -*/ - -Locale.define('es-ES', 'FormValidator', { - - required: 'Este campo es obligatorio.', - minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).', - maxLength: 'Por favor introduce no más de {maxLength} caracteres (has introducido {length} caracteres).', - integer: 'Por favor introduce un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', - numeric: 'Por favor introduce solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', - digits: 'Por favor usa solo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y puntos no esta permitido).', - alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.', - alphanum: 'Por favor, usa solo letras (a-z) o números (0-9) en este campo. No se admiten espacios ni otros caracteres.', - dateSuchAs: 'Por favor introduce una fecha válida como {date}', - dateInFormatMDY: 'Por favor introduce una fecha válida como DD/MM/YYYY (p.e. "31/12/1999")', - email: 'Por favor, introduce una dirección de email válida. Por ejemplo, "fred@domain.com".', - url: 'Por favor introduce una URL válida como http://www.example.com.', - currencyDollar: 'Por favor introduce una cantidad válida de €. Por ejemplo €100,00 .', - oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.', - errorPrefix: 'Error: ', - warningPrefix: 'Aviso: ', - - // Form.Validator.Extras - noSpace: 'No pueden haber espacios en esta entrada.', - reqChkByNode: 'No hay elementos seleccionados.', - requiredChk: 'Este campo es obligatorio.', - reqChkByName: 'Por favor selecciona una {label}.', - match: 'Este campo necesita coincidir con el campo {matchName}', - startDate: 'la fecha de inicio', - endDate: 'la fecha de fin', - currendDate: 'la fecha actual', - afterDate: 'La fecha debe ser igual o posterior a {label}.', - beforeDate: 'La fecha debe ser igual o anterior a {label}.', - startMonth: 'Por favor selecciona un mes de origen', - sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' - -}); - - -/* ---- - -name: Locale.et-EE.Date - -description: Date messages for Estonian. - -license: MIT-style license - -authors: - - Kevin Valdek - -requires: - - /Locale - -provides: [Locale.et-EE.Date] - -... -*/ - -Locale.define('et-EE', 'Date', { - - months: ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], - months_abbr: ['jaan', 'veebr', 'märts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], - days: ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'], - days_abbr: ['pühap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'], - - // Culture's date order: MM.DD.YYYY - dateOrder: ['month', 'date', 'year'], - shortDate: '%m.%d.%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: 'vähem kui minut aega tagasi', - minuteAgo: 'umbes minut aega tagasi', - minutesAgo: '{delta} minutit tagasi', - hourAgo: 'umbes tund aega tagasi', - hoursAgo: 'umbes {delta} tundi tagasi', - dayAgo: '1 päev tagasi', - daysAgo: '{delta} päeva tagasi', - weekAgo: '1 nädal tagasi', - weeksAgo: '{delta} nädalat tagasi', - monthAgo: '1 kuu tagasi', - monthsAgo: '{delta} kuud tagasi', - yearAgo: '1 aasta tagasi', - yearsAgo: '{delta} aastat tagasi', - - lessThanMinuteUntil: 'vähem kui minuti aja pärast', - minuteUntil: 'umbes minuti aja pärast', - minutesUntil: '{delta} minuti pärast', - hourUntil: 'umbes tunni aja pärast', - hoursUntil: 'umbes {delta} tunni pärast', - dayUntil: '1 päeva pärast', - daysUntil: '{delta} päeva pärast', - weekUntil: '1 nädala pärast', - weeksUntil: '{delta} nädala pärast', - monthUntil: '1 kuu pärast', - monthsUntil: '{delta} kuu pärast', - yearUntil: '1 aasta pärast', - yearsUntil: '{delta} aasta pärast' - -}); - - -/* ---- - -name: Locale.et-EE.Form.Validator - -description: Form Validator messages for Estonian. - -license: MIT-style license - -authors: - - Kevin Valdek - -requires: - - /Locale - -provides: [Locale.et-EE.Form.Validator] - -... -*/ - -Locale.define('et-EE', 'FormValidator', { - - required: 'Väli peab olema täidetud.', - minLength: 'Palun sisestage vähemalt {minLength} tähte (te sisestasite {length} tähte).', - maxLength: 'Palun ärge sisestage rohkem kui {maxLength} tähte (te sisestasite {length} tähte).', - integer: 'Palun sisestage väljale täisarv. Kümnendarvud (näiteks 1.25) ei ole lubatud.', - numeric: 'Palun sisestage ainult numbreid väljale (näiteks "1", "1.1", "-1" või "-1.1").', - digits: 'Palun kasutage ainult numbreid ja kirjavahemärke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).', - alpha: 'Palun kasutage ainult tähti (a-z). Tühikud ja teised sümbolid on keelatud.', - alphanum: 'Palun kasutage ainult tähti (a-z) või numbreid (0-9). Tühikud ja teised sümbolid on keelatud.', - dateSuchAs: 'Palun sisestage kehtiv kuupäev kujul {date}', - dateInFormatMDY: 'Palun sisestage kehtiv kuupäev kujul MM.DD.YYYY (näiteks: "12.31.1999").', - email: 'Palun sisestage kehtiv e-maili aadress (näiteks: "fred@domain.com").', - url: 'Palun sisestage kehtiv URL (näiteks: http://www.example.com).', - currencyDollar: 'Palun sisestage kehtiv $ summa (näiteks: $100.00).', - oneRequired: 'Palun sisestage midagi vähemalt ühele antud väljadest.', - errorPrefix: 'Viga: ', - warningPrefix: 'Hoiatus: ', - - // Form.Validator.Extras - noSpace: 'Väli ei tohi sisaldada tühikuid.', - reqChkByNode: 'Ükski väljadest pole valitud.', - requiredChk: 'Välja täitmine on vajalik.', - reqChkByName: 'Palun valige üks {label}.', - match: 'Väli peab sobima {matchName} väljaga', - startDate: 'algkuupäev', - endDate: 'lõppkuupäev', - currendDate: 'praegune kuupäev', - afterDate: 'Kuupäev peab olema võrdne või pärast {label}.', - beforeDate: 'Kuupäev peab olema võrdne või enne {label}.', - startMonth: 'Palun valige algkuupäev.', - sameMonth: 'Antud kaks kuupäeva peavad olema samas kuus - peate muutma ühte kuupäeva.' - -}); - - -/* ---- - -name: Locale.fa.Date - -description: Date messages for Persian. - -license: MIT-style license - -authors: - - Amir Hossein Hodjaty Pour - -requires: - - /Locale - -provides: [Locale.fa.Date] - -... -*/ - -Locale.define('fa', 'Date', { - - months: ['ژانویه', 'فوریه', 'مارس', 'آپریل', 'مه', 'ژوئن', 'ژوئیه', 'آگوست', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], - months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], - days: ['یکشنبه', 'دوشنبه', 'سه شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], - days_abbr: ['ي', 'د', 'س', 'چ', 'پ', 'ج', 'ش'], - - // Culture's date order: MM/DD/YYYY - dateOrder: ['month', 'date', 'year'], - shortDate: '%m/%d/%Y', - shortTime: '%I:%M%p', - AM: 'ق.ظ', - PM: 'ب.ظ', - - // Date.Extras - ordinal: 'ام', - - lessThanMinuteAgo: 'کمتر از یک دقیقه پیش', - minuteAgo: 'حدود یک دقیقه پیش', - minutesAgo: '{delta} دقیقه پیش', - hourAgo: 'حدود یک ساعت پیش', - hoursAgo: 'حدود {delta} ساعت پیش', - dayAgo: '1 روز پیش', - daysAgo: '{delta} روز پیش', - weekAgo: '1 هفته پیش', - weeksAgo: '{delta} هفته پیش', - monthAgo: '1 ماه پیش', - monthsAgo: '{delta} ماه پیش', - yearAgo: '1 سال پیش', - yearsAgo: '{delta} سال پیش', - - lessThanMinuteUntil: 'کمتر از یک دقیقه از حالا', - minuteUntil: 'حدود یک دقیقه از حالا', - minutesUntil: '{delta} دقیقه از حالا', - hourUntil: 'حدود یک ساعت از حالا', - hoursUntil: 'حدود {delta} ساعت از حالا', - dayUntil: '1 روز از حالا', - daysUntil: '{delta} روز از حالا', - weekUntil: '1 هفته از حالا', - weeksUntil: '{delta} هفته از حالا', - monthUntil: '1 ماه از حالا', - monthsUntil: '{delta} ماه از حالا', - yearUntil: '1 سال از حالا', - yearsUntil: '{delta} سال از حالا' - -}); - - -/* ---- - -name: Locale.fa.Form.Validator - -description: Form Validator messages for Persian. - -license: MIT-style license - -authors: - - Amir Hossein Hodjaty Pour - -requires: - - /Locale - -provides: [Locale.fa.Form.Validator] - -... -*/ - -Locale.define('fa', 'FormValidator', { - - required: 'این فیلد الزامی است.', - minLength: 'شما باید حداقل {minLength} حرف وارد کنید ({length} حرف وارد کرده اید).', - maxLength: 'لطفا حداکثر {maxLength} حرف وارد کنید (شما {length} حرف وارد کرده اید).', - integer: 'لطفا از عدد صحیح استفاده کنید. اعداد اعشاری (مانند 1.25) مجاز نیستند.', - numeric: 'لطفا فقط داده عددی وارد کنید (مانند "1" یا "1.1" یا "1-" یا "1.1-").', - digits: 'لطفا فقط از اعداد و علامتها در این فیلد استفاده کنید (برای مثال شماره تلفن با خط تیره و نقطه قابل قبول است).', - alpha: 'لطفا فقط از حروف الفباء برای این بخش استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.', - alphanum: 'لطفا فقط از حروف الفباء و اعداد در این بخش استفاده کنید. کاراکترهای دیگر و فاصله مجاز نیستند.', - dateSuchAs: 'لطفا یک تاریخ معتبر مانند {date} وارد کنید.', - dateInFormatMDY: 'لطفا یک تاریخ معتبر به شکل MM/DD/YYYY وارد کنید (مانند "12/31/1999").', - email: 'لطفا یک آدرس ایمیل معتبر وارد کنید. برای مثال "fred@domain.com".', - url: 'لطفا یک URL معتبر مانند http://www.example.com وارد کنید.', - currencyDollar: 'لطفا یک محدوده معتبر برای این بخش وارد کنید مانند 100.00$ .', - oneRequired: 'لطفا حداقل یکی از فیلدها را پر کنید.', - errorPrefix: 'خطا: ', - warningPrefix: 'هشدار: ', - - // Form.Validator.Extras - noSpace: 'استفاده از فاصله در این بخش مجاز نیست.', - reqChkByNode: 'موردی انتخاب نشده است.', - requiredChk: 'این فیلد الزامی است.', - reqChkByName: 'لطفا یک {label} را انتخاب کنید.', - match: 'این فیلد باید با فیلد {matchName} مطابقت داشته باشد.', - startDate: 'تاریخ شروع', - endDate: 'تاریخ پایان', - currendDate: 'تاریخ کنونی', - afterDate: 'تاریخ میبایست برابر یا بعد از {label} باشد', - beforeDate: 'تاریخ میبایست برابر یا قبل از {label} باشد', - startMonth: 'لطفا ماه شروع را انتخاب کنید', - sameMonth: 'این دو تاریخ باید در یک ماه باشند - شما باید یکی یا هر دو را تغییر دهید.', - creditcard: 'شماره کارت اعتباری که وارد کرده اید معتبر نیست. لطفا شماره را بررسی کنید و مجددا تلاش کنید. {length} رقم وارد شده است.' - -}); - - -/* ---- - -name: Locale.fi-FI.Date - -description: Date messages for Finnish. - -license: MIT-style license - -authors: - - ksel - -requires: - - /Locale - -provides: [Locale.fi-FI.Date] - -... -*/ - -Locale.define('fi-FI', 'Date', { - - // NOTE: months and days are not capitalized in finnish - months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesäkuu', 'heinäkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], - - // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;) - // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish. - months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesäk.', 'heinäk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'], - - days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], - days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], - - // Culture's date order: DD/MM/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d.%m.%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '.', - - lessThanMinuteAgo: 'vajaa minuutti sitten', - minuteAgo: 'noin minuutti sitten', - minutesAgo: '{delta} minuuttia sitten', - hourAgo: 'noin tunti sitten', - hoursAgo: 'noin {delta} tuntia sitten', - dayAgo: 'päivä sitten', - daysAgo: '{delta} päivää sitten', - weekAgo: 'viikko sitten', - weeksAgo: '{delta} viikkoa sitten', - monthAgo: 'kuukausi sitten', - monthsAgo: '{delta} kuukautta sitten', - yearAgo: 'vuosi sitten', - yearsAgo: '{delta} vuotta sitten', - - lessThanMinuteUntil: 'vajaan minuutin kuluttua', - minuteUntil: 'noin minuutin kuluttua', - minutesUntil: '{delta} minuutin kuluttua', - hourUntil: 'noin tunnin kuluttua', - hoursUntil: 'noin {delta} tunnin kuluttua', - dayUntil: 'päivän kuluttua', - daysUntil: '{delta} päivän kuluttua', - weekUntil: 'viikon kuluttua', - weeksUntil: '{delta} viikon kuluttua', - monthUntil: 'kuukauden kuluttua', - monthsUntil: '{delta} kuukauden kuluttua', - yearUntil: 'vuoden kuluttua', - yearsUntil: '{delta} vuoden kuluttua' - -}); - - -/* ---- - -name: Locale.fi-FI.Form.Validator - -description: Form Validator messages for Finnish. - -license: MIT-style license - -authors: - - ksel - -requires: - - /Locale - -provides: [Locale.fi-FI.Form.Validator] - -... -*/ - -Locale.define('fi-FI', 'FormValidator', { - - required: 'Tämä kenttä on pakollinen.', - minLength: 'Ole hyvä ja anna vähintään {minLength} merkkiä (annoit {length} merkkiä).', - maxLength: 'Älä anna enempää kuin {maxLength} merkkiä (annoit {length} merkkiä).', - integer: 'Ole hyvä ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivät ole sallittuja.', - numeric: 'Anna tähän kenttään lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").', - digits: 'Käytä pelkästään numeroita ja välimerkkejä tässä kentässä (syötteet, kuten esim. puhelinnumero, jossa on väliviivoja, pilkkuja tai pisteitä, kelpaa).', - alpha: 'Anna tähän kenttään vain kirjaimia (a-z). Välilyönnit tai muut merkit eivät ole sallittuja.', - alphanum: 'Anna tähän kenttään vain kirjaimia (a-z) tai numeroita (0-9). Välilyönnit tai muut merkit eivät ole sallittuja.', - dateSuchAs: 'Ole hyvä ja anna kelvollinen päivmäärä, kuten esimerkiksi {date}', - dateInFormatMDY: 'Ole hyvä ja anna kelvollinen päivämäärä muodossa pp/kk/vvvv (kuten "12/31/1999")', - email: 'Ole hyvä ja anna kelvollinen sähköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").', - url: 'Ole hyvä ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.', - currencyDollar: 'Ole hyvä ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .', - oneRequired: 'Ole hyvä ja syötä jotakin ainakin johonkin näistä kentistä.', - errorPrefix: 'Virhe: ', - warningPrefix: 'Varoitus: ', - - // Form.Validator.Extras - noSpace: 'Tässä syötteessä ei voi olla välilyöntejä', - reqChkByNode: 'Ei valintoja.', - requiredChk: 'Tämä kenttä on pakollinen.', - reqChkByName: 'Ole hyvä ja valitse {label}.', - match: 'Tämän kentän tulee vastata kenttää {matchName}', - startDate: 'alkupäivämäärä', - endDate: 'loppupäivämäärä', - currendDate: 'nykyinen päivämäärä', - afterDate: 'Päivämäärän tulisi olla sama tai myöhäisempi ajankohta kuin {label}.', - beforeDate: 'Päivämäärän tulisi olla sama tai aikaisempi ajankohta kuin {label}.', - startMonth: 'Ole hyvä ja valitse aloituskuukausi', - sameMonth: 'Näiden kahden päivämäärän tulee olla saman kuun sisällä -- sinun pitää muuttaa jompaa kumpaa.', - creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvä ja tarkista numero sekä yritä uudelleen. {length} numeroa syötetty.' - -}); - - -/* ---- - -name: Locale.fi-FI.Number - -description: Finnish number messages - -license: MIT-style license - -authors: - - ksel - -requires: - - /Locale - - /Locale.EU.Number - -provides: [Locale.fi-FI.Number] - -... -*/ - -Locale.define('fi-FI', 'Number', { - - group: ' ' // grouped by space - -}).inherit('EU', 'Number'); - - -/* ---- - -name: Locale.fr-FR.Date - -description: Date messages for French. - -license: MIT-style license - -authors: - - Nicolas Sorosac - - Antoine Abt - -requires: - - /Locale - -provides: [Locale.fr-FR.Date] - -... -*/ - -Locale.define('fr-FR', 'Date', { - - months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'], - months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'], - days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'], - days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], - - // Culture's date order: DD/MM/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d/%m/%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: function(dayOfMonth){ - return (dayOfMonth > 1) ? '' : 'er'; - }, - - lessThanMinuteAgo: "il y a moins d'une minute", - minuteAgo: 'il y a une minute', - minutesAgo: 'il y a {delta} minutes', - hourAgo: 'il y a une heure', - hoursAgo: 'il y a {delta} heures', - dayAgo: 'il y a un jour', - daysAgo: 'il y a {delta} jours', - weekAgo: 'il y a une semaine', - weeksAgo: 'il y a {delta} semaines', - monthAgo: 'il y a 1 mois', - monthsAgo: 'il y a {delta} mois', - yearthAgo: 'il y a 1 an', - yearsAgo: 'il y a {delta} ans', - - lessThanMinuteUntil: "dans moins d'une minute", - minuteUntil: 'dans une minute', - minutesUntil: 'dans {delta} minutes', - hourUntil: 'dans une heure', - hoursUntil: 'dans {delta} heures', - dayUntil: 'dans un jour', - daysUntil: 'dans {delta} jours', - weekUntil: 'dans 1 semaine', - weeksUntil: 'dans {delta} semaines', - monthUntil: 'dans 1 mois', - monthsUntil: 'dans {delta} mois', - yearUntil: 'dans 1 an', - yearsUntil: 'dans {delta} ans' - -}); - - -/* ---- - -name: Locale.fr-FR.Form.Validator - -description: Form Validator messages for French. - -license: MIT-style license - -authors: - - Miquel Hudin - - Nicolas Sorosac - -requires: - - /Locale - -provides: [Locale.fr-FR.Form.Validator] - -... -*/ - -Locale.define('fr-FR', 'FormValidator', { - - required: 'Ce champ est obligatoire.', - minLength: 'Veuillez saisir un minimum de {minLength} caractère(s) (vous avez saisi {length} caractère(s)).', - maxLength: 'Veuillez saisir un maximum de {maxLength} caractère(s) (vous avez saisi {length} caractère(s)).', - integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres décimaux (ex : "1,25") ne sont pas autorisés.', - numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").', - digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un numéro de téléphone avec des traits d'union est autorisé).", - alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', - alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', - dateSuchAs: 'Veuillez saisir une date correcte comme {date}', - dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").', - email: 'Veuillez saisir une adresse de courrier électronique. Par example "fred@domaine.com".', - url: 'Veuillez saisir une URL, comme http://www.example.com.', - currencyDollar: 'Veuillez saisir une quantité correcte. Par example 100,00€.', - oneRequired: 'Veuillez sélectionner au moins une de ces options.', - errorPrefix: 'Erreur : ', - warningPrefix: 'Attention : ', - - // Form.Validator.Extras - noSpace: "Ce champ n'accepte pas les espaces.", - reqChkByNode: "Aucun élément n'est sélectionné.", - requiredChk: 'Ce champ est obligatoire.', - reqChkByName: 'Veuillez sélectionner un(e) {label}.', - match: 'Ce champ doit correspondre avec le champ {matchName}.', - startDate: 'date de début', - endDate: 'date de fin', - currendDate: 'date actuelle', - afterDate: 'La date doit être identique ou postérieure à {label}.', - beforeDate: 'La date doit être identique ou antérieure à {label}.', - startMonth: 'Veuillez sélectionner un mois de début.', - sameMonth: 'Ces deux dates doivent être dans le même mois - vous devez en modifier une.', - creditcard: 'Le numéro de carte de crédit est invalide. Merci de vérifier le numéro et de réessayer. Vous avez entré {length} chiffre(s).' - -}); - - -/* ---- - -name: Locale.fr-FR.Number - -description: Number messages for French. - -license: MIT-style license - -authors: - - Arian Stolwijk - - sv1l - -requires: - - /Locale - - /Locale.EU.Number - -provides: [Locale.fr-FR.Number] - -... -*/ - -Locale.define('fr-FR', 'Number', { - - group: ' ' // In fr-FR localization, group character is a blank space - -}).inherit('EU', 'Number'); - - -/* ---- - -name: Locale.he-IL.Date - -description: Date messages for Hebrew. - -license: MIT-style license - -authors: - - Elad Ossadon - -requires: - - /Locale - -provides: [Locale.he-IL.Date] - -... -*/ - -Locale.define('he-IL', 'Date', { - - months: ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], - months_abbr: ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], - days: ['ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת'], - days_abbr: ['ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת'], - - // Culture's date order: MM/DD/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d/%m/%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 0, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: 'לפני פחות מדקה', - minuteAgo: 'לפני כדקה', - minutesAgo: 'לפני {delta} דקות', - hourAgo: 'לפני כשעה', - hoursAgo: 'לפני {delta} שעות', - dayAgo: 'לפני יום', - daysAgo: 'לפני {delta} ימים', - weekAgo: 'לפני שבוע', - weeksAgo: 'לפני {delta} שבועות', - monthAgo: 'לפני חודש', - monthsAgo: 'לפני {delta} חודשים', - yearAgo: 'לפני שנה', - yearsAgo: 'לפני {delta} שנים', - - lessThanMinuteUntil: 'בעוד פחות מדקה', - minuteUntil: 'בעוד כדקה', - minutesUntil: 'בעוד {delta} דקות', - hourUntil: 'בעוד כשעה', - hoursUntil: 'בעוד {delta} שעות', - dayUntil: 'בעוד יום', - daysUntil: 'בעוד {delta} ימים', - weekUntil: 'בעוד שבוע', - weeksUntil: 'בעוד {delta} שבועות', - monthUntil: 'בעוד חודש', - monthsUntil: 'בעוד {delta} חודשים', - yearUntil: 'בעוד שנה', - yearsUntil: 'בעוד {delta} שנים' - -}); - - -/* ---- - -name: Locale.he-IL.Form.Validator - -description: Form Validator messages for Hebrew. - -license: MIT-style license - -authors: - - Elad Ossadon - -requires: - - /Locale - -provides: [Locale.he-IL.Form.Validator] - -... -*/ - -Locale.define('he-IL', 'FormValidator', { - - required: 'נא למלא שדה זה.', - minLength: 'נא להזין לפחות {minLength} תווים (הזנת {length} תווים).', - maxLength: 'נא להזין עד {maxLength} תווים (הזנת {length} תווים).', - integer: 'נא להזין מספר שלם לשדה זה. מספרים עשרוניים (כמו 1.25) אינם חוקיים.', - numeric: 'נא להזין ערך מספרי בלבד בשדה זה (כמו "1", "1.1", "-1" או "-1.1").', - digits: 'נא להזין רק ספרות וסימני הפרדה בשדה זה (למשל, מספר טלפון עם מקפים או נקודות הוא חוקי).', - alpha: 'נא להזין רק אותיות באנגלית (a-z) בשדה זה. רווחים או תווים אחרים אינם חוקיים.', - alphanum: 'נא להזין רק אותריות באנגלית (a-z) או ספרות (0-9) בשדה זה. אווחרים או תווים אחרים אינם חוקיים.', - dateSuchAs: 'נא להזין תאריך חוקי, כמו {date}', - dateInFormatMDY: 'נא להזין תאריך חוקי בפורמט MM/DD/YYYY (כמו "12/31/1999")', - email: 'נא להזין כתובת אימייל חוקית. לדוגמה: "fred@domain.com".', - url: 'נא להזין כתובת אתר חוקית, כמו http://www.example.com.', - currencyDollar: 'נא להזין סכום דולרי חוקי. לדוגמה $100.00.', - oneRequired: 'נא לבחור לפחות בשדה אחד.', - errorPrefix: 'שגיאה: ', - warningPrefix: 'אזהרה: ', - - // Form.Validator.Extras - noSpace: 'אין להזין רווחים בשדה זה.', - reqChkByNode: 'נא לבחור אחת מהאפשרויות.', - requiredChk: 'שדה זה נדרש.', - reqChkByName: 'נא לבחור {label}.', - match: 'שדה זה צריך להתאים לשדה {matchName}', - startDate: 'תאריך ההתחלה', - endDate: 'תאריך הסיום', - currendDate: 'התאריך הנוכחי', - afterDate: 'התאריך צריך להיות זהה או אחרי {label}.', - beforeDate: 'התאריך צריך להיות זהה או לפני {label}.', - startMonth: 'נא לבחור חודש התחלה', - sameMonth: 'שני תאריכים אלה צריכים להיות באותו חודש - נא לשנות אחד התאריכים.', - creditcard: 'מספר כרטיס האשראי שהוזן אינו חוקי. נא לבדוק שנית. הוזנו {length} ספרות.' - -}); - - -/* ---- - -name: Locale.he-IL.Number - -description: Number messages for Hebrew. - -license: MIT-style license - -authors: - - Elad Ossadon - -requires: - - /Locale - -provides: [Locale.he-IL.Number] - -... -*/ - -Locale.define('he-IL', 'Number', { - - decimal: '.', - group: ',', - - currency: { - suffix: ' ₪' - } - -}); - - -/* ---- - -name: Locale.hu-HU.Date - -description: Date messages for Hungarian. - -license: MIT-style license - -authors: - - Zsolt Szegheő - -requires: - - /Locale - -provides: [Locale.hu-HU.Date] - -... -*/ - -Locale.define('hu-HU', 'Date', { - - months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'], - months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], - days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek', 'Szombat'], - days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'], - - // Culture's date order: YYYY.MM.DD. - dateOrder: ['year', 'month', 'date'], - shortDate: '%Y.%m.%d.', - shortTime: '%I:%M', - AM: 'de.', - PM: 'du.', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '.', - - lessThanMinuteAgo: 'alig egy perce', - minuteAgo: 'egy perce', - minutesAgo: '{delta} perce', - hourAgo: 'egy órája', - hoursAgo: '{delta} órája', - dayAgo: '1 napja', - daysAgo: '{delta} napja', - weekAgo: '1 hete', - weeksAgo: '{delta} hete', - monthAgo: '1 hónapja', - monthsAgo: '{delta} hónapja', - yearAgo: '1 éve', - yearsAgo: '{delta} éve', - - lessThanMinuteUntil: 'alig egy perc múlva', - minuteUntil: 'egy perc múlva', - minutesUntil: '{delta} perc múlva', - hourUntil: 'egy óra múlva', - hoursUntil: '{delta} óra múlva', - dayUntil: '1 nap múlva', - daysUntil: '{delta} nap múlva', - weekUntil: '1 hét múlva', - weeksUntil: '{delta} hét múlva', - monthUntil: '1 hónap múlva', - monthsUntil: '{delta} hónap múlva', - yearUntil: '1 év múlva', - yearsUntil: '{delta} év múlva' - -}); - - -/* ---- - -name: Locale.hu-HU.Form.Validator - -description: Form Validator messages for Hungarian. - -license: MIT-style license - -authors: - - Zsolt Szegheő - -requires: - - /Locale - -provides: [Locale.hu-HU.Form.Validator] - -... -*/ - -Locale.define('hu-HU', 'FormValidator', { - - required: 'A mező kitöltése kötelező.', - minLength: 'Legalább {minLength} karakter megadása szükséges (megadva {length} karakter).', - maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).', - integer: 'Egész szám megadása szükséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.', - numeric: 'Szám megadása szükséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").', - digits: 'Csak számok és írásjelek megadása lehetséges (pl. telefonszám kötőjelek és/vagy perjelekkel).', - alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', - alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', - dateSuchAs: 'Valós dátum megadása szükséges (pl. {date}).', - dateInFormatMDY: 'Valós dátum megadása szükséges ÉÉÉÉ.HH.NN. formában. (pl. "1999.12.31.")', - email: 'Valós e-mail cím megadása szükséges (pl. "fred@domain.hu").', - url: 'Valós URL megadása szükséges (pl. http://www.example.com).', - currencyDollar: 'Valós pénzösszeg megadása szükséges (pl. 100.00 Ft.).', - oneRequired: 'Az alábbi mezők legalább egyikének kitöltése kötelező.', - errorPrefix: 'Hiba: ', - warningPrefix: 'Figyelem: ', - - // Form.Validator.Extras - noSpace: 'A mező nem tartalmazhat szóközöket.', - reqChkByNode: 'Nincs egyetlen kijelölt elem sem.', - requiredChk: 'A mező kitöltése kötelező.', - reqChkByName: 'Egy {label} kiválasztása szükséges.', - match: 'A mezőnek egyeznie kell a(z) {matchName} mezővel.', - startDate: 'a kezdet dátuma', - endDate: 'a vég dátuma', - currendDate: 'jelenlegi dátum', - afterDate: 'A dátum nem lehet kisebb, mint {label}.', - beforeDate: 'A dátum nem lehet nagyobb, mint {label}.', - startMonth: 'Kezdeti hónap megadása szükséges.', - sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.', - creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).' - -}); - - -/* ---- - -name: Locale.it-IT.Date - -description: Date messages for Italian. - -license: MIT-style license. - -authors: - - Andrea Novero - - Valerio Proietti - -requires: - - /Locale - -provides: [Locale.it-IT.Date] - -... -*/ - -Locale.define('it-IT', 'Date', { - - months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'], - months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], - days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'], - days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], - - // Culture's date order: DD/MM/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d/%m/%Y', - shortTime: '%H.%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: 'º', - - lessThanMinuteAgo: 'meno di un minuto fa', - minuteAgo: 'circa un minuto fa', - minutesAgo: 'circa {delta} minuti fa', - hourAgo: "circa un'ora fa", - hoursAgo: 'circa {delta} ore fa', - dayAgo: 'circa 1 giorno fa', - daysAgo: 'circa {delta} giorni fa', - - lessThanMinuteUntil: 'tra meno di un minuto', - minuteUntil: 'tra circa un minuto', - minutesUntil: 'tra circa {delta} minuti', - hourUntil: "tra circa un'ora", - hoursUntil: 'tra circa {delta} ore', - dayUntil: 'tra circa un giorno', - daysUntil: 'tra circa {delta} giorni' - -}); - - -/* ---- - -name: Locale.it-IT.Form.Validator - -description: Form Validator messages for Italian. - -license: MIT-style license - -authors: - - Leonardo Laureti - - Andrea Novero - -requires: - - /Locale - -provides: [Locale.it-IT.Form.Validator] - -... -*/ - -Locale.define('it-IT', 'FormValidator', { - - required: 'Il campo è obbligatorio.', - minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).', - maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).', - integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).', - numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").', - digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio è consentito un numero telefonico con trattini o punti.', - alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.', - alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.', - dateSuchAs: 'Inserire una data valida del tipo {date}', - dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")', - email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".', - url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".', - currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".', - oneRequired: 'Completare almeno uno dei campi richiesti.', - errorPrefix: 'Errore: ', - warningPrefix: 'Attenzione: ', - - // Form.Validator.Extras - noSpace: 'Non sono consentiti spazi.', - reqChkByNode: 'Nessuna voce selezionata.', - requiredChk: 'Il campo è obbligatorio.', - reqChkByName: 'Selezionare un(a) {label}.', - match: 'Il valore deve corrispondere al campo {matchName}', - startDate: "data d'inizio", - endDate: 'data di fine', - currendDate: 'data attuale', - afterDate: 'La data deve corrispondere o essere successiva al {label}.', - beforeDate: 'La data deve corrispondere o essere precedente al {label}.', - startMonth: "Selezionare un mese d'inizio", - sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.' - -}); - - -/* ---- - -name: Locale.ja-JP.Date - -description: Date messages for Japanese. - -license: MIT-style license - -authors: - - Noritaka Horio - -requires: - - /Locale - -provides: [Locale.ja-JP.Date] - -... -*/ - -Locale.define('ja-JP', 'Date', { - - months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], - months_abbr: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], - days: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'], - days_abbr: ['日', '月', '火', '水', '木', '金', '土'], - - // Culture's date order: YYYY/MM/DD - dateOrder: ['year', 'month', 'date'], - shortDate: '%Y/%m/%d', - shortTime: '%H:%M', - AM: '午前', - PM: '午後', - firstDayOfWeek: 0, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: '1分以内前', - minuteAgo: '約1分前', - minutesAgo: '約{delta}分前', - hourAgo: '約1時間前', - hoursAgo: '約{delta}時間前', - dayAgo: '1日前', - daysAgo: '{delta}日前', - weekAgo: '1週間前', - weeksAgo: '{delta}週間前', - monthAgo: '1ヶ月前', - monthsAgo: '{delta}ヶ月前', - yearAgo: '1年前', - yearsAgo: '{delta}年前', - - lessThanMinuteUntil: '今から約1分以内', - minuteUntil: '今から約1分', - minutesUntil: '今から約{delta}分', - hourUntil: '今から約1時間', - hoursUntil: '今から約{delta}時間', - dayUntil: '今から1日間', - daysUntil: '今から{delta}日間', - weekUntil: '今から1週間', - weeksUntil: '今から{delta}週間', - monthUntil: '今から1ヶ月', - monthsUntil: '今から{delta}ヶ月', - yearUntil: '今から1年', - yearsUntil: '今から{delta}年' - -}); - - -/* ---- - -name: Locale.ja-JP.Form.Validator - -description: Form Validator messages for Japanese. - -license: MIT-style license - -authors: - - Noritaka Horio - -requires: - - /Locale - -provides: [Locale.ja-JP.Form.Validator] - -... -*/ - -Locale.define("ja-JP", "FormValidator", { - - required: '入力は必須です。', - minLength: '入力文字数は{minLength}以上にしてください。({length}文字)', - maxLength: '入力文字数は{maxLength}以下にしてください。({length}文字)', - integer: '整数を入力してください。', - numeric: '入力できるのは数値だけです。(例: "1", "1.1", "-1", "-1.1"....)', - digits: '入力できるのは数値と句読記号です。 (例: -や+を含む電話番号など).', - alpha: '入力できるのは半角英字だけです。それ以外の文字は入力できません。', - alphanum: '入力できるのは半角英数字だけです。それ以外の文字は入力できません。', - dateSuchAs: '有効な日付を入力してください。{date}', - dateInFormatMDY: '日付の書式に誤りがあります。YYYY/MM/DD (i.e. "1999/12/31")', - email: 'メールアドレスに誤りがあります。', - url: 'URLアドレスに誤りがあります。', - currencyDollar: '金額に誤りがあります。', - oneRequired: 'ひとつ以上入力してください。', - errorPrefix: 'エラー: ', - warningPrefix: '警告: ', - - // FormValidator.Extras - noSpace: 'スペースは入力できません。', - reqChkByNode: '選択されていません。', - requiredChk: 'この項目は必須です。', - reqChkByName: '{label}を選択してください。', - match: '{matchName}が入力されている場合必須です。', - startDate: '開始日', - endDate: '終了日', - currendDate: '今日', - afterDate: '{label}以降の日付にしてください。', - beforeDate: '{label}以前の日付にしてください。', - startMonth: '開始月を選択してください。', - sameMonth: '日付が同一です。どちらかを変更してください。' - -}); - - -/* ---- - -name: Locale.ja-JP.Number - -description: Number messages for Japanese. - -license: MIT-style license - -authors: - - Noritaka Horio - -requires: - - /Locale - -provides: [Locale.ja-JP.Number] - -... -*/ - -Locale.define('ja-JP', 'Number', { - - decimal: '.', - group: ',', - - currency: { - decimals: 0, - prefix: '\\' - } - -}); - - -/* ---- - -name: Locale.nl-NL.Date - -description: Date messages for Dutch. - -license: MIT-style license - -authors: - - Lennart Pilon - - Tim Wienk - -requires: - - /Locale - -provides: [Locale.nl-NL.Date] - -... -*/ - -Locale.define('nl-NL', 'Date', { - - months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], - months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], - days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], - days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], - - // Culture's date order: DD-MM-YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d-%m-%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: 'e', - - lessThanMinuteAgo: 'minder dan een minuut geleden', - minuteAgo: 'ongeveer een minuut geleden', - minutesAgo: '{delta} minuten geleden', - hourAgo: 'ongeveer een uur geleden', - hoursAgo: 'ongeveer {delta} uur geleden', - dayAgo: 'een dag geleden', - daysAgo: '{delta} dagen geleden', - weekAgo: 'een week geleden', - weeksAgo: '{delta} weken geleden', - monthAgo: 'een maand geleden', - monthsAgo: '{delta} maanden geleden', - yearAgo: 'een jaar geleden', - yearsAgo: '{delta} jaar geleden', - - lessThanMinuteUntil: 'over minder dan een minuut', - minuteUntil: 'over ongeveer een minuut', - minutesUntil: 'over {delta} minuten', - hourUntil: 'over ongeveer een uur', - hoursUntil: 'over {delta} uur', - dayUntil: 'over ongeveer een dag', - daysUntil: 'over {delta} dagen', - weekUntil: 'over een week', - weeksUntil: 'over {delta} weken', - monthUntil: 'over een maand', - monthsUntil: 'over {delta} maanden', - yearUntil: 'over een jaar', - yearsUntil: 'over {delta} jaar' - -}); - - -/* ---- - -name: Locale.nl-NL.Form.Validator - -description: Form Validator messages for Dutch. - -license: MIT-style license - -authors: - - Lennart Pilon - - Arian Stolwijk - - Tim Wienk - -requires: - - /Locale - -provides: [Locale.nl-NL.Form.Validator] - -... -*/ - -Locale.define('nl-NL', 'FormValidator', { - - required: 'Dit veld is verplicht.', - minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).', - maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).', - integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.', - numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").', - digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).', - alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.', - alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.', - dateSuchAs: 'Vul een geldige datum in, zoals {date}', - dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")', - email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".', - url: 'Vul een geldige URL in, zoals http://www.example.com.', - currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .', - oneRequired: 'Vul iets in bij in ieder geval een van deze velden.', - warningPrefix: 'Waarschuwing: ', - errorPrefix: 'Fout: ', - - // Form.Validator.Extras - noSpace: 'Spaties zijn niet toegestaan in dit veld.', - reqChkByNode: 'Er zijn geen items geselecteerd.', - requiredChk: 'Dit veld is verplicht.', - reqChkByName: 'Selecteer een {label}.', - match: 'Dit veld moet overeen komen met het {matchName} veld', - startDate: 'de begin datum', - endDate: 'de eind datum', - currendDate: 'de huidige datum', - afterDate: 'De datum moet hetzelfde of na {label} zijn.', - beforeDate: 'De datum moet hetzelfde of voor {label} zijn.', - startMonth: 'Selecteer een begin maand', - sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.', - creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.' - -}); - - -/* ---- - -name: Locale.nl-NL.Number - -description: Number messages for Dutch. - -license: MIT-style license - -authors: - - Arian Stolwijk - -requires: - - /Locale - - /Locale.EU.Number - -provides: [Locale.nl-NL.Number] - -... -*/ - -Locale.define('nl-NL').inherit('EU', 'Number'); - - - - - -/* ---- - -name: Locale.no-NO.Date - -description: Date messages for Norwegian. - -license: MIT-style license - -authors: - - Espen 'Rexxars' Hovlandsdal - -requires: - - /Locale - -provides: [Locale.no-NO.Date] - -... -*/ - -Locale.define('no-NO', 'Date', { - - // Culture's date order: DD.MM.YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d.%m.%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - lessThanMinuteAgo: 'kortere enn et minutt siden', - minuteAgo: 'omtrent et minutt siden', - minutesAgo: '{delta} minutter siden', - hourAgo: 'omtrent en time siden', - hoursAgo: 'omtrent {delta} timer siden', - dayAgo: '{delta} dag siden', - daysAgo: '{delta} dager siden' - -}); - - -/* ---- - -name: Locale.no-NO.Form.Validator - -description: Form Validator messages for Norwegian. - -license: MIT-style license - -authors: - - Espen 'Rexxars' Hovlandsdal - -requires: - - /Locale - -provides: [Locale.no-NO.Form.Validator] - -... -*/ - -Locale.define('no-NO', 'FormValidator', { - - required: 'Dette feltet er pÃ¥krevd.', - minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).', - maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).', - integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.', - numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").', - digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.', - alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', - alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', - dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}', - dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")', - email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".', - url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.', - currencyDollar: 'Vennligst fyll ut et gyldig $ beløp. For eksempel $100.00 .', - oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.', - errorPrefix: 'Feil: ', - warningPrefix: 'Advarsel: ' - -}); - - -/* ---- - -name: Locale.pl-PL.Date - -description: Date messages for Polish. - -license: MIT-style license - -authors: - - Oskar Krawczyk - -requires: - - /Locale - -provides: [Locale.pl-PL.Date] - -... -*/ - -Locale.define('pl-PL', 'Date', { - - months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'], - months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'], - days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'], - days_abbr: ['niedz.', 'pon.', 'wt.', 'śr.', 'czw.', 'pt.', 'sob.'], - - // Culture's date order: YYYY-MM-DD - dateOrder: ['year', 'month', 'date'], - shortDate: '%Y-%m-%d', - shortTime: '%H:%M', - AM: 'nad ranem', - PM: 'po południu', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: function(dayOfMonth){ - return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)]; - }, - - lessThanMinuteAgo: 'mniej niż minute temu', - minuteAgo: 'około minutę temu', - minutesAgo: '{delta} minut temu', - hourAgo: 'około godzinę temu', - hoursAgo: 'około {delta} godzin temu', - dayAgo: 'Wczoraj', - daysAgo: '{delta} dni temu', - - lessThanMinuteUntil: 'za niecałą minutę', - minuteUntil: 'za około minutę', - minutesUntil: 'za {delta} minut', - hourUntil: 'za około godzinę', - hoursUntil: 'za około {delta} godzin', - dayUntil: 'za 1 dzień', - daysUntil: 'za {delta} dni' - -}); - - -/* ---- - -name: Locale.pl-PL.Form.Validator - -description: Form Validator messages for Polish. - -license: MIT-style license - -authors: - - Oskar Krawczyk - -requires: - - /Locale - -provides: [Locale.pl-PL.Form.Validator] - -... -*/ - -Locale.define('pl-PL', 'FormValidator', { - - required: 'To pole jest wymagane.', - minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).', - maxLength: 'Dozwolone jest nie więcej niż {maxLength} znaków (wpisanych zostało {length})', - integer: 'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.', - numeric: 'Prosimy używać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").', - digits: 'Prosimy używać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).', - alpha: 'Prosimy używać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.', - alphanum: 'Prosimy używać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.', - dateSuchAs: 'Prosimy podać prawidłową datę w formacie: {date}', - dateInFormatMDY: 'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")', - email: 'Prosimy podać prawidłowy adres e-mail, np. "jan@domena.pl".', - url: 'Prosimy podać prawidłowy adres URL, np. http://www.example.com.', - currencyDollar: 'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.', - oneRequired: 'Prosimy wypełnić chociaż jedno z pól.', - errorPrefix: 'Błąd: ', - warningPrefix: 'Uwaga: ', - - // Form.Validator.Extras - noSpace: 'W tym polu nie mogą znajdować się spacje.', - reqChkByNode: 'Brak zaznaczonych elementów.', - requiredChk: 'To pole jest wymagane.', - reqChkByName: 'Prosimy wybrać z {label}.', - match: 'To pole musi być takie samo jak {matchName}', - startDate: 'data początkowa', - endDate: 'data końcowa', - currendDate: 'aktualna data', - afterDate: 'Podana data poinna być taka sama lub po {label}.', - beforeDate: 'Podana data poinna być taka sama lub przed {label}.', - startMonth: 'Prosimy wybrać początkowy miesiąc.', - sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.' - -}); - - -/* ---- - -name: Locale.pt-PT.Date - -description: Date messages for Portuguese. - -license: MIT-style license - -authors: - - Fabio Miranda Costa - -requires: - - /Locale - -provides: [Locale.pt-PT.Date] - -... -*/ - -Locale.define('pt-PT', 'Date', { - - months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'], - months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], - days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'], - days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'], - - // Culture's date order: DD-MM-YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d-%m-%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: 'º', - - lessThanMinuteAgo: 'há menos de um minuto', - minuteAgo: 'há cerca de um minuto', - minutesAgo: 'há {delta} minutos', - hourAgo: 'há cerca de uma hora', - hoursAgo: 'há cerca de {delta} horas', - dayAgo: 'há um dia', - daysAgo: 'há {delta} dias', - weekAgo: 'há uma semana', - weeksAgo: 'há {delta} semanas', - monthAgo: 'há um mês', - monthsAgo: 'há {delta} meses', - yearAgo: 'há um ano', - yearsAgo: 'há {delta} anos', - - lessThanMinuteUntil: 'em menos de um minuto', - minuteUntil: 'em um minuto', - minutesUntil: 'em {delta} minutos', - hourUntil: 'em uma hora', - hoursUntil: 'em {delta} horas', - dayUntil: 'em um dia', - daysUntil: 'em {delta} dias', - weekUntil: 'em uma semana', - weeksUntil: 'em {delta} semanas', - monthUntil: 'em um mês', - monthsUntil: 'em {delta} meses', - yearUntil: 'em um ano', - yearsUntil: 'em {delta} anos' - -}); - - -/* ---- - -name: Locale.pt-BR.Date - -description: Date messages for Portuguese (Brazil). - -license: MIT-style license - -authors: - - Fabio Miranda Costa - -requires: - - /Locale - - /Locale.pt-PT.Date - -provides: [Locale.pt-BR.Date] - -... -*/ - -Locale.define('pt-BR', 'Date', { - - // Culture's date order: DD/MM/YYYY - shortDate: '%d/%m/%Y' - -}).inherit('pt-PT', 'Date'); - - -/* ---- - -name: Locale.pt-BR.Form.Validator - -description: Form Validator messages for Portuguese (Brazil). - -license: MIT-style license - -authors: - - Fábio Miranda Costa - -requires: - - /Locale - -provides: [Locale.pt-BR.Form.Validator] - -... -*/ - -Locale.define('pt-BR', 'FormValidator', { - - required: 'Este campo é obrigatório.', - minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).', - maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).', - integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).', - numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").', - digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).', - alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.', - alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.', - dateSuchAs: 'Digite uma data válida, como {date}', - dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")', - email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".', - url: 'Digite uma URL válida. Exemplo: http://www.example.com.', - currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .', - oneRequired: 'Digite algo para pelo menos um desses campos.', - errorPrefix: 'Erro: ', - warningPrefix: 'Aviso: ', - - // Form.Validator.Extras - noSpace: 'Não é possível digitar espaços neste campo.', - reqChkByNode: 'Não foi selecionado nenhum item.', - requiredChk: 'Este campo é obrigatório.', - reqChkByName: 'Por favor digite um {label}.', - match: 'Este campo deve ser igual ao campo {matchName}.', - startDate: 'a data inicial', - endDate: 'a data final', - currendDate: 'a data atual', - afterDate: 'A data deve ser igual ou posterior a {label}.', - beforeDate: 'A data deve ser igual ou anterior a {label}.', - startMonth: 'Por favor selecione uma data inicial.', - sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.', - creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.' - -}); - - -/* ---- - -name: Locale.pt-PT.Form.Validator - -description: Form Validator messages for Portuguese. - -license: MIT-style license - -authors: - - Miquel Hudin - -requires: - - /Locale - -provides: [Locale.pt-PT.Form.Validator] - -... -*/ - -Locale.define('pt-PT', 'FormValidator', { - - required: 'Este campo é necessário.', - minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).', - maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).', - integer: 'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.', - numeric: 'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").', - digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).', - alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.', - alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.', - dateSuchAs: 'Digite uma data válida, como {date}', - dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")', - email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".', - url: 'Digite uma URL válida, como http://www.example.com.', - currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ', - oneRequired: 'Digite algo para pelo menos um desses insumos.', - errorPrefix: 'Erro: ', - warningPrefix: 'Aviso: ' - -}); - - -/* ---- - -name: Locale.ru-RU-unicode.Date - -description: Date messages for Russian (utf-8). - -license: MIT-style license - -authors: - - Evstigneev Pavel - - Kuryanovich Egor - -requires: - - /Locale - -provides: [Locale.ru-RU.Date] - -... -*/ - -(function(){ - -// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/ -// one -> n mod 10 is 1 and n mod 100 is not 11; -// few -> n mod 10 in 2..4 and n mod 100 not in 12..14; -// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14; -// other -> everything else (example 3.14) -var pluralize = function (n, one, few, many, other){ - var modulo10 = n % 10, - modulo100 = n % 100; - - if (modulo10 == 1 && modulo100 != 11){ - return one; - } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ - return few; - } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ - return many; - } else { - return other; - } -}; - -Locale.define('ru-RU', 'Date', { - - months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'], - months_abbr: ['янв', 'февр', 'март', 'апр', 'май','июнь','июль','авг','сент','окт','нояб','дек'], - days: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'], - days_abbr: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'], - - // Culture's date order: DD.MM.YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d.%m.%Y', - shortTime: '%H:%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: 'меньше минуты назад', - minuteAgo: 'минуту назад', - minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'минуту', 'минуты', 'минут') + ' назад'; }, - hourAgo: 'час назад', - hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'час', 'часа', 'часов') + ' назад'; }, - dayAgo: 'вчера', - daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'день', 'дня', 'дней') + ' назад'; }, - weekAgo: 'неделю назад', - weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'неделя', 'недели', 'недель') + ' назад'; }, - monthAgo: 'месяц назад', - monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'месяц', 'месяца', 'месецев') + ' назад'; }, - yearAgo: 'год назад', - yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'год', 'года', 'лет') + ' назад'; }, - - lessThanMinuteUntil: 'меньше чем через минуту', - minuteUntil: 'через минуту', - minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часов') + ''; }, - hourUntil: 'через час', - hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'час', 'часа', 'часов') + ''; }, - dayUntil: 'завтра', - daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'день', 'дня', 'дней') + ''; }, - weekUntil: 'через неделю', - weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'неделю', 'недели', 'недель') + ''; }, - monthUntil: 'через месяц', - monthsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'месяц', 'месяца', 'месецев') + ''; }, - yearUntil: 'через', - yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'год', 'года', 'лет') + ''; } - -}); - -//<1.2compat> - -Locale.define('ru-RU-unicode').inherit('ru-RU', 'Date'); - -// - -})(); - - -/* ---- - -name: Locale.ru-RU-unicode.Form.Validator - -description: Form Validator messages for Russian (utf-8). - -license: MIT-style license - -authors: - - Chernodarov Egor - -requires: - - /Locale - -provides: [Locale.ru-RU.Form.Validator] - -... -*/ - -Locale.define('ru-RU', 'FormValidator', { - - required: 'Это поле обязательно к заполнению.', - minLength: 'Пожалуйста, введите хотя бы {minLength} символов (Вы ввели {length}).', - maxLength: 'Пожалуйста, введите не больше {maxLength} символов (Вы ввели {length}).', - integer: 'Пожалуйста, введите в это поле число. Дробные числа (например 1.25) тут не разрешены.', - numeric: 'Пожалуйста, введите в это поле число (например "1" или "1.1", или "-1", или "-1.1").', - digits: 'В этом поле Вы можете использовать только цифры и знаки пунктуации (например, телефонный номер со знаками дефиса или с точками).', - alpha: 'В этом поле можно использовать только латинские буквы (a-z). Пробелы и другие символы запрещены.', - alphanum: 'В этом поле можно использовать только латинские буквы (a-z) и цифры (0-9). Пробелы и другие символы запрещены.', - dateSuchAs: 'Пожалуйста, введите корректную дату {date}', - dateInFormatMDY: 'Пожалуйста, введите дату в формате ММ/ДД/ГГГГ (например "12/31/1999")', - email: 'Пожалуйста, введите корректный емейл-адрес. Для примера "fred@domain.com".', - url: 'Пожалуйста, введите правильную ссылку вида http://www.example.com.', - currencyDollar: 'Пожалуйста, введите сумму в долларах. Например: $100.00 .', - oneRequired: 'Пожалуйста, выберите хоть что-нибудь в одном из этих полей.', - errorPrefix: 'Ошибка: ', - warningPrefix: 'Внимание: ' - -}); - -//<1.2compat> - -Locale.define('ru-RU-unicode').inherit('ru-RU', 'FormValidator'); - -// - - -/* ---- - -name: Locale.si-SI.Date - -description: Date messages for Slovenian. - -license: MIT-style license - -authors: - - Radovan Lozej - -requires: - - /Locale - -provides: [Locale.si-SI.Date] - -... -*/ - -(function(){ - -var pluralize = function(n, one, two, three, other){ - return (n >= 1 && n <= 3) ? arguments[n] : other; -}; - -Locale.define('si-SI', 'Date', { - - months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], - months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], - days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'], - days_abbr: ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'], - - // Culture's date order: DD.MM.YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d.%m.%Y', - shortTime: '%H.%M', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '.', - - lessThanMinuteAgo: 'manj kot minuto nazaj', - minuteAgo: 'minuto nazaj', - minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; }, - hourAgo: 'uro nazaj', - hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; }, - dayAgo: 'dan nazaj', - daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; }, - weekAgo: 'teden nazaj', - weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; }, - monthAgo: 'mesec nazaj', - monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; }, - yearthAgo: 'leto nazaj', - yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; }, - - lessThanMinuteUntil: 'še manj kot minuto', - minuteUntil: 'še minuta', - minutesUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); }, - hourUntil: 'še ura', - hoursUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); }, - dayUntil: 'še dan', - daysUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); }, - weekUntil: 'še tedn', - weeksUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); }, - monthUntil: 'še mesec', - monthsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); }, - yearUntil: 'še leto', - yearsUntil: function(delta){ return 'še {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); } - -}); - -})(); - - -/* ---- - -name: Locale.si-SI.Form.Validator - -description: Form Validator messages for Slovenian. - -license: MIT-style license - -authors: - - Radovan Lozej - -requires: - - /Locale - -provides: [Locale.si-SI.Form.Validator] - -... -*/ - -Locale.define('si-SI', 'FormValidator', { - - required: 'To polje je obvezno', - minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).', - maxLength: 'Prosim, ne vnesite več kot {maxLength} znakov (vnesli ste {length} znakov).', - integer: 'Prosim, vnesite celo število. Decimalna števila (kot 1,25) niso dovoljena.', - numeric: 'Prosim, vnesite samo numerične vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").', - digits: 'Prosim, uporabite številke in ločila le na tem polju (na primer, dovoljena je telefonska številka z pomišlaji ali pikami).', - alpha: 'Prosim, uporabite le črke v tem plju. Presledki in drugi znaki niso dovoljeni.', - alphanum: 'Prosim, uporabite samo črke ali številke v tem polju. Presledki in drugi znaki niso dovoljeni.', - dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}', - dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")', - email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".', - url: 'Prosim, vnesite pravilen URL kot http://www.example.com.', - currencyDollar: 'Prosim, vnesit epravilno vrednost €. Primer 100,00€ .', - oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.', - errorPrefix: 'Napaka: ', - warningPrefix: 'Opozorilo: ', - - // Form.Validator.Extras - noSpace: 'To vnosno polje ne dopušča presledkov.', - reqChkByNode: 'Nič niste izbrali.', - requiredChk: 'To polje je obvezno', - reqChkByName: 'Prosim, izberite {label}.', - match: 'To polje se mora ujemati z poljem {matchName}', - startDate: 'datum začetka', - endDate: 'datum konca', - currendDate: 'trenuten datum', - afterDate: 'Datum bi moral biti isti ali po {label}.', - beforeDate: 'Datum bi moral biti isti ali pred {label}.', - startMonth: 'Prosim, vnesite začetni datum', - sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.', - creditcard: 'Številka kreditne kartice ni pravilna. Preverite številko ali poskusite še enkrat. Vnešenih {length} znakov.' - -}); - - -/* ---- - -name: Locale.sv-SE.Date - -description: Date messages for Swedish. - -license: MIT-style license - -authors: - - Martin Lundgren - -requires: - - /Locale - -provides: [Locale.sv-SE.Date] - -... -*/ - -Locale.define('sv-SE', 'Date', { - - months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], - months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], - days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'], - days_abbr: ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'], - - // Culture's date order: YYYY-MM-DD - dateOrder: ['year', 'month', 'date'], - shortDate: '%Y-%m-%d', - shortTime: '%H:%M', - AM: '', - PM: '', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: 'mindre än en minut sedan', - minuteAgo: 'ungefär en minut sedan', - minutesAgo: '{delta} minuter sedan', - hourAgo: 'ungefär en timme sedan', - hoursAgo: 'ungefär {delta} timmar sedan', - dayAgo: '1 dag sedan', - daysAgo: '{delta} dagar sedan', - - lessThanMinuteUntil: 'mindre än en minut sedan', - minuteUntil: 'ungefär en minut sedan', - minutesUntil: '{delta} minuter sedan', - hourUntil: 'ungefär en timme sedan', - hoursUntil: 'ungefär {delta} timmar sedan', - dayUntil: '1 dag sedan', - daysUntil: '{delta} dagar sedan' - -}); - - -/* ---- - -name: Locale.sv-SE.Form.Validator - -description: Form Validator messages for Swedish. - -license: MIT-style license - -authors: - - Martin Lundgren - -requires: - - /Locale - -provides: [Locale.sv-SE.Form.Validator] - -... -*/ - -Locale.define('sv-SE', 'FormValidator', { - - required: 'Fältet är obligatoriskt.', - minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).', - maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ', - integer: 'Ange ett heltal i fältet. Tal med decimaler (t.ex. 1,25) är inte tillåtna.', - numeric: 'Ange endast numeriska värden i detta fält (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").', - digits: 'Använd endast siffror och skiljetecken i detta fält (till exempel ett telefonnummer med bindestreck tillåtet).', - alpha: 'Använd endast bokstäver (a-ö) i detta fält. Inga mellanslag eller andra tecken är tillåtna.', - alphanum: 'Använd endast bokstäver (a-ö) och siffror (0-9) i detta fält. Inga mellanslag eller andra tecken är tillåtna.', - dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}', - dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")', - email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".', - url: 'Ange en giltig webbadress som http://www.example.com.', - currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.', - oneRequired: 'Vänligen ange minst ett av dessa alternativ.', - errorPrefix: 'Fel: ', - warningPrefix: 'Varning: ', - - // Form.Validator.Extras - noSpace: 'Det får inte finnas några mellanslag i detta fält.', - reqChkByNode: 'Inga objekt är valda.', - requiredChk: 'Detta är ett obligatoriskt fält.', - reqChkByName: 'Välj en {label}.', - match: 'Detta fält måste matcha {matchName}', - startDate: 'startdatumet', - endDate: 'slutdatum', - currendDate: 'dagens datum', - afterDate: 'Datumet bör vara samma eller senare än {label}.', - beforeDate: 'Datumet bör vara samma eller tidigare än {label}.', - startMonth: 'Välj en start månad', - sameMonth: 'Dessa två datum måste vara i samma månad - du måste ändra det ena eller det andra.' - -}); - - -/* ---- - -name: Locale.uk-UA.Date - -description: Date messages for Ukrainian (utf-8). - -license: MIT-style license - -authors: - - Slik - -requires: - - /Locale - -provides: [Locale.uk-UA.Date] - -... -*/ - -(function(){ - -var pluralize = function(n, one, few, many, other){ - var d = (n / 10).toInt(), - z = n % 10, - s = (n / 100).toInt(); - - if (d == 1 && n > 10) return many; - if (z == 1) return one; - if (z > 0 && z < 5) return few; - return many; -}; - -Locale.define('uk-UA', 'Date', { - - months: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень'], - months_abbr: ['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Черв', 'Лип', 'Серп', 'Вер', 'Жовт', 'Лист', 'Груд' ], - days: ['Неділя', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', "П'ятниця", 'Субота'], - days_abbr: ['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'], - - // Culture's date order: DD/MM/YYYY - dateOrder: ['date', 'month', 'year'], - shortDate: '%d/%m/%Y', - shortTime: '%H:%M', - AM: 'до полудня', - PM: 'по полудню', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: 'меньше хвилини тому', - minuteAgo: 'хвилину тому', - minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин') + ' тому'; }, - hourAgo: 'годину тому', - hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'годину', 'години', 'годин') + ' тому'; }, - dayAgo: 'вчора', - daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'день', 'дня', 'днів') + ' тому'; }, - weekAgo: 'тиждень тому', - weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів') + ' тому'; }, - monthAgo: 'місяць тому', - monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'місяць', 'місяці', 'місяців') + ' тому'; }, - yearAgo: 'рік тому', - yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'рік', 'роки', 'років') + ' тому'; }, - - lessThanMinuteUntil: 'за мить', - minuteUntil: 'через хвилину', - minutesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин'); }, - hourUntil: 'через годину', - hoursUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'годину', 'години', 'годин'); }, - dayUntil: 'завтра', - daysUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'день', 'дня', 'днів'); }, - weekUntil: 'через тиждень', - weeksUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів'); }, - monthUntil: 'через місяць', - monthesUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'місяць', 'місяці', 'місяців'); }, - yearUntil: 'через рік', - yearsUntil: function(delta){ return 'через {delta} ' + pluralize(delta, 'рік', 'роки', 'років'); } - -}); - -})(); - - -/* ---- - -name: Locale.uk-UA.Form.Validator - -description: Form Validator messages for Ukrainian (utf-8). - -license: MIT-style license - -authors: - - Slik - -requires: - - /Locale - -provides: [Locale.uk-UA.Form.Validator] - -... -*/ - -Locale.define('uk-UA', 'FormValidator', { - - required: 'Це поле повинне бути заповненим.', - minLength: 'Введіть хоча б {minLength} символів (Ви ввели {length}).', - maxLength: 'Кількість символів не може бути більше {maxLength} (Ви ввели {length}).', - integer: 'Введіть в це поле число. Дробові числа (наприклад 1.25) не дозволені.', - numeric: 'Введіть в це поле число (наприклад "1" або "1.1", або "-1", або "-1.1").', - digits: 'В цьому полі ви можете використовувати лише цифри і знаки пунктіації (наприклад, телефонний номер з знаками дефізу або з крапками).', - alpha: 'В цьому полі можна використовувати лише латинські літери (a-z). Пробіли і інші символи заборонені.', - alphanum: 'В цьому полі можна використовувати лише латинські літери (a-z) і цифри (0-9). Пробіли і інші символи заборонені.', - dateSuchAs: 'Введіть коректну дату {date}.', - dateInFormatMDY: 'Введіть дату в форматі ММ/ДД/РРРР (наприклад "12/31/2009").', - email: 'Введіть коректну адресу електронної пошти (наприклад "name@domain.com").', - url: 'Введіть коректне інтернет-посилання (наприклад http://www.example.com).', - currencyDollar: 'Введіть суму в доларах (наприклад "$100.00").', - oneRequired: 'Заповніть одне з полів.', - errorPrefix: 'Помилка: ', - warningPrefix: 'Увага: ', - - noSpace: 'Пробіли заборонені.', - reqChkByNode: 'Не відмічено жодного варіанту.', - requiredChk: 'Це поле повинне бути віміченим.', - reqChkByName: 'Будь ласка, відмітьте {label}.', - match: 'Це поле повинно відповідати {matchName}', - startDate: 'початкова дата', - endDate: 'кінцева дата', - currendDate: 'сьогоднішня дата', - afterDate: 'Ця дата повинна бути такою ж, або пізнішою за {label}.', - beforeDate: 'Ця дата повинна бути такою ж, або ранішою за {label}.', - startMonth: 'Будь ласка, виберіть початковий місяць', - sameMonth: 'Ці дати повинні відноситись одного і того ж місяця. Будь ласка, змініть одну з них.', - creditcard: 'Номер кредитної карти введений неправильно. Будь ласка, перевірте його. Введено {length} символів.' - -}); - - -/* ---- - -name: Locale.zh-CH.Date - -description: Date messages for Chinese (simplified and traditional). - -license: MIT-style license - -authors: - - YMind Chan - -requires: - - /Locale - -provides: [Locale.zh-CH.Date] - -... -*/ - -// Simplified Chinese -Locale.define('zh-CHS', 'Date', { - - months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], - months_abbr: ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'], - days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], - days_abbr: ['日', '一', '二', '三', '四', '五', '六'], - - // Culture's date order: YYYY-MM-DD - dateOrder: ['year', 'month', 'date'], - shortDate: '%Y-%m-%d', - shortTime: '%I:%M%p', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: '不到1分钟前', - minuteAgo: '大约1分钟前', - minutesAgo: '{delta}分钟之前', - hourAgo: '大约1小时前', - hoursAgo: '大约{delta}小时前', - dayAgo: '1天前', - daysAgo: '{delta}天前', - weekAgo: '1星期前', - weeksAgo: '{delta}星期前', - monthAgo: '1个月前', - monthsAgo: '{delta}个月前', - yearAgo: '1年前', - yearsAgo: '{delta}年前', - - lessThanMinuteUntil: '从现在开始不到1分钟', - minuteUntil: '从现在开始約1分钟', - minutesUntil: '从现在开始约{delta}分钟', - hourUntil: '从现在开始1小时', - hoursUntil: '从现在开始约{delta}小时', - dayUntil: '从现在开始1天', - daysUntil: '从现在开始{delta}天', - weekUntil: '从现在开始1星期', - weeksUntil: '从现在开始{delta}星期', - monthUntil: '从现在开始一个月', - monthsUntil: '从现在开始{delta}个月', - yearUntil: '从现在开始1年', - yearsUntil: '从现在开始{delta}年' - -}); - -// Traditional Chinese -Locale.define('zh-CHT', 'Date', { - - months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], - months_abbr: ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'], - days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], - days_abbr: ['日', '一', '二', '三', '四', '五', '六'], - - // Culture's date order: YYYY-MM-DD - dateOrder: ['year', 'month', 'date'], - shortDate: '%Y-%m-%d', - shortTime: '%I:%M%p', - AM: 'AM', - PM: 'PM', - firstDayOfWeek: 1, - - // Date.Extras - ordinal: '', - - lessThanMinuteAgo: '不到1分鐘前', - minuteAgo: '大約1分鐘前', - minutesAgo: '{delta}分鐘之前', - hourAgo: '大約1小時前', - hoursAgo: '大約{delta}小時前', - dayAgo: '1天前', - daysAgo: '{delta}天前', - weekAgo: '1星期前', - weeksAgo: '{delta}星期前', - monthAgo: '1个月前', - monthsAgo: '{delta}个月前', - yearAgo: '1年前', - yearsAgo: '{delta}年前', - - lessThanMinuteUntil: '從現在開始不到1分鐘', - minuteUntil: '從現在開始約1分鐘', - minutesUntil: '從現在開始約{delta}分鐘', - hourUntil: '從現在開始1小時', - hoursUntil: '從現在開始約{delta}小時', - dayUntil: '從現在開始1天', - daysUntil: '從現在開始{delta}天', - weekUntil: '從現在開始1星期', - weeksUntil: '從現在開始{delta}星期', - monthUntil: '從現在開始一個月', - monthsUntil: '從現在開始{delta}個月', - yearUntil: '從現在開始1年', - yearsUntil: '從現在開始{delta}年' - -}); - - -/* ---- - -name: Locale.zh-CH.Form.Validator - -description: Form Validator messages for Chinese (simplified and traditional). - -license: MIT-style license - -authors: - - YMind Chan - -requires: - - /Locale - - /Form.Validator - -provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator] - -... -*/ - -// Simplified Chinese -Locale.define('zh-CHS', 'FormValidator', { - - required: '此项必填。', - minLength: '请至少输入 {minLength} 个字符 (已输入 {length} 个)。', - maxLength: '最多只能输入 {maxLength} 个字符 (已输入 {length} 个)。', - integer: '请输入一个整数,不能包含小数点。例如:"1", "200"。', - numeric: '请输入一个数字,例如:"1", "1.1", "-1", "-1.1"。', - digits: '请输入由数字和标点符号组成的内容。例如电话号码。', - alpha: '请输入 A-Z 的 26 个字母,不能包含空格或任何其他字符。', - alphanum: '请输入 A-Z 的 26 个字母或 0-9 的 10 个数字,不能包含空格或任何其他字符。', - dateSuchAs: '请输入合法的日期格式,如:{date}。', - dateInFormatMDY: '请输入合法的日期格式,例如:YYYY-MM-DD ("2010-12-31")。', - email: '请输入合法的电子信箱地址,例如:"fred@domain.com"。', - url: '请输入合法的 Url 地址,例如:http://www.example.com。', - currencyDollar: '请输入合法的货币符号,例如:¥100.0', - oneRequired: '请至少选择一项。', - errorPrefix: '错误:', - warningPrefix: '警告:', - - // Form.Validator.Extras - noSpace: '不能包含空格。', - reqChkByNode: '未选择任何内容。', - requiredChk: '此项必填。', - reqChkByName: '请选择 {label}.', - match: '必须与{matchName}相匹配', - startDate: '起始日期', - endDate: '结束日期', - currendDate: '当前日期', - afterDate: '日期必须等于或晚于 {label}.', - beforeDate: '日期必须早于或等于 {label}.', - startMonth: '请选择起始月份', - sameMonth: '您必须修改两个日期中的一个,以确保它们在同一月份。', - creditcard: '您输入的信用卡号码不正确。当前已输入{length}个字符。' - -}); - -// Traditional Chinese -Locale.define('zh-CHT', 'FormValidator', { - - required: '此項必填。 ', - minLength: '請至少輸入{minLength} 個字符(已輸入{length} 個)。 ', - maxLength: '最多只能輸入{maxLength} 個字符(已輸入{length} 個)。 ', - integer: '請輸入一個整數,不能包含小數點。例如:"1", "200"。 ', - numeric: '請輸入一個數字,例如:"1", "1.1", "-1", "-1.1"。 ', - digits: '請輸入由數字和標點符號組成的內容。例如電話號碼。 ', - alpha: '請輸入AZ 的26 個字母,不能包含空格或任何其他字符。 ', - alphanum: '請輸入AZ 的26 個字母或0-9 的10 個數字,不能包含空格或任何其他字符。 ', - dateSuchAs: '請輸入合法的日期格式,如:{date}。 ', - dateInFormatMDY: '請輸入合法的日期格式,例如:YYYY-MM-DD ("2010-12-31")。 ', - email: '請輸入合法的電子信箱地址,例如:"fred@domain.com"。 ', - url: '請輸入合法的Url 地址,例如:http://www.example.com。 ', - currencyDollar: '請輸入合法的貨幣符號,例如:¥100.0', - oneRequired: '請至少選擇一項。 ', - errorPrefix: '錯誤:', - warningPrefix: '警告:', - - // Form.Validator.Extras - noSpace: '不能包含空格。 ', - reqChkByNode: '未選擇任何內容。 ', - requiredChk: '此項必填。 ', - reqChkByName: '請選擇 {label}.', - match: '必須與{matchName}相匹配', - startDate: '起始日期', - endDate: '結束日期', - currendDate: '當前日期', - afterDate: '日期必須等於或晚於{label}.', - beforeDate: '日期必須早於或等於{label}.', - startMonth: '請選擇起始月份', - sameMonth: '您必須修改兩個日期中的一個,以確保它們在同一月份。 ', - creditcard: '您輸入的信用卡號碼不正確。當前已輸入{length}個字符。 ' - -}); - -Form.Validator.add('validate-currency-yuan', { - - errorMsg: function(){ - return Form.Validator.getMsg('currencyYuan'); - }, - - test: function(element){ - // [¥]1[##][,###]+[.##] - // [¥]1###+[.##] - // [¥]0.## - // [¥].## - return Form.Validator.getValidator('IsEmpty').test(element) || (/^¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); - } - -}); - diff --git a/Demos/multiple-demos.php b/Demos/multiple-demos.php deleted file mode 100644 index bacac96..0000000 --- a/Demos/multiple-demos.php +++ /dev/null @@ -1,317 +0,0 @@ - - - - - MooTools FileManager Testground - - - - - - - - - - - - - - - - - - - - - -
    -
    - -
    - -

    FileManager Demo

    -
    - -
    -
    - -
    - - - - -
    - - -
    - -
    - -
    - - \ No newline at end of file diff --git a/Demos/selectImage.php b/Demos/selectImage.php deleted file mode 100644 index c41ad94..0000000 --- a/Demos/selectImage.php +++ /dev/null @@ -1,70 +0,0 @@ - $fm_basedir . 'Files/', // absolute paths: as the relative ones, they sit in URI space, i.e. assume DocumentRoot is root '/' - - 'directory' => 'Files/', // relative paths: are relative to the URI request script path, i.e. dirname(__FILE__) or rather: $_SERVER['SCRIPT_NAME'] - - 'filter' => 'image/', - 'upload' => true, - 'destroy' => false, - 'create' => true, - 'move' => true, - 'download' => false, - 'allowExtChange' => false // allow file name extensions to be changed; the default however is: NO (FALSE) -)); - - - -$event_cmd = (!empty($_GET['event']) ? $_GET['event'] : null); - -// log request data: -FM_vardumper($browser, 'init' . $event_cmd); - - - -// and process the request: -$browser->fireEvent($event_cmd); - - - - - - -// Do *NOT* add a close tag here! Any whitespace after that makes PHP output both a Content-Type: test/html header AND the whitespace as content. -// This BREAKS any operation (such as mootools-filemanager::event=thumbnail) which outputs BINARY DATA (in that particular case, PHP spits out an image) -// The safest way to prevent ANY PHP file from producing undesirable [whitespace] output is to never add that ?-> close tag. diff --git a/Demos/test-backend-basics.php b/Demos/test-backend-basics.php deleted file mode 100644 index 5f675fa..0000000 --- a/Demos/test-backend-basics.php +++ /dev/null @@ -1,398 +0,0 @@ - - - - - MooTools FileManager Backend Testground - - - - - - - - - - - - - - - - - -
    -
    - -
    - -

    FileManager Backend Tests

    - -

    Basic PHP tests

    -
    -= 32 ? htmlentities(chr($c), ENT_NOQUOTES, 'UTF-8') : '&#' . $c . ';');
    -				break;
    -			}
    -			$str .= chr($c);
    -		}
    -
    -		echo "ORIG:     [X" . $msg . "X]";
    -		if ($i < 2)
    -		{
    -			echo " (these characters are 'low ASCII' charactercodes " . ($i * 16) . " ... " . ($i * 16 + 15);
    -		}
    -		echo "\n";
    -
    -		$r = FileManagerUtility::pagetitle('X' . $str . 'X', null, $re_extra, $trim_extra);
    -
    -		echo "FILTERED: [" . htmlentities($r, ENT_NOQUOTES, 'UTF-8') . "]\n\n";
    -	}
    -
    -	$trimset = '_.';
    -	echo "\n\ntrim() with multiple characters in the trim set: [$trimset]\n";
    -
    -	$test = array(
    -		'.ignore',
    -		'___ignore',
    -		'_._.ignore',
    -		'._._ignore',
    -		'X.ignore',
    -		'X___ignore',
    -		'X_._.ignore',
    -		'X._._ignore',
    -		'__X_ignore',
    -		'_._X.ignore',
    -		'._.X_ignore'
    -		);
    -	foreach ($test as $t)
    -	{
    -		$r = trim($t, $trimset);
    -
    -		echo "\nORIG: [" . htmlentities($t, ENT_NOQUOTES, 'UTF-8') . "]\nRES:  [" . htmlentities($r, ENT_NOQUOTES, 'UTF-8') . "]\n";
    -	}
    -}
    -
    -
    -$browser = new FileManagerWithAliasSupport(array(
    -	'directory' => 'Files/',                   // relative paths: are relative to the URI request script path, i.e. dirname(__FILE__)
    -	//'thumbnailPath' => 'Files/Thumbnails/',
    -	'assetBasePath' => '../Assets',
    -	'chmod' => 0777,
    -	//'maxUploadSize' => 1024 * 1024 * 5,
    -	//'upload' => false,
    -	//'destroy' => false,
    -	//'create' => false,
    -	//'move' => false,
    -	//'download' => false,
    -	//'filter' => 'image/',
    -	'allowExtChange' => true,                  // allow file name extensions to be changed; the default however is: NO (FALSE)
    -	'UploadIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'DownloadIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'CreateIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'DestroyIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'MoveIsAuthorized_cb' => 'FM_IsAuthorized'
    -
    -	// http://httpd.apache.org/docs/2.2/mod/mod_alias.html -- we only emulate the Alias statement. (Also useful for VhostAlias, BTW!)
    -	// Implementing other path translation features is left as an exercise to the reader:
    -	, 'Aliases' => array(
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/alias' => "D:/xxx",
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/d' => "D:/xxx.tobesorted",
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/u' => "D:/websites-uploadarea",
    -
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files' => "D:/experiment"
    -	)
    -));
    -
    -echo "\n\n";
    -$settings = $browser->getSettings();
    -var_dump($settings);
    -
    -?>
    -	
    -

    Important server variables

    - -

    $_SERVER['DOCUMENT_ROOT'] = ''

    -

    $_SERVER['SCRIPT_NAME'] = ''

    - -

    FileManagerUtility class static methods

    - - -

    pagetitle(str, NULL, '', '')

    - - '.htaccess', 'expect' => 'htaccess'), - array('src' => 'regular.jpg', 'expect' => 'regular.jpg'), - array('src' => 'Umgebung Altstadt Østgat', 'expect' => 'Umgebung Altstadt Ostgat'), - array('src' => ' Sed ut perspiciatis unde omnis iste natus error ', 'expect' => 'Sed ut perspiciatis unde omnis iste natus error'), - array('src' => ' advantage from it? But who has any right ', 'expect' => 'advantage from it_ But who has any right'), - array('src' => 'welche aus geistiger Schwäche, d.h.', 'expect' => 'welche aus geistiger Schwaeche, d.h'), - array('src' => 'München - Ausrüstung - Spaß - Viele Grüße!', 'expect' => 'Muenchen - Ausruestung - Spass - Viele Gruesse!'), - array('src' => 'C:\\Windows\\TEMP\\', 'expect' => 'C_Windows_TEMP'), - array('src' => '/etc/passwd', 'expect' => 'etc_passwd'), - array('src' => 'Let\'s see what " quotes do?', 'expect' => 'Let_s see what _ quotes do'), - array('src' => '中国出售的软件必须使用编码 新 和 湖南北部 父母通常都会对子女说地方方言 现在香港的日常使用中出现了越来越多的简体汉字 华语, 走 贵州 看不懂 清浊声', 'expect' => ''), - array('src' => 'également appelé lorem ipsum', 'expect' => 'egalement appele lorem ipsum'), - array('src' => 'łaciński tekst pochodzący ze starożytności, zaczerpnięty', 'expect' => 'aci_ski tekst pochodz_cy ze staro_ytno_ci, zaczerpni_ty'), - array('src' => 'Ipsum текст Lorem, которые, как правило, бессмысленный список полу-латинские слова', 'expect' => 'Ipsum _ Lorem'), - array('src' => 'اول دو واژه از رشته ای از متن لاتین مورد استفاده در طراحی وب سایت و چاپ به جای انگلیسی به استرس و با تکیه تلفظ کردن اهمیت', 'expect' => ''), - array('src' => '설명하는 그래픽 등의 요소의 시각적 프레 젠 테이션, 문서 또는 글꼴 , 활판 인쇄술 , 그리고 레이아웃 . 의 세미 라틴어', 'expect' => ''), - array('src' => 'פילער טעקסט) צו באַווייַזן די גראַפיק עלעמענטן פון אַ דאָקומענט אָדער וויסואַל פּרעזענטירונג, אַזאַ ווי שריפֿט , טאַפּאַגראַפי', 'expect' => ')'), - array('src' => 'χρησιμοποιούνται κείμενο κράτησης θέσης (κείμενο πλήρωσης), για να αποδειχθεί η γραφικά στοιχεία', 'expect' => '(_ _)'), - array('src' => 'ルダテキスト (フィラーテキスト)示すために、グラフィックなどの要素を指定するの視覚的なプレゼンテーション、', 'expect' => ''), - array('src' => 'SQL: \'\'; DROP TABLE; \'', 'expect' => 'SQL_ _ DROP TABLE'), - array('src' => '', 'expect' => 'script_alert(_boom!_)_script'), - array('src' => '%20%2F%41%39 & X?', 'expect' => '20_2F_41_39 _amp_ X'), - array('src' => 'https://127893215784/xyz', 'expect' => 'https_127893215784_xyz'), - ); - -foreach ($test as $tc) -{ - $t = $tc['src']; - $e = $tc['expect']; - $r = FileManagerUtility::pagetitle($t, null, $re_extra, $trim_extra); - - echo "\n
    ORIG: [" . htmlentities($t, ENT_NOQUOTES, 'UTF-8') . "]\nRES:  [" . htmlentities($r, ENT_NOQUOTES, 'UTF-8') . "]
    \n"; - - if (strcmp($e, $r) != 0) - { - echo "

    FAILED!

    \n"; - } - echo "\n
    \n"; -} - -?> - - -

    getSiteRoot

    - -

    $_SERVER['DOCUMENT_ROOT'] = ''

    - -

    realpath('/') = ''

    - -

    getRequestPath

    - -

    getRequestPath() => 'getRequestPath(); ?>'

    - - -

    URI to abs & file path transform for DocumentRoot based URIs

    - - ''), - array('src' => '/'), - array('src' => 'Files/'), - array('src' => '/Files'), - array('src' => '/Files/'), - array('src' => 'Files/../alias'), - array('src' => 'Files/../d'), - array('src' => 'Files/../u'), - array('src' => '/alias'), - array('src' => '/d'), - array('src' => '/u'), - array('src' => 'Files/alias'), - array('src' => 'Files/d'), - array('src' => 'Files/u'), - array('src' => '../Demos/Files/u'), - array('src' => '../Assets/../Demos/Files'), - array('src' => 'Files/././../../D/.././Demos/Files'), - ); - -foreach ($test as $tc) -{ - $t = $tc['src']; - $emsg = null; - $r1 = ''; - $r2 = ''; - - try - { - $r1 = $browser->rel2abs_url_path($t); - $r2 = $browser->url_path2file_path($t); - } - catch(FileManagerException $e) - { - $emsg = $e->getMessage(); - } - - echo "\n
    ORIG:    [" . htmlentities($t, ENT_NOQUOTES, 'UTF-8') . "]\nURI.ABS: [" . htmlentities($r1, ENT_NOQUOTES, 'UTF-8') . "]\nDIR.ABS: [" . htmlentities($r2, ENT_NOQUOTES, 'UTF-8') . "]
    \n"; - - if ($emsg !== null) - { - echo "

    FileManagerException('$emsg')!

    \n"; - } - echo "\n
    \n"; -} - -?> - -

    URI to abs & file path transform for options['directory'] based URIs

    - -normalize($r3); - $r4 = $browser->url_path2file_path($r3); - $r1 = $browser->rel2abs_legal_url_path($t); - $r2 = $browser->legal_url_path2file_path($t); - } - catch(FileManagerException $e) - { - $emsg = $e->getMessage(); - } - - echo "\n
    ORIG:    [" . htmlentities($t, ENT_NOQUOTES, 'UTF-8') . "]\nURI.ABS: [" . htmlentities($r1, ENT_NOQUOTES, 'UTF-8') . "]\nDIR.ABS: [" . htmlentities($r2, ENT_NOQUOTES, 'UTF-8') . "]\nRAW.URI: [" . htmlentities($r3, ENT_NOQUOTES, 'UTF-8') . "]\nNORMLZD: [" . htmlentities($r5, ENT_NOQUOTES, 'UTF-8') . "]\nDIR.ABS: [" . htmlentities($r4, ENT_NOQUOTES, 'UTF-8') . "]
    \n"; - - if ($emsg !== null) - { - echo "

    FileManagerException('$emsg')!

    \n"; - } - echo "\n
    \n"; -} - -?> - -

    FM Aliased directory scan output

    - - ''), - array('src' => '/'), - array('src' => '/Files'), - array('src' => '/Files/..'), - array('src' => '/Files/../alias/'), - array('src' => '/Files/../d'), - array('src' => '/Files/../u'), - ); - - -foreach ($test as $tc) -{ - $t = $tc['src']; - $emsg = null; - $r1 = ''; - $r2 = ''; - $c1 = ''; - - try - { - $r1 = $browser->rel2abs_legal_url_path($t); - $r2 = $browser->legal_url_path2file_path($t); - - $c1 = $browser->scandir($r2, '*', false, 0, ~0); - } - catch(FileManagerException $e) - { - $emsg = $e->getMessage(); - } - - echo "\n
    dir = '$r2'
    \n"; - - echo "\n

    scandir output:

    \n
    ";
    -	var_dump($c1);
    -	echo "
    \n"; - - if ($emsg !== null) - { - echo "

    FileManagerException('$emsg')!

    \n"; - } - echo "\n
    \n"; -} - -?> - - - -
    - - - - - diff --git a/Demos/test-backend-detail.php b/Demos/test-backend-detail.php deleted file mode 100644 index e16063d..0000000 --- a/Demos/test-backend-detail.php +++ /dev/null @@ -1,155 +0,0 @@ - - - - - MooTools FileManager Backend Testground - - - - - - - - - - - - - - - - - -
    -
    - -
    - -

    FileManager Backend Tests

    - -

    Basic PHP tests

    -
    - 'Files/',                   // relative paths: are relative to the URI request script path, i.e. dirname(__FILE__)
    -	'thumbnailPath' => 'Files/Thumbnails/',
    -	//'assetBasePath' => '../Assets',
    -	'chmod' => 0777,
    -	//'maxUploadSize' => 1024 * 1024 * 5,
    -	//'upload' => false,
    -	//'destroy' => false,
    -	//'create' => false,
    -	//'move' => false,
    -	//'download' => false,
    -	//'filter' => 'image/',
    -	'allowExtChange' => true,                  // allow file name extensions to be changed; the default however is: NO (FALSE)
    -	'UploadIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'DownloadIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'CreateIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'DestroyIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'MoveIsAuthorized_cb' => 'FM_IsAuthorized'
    -
    -	// http://httpd.apache.org/docs/2.2/mod/mod_alias.html -- we only emulate the Alias statement. (Also useful for VhostAlias, BTW!)
    -	// Implementing other path translation features is left as an exercise to the reader:
    -	, 'Aliases' => array(
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/alias' => "D:/xxx",
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/d' => "D:/xxx.tobesorted",
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/u' => "D:/websites-uploadarea",
    -
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files' => "D:/experiment"
    -	)
    -));
    -
    -echo "\n\n";
    -$settings = $browser->getSettings();
    -var_dump($settings);
    -
    -?>
    -	
    -

    Important server variables

    - -

    $_SERVER['DOCUMENT_ROOT'] = ''

    -

    $_SERVER['SCRIPT_NAME'] = ''

    - - - -

    FM 'detail' output

    - -
    -fireEvent('detail');
    -$dump = ob_get_clean();
    -
    -echo "input:\n";
    -var_dump($_POST);
    -echo "\n\noutput for event 'detail':\n\n";
    -
    -$json = json_decode($dump);
    -var_dump($json);
    -
    -?>
    -	
    - - -
    -fireEvent('detail');
    -$dump = ob_get_clean();
    -
    -echo "input:\n";
    -var_dump($_POST);
    -echo "\n\noutput for event 'detail':\n\n";
    -
    -//echo "
    " . htmlentities($dump, ENT_NOQUOTES, 'UTF-8') . "
    \n";
    -//echo "\n\n";
    -
    -$json = json_decode($dump);
    -var_dump($json);
    -
    -?>
    -	
    - -
    - - diff --git a/Demos/test-backend-dirscan.php b/Demos/test-backend-dirscan.php deleted file mode 100644 index a8dba25..0000000 --- a/Demos/test-backend-dirscan.php +++ /dev/null @@ -1,175 +0,0 @@ - - - - - MooTools FileManager Backend Testground - - - - - - - - - - - - - - - - - -
    -
    - -
    - -

    FileManager Backend Tests

    - -

    Basic PHP tests

    -
    - 'Files/',                   // relative paths: are relative to the URI request script path, i.e. dirname(__FILE__)
    -	//'thumbnailPath' => 'Files/Thumbnails/',
    -	'assetBasePath' => '../Assets',
    -	'chmod' => 0777,
    -	//'maxUploadSize' => 1024 * 1024 * 5,
    -	//'upload' => false,
    -	//'destroy' => false,
    -	//'create' => false,
    -	//'move' => false,
    -	//'download' => false,
    -	//'filter' => 'image/',
    -	'allowExtChange' => true,                  // allow file name extensions to be changed; the default however is: NO (FALSE)
    -	'UploadIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'DownloadIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'CreateIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'DestroyIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'MoveIsAuthorized_cb' => 'FM_IsAuthorized'
    -
    -	// http://httpd.apache.org/docs/2.2/mod/mod_alias.html -- we only emulate the Alias statement. (Also useful for VhostAlias, BTW!)
    -	// Implementing other path translation features is left as an exercise to the reader:
    -	, 'Aliases' => array(
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/alias' => "D:/xxx",
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/d' => "D:/xxx.tobesorted",
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/u' => "D:/websites-uploadarea",
    -
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files' => "D:/experiment"
    -	)
    -));
    -
    -echo "\n\n";
    -$settings = $browser->getSettings();
    -var_dump($settings);
    -
    -?>
    -	
    -

    Important server variables

    - -

    $_SERVER['DOCUMENT_ROOT'] = ''

    -

    $_SERVER['SCRIPT_NAME'] = ''

    - - - -

    getSiteRoot

    - -

    $_SERVER['DOCUMENT_ROOT'] = ''

    - -

    realpath('/') = ''

    - -

    getRequestPath

    - -

    getRequestPath() => 'getRequestPath(); ?>'

    - - -

    FM Aliased directory scan output

    - - ''), - array('src' => '/'), - array('src' => '/Files'), - array('src' => '/Files/..'), - array('src' => '/Files/../alias/'), - array('src' => '/Files/../d'), - array('src' => '/Files/../u'), - ); - - -foreach ($test as $tc) -{ - $t = $tc['src']; - $emsg = null; - $r1 = ''; - $r2 = ''; - $c1 = ''; - - try - { - $r1 = $browser->rel2abs_legal_url_path($t); - $r2 = $browser->legal_url_path2file_path($t); - - $c1 = $browser->scandir($r2, '*', false, 0, ~0); - } - catch(FileManagerException $e) - { - $emsg = $e->getMessage(); - } - - echo "\n
    dir = '$r2'
    \n"; - - echo "\n

    scandir output:

    \n
    ";
    -	var_dump($c1);
    -	echo "
    \n"; - - if ($emsg !== null) - { - echo "

    FileManagerException('$emsg')!

    \n"; - } - echo "\n
    \n"; -} - -?> - - - - -
    - - - - - - diff --git a/Demos/test-backend-view.php b/Demos/test-backend-view.php deleted file mode 100644 index 6f8cc93..0000000 --- a/Demos/test-backend-view.php +++ /dev/null @@ -1,137 +0,0 @@ - - - - - MooTools FileManager Backend Testground - - - - - - - - - - - - - - - - - -
    -
    - -
    - -

    FileManager Backend Tests

    - -

    Basic PHP tests

    -
    - 'Files/',                   // relative paths: are relative to the URI request script path, i.e. dirname(__FILE__)
    -	//'thumbnailPath' => 'Files/Thumbnails/',
    -	'assetBasePath' => '../Assets',
    -	'chmod' => 0777,
    -	//'maxUploadSize' => 1024 * 1024 * 5,
    -	//'upload' => false,
    -	//'destroy' => false,
    -	//'create' => false,
    -	//'move' => false,
    -	//'download' => false,
    -	//'filter' => 'image/',
    -	'allowExtChange' => true,                  // allow file name extensions to be changed; the default however is: NO (FALSE)
    -	'UploadIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'DownloadIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'CreateIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'DestroyIsAuthorized_cb' => 'FM_IsAuthorized',
    -	'MoveIsAuthorized_cb' => 'FM_IsAuthorized'
    -
    -	// http://httpd.apache.org/docs/2.2/mod/mod_alias.html -- we only emulate the Alias statement. (Also useful for VhostAlias, BTW!)
    -	// Implementing other path translation features is left as an exercise to the reader:
    -	, 'Aliases' => array(
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/alias' => "D:/xxx",
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/d' => "D:/xxx.tobesorted",
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files/u' => "D:/websites-uploadarea",
    -
    -	//  '/c/lib/includes/js/mootools-filemanager/Demos/Files' => "D:/experiment"
    -	)
    -));
    -
    -echo "\n\n";
    -$settings = $browser->getSettings();
    -var_dump($settings);
    -
    -?>
    -	
    -

    Important server variables

    - -

    $_SERVER['DOCUMENT_ROOT'] = ''

    -

    $_SERVER['SCRIPT_NAME'] = ''

    - - - -

    FM 'view' output

    - - - -
    -fireEvent('view');
    -$dump = ob_get_clean();
    -
    -echo "input:\n";
    -var_dump($_POST);
    -echo "\n\noutput for event 'view':\n\n";
    -
    -$json = json_decode($dump);
    -var_dump($json);
    -
    -?>
    -	
    -
    - - - -
    - - - - diff --git a/Demos/test-backend.php b/Demos/test-backend.php deleted file mode 100644 index df2fba8..0000000 --- a/Demos/test-backend.php +++ /dev/null @@ -1,59 +0,0 @@ - - - - - MooTools FileManager Backend Testground - - - - -
    -
    - -
    - -

    FileManager Backend Tests

    - -

    - basic tests -

    - -

    - dirscan tests -

    - -

    - 'detail' event tests -

    - -

    - 'view' event tests -

    -
    - - - - diff --git a/Demos/tinymce.php b/Demos/tinymce.php deleted file mode 100644 index 013f057..0000000 --- a/Demos/tinymce.php +++ /dev/null @@ -1,120 +0,0 @@ - - - - - MooTools FileManager TinyMCE example - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - -
    - -

    FileManager Demo

    - -
    - - -
    -
    -
    - - \ No newline at end of file diff --git a/Source/FileManager.TinyMCE.js b/Source/FileManager.TinyMCE.js index 89bc289..cb2189a 100644 --- a/Source/FileManager.TinyMCE.js +++ b/Source/FileManager.TinyMCE.js @@ -18,67 +18,50 @@ Usage: */ FileManager.TinyMCE = function(options){ - /* - * field: Id of the element to set value in. - * url: value currently stored in the indicated element - * type: Type of browser to open image/file/flash: 'file' ~ page links, 'image' ~ insert picture, 'media' ~ insert media/movie - * win: window object reference - */ - return function(field, url, type, win){ - var manager = new FileManager(Object.append({ - onComplete: function(path, file, mgr) { - if (!win.document) return; - win.document.getElementById(field).value = path; - if (win.ImageDialog) { - win.ImageDialog.showPreviewImage(path, 1); - } - this.container.destroy(); - } - }, options(type), - { - zIndex: 400000, - styles: { - 'width': '90%', - 'height': '90%' - } - })); - //manager.dragZIndex = 400002; - //manager.SwiffZIndex = 400003; - //manager.filemanager.setStyle('width','90%'); - //manager.filemanager.setStyle('height','90%'); - //manager.filemanager.setStyle('zIndex', 400001); - //if (manager.overlay) manager.overlay.el.setStyle('zIndex', 400000); // i.e. only do this when FileManager settings has 'hideOverlay: false' (default) - //document.id(manager.tips).setStyle('zIndex', 400010); - var src = win.document.getElementById(field).value; + /* + * field: Id of the element to set value in. + * url: value currently stored in the indicated element + * type: Type of browser to open image/file/flash: 'file' ~ page links, 'image' ~ insert picture, 'media' ~ insert media/movie + * win: window object reference + */ + return function(field, url, type, win){ + var manager; // jsLint warning fix + manager = new FileManager(Object.append({ + onComplete: function(path, file, mgr) { + if (!win.document) + return; + //path = manager.escapeRFC3986(path); + win.document.getElementById(field).value = path; + if (win.ImageDialog) { + win.ImageDialog.showPreviewImage(path, 1); + } + this.container.destroy(); + } + }, options(type), + { + zIndex: 400000, + styles: { + 'width': '90%', + 'height': '90%' + } + })); + var src = win.document.getElementById(field).value; - src = decodeURI(src); + src = decodeURI(src); - if (src.length > 0) - { - src = this.documentBaseURI.toAbsolute(src); - } - if (src.match(/^[a-z]+:/i)) - { - // strip off scheme + authority sections: - src = src.replace(/^[a-z]+:(\/\/?)[^\/]*/i, ''); - } + if (src.length > 0) + { + src = this.documentBaseURI.toAbsolute(src); + } + if (src.match(/^[a-z]+:/i)) + { + // strip off scheme + authority sections: + src = src.replace(/^[a-z]+:(\/\/?)[^\/]*/i, ''); + } - // pass full path to 'preselect': backend will take care of it for us - manager.show(null, null, (src.length > 0 ? src : null)); - return manager; - }; + // pass full path to 'preselect': backend will take care of it for us + manager.show(null, null, (src.length > 0 ? src : null)); + return manager; + }; }; -//FileManager.implement('SwiffZIndex', 400003); - -//FileManager.Dialog = new Class({ -// -// Extends: FileManager.Dialog, -// -// initialize: function(text, options){ -// this.parent(text, options); -// this.el.setStyle('zIndex', 400010); -// this.overlay.el.setStyle('zIndex', 400009); -// } -//}); - diff --git a/Source/FileManager.js b/Source/FileManager.js index f4640b2..2a41925 100644 --- a/Source/FileManager.js +++ b/Source/FileManager.js @@ -8,8 +8,8 @@ authors: Christoph Pojer (@cpojer), Fabian Vogelsteller (@frozeman) license: MIT-style license requires: - core/1.3.1: '*' - more/1.3.1.1: [Request.Queue, Array.Extras, String.QueryString, Hash, Element.Delegation, Element.Measure, Fx.Scroll, Fx.SmoothScroll, Drag, Drag.Move, Assets, Tips ] + core/1.3.2: '*' + more/1.3.2.1: [Request.Queue, Array.Extras, String.QueryString, Hash, Element.Delegation, Element.Measure, Fx.Scroll, Fx.SmoothScroll, Drag, Drag.Move, Assets, Tips, Scroller ] provides: Filemanager @@ -27,54 +27,9 @@ var FileManager = new Class({ ID: null, options: { - /* - * onComplete: function( // Fired when the 'Select' button is clicked - * path, // URLencoded absolute URL path to selected file - * file, // the file specs object: .name, .path, .size, .date, .mime, .icon, .icon48, .thumb48, .thumb250 - * fmobj // reference to the FileManager instance which fired the event - * ) - * - * onModify: function( // Fired when either the 'Rename' or 'Delete' icons are clicked or when a file is drag&dropped. - * // Fired AFTER the action is executed. - * file, // a CLONE of the file specs object: .name, .path, .size, .date, .mime, .icon, .icon48, .thumb48, .thumb250 - * json, // The JSON data as sent by the server for this 'destroy/rename/move/copy' request - * mode, // string specifying the action: 'destroy', 'rename', 'move', 'copy' - * fmobj // reference to the FileManager instance which fired the event - * ) - * - * onShow: function( // Fired AFTER the file manager is rendered - * fmobj // reference to the FileManager instance which fired the event - * ) - * - * onHide: function( // Fired AFTER the file manager is removed from the DOM - * fmobj // reference to the FileManager instance which fired the event - * ) - * - * onScroll: function( // Cascade of the window scroll event - * e, // reference to the event object (argument passed from the window.scroll event) - * fmobj // reference to the FileManager instance which fired the event - * ) - * - * onPreview: function( // Fired when the preview thumbnail image is clicked - * src, // this.get('src') ??? - * fmobj, // reference to the FileManager instance which fired the event - * el // reference to the 'this' ~ the element which was clicked - * ) - * - * onDetails: function( // Fired when an item is picked from the files list to be previewed - * // Fired AFTER the server request is completed and BEFORE the preview is rendered. - * json, // The JSON data as sent by the server for this 'detail' request - * fmobj // reference to the FileManager instance which fired the event - * ) - * - * onHidePreview: function( // Fired when the preview is hidden (e.g. when uploading) - * // Fired BEFORE the preview is removed from the DOM. - * fmobj // reference to the FileManager instance which fired the event - * ) - */ - directory: '', + directory: '', // (string) the directory (relative path) which should be loaded on startup (show). url: null, - assetBasePath: null, + assetsUrl: null, language: 'en', selectable: false, destroy: false, @@ -83,21 +38,24 @@ var FileManager = new Class({ download: false, createFolders: false, filter: '', + detailInfoMode: '', // (string) whether you want to receive extra metadata on select/etc. and/or view this metadata in the preview pane (modes: '', '+metaHTML', '+metaJSON'. Modes may be combined) + deliverPathAsLegalURL: false, // (boolean) TRUE: deliver 'legal URL' paths, i.e. PHP::options['URLpath4FileManagedDirTree']-rooted (~ this.root), FALSE: deliver absolute URI paths. hideOnClick: false, hideClose: false, hideOverlay: false, hideQonDelete: false, - verbose: false, + hideOnSelect: true, // (boolean). Default to true. If set to false, it leavers the FM open after a picture select. + thumbSize4DirGallery: 120, // To set the thumb gallery container size for each thumb (dir-gal-thumb-bg); depending on size, it will pick either the small or large thumbnail provided by the backend and scale that one zIndex: 1000, styles: {}, listPaginationSize: 100, // add pagination per N items for huge directories (speed up interaction) listPaginationAvgWaitTime: 2000, // adaptive pagination: strive to, on average, not spend more than this on rendering a directory chunk - propagateData: {}, // extra query parameters sent with every request to the backend standalone: true, // (boolean). Default to true. If set to false, returns the Filemanager without enclosing window / overlay. + advancedEffects: true, // (boolean). Default to true. Fading effect on panels. Slow when large thumbs dir. parentContainer: null, // (string). ID of the parent container. If not set, FM will consider its first container parent for fitSizes(); - hideOnSelect: true, // (boolean). Default to true. If set to false, it leavers the FM open after a picture select. - + propagateData: {}, // extra query parameters sent with every request to the backend + verbose: false, mkServerRequestURL: null // (function) specify your own alternative URL/POST data constructor when you use a framework/system which requires such. function([object] fm_obj, [string] request_code, [assoc.array] post_data) }, @@ -119,7 +77,9 @@ var FileManager = new Class({ this.diag.verbose = this.options.verbose; this.ID = String.uniqueID(); this.droppables = []; - this.assetBasePath = this.options.assetBasePath.replace(/(\/|\\)*$/, '/'); + // Relative or absolute URL to the Filemanager assets + this.assetsUrl = this.options.assetsUrl.replace(/(\/|\\)*$/, '/'); + this.root = null; this.CurrentDir = null; this.listType = 'list'; @@ -140,11 +100,11 @@ var FileManager = new Class({ // timer for dir-gallery click / dblclick events: this.dir_gallery_click_timer = null; - var dbg_cnt = 0; this.RequestQueue = new Request.Queue({ - concurrent: 3, // 3 --> 75% max load on a quad core server + // 3 --> 75% max load on a quad core server + concurrent: 3, autoAdvance: true, stopOnFailure: false, @@ -153,8 +113,6 @@ var FileManager = new Class({ }).bind(this), onComplete: (function(name){ - //this.diag.log('request queue: onComplete: ', arguments); - // clean out the item from the queue; doesn't seem to happen automatically :-( var cnt = 0; Object.each(this.RequestQueue.requests, function() { @@ -215,13 +173,12 @@ var FileManager = new Class({ this.language = Object.merge(this.language, FileManager.Language[this.options.language]); } -// Partikule + // Hides the overlay / close button in standalone mode if (!this.options.standalone) { this.options.hideOverlay = true; this.options.hideClose = true; } -// /Partikule this.container = new Element('div', { 'class': 'filemanager-container' + (Browser.opera ? ' filemanager-engine-presto' : '') + (Browser.ie ? ' filemanager-engine-trident' : '') + (Browser.ie8 ? '4' : '') + (Browser.ie9 ? '5' : ''), @@ -239,19 +196,12 @@ var FileManager = new Class({ }) }).inject(this.container); this.header = new Element('div', { - 'class': 'filemanager-header' /* , - styles: - { - 'z-index': this.options.zIndex + 3 - } */ + 'class': 'filemanager-header' }).inject(this.filemanager); this.menu = new Element('div', { - 'class': 'filemanager-menu' /* , - styles: - { - 'z-index': this.options.zIndex + 2 - } */ + 'class': 'filemanager-menu' }).inject(this.filemanager); + this.loader = new Element('div', {'class': 'loader', opacity: 0, tween: {duration: 'short'}}).inject(this.header); this.previewLoader = new Element('div', {'class': 'loader', opacity: 0, tween: {duration: 'short'}}); this.browserLoader = new Element('div', {'class': 'loader', opacity: 0, tween: {duration: 'short'}}); @@ -271,15 +221,12 @@ var FileManager = new Class({ }).bind(this)); this.header.adopt(this.pathTitle,this.clickablePath); -// Partikule -// Because the header is positioned -30px before the container, we hide it for the moment if the FM isn't standalone. -// Need to think about a better integration + // Because the header is positioned -30px before the container, we hide it for the moment if the FM isn't standalone. if (!this.options.standalone) { this.header.hide(); this.filemanager.setStyle('width', '100%'); } -// /Partikule var self = this; @@ -287,74 +234,8 @@ var FileManager = new Class({ this.browserheader = new Element('div',{'class': 'filemanager-browserheader'}).inject(this.browsercontainer); this.browserheader.adopt(this.browserLoader); this.browserScroll = new Element('div', {'class': 'filemanager-browserscroll'}).inject(this.browsercontainer).addEvents({ - 'mouseover': (function(e) { - //this.diag.log('mouseover: ', e); - - // sync mouse and keyboard-driven browsing: the keyboard requires that we keep track of the hovered item, - // so we cannot simply leave it to a :hover CSS style. Instead, we find out which element is currently - // hovered: - var row = null; - if (e.target) - { - row = (e.target.hasClass('fi') ? e.target : e.target.getParent('span.fi')); - if (row) - { - row.addClass('hover'); - } - } - this.browser.getElements('span.fi.hover').each(function(span) { - // prevent screen flicker: only remove the class for /other/ nodes: - if (span != row) { - span.removeClass('hover'); - var rowicons = span.getElements('img.browser-icon'); - if (rowicons) - { - rowicons.each(function(icon) { - icon.set('tween', {duration: 'short'}).fade(0); - }); - } - } - }); - - if (row) - { - var icons = row.getElements('img.browser-icon'); - if (icons) - { - icons.each(function(icon) { - if (e.target == icon) - { - icon.set('tween', {duration: 'short'}).fade(1); - } - else - { - icon.set('tween', {duration: 'short'}).fade(0.5); - } - }); - } - } - }).bind(this), - - /* 'mouseout' */ - 'mouseleave': (function(e) { - //this.diag.log('mouseout: ', e); - - // only bother us when the mouse cursor has just left the browser area; anything inside there is handled - // by the recurring 'mouseover' event above... - // - // - do NOT remove the 'hover' marker from the row; it will be used by the keyboard! - // - DO fade out the action icons, though! - this.browser.getElements('span.fi.hover').each(function(span) { - var rowicons = span.getElements('img.browser-icon'); - if (rowicons) - { - rowicons.each(function(icon) { - icon.set('tween', {duration: 'short'}).fade(0); - }); - } - }); - - }).bind(this) + 'mouseover': this.mouseOver4browserScroll.bind(this), + 'mouseleave': this.mouseLeave4browserScroll.bind(this) }); this.browserMenu_thumb = new Element('a',{ 'id':'toggle_side_boxes', @@ -372,32 +253,26 @@ var FileManager = new Class({ click: this.toggleList.bind(this) }); -// Partikule : Thumbs list in preview panel + // Add a scroller to scroll the browser list when dragging a file + this.scroller = new Scroller(this.browserScroll, { + onChange: function(x, y) + { + // restrict scrolling to Y direction only! + var scroll = this.element.getScroll(); + self.diag.log('scroller.onChange: ', x, y, scroll); + this.element.scrollTo(scroll.x, y); + } + }); + + // Thumbs list in preview panel this.browserMenu_thumbList = new Element('a',{ 'id': 'show_dir_thumb_gallery', 'title': this.language.show_dir_thumb_gallery }).addEvent('click', function() { - // do NOT change the jsGET history carrying our browsing so far; the fact we want to view the dirtree should - // *NOT* blow away the recall in which directory we are (and what item is currently selected): - - //if (typeof jsGET !== 'undefined') - // jsGET.clear(); - - // no need to request the dirscan again: after all, we only wish to render another view of the same directory. - // (This means, however, that we MAY requesting any deferred thumbnails) - - //self.load(self.options.directory, true); - //return self.deselect(); // nothing to return on a click event, anyway. And do NOT loose the selection! - - // the code you need here is identical to clicking on the current directory in the top path bar: - - // show the 'directory' info in the detail pane again (this is a way to get back from previewing single files to previewing the directory as a gallery) this.diag.log('show_dir_Thumb_gallery button click: current directory!', this.CurrentDir, ', startdir: ', this.options.directory); this.fillInfo(); }.bind(this)); -// /Partikule - this.browser_dragndrop_info = new Element('a',{ 'id':'drag_n_drop', @@ -432,9 +307,8 @@ var FileManager = new Class({ }); this.browser_paging.adopt([this.browser_paging_first, this.browser_paging_prev, this.browser_paging_info, this.browser_paging_next, this.browser_paging_last]); -// Partikule : Added the browserMenu_thumbList to the browserheader + // Added the browserMenu_thumbList to the browserheader this.browserheader.adopt([this.browserMenu_thumbList, this.browserMenu_thumb, this.browserMenu_list, this.browser_dragndrop_info, this.browser_paging]); -// /Partikule this.browser = new Element('ul', {'class': 'filemanager-browser'}).inject(this.browserScroll); @@ -463,55 +337,32 @@ var FileManager = new Class({ // use some CSS to reorganise it a bit in the custom event handler. So we create "filemanager-preview-area" which // will contain the h2 for the preview and also the preview content returned from // Backend/FileManager.php - this.preview_area = new Element('div', {'class': 'filemanager-preview-area', + this.preview_area = new Element('div', { + 'class': 'filemanager-preview-area', styles: { opacity: 0 } }); -// Partikule. Removed new Element('h2', {'class': 'filemanager-headline' : -// 1. To gain more vertical space for preview -// 2. Because the user knows this is info about the file this.preview_area.adopt([ - //new Element('h2', {'class': 'filemanager-headline', text: this.language.more}), this.preview ]); -// Partikule. -// 1. To gain more vertical space for preview -// 2. Because the user knows this is info about the file -// 3. Less is more :-) this.info.adopt([this.info_head, this.preview_area]).inject(this.filemanager); -// /Partikule - -// Partikule -// Add of the thumbnail list in the preview panel - -// We fill this one while we render the directory tree view to ensure that the deferred thumbnail loading system -// (using 'detail / mode=direct' requests to obtain the actual thumbnail paths) doesn't become a seriously complex -// mess. -// This way, any updates coming from the server are automatically edited into this list; whether it is shown or -// not depends on the decisions in fillInfo() -// -// Usage: -// - One doubleclick on one thumb in this list will select the file : quicker select -// - One click displays the preview, but with the file in bigger format : less clicks to see the picture wider. // Thumbs list container (in preview panel) this.dir_filelist = new Element('div', {'class': 'filemanager-filelist'}); -// /Partikule - if (!this.options.hideClose) { this.closeIcon = new Element('a', { 'class': 'filemanager-close', opacity: 0.5, title: this.language.close, events: {click: this.hide.bind(this)} - }).inject(this.filemanager).addEvent('mouseover',function() { + }).inject(this.filemanager).addEvent('mouseenter',function() { this.fade(1); - }).addEvent('mouseout',function() { + }).addEvent('mouseleave',function() { this.fade(0.5); }); } @@ -535,17 +386,51 @@ var FileManager = new Class({ this.tips.attach(this.closeIcon); } - this.imageadd = new Asset.image(this.assetBasePath + 'Images/add.png', { - 'class': 'browser-add', + this.imagedragstate = Asset.image(this.assetsUrl + 'Images/transparent.gif', { + 'class': 'browser-dragstate', styles: { 'z-index': this.options.zIndex + 1600 } - }).set('opacity', 0).set('tween', {duration: 'short'}).inject(this.container); + }).inject(this.container); + this.imagedragstate.changeState = function(new_state) + { + /* 'this' points at this.imagedragstate in here! */ + if (new_state !== this.current_state) + { + switch (new_state) + { + default: + this.setStyles({ + 'background-position': '0px 0px', + 'display': 'none' + }); + break; + + case 1: // move + this.setStyles({ + 'background-position': '0px -16px', + 'display': 'block' + }); + break; + + case 2: // copy + this.setStyles({ + 'background-position': '0px -32px', + 'display': 'block' + }); + break; -// Partikule : Moved a little bit more on the bottom... -// this.container.inject(document.body); -// /Partikule + case 3: // cannot drop here... + this.setStyles({ + 'background-position': '0px -48px', + 'display': 'block' + }); + break; + } + this.current_state = new_state; + } + }; if (!this.options.hideOverlay) { this.overlay = new Overlay(Object.append((this.options.hideOnClick ? { @@ -564,15 +449,15 @@ var FileManager = new Class({ this.bound = { keydown: (function(e) { - // at least FF on Win will trigger this function multiple times when keys are depressed for a long time. Hence time consuming actions are don in 'keyup' whenever possible. - + // at least FF on Win will trigger this function multiple times when keys are depressed for a long time. + // Hence time consuming actions are don in 'keyup' whenever possible. this.diag.log('keydown: key press: ', e); if (e.control || e.meta) { if (this.drag_is_active && !this.ctrl_key_pressed) { - // only init the fade when actually switching CONTROL key states! - this.imageadd.fade(1); + // only init the change when actually switching CONTROL key states! + this.imagedragstate.changeState(2); } this.ctrl_key_pressed = true; } @@ -585,8 +470,8 @@ var FileManager = new Class({ { if (/* this.drag_is_active && */ this.ctrl_key_pressed) { - // only init the fade when actually switching CONTROL key states! - this.imageadd.fade(0); + // only init the change when actually switching CONTROL key states! + this.imagedragstate.changeState(1); } this.ctrl_key_pressed = false; } @@ -601,8 +486,10 @@ var FileManager = new Class({ break; case 'esc': - e.stop(); - this.hide(); + if (this.standalone == true) { + e.stop(); + this.hide(); + } break; } } @@ -620,7 +507,7 @@ var FileManager = new Class({ case 'home': case 'end': case 'enter': - case 'delete': +// case 'delete': e.preventDefault(); this.browserSelection(e.key); break; @@ -634,7 +521,6 @@ var FileManager = new Class({ }).bind(this) }; -// Partikule if (this.options.standalone) { this.container.inject(document.body); @@ -644,8 +530,6 @@ var FileManager = new Class({ } else { -// Partikule : Removed the autostart bacause of the standalone mode. -// Certainly a better way to do that... this.options.hideOverlay = true; } return this; @@ -679,20 +563,97 @@ var FileManager = new Class({ return (j.dirs.length + j.files.length <= pagesize * 4); }, + mouseOver4browserScroll: function(e) { + this.diag.log('browserScroll.mouseover: ', e); + + // see comment at the drag&drop section further below about the 'mouseleave' / 'mouseover' trouble due to the dragged element: + // here we make sure we don't 'track' the element hover while a drag&drop is in progress: + if (this.drag_is_active) { + return; + } + + // sync mouse and keyboard-driven browsing: the keyboard requires that we keep track of the hovered item, + // so we cannot simply leave it to a :hover CSS style. Instead, we find out which element is currently + // hovered: + var row = null; + if (e.target) + { + row = (e.target.hasClass('fi') ? e.target : e.target.getParent('span.fi')); + if (row) + { + row.addClass('hover'); + } + } + this.browser.getElements('span.fi.hover').each(function(span) { + // prevent screen flicker: only remove the class for /other/ nodes: + if (span != row) { + span.removeClass('hover'); + var rowicons = span.getElements('img.browser-icon'); + if (rowicons) + { + rowicons.each(function(icon) { + icon.hide(); + }); + } + } + }); + + if (row) + { + var icons = row.getElements('img.browser-icon'); + if (icons) + { + icons.each(function(icon) { + if (e.target == icon) + { + icon.show(); + } + else + { + icon.show(); + } + }); + } + } + }, + + mouseLeave4browserScroll: function(e) { + this.diag.log('browserScroll.mouseleave: ', e); + + if (this.drag_is_active) { + return; + } + + // - do NOT remove the 'hover' marker from the row; it will be used by the keyboard! + // - DO fade out the action icons, though! + this.browser.getElements('span.fi.hover').each(function(span) { + var rowicons = span.getElements('img.browser-icon'); + if (rowicons) + { + rowicons.each(function(icon) { + icon.hide(); + }); + } + }); + }, + /* * default method to produce a suitable request URL/POST; as several frameworks out there employ url rewriting, one way or another, * we now allow users to provide their own construction method to replace this one: simply provide your own method in - * options.mkServerRequestURL + * options.mkServerRequestURL * Like this one, it MUST return an object, containing two properties: * * url: (string) contains the URL sent to the server for the given event/request (which is always transmitted as a POST request) * data: (assoc. array): extra parameters added to this POST. (Mainly there in case a framework wants to have the 'event' parameter * transmitted as a POST data element, rather than having it included in the request URL itself in some form. + * + * WARNING: 'this' in here is actually **NOT** pointing at the FM instance; use 'fm_obj' for that! + * + * In fact, 'this' points at the 'fm_obj.options' object, but consider that an 'undocumented feature' + * as it may change in the future without notice! */ mkServerRequestURL: function(fm_obj, request_code, post_data) { - // WARNING: 'this' in here is actually **NOT** pointing at the FM instance; use 'fm_obj' for that! (In fact, 'this' points at the 'options' object, but consider that an 'undocumented feature' as it may change in the future without notice!) - return { url: fm_obj.options.url + (fm_obj.options.url.indexOf('?') == -1 ? '?' : '&') + Object.toQueryString({ event: request_code @@ -703,7 +664,6 @@ var FileManager = new Class({ fitSizes: function() { -// Partikule : Add the standalone check if (this.options.standalone) { this.filemanager.center(this.offsets); @@ -717,7 +677,6 @@ var FileManager = new Class({ this.filemanager.setStyle('height', parentSize.y); } } -// /Partikule var containerSize = this.filemanager.getSize(); var headerSize = this.browserheader.getSize(); @@ -739,17 +698,13 @@ var FileManager = new Class({ return encodeURI(s.toString()).replace(/\+/g, '%2B').replace(/#/g, '%23'); }, unescapeRFC3986: function(s) { - return decodeURI(s.toString()); + return decodeURI(s.toString().replace(/%23/g, '#').replace(/%2B/g, '+')); }, // -> catch a click on an element in the file/folder browser - relayClick: function(e, el) { + relayClickOnItemInLeftPanel: function(e, el) { if (e) e.stop(); - // ignore mouse clicks while drag&drop + resulting copy/move is pending. - // - // Theoretically only the first click originates from the same mouse event as the 'drop' event, so we - // COULD reset 'drop_pending' after processing that one. if (this.drop_pending != 0) { this.drop_pending = 0; @@ -759,7 +714,7 @@ var FileManager = new Class({ this.storeHistory = true; var file = el.retrieve('file'); - this.diag.log('on relayClick file = ', file, ', current directory: ', this.CurrentDir, '@ el = ', el); + this.diag.log('on relayClickOnItemInLeftPanel file = ', file, ', current directory: ', this.CurrentDir, '@ el = ', el); if (el.retrieve('edit')) { el.eliminate('edit'); return; @@ -773,34 +728,29 @@ var FileManager = new Class({ return; } - // when we're right smack in the middle of a drag&drop, which may end up as a MOVE, do NOT send a 'detail' request - // alongside (through fillInfo) as that may lock the file being moved, server-side. - // It's good enough to disable the detail view, if we want/need to. - // - // Note that this.drop_pending tracks the state of the drag&drop state machine -- more functions may check this one! if (this.Current) { this.Current.removeClass('selected'); } - // ONLY do this when we're doing a COPY or on a failed attempt... + // CORRECTION: as even a failed 'drop' action will have moved the cursor, we can't keep this one selected right now: this.Current = el.addClass('selected'); - // We need to have Current assigned before fillInfo because fillInfo adds to it + this.fillInfo(file); this.switchButton4Current(); } }, -// Partikule - /** * Catches both single and double click on thumb list icon in the directory preview thumb/gallery list */ - relayDblClick: function(e, self, dg_el, file, clicks) + relaySingleOrDoubleClick: function(e, self, dg_el, file, clicks) { - if (e) e.stop(); + // IE7 / IE8 event problem + if( ! Browser.ie) + if (e) e.stop(); - this.diag.log('on relayDblClick file = ', file, ', current dir: ', this.CurrentDir, ', # clicks: ', clicks); + this.diag.log('on relaySingleOrDoubleClick file = ', file, ', current dir: ', this.CurrentDir, ', # clicks: ', clicks); this.tips.hide(); @@ -812,9 +762,6 @@ var FileManager = new Class({ } this.Current = el_ref.addClass('selected'); - file = el_ref.retrieve('file'); - - this.CurrentFile = file; // now make sure we can see the selected item in the left pane: scroll there: this.browserSelection('none'); @@ -827,12 +774,10 @@ var FileManager = new Class({ else { // the single-click action is to simulate a click on the corresponding line in the directory view (left pane) - this.relayClick(e, el_ref); + this.relayClickOnItemInLeftPanel(e, el_ref); } }, -// /Partikule - toggleList: function(e) { if (e) e.stop(); @@ -863,11 +808,13 @@ var FileManager = new Class({ * * Is fired for two reasons: * - * 1) the user clicked on a file or directory to view and that change was also pushed to the history through one or more jsGET.set() calls. + * 1) the user clicked on a file or directory to view and that change was also pushed to the history + * through one or more jsGET.set() calls. * (In this case, we've already done what needed doing, so we should not redo that effort in here!) * * 2) the user went back in browser history or manually edited the URI hash section. - * (This is an 'change from the outside' and exactly what this listener is for. This time around, we should follow up on those changes!) + * (This is an 'change from the outside' and exactly what this listener is for. + * This time around, we should follow up on those changes!) */ hashHistory: function(vars) { @@ -919,7 +866,6 @@ var FileManager = new Class({ return; } this.fmShown = true; - this.onShow = false; if (typeof preselect === 'undefined') preselect = null; if (typeof loaddir === 'undefined') loaddir = null; @@ -942,17 +888,11 @@ var FileManager = new Class({ loaddir = this.options.directory; } } - if (preselect) - { - this.diag.log('on show: set onShow on PRESELECT: ', preselect); - this.onShow = true; - } // get and set history if (typeof jsGET !== 'undefined') { if (jsGET.get('fmFile')) { this.diag.log('on show: set onShow on fmFile: ', jsGET.get('fmFile')); - this.onShow = true; } if (jsGET.get('fmListType') != null) { $$('.filemanager-browserheader a.listType').set('opacity',0.5); @@ -975,9 +915,11 @@ var FileManager = new Class({ } this.show_our_info_sections(false); - this.container.fade(0).setStyles({ - display: 'block' - }); + this.container.hide(); + + // this.container.fade(0).setStyles({ + // display: 'block' + // }); window.addEvents({ 'scroll': this.bound.scroll, @@ -991,18 +933,17 @@ var FileManager = new Class({ document.addEvent('keydown', this.bound.keyboardInput); else document.addEvent('keypress', this.bound.keyboardInput); - this.container.fade(1); + this.container.show(); this.fitSizes(); this.fireEvent('show', [this]); this.fireHooks('show'); -// Partikule : If not standalone, returns the HTML content + // If not standalone, returns the HTML content if (!this.options.standalone) { return this.container; } -// /Partikule }, hide: function(e) { @@ -1044,6 +985,8 @@ var FileManager = new Class({ show_our_info_sections: function(state) { if (!state) { + // this.info_head.hide(); + // this.preview_area.hide(); this.info_head.fade(0).get('tween').chain(function() { this.element.setStyle('display', 'none'); }); @@ -1053,6 +996,8 @@ var FileManager = new Class({ } else { + // this.info_head.show() + // this.preview_area.show(); this.info_head.setStyle('display', 'block').fade(1); this.preview_area.setStyle('display', 'block').fade(1); } @@ -1066,18 +1011,18 @@ var FileManager = new Class({ var file = this.Current.retrieve('file'); this.fireEvent('complete', [ - this.escapeRFC3986(this.normalize('/' + this.root + file.path)), // the absolute URL for the selected file, rawURLencoded - file, // the file specs: .name, .path, .size, .date, .mime, .icon, .icon48, .thumb48, .thumb250 + // the absolute URL for the selected file, rawURLencoded + (this.options.deliverPathAsLegalURL ? file.path : this.escapeRFC3986(this.normalize('/' + this.root + file.path))), + // the file specs: .name, .path, .size, .date, .mime, .icon, .icon48, .thumb48, .thumb250 + file, this ]); -// Partikule -// Why Hide ? For some usage, it can be useful to keep it open (more than 3 files and it will be really annoying to re-open the FM for each file select) + // Only hide if hideOnSelect is true if (this.options.hideOnSelect) { this.hide(); } -// /Partikule }, download_on_click: function(e) { @@ -1091,6 +1036,9 @@ var FileManager = new Class({ }, download: function(file) { + var self = this; + var dummyframe_active = false; + // the chained display:none code inside the Tips class doesn't fire when the 'Save As' dialog box appears right away (happens in FF3.6.15 at least): if (this.tips.tip) { this.tips.tip.setStyle('display', 'none'); @@ -1110,7 +1058,50 @@ var FileManager = new Class({ this.downloadForm = null; } - this.downloadIframe = (new IFrame()).set({src: 'about:blank', name: '_downloadIframe'}).setStyles({display:'none'}); + this.downloadIframe = new IFrame({ + src: 'about:blank', + name: '_downloadIframe', + styles: { + display: 'none' + }, + events: { + load: function() + { + var iframe = this; + self.diag.log('download response: ', this, ', iframe: ', self.downloadIframe, ', ready: ', (1 * dummyframe_active)); + + // make sure we don't act on premature firing of the event in MSIE / Safari browsers: + if (!dummyframe_active) + return; + + var response = null; + Function.attempt(function() { + response = iframe.contentDocument.documentElement.textContent; + }, + function() { + response = iframe.contentWindow.document.innerText; + }, + function() { + response = iframe.contentDocument.innerText; + }, + function() { + response = "{status: 0, error: \"Download: download assumed okay: can't find response.\"}"; + } + ); + + var j = JSON.decode(response); + + if (j && !j.status) + { + self.showError('' + j.error); + } + else if (!j) + { +// self.showError('bugger! No or faulty JSON response! ' + response); + } + } + } + }); this.menu.adopt(this.downloadIframe); this.downloadForm = new Element('form', {target: '_downloadIframe', method: 'post', enctype: 'multipart/form-data'}); @@ -1131,6 +1122,8 @@ var FileManager = new Class({ this.downloadForm.adopt((new Element('input')).set({type: 'hidden', name: k, value: v})); }.bind(this)); + dummyframe_active = true; + return this.downloadForm.submit(); }, @@ -1138,7 +1131,7 @@ var FileManager = new Class({ e.stop(); var input = new Element('input', {'class': 'createDirectory'}); var click_ok_f = (function(e) { - this.diag.log('create on click: KEYBOARD handler: key press: ', e); +// this.diag.log('create on click: KEYBOARD handler: key press: ', e); if (e.key === 'enter') { e.stopPropagation(); @@ -1322,6 +1315,19 @@ var FileManager = new Class({ this.browserLoader.fade(1); + if ((typeof jsGET !== 'undefined') && this.storeHistory) + { + if (file.mime !== 'text/directory') + { + // TODO: really, a full check should also check whether the fmPath equals the this.CurrentDir.path + if (file.name === jsGET.get('fmFile')) + { + // this will ensure the subsequent fill() action will revert the detail view to the directory details. + jsGET.remove(['fmFile']); + } + } + } + var tx_cfg = this.options.mkServerRequestURL(this, 'destroy', { file: file.name, directory: this.CurrentDir.path, @@ -1341,14 +1347,13 @@ var FileManager = new Class({ this.fireEvent('modify', [Object.clone(file), j, 'destroy', this]); // remove entry from cached JSON directory list and remove the item from the view. - // This is particularly important when working on a paginated directory and afterwards the pages are jumped back & forth: - // the next time around, this item should NOT appear in the list anymore! + // Also, this call will clear the detail pane view if the deleted file is shown over there. this.deselect(file.element); var rerendering_list = false; if (this.view_fill_json) { - this.delete_from_dircache(file); /* do NOT use j.name, as that one can be 'cleaned up' as part of the 'move' operation! */ + this.delete_from_dircache(file); // minor caveat: when we paginate the directory list, then browsing to the next page will skip one item (which would // have been the first on the next page). The brute-force fix for this is to force a re-render of the page when in @@ -1507,6 +1512,11 @@ var FileManager = new Class({ if (csel != null) csel.removeClass('selected'); } + else if (direction === 'none') + { + // select the current item (don't look for selected / hover classes to find out who is selected) + current = this.Current; + } else if (this.browser.getElement('span.fi.hover') == null && this.browser.getElement('span.fi.selected') == null) { // none is selected: select first item (folder/file) @@ -1528,7 +1538,7 @@ var FileManager = new Class({ span.removeClass('hover'); }); - var stepsize = 1, next, currentFile; + var stepsize = 1, next, file; switch (direction) { // go down @@ -1629,13 +1639,13 @@ var FileManager = new Class({ csel.removeClass('selected'); current.addClass('selected'); - currentFile = current.retrieve('file'); - this.diag.log('on key ENTER file = ', currentFile); - if (currentFile.mime === 'text/directory') { - this.load(currentFile.dir + currentFile.name /*.replace(this.root,'')*/); + file = current.retrieve('file'); + this.diag.log('on key ENTER file = ', file); + if (file.mime === 'text/directory') { + this.load(file.path); } else { - this.fillInfo(currentFile); + this.fillInfo(file); } break; @@ -1658,9 +1668,9 @@ var FileManager = new Class({ next.addClass('hover'); } - currentFile = current.retrieve('file'); - this.diag.log('on key DELETE file = ', currentFile); - this.destroy(currentFile); + file = current.retrieve('file'); + this.diag.log('on key DELETE file = ', file); + this.destroy(file); current = next; this.Current = current; @@ -1706,13 +1716,14 @@ var FileManager = new Class({ var icons = el.getElements('img.browser-icon'); if (icons) { icons.each(function(icon) { - icon.fade(0); + icon.hide(); + //icon.fade(0); }); } this.diag.log('DISABLE keyboard up/down on revert'); this.drag_is_active = false; - this.imageadd.fade(0); + this.imagedragstate.changeState(0); }, // clicked 'first' button in the paged list/thumb view: @@ -1815,26 +1826,18 @@ var FileManager = new Class({ span.removeClass('hover'); }); - this.diag.log('# fill: file = ', j, ', onShow: ', this.onShow, ', mgr: ', this); + this.diag.log('# fill: JSON = ', j, ', mgr: ', this); this.root = j.root; this.CurrentDir = j.this_dir; - if (!this.onShow) { - this.diag.log('fill internal: fillInfo: file = ', j, j.this_dir); - this.fillInfo(j.this_dir); // XXXX - } this.browser.empty(); -// Partikule -// Add of the thumbnail list in the preview panel: blow away any pre-existing list now, as we'll generate a new one now: - + // Adding the thumbnail list in the preview panel: blow away any pre-existing list now, as we'll generate a new one now: this.dir_filelist.empty(); -// /Partikule - // set history - if (typeof jsGET !== 'undefined' && this.storeHistory && j.this_dir.mime === 'text/directory') + if (typeof jsGET !== 'undefined' && this.storeHistory) { - jsGET.set({'fmPath': j.path}); + jsGET.set({'fmPath': this.CurrentDir.path}); } var current_path = this.normalize(this.root + this.CurrentDir.path); @@ -1847,15 +1850,16 @@ var FileManager = new Class({ this.showError('' + j.error); return false; } - var rootPath = j.root.slice(0,-1).split('/'); - rootPath.pop(); + var rootPath = '/' + j.root; + var rootParent = this.dirname(rootPath); + var rplen = rootParent.length; current_path.split('/').each((function(folderName) { if (!folderName) return; pre.push(folderName); - var path = ('/'+pre.join('/')+'/').replace(j.root,''); - this.diag.log('on fill: display directory path chuncks: root = ', j.root, ', path: ' , path, ', folder: ', folderName); - if (rootPath.contains(folderName)) { + var path = ('/'+pre.join('/')+'/'); + this.diag.log('on fill: display directory path chunks: JSON root = ', j.root, ', path: ' , path, ', folder: ', folderName, ', root: ', rootPath, ', parent: ', rootParent); + if (path.length <= rplen) { // add non-clickable path text.push(new Element('span', {'class': 'icon', text: folderName})); } else { @@ -1865,8 +1869,9 @@ var FileManager = new Class({ href: '#', text: folderName }).addEvent('click', (function(e) { - this.diag.log('path section - click event: ', e, path); e.stop(); + path = path.replace(j.root,''); + this.diag.log('## path section - click event: ', e, ', path: ', path); this.load(path); }).bind(this)) ); @@ -1895,16 +1900,6 @@ var FileManager = new Class({ * For very large directories, where the number of directories in there and/or the number of files is HUGE (> 200), * we DISABLE drag&drop functionality. * - * Yes, we could have opted for the alternative, which is splitting up the .makeDraggable() activity in multiple - * setTimeout(callback, 0) initiated chunks in order to spare the user the hassle of a 'slow script' dialog, - * but in reality drag&drop is ludicrous in such an environment; currently we do not (yet) support autoscrolling - * the list to enable drag&dropping it to elements further away that the current viewport can hold at the same time, - * but drag&drop in a 500+ image carrying directory is resulting in a significant load of the browser anyway; - * alternative means to move/copy files should be provided in such cases instead. - * - * Hence we run through the list here and abort / limit the drag&drop assignment process when the hardcoded number of - * directories or files have been reached (support_DnD_for_this_dir). - * * TODO: make these numbers 'auto adaptive' based on timing measurements: how long does it take to initialize * a view on YOUR machine? --> adjust limits accordingly. */ @@ -1973,6 +1968,9 @@ var FileManager = new Class({ // remember pagination position history this.store_view_fill_startindex(startindex); + // reset the fillInfo fire marker: + this.fillInfoOnFillFired = false; + this.view_fill_timer = this.fill_chunkwise_1.delay(1, this, [startindex, endindex, endindex - startindex, pagesize, support_DnD_for_this_dir, starttime, els, kbd_dir, preselect]); return true; @@ -1984,7 +1982,7 @@ var FileManager = new Class({ new Element('span', { 'class': this.listType, 'styles': { - 'background-image': 'url(' + (thumbnail_url ? thumbnail_url : this.assetBasePath + 'Images/loader.gif') + ')' + 'background-image': 'url(' + (thumbnail_url ? thumbnail_url : this.assetsUrl + 'Images/loader.gif') + ')' } }).addClass('fm-thumb-bg'), new Element('span', {'class': 'filemanager-filename', text: file.name, title: file.name}) @@ -1993,14 +1991,6 @@ var FileManager = new Class({ dir_gallery_item_maker: function(thumbnail_url, file) { - // as we want the thumbnails line up reasonably well in a grid and have no huge white areas thanks to long filenames, - // we strip the filename on display. When the user wishes to know the full filename, he can hover over the image, anyway. - var fname = file.name; - if (fname.length > 6) - { - fname = fname.substr(0, 6) + '...'; - } - var el = new Element('div', { 'class': 'fi', 'title': file.name @@ -2008,18 +1998,91 @@ var FileManager = new Class({ new Element('div', { 'class': 'dir-gal-thumb-bg', 'styles': { - 'background-image': 'url(' + (thumbnail_url ? thumbnail_url : this.assetBasePath + 'Images/loader.gif') + ')' + 'width': this.options.thumbSize4DirGallery + 'px', + 'height': this.options.thumbSize4DirGallery + 'px', + 'background-image': 'url(' + (thumbnail_url ? thumbnail_url : this.assetsUrl + 'Images/loader.gif') + ')' } }), new Element('div', { 'class': 'name', - 'text': fname + 'styles': { + 'width': this.options.thumbSize4DirGallery + 'px' + }, + 'text': file.name }) ); this.tips.attach(el); return el; }, + dir_gallery_set_actual_img: function(file, dg_el) + { + // calculate which thumb to use and how to center it: + var img_url, iw, ih, ds, mt, mb, ml, mr, ratio; + + ds = this.options.thumbSize4DirGallery; + if (ds > 48) + { + img_url = file.thumb250; + iw = file.thumb250_width; + ih = file.thumb250_height; + } + else + { + img_url = file.thumb48; + iw = file.thumb48_width; + ih = file.thumb48_height; + } + + // 'zoom' image to fit area: + if (iw > ds) + { + var redux = ds / iw; + iw *= redux; + ih *= redux; + } + if (ih > ds) + { + var redux = ds / ih; + iw *= redux; + ih *= redux; + } + iw = Math.round(iw); + ih = Math.round(ih); + ml = Math.round((ds - iw) / 2); + mr = ds - ml - iw; + mt = Math.round((ds - ih) / 2); + mb = ds - mt - ih; + + var self = this; + + Asset.image(img_url, { + styles: { + width: iw, + height: ih, + 'margin-left': ml, + 'margin-top': mt, + 'margin-right': mr, + 'margin-bottom': mb + }, + onLoad: function() { + var img_el = this; + var img_div = dg_el.getElement('div.dir-gal-thumb-bg').setStyle('background-image', ''); + img_div.adopt(img_el); + }, + onError: function() { + self.diag.log('dirgallery image asset: error!'); + var iconpath = self.URLpath4assets + 'Images/Icons/Large/default-error.png'; + dg_el.getElement('div.dir-gal-thumb-bg').setStyle('background-image', 'url(' + iconpath + ')'); + }, + onAbort: function() { + self.diag.log('dirgallery image asset: ABORT!'); + var iconpath = self.URLpath4assets + 'Images/Icons/Large/default-error.png'; + dg_el.getElement('div.dir-gal-thumb-bg').setStyle('background-image', 'url(' + iconpath + ')'); + } + }); + }, + /* * The old one-function-does-all fill() would take an awful long time when processing large directories. This function * contains the most costly code chunk of the old fill() and has adjusted the looping through the j.dirs[] and j.files[] lists @@ -2040,15 +2103,6 @@ var FileManager = new Class({ var duration = new Date().getTime() - starttime; //this.diag.log(' + time duration @ fill_chunkwise_1(', startindex, '): ', duration); - /* - * Note that the '< j.dirs.length' / '< j.files.length' checks MUST be kept around: one of the fastest ways to abort/cancel - * the render is emptying the dirs[] + files[] array, as that would abort the loop on the '< j.dirs.length' / '< j.files.length' - * condition. - * - * This, together with killing our delay-timer, is done when anyone calls reset_view_fill_store() to - * abort this render pronto. - */ - // first loop: only render directories, when the indexes fit the range: 0 .. j.dirs.length-1 // Assume several directory aspects, such as no thumbnail hassle (it's one of two icons anyway, really!) var el, editButtons; @@ -2089,7 +2143,7 @@ var FileManager = new Class({ el.addEvent('click', (function(e) { self.diag.log('is_dir:CLICK: ', e); var node = this; - self.relayClick.apply(self, [e, node]); + self.relayClickOnItemInLeftPanel.apply(self, [e, node]); }).bind(el)); editButtons = []; @@ -2102,8 +2156,7 @@ var FileManager = new Class({ } editButtons.each(function(v) { - //icons.push( - new Asset.image(this.assetBasePath + 'Images/' + v + '.png', {title: this.language[v]}).addClass('browser-icon').set('opacity', 0).addEvent('mouseup', (function(e, target) { + Asset.image(this.assetsUrl + 'Images/' + v + '.png', {title: this.language[v]}).addClass('browser-icon').hide().addEvent('mouseup', (function(e, target) { // this = el, self = FM instance e.preventDefault(); this.store('edit', true); @@ -2112,11 +2165,10 @@ var FileManager = new Class({ self.tips.hide(); self[v](file); }).bind(el)).inject(el,'top'); - //); }, this); els[1].push(el); - //if (file.name === '..') el.fade(0.7); + el.inject(new Element('li',{'class':this.listType}).inject(this.browser)).store('parent', el.getParent()); //icons = $$(icons.map((function(icon) { // this.showFunctions(icon,icon,0.5,1); @@ -2166,34 +2218,31 @@ var FileManager = new Class({ // As we now have two views into the directory, we have to fetch the thumbnails, even when we're in 'list' view: the direcory gallery will need them! // Besides, fetching the thumbs and all right after we render the directory also makes these thumbs + metadata available for drag&drop gallery and // 'select mode', so they don't always have to ask for the meta data when it is required there and then. - if (file.thumb48 || /* this.listType !== 'thumb' || */ !file.thumbs_deferred) + if (file.thumb48 || !file.thumbs_deferred) { // This is just a raw image el = this.list_row_maker((this.listType === 'thumb' ? (file.thumb48 ? file.thumb48 : file.icon48) : file.icon), file); - dg_el = this.dir_gallery_item_maker((file.thumb48 ? file.thumb48 : file.icon48), file); + dg_el = this.dir_gallery_item_maker(file.icon48, file); + if (file.thumb48) + { + this.dir_gallery_set_actual_img(file, dg_el); + } } else // thumbs_deferred... { - // We must AJAX POST our propagateData, so we need to do the post and take the url to the - // thumbnail from the post results. - // - // The alternative here, taking only 1 round trip instead of 2, would have been to FORM POST - // to a tiny iframe, which is suitably sized to contain the generated thumbnail and the POST - // actually returning the binary image data, thus the iframe contents becoming the thumbnail image. - // update this one alongside the 'el': dg_el = this.dir_gallery_item_maker(file.icon48, file); el = (function(file, dg_el) { // Closure - var iconpath = this.assetBasePath + 'Images/Icons/' + (this.listType === 'thumb' ? 'Large/' : '') + 'default-error.png'; + var iconpath = this.assetsUrl + 'Images/Icons/' + (this.listType === 'thumb' ? 'Large/' : '') + 'default-error.png'; var list_row = this.list_row_maker((this.listType === 'thumb' ? file.icon48 : file.icon), file); var tx_cfg = this.options.mkServerRequestURL(this, 'detail', { directory: this.dirname(file.path), file: file.name, filter: this.options.filter, - mode: 'direct' + mode: 'direct' + this.options.detailInfoMode }); var req = new FileManager.Request({ @@ -2210,7 +2259,10 @@ var FileManager = new Class({ else { list_row.getElement('span.fm-thumb-bg').setStyle('background-image', 'url(' + (this.listType === 'thumb' ? j.thumb48 : j.icon) + ')'); - dg_el.getElement('div.dir-gal-thumb-bg').setStyle('background-image', 'url(' + j.thumb48 + ')'); + if (j.thumb48) + { + this.dir_gallery_set_actual_img(j, dg_el); + } } // update the stored json for this file as well: @@ -2242,62 +2294,6 @@ var FileManager = new Class({ }).bind(this)(file, dg_el); } - /* - * WARNING: for some (to me) incomprehensible reason the old code which bound the event handlers to 'this==self' and which used the 'el' variable - * available here, does NOT WORK ANY MORE - tested in FF3.6. Turns out 'el' is pointing anywhere but where you want it by the time - * the event handler is executed. - * - * The 'solution' which I found was to rely on the 'self' reference instead and bind to 'el'. If the one wouldn't work, the other shouldn't, - * but there you have it: this way around it works. FF3.6.14 :-( - * - * EDIT 2011/03/16: the problem started as soon as the old Array.each(function(...) {...}) by the chunked code which uses a for loop: - * - * http://jibbering.com/faq/notes/closures/ - * - * as it says there: - * - * A closure is formed when one of those inner functions is made accessible outside of the function in which it was - * contained, so that it may be executed after the outer function has returned. At which point it still has access to - * the local variables, parameters and inner function declarations of its outer function. Those local variables, - * parameter and function declarations (initially) >>>> have the values that they had when the outer function returned <<<< - * and may be interacted with by the inner function. - * - * The >>>> <<<< emphasis is mine: in the .each() code, each el was a separate individual, while due to the for loop, - * the last 'el' to exist at all is the one created during the last round of the loop in that chunk. Which explains the - * observed behaviour before the fix: the file names associated with the 'el' element object were always pointing - * at some item further down the list, not necessarily the very last one, but always these references were 'grouped': - * multiple rows would produce the same filename. - * - * EXTRA: 2011/04/09: why you don't want to add this event for any draggable item! - * - * It turns out that IE9 (IE6-8 untested as I write this) and Opera do NOT fire the 'click' event after the drag operation is - * 'cancel'led, while other browsers fire both (Chrome/Safari/FF3). - * For the latter ones, the event handler sequence after a simple click on a draggable item is: - * - Drag::onBeforeStart - * - Drag::onCancel - * - 'click' - * while a tiny amount of dragging produces this sequence instead: - * - Drag::onBeforeStart - * - Drag::onStart - * - Drag::onDrop - * - 'click' - * - * Meanwhile, Opera and IE9 do this: - * - Drag::onBeforeStart - * - Drag::onCancel - * - **NO** click event! - * while a tiny amount of dragging produces this sequence instead: - * - Drag::onBeforeStart - * - Drag::onStart - * - Drag::onDrop - * - **NO** click event! - * - * which explains why the old implementation did not simply register this 'click' event handler and had 'revert' fake the 'click' - * event instead. - * HOWEVER, the old way, using revert() (now called revert_drag_n_drop()) was WAY too happy to hit the 'click' event handler. In - * fact, the only spot where such 'manually firing' was desirable is when the drag operation is CANCELLED. And only there! - */ - // 2011/04/09: only register the 'click' event when the element is NOT a draggable: if (!support_DnD_for_this_dir) { @@ -2305,22 +2301,19 @@ var FileManager = new Class({ el.addEvent('click', (function(e) { self.diag.log('is_file:CLICK: ', e); var node = this; - self.relayClick.apply(self, [e, node]); + self.relayClickOnItemInLeftPanel.apply(self, [e, node]); }).bind(el)); } editButtons = []; - // download icon - if (this.options.download) { - if (this.options.download) editButtons.push('download'); - } - // rename, delete icon + // download, rename, delete icon + if (this.options.download) editButtons.push('download'); if (this.options.rename) editButtons.push('rename'); if (this.options.destroy) editButtons.push('destroy'); editButtons.each(function(v) { - new Asset.image(this.assetBasePath + 'Images/' + v + '.png', {title: this.language[v]}).addClass('browser-icon').set('opacity', 0).addEvent('mouseup', (function(e, target) { + Asset.image(this.assetsUrl + 'Images/' + v + '.png', {title: this.language[v]}).addClass('browser-icon').hide().addEvent('mouseup', (function(e, target) { // this = el, self = FM instance e.preventDefault(); this.store('edit', true); @@ -2335,8 +2328,8 @@ var FileManager = new Class({ el.inject(new Element('li',{'class':this.listType}).inject(this.browser)).store('parent', el.getParent()); // ->> LOAD the FILE/IMAGE from history when PAGE gets REFRESHED (only directly after refresh) - //this.diag.log('fill on PRESELECT (test): onShow = ', this.onShow, ', file = ', file, ', fmFile = ', fmFile, ', preselect = ', preselect); - if (this.onShow) + //this.diag.log('fill on PRESELECT (test): onShow = ', this.fillInfoOnFillFired, ', file = ', file, ', fmFile = ', fmFile, ', preselect = ', preselect); + if (!this.fillInfoOnFillFired) { if (preselect) { @@ -2348,6 +2341,7 @@ var FileManager = new Class({ file.element.addClass('selected'); this.diag.log('fill on PRESELECT: fillInfo: file = ', file); this.fillInfo(file); + this.fillInfoOnFillFired = true; } } else if (fmFile) @@ -2360,19 +2354,12 @@ var FileManager = new Class({ file.element.addClass('selected'); this.diag.log('fill: fillInfo: file = ', file); this.fillInfo(file); + this.fillInfoOnFillFired = true; } } - else - { - this.diag.log('fill: RESET onShow: file = ', file); - this.onShow = false; - } } -// Partikule -// Thumbs list - // use a closure to keep a reference to the current dg_el, otherwise dg_el, file, etc. will carry the values they got at the end of the loop! (function(dg_el, el, file) { @@ -2380,22 +2367,28 @@ var FileManager = new Class({ 'click': function(e) { clearTimeout(self.dir_gallery_click_timer); - self.dir_gallery_click_timer = self.relayDblClick.delay(700, self, [e, this, dg_el, file, 1]); + self.dir_gallery_click_timer = self.relaySingleOrDoubleClick.delay(700, self, [e, this, dg_el, file, 1]); }, 'dblclick': function(e) { - clearTimeout(this.dir_gallery_click_timer); - this.dir_gallery_click_timer = self.relayDblClick.delay(0, self, [e, this, dg_el, file, 2]); + clearTimeout(self.dir_gallery_click_timer); + self.dir_gallery_click_timer = self.relaySingleOrDoubleClick.delay(0, self, [e, this, dg_el, file, 2]); } }); dg_el.inject(this.dir_filelist); }).bind(this)(dg_el, el, file); - -// / Partikule } } + // when we get here, we have rendered all files in the current page and we know whether we have fired off a fillInfo on a preselect/history-recalled file now, or not: + if (!this.fillInfoOnFillFired) + { + this.diag.log('fill internal: fillInfo: file = ', j, j.this_dir); + this.fillInfo(j.this_dir); + this.fillInfoOnFillFired = true; + } + // check how much we've consumed so far: duration = new Date().getTime() - starttime; //this.diag.log(' + time taken in array traversal = ', duration); @@ -2417,34 +2410,57 @@ var FileManager = new Class({ duration = new Date().getTime() - starttime; //this.diag.log(' + time taken in array traversal + revert = ', duration); - if (support_DnD_for_this_dir) { + if (support_DnD_for_this_dir) + { + var self = this; + // -> make draggable $$(els[0]).makeDraggable({ - droppables: $$(this.droppables.combine(els[1])), - //stopPropagation: true, - onDrag: (function(el, e) { - this.imageadd.setStyles({ - 'left': e.page.x + 25, - 'top': e.page.y + 25 + droppables: $$(els[1]), + + // We position the element relative to its original position; this ensures the drag always works with arbitrary container. + onDrag: (function(el, e) + { + var dpos = el.retrieve('delta_pos'); + if (this.browserScroll.contains(el)) this.diag.log('~~~ positions while dragging: ', dpos, e, 1 * this.browserScroll.contains(el)); + + el.setStyles({ + display: 'block', + left: e.page.x - dpos.x + 12, + top: e.page.y - dpos.y + 10 }); + + this.imagedragstate.setStyles({ + 'left': e.page.x - dpos.x - 12, + 'top': e.page.y - dpos.y + 2 + }); + + this.scroller.getCoords(e); + }).bind(this), onBeforeStart: (function(el) { - this.diag.log('draggable:onBeforeStart', el); - var position = el.getPosition(); - el.addClass('drag').setStyles({ - 'z-index': this.options.zIndex + 1500, - 'position': 'absolute', - 'width': el.getWidth() - el.getStyle('paddingLeft').toInt() - el.getStyle('paddingRight').toInt(), - 'left': position.x, - 'top': position.y - }).inject(this.container); + + var dpos = self.container.getPosition(); + + // fetch this Drag.Move instance: + var dragger = el.retrieve('dragger'); + var mouse_start = dragger.mouse.start; // contains the event.page.x/y values + + dpos.mouse_start = mouse_start; + el.store('delta_pos', dpos); + this.diag.log('~~~ positions before start: ', dpos, this.container.getPosition(), el.getPosition(), this.browsercontainer.getPosition(), 1 * this.browserScroll.contains(el)); + + // start the scroller; sensitive areas are top and bottom 20% of the total height: + this.scroller.options.area = this.browserScroll.getSize().y * 0.2; + this.scroller.start(); }).bind(this), // FIX: do not deselect item when aborting dragging _another_ item! onCancel: (function(el) { this.diag.log('draggable:onCancel', el); + this.scroller.stop(); this.revert_drag_n_drop(el); /* * Fixing the 'click' on FF+Opera (other browsers do get that event for any item which is made draggable): @@ -2455,33 +2471,49 @@ var FileManager = new Class({ * So we then manually fire the 'click' event. See also the comment near the 'click' event handler registration in fill_chunkwise_1() * about the different behaviour in different browsers. */ - this.relayClick(null, el); + this.relayClickOnItemInLeftPanel(null, el); }).bind(this), - onStart: (function(el) { + onStart: (function(el, e) { this.diag.log('draggable:onStart', el); this.tips.hide(); - el.fade(0.7).addClass('move'); + var position = el.getPosition(); + var dpos = el.retrieve('delta_pos'); + // this.diag.log('~~~ positions at start: ', position, dpos, e, 1 * this.browserScroll.contains(el)); - this.diag.log('ENABLE keyboard up/down on drag start'); + el.addClass('drag').setStyles({ + 'z-index': this.options.zIndex + 1500, + 'position': 'absolute', + 'width': el.getWidth() - el.getStyle('paddingLeft').toInt() - el.getStyle('paddingRight').toInt(), + 'display': 'none', + 'left': e.page.x - dpos.x, + 'top': e.page.y - dpos.y + }).inject(this.container); + + el.fade(0.7).addClass('move'); - // FIX wrong visual when CONTROL key is kept depressed between drag&drops: the old code discarded the relevant keyboard handler; we simply switch visuals but keep the keyboard handler active. - // This state change will be reverted in revert_drag_n_drop(). this.drag_is_active = true; - this.imageadd.fade(0 + this.ctrl_key_pressed); + + this.imagedragstate.setStyles({ + 'left': dpos.x - dpos.x - 12, + 'top': dpos.y - dpos.y + 2 + }).changeState(1 + this.ctrl_key_pressed); }).bind(this), - onEnter: function(el, droppable) { + onEnter: (function(el, droppable) { droppable.addClass('droppable'); - }, + this.imagedragstate.changeState(1 + this.ctrl_key_pressed); + }).bind(this), - onLeave: function(el, droppable) { + onLeave: (function(el, droppable) { droppable.removeClass('droppable'); - }, + this.imagedragstate.changeState(3); + }).bind(this), onDrop: (function(el, droppable, e) { this.diag.log('draggable:onDrop', el, droppable, e); + this.scroller.stop(); var is_a_move = !(e.control || e.meta); this.drop_pending = 1 + is_a_move; @@ -2499,7 +2531,7 @@ var FileManager = new Class({ if (this.onDragComplete(el, droppable)) { this.drop_pending = 0; - this.revert_drag_n_drop(el); // go and request the details anew, then refresh them in the view + this.revert_drag_n_drop(el); return; } @@ -2507,17 +2539,17 @@ var FileManager = new Class({ this.diag.log('on drop dir = ', dir); } - if ((!this.options.move_or_copy) || (is_a_move && !droppable)) { + if ((!this.options.move_or_copy) || (is_a_move && !dir)) { this.drop_pending = 0; - this.revert_drag_n_drop(el); // go and request the details anew, then refresh them in the view + this.revert_drag_n_drop(el); return; } - this.revert_drag_n_drop(el); // do not send the 'detail' request in here: this.drop_pending takes care of that! + this.revert_drag_n_drop(el); var file = el.retrieve('file'); - this.diag.log('on drop file = ', file, ', current dir:', this.CurrentDir, ', droppable: ', droppable); + this.diag.log('on drop file = ', file, ', current dir:', this.CurrentDir, ', droppable: ', droppable, ', dir: ', dir); if (this.Request) this.Request.cancel(); @@ -2552,7 +2584,8 @@ var FileManager = new Class({ if (this.view_fill_json) { - this.delete_from_dircache(file); /* do NOT use j.name, as that one can be 'cleaned up' as part of the 'move' operation! */ + /* do NOT use the resulting j.name, as that one can be 'cleaned up' as part of the 'move' operation! */ + this.delete_from_dircache(file); // minor caveat: when we paginate the directory list, then browsing to the next page will skip one item (which would // have been the first on the next page). The brute-force fix for this is to force a re-render of the page when in @@ -2647,7 +2680,6 @@ var FileManager = new Class({ this.adaptive_update_pagination_size(render_count, render_count, render_count, pagesize, duration, 1.0 / 5.0, 1.0, 0); // we're done: erase the timer so it can be garbage collected - //this.RequestQueue.cancel_bulk('fill'); -- do NOT do this! clearTimeout(this.view_fill_timer); this.view_fill_timer = null; @@ -2719,7 +2751,7 @@ var FileManager = new Class({ if (file.mime !== 'text/directory') jsGET.set({'fmFile': file.name}); else - jsGET.set({'fmFile': ''}); + jsGET.remove(['fmFile']); } var icon = file.icon; @@ -2732,8 +2764,6 @@ var FileManager = new Class({ this.preview.empty(); - //if (file.mime === 'text/directory') return; - if (this.drop_pending == 0) { if (this.Request) this.Request.cancel(); @@ -2744,10 +2774,11 @@ var FileManager = new Class({ var tx_cfg = this.options.mkServerRequestURL(this, 'detail', { directory: this.dirname(file.path), - // fixup for *directory* detail requests: (file.mime === 'text/directory' - file: (file.mime === 'text/directory' && file.name === '') ? '/' : file.name, + // fixup for root directory detail requests: + file: (file.mime === 'text/directory' && file.path === '/') ? '/' : file.name, filter: this.options.filter, - mode: 'auto' // provide either direct links to the thumbnails (when available in cache) or PHP event trigger URLs for delayed thumbnail image creation (performance optimization: faster page render) + // provide either direct links to the thumbnails (when available in cache) or PHP event trigger URLs for delayed thumbnail image creation (performance optimization: faster page render): + mode: 'auto' + this.options.detailInfoMode }); this.Request = new FileManager.Request({ @@ -2807,7 +2838,7 @@ var FileManager = new Class({ if (prev && !j.thumb250 && j.thumbs_deferred) { - var iconpath = this.assetBasePath + 'Images/Icons/Large/default-error.png'; + var iconpath = this.assetsUrl + 'Images/Icons/Large/default-error.png'; if (0) { @@ -2823,7 +2854,7 @@ var FileManager = new Class({ directory: this.dirname(file.path), file: file.name, filter: this.options.filter, - mode: 'direct' + mode: 'direct' + this.options.detailInfoMode }); var req = new FileManager.Request({ @@ -2840,11 +2871,6 @@ var FileManager = new Class({ prev.set('src', img_url); prev.addEvent('load', function() { - // when the thumb250 image has loaded, remove the loader animation in the background ... - //this.setStyle('background', 'none'); - // ... AND blow away the encoded 'width' and 'height' styles: after all, the thumb250 generation MAY have failed. - // In that case, an icon is produced by the backend, but it will have different dimensions, and we don't want to - // distort THAT one, either. this.setStyles({ 'width': '', 'height': '', @@ -2858,7 +2884,6 @@ var FileManager = new Class({ this.fireEvent('details', [j, this]); // update the stored json for this file as well: - // now mix with the previously existing 'file' info (as produced by a 'view' run): file = Object.merge(file, j); @@ -2945,11 +2970,17 @@ var FileManager = new Class({ $(appearOn).addEvents({ mouseenter: (function() { - this.set('opacity', opacity[0]); - }).bind(icon), + // see comment at the drag&drop section further above about the 'mouseleave' / 'mouseover' trouble due to the dragged element: + // here we make sure we don't 'track' the element hover while a drag&drop is in progress: + if (this.drag_is_active) { + return; + } + + icon.set('opacity', opacity[0]); + }).bind(this), mouseleave: (function() { - this.set('opacity', opacity[1]); - }).bind(icon) + icon.set('opacity', opacity[1]); + }).bind(this) }); return icon; }, @@ -3012,7 +3043,8 @@ var FileManager = new Class({ this.reset_fill(); - this.view_fill_json = ((j && j.status) ? j : null); // clear out the old JSON data and set up possibly new data. + // clear out the old JSON data and set up possibly new data. + this.view_fill_json = ((j && j.status) ? j : null); // ^^^ the latest JSON array describing the entire list; used with pagination to hop through huge dirs without repeatedly // consulting the server. The server doesn't need to know we're so slow we need pagination now! ;-) }, @@ -3169,14 +3201,111 @@ var FileManager = new Class({ if (typeof console !== 'undefined') { - if (console.info) + // WARNING: MS IE9 (+ v8?) says: this object doesn't support the 'apply' method. :-((( + // Also, MSIE 8/9 doesn't show object dumps like you'd expect; Firebug Lite allegedly fixes that, + // but this is code which intends to 'hide' all that shite, so we can simply write diag.log() and + // not bother where it will end up. + if (console.info && console.info.apply) { console.info.apply(console, arguments); } - else if (console.log) + else if (console.log && console.log.apply) { console.log.apply(console, arguments); } + else if (console.info || console.log) + { + // the MSIE downgrade + var l = (console.info || console.log); + var a; + var lt = ''; + var m, e, v; + var multiobj = 0; // count items dumped without inter-WS + for (a in arguments) + { + multiobj++; + a = arguments[a]; + switch (typeof a) + { + case 'undefined': + lt += '(undefined)'; + break; + + case 'null': + lt += '(null)'; + break; + + case 'object': + lt += '{'; + m = ''; + for (e in a) + { + lt += m; + + v = a[e]; + //if (typeof e !== 'string') continue; + switch (typeof v) + { + case 'function': + continue; // skip these + + case 'undefined': + lt += e + ': (undefined)'; + break; + + case 'null': + lt += e + ': (null)'; + break; + + case 'object': + // nuts of course: IE9 has objects which turn out as === null and clunk on .toString() as a consequence >:-S + if (v === null) + { + lt += e + ': (null)'; + } + else + { + lt += e + ': ' + v.toString(); + } + break; + + case 'string': + lt += e + ': "' + v + '"'; + break; + + default: + lt += e + ': ' + v.toString(); + break; + } + m = ', '; + } + lt += '}'; + break; + + case 'string': + // reset inter-WS formatting assist: + multiobj = 0; + lt += a; + break; + + default: + try + { + m = a.toString(); + } + catch (e) + { + m = '(*clunk*)'; + } + lt += v; + break; + } + if (multiobj >= 1) + { + lt += ' '; + } + } + } } } } @@ -3253,6 +3382,9 @@ Asset.javascript(__MFM_ASSETS_DIR__+'/js/milkbox/milkbox.js'); Asset.css(__MFM_ASSETS_DIR__+'/js/milkbox/css/milkbox.css'); Asset.css(__MFM_ASSETS_DIR__+'/Css/FileManager.css'); Asset.css(__MFM_ASSETS_DIR__+'/Css/Additions.css'); +if (Browser.ie && Browser.version <= 7) { + Asset.css(__MFM_ASSETS_DIR__+'/Css/FileManager_ie7.css'); +} Asset.javascript(__MFM_ASSETS_DIR__+'/js/jsGET.js', { events: { load: (function() { @@ -3392,8 +3524,6 @@ FileManager.Dialog = new Class({ if (('autofocus' in autofocus_el) && !(Browser.Engine && Browser.Engine.webkit)) { // HTML5 support: see http://diveintohtml5.org/detect.html - // - // Unfortunately, it's not really working for me in webkit browsers (Chrome, Safari) :-(( autofocus_el.set('autofocus', 'autofocus'); autofocus_el = null; } @@ -3408,7 +3538,7 @@ FileManager.Dialog = new Class({ // and http://www.mkyong.com/javascript/focus-is-not-working-in-ie-solution/ if (autofocus_el) { - if (0) // the delay suggested as a fix there is part of the fade()... + if (0) { (function(el) { el.focus(); @@ -3416,8 +3546,6 @@ FileManager.Dialog = new Class({ } else { - //autofocus_el.set('tabIndex', 0); // http://code.google.com/p/chromium/issues/detail?id=27868#c15 - // ^-- not needed. When you debug JS in a Webkit browser, you're toast when it comes to getting input field focus, period. :-( autofocus_el.focus(); } } @@ -3446,14 +3574,15 @@ FileManager.Dialog = new Class({ // make sure the dialog never is larger than the viewport! var ddim = this.el.getSize(); var vdim = window.getSize(); - var maxx = (vdim.x - 20) * 0.85; // heuristic: make dialog a little smaller than the screen itself and keep a place for possible outer scrollbars + // heuristic: make dialog a little smaller than the screen itself and keep a place for possible outer scrollbars + var maxx = (vdim.x - 20) * 0.85; if (ddim.x >= maxx) { this.el.setStyle('width', maxx.toInt()); } ddim = this.el.getSize(); var cdim = this.content_el.getSize(); - var maxy = (vdim.y - 20) * 0.85; // heuristic: make dialog a little less high than the screen itself and keep a place for possible outer scrollbars + var maxy = (vdim.y - 20) * 0.85; if (ddim.y >= maxy) { // first attempt to correct this by making the dialog wider: @@ -3565,10 +3694,12 @@ this.Overlay = new Class({ this.destroy(); } else { - this.el.setStyles({ - width: document.getScrollWidth(), - height: document.getScrollHeight() - }); + if (!Browser.ie) { + this.el.setStyles({ + width: document.getScrollWidth(), + height: document.getScrollHeight() + }); + } } }, diff --git a/Source/Gallery.js b/Source/Gallery.js old mode 100644 new mode 100755 index 525f441..61c4bc2 --- a/Source/Gallery.js +++ b/Source/Gallery.js @@ -56,6 +56,9 @@ FileManager.Gallery = new Class({ }, show: function(self) { + self.gallery.empty(); + self.files = {}; + self.show_gallery(); self.populate(); }, @@ -257,8 +260,10 @@ FileManager.Gallery = new Class({ // determine how many images we can show next to one another: var galWidthComp = 2; // 2px not accounted for in the Width calculation; without these the width slowly grows instead of 'jump' to fit one more/less image horizontally var imgMarginWidth = 4; // 4px unaccounted for in the element width - var radiiCorr = 20; // correction for the radii in the FileManager container: looks better that way + var radiiCorr = 40; // correction for the radii in the FileManager container: looks better that way; must at least be 20 var imgWidth = 84; // derived from current CSS; bad form to do it this way, but this is a good guess when there's no image in there yet. + var scrollbarCorr = 20; // correction for optional Y scrollbar when the number of images doesn't fit in two rows anymore. + var imgs = this.galleryContainer.getElements('li'); if (imgs && imgs[0]) { @@ -266,10 +271,10 @@ FileManager.Gallery = new Class({ imgWidth = imgs[0].getWidth() + imgMarginWidth; } var galWidth = this.galleryContainer.getWidth(); - var g_rem = galWidth % imgWidth; - var img_cnt = Math.floor((size.x - g_rem - radiiCorr) / imgWidth); + var g_rem = (galWidth - scrollbarCorr) % imgWidth; + var img_cnt = Math.floor((size.x - g_rem - scrollbarCorr - radiiCorr) / imgWidth); if (img_cnt < 3) img_cnt = 3; - var gw = img_cnt * imgWidth + g_rem; + var gw = img_cnt * imgWidth + g_rem + scrollbarCorr; this.galleryContainer.setStyles({ top: pos.y + size.y - 1, @@ -441,7 +446,7 @@ FileManager.Gallery = new Class({ mb = ch - mt - h; var self = this; - var img = new Asset.image(pic, { + var img = Asset.image(pic, { styles: { width: w, height: h, @@ -462,12 +467,12 @@ FileManager.Gallery = new Class({ }, onError: function() { self.diag.log('image asset: error!'); - var iconpath = self.assetBasePath + 'Images/Icons/Large/default-error.png'; + var iconpath = self.URLpath4assets + 'Images/Icons/Large/default-error.png'; this.src = iconpath; }, onAbort: function() { self.diag.log('image asset: ABORT!'); - var iconpath = self.assetBasePath + 'Images/Icons/Large/default-error.png'; + var iconpath = self.URLpath4assets + 'Images/Icons/Large/default-error.png'; this.src = iconpath; } }); @@ -516,7 +521,7 @@ FileManager.Gallery = new Class({ // store & display item in gallery: var self = this; - var destroyIcon = new Asset.image(this.assetBasePath + 'Images/destroy.png').set({ + var destroyIcon = Asset.image(this.URLpath4assets + 'Images/destroy.png').set({ 'class': 'filemanager-remove', title: this.language.gallery.remove, events: { @@ -571,7 +576,7 @@ FileManager.Gallery = new Class({ directory: this.dirname(file.path), file: file.name, filter: this.options.filter, - mode: 'direct' // provide direct links to the thumbnail files + mode: 'direct' + this.options.detailInfoMode // provide direct links to the thumbnail files }); var req = new FileManager.Request({ @@ -755,6 +760,8 @@ FileManager.Gallery = new Class({ { // we've got work to do, folks! var i; + var abs_root = this.normalize('/' + this.root); + for (i = 0; i < count; ++i) { // LIFO queue: @@ -763,7 +770,7 @@ FileManager.Gallery = new Class({ // coded so that we support 'legal URL space' items and 'absolute URL path' items at the same time: // when paths start with the root directory, we'll strip that off to make them 'legal URL space' compliant filespecs. - if (path.indexOf(this.root) === 0) + if (path.indexOf(abs_root) === 0) { path = path.substr(this.root.length); } @@ -785,11 +792,16 @@ FileManager.Gallery = new Class({ var serialized = {}; var metas = {}; var index = 0; - Object.each(this.files,function(file) { - serialized[file.legal_path] = (file.caption || ''); + Object.each(this.files,function(file) + { + var path = (this.options.deliverPathAsLegalURL ? file.file.path : this.escapeRFC3986(this.normalize('/' + this.root + file.file.path))); // the absolute URL for the given file, rawURLencoded + var caption = (file.caption || ''); + serialized[path] = caption; var m = Object.clone(file.file); m['order_no'] = index++; - metas[file.legal_path] = m; + m['caption'] = caption; + m['pathRFC3986'] = path; + metas[path] = m; }, this); this.keepGalleryData = true; this.hide(e); diff --git a/Source/NoFlash.Uploader.js b/Source/NoFlash.Uploader.js index 6f227f4..7c061fc 100644 --- a/Source/NoFlash.Uploader.js +++ b/Source/NoFlash.Uploader.js @@ -18,7 +18,7 @@ provides: Filemanager.NoFlashUploader * While the flash uploader is preferable, sometimes it is not possible to use it due to * server restrictions (eg, mod_security), or perhaps users refuse to use flash. * - * This Upload handler will allow the MFM to continue to function, without multiple-upload-at-once + * This Upload handler will allow the MTFM to continue to function, without multiple-upload-at-once * function and without progress bars. But otherwise, it should work. */ FileManager.implement({ @@ -26,9 +26,7 @@ FileManager.implement({ options: { resizeImages: true, upload: true, - - // Is there a useful reason to have this different from propagateData... dubious :-/ - uploadAuthData: {} + uploadAuthData: {} // deprecated; use FileManager.propagateData instead! }, hooks: { @@ -39,35 +37,28 @@ FileManager.implement({ }, cleanup: { - upload: function(){ - if (!this.options.upload || !this.upload) { - return; - } - - try { - if (this.upload.uploader) { - this.upload.uploader.set('opacity', 0).dispose(); - } - } - catch(e) { } + upload: function() { + this.hideUpload(); } } }, - _dummyframe: null, - - onDialogOpenWhenUpload: function(){ + onDialogOpenWhenUpload: function() { }, - onDialogCloseWhenUpload: function(){ + onDialogCloseWhenUpload: function() { }, // Writing to file input values is not permitted, we replace the field to blank it. make_file_input: function(form_el) { - var fileinput = (new Element('input')).set({type: 'file', 'name': 'Filedata'}).setStyles({width: 120}); + var fileinput = (new Element('input')).set({ + type: 'file', + name: 'Filedata', + id: 'filemanager_upload_Filedata' + }); if (form_el.getElement('input[type=file]')) { fileinput.replaces(form_el.getElement('input[type=file]')); @@ -79,149 +70,202 @@ FileManager.implement({ return form_el; }, - - startUpload: function() + hideUpload: function() { - if (!this.options.upload) { - return; + if (!this.options.upload || !this.upload) return; + + if (this.upload.uploadButton.label) + { + this.upload.uploadButton.label.fade(0).get('tween').chain(function() { + this.element.dispose().destroy(); + }); + this.upload.uploadButton.label = null; + } + if (this.upload.uploadButton) + { + this.upload.uploadButton.fade(0).get('tween').chain(function() { + this.element.dispose().destroy(); + }); + this.upload.uploadButton = null; + } + if (this.upload.form) + { + this.upload.inputs = null; + + this.upload.form.dispose().destroy(); + this.upload.form = null; + } + this.menu.setStyle('height', ''); + + if (this.upload.resizer) + { + this.upload.resizer.dispose().destroy(); + this.upload.resizer = null; } // discard old iframe, if it exists: - if (this._dummyframe) + if (this.upload.dummyframe) { // remove from the menu (dispose) and trash it (destroy) - this._dummyframe.dispose().destroy(); - this._dummyframe = null; + this.upload.dummyframe.dispose().destroy(); + this.upload.dummyframe = null; } + }, - var mfm = this; - - var f = (new Element('form')) - //.set('action', tx_cfg.url) - .set('method', 'post') - .set('enctype', 'multipart/form-data') - .set('target', 'dummyframe') - .setStyles({ 'float': 'left', 'padding-left': '3px', 'display': 'block'}); - - var uploadButton = this.addMenuButton('upload').addEvents({ - click: function(e) { - e.stop(); - mfm.browserLoader.set('opacity', 1); - f.action = tx_cfg.url; - // TODO: fixup the resize setting! - // resize: ((this.label && this.label.getElement('.checkbox').hasClass('checkboxChecked')) ? 1 : 0) - - f.submit(); - }, - mouseenter: function(){ - this.addClass('hover'); - }, - mouseleave: function(){ - this.removeClass('hover'); - this.blur(); - }, - mousedown: function(){ - this.focus(); - } - }); + startUpload: function() + { + if (!this.options.upload) { + return; + } - this.menu.adopt(uploadButton); + var self = this; + + this.upload = { + inputs: {}, + resizer: null, + dummyframe: null, + dummyframe_active: false, // prevent premature firing of the load event (hello, MSIE!) to cause us serious trouble in there + + form: (new Element('form')) + //.set('action', tx_cfg.url) + .set('method', 'post') + .set('enctype', 'multipart/form-data') + .set('encoding', 'multipart/form-data') // IE7 + .set('target', 'dummyframe') + .setStyles({ + 'float': 'left', + 'padding-left': '3px', + 'display': 'block' + }), + + uploadButton: this.addMenuButton('upload').inject(this.menu, 'bottom').addEvents({ + click: function(e) { + e.stop(); + self.browserLoader.fade(1); + self.upload.form.action = tx_cfg.url; + + // Update curent dir path to form hidden field + self.upload.inputs['directory'].setProperty('value', self.CurrentDir.path); + + self.upload.dummyframe_active = true; // NOW you may fire when ready... + + self.upload.form.submit(); + }, + mouseenter: function() { + this.addClass('hover'); + }, + mouseleave: function() { + this.removeClass('hover'); + this.blur(); + }, + mousedown: function() { + this.focus(); + } + }), - if (this.options.resizeImages){ - var resizer = new Element('div', {'class': 'checkbox'}); - var check = (function(){ - this.toggleClass('checkboxChecked'); - }).bind(resizer); - - check(); - uploadButton.label = new Element('label').adopt( - resizer, new Element('span', {text: this.language.resizeImages}) - ).addEvent('click', check).inject(this.menu); - } + lastFileUploaded: null, // name of the last successfully uploaded file; will be preselected in the list view + error_count: 0 + }; var tx_cfg = this.options.mkServerRequestURL(this, 'upload', Object.merge({}, this.options.propagateData, (this.options.uploadAuthData || {}), { - directory: this.CurrentDir.path, + directory: (this.CurrentDir ? this.CurrentDir.path : null), filter: this.options.filter, - resize: this.options.resizeImages, // TODO: must be updated when button is clicked + resize: this.options.resizeImages, reportContentType: 'text/plain' // Safer for iframes: the default 'application/json' mime type would cause FF3.X to pop up a save/view dialog! })); + // Create hidden input for each form data Object.each(tx_cfg.data, function(v, k){ - f.adopt((new Element('input')).set({type: 'hidden', name: k, value: v})); + var input = new Element('input').set({type: 'hidden', name: k, value: v, id: 'filemanager_upload_' + k }); + self.upload.form.adopt(input); + self.upload.inputs[k] = input; }); - this.make_file_input(f); - - f.inject(this.menu, 'top'); + if (this.options.resizeImages) + { + this.upload.resizer = new Element('div', {'class': 'checkbox'}); + var check = (function() + { + this.toggleClass('checkboxChecked'); - this._dummyframe = (new IFrame).set({src: 'about:blank', name: 'dummyframe'}).setStyles({display: 'none'}); - this.menu.adopt(this._dummyframe); + // Update the resize hidden field + self.upload.inputs['resize'].setProperty('value', (this.hasClass('checkboxChecked')) ? 1 : 0); + }).bind(this.upload.resizer); + check(); + this.upload.uploadButton.label = new Element('label', { 'class': 'filemanager-resize' }).adopt( + this.upload.resizer, + new Element('span', {text: this.language.resizeImages}) + ).addEvent('click', check).inject(this.menu); + } - this._dummyframe.addEvent('load', function() - { - mfm.browserLoader.fade(0); + this.make_file_input(self.upload.form); - try - { - var response; - try - { - response = this.contentDocument.documentElement.textContent; - } - catch(e) {} + self.upload.form.inject(this.menu, 'top'); + //this.menu.setStyle('height', '60px'); - if (!response) - { - try - { - response = this.contentWindow.document.innerText; - } - catch(e) {} - } + // discard old iframe, if it exists: + if (this.upload.dummyframe) + { + // remove from the menu (dispose) and trash it (destroy) + this.upload.dummyframe.dispose().destroy(); + this.upload.dummyframe = null; + } - if (!response) - { - try - { - response = this.contentDocument.innerText; - } - catch(e) {} - } + this.upload.dummyframe = (new IFrame).set({src: 'about:blank', name: 'dummyframe'}).setStyles({display: 'none'}); + this.menu.adopt(this.upload.dummyframe); - if (!response) - { - throw "Can't find response."; + this.upload.dummyframe.addEvent('load', function() + { + var iframe = this; + self.diag.log('NoFlash upload response: ', this, ', iframe: ', self.upload.dummyframe, ', ready:', (1 * self.upload.dummyframe_active)); + + // make sure we don't act on premature firing of the event in MSIE browsers: + if (!self.upload.dummyframe_active) + return; + + self.browserLoader.fade(0); + + var response = null; + Function.attempt(function() { + response = iframe.contentDocument.documentElement.textContent; + }, + function() { + response = iframe.contentWindow.document.innerText; + }, + function() { + response = iframe.contentDocument.innerText; + }, + function() { + // Maybe this.contentDocument.documentElement.innerText isn't where we need to look? + //debugger; + response = "{status: 0, error: \"noFlashUpload: document innerText grab FAIL: Can't find response.\"}"; } + ); - response = JSON.decode(response); + var j = JSON.decode(response); - if (response && !response.status) - { - mfm.showError('' + response.error); - mfm.load(mfm.CurrentDir.path); - } - else if (response) - { - // mfm.onShow = true; // why exactly do we need to set this, what purpose does the default of NOT preselecting the thing we asked to preselect have? - mfm.load(mfm.dirname(response.path), response.name); - } - else - { - this.showError('bugger! No JSON response!'); - mfm.load(mfm.CurrentDir.path); - } + if (j && !j.status) + { + self.showError('' + j.error); + self.load(self.CurrentDir.path); } - catch(e) + else if (j) { - // Maybe this.contentDocument.documentElement.innerText isn't where we need to look? - //debugger; - mfm.diag.log('noFlashUpload: document innerText grab FAIL:', e, this, tx_cfg); - mfm.load(mfm.CurrentDir.path); + self.load(self.CurrentDir.path, j.name); + } + else + { + // IE9 fires the load event on init! :-( + if (self.CurrentDir) + { +// self.showError('bugger! No or faulty JSON response! ' + response); + self.load(self.CurrentDir.path); + } } - mfm.make_file_input(f); + self.make_file_input(self.upload.form); }); } }); diff --git a/Source/Uploader.js b/Source/Uploader.js index bf0b9c6..ce7bbdc 100644 --- a/Source/Uploader.js +++ b/Source/Uploader.js @@ -20,7 +20,7 @@ FileManager.implement({ resizeImages: true, upload: true, uploadAuthData: {}, // deprecated; use FileManager.propagateData instead! - uploadTimeLimit: 260, + uploadTimeLimit: 300, uploadFileSizeMax: 2600 * 2600 * 25 }, @@ -33,21 +33,17 @@ FileManager.implement({ cleanup: { upload: function() { + if (!this.options.upload || !this.upload) return; - if (this.upload.uploader) { - this.upload.uploader.fade(0).get('tween').chain(function() { - this.element.dispose(); - }); - } + if (this.upload.uploader) this.upload.uploader.dispose(); + if (this.upload.button) this.upload.button.dispose(); + if (this.upload.list) this.upload.list.dispose(); + if (this.swf.box) this.swf.box.dispose(); } } }, - lastFileUploaded: null, // name of the last successfully uploaded file; will be preselected in the list view - error_count: 0, - - onDialogOpenWhenUpload: function() { if (this.swf && this.swf.box) this.swf.box.setStyle('visibility', 'hidden'); }, @@ -81,7 +77,9 @@ FileManager.implement({ uploader: new Element('div', {opacity: 0, 'class': 'filemanager-uploader-area'}).adopt( new Element('h2', {text: this.language.upload}), new Element('div', {'class': 'filemanager-uploader'}) - ) + ), + lastFileUploaded: null, // name of the last successfully uploaded file; will be preselected in the list view + error_count: 0 }; this.upload.uploader.getElement('div').adopt(this.upload.list); @@ -148,10 +146,10 @@ FileManager.implement({ }); this.ui = {}; - this.ui.icon = new Asset.image(self.assetBasePath+'Images/Icons/' + this.extension + '.png', { + this.ui.icon = new Asset.image(self.URLpath4assets+'Images/Icons/' + this.extension + '.png', { 'class': 'icon', onerror: function() { - new Asset.image(self.assetBasePath + 'Images/Icons/default.png').replaces(this); + new Asset.image(self.URLpath4assets + 'Images/Icons/default.png').replaces(this); } }); this.ui.element = new Element('li', {'class': 'file', id: 'file-' + this.id}); @@ -164,14 +162,14 @@ FileManager.implement({ this.ui.size = new Element('span', {'class': 'file-size', text: Swiff.Uploader.formatUnit(this.size, 'b')}); var file = this; - this.ui.cancel = new Asset.image(self.assetBasePath+'Images/cancel.png', {'class': 'file-cancel', title: self.language.cancel}).addEvent('click', function() { + this.ui.cancel = new Asset.image(self.URLpath4assets+'Images/cancel.png', {'class': 'file-cancel', title: self.language.cancel}).addEvent('click', function() { file.remove(); self.tips.hide(); self.tips.detach(this); }); self.tips.attach(this.ui.cancel); - var progress = new Element('img', {'class': 'file-progress', src: self.assetBasePath+'Images/bar.gif'}); + var progress = new Element('img', {'class': 'file-progress', src: self.URLpath4assets+'Images/bar.gif'}); this.ui.element.adopt( this.ui.cancel, @@ -197,11 +195,11 @@ FileManager.implement({ // when all items in the list have been cancelled/removed, and the transmission of the files is done, i.e. after the onComplete has fired, destroy the list! var cnt = self.upload.list.getElements('li').length; - if (cnt == 0 && this.has_completed) + +// if (cnt == 0 && this.has_completed) + if (cnt == 0 ) { - self.upload.uploader.fade(0).get('tween').chain(function() { - self.upload.uploader.setStyle('display', 'none'); - }); + self.upload.uploader.setStyle('display', 'none'); } }, @@ -275,7 +273,7 @@ FileManager.implement({ if (failure) { - self.error_count++; + self.upload.error_count++; } // don't wait for the cute delays to start updating the directory view! @@ -302,11 +300,9 @@ FileManager.implement({ }; this.diag.log('Uploader: SWF init'); - this.lastFileUploaded = null; - this.error_count = 0; this.swf = new Swiff.Uploader({ id: 'SwiffFileManagerUpload', - path: this.assetBasePath + 'Swiff.Uploader.swf', + path: this.URLpath4assets + 'Swiff.Uploader.swf', queued: false, target: this.upload.button, allowDuplicates: true, @@ -321,7 +317,7 @@ FileManager.implement({ timeLimit: self.options.uploadTimeLimit, fileSizeMax: self.options.uploadFileSizeMax, typeFilter: this.getFileTypes(), - zIndex: this.options.zIndex + 3000, + zIndex: this.options.zIndex + 400000, onSelectSuccess: function() { self.diag.log('FlashUploader: onSelectSuccess', arguments, ', fileList: ', self.swf.fileList); //self.fillInfo(); @@ -339,17 +335,16 @@ FileManager.implement({ this.diag.log('upload:onComplete', info, cnt, fcnt); // add a 5 second delay when there were upload errors: (function() { - this.onShow = true; - this.load(this.CurrentDir.path, this.lastFileUploaded); + this.load(this.CurrentDir.path, this.upload.lastFileUploaded); // this.fillInfo(); - }).bind(this).delay(this.error_count > 0 ? 5500 : 1); + }).bind(this).delay(this.upload.error_count > 0 ? 5500 : 1); }.bind(this), onFileComplete: function(f) { self.diag.log('FlashUploader: onFileComplete', arguments, ', fileList: ', self.swf.fileList); - self.lastFileUploaded = f.name; + self.upload.lastFileUploaded = f.name; }, onFail: function(error) { - self.diag.log('FlashUploader: onFail', arguments, ', fileList: ', self.swf.fileList); + self.diag.log('FlashUploader: onFail', arguments, ', swf: ', self.swf, ', fileList: ', (typeof self.swf !== 'undefined' ? self.swf : '---')); if (error !== 'empty') { $$(self.upload.button, self.upload.label).dispose(); self.showError(self.language.flash[error] || self.language.flash.flash); diff --git a/dev_status.txt b/dev_status.txt new file mode 100644 index 0000000..caa6625 --- /dev/null +++ b/dev_status.txt @@ -0,0 +1,5 @@ + +Line 2428 : +// Store thumb 250 path in meta with getThumb +// or try to get it through +// $thumbPath = $meta->getThumbPath($width . 'x' . $height);