Page MenuHomePhorge

No OneTemporary

Size
84 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/client/file_ui_client_main.php b/lib/client/file_ui_client_main.php
index a1b7baa..5da165c 100644
--- a/lib/client/file_ui_client_main.php
+++ b/lib/client/file_ui_client_main.php
@@ -1,169 +1,170 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2011-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 file_ui_client_main extends file_ui
{
public function action_default()
{
// assign default set of translations
$this->output->add_translation('saving', 'deleting', 'search', 'search.loading',
'collection.audio', 'collection.video', 'collection.image', 'collection.document',
'moving', 'copying', 'file.skip', 'file.skipall', 'file.overwrite', 'file.overwriteall',
- 'file.moveconfirm', 'file.progress'
+ 'file.moveconfirm', 'file.progress', 'upload.size', 'upload.progress',
+ 'upload.eta', 'upload.rate'
);
$result = $this->api_get('mimetypes');
$this->output->set_env('search_threads', $this->config->get('files_search_threads'));
$this->output->set_env('supported_mimetypes', $result->get());
$this->ui_init();
}
public function folder_create_form()
{
$input_name = new html_inputfield(array(
'type' => 'text',
'name' => 'name',
'value' => '',
));
$input_parent = new html_checkbox(array(
'name' => 'parent',
'value' => '1',
'id' => 'folder-parent-checkbox',
));
$submit = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.folder_create_submit()',
'value' => $this->translate('form.submit'),
));
$cancel = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.folder_create_stop()',
'value' => $this->translate('form.cancel'),
));
$table = new html_table;
$table->add(null, $input_name->show() . $input_parent->show()
. html::label('folder-parent-checkbox', $this->translate('folder.under')));
$table->add('buttons', $submit->show() . $cancel->show());
$content = html::tag('fieldset', null,
html::tag('legend', null,
$this->translate('folder.createtitle')) . $table->show());
$form = html::tag('form', array(
'id' => 'folder-create-form',
'onsubmit' => 'ui.folder_create_submit(); return false'),
$content);
return $form;
}
public function folder_edit_form()
{
$input_name = new html_inputfield(array(
'type' => 'text',
'name' => 'name',
'value' => '',
));
$submit = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.folder_edit_submit()',
'value' => $this->translate('form.submit'),
));
$cancel = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.folder_edit_stop()',
'value' => $this->translate('form.cancel'),
));
$table = new html_table;
$table->add(null, $input_name->show());
$table->add('buttons', $submit->show() . $cancel->show());
$content = html::tag('fieldset', null,
html::tag('legend', null,
$this->translate('folder.edittitle')) . $table->show());
$form = html::tag('form', array(
'id' => 'folder-edit-form',
'onsubmit' => 'ui.folder_edit_submit(); return false'),
$content);
return $form;
}
public function file_search_form()
{
$input_name = new html_inputfield(array(
'type' => 'text',
'name' => 'name',
'value' => '',
));
$input_in1 = new html_inputfield(array(
'type' => 'radio',
'name' => 'all_folders',
'value' => '0',
'id' => 'all-folders-radio1',
));
$input_in2 = new html_inputfield(array(
'type' => 'radio',
'name' => 'all_folders',
'value' => '1',
'id' => 'all-folders-radio2',
));
$submit = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.file_search_submit()',
'value' => $this->translate('form.submit'),
));
$cancel = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.file_search_stop()',
'value' => $this->translate('form.cancel'),
));
$table = new html_table;
$table->add(null, $input_name->show()
. $input_in1->show() . html::label('all-folders-radio1', $this->translate('search.in_current_folder'))
. $input_in2->show() . html::label('all-folders-radio2', $this->translate('search.in_all_folders'))
);
$table->add('buttons', $submit->show() . $cancel->show());
$content = html::tag('fieldset', null,
html::tag('legend', null,
$this->translate('file.search')) . $table->show());
$form = html::tag('form', array(
'id' => 'file-search-form',
'onsubmit' => 'ui.file_search_submit(); return false'),
$content);
return $form;
}
}
diff --git a/lib/file_api.php b/lib/file_api.php
index cafb349..c9fb99b 100644
--- a/lib/file_api.php
+++ b/lib/file_api.php
@@ -1,715 +1,718 @@
<?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 file_api
{
const ERROR_CODE = 500;
const OUTPUT_JSON = 'application/json';
const OUTPUT_HTML = 'text/html';
const PATH_SEPARATOR = '/';
public $session;
public $api;
private $app_name = 'Kolab File API';
private $conf;
private $output_type = self::OUTPUT_JSON;
private $config = array(
'date_format' => 'Y-m-d H:i',
'language' => 'en_US',
);
public function __construct()
{
$rcube = rcube::get_instance();
$rcube->add_shutdown_function(array($this, 'shutdown'));
$this->conf = $rcube->config;
$this->session_init();
}
/**
* Initialise backend class
*/
protected function api_init()
{
if ($this->api) {
return;
}
// @TODO: config
$driver = 'kolab';
$class = $driver . '_file_storage';
require_once $driver . '/' . $class . '.php';
$this->api = new $class;
}
/**
* Process the request and dispatch it to the requested service
*/
public function run()
{
$this->request = strtolower($_GET['method']);
// Check the session, authenticate the user
if (!$this->session_validate()) {
$this->session->destroy(session_id());
if ($this->request == 'authenticate') {
$this->session->regenerate_id(false);
if ($username = $this->authenticate()) {
$_SESSION['user'] = $username;
$_SESSION['time'] = time();
$_SESSION['config'] = $this->config;
$this->output_success(array(
'token' => session_id(),
'capabilities' => $this->capabilities(),
));
}
}
throw new Exception("Invalid session", 403);
}
// Call service method
$result = $this->request_handler($this->request);
// Send success response, errors should be handled by driver class
// by throwing exceptions or sending output by itself
$this->output_success($result);
}
/**
* Session validation check and session start
*/
private function session_validate()
{
$sess_id = rcube_utils::request_header('X-Session-Token') ?: $_REQUEST['token'];
if (empty($sess_id)) {
session_start();
return false;
}
session_id($sess_id);
session_start();
if (empty($_SESSION['user'])) {
return false;
}
$timeout = $this->conf->get('session_lifetime', 0) * 60;
if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) {
return false;
}
// update session time
$_SESSION['time'] = time();
return true;
}
/**
* Initializes session
*/
private function session_init()
{
$rcube = rcube::get_instance();
$sess_name = $this->conf->get('session_name');
$lifetime = $this->conf->get('session_lifetime', 0) * 60;
if ($lifetime) {
ini_set('session.gc_maxlifetime', $lifetime * 2);
}
ini_set('session.name', $sess_name ? $sess_name : 'file_api_sessid');
ini_set('session.use_cookies', 0);
ini_set('session.serialize_handler', 'php');
// use database for storing session data
$this->session = new rcube_session($rcube->get_dbh(), $this->conf);
$this->session->register_gc_handler(array($rcube, 'temp_gc'));
$this->session->register_gc_handler(array($rcube, 'cache_gc'));
$this->session->set_secret($this->conf->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
$this->session->set_ip_check($this->conf->get('ip_check'));
}
/**
* Script shutdown handler
*/
public function shutdown()
{
session_write_close();
// write performance stats to logs/console
if ($this->conf->get('devel_mode')) {
if (function_exists('memory_get_peak_usage'))
$mem = memory_get_peak_usage();
else if (function_exists('memory_get_usage'))
$mem = memory_get_usage();
$log = trim($this->request . ($mem ? sprintf(' [%.1f MB]', $mem/1024/1024) : ''));
if (defined('FILE_API_START')) {
rcube::print_timer(FILE_API_START, $log);
}
else {
rcube::console($log);
}
}
}
/**
* Authentication request handler (HTTP Auth)
*/
private function authenticate()
{
if (isset($_POST['username'])) {
$username = $_POST['username'];
$password = $_POST['password'];
}
else if (!empty($_SERVER['PHP_AUTH_USER'])) {
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
}
// when used with (f)cgi no PHP_AUTH* variables are available without defining a special rewrite rule
else if (!isset($_SERVER['PHP_AUTH_USER'])) {
// "Basic didhfiefdhfu4fjfjdsa34drsdfterrde..."
if (isset($_SERVER["REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REMOTE_USER"], 6));
}
else if (isset($_SERVER["REDIRECT_REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6));
}
else if (isset($_SERVER["Authorization"])) {
$basicAuthData = base64_decode(substr($_SERVER["Authorization"], 6));
}
else if (isset($_SERVER["HTTP_AUTHORIZATION"])) {
$basicAuthData = base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6));
}
if (isset($basicAuthData) && !empty($basicAuthData)) {
list($username, $password) = explode(":", $basicAuthData);
}
}
if (!empty($username)) {
$this->api_init();
$result = $this->api->authenticate($username, $password);
}
if (empty($result)) {
/*
header('WWW-Authenticate: Basic realm="' . $this->app_name .'"');
header('HTTP/1.1 401 Unauthorized');
exit;
*/
throw new Exception("Invalid password or username", file_api::ERROR_CODE);
}
return $username;
}
/**
* Storage/System method handler
*/
private function request_handler($request)
{
// handle "global" requests that don't require api driver
switch ($request) {
case 'ping':
return array();
case 'quit':
$this->session->destroy(session_id());
return array();
case 'configure':
foreach (array_keys($this->config) as $name) {
if (isset($_GET[$name])) {
$this->config[$name] = $_GET[$name];
}
}
$_SESSION['config'] = $this->config;
return $this->config;
case 'upload_progress':
return $this->upload_progress();
case 'mimetypes':
return $this->supported_mimetypes();
case 'capabilities':
// this one actually uses api driver, but we put it here
// because we'd need session for the api driver
return $this->capabilities();
}
// init API driver
$this->api_init();
// GET arguments
$args = &$_GET;
// POST arguments (JSON)
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$post = file_get_contents('php://input');
$args += (array) json_decode($post, true);
unset($post);
}
+ // disable script execution time limit, so we can handle big files
+ @set_time_limit(0);
+
// handle request
switch ($request) {
case 'file_list':
$params = array('reverse' => !empty($args['reverse']) && rcube_utils::get_boolean($args['reverse']));
if (!empty($args['sort'])) {
$params['sort'] = strtolower($args['sort']);
}
if (!empty($args['search'])) {
$params['search'] = $args['search'];
if (!is_array($params['search'])) {
$params['search'] = array('name' => $params['search']);
}
}
return $this->api->file_list($args['folder'], $params);
case 'file_upload':
// for Opera upload frame response cannot be application/json
$this->output_type = self::OUTPUT_HTML;
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
$uploads = $this->upload();
$result = array();
foreach ($uploads as $file) {
$this->api->file_create($args['folder'] . self::PATH_SEPARATOR . $file['name'], $file);
unset($file['path']);
$result[$file['name']] = array(
'type' => $file['type'],
'size' => $file['size'],
);
}
return $result;
case 'file_delete':
$files = (array) $args['file'];
if (empty($files)) {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
foreach ($files as $file) {
$this->api->file_delete($file);
}
return;
case 'file_info':
if (!isset($args['file']) || $args['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
$info = $this->api->file_info($args['file']);
if (!empty($args['viewer']) && rcube_utils::get_boolean($args['viewer'])) {
$this->file_viewer_info($args['file'], $info);
}
return $info;
case 'file_get':
$this->output_type = self::OUTPUT_HTML;
if (!isset($args['file']) || $args['file'] === '') {
header("HTTP/1.0 ".file_api::ERROR_CODE." Missing file name");
}
$params = array(
'force-download' => !empty($args['force-download']) && rcube_utils::get_boolean($args['force-download']),
'force-type' => $args['force-type'],
);
if (!empty($args['viewer'])) {
$this->file_view($args['file'], $args['viewer'], $args, $params);
}
try {
$this->api->file_get($args['file'], $params);
}
catch (Exception $e) {
header("HTTP/1.0 " . file_api::ERROR_CODE . " " . $e->getMessage());
}
exit;
case 'file_move':
case 'file_copy':
if (!isset($args['file']) || $args['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
if (is_array($args['file'])) {
if (empty($args['file'])) {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
}
else {
if (!isset($args['new']) || $args['new'] === '') {
throw new Exception("Missing new file name", file_api::ERROR_CODE);
}
$args['file'] = array($args['file'] => $args['new']);
}
$overwrite = !empty($args['overwrite']) && rcube_utils::get_boolean($args['overwrite']);
$files = (array) $args['file'];
$errors = array();
foreach ($files as $file => $new_file) {
if ($new_file === '') {
throw new Exception("Missing new file name", file_api::ERROR_CODE);
}
if ($new_file === $file) {
throw new Exception("Old and new file name is the same", file_api::ERROR_CODE);
}
try {
$this->api->{$request}($file, $new_file);
}
catch (Exception $e) {
if ($e->getCode() == file_storage::ERROR_FILE_EXISTS) {
// delete existing file and do copy/move again
if ($overwrite) {
$this->api->file_delete($new_file);
$this->api->{$request}($file, $new_file);
}
// collect file-exists errors, so the client can ask a user
// what to do and skip or replace file(s)
else {
$errors[] = array(
'src' => $file,
'dst' => $new_file,
);
}
}
else {
throw $e;
}
}
}
if (!empty($errors)) {
return array('already_exist' => $errors);
}
return;
case 'folder_create':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
return $this->api->folder_create($args['folder']);
case 'folder_delete':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
return $this->api->folder_delete($args['folder']);
case 'folder_rename':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing source folder name", file_api::ERROR_CODE);
}
if (!isset($args['new']) || $args['new'] === '') {
throw new Exception("Missing destination folder name", file_api::ERROR_CODE);
}
if ($args['new'] === $args['folder']) {
return;
}
return $this->api->folder_rename($args['folder'], $args['new']);
case 'folder_list':
return $this->api->folder_list();
}
if ($request) {
throw new Exception("Unknown method", 501);
}
}
/**
* File uploads handler
*/
protected function upload()
{
$files = array();
if (is_array($_FILES['file']['tmp_name'])) {
foreach ($_FILES['file']['tmp_name'] as $i => $filepath) {
if ($err = $_FILES['file']['error'][$i]) {
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
throw new Exception("Maximum file size exceeded", file_api::ERROR_CODE);
}
throw new Exception("File upload failed", file_api::ERROR_CODE);
}
$files[] = array(
'path' => $filepath,
'name' => $_FILES['file']['name'][$i],
'size' => filesize($filepath),
'type' => rcube_mime::file_content_type($filepath, $_FILES['file']['name'][$i], $_FILES['file']['type']),
);
}
}
else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
throw new Exception("File upload failed", file_api::ERROR_CODE);
}
return $files;
}
/**
* File upload progress handler
*/
protected function upload_progress()
{
if (function_exists('apc_fetch')) {
$prefix = ini_get('apc.rfc1867_prefix');
$uploadid = rcube_utils::get_input_value('id', rcube_utils::INPUT_GET);
$status = apc_fetch($prefix . $uploadid);
if (!empty($status)) {
$status['percent'] = round($status['current']/$status['total']*100);
if ($status['percent'] < 100) {
$diff = time() - intval($status['start_time']);
// calculate time to end of uploading (in seconds)
- $status['left'] = intval($diff * (100 - $status['percent']) / $status['percent']);
+ $status['eta'] = intval($diff * (100 - $status['percent']) / $status['percent']);
// average speed (bytes per second)
$status['rate'] = intval($status['current'] / $diff);
}
}
$status['id'] = $uploadid;
return $status; // id, done, total, current, percent, start_time, left, rate
}
throw new Exception("Not supported", file_api::ERROR_CODE);
}
/*
* Returns API capabilities
*/
protected function capabilities()
{
$this->api_init();
$caps = array();
// check support for upload progress
if (($progress_sec = $this->conf->get('upload_progress'))
&& ini_get('apc.rfc1867') && function_exists('apc_fetch')
) {
$caps[file_storage::CAPS_PROGRESS_NAME] = ini_get('apc.rfc1867_name');
$caps[file_storage::CAPS_PROGRESS_TIME] = $progress_sec;
}
foreach ($this->api->capabilities() as $name => $value) {
// skip disabled capabilities
if ($value !== false) {
$caps[$name] = $value;
}
}
return $caps;
}
/**
* Return mimetypes list supported by built-in viewers
*
* @return array List of mimetypes
*/
protected function supported_mimetypes()
{
$mimetypes = array();
$dir = RCUBE_INSTALL_PATH . 'lib/viewers';
if ($handle = opendir($dir)) {
while (false !== ($file = readdir($handle))) {
if (preg_match('/^([a-z0-9_]+)\.php$/i', $file, $matches)) {
include_once $dir . '/' . $file;
$class = 'file_viewer_' . $matches[1];
$viewer = new $class($this);
$mimetypes = array_merge($mimetypes, $viewer->supported_mimetypes());
}
}
closedir($handle);
}
return $mimetypes;
}
/**
* Merge file viewer data into file info
*/
protected function file_viewer_info($file, &$info)
{
if ($viewer = $this->find_viewer($info['type'])) {
$info['viewer'] = array();
if ($frame = $viewer->frame($file, $info['type'])) {
$info['viewer']['frame'] = $frame;
}
else if ($href = $viewer->href($file, $info['type'])) {
$info['viewer']['href'] = $href;
}
}
}
/**
* File vieweing request handler
*/
protected function file_view($file, $viewer, &$args, &$params)
{
$path = RCUBE_INSTALL_PATH . "lib/viewers/$viewer.php";
$class = "file_viewer_$viewer";
if (!file_exists($path)) {
return;
}
// get file info
try {
$info = $this->api->file_info($file);
}
catch (Exception $e) {
header("HTTP/1.0 " . file_api::ERROR_CODE . " " . $e->getMessage());
exit;
}
include_once $path;
$viewer = new $class($this);
// check if specified viewer supports file type
// otherwise return (fallback to file_get action)
if (!$viewer->supports($info['type'])) {
return;
}
$viewer->output($file, $info['type']);
exit;
}
/**
* Return built-in viewer opbject for specified mimetype
*
* @return object Viewer object
*/
protected function find_viewer($mimetype)
{
$dir = RCUBE_INSTALL_PATH . 'lib/viewers';
if ($handle = opendir($dir)) {
while (false !== ($file = readdir($handle))) {
if (preg_match('/^([a-z0-9_]+)\.php$/i', $file, $matches)) {
include_once $dir . '/' . $file;
$class = 'file_viewer_' . $matches[1];
$viewer = new $class($this);
if ($viewer->supports($mimetype)) {
return $viewer;
}
}
}
closedir($handle);
}
}
/**
* Returns complete File URL
*
* @param string $file File name (with path)
*
* @return string File URL
*/
public function file_url($file)
{
return $_SERVER['SCRIPT_URI'] . '?method=file_get'
. '&file=' . urlencode($file)
. '&token=' . urlencode(session_id());
}
/**
* Send success response
*
* @param mixed $data Data
*/
public function output_success($data)
{
if (!is_array($data)) {
$data = array();
}
$this->output_send(array('status' => 'OK', 'result' => $data));
}
/**
* Send error response
*
* @param mixed $data Data
*/
public function output_error($errdata, $code = null)
{
if (is_string($errdata)) {
$errdata = array('reason' => $errdata);
}
if (!$code) {
$code = file_api::ERROR_CODE;
}
$this->output_send(array('status' => 'ERROR', 'code' => $code) + $errdata);
}
/**
* Send response
*
* @param mixed $data Data
*/
protected function output_send($data)
{
// Send response
header("Content-Type: {$this->output_type}; charset=utf-8");
echo json_encode($data);
exit;
}
}
diff --git a/lib/init.php b/lib/init.php
index aa9f8ba..6b8e86a 100644
--- a/lib/init.php
+++ b/lib/init.php
@@ -1,43 +1,40 @@
<?php
/**
+--------------------------------------------------------------------------+
| Kolab File API |
| |
| Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> |
| |
| 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> |
+--------------------------------------------------------------------------+
*/
// Roundcube Framework constants
define('FILE_API_START', microtime(true));
define('RCUBE_INSTALL_PATH', realpath(dirname(__FILE__)) . '/../');
define('RCUBE_CONFIG_DIR', RCUBE_INSTALL_PATH . 'config/');
define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'lib/kolab/plugins');
// Define include path
$include_path = RCUBE_INSTALL_PATH . '/lib' . PATH_SEPARATOR;
$include_path .= RCUBE_INSTALL_PATH . '/lib/ext' . PATH_SEPARATOR;
$include_path .= RCUBE_INSTALL_PATH . '/lib/client' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
-// @TODO: what is a reasonable value for File API?
-//@set_time_limit(600);
-
// include global functions from Roundcube Framework
require_once 'Roundcube/bootstrap.php';
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index 1342d12..80cafdc 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -1,68 +1,73 @@
<?php
$LANG['about.community'] = 'This is the Community Edition of the <b>Kolab Server</b>.';
$LANG['about.warranty'] = 'Professional support is available from <a href="http://kolabsys.com">Kolab Systems</a>.';
$LANG['about.support'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help &amp; information on the community <a href="http://kolab.org">web site</a> &amp; <a href="http://wiki.kolab.org">wiki</a>.';
$LANG['collection.audio'] = 'Audio';
$LANG['collection.video'] = 'Video';
$LANG['collection.image'] = 'Images';
$LANG['collection.document'] = 'Documents';
$LANG['file.copy'] = 'Copy';
$LANG['file.create'] = 'Create File';
$LANG['file.download'] = 'Download';
$LANG['file.edit'] = 'Edit';
$LANG['file.upload'] = 'Upload File';
$LANG['file.name'] = 'Name';
$LANG['file.move'] = 'Move';
$LANG['file.mtime'] = 'Modified';
$LANG['file.size'] = 'Size';
$LANG['file.open'] = 'Open';
$LANG['file.delete'] = 'Delete';
$LANG['file.rename'] = 'Rename';
$LANG['file.search'] = 'Search file';
$LANG['file.type'] = 'Type';
$LANG['file.skip'] = 'Skip';
$LANG['file.skipall'] = 'Skip all';
$LANG['file.overwrite'] = 'Overwrite';
$LANG['file.overwriteall'] = 'Overwrite all';
$LANG['file.moveconfirm'] = 'This action is going to overwrite the destination file: <b>$file</b>.';
$LANG['file.progress'] = 'Uploaded $current of $total ($percent%)';
$LANG['folder.createtitle'] = 'Create Folder';
$LANG['folder.delete'] = 'Delete';
$LANG['folder.edit'] = 'Edit';
$LANG['folder.edittitle'] = 'Edit Folder';
$LANG['folder.under'] = 'under current folder';
$LANG['form.submit'] = 'Submit';
$LANG['form.cancel'] = 'Cancel';
$LANG['login.username'] = 'Username';
$LANG['login.password'] = 'Password';
$LANG['login.login'] = 'Log in';
$LANG['reqtime'] = 'Request time: $1 sec.';
$LANG['maxupload'] = 'Maximum file size: $1';
$LANG['internalerror'] = 'Internal system error!';
$LANG['loginerror'] = 'Incorrect username or password!';
$LANG['loading'] = 'Loading...';
$LANG['saving'] = 'Saving...';
$LANG['deleting'] = 'Deleting...';
$LANG['copying'] = 'Copying...';
$LANG['moving'] = 'Moving...';
$LANG['logout'] = 'Logout';
$LANG['close'] = 'Close';
$LANG['servererror'] = 'Server Error!';
$LANG['session.expired'] = 'Session has expired. Login again, please';
$LANG['search'] = 'Search';
$LANG['search.loading'] = 'Searching...';
$LANG['search.in_all_folders'] = 'in all folders';
$LANG['search.in_current_folder'] = 'in current folder';
$LANG['size.B'] = 'B';
$LANG['size.KB'] = 'KB';
$LANG['size.MB'] = 'MB';
$LANG['size.GB'] = 'GB';
+
+$LANG['upload.size'] = 'Size:';
+$LANG['upload.progress'] = 'Progress:';
+$LANG['upload.rate'] = 'Rate:';
+$LANG['upload.eta'] = 'ETA:';
diff --git a/lib/viewers/image.php b/lib/viewers/image.php
new file mode 100644
index 0000000..06fcb3e
--- /dev/null
+++ b/lib/viewers/image.php
@@ -0,0 +1,109 @@
+<?php
+/*
+ +--------------------------------------------------------------------------+
+ | This file is part of the Kolab File API |
+ | |
+ | Copyright (C) 2011-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 implementing image viewer (with format converter)
+ *
+ * NOTE: some formats are supported by browser, don't use viewer when not needed.
+ */
+class file_viewer_image extends file_viewer
+{
+ protected $mimetypes = array(
+ 'image/bmp',
+ 'image/png',
+ 'image/jpeg',
+ 'image/jpg',
+ 'image/pjpeg',
+ 'image/gif',
+ 'image/tiff',
+ 'image/x-tiff',
+ );
+
+
+ /**
+ * Class constructor
+ *
+ * @param file_api File API object
+ */
+ public function __construct($api)
+ {
+ // @TODO: disable types not supported by some browsers
+ $this->api = $api;
+ }
+
+ /**
+ * Return file viewer URL
+ *
+ * @param string $file File name
+ * @param string $mimetype File type
+ */
+ public function href($file, $mimetype = null)
+ {
+ $href = $_SERVER['SCRIPT_URI'] . '?method=file_get'
+ . '&file=' . urlencode($file)
+ . '&token=' . urlencode(session_id());
+
+ // we redirect to self only images with types unsupported
+ // by browser
+ if (in_array($mimetype, $this->mimetypes)) {
+ $href .= '&viewer=image';
+ }
+
+ return $href;
+ }
+
+ /**
+ * Print output and exit
+ *
+ * @param string $file File name
+ * @param string $mimetype File type
+ */
+ public function output($file, $mimetype = null)
+ {
+/*
+ // conversion not needed
+ if (preg_match('/^image/p?jpe?g$/i', $mimetype)) {
+ $this->api->api->file_get($file);
+ return;
+ }
+*/
+ $rcube = rcube::get_instance();
+ $temp_dir = unslashify($rcube->config->get('temp_dir'));
+ $file_path = tempnam($temp_dir, 'rcmImage');
+
+ // write content to temp file
+ $fd = fopen($file_path, 'w');
+ $this->api->api->file_get($file, array(), $fd);
+ fclose($fd);
+
+ // convert image to jpeg and send it to the browser
+ $image = new rcube_image($file_path);
+ if ($image->convert(rcube_image::TYPE_JPG, $file_path)) {
+ header("Content-Type: image/jpeg");
+ header("Content-Length: " . filesize($file_path));
+ readfile($file_path);
+ }
+ unlink($file_path);
+ }
+}
diff --git a/public_html/js/files_api.js b/public_html/js/files_api.js
index d57dae2..301dcfd 100644
--- a/public_html/js/files_api.js
+++ b/public_html/js/files_api.js
@@ -1,540 +1,550 @@
/*
+--------------------------------------------------------------------------+
| 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: '/',
resources_dir: 'resources'
};
/*********************************************************/
/********* 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, data, func)
{
var url = this.env.url + '?method=' + action;
if (!func) func = 'response';
this.set_request_time();
return $.ajax({
type: 'POST', url: url, data: JSON.stringify(data), 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); }
});
};
// send request with auto-selection of POST/GET method
this.request = function(action, data, func)
{
// Use POST for modification actions with probable big request size
var method = /(create|delete|move|copy|rename)/.test(action) ? 'post' : 'get';
this[method](action, data, func);
};
// 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)
{
var key = this.env.sort_col || 'name';
if (key == 'mtime')
key = 'modified';
data1 = data1[key];
data2 = data2[key];
if (key == 'size' || key == 'modified')
// numeric comparison
return this.env.sort_reverse ? data2 - data1 : 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 ? 1 : -1;
}
};
// Checks if specified mimetype is supported natively by the browser (return 1)
// or can be displayed in the browser using File API viewer (return 2)
this.file_type_supported = function(type)
{
var i, t, regexps = [], img = 'jpg|jpeg|gif|bmp|png',
caps = this.env.browser_capabilities || {};
if (caps.tif)
img += '|tiff';
if ((new RegExp('^image/(' + img + ')$', 'i')).test(type))
return 1;
// prefer text viewer for any text type
if (/^text\/(?!(pdf|x-pdf))/i.test(type))
return 2;
if (caps.pdf) {
regexps.push(/^application\/(pdf|x-pdf|acrobat|vnd.pdf)/i);
regexps.push(/^text\/(pdf|x-pdf)/i);
}
if (caps.flash)
regexps.push(/^application\/x-shockwave-flash/i);
for (i in regexps)
if (regexps[i].test(type))
return 1;
for (i in navigator.mimeTypes) {
t = navigator.mimeTypes[i].type;
if (t == type && navigator.mimeTypes[i].enabledPlugin)
return 1;
}
// types with viewer support
if ($.inArray(type, this.env.supported_mimetypes) > -1)
return 2;
};
// Return browser capabilities
this.browser_capabilities = function()
{
var i, caps = [], ctypes = ['pdf', 'flash', 'tif'];
for (i in ctypes)
if (this.env.browser_capabilities[ctypes[i]])
caps.push(ctypes[i]);
return caps;
};
// 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(), ref = this;
img.onload = function() { ref.env.browser_capabilities.tif = 1; };
img.onerror = function() { ref.env.browser_capabilities.tif = 0; };
img.src = this.env.resources_dir + '/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,
ref = this;
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;
}
var obj = document.createElement('OBJECT');
obj.onload = function() { ref.env.browser_capabilities.pdf = 1; };
obj.onerror = function() { ref.env.browser_capabilities.pdf = 0; };
obj.style.display = 'none';
obj.type = 'application/pdf';
obj.data = this.env.resources_dir + '/blank.pdf';
document.body.appendChild(obj);
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;
};
+ // converts number of seconds into HH:MM:SS format
+ this.time_format = function(s)
+ {
+ s = parseInt(s);
+
+ if (s >= 60*60*24)
+ return '-';
+
+ return (new Date(1970, 1, 1, 0, 0, s, 0)).toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1');
+ };
};
// 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, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
};
function object_is_empty(obj)
{
if (obj)
for (var i in obj)
if (i !== null)
return true;
return false;
}
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 9207540..f962564 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -1,1374 +1,1407 @@
body {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
color: #333;
margin: 0;
color: #514949;
background: url(images/body.png) top repeat-x #f0f0f0;
}
a {
color: #1E90FF;
text-decoration: none;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 5px;
color: #ff9900;
}
input[type="text"],
input[type="password"],
select[multiple="multiple"],
textarea {
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
padding-left: 2px;
color: black;
}
select[multiple="multiple"] {
padding-left: 0;
}
table.list {
width: 100%;
border-spacing: 0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
table.list td {
padding: 2px 4px;
border: 1px solid white;
border-left: none;
border-top: none;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
table.list thead tr {
background-color: #e0e0e0;
font-weight: bold;
}
table.list tbody tr {
background-color: #f0f0f0;
}
table.list tfoot tr {
background-color: #e0e0e0;
}
table.list tfoot tr td {
padding: 3px 3px;
font-size: 10px;
text-shadow: white 1px 1px;
}
table.list tfoot tr td {
border-top: solid 1px white;
border-bottom: none;
}
table.list td:last-child {
border-right: none;
}
table.list tbody tr.selectable td {
cursor: default;
}
table.list tbody tr.selectable:hover {
background-color: #d6efff;
}
table.list tbody tr td.empty-body {
height: 150px;
color: #ff9900;
text-align: center;
}
fieldset {
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
margin-top: 10px;
}
legend {
padding-left: 0;
color: #909090;
}
table.form {
width: 100%;
}
table.form td {
padding: 1px 5px;
}
table.form tr.required input,
table.form tr.required select,
table.form tr.required textarea {
background-color: #f0f9ff;
}
table.form tr input.error,
table.form tr select.error,
table.form tr textarea.error {
background-color: #f5e3e3;
}
td.label {
width: 1%;
min-width: 120px;
text-align: right;
font-weight: bold;
white-space: nowrap;
}
table tbody tr.selected {
background-color: #d6efff;
}
/**** Common UI elements ****/
#topmenu {
text-align: right;
height: 20px;
padding: 0 10px;
margin: 0;
white-space: nowrap;
background: url(images/linen_header.jpg) 0 0 repeat-x;
}
#topmenu > span {
color: #aaa;
font-size: 11px;
padding-top: 2px;
display: inline-block;
height: 18px;
}
#navigation {
margin: 0 15px;
text-align: right;
height: 36px;
z-index: 2;
white-space: nowrap;
}
#task_navigation {
margin: 0 15px;
text-align: right;
height: 21px;
z-index: 2;
white-space: nowrap;
}
#message {
position: absolute;
top: 80px;
left: 20px;
width: 350px;
height: 60px;
z-index: 20;
}
#message div {
opacity: 0.97;
padding-left: 70px;
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
-moz-box-shadow: 1px 1px 3px #999;
-webkit-box-shadow: #999 1px 1px 3px;
width: 100%;
height: 100%;
display: table;
}
#message.notice div {
border-color: #aec1db;
color: #3465a4;
background: url(images/info.png) 10px center no-repeat #c0e0ff;
}
#message.error div {
border-color: #db9999;
color: #a40000;
background: url(images/error.png) 10px center no-repeat #edcccc;
}
#message div span {
vertical-align: middle;
display: table-cell;
line-height: 20px;
}
#logo {
position: absolute;
top: 0px;
left: 10px;
width: 198px;
height: 74px;
cursor: pointer;
background: url(images/logo.png) 0 0 no-repeat;
}
#content {
position: absolute;
left: 0;
right: 0;
top: 74px;
bottom: 55px;
margin: 0 15px;
padding: 10px;
background-color: #f5f5f5;
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
#footer {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 50px;
margin: 2px 15px;
color: #b0b0b0;
font-size: 9px;
}
#loading {
position: absolute;
display: none;
top: 2px;
left: 15px;
width: 150px;
height: 18px;
padding-left: 86px;
color: #aaa;
font-size: 11px;
z-index: 1;
background: url(images/loading.gif) 0 3px no-repeat;
white-space: nowrap;
}
#topmenu .logout {
background: url(images/buttons.png) -1px -100px no-repeat;
padding-left: 20px;
margin-right: 10px;
color: white;
}
#topmenu .login {
padding-left: 20px;
margin-right: 20px;
background: url(images/user_ico.png) 0 2px no-repeat;
}
#topmenu .domain {
padding-left: 20px;
margin-right: 10px;
background: url(images/domain_ico.png) 0 3px no-repeat;
}
#navigation ul {
list-style-type: none;
margin: 0;
padding: 8px 0;
}
#navigation ul li {
display: inline;
font-size: 13px;
font-weight: bold;
padding: 8px 0;
}
#navigation ul li a {
padding: 8px 10px;
text-decoration: none;
color: #514949;
}
#navigation ul li.active {
background: url(images/navbg.png) 0 0 repeat-x;
}
#navigation ul li.active a {
text-shadow: white 1px 1px;
color: #ff9900;
}
#task_navigation ul {
list-style-type: none;
margin: 0;
padding: 2px 0;
}
#task_navigation ul li {
padding: 1px 0;
display: inline;
font-size: 12px;
font-weight: bold;
text-shadow: white 1px 1px;
}
#task_navigation ul li a {
padding: 1px 10px;
text-decoration: none;
color: #808080;
}
#navigation ul li a:active,
#task_navigation ul li a:active {
outline: none;
}
#search {
padding: 7px;
margin-bottom: 10px;
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: #e0e0e0;
}
#searchinput {
border: none;
background-color: white;
margin-top: 2px;
}
#searchinput:focus {
outline: none;
}
.searchinput {
height: 20px;
margin: 0;
padding: 0;
background-color: white;
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
overflow: hidden;
}
.searchactions {
float: left;
padding: 1px;
margin-left: -1px;
height: 18px;
width: 37px;
background-color: #f0f0f0;
cursor: pointer;
border-right: 1px solid #e0e0e0;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
-moz-border-radius-topleft: 3px;
-moz-border-radius-bottomleft: 3px;
-webkit-border-top-left-radius: 3px;
-webkit-border-left-right-radius: 3px;
}
#search-reset,
#search-details {
display: block;
float: left;
width: 18px;
height: 18px;
background: url(images/buttons.png) -1px 0 no-repeat;
}
#search-reset:hover,
#search-details:hover {
background-color: white;
}
#search-reset {
border-left: 1px solid #e0e0e0;
}
#search-details {
background-position: -1px 20px;
}
.searchdetails {
display: none;
}
.searchfilter {
color: #909090;
font-weight: bold;
margin-top: 5px;
}
#search fieldset {
margin: 0;
color: #909090;
margin-top: 5px;
}
#search td.label {
min-width: 0;
}
div.vsplitter {
float: left;
width: 10px;
min-height: 400px;
}
/* fix cursor on upload button */
input[type="file"]::-webkit-file-upload-button {
cursor: pointer;
}
#draglayer {
min-width: 300px;
width: auto !important;
width: 300px;
border: 1px solid #999999;
background-color: #fff;
padding-left: 8px;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 3px;
white-space: nowrap;
opacity: 0.9;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
-moz-box-shadow: 1px 1px 12px #999;
-webkit-box-shadow: #999 1px 1px 12px;
}
/**** Common classes ****/
a.disabled {
opacity: 0.5;
filter: alpha(opacity=50);
}
.nowrap {
white-space: nowrap;
}
.clear {
clear: both;
}
.watermark {
padding-top: 40px;
text-align: center;
width: 100%;
}
.link {
cursor: pointer;
}
.icon {
width: 16px;
height: 16px;
}
input.inactive {
color: #d0d0d0;
}
.formtitle {
color: #ff9900;
font-size: 18px;
font-weight: bold;
margin-left: 5px;
}
.formbuttons {
text-align: center;
white-space: nowrap;
}
.formbuttons input {
margin: 5px;
}
div.scroller {
left: 0;
top: 0;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
position: absolute;
bottom: 19px;
}
.listnav {
width: 100%;
text-align: right;
}
.listnav a {
float: left;
display: block;
width: 16px;
height: 16px;
background: url(images/arrow_left.png) center no-repeat;
}
.listnav a.next {
background: url(images/arrow_right.png) center no-repeat;
}
.listnav a.disabled {
opacity: .3;
cursor: default;
}
.listnav span span {
float: left;
display: block;
height: 14px;
padding: 1px 5px;
}
.disabled,
.readonly,
.select.readonly option {
color: #a0a0a0;
}
input.disabled,
input.readonly,
select.disabled,
select.readonly,
textarea.disabled,
textarea.readonly {
background-color: #f5f5f5;
color: #a0a0a0;
}
input.maxsize {
width: 368px; /* span.listarea width - 2px */
}
pre.debug {
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: white;
padding: 2px;
width: 500px;
height: 200px;
margin: 0;
overflow: auto;
}
.popup {
display: none;
position: absolute;
border: 1px solid #d0d0d0;
border-radius: 3px;
box-shadow: 0 2px 6px 0 #d0d0d0;
-moz-box-shadow: 0 2px 6px 0 #d0d0d0;
-webkit-box-shadow: 0 2px 6px 0 #d0d0d0;
-o-box-shadow: 0 2px 6px 0 #d0d0d0;
background-color: #f0f0f0;
}
a.button {
display: inline-block;
width: 18px;
height: 18px;
background: url(images/buttons.png) 0 0 no-repeat;
}
a.button.edit {
background-position: -1px -81px;
}
a.button.add {
background-position: -1px -41px;
}
a.button.delete {
background-position: -1px -1px;
}
.popup ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.popup ul li {
padding: 2px 4px;
min-width: 100px;
cursor: default;
}
.popup ul li a {
cursor: default;
display: block;
}
.popup ul li:hover {
background-color: #d6efff;
}
div.boxfooter {
position: absolute;
height: 18px;
left: 0;
right: 0;
bottom: 0;
background-color: #e0e0e0;
border-top: 1px solid #d0d0d0;
}
div.boxfooter a.button {
width: auto;
white-space: nowrap;
color: #514949;
display: inline;
line-height: 18px;
padding: 0 5px 0 20px;
}
/********* Form smart inputs *********/
span.listarea {
display: block;
width: 370px;
max-height: 209px;
overflow-y: auto;
overflow-x: hidden;
margin: 0;
padding: 0;
background-color: white;
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
text-align: left;
text-shadow: none;
color: black;
}
span.listelement {
display: block;
padding: 0;
margin: 0;
height: 18px;
overflow: hidden;
border-top: 1px solid #d0d0d0;
white-space: nowrap;
}
span.listelement:first-child {
border: none;
}
span.listelement input {
border: none;
background-color: transparent;
padding-left: 2px;
width: 328px;
height: 16px;
}
span.listarea.disabled span.listelement input,
span.listarea.readonly span.listelement input {
width: 370px;
}
span.listelement input:focus {
outline: none;
}
span.listelement span.actions {
float: left;
padding: 1px 0;
margin-left: -1px;
margin-top: -1px;
height: 18px;
width: 37px;
border: 1px solid #d0d0d0;
background-color: #f0f0f0;
cursor: pointer;
}
span.listelement span.actions span {
display: block;
float: left;
width: 18px;
height: 18px;
background: url(images/buttons.png) 0 0 no-repeat;
}
span.listelement span.actions span:hover {
background-color: white;
}
span.listelement span.actions span.reset {
background-position: -1px -1px;
border-left: 1px solid #e0e0e0;
}
span.listelement span.actions span.add {
background-position: -41px -2px;
}
span.listelement span.actions span.search {
background-position: -61px -1px;
cursor: default;
}
span.listarea.disabled,
span.listarea.readonly {
background-color: #f5f5f5;
}
input.disabled,
input.readonly,
span.listarea.disabled span.listelement input,
span.listarea.readonly span.listelement input {
color: #a0a0a0;
cursor: default;
}
span.listarea.autocomplete span.listelement input {
color: #514949;
}
span.listarea.autocomplete span.listelement input.autocomplete {
color: black;
}
.autocomplete > span.listelement input {
width: 346px;
}
.autocomplete > span.listelement span.actions {
width: 18px;
}
.autocomplete > span.listelement span.actions span.reset {
border-left: none;
}
.autocomplete > span.listelement span.actions span.search:hover {
background-color: #f0f0f0;
}
span.listarea.select {
width: 200px;
}
span.listarea.select > span.listelement input {
width: 180px;
}
span.listcontent {
display: block;
padding: 0;
margin: 0;
overflow: hidden;
max-height: 94px;
overflow-x: hidden;
overflow-y: auto;
border-top: 1px solid #d0d0d0;
background-color: #f5f5f5;
cursor: default;
}
span.listcontent span.listelement {
padding-left: 3px;
}
span.listcontent span.listelement:hover {
background-color: #d6efff;
}
span.listcontent span.listelement.selected {
background-color: #d6efff;
}
span.form_error {
color: #FF0000;
font-weight: bold;
font-size: 90%;
padding-left: 5px;
}
/***** progress bar ****/
table.progress {
width: 100%;
height: 5px;
- background-color: #F0F0F0;
- border: 1px solid #D0D0D0;
+ background-color: #f0f0f0;
+ border: 1px solid #d0d0d0;
border-spacing: 0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
margin-bottom: 3px;
}
table.progress td.bar {
background-color: #71b9e1;
}
+table.progressinfo {
+ font-size: 9px;
+ border: 1px solid #d0d0d0;
+ width: 5%;
+ background-color: #f0f0f0;
+ z-index: 100;
+ border-spacing: 0;
+ border-radius: 3px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ padding: 3px;
+}
+
+table.progressinfo td {
+ padding: 0 1px;
+ white-space: nowrap;
+}
+
+table.progressinfo td.label {
+ text-align: left;
+ font-weight: normal;
+ padding-right: 10px;
+ min-width: 50px;
+ width: 1%;
+}
+
+table.progressinfo td.value {
+ text-align: right;
+ width: 99%;
+}
+
+
+
/***** autocomplete list *****/
#autocompletepane
{
background-color: white;
border: 1px solid #d0d0d0;
min-width: 351px;
}
#autocompletepane ul
{
margin: 0px;
padding: 2px;
list-style-image: none;
list-style-type: none;
}
#autocompletepane ul li
{
display: block;
height: 16px;
font-size: 11px;
padding-left: 6px;
padding-top: 2px;
padding-right: 6px;
white-space: nowrap;
cursor: pointer;
}
#autocompletepane ul li.selected
{
background-color: #d6efff;
}
/***** tabbed interface elements *****/
div.tabsbar
{
height: 22px;
border-bottom: 1px solid #d0d0d0;
white-space: nowrap;
margin: 10px 5px 0 5px;
}
span.tablink,
span.tablink-selected
{
float: left;
height: 23px !important;
height: 22px;
overflow: hidden;
background: url(images/tabs-left.gif) top left no-repeat;
font-weight: bold;
}
span.tablink
{
cursor: pointer;
text-shadow: white 1px 1px;
}
span.tablink-selected
{
cursor: default;
background-position: 0px -23px;
}
span.tablink a,
span.tablink-selected a
{
display: inline-block;
padding: 4px 10px 0 5px;
margin-left: 5px;
height: 23px;
color: #808080;
max-width: 185px;
text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
background: url(images/tabs-right.gif) top right no-repeat;
outline: none;
}
span.tablink-selected a
{
cursor: inherit;
color: #514949;
background-position: right -23px;
}
fieldset.tabbed
{
margin-top: 0;
padding-top: 12px;
border-top: none;
}
/***** Dialog windows *****/
#_wModal_bg {
background-color: #000;
z-index: 10000;
opacity: 0.2;
filter: alpha(opacity=20);
}
#_wModal_pixel {
z-index: 10001;
}
._wModal {
position: relative;
min-width: 350px;
overflow: hidden;
line-height: 15px;
background-color: #FFF;
color: #333;
border: 1px solid rgba(51, 51, 51, 0.5);
border-radius: 4px;
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
}
._wModal_header {
padding: 10px;
font-size: 14px;
font-weight: bold;
border-bottom: solid 1px #DDD;
}
._wModal_close {
position: absolute;
right: 10px;
top: 10px;
font-weight: normal;
font-size: 12px;
cursor: pointer;
color: #BABABA;
}
._wModal_msg {
font-size: 12px;
padding: 20px;
color: #3A3A3A;
text-shadow: rgba(255, 255, 255, 0.75) 0 1px 1px;
}
._wModal_btns {
padding: 10px;
font-size: 10px;
font-weight: bold;
border-top: solid 1px #DDD;
background-color: #EFEFEF;
text-align: right;
white-space: nowrap;
}
._wModal_btns div {
display: inline-block;
min-width: 40px;
padding: 0 10px;
height: 25px;
line-height: 25px;
margin-left: 10px;
text-align: center;
cursor: pointer;
border-radius: 4px;
box-shadow: rgba(255, 255, 255, 0.2) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.0470588) 0px 1px 2px 0px;
text-shadow: rgba(255, 255, 255, 0.75) 0 1px 1px;
border: 1px solid rgba(0, 0, 0, 0.14902);
border-bottom-color: rgba(0, 0, 0, 0.247059);
background-color: #F5F5F5;
color: #333;
}
._wModal_btns div:hover {
background-color: #E6E6E6;
}
._wModal_btns div.default {
text-shadow: rgba(0, 0, 0, 0.247059) 0px -1px 0px;
border: 1px solid rgba(0, 0, 0, 0.0980392);
background-color: #006DCC;
color: #FFF;
}
._wModal_btns div.default:hover {
background-color: #0044CC
}
/**** Login form elements ****/
#login_form {
margin: auto;
margin-top: 75px;
padding: 20px;
width: 330px;
background-color: #e0e0e0;
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
text-align: center;
}
#login_form span {
display: block;
line-height: 24px;
width: 330px;
text-align: left;
}
#login_form label {
display: block;
width: 80px;
text-align: right;
float: left;
margin-right: 10px;
}
#login_form select,
#login_form input[type="text"],
#login_form input[type="password"] {
width: 210px;
}
#login_submit {
margin-top: 5px;
}
/**** Main screen elements ****/
#main {
padding: 5px 30px;
}
#footer .foot {
white-space: nowrap;
vertical-align: top;
text-align: right;
}
/***** tree indicators *****/
td span.branch span
{
float: left;
height: 16px;
}
td span.branch span.tree
{
height: 17px;
width: 15px;
background: url(images/tree.gif) 0 0 no-repeat;
}
td span.branch span.l1
{
background-position: 0px 0px; /* L */
}
td span.branch span.l2
{
background-position: -30px 0px; /* | */
}
td span.branch span.l3
{
background-position: -15px 0px; /* |- */
}
/**** File manager elements ****/
#taskcontent {
position: absolute;
left: 310px;
right: 10px;
bottom: 10px;
top: 10px;
background-color: #f0f0f0;
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
overflow-y: auto;
}
#actionbar {
position: absolute;
width: 82px;
top: 10px;
bottom: 10px;
overflow: hidden;
}
#forms {
position: absolute;
left: 310px;
right: 10px;
top: 10px;
}
#forms form {
display: none;
}
#forms fieldset {
margin: 0;
background-color: #f0f0f0;
}
#forms table {
border-spacing: 0;
margin: 0;
padding: 0;
}
#forms table td.buttons {
width: 1%;
white-space: nowrap;
}
#actionbar a {
display: block;
width: 80px;
height: 55px;
border: 1px solid #d0d0d0;
border-spacing: 0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: #f0f0f0;
margin-bottom: 6px;
cursor: pointer;
color: #333;
}
#actionbar a span {
display: block;
position: absolute;
width: 100%;
text-align: center;
padding-top: 40px;
font-size: 9px;
}
#folder-create-button {
background: url(images/folder_new.png) center 6px no-repeat;
}
#file-create-button {
background: url(images/file_new.png) center 6px no-repeat;
}
#file-upload-button {
background: url(images/file_new.png) center 6px no-repeat;
}
#file-search-button {
background: url(images/search.png) center 6px no-repeat;
}
#folderlist,
#filedata {
position: absolute;
width: 200px;
top: 10px;
bottom: 10px;
left: 98px;
border: 1px solid #d0d0d0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: #f0f0f0;
padding: 2px;
}
#folderlist table,
#filelist table {
width: 100%;
border-spacing: 0;
}
#filelist td {
white-space: nowrap;
cursor: default;
}
#filelist td.filename {
width: 98%;
height: 20px;
padding: 0 4px;
}
#filelist td.filesize {
text-align: right;
}
#filelist tbody td.filename span {
background: url(images/mimetypes/unknown.png) 0 0 no-repeat;
padding: 0 0 0 20px;
height: 16px;
cursor: pointer;
}
#filelist tbody td.filename span input {
padding: 0 2px;
height: 18px;
}
#filelist thead td {
cursor: pointer;
}
#filelist thead td.sorted {
padding-right: 16px;
text-decoration: underline;
background: url(images/buttons.png) right -140px no-repeat;
}
#filelist thead td.sorted.reverse {
background-position: right -120px;
}
#folderlist td span.name {
background: url(images/folder.png) 0 0 no-repeat;
height: 18px;
padding-left: 20px;
margin-left: 3px;
cursor: pointer;
}
#folderlist tr.selected td span.name {
background-image: url(images/folder_open.png);
}
#folderlist tr.selected {
background-color: inherit;
}
#folderlist tr.selected td span.name {
font-weight: bold;
}
#folderlist tr.virtual td span.name {
color: #bbb;
cursor: default;
}
#folderlist tr.droptarget {
background-color: #e0e0e0;
}
#folder-collection-audio td span.name,
#folderlist #folder-collection-audio.selected td span.name {
background: url(images/audio.png) 1px 0 no-repeat;
}
#folder-collection-video td span.name,
#folderlist #folder-collection-video.selected td span.name {
background: url(images/video.png) 0 0 no-repeat;
}
#folder-collection-image td span.name,
#folderlist #folder-collection-image.selected td span.name {
background: url(images/image.png) 0 0 no-repeat;
}
#folder-collection-document td span.name,
#folderlist #folder-collection-document.selected td span.name {
background: url(images/document.png) 0 0 no-repeat;
}
/****** File open interface elements ******/
#actionbar #file-edit-button {
background: url(images/edit.png) center 6px no-repeat #f0f0f0;
}
#actionbar #file-delete-button {
background: url(images/trash.png) center 6px no-repeat #f0f0f0;
}
#actionbar #file-download-button {
background: url(images/download.png) center 6px no-repeat #f0f0f0;
}
#taskcontent iframe {
border: none;
width: 100%;
height: 100%;
background-color: white;
overflow: auto;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
opacity: 0.1;
}
.fileopen #taskcontent {
overflow: hidden;
background-color: white;
}
#filedata table {
width: 200px;
}
#filedata table td.label {
min-width: 30px;
}
#filedata table td.data {
/*
text-overflow: ellipsis;
overflow: hidden;
*/
}
#filedata table td.data.filename {
font-weight: bold;
}
#loader {
display: none;
z-index: 10;
width: 100px;
background-color: #fafafa;
color: #a0a0a0;
position: absolute;
border: 1px solid #e0e0e0;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
text-align: center;
padding: 10px;
font-weight: bold;
}
diff --git a/public_html/skins/default/ui.js b/public_html/skins/default/ui.js
index b8d2c91..301767f 100644
--- a/public_html/skins/default/ui.js
+++ b/public_html/skins/default/ui.js
@@ -1,92 +1,109 @@
function file_list_sort(name, elem)
{
var td = $(elem), reverse = ui.env.sort_reverse;
if (ui.env.sort_col == name)
reverse = !reverse;
else
reverse = 0;
$('td', td.parent()).removeClass('sorted reverse');
td.addClass('sorted').removeClass('reverse');
if (reverse)
td.addClass('reverse');
ui.file_list_sort(name, reverse);
};
function hack_file_input(id)
{
var link = $('#'+id),
file = $('<input>'),
offset = link.offset();
file.attr({name: 'file[]', type: 'file', multiple: 'multiple', size: 5})
.change(function() { ui.file_upload(); })
// opacity:0 does the trick, display/visibility doesn't work
.css({opacity: 0, cursor: 'pointer', position: 'relative', outline: 'none', top: '10000px', left: '10000px'});
// In FF and IE we need to move the browser file-input's button under the cursor
// Thanks to the size attribute above we know the length of the input field
if (navigator.userAgent.match(/Firefox|MSIE/))
file.css({marginLeft: '-80px'});
// Note: now, I observe problem with cursor style on FF < 4 only
link.css({overflow: 'hidden', cursor: 'pointer'})
// place button under the cursor
.mousemove(function(e) {
if (ui.commands['file.upload'])
file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'});
// move the input away if button is disabled
else
$(this).mouseleave();
})
.mouseleave(function() { file.css({top: '10000px', left: '10000px'}); })
.append(file);
};
function progress_update(data)
{
- var txt = ui.t('file.progress'), id = 'progress' + data.id, table = $('#' + id);
+ var txt = ui.t('file.progress'), id = 'progress' + data.id,
+ table = $('#' + id), content = $('#info' + id),
+ i, row, offset, rows = [];
if (!data || data.done) {
- if (table.length)
+ if (table.length) {
table.remove();
+ content.remove();
+ }
return;
}
if (!table.length) {
table = $('<table class="progress" id="' + id + '"><tr><td class="bar"></td><td></td></tr></table>');
- table.appendTo($('#actionbar'));
- }
+ content = $('<table class="progressinfo" id="info' + id + '"></table>');
+
+ table.appendTo($('#actionbar'))
+ .on('mouseleave', function() { content.hide(); })
+ .on('mouseenter', function() { content.show(); });
- txt = txt.replace('$current', ui.file_size(data.current))
- .replace('$total', ui.file_size(data.total))
- .replace('$percent', data.percent);
+ offset = table.offset();
+ content.css({display: 'none', position: 'absolute', top: offset.top + 8, left: offset.left})
+ .appendTo(document.body);
+ }
$('td.bar', table).width(data.percent + '%');
- // @TODO: display text in nice-looking hint instead of title,
- // @TODO: add 'rate' and 'left' information to the text
- table.attr('title', txt);
+
+ rows[ui.t('upload.size')] = ui.file_size(data.total);
+ rows[ui.t('upload.progress')] = data.percent + '%';
+ rows[ui.t('upload.rate')] = ui.file_size(data.rate) + '/s';
+ rows[ui.t('upload.eta')] = ui.time_format(data.eta);
+
+ content.empty();
+
+ for (i in rows)
+ $('<tr>').append($('<td class="label">').text(i))
+ .append($('<td class="value">').text(rows[i]))
+ .appendTo(content);
};
$(window).load(function() {
hack_file_input('file-upload-button');
$('#forms > form').hide();
});
// register buttons
ui.buttons({
'folder.create': 'folder-create-button',
'folder.edit': 'folder-edit-button',
'folder.delete': 'folder-delete-button',
'file.upload': 'file-upload-button',
'file.search': 'file-search-button',
'file.delete': 'file-delete-button',
'file.download': 'file-download-button',
'file.edit': 'file-edit-button',
'file.copy': 'file-copy-button',
'file.move': 'file-move-button'
});

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jul 8, 7:36 PM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1049477
Default Alt Text
(84 KB)

Event Timeline