Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F8224382
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
78 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/kolab/kolab_file_storage.php b/lib/kolab/kolab_file_storage.php
index 4cb9175..4d507ee 100644
--- a/lib/kolab/kolab_file_storage.php
+++ b/lib/kolab/kolab_file_storage.php
@@ -1,771 +1,770 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class kolab_file_storage implements file_storage
{
/**
* @var rcube
*/
protected $rc;
/**
* @var array
*/
protected $folders;
/**
* Class constructor
*/
public function __construct()
{
$include_path = RCUBE_INSTALL_PATH . '/lib/kolab' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
$this->rc = rcube::get_instance();
// Get list of plugins
// WARNING: We can use only plugins that are prepared for this
// e.g. are not using output or rcmail objects or
// doesn't throw errors when using them
$plugins = (array)$this->rc->config->get('fileapi_plugins', array('kolab_auth', 'kolab_folders'));
$required = array('libkolab', 'kolab_folders');
// Initialize/load plugins
$this->rc->plugins = kolab_file_plugin_api::get_instance();
$this->rc->plugins->init($this, '');
$this->rc->plugins->load_plugins($plugins, $required);
$this->init();
}
/**
* Authenticates a user
*
* @param string $username User name
* @param string $password User password
*
* @param bool True on success, False on failure
*/
public function authenticate($username, $password)
{
$auth = $this->rc->plugins->exec_hook('authenticate', array(
'host' => $this->select_host($username),
'user' => $username,
'pass' => $password,
'valid' => true,
));
// Authenticate - get Roundcube user ID
if ($auth['valid'] && !$auth['abort']
&& ($this->login($auth['user'], $auth['pass'], $auth['host']))) {
return true;
}
$this->rc->plugins->exec_hook('login_failed', array(
'host' => $auth['host'],
'user' => $auth['user'],
));
}
/**
* Storage host selection
*/
private function select_host($username)
{
// Get IMAP host
$host = $this->rc->config->get('default_host');
if (is_array($host)) {
list($user, $domain) = explode('@', $username);
// try to select host by mail domain
if (!empty($domain)) {
foreach ($host as $storage_host => $mail_domains) {
if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
$host = $storage_host;
break;
}
else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) {
$host = is_numeric($storage_host) ? $mail_domains : $storage_host;
break;
}
}
}
// take the first entry if $host is not found
if (is_array($host)) {
list($key, $val) = each($default_host);
$host = is_numeric($key) ? $val : $key;
}
}
return rcube_utils::parse_host($host);
}
/**
* Authenticates a user in IMAP
*/
private function login($username, $password, $host)
{
if (empty($username)) {
return false;
}
$login_lc = $this->rc->config->get('login_lc');
$default_port = $this->rc->config->get('default_port', 143);
// parse $host
$a_host = parse_url($host);
if ($a_host['host']) {
$host = $a_host['host'];
$ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
if (!empty($a_host['port'])) {
$port = $a_host['port'];
}
else if ($ssl && $ssl != 'tls' && (!$default_port || $default_port == 143)) {
$port = 993;
}
}
if (!$port) {
$port = $default_port;
}
// Convert username to lowercase. If storage backend
// is case-insensitive we need to store always the same username
if ($login_lc) {
if ($login_lc == 2 || $login_lc === true) {
$username = mb_strtolower($username);
}
else if (strpos($username, '@')) {
// lowercase domain name
list($local, $domain) = explode('@', $username);
$username = $local . '@' . mb_strtolower($domain);
}
}
// Here we need IDNA ASCII
// Only rcube_contacts class is using domain names in Unicode
$host = rcube_utils::idn_to_ascii($host);
$username = rcube_utils::idn_to_ascii($username);
// user already registered?
if ($user = rcube_user::query($username, $host)) {
$username = $user->data['username'];
}
// authenticate user in IMAP
$storage = $this->rc->get_storage();
if (!$storage->connect($host, $username, $password, $port, $ssl)) {
return false;
}
// No user in database, but IMAP auth works
if (!is_object($user)) {
if ($this->rc->config->get('auto_create_user')) {
// create a new user record
$user = rcube_user::create($username, $host);
if (!$user) {
rcube::raise_error(array(
'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to create a user record",
), true, false);
return false;
}
}
else {
rcube::raise_error(array(
'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__,
'message' => "Access denied for new user $username. 'auto_create_user' is disabled",
), true, false);
return false;
}
}
// set session vars
$_SESSION['user_id'] = $user->ID;
$_SESSION['username'] = $user->data['username'];
$_SESSION['storage_host'] = $host;
$_SESSION['storage_port'] = $port;
$_SESSION['storage_ssl'] = $ssl;
$_SESSION['password'] = $this->rc->encrypt($password);
$this->init($user);
// force reloading of mailboxes list/data
$storage->clear_cache('mailboxes', true);
return true;
}
protected function init($user = null)
{
if ($_SESSION['user_id'] || $user) {
// overwrite config with user preferences
$this->rc->user = $user ? $user : new rcube_user($_SESSION['user_id']);
$this->rc->config->set_user_prefs((array)$this->rc->user->get_prefs());
$storage = $this->rc->get_storage();
$storage->set_charset($this->rc->config->get('default_charset', RCUBE_CHARSET));
setlocale(LC_ALL, 'en_US.utf8', 'en_US.UTF-8');
}
}
/**
* Storage driver capabilities
*
* @return array List of capabilities
*/
public function capabilities()
{
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize) {
$max_filesize = $max_postsize;
}
return array(
file_storage::CAPS_MAX_UPLOAD => $max_filesize,
);
}
/**
* Create a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_create($file_name, $file)
{
$exists = $this->get_file_object($file_name, $folder);
if (!empty($exists)) {
throw new Exception("Storage error. File exists.", file_api::ERROR_CODE);
}
$object = $this->to_file_object(array(
'name' => $file_name,
'type' => $file['type'],
'path' => $file['path'],
));
// save the file object in IMAP
$saved = $folder->save($object, 'file');
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving object to Kolab server"),
true, false);
throw new Exception("Storage error. Saving object failed.", file_api::ERROR_CODE);
}
}
/**
* Delete a file.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_delete($file_name)
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_api::ERROR_CODE);
}
$deleted = $folder->delete($file);
if (!$deleted) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting object from Kolab server"),
true, false);
throw new Exception("Storage error. Deleting object failed.", file_api::ERROR_CODE);
}
}
/**
* Return file body.
*
* @param string $file_name Name of a file (with folder path)
* @param array $params Parameters (force-download)
*
* @throws Exception
*/
public function file_get($file_name, $params = array())
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_api::ERROR_CODE);
}
$file = $this->from_file_object($file);
if (!empty($params['force-download'])) {
$disposition = 'attachment';
header("Content-Type: application/octet-stream");
// @TODO
// if ($browser->ie)
// header("Content-Type: application/force-download");
}
else {
$mimetype = $params['force-type'] ? $params['force-type'] : $file['type'];
$disposition = 'inline';
header("Content-Transfer-Encoding: binary");
header("Content-Type: $mimetype");
}
$filename = addcslashes($file['name'], '"');
// Workaround for nasty IE bug (#1488844)
// If Content-Disposition header contains string "attachment" e.g. in filename
// IE handles data as attachment not inline
/*
@TODO
if ($disposition == 'inline' && $browser->ie && $browser->ver < 9) {
$filename = str_ireplace('attachment', 'attach', $filename);
}
*/
header("Content-Length: " . $file['size']);
header("Content-Disposition: $disposition; filename=\"$filename\"");
$folder->get_attachment($file['_msguid'], $file['fileid'], $file['_mailbox'], true);
}
/**
* Returns file metadata.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_info($file_name)
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_api::ERROR_CODE);
}
$file = $this->from_file_object($file);
return array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
'mtime' => $file['changed']->format($_SESSION['config']['date_format']),
);
}
/**
* List files in a folder.
*
* @param string $folder_name Name of a folder with full path
* @param array $params List parameters ('sort', 'reverse', 'search')
*
* @return array List of files (file properties array indexed by filename)
* @throws Exception
*/
public function file_list($folder_name, $params = array())
{
$filter = array(array('type', '=', 'file'));
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
switch ($idx) {
case 'name':
$filter[] = array('filename', '~', $value);
break;
case 'class':
foreach (file_utils::class2mimetypes($value) as $tag) {
$for[] = array('tags', '~', ' ' . $tag);
}
$filter[] = array($for, 'OR');
break;
}
}
}
// get files list
$folder = $this->get_folder_object($folder_name);
$files = $folder->select($filter);
$result = array();
// convert to kolab_storage files list data format
foreach ($files as $idx => $file) {
$file = $this->from_file_object($file);
if (!isset($file['name'])) {
continue;
}
$filename = $folder_name . file_api::PATH_SEPARATOR . $file['name'];
$result[$filename] = array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
'mtime' => $file['changed']->format($_SESSION['config']['date_format']),
+ 'modified' => $file['changed']->format('U'),
);
unset($files[$idx]);
}
// @TODO: pagination, search (by filename, mimetype)
// Sorting
$sort = !empty($params['sort']) ? $params['sort'] : 'name';
$index = array();
- if (in_array($sort, array('name', 'size'))) {
- foreach ($result as $key => $val) {
- $index[$key] = $val[$sort];
- }
- array_multisort($index, SORT_ASC, SORT_NUMERIC, $result);
+ if ($sort == 'mtime') {
+ $sort = 'modified';
}
- else if ($sort == 'mtime') {
+
+ if (in_array($sort, array('name', 'size', 'modified'))) {
foreach ($result as $key => $val) {
- $index[$key] = strtotime($val['mtime']);
+ $index[$key] = $val[$sort];
}
array_multisort($index, SORT_ASC, SORT_NUMERIC, $result);
}
if ($params['reverse']) {
$result = array_reverse($result, true);
}
return $result;
}
/**
* Copy a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_copy($file_name, $new_name)
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_api::ERROR_CODE);
}
$new = $this->get_file_object($new_name, $new_folder);
if (!empty($new)) {
throw new Exception("Storage error. File exists.", file_api::ERROR_CODE);
}
$file = $this->from_file_object($file);
// Save to temp file
// @TODO: use IMAP CATENATE extension
$temp_dir = unslashify($this->rc->config->get('temp_dir'));
$file_path = tempnam($temp_dir, 'rcmAttmnt');
$fh = fopen($file_path, 'w');
if (!$fh) {
throw new Exception("Storage error. File copying failed.", file_api::ERROR_CODE);
}
$folder->get_attachment($file['uid'], $file['fileid'], null, false, $fh, true);
fclose($fh);
if (!file_exists($file_path)) {
throw new Exception("Storage error. File copying failed.", file_api::ERROR_CODE);
}
// Update object
$file['_attachments'] = array(
0 => array(
'name' => $file['name'],
'path' => $file_path,
'mimetype' => $file['type'],
'size' => $file['size'],
));
$fields = array('created', 'changed', '_attachments', 'notes', 'sensitivity', 'categories', 'x-custom');
$file = array_intersect_key($file, array_combine($fields, $fields));
$saved = $new_folder->save($file, 'file');
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error updating object on Kolab server"),
true, false);
throw new Exception("Storage error. File copying failed.", file_api::ERROR_CODE);
}
}
/**
* Move (or rename) a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_move($file_name, $new_name)
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_api::ERROR_CODE);
}
$new = $this->get_file_object($new_name, $new_folder);
if (!empty($new)) {
throw new Exception("Storage error. File exists.", file_api::ERROR_CODE);
}
// Move the file
if ($folder->name != $new_folder->name) {
$saved = $folder->move($file['uid'], $new_folder->name);
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error moving object on Kolab server"),
true, false);
throw new Exception("Storage error. File move failed.", file_api::ERROR_CODE);
}
$folder = $new_folder;
}
if ($file_name === $new_name) {
return;
}
// Update object (changing the name)
$cid = key($file['_attachments']);
$file['_attachments'][$cid]['name'] = $new_name;
$file['_attachments'][0] = $file['_attachments'][$cid];
$file['_attachments'][$cid] = false;
$saved = $folder->save($file, 'file');
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error updating object on Kolab server"),
true, false);
throw new Exception("Storage error. File rename failed.", file_api::ERROR_CODE);
}
}
/**
* Create a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_create($folder_name)
{
$folder_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$success = kolab_storage::folder_create($folder_name, 'file');
if (!$success) {
throw new Exception("Storage error. Unable to create folder", file_api::ERROR_CODE);
}
}
/**
* Delete a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_delete($folder_name)
{
$folder_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$success = kolab_storage::folder_delete($folder_name);
if (!$success) {
throw new Exception("Storage error. Unable to delete folder.", file_api::ERROR_CODE);
}
}
/**
* Rename a folder.
*
* @param string $folder_name Name of a folder with full path
* @param string $new_name New name of a folder with full path
*
* @throws Exception on error
*/
public function folder_rename($folder_name, $new_name)
{
$folder_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$new_name = rcube_charset::convert($new_name, RCUBE_CHARSET, 'UTF7-IMAP');
$success = kolab_storage::folder_rename($folder_name, $new_name);
if (!$success) {
throw new Exception("Storage error. Unable to rename folder", file_api::ERROR_CODE);
}
}
/**
* Returns list of folders.
*
* @return array List of folders
* @throws Exception
*/
public function folder_list()
{
$storage = $this->rc->get_storage();
$folders = $storage->list_folders('', '*', 'file');
if (!is_array($folders)) {
throw new Exception("Storage error. Unable to get folders list.", file_api::ERROR_CODE);
}
foreach ($folders as $folder) {
$folder = rcube_charset::convert($folder_name, 'UTF7-IMAP', RCUBE_CHARSET);
}
return $folders;
}
/**
* Get file object.
*
* @param string $file_name Name of a file (with folder path)
* @param kolab_storage_folder $folder Reference to folder object
*
* @return array File data
* @throws Exception
*/
protected function get_file_object(&$file_name, &$folder = null)
{
// extract file path and file name
$path = explode(file_api::PATH_SEPARATOR, $file_name);
$file_name = array_pop($path);
$folder_name = implode(file_api::PATH_SEPARATOR, $path);
if ($folder_name === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
// get folder object
$folder = $this->get_folder_object($folder_name);
$files = $folder->select(array(
array('type', '=', 'file'),
array('filename', '=', $file_name)
));
return array_shift($files);
}
/**
* Get folder object.
*
* @param string $folder_name Name of a folder with full path
*
* @return kolab_storage_folder Folder object
* @throws Exception
*/
protected function get_folder_object($folder_name)
{
if ($folder_name === null || $folder_name === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
if (empty($this->folders[$folder_name])) {
$storage = $this->rc->get_storage();
$separator = $storage->get_hierarchy_delimiter();
$folder_name = str_replace(file_api::PATH_SEPARATOR, $separator, $folder_name);
$imap_name = rcube_charset::convert($folder_name, RCUBE_CHARSET, 'UTF7-IMAP');
$folder = kolab_storage::get_folder($imap_name);
if (!$folder) {
throw new Exception("Storage error. Folder not found.", file_api::ERROR_CODE);
}
$this->folders[$folder_name] = $folder;
}
return $this->folders[$folder_name];
}
/**
* Simplify internal structure of the file object
*/
protected function from_file_object($file)
{
if (empty($file['_attachments'])) {
return $file;
}
$attachment = array_shift($file['_attachments']);
$file['name'] = $attachment['name'];
$file['size'] = $attachment['size'];
$file['type'] = $attachment['mimetype'];
$file['fileid'] = $attachment['id'];
unset($file['_attachments']);
return $file;
}
/**
* Convert to kolab_format internal structure of the file object
*/
protected function to_file_object($file)
{
// @TODO if path is empty and fileid exists it is an update
// get attachment body and save it in path
$file['_attachments'] = array(
0 => array(
'name' => $file['name'],
'path' => $file['path'],
'mimetype' => $file['type'],
'size' => $file['size'],
));
unset($file['name']);
unset($file['size']);
unset($file['type']);
unset($file['path']);
unset($file['fileid']);
return $file;
}
}
diff --git a/public_html/js/files_api.js b/public_html/js/files_api.js
index c922223..04ef7eb 100644
--- a/public_html/js/files_api.js
+++ b/public_html/js/files_api.js
@@ -1,386 +1,504 @@
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
function files_api()
{
var ref = this;
// default config
this.translations = {};
this.env = {
url: 'api/',
directory_separator: '/'
};
/*********************************************************/
/********* Basic utilities *********/
/*********************************************************/
// set environment variable(s)
this.set_env = function(p, value)
{
if (p != null && typeof p === 'object' && !value)
for (var n in p)
this.env[n] = p[n];
else
this.env[p] = value;
};
// add a localized label(s) to the client environment
this.tdef = function(p, value)
{
if (typeof p == 'string')
this.translations[p] = value;
else if (typeof p == 'object')
$.extend(this.translations, p);
};
// return a localized string
this.t = function(label)
{
if (this.translations[label])
return this.translations[label];
else
return label;
};
// print a message into browser console
this.log = function(msg)
{
if (window.console && console.log)
console.log(msg);
};
/********************************************************/
/********* Remote request methods *********/
/********************************************************/
// send a http POST request to the API service
this.post = function(action, postdata, func)
{
var url = this.env.url + action, ref = this;
if (!func) func = 'response';
this.set_request_time();
return $.ajax({
type: 'POST', url: url, data: postdata, dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: function(response) { ref[func](response); },
error: function(o, status, err) { ref.http_error(o, status, err); },
cache: false,
beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); }
});
};
// send a http GET request to the API service
this.get = function(action, data, func)
{
var url = this.env.url;
if (!func) func = 'response';
this.set_request_time();
data.method = action;
return $.ajax({
type: 'GET', url: url, data: data,
success: function(response) { ref[func](response); },
error: function(o, status, err) { ref.http_error(o, status, err); },
cache: false,
beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); }
});
};
// handle HTTP request errors
this.http_error = function(request, status, err)
{
var errmsg = request.statusText;
this.set_busy(false);
request.abort();
if (request.status && errmsg)
this.display_message(this.t('servererror') + ' (' + errmsg + ')', 'error');
};
this.response = function(response)
{
this.update_request_time();
this.set_busy(false);
return this.response_parse(response);
};
this.response_parse = function(response)
{
if (!response || response.status != 'OK') {
// Logout on invalid-session error
if (response && response.code == 403)
this.logout(response);
else
this.display_message(response && response.reason ? response.reason : this.t('servererror'), 'error');
return false;
}
return true;
};
/*********************************************************/
/********* Utilities *********/
/*********************************************************/
// Called on "session expired" session
this.logout = function(response) {};
// set state
this.set_busy = function(a, message) {};
// displays error message
this.display_message = function(label) {};
// called when a request timed out
this.request_timed_out = function() {};
// called on start of the request
this.set_request_time = function() {};
// called on request response
this.update_request_time = function() {};
/*********************************************************/
/********* Helpers *********/
/*********************************************************/
// compose a valid url with the given parameters
this.url = function(action, query)
{
var k, param = {},
querystring = typeof query === 'string' ? '&' + query : '';
if (typeof action !== 'string')
query = action;
else if (!query || typeof query !== 'object')
query = {};
// overwrite task name
if (action)
query.method = action;
// remove undefined values
for (k in query) {
if (query[k] !== undefined && query[k] !== null)
param[k] = query[k];
}
return '?' + $.param(param) + querystring;
};
// Folder list parser, converts it into structure
this.folder_list_parse = function(list)
{
var i, n, items, items_len, f, tmp, folder, num = 1,
len = list.length, folders = {};
for (i=0; i<len; i++) {
folder = list[i];
items = folder.split(this.env.directory_separator);
items_len = items.length;
for (n=0; n<items_len-1; n++) {
tmp = items.slice(0,n+1);
f = tmp.join(this.env.directory_separator);
if (!folders[f])
folders[f] = {name: tmp.pop(), depth: n, id: 'f'+num++, virtual: 1};
}
folders[folder] = {name: items.pop(), depth: items_len-1, id: 'f'+num++};
}
return folders;
};
// folder structure presentation (structure icons)
this.folder_list_tree = function(folders)
{
var i, n, diff, tree = [], folder;
for (i in folders) {
items = i.split(this.env.directory_separator);
items_len = items.length;
// skip root
if (items_len < 2) {
tree = [];
continue;
}
folders[i].tree = [1];
for (n=0; n<tree.length; n++) {
folder = tree[n];
diff = folders[folder].depth - (items_len - 1);
if (diff >= 0)
folders[folder].tree[diff] = folders[folder].tree[diff] ? folders[folder].tree[diff] + 2 : 2;
}
tree.push(i);
}
for (i in folders) {
if (tree = folders[i].tree) {
var html = '', divs = [];
for (n=0; n<folders[i].depth; n++) {
if (tree[n] > 2)
divs.push({'class': 'l3', width: 15});
else if (tree[n] > 1)
divs.push({'class': 'l2', width: 15});
else if (tree[n] > 0)
divs.push({'class': 'l1', width: 15});
// separator
else if (divs.length && !divs[divs.length-1]['class'])
divs[divs.length-1].width += 15;
else
divs.push({'class': null, width: 15});
}
for (n=divs.length-1; n>=0; n--) {
if (divs[n]['class'])
html += '<span class="tree '+divs[n]['class']+'" />';
else
html += '<span style="width:'+divs[n].width+'px" />';
}
if (html)
$('#' + folders[i].id + ' span.branch').html(html);
}
}
};
// convert content-type string into class name
this.file_type_class = function(type)
{
if (!type)
return '';
type = type.replace(/[^a-z0-9]/g, '_');
return type;
};
// convert bytes into number with size unit
this.file_size = function(size)
{
if (size >= 1073741824)
return parseFloat(size/1073741824).toFixed(2) + ' GB';
if (size >= 1048576)
return parseFloat(size/1048576).toFixed(2) + ' MB';
if (size >= 1024)
return parseInt(size/1024) + ' kB';
return parseInt(size || 0)+ ' B';
};
// Extract file name from full path
this.file_name = function(path)
{
var path = path.split(this.env.directory_separator);
return path.pop();
};
// Extract file path from full path
this.file_path = function(path)
{
var path = path.split(this.env.directory_separator);
path.pop();
return path.join(this.env.directory_separator);
};
// compare two sortable objects
this.sort_compare = function(data1, data2)
{
- data1 = data1[this.env.sort_col || 'name'];
- data2 = data2[this.env.sort_col || 'name'];
+ var key = this.env.sort_col || 'name';
- if (this.env.sort_col == 'size')
+ if (key == 'mtime')
+ key = 'modified';
+
+ data1 = data1[key];
+ data2 = data2[key];
+
+ if (key == 'size' || key == 'modified')
// numeric comparison
return this.env.sort_reverse ? data1 < data2 : data1 > data2;
else {
// use Array.sort() for sting comparison
var arr = [data1, data2];
arr.sort(function (a, b) {
// @TODO: use localeCompare() arguments for better results
return a.localeCompare(b);
});
if (this.env.sort_reverse)
arr.reverse();
return arr[0] === data2;
}
};
+ // Checks if specified mimetype is supported natively by the browser
+ // (or we implement it) and can be displayed in the browser
+ this.file_type_supported = function(type)
+ {
+ var i, t, img = 'jpg|jpeg|gif|bmp|png',
+ regexps = [
+ /^text\/(?!(pdf|x-pdf))/i,
+ /^message\/rfc822/i,
+ ];
+
+ if (this.env.browser_capabilities.tif)
+ img += '|tiff';
+
+ regexps.push(new RegExp('^image/(' + img + ')$', 'i'));
+
+ if (this.env.browser_capabilities.pdf) {
+ regexps.push(/^application\/(pdf|x-pdf|acrobat|vnd.pdf)/i);
+ regexps.push(/^text\/(pdf|x-pdf)/i);
+ }
+
+ if (this.env.browser_capabilities.flash)
+ regexps.push(/^application\/x-shockwave-flash/i);
+
+ for (i in regexps)
+ if (regexps[i].test(type))
+ return true;
+
+ for (i in navigator.mimeTypes) {
+ t = navigator.mimeTypes[i].type;
+ if (t == type)
+ return true;
+ }
+ };
+
+ // Checks browser capabilities eg. PDF support, TIF support
+ this.browser_capabilities_check = function()
+ {
+ if (!this.env.browser_capabilities)
+ this.env.browser_capabilities = {};
+
+ if (this.env.browser_capabilities.pdf === undefined)
+ this.env.browser_capabilities.pdf = this.pdf_support_check();
+
+ if (this.env.browser_capabilities.flash === undefined)
+ this.env.browser_capabilities.flash = this.flash_support_check();
+
+ if (this.env.browser_capabilities.tif === undefined)
+ this.tif_support_check();
+ };
+
+ this.tif_support_check = function()
+ {
+ var img = new Image();
+
+ img.onload = function() { ui.env.browser_capabilities.tif = 1; };
+ img.onerror = function() { ui.env.browser_capabilities.tif = 0; };
+ img.src = 'resources/blank.tif';
+ };
+
+ this.pdf_support_check = function()
+ {
+ var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {},
+ plugins = navigator.plugins,
+ len = plugins.length,
+ regex = /Adobe Reader|PDF|Acrobat/i;
+
+ if (plugin && plugin.enabledPlugin)
+ return 1;
+
+ if (window.ActiveXObject) {
+ try {
+ if (axObj = new ActiveXObject("AcroPDF.PDF"))
+ return 1;
+ }
+ catch (e) {}
+ try {
+ if (axObj = new ActiveXObject("PDF.PdfCtrl"))
+ return 1;
+ }
+ catch (e) {}
+ }
+
+ for (i=0; i<len; i++) {
+ plugin = plugins[i];
+ if (typeof plugin === 'String') {
+ if (regex.test(plugin))
+ return 1;
+ }
+ else if (plugin.name && regex.test(plugin.name))
+ return 1;
+ }
+
+ return 0;
+ };
+
+ this.flash_support_check = function()
+ {
+ var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/x-shockwave-flash"] : {};
+
+ if (plugin && plugin.enabledPlugin)
+ return 1;
+
+ if (window.ActiveXObject) {
+ try {
+ if (axObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))
+ return 1;
+ }
+ catch (e) {}
+ }
+
+ return 0;
+ };
+
};
// Add escape() method to RegExp object
// http://dev.rubyonrails.org/changeset/7271
RegExp.escape = function(str)
{
return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
// make a string URL safe (and compatible with PHP's rawurlencode())
function urlencode(str)
{
if (window.encodeURIComponent)
return encodeURIComponent(str).replace('*', '%2A');
return escape(str)
.replace('+', '%2B')
.replace('*', '%2A')
.replace('/', '%2F')
.replace('@', '%40');
};
function escapeHTML(str)
{
return str === undefined ? '' : String(str)
.replace(/&/g, '&')
.replace(/>/g, '>')
.replace(/</g, '<');
};
function object_is_empty(obj)
{
if (obj)
for (var i in obj)
if (i !== null)
return true;
return false;
}
diff --git a/public_html/js/files_ui.js b/public_html/js/files_ui.js
index abc2621..1597f8f 100644
--- a/public_html/js/files_ui.js
+++ b/public_html/js/files_ui.js
@@ -1,1364 +1,1251 @@
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
function files_ui()
{
var ref = this;
this.request_timeout = 300;
this.message_time = 3000;
this.events = {};
this.commands = {};
this.env = {
url: 'api/',
sort_col: 'name',
sort_reverse: 0,
search_threads: 1,
directory_separator: '/'
};
// set jQuery ajax options
$.ajaxSetup({
cache: false,
error: function(request, status, err) { ref.http_error(request, status, err); },
beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); }
});
/*********************************************************/
/********* basic utilities *********/
/*********************************************************/
// initialize interface
this.init = function()
{
if (!this.env.token)
return;
if (this.env.task == 'main') {
this.enable_command('folder.list', 'folder.create', 'file.search', true);
this.command('folder.list');
}
else if (this.env.task == 'file') {
this.load_file('#file-content', this.env.filedata);
this.enable_command('file.delete', 'file.download', true);
}
this.browser_capabilities_check();
};
// set environment variable(s)
this.set_env = function(p, value)
{
if (p != null && typeof p === 'object' && !value)
for (var n in p)
this.env[n] = p[n];
else
this.env[p] = value;
};
// execute a specific command on the web client
this.command = function(command, props, obj)
{
if (obj && obj.blur)
obj.blur();
if (this.busy)
return false;
if (!this.commands[command])
return;
var ret = undefined,
func = command.replace(/[^a-z]/g, '_'),
task = command.replace(/\.[a-z-_]+$/g, '');
if (this[func] && typeof this[func] === 'function') {
ret = this[func](props);
}
return ret === false ? false : obj ? false : true;
};
this.set_busy = function(a, message)
{
if (a && this.busy)
return;
if (a && message) {
var msg = this.t(message);
if (msg == message)
msg = 'Loading...';
this.display_message(msg, 'loading');
}
else if (!a) {
this.hide_message('loading');
}
this.busy = a;
// if (this.gui_objects.editform)
// this.lock_form(this.gui_objects.editform, a);
// clear pending timer
if (this.request_timer)
clearTimeout(this.request_timer);
// set timer for requests
if (a && this.request_timeout)
this.request_timer = window.setTimeout(function() { ref.request_timed_out(); }, this.request_timeout * 1000);
};
// called when a request timed out
this.request_timed_out = function()
{
this.set_busy(false);
this.display_message('Request timed out!', 'error');
};
// Add variable to GET string, replace old value if exists
this.add_url = function(url, name, value)
{
value = urlencode(value);
if (/(\?.*)$/.test(url)) {
var urldata = RegExp.$1,
datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
if (datax.test(urldata))
urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
else
urldata += '&' + name + '=' + value
return url.replace(/(\?.*)$/, urldata);
}
return url + '?' + name + '=' + value;
};
this.trigger_event = function(event, data)
{
if (this.events[event])
for (var i in this.events[event])
this.events[event][i](data);
};
this.add_event_listener = function(event, func)
{
if (!this.events[event])
this.events[event] = [];
this.events[event].push(func);
};
this.buttons = function(p)
{
$.each(p, function(i, v) {
if (!ui.buttons[i])
ui.buttons[i] = [];
if (typeof v == 'object')
ui.buttons[i] = $.merge(ui.buttons[i], v);
else
ui.buttons[i].push(v);
});
};
this.enable_command = function()
{
var i, n, args = Array.prototype.slice.call(arguments),
enable = args.pop(), cmd;
for (n=0; n<args.length; n++) {
cmd = args[n];
// argument of type array
if (typeof cmd === 'string') {
this.commands[cmd] = enable;
if (this.buttons[cmd])
$.each(this.buttons[cmd], function (i, button) {
$('#'+button)[enable ? 'removeClass' : 'addClass']('disabled');
});
}
// push array elements into commands array
else {
for (i in cmd)
args.push(cmd[i]);
}
}
};
/*********************************************************/
/********* GUI functionality *********/
/*********************************************************/
// write to the document/window title
this.set_pagetitle = function(title)
{
if (title && document.title)
document.title = title;
};
// display a system message (types: loading, notice, error)
this.display_message = function(msg, type, timeout)
{
var obj, ref = this;
if (!type)
type = 'notice';
if (msg)
msg = this.t(msg);
if (type == 'loading') {
timeout = this.request_timeout * 1000;
if (!msg)
msg = this.t('loading');
}
else if (!timeout)
timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1);
obj = $('<div>');
if (type != 'loading') {
msg = '<div><span>' + msg + '</span></div>';
obj.addClass(type).click(function() { return ref.hide_message(); });
}
if (timeout > 0)
window.setTimeout(function() { ref.hide_message(type, type != 'loading'); }, timeout);
obj.attr('id', type == 'loading' ? 'loading' : 'message')
.appendTo('body').html(msg).show();
};
// make a message to disapear
this.hide_message = function(type, fade)
{
if (type == 'loading')
$('#loading').remove();
else
$('#message').fadeOut('normal', function() { $(this).remove(); });
};
this.set_watermark = function(id)
{
if (this.env.watermark)
$('#'+id).html(this.env.watermark);
};
/********************************************************/
/********* Remote request methods *********/
/********************************************************/
// send a http POST request to the server
this.http_post = function(action, postdata)
{
var url = this.url(action);
if (postdata && typeof postdata === 'object')
postdata.remote = 1;
else {
if (!postdata)
postdata = '';
postdata += '&remote=1';
}
this.set_request_time();
return $.ajax({
type: 'POST', url: url, data: postdata, dataType: 'json',
success: function(response) { ui.http_response(response); },
error: function(o, status, err) { ui.http_error(o, status, err); }
});
};
// handle HTTP response
this.http_response = function(response)
{
var i;
if (!response)
return;
// set env vars
if (response.env)
this.set_env(response.env);
// we have translation labels to add
if (typeof response.labels === 'object')
this.tdef(response.labels);
// HTML page elements
if (response.objects)
for (i in response.objects)
$('#'+i).html(response.objects[i]);
this.update_request_time();
this.set_busy(false);
// if we get javascript code from server -> execute it
if (response.exec)
eval(response.exec);
this.trigger_event('http-response', response);
};
// handle HTTP request errors
this.http_error = function(request, status, err)
{
var errmsg = request.statusText;
this.set_busy(false);
request.abort();
if (request.status && errmsg)
this.display_message(this.t('servererror') + ' (' + errmsg + ')', 'error');
};
/********************************************************/
/********* Helper methods *********/
/********************************************************/
// disable/enable all fields of a form
this.lock_form = function(form, lock)
{
if (!form || !form.elements)
return;
var n, len, elm;
if (lock)
this.disabled_form_elements = [];
for (n=0, len=form.elements.length; n<len; n++) {
elm = form.elements[n];
if (elm.type == 'hidden')
continue;
// remember which elem was disabled before lock
if (lock && elm.disabled)
this.disabled_form_elements.push(elm);
// check this.disabled_form_elements before inArray() as a workaround for FF5 bug
// http://bugs.jquery.com/ticket/9873
else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0))
elm.disabled = lock;
}
};
this.set_request_time = function()
{
this.env.request_time = (new Date()).getTime();
};
// Update request time element
this.update_request_time = function()
{
if (this.env.request_time) {
var t = ((new Date()).getTime() - this.env.request_time)/1000,
el = $('#reqtime');
el.text(el.text().replace(/[0-9.,]+/, t));
}
};
// position and display popup
this.popup_show = function(e, popup)
{
var popup = $(popup),
pos = this.mouse_pos(e),
win = $(window),
w = popup.width(),
h = popup.height(),
left = pos.left - w,
top = pos.top;
if (top + h > win.height())
top -= h;
if (left + w > win.width())
left -= w;
popup.css({left: left + 'px', top: top + 'px'})
.click(function(e) { e.stopPropagation(); $(this).hide(); }).show();
e.stopPropagation();
};
// Return absolute mouse position of an event
this.mouse_pos = function(e)
{
if (!e) e = window.event;
var mX = (e.pageX) ? e.pageX : e.clientX,
mY = (e.pageY) ? e.pageY : e.clientY;
if (document.body && document.all) {
mX += document.body.scrollLeft;
mY += document.body.scrollTop;
}
if (e._offset) {
mX += e._offset.left;
mY += e._offset.top;
}
return { left:mX, top:mY };
};
this.serialize_form = function(id)
{
var i, v, json = {},
form = $(id),
query = form.serializeArray();
for (i in query)
json[query[i].name] = query[i].value;
// serializeArray() doesn't work properly for multi-select
$('select[multiple="multiple"]', form).each(function() {
var name = this.name;
json[name] = [];
$(':selected', this).each(function() {
json[name].push(this.value);
});
});
return json;
};
/*********************************************************/
/********* Commands *********/
/*********************************************************/
this.logout = function()
{
this.main_logout();
};
this.main_logout = function(params)
{
location.href = '?task=main&action=logout' + (params ? '&' + $.param(params) : '');
return false;
};
// folder list request
this.folder_list = function()
{
this.set_busy(true, 'loading');
this.get('folder_list', {}, 'folder_list_response');
};
// folder list response handler
this.folder_list_response = function(response)
{
if (!this.response(response))
return;
var elem = $('#folderlist'), table = $('table', elem);
this.env.folders = this.folder_list_parse(response.result);
table.empty();
$.each(this.env.folders, function(i, f) {
var row = $('<tr><td><span class="branch"></span><span class="name"></span></td></tr>'),
span = $('span.name', row);
span.text(f.name);
row.attr('id', f.id);
if (f.depth)
$('span.branch', row).width(15 * f.depth);
if (f.virtual)
row.addClass('virtual');
else
span.click(function() { ui.folder_select(i); });
if (i == ui.env.folder)
row.addClass('selected');
table.append(row);
});
// add virtual collections
$.each(['audio', 'video', 'image', 'document'], function(i, n) {
var row = $('<tr><td><span class="name"></span></td></tr>'),
span = $('span.name', row);
row.attr('id', 'folder-collection-' + n);
span.text(ui.t('collection.' + n))
.click(function() { ui.folder_select(n, true); });
if (n == ui.env.collection)
row.addClass('selected');
table.append(row);
});
// add tree icons
this.folder_list_tree(this.env.folders);
};
this.folder_select = function(folder, is_collection)
{
this.env.search = null;
this.file_search_stop();
var list = $('#folderlist');
$('tr.selected', list).removeClass('selected');
if (is_collection) {
var found = $('#folder-collection-' + folder, list).addClass('selected');
this.env.folder = null;
this.enable_command('file.list', true);
this.enable_command('folder.delete', 'folder.edit', 'file.upload', false);
this.command('file.list', {collection: folder});
}
else {
var found = $('#' + this.env.folders[folder].id, list).addClass('selected');
this.env.collection = null;
this.enable_command('file.list', 'folder.delete', 'folder.edit', 'file.upload', found.length);
this.command('file.list', {folder: folder});
}
};
this.folder_unselect = function()
{
this.env.search = null;
this.env.folder = null;
this.env.collection = null;
this.file_search_stop();
var list = $('#folderlist');
$('tr.selected', list).removeClass('selected');
this.enable_command('file.list', 'folder.delete', 'folder.edit', 'file.upload', false);
};
// folder create request
this.folder_create = function(folder)
{
if (!folder) {
this.folder_create_start();
return;
}
this.set_busy(true, 'saving');
this.get('folder_create', {folder: folder}, 'folder_create_response');
};
// folder create response handler
this.folder_create_response = function(response)
{
if (!this.response(response))
return;
this.folder_list();
};
// folder edit (rename) request
this.folder_edit = function(folder)
{
if (!folder) {
this.folder_edit_start();
return;
}
this.set_busy(true, 'saving');
this.get('folder_rename', {folder: folder.folder, 'new': folder['new']}, 'folder_rename_response');
};
// folder rename response handler
this.folder_rename_response = function(response)
{
if (!this.response(response))
return;
this.env.folder = this.env.folder_rename;
this.folder_list();
};
// folder delete request
this.folder_delete = function(folder)
{
if (folder === undefined)
folder = this.env.folder;
if (!folder)
return;
// @todo: confirm
this.set_busy(true, 'saving');
this.get('folder_delete', {folder: folder}, 'folder_delete_response');
};
// folder delete response handler
this.folder_delete_response = function(response)
{
if (!this.response(response))
return;
this.env.folder = null;
$('#filelist tbody').empty();
this.enable_command('folder.delete', 'folder.edit', 'file.list', 'file.search', 'file.upload', false);
this.folder_list();
};
// file list request
this.file_list = function(params)
{
if (!params)
params = {};
if (params.all_folders) {
params.collection = null;
params.folder = null;
this.folder_unselect();
}
if (params.collection == undefined)
params.collection = this.env.collection;
if (params.folder == undefined)
params.folder = this.env.folder;
if (params.sort == undefined)
params.sort = this.env.sort_col;
if (params.reverse == undefined)
params.reverse = this.env.sort_reverse;
if (params.search == undefined)
params.search = this.env.search;
this.env.collection = params.collection;
this.env.folder = params.folder;
this.env.sort_col = params.sort;
this.env.sort_reverse = params.reverse;
// empty the list
$('#filelist tbody').empty();
this.env.file_list = [];
this.env.list_shift_start = null;
this.enable_command('file.open', 'file.get', 'file.rename', 'file.delete', false);
// request
if (params.collection || params.all_folders)
this.file_list_loop(params);
else {
this.set_busy(true, 'loading');
this.get('file_list', params, 'file_list_response');
}
};
// call file.list request for every folder (used for search and virt. collections)
this.file_list_loop = function(params)
{
var i, folders = [], limit = Math.max(this.env.search_threads || 1, 1);
if (params.collection) {
if (!params.search)
params.search = {};
params.search['class'] = params.collection;
delete params['collection'];
}
delete params['all_folders'];
$.each(this.env.folders, function(i, f) {
if (!f.virtual)
folders.push(i);
});
this.env.folders_loop = folders;
this.env.folders_loop_params = params;
this.env.folders_loop_lock = false;
for (i=0; i<folders.length && i<limit; i++) {
this.set_busy(true, 'loading');
params.folder = folders.shift();
this.get('file_list', params, 'file_list_loop_response');
}
};
// file list response handler
this.file_list_response = function(response)
{
if (!this.response(response))
return;
var table = $('#filelist'), list = [];
$.each(response.result, function(key, data) {
var row = ui.file_list_row(key, data);
table.append(row);
data.row = row;
list.push(data);
});
this.env.file_list = list;
};
// file list response handler for loop'ed request
this.file_list_loop_response = function(response)
{
var i, folders = this.env.folders_loop,
params = this.env.folders_loop_params,
limit = Math.max(this.env.search_threads || 1, 1),
valid = this.response(response);
for (i=0; i<folders.length && i<limit; i++) {
this.set_busy(true, 'loading');
params.folder = folders.shift();
this.get('file_list', params, 'file_list_loop_response');
}
if (!valid)
return;
this.file_list_loop_result_add(response.result);
};
// add files from list request to the table (with sorting)
this.file_list_loop_result_add = function(result)
{
// chack if result (hash-array) is empty
if (!object_is_empty(result))
return;
if (this.env.folders_loop_lock) {
setTimeout(function() { ui.file_list_loop_result_add(result); }, 100);
return;
}
// lock table, other list responses will wait
this.env.folders_loop_lock = true;
var n, i, len, elem, list = [],
table = $('#filelist'), rows = $('tbody tr', table);
for (n=0, len=this.env.file_list.length; n<len; n++) {
elem = this.env.file_list[n];
for (i in result) {
if (!this.sort_compare(elem, result[i]))
break;
var row = ui.file_list_row(i, result[i]);
elem.row.before(row);
result[i].row = row;
list.push(result[i]);
delete result[i];
}
list.push(elem);
}
// add the rest of rows
$.each(result, function(key, data) {
var row = ui.file_list_row(key, data);
table.append(row);
result[key].row = row;
list.push(result[key]);
});
this.env.file_list = list;
this.env.folders_loop_lock = false;
};
// sort files list (without API request)
this.file_list_sort = function(col, reverse)
{
var n, len, list = this.env.file_list,
table = $('#filelist'), tbody = $('<tbody>');
this.env.sort_col = col;
this.env.sort_reverse = reverse;
- if (!list)
+ if (!list || !list.length)
return;
// sort the list
list.sort(function (a, b) {
return ui.sort_compare(a, b);
});
// add rows to the new body
for (n=0, len=list.length; n<len; n++) {
tbody.append(list[n].row);
}
// replace table bodies
$('tbody', table).replaceWith(tbody);
};
this.file_list_row = function(filename, data)
{
var row = $('<tr><td class="filename"></td>'
+' <td class="filemtime"></td><td class="filesize"></td></tr>'),
link = $('<span></span>').text(data.name).click(function(e) { ui.file_menu(e, filename, data.type); });
$('td.filename', row).addClass(ui.file_type_class(data.type)).append(link);
$('td.filemtime', row).text(data.mtime);
$('td.filesize', row).text(ui.file_size(data.size));
row.attr('data-file', filename)
.click(function(e) { ui.file_list_click(e, this); });
// disables selection in IE
if (document.all)
row.on('selectstart', function() { return false; });
return row;
};
this.file_list_click = function(e, row)
{
var list = $('#filelist'), org = row, row = $(row),
found, selected, shift = this.env.list_shift_start;
if (e.shiftKey && shift && org != shift) {
$('tr', list).each(function(i, r) {
if (r == org) {
found = 1;
$(r).addClass('selected');
return;
}
else if (!selected && r == shift) {
selected = 1;
return;
}
if ((!found && selected) || (found && !selected))
$(r).addClass('selected');
else
$(r).removeClass('selected');
});
}
else if (e.ctrlKey)
row.toggleClass('selected');
else {
$('tr.selected', list).removeClass('selected');
$(row).addClass('selected');
this.env.list_shift_start = org;
}
selected = $('tr.selected', list).length;
if (!selected)
this.env.list_shift_start = null;
this.enable_command('file.delete', selected);
this.enable_command('file.open', 'file.get', 'file.rename', selected == 1);
};
// file delete request
this.file_delete = function(file)
{
if (!file) {
file = [];
if (this.env.file)
file.push(this.env.file);
else
$('#filelist tr.selected').each(function() {
file.push($(this).data('file'));
});
}
this.set_busy(true, 'deleting');
this.get('file_delete', {file: file}, 'file_delete_response');
};
// file delete response handler
this.file_delete_response = function(response)
{
if (!this.response(response))
return;
if (this.env.file) {
// @TODO: reload list if on the same folder only
if (window.opener && window.opener.ui)
window.opener.ui.file_list();
window.close();
}
else
this.file_list();
};
// file rename request
this.file_move = function(file, newname)
{
if (file === newname)
return;
this.set_busy(true, 'saving');
this.get('file_move', {file: file, 'new': newname}, 'file_move_response');
};
// file delete response handler
this.file_move_response = function(response)
{
if (!this.response(response))
return;
this.file_list();
};
this.file_download = function(file)
{
if (!file)
file = this.env.file;
location.href = this.env.url + this.url('file_get', {token: this.env.token, file: file, 'force-download': 1});
};
this.file_menu = function(e, file, type)
{
var menu = $('#file-menu'),
open_action = $('li.file-open > a', menu);
if (this.file_type_supported(type))
open_action.attr({target: '_blank', href: '?' + $.param({task: 'file', action: 'open', token: this.env.token, file: file})})
.removeClass('disabled').off('click');
else
open_action.click(function() { return false; }).addClass('disabled');
$('li.file-download > a', menu)
.attr({href: this.env.url + this.url('file_get', {token: this.env.token, file: file, 'force-download': 1})});
$('li.file-delete > a', menu).off('click').click(function() { ui.file_delete(file); });
$('li.file-rename > a', menu).off('click').click(function() { ui.file_rename_start(e); });
this.popup_show(e, menu);
};
this.file_rename_start = function(e)
{
var list = $('#filelist'),
tr = $(e.target).parents('tr'),
td = $('td.filename', tr),
file = tr.data('file'),
name = this.file_name(file),
input = $('<input>').attr({type: 'text', name: 'filename', 'class': 'filerename'})
.val(name).data('file', file)
.click(function(e) { e.stopPropagation(); })
.keydown(function(e) {
switch (e.which) {
case 27: // ESC
ui.file_rename_stop();
break;
case 13: // Enter
var elem = $(this),
newname = elem.val(),
oldname = elem.data('file'),
path = ui.file_path(file);
ui.file_move(oldname, path + ui.env.directory_separator + newname);
elem.parent().text(newname);
break;
}
});
$('span', td).text('').append(input);
input.focus();
};
this.file_rename_stop = function()
{
$('input.filerename').each(function() {
var elem = $(this), name = ui.file_name(elem.data('file'));
elem.parent().text(name);
});
};
// file upload request
this.file_upload = function()
{
var form = $('#uploadform'),
field = $('input[type=file]', form).get(0),
files = field.files ? field.files.length : field.value ? 1 : 0;
if (files) {
// submit form and read server response
this.file_upload_form(form, 'file_create', function(e) {
var doc, response;
try {
doc = this.contentDocument ? this.contentDocument : this.contentWindow.document;
response = doc.body.innerHTML;
// in Opera onload is called twice, once with empty body
if (!response)
return;
// response may be wrapped in <pre> tag
if (response.match(/^<pre[^>]*>(.*)<\/pre>$/i)) {
response = RegExp.$1;
}
response = eval('(' + response + ')');
} catch (err) {
response = {status: 'ERROR'};
}
if (ui.response_parse(response))
ui.file_list();
});
}
};
// post the given form to a hidden iframe
this.file_upload_form = function(form, action, onload)
{
var ts = new Date().getTime(),
frame_name = 'fileupload'+ts;
/*
// upload progress support
if (this.env.upload_progress_name) {
var fname = this.env.upload_progress_name,
field = $('input[name='+fname+']', form);
if (!field.length) {
field = $('<input>').attr({type: 'hidden', name: fname});
field.prependTo(form);
}
field.val(ts);
}
*/
// have to do it this way for IE
// otherwise the form will be posted to a new window
if (document.all) {
var html = '<iframe id="'+frame_name+'" name="'+frame_name+'"'
+ ' src="program/resources/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
document.body.insertAdjacentHTML('BeforeEnd', html);
}
// for standards-compliant browsers
else
$('<iframe>')
.attr({name: frame_name, id: frame_name})
.css({border: 'none', width: 0, height: 0, visibility: 'hidden'})
.appendTo(document.body);
// handle upload errors, parsing iframe content in onload
$('#'+frame_name).bind('load', {ts:ts}, onload);
$(form).attr({
target: frame_name,
action: this.env.url + this.url(action, {folder: this.env.folder, token: this.env.token, uploadid:ts}),
method: 'POST'
}).attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
.submit();
};
// Display file search form
this.file_search = function()
{
var form = this.form_show('file-search'),
has_folder = this.env.folder || this.env.collection,
radio1 = $('input[name="all_folders"][value="0"]', form);
$('input[name="name"]', form).val('').focus();
if (has_folder)
radio1.prop('disabled', false).click();
else {
radio1.prop('disabled', true);
$('input[name="all_folders"][value="1"]', form).click();
}
};
// Hide file search form
this.file_search_stop = function()
{
if (this.env.search)
this.file_list(null, {search: null});
this.form_hide('file-search');
this.env.search = null;
};
// Execute file search
this.file_search_submit = function()
{
var form = this.form_show('file-search'),
value = $('input[name="name"]', form).val(),
all = $('input[name="all_folders"]:checked', form).val();
if (value) {
this.env.search = {name: value};
this.file_list({search: this.env.search, all_folders: all == 1});
}
else
this.file_search_stop();
};
// Display folder creation form
this.folder_create_start = function()
{
var form = this.form_show('folder-create');
$('input[name="name"]', form).val('').focus();
$('input[name="parent"]', form).prop('checked', this.env.folder)
.prop('disabled', !this.env.folder);
};
// Hide folder creation form
this.folder_create_stop = function()
{
this.form_hide('folder-create');
};
// Submit folder creation form
this.folder_create_submit = function()
{
var folder = '', data = this.serialize_form('#folder-create-form');
if (!data.name)
return;
if (data.parent && this.env.folder)
folder = this.env.folder + this.env.directory_separator;
folder += data.name;
this.folder_create_stop();
this.command('folder.create', folder);
};
// Display folder edit form
this.folder_edit_start = function()
{
var form = this.form_show('folder-edit'),
arr = this.env.folder.split(this.env.directory_separator),
name = arr.pop();
this.env.folder_edit_path = arr.join(this.env.directory_separator);
$('input[name="name"]', form).val(name).focus();
};
// Hide folder edit form
this.folder_edit_stop = function()
{
this.form_hide('folder-edit');
};
// Submit folder edit form
this.folder_edit_submit = function()
{
var folder = '', data = this.serialize_form('#folder-edit-form');
if (!data.name)
return;
if (this.env.folder_edit_path)
folder = this.env.folder_edit_path + this.env.directory_separator;
folder += data.name;
this.env.folder_rename = folder;
this.folder_edit_stop();
this.command('folder.edit', {folder: this.env.folder, 'new': folder});
};
/*********************************************************/
/********* Utilities *********/
/*********************************************************/
// Display folder creation form
this.form_show = function(name)
{
var form = $('#' + name + '-form');
$('#forms > form').hide();
form.show();
$('#taskcontent').css('top', form.height() + 20);
return form;
};
// Display folder creation form
this.form_hide = function(name)
{
var form = $('#' + name + '-form');
form.hide();
$('#taskcontent').css('top', 10);
};
- // Checks if specified mimetype is supported natively by the browser
- // (or we implement it) and can be displayed in the browser
- this.file_type_supported = function(type)
- {
- var i, t, img = 'jpg|jpeg|gif|bmp|png',
- regexps = [
- /^text\/(?!(pdf|x-pdf))/i,
- /^message\/rfc822/i,
- ];
-
- if (this.env.browser_capabilities.tif)
- img += '|tiff';
-
- regexps.push(new RegExp('^image/(' + img + ')$', 'i'));
-
- if (this.env.browser_capabilities.pdf) {
- regexps.push(/^application\/(pdf|x-pdf|acrobat|vnd.pdf)/i);
- regexps.push(/^text\/(pdf|x-pdf)/i);
- }
-
- if (this.env.browser_capabilities.flash)
- regexps.push(/^application\/x-shockwave-flash/i);
-
- for (i in regexps)
- if (regexps[i].test(type))
- return true;
-
- for (i in navigator.mimeTypes) {
- t = navigator.mimeTypes[i].type;
- if (t == type)
- return true;
- }
- };
-
- // Checks browser capabilities eg. PDF support, TIF support
- this.browser_capabilities_check = function()
- {
- if (!this.env.browser_capabilities)
- this.env.browser_capabilities = {};
-
- if (this.env.browser_capabilities.pdf === undefined)
- this.env.browser_capabilities.pdf = this.pdf_support_check();
-
- if (this.env.browser_capabilities.flash === undefined)
- this.env.browser_capabilities.flash = this.flash_support_check();
-
- if (this.env.browser_capabilities.tif === undefined)
- this.tif_support_check();
- };
-
- this.tif_support_check = function()
- {
- var img = new Image();
-
- img.onload = function() { ui.env.browser_capabilities.tif = 1; };
- img.onerror = function() { ui.env.browser_capabilities.tif = 0; };
- img.src = 'resources/blank.tif';
- };
-
- this.pdf_support_check = function()
- {
- var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {},
- plugins = navigator.plugins,
- len = plugins.length,
- regex = /Adobe Reader|PDF|Acrobat/i;
-
- if (plugin && plugin.enabledPlugin)
- return 1;
-
- if (window.ActiveXObject) {
- try {
- if (axObj = new ActiveXObject("AcroPDF.PDF"))
- return 1;
- }
- catch (e) {}
- try {
- if (axObj = new ActiveXObject("PDF.PdfCtrl"))
- return 1;
- }
- catch (e) {}
- }
-
- for (i=0; i<len; i++) {
- plugin = plugins[i];
- if (typeof plugin === 'String') {
- if (regex.test(plugin))
- return 1;
- }
- else if (plugin.name && regex.test(plugin.name))
- return 1;
- }
-
- return 0;
- };
-
- this.flash_support_check = function()
- {
- var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/x-shockwave-flash"] : {};
-
- if (plugin && plugin.enabledPlugin)
- return 1;
-
- if (window.ActiveXObject) {
- try {
- if (axObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))
- return 1;
- }
- catch (e) {}
- }
-
- return 0;
- };
-
// loads a file content into an iframe (with loading image)
this.load_file = function(content, filedata)
{
var href = filedata.href, iframe = $(content),
div = iframe.parent(),
loader = $('#loader'),
offset = div.offset(),
w = loader.width(), h = loader.height(),
width = div.width(), height = div.height();
loader.css({
top: offset.top + height/2 - h/2 - 20,
left: offset.left + width/2 - w/2
}).show();
iframe.css('opacity', 0.1)
.load(function() { ui.loader_hide(this); })
.attr('src', href);
// some content, e.g. movies or flash doesn't execute onload on iframe
// let's wait some time and check document ready state
if (!/^text/i.test(filedata.mimetype))
setTimeout(function() {
$(iframe.get(0).contentWindow.document).ready(function() {
parent.ui.loader_hide(content);
});
}, 1000);
};
// hide content loader element, show content element
this.loader_hide = function(content)
{
$('#loader').hide();
$(content).css('opacity', 1);
};
};
// Initialize application object (don't change var name!)
var ui = $.extend(new files_api(), new files_ui());
// general click handler
$(document).click(function() {
$('.popup').hide();
ui.file_rename_stop();
}).ready(function() {
ui.init();
});
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jul 9, 10:43 PM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1057252
Default Alt Text
(78 KB)
Attached To
Mode
R26 chwala
Attached
Detach File
Event Timeline
Log In to Comment