Page MenuHomePhorge

No OneTemporary

diff --git a/lib/client/file_ui_client_main.php b/lib/client/file_ui_client_main.php
index a2f4b6d..7e3ac61 100644
--- a/lib/client/file_ui_client_main.php
+++ b/lib/client/file_ui_client_main.php
@@ -1,167 +1,168 @@
<?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'
+ 'moving', 'copying', 'file.skip', 'file.skipall', 'file.overwrite', 'file.overwriteall',
+ 'file.moveconfirm'
);
$this->output->set_env('search_threads', $this->config->get('files_search_threads'));
$this->output->set_env('supported_mimetypes', $this->supported_mimetypes());
$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()',
+ '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 ba12479..ef61482 100644
--- a/lib/file_api.php
+++ b/lib/file_api.php
@@ -1,445 +1,473 @@
<?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 = '/';
private $app_name = 'Kolab File API';
private $api;
private $output_type = self::OUTPUT_JSON;
private $config = array(
'date_format' => 'Y-m-d H:i',
'language' => 'en_US',
);
public function __construct()
{
}
/**
* 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()
{
$request = strtolower($_GET['method']);
// Check the session, authenticate the user
if (!$this->session_validate()) {
@session_destroy();
if ($request == 'authenticate') {
session_start();
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($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
*/
private function session_validate()
{
$sess_id = rcube_utils::request_header('X-Session-Token') ?: $_REQUEST['token'];
if (empty($sess_id)) {
return false;
}
session_id($sess_id);
session_start();
if (empty($_SESSION['user'])) {
return false;
}
// Session timeout
// $timeout = $this->config->get('kolab_wap', 'session_timeout');
$timeout = 3600;
if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) {
return false;
}
// update session time
$_SESSION['time'] = time();
return true;
}
/**
* 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 driver method caller
*/
private function request($request)
{
if ($request == 'ping') {
return array();
}
$this->api_init();
switch ($request) {
case 'file_list':
$params = array('reverse' => !empty($_GET['reverse']) && rcube_utils::get_boolean($_GET['reverse']));
if (!empty($_GET['sort'])) {
$params['sort'] = strtolower($_GET['sort']);
}
if (!empty($_GET['search'])) {
$params['search'] = $_GET['search'];
if (!is_array($params['search'])) {
$params['search'] = array('name' => $params['search']);
}
}
return $this->api->file_list($_GET['folder'], $params);
case 'file_create':
// for Opera upload frame response cannot be application/json
$this->output_type = self::OUTPUT_HTML;
if (!isset($_GET['folder']) || $_GET['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
$uploads = $this->upload();
$result = array();
foreach ($uploads as $file) {
$this->api->file_create($_GET['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) $_GET['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($_GET['file']) || $_GET['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
return $this->api->file_info($_GET['file']);
case 'file_get':
$this->output_type = self::OUTPUT_HTML;
if (!isset($_GET['file']) || $_GET['file'] === '') {
header("HTTP/1.0 ".file_api::ERROR_CODE." Missing file name");
}
$params = array(
'force-download' => !empty($_GET['force-download']) && rcube_utils::get_boolean($_GET['force-download']),
'force-type' => $_GET['force-type'],
);
try {
$this->api->file_get($_GET['file'], $params);
}
catch (Exception $e) {
header("HTTP/1.0 " . file_api::ERROR_CODE . " " . $e->getMessage());
}
exit;
case 'file_move':
case 'file_copy':
if (!isset($_GET['file']) || $_GET['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
if (is_array($_GET['file'])) {
if (empty($_GET['file'])) {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
}
else {
if (!isset($_GET['new']) || $_GET['new'] === '') {
throw new Exception("Missing new file name", file_api::ERROR_CODE);
}
$_GET['file'] = array($_GET['file'] => $_GET['new']);
}
- $files = (array) $_GET['file'];
+ $overwrite = !empty($_GET['overwrite']) && rcube_utils::get_boolean($_GET['overwrite']);
+ $files = (array) $_GET['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);
}
- $this->api->{$request}($file, $new_file);
+ 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($_GET['folder']) || $_GET['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
return $this->api->folder_create($_GET['folder']);
case 'folder_delete':
if (!isset($_GET['folder']) || $_GET['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
return $this->api->folder_delete($_GET['folder']);
case 'folder_rename':
if (!isset($_GET['folder']) || $_GET['folder'] === '') {
throw new Exception("Missing source folder name", file_api::ERROR_CODE);
}
if (!isset($_GET['new']) || $_GET['new'] === '') {
throw new Exception("Missing destination folder name", file_api::ERROR_CODE);
}
if ($_GET['new'] === $_GET['folder']) {
return;
}
return $this->api->folder_rename($_GET['folder'], $_GET['new']);
case 'folder_list':
return $this->api->folder_list();
case 'quit':
@session_destroy();
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 'capabilities':
return $this->capabilities();
}
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;
}
/*
* Returns storage (driver) capabilities
*/
protected function capabilities()
{
$this->api_init();
$caps = array();
foreach ($this->api->capabilities() as $name => $value) {
// skip disabled capabilities
if ($value !== false) {
$caps[$name] = $value;
}
}
return $caps;
}
/**
* 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/file_storage.php b/lib/file_storage.php
index b3c8faa..abe1a0d 100644
--- a/lib/file_storage.php
+++ b/lib/file_storage.php
@@ -1,151 +1,153 @@
<?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> |
+--------------------------------------------------------------------------+
*/
interface file_storage
{
const CAPS_MAX_UPLOAD = 'MAX_UPLOAD';
const CAPS_ACL = 'ACL';
+ const ERROR_FILE_EXISTS = 550;
+
/**
* Authenticates a user
*
* @param string $username User name
* @param string $password User password
*
* @return bool True on success, False on failure
*/
public function authenticate($username, $password);
/**
* Storage driver capabilities
*
* @return array List of capabilities
*/
public function capabilities();
/**
* 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);
/**
* Delete a file.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_delete($file_name);
/**
* Returns 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());
/**
* 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);
/**
* 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);
/**
* Returns file metadata.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_info($file_name);
/**
* 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());
/**
* Create a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_create($folder_name);
/**
* Delete a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_delete($folder_name);
/**
* 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
*/
public function folder_rename($folder_name, $new_name);
/**
* Returns list of folders.
*
* @return array List of folders
* @throws Exception
*/
public function folder_list();
}
diff --git a/lib/file_ui.php b/lib/file_ui.php
index a724d91..4f1d133 100644
--- a/lib/file_ui.php
+++ b/lib/file_ui.php
@@ -1,702 +1,701 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2011-2012, 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> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
-
class file_ui
{
/**
* @var kolab_client_output
*/
protected $output;
/**
* @var kolab_client_api
*/
public $api;
/**
* @var Conf
*/
protected $config;
protected $ajax_only = false;
protected $page_title = 'Kolab File API';
protected $menu = array();
protected $cache = array();
protected $devel_mode = false;
protected $object_types = array();
protected static $translation = array();
/**
* Class constructor.
*
* @param file_ui_output $output Optional output object
*/
public function __construct($output = null)
{
$this->config_init();
$this->devel_mode = $this->config->get('devel_mode', false);
$this->output_init($output);
$this->api_init();
ini_set('session.use_cookies', 'On');
session_start();
// Initialize locales
$this->locale_init();
$this->auth();
}
/**
* Localization initialization.
*/
protected function locale_init()
{
$language = $this->get_language();
$LANG = array();
if (!$language) {
$language = 'en_US';
}
@include RCUBE_INSTALL_PATH . '/lib/locale/en_US.php';
if ($language != 'en_US') {
@include RCUBE_INSTALL_PATH . "/lib/locale/$language.php";
}
setlocale(LC_ALL, $language . '.utf8', 'en_US.utf8');
self::$translation = $LANG;
}
/**
* Configuration initialization.
*/
private function config_init()
{
$this->config = rcube::get_instance()->config;
}
/**
* Output initialization.
*/
private function output_init($output = null)
{
if ($output) {
$this->output = $output;
return;
}
$skin = $this->config->get('file_api_skin', 'default');
$this->output = new file_ui_output($skin);
// Assign self to template variable
$this->output->assign('engine', $this);
}
/**
* API initialization
*/
private function api_init()
{
$url = $this->config->get('file_api_url', '');
if (!$url) {
$url = rcube_utils::https_check() ? 'https://' : 'http://';
$url .= $_SERVER['SERVER_NAME'];
$url .= preg_replace('/\/?\?.*$/', '', $_SERVER['REQUEST_URI']);
$url .= '/api/';
}
$this->api = new file_ui_api($url);
}
/**
* Initializes User Interface
*/
protected function ui_init()
{
// assign token
$this->output->set_env('token', $_SESSION['user']['token']);
// assign capabilities
$this->output->set_env('capabilities', $_SESSION['caps']);
// add watermark content
$this->output->set_env('watermark', $this->output->get_template('watermark'));
// $this->watermark('taskcontent');
// assign default set of translations
$this->output->add_translation('loading', 'servererror');
// $this->output->assign('tasks', $this->menu);
// $this->output->assign('main_menu', $this->menu());
$this->output->assign('user', $_SESSION['user']);
$this->output->assign('max_upload', $this->show_bytes($_SESSION['caps']['MAX_UPLOAD']));
}
/**
* Returns system language (locale) setting.
*
* @return string Language code
*/
private function get_language()
{
$aliases = array(
'de' => 'de_DE',
'en' => 'en_US',
'pl' => 'pl_PL',
);
// UI language
$langs = !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
$langs = explode(',', $langs);
if (!empty($_SESSION['user']) && !empty($_SESSION['user']['language'])) {
array_unshift($langs, $_SESSION['user']['language']);
}
while ($lang = array_shift($langs)) {
$lang = explode(';', $lang);
$lang = $lang[0];
$lang = str_replace('-', '_', $lang);
if (file_exists(RCUBE_INSTALL_PATH . "/lib/locale/$lang.php")) {
return $lang;
}
if (isset($aliases[$lang]) && ($alias = $aliases[$lang])
&& file_exists(RCUBE_INSTALL_PATH . "/lib/locale/$alias.php")
) {
return $alias;
}
}
return null;
}
/**
* User authentication (and authorization).
*/
private function auth()
{
if (isset($_POST['login'])) {
$login = $this->get_input('login', 'POST');
if ($login['username']) {
$result = $this->api->login($login['username'], $login['password']);
if ($token = $result->get('token')) {
$user = array(
'token' => $token,
'username' => $login['username'],
);
$this->api->set_session_token($user['token']);
/*
// Find user settings
// Don't call API user.info for non-existing users (#1025)
if (preg_match('/^cn=([a-z ]+)/i', $login['username'], $m)) {
$user['fullname'] = ucwords($m[1]);
}
else {
$res = $this->api->get('user.info', array('user' => $user['id']));
$res = $res->get();
if (is_array($res) && !empty($res)) {
$user['language'] = $res['preferredlanguage'];
$user['fullname'] = $res['cn'];
}
}
*/
// Save capabilities
$_SESSION['caps'] = $result->get('capabilities');
// Save user data
$_SESSION['user'] = $user;
if (($language = $this->get_language()) && $language != 'en_US') {
$_SESSION['user']['language'] = $language;
$session_config['language'] = $language;
}
/*
// Configure API session
if (!empty($session_config)) {
$this->api->post('system.configure', null, $session_config);
}
*/
header('Location: ?');
die;
}
else {
$code = $result->get_error_code();
$str = $result->get_error_str();
$label = 'loginerror';
if ($code == file_ui_api::ERROR_INTERNAL
|| $code == file_ui_api::ERROR_CONNECTION
) {
$label = 'internalerror';
$this->raise_error(500, 'Login failed. ' . $str);
}
$this->output->command('display_message', $label, 'error');
}
}
}
else if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token'])) {
// Validate session
$timeout = $this->config->get('session_timeout', 3600);
if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) {
$this->action_logout(true);
}
// update session time
$_SESSION['time'] = time();
// Set API session key
$this->api->set_session_token($_SESSION['user']['token']);
}
}
/**
* Main execution.
*/
public function run()
{
// Session check
if (empty($_SESSION['user']) || empty($_SESSION['user']['token'])) {
$this->action_logout();
}
// Run security checks
$this->input_checks();
$this->action = $this->get_input('action', 'GET');
if ($this->action) {
$method = 'action_' . $this->action;
if (method_exists($this, $method)) {
$this->$method();
}
}
else if (method_exists($this, 'action_default')) {
$this->action_default();
}
}
/**
* Security checks and input validation.
*/
public function input_checks()
{
$ajax = $this->output->is_ajax();
// Check AJAX-only tasks
if ($this->ajax_only && !$ajax) {
$this->raise_error(500, 'Invalid request type!', null, true);
}
// CSRF prevention
$token = $ajax ? rcube_utils::request_header('X-Session-Token') : $this->get_input('token');
$task = $this->get_task();
if ($task != 'main' && $token != $_SESSION['user']['token']) {
$this->raise_error(403, 'Invalid request data!', null, true);
}
}
/**
* Logout action.
*/
private function action_logout($sess_expired = false, $stop_sess = true)
{
if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token']) && $stop_sess) {
$this->api->logout();
}
$_SESSION = array();
if ($this->output->is_ajax()) {
if ($sess_expired) {
$args = array('error' => 'session.expired');
}
$this->output->command('main_logout', $args);
if ($sess_expired) {
$this->output->send();
exit;
}
}
else {
$this->output->add_translation('loginerror', 'internalerror', 'session.expired');
}
if ($sess_expired) {
$error = 'session.expired';
}
else {
$error = $this->get_input('error', 'GET');
}
if ($error) {
$this->output->command('display_message', $error, 'error', 60000);
}
$this->output->send('login');
exit;
}
/**
* Error action (with error logging).
*
* @param int $code Error code
* @param string $msg Error message
* @param array $args Optional arguments (type, file, line)
* @param bool $output Enable to send output and finish
*/
public function raise_error($code, $msg, $args = array(), $output = false)
{
$log_line = sprintf("%s Error: %s (%s)",
isset($args['type']) ? $args['type'] : 'PHP',
$msg . (isset($args['file']) ? sprintf(' in %s on line %d', $args['file'], $args['line']) : ''),
$_SERVER['REQUEST_METHOD']);
rcube::write_log('errors', $log_line);
if (!$output) {
return;
}
if ($this->output->is_ajax()) {
header("HTTP/1.0 $code $msg");
die;
}
$this->output->assign('error_code', $code);
$this->output->assign('error_message', $msg);
$this->output->send('error');
exit;
}
/**
* Output sending.
*/
public function send()
{
$task = $this->get_task();
if ($this->page_title) {
$this->output->assign('pagetitle', $this->page_title);
}
$this->output->set_env('task', $task);
$this->output->send($this->task_template ? $this->task_template : $task);
exit;
}
/**
* Returns name of the current task.
*
* @return string Task name
*/
public function get_task()
{
$class_name = get_class($this);
if (preg_match('/^file_ui_client_([a-z]+)$/', $class_name, $m)) {
return $m[1];
}
}
/**
* Returns translation of defined label/message.
*
* @return string Translated string.
*/
public static function translate()
{
$args = func_get_args();
if (is_array($args[0])) {
$args = $args[0];
}
$label = $args[0];
if (isset(self::$translation[$label])) {
$content = trim(self::$translation[$label]);
}
else {
$content = $label;
}
for ($i = 1, $len = count($args); $i < $len; $i++) {
$content = str_replace('$'.$i, $args[$i], $content);
}
return $content;
}
/**
* Returns input parameter value.
*
* @param string $name Parameter name
* @param string $type Parameter type (GET|POST|NULL)
* @param bool $allow_html Disables stripping of insecure content (HTML tags)
*
* @see rcube_utils::get_input_value
* @return mixed Input value.
*/
public static function get_input($name, $type = null, $allow_html = false)
{
if ($type == 'GET') {
$type = rcube_utils::INPUT_GET;
}
else if ($type == 'POST') {
$type = rcube_utils::INPUT_POST;
}
else {
$type = rcube_utils::INPUT_GPC;
}
$result = rcube_utils::get_input_value($name, $type, $allow_html);
return $result;
}
/**
* Returns task menu output.
*
* @return string HTML output
*/
protected function menu()
{
}
/**
* Adds watermark page definition into main page.
*/
protected function watermark($name)
{
$this->output->command('set_watermark', $name);
}
/**
* API GET request wrapper
*/
protected function api_get($action, $get = array())
{
return $this->api_call('get', $action, $get);
}
/**
* API POST request wrapper
*/
protected function api_post($action, $get = array(), $post = array())
{
return $this->api_call('post', $action, $get, $post);
}
/**
* API request wrapper with error handling
*/
protected function api_call($type, $action, $get = array(), $post = array())
{
if ($type == 'post') {
$result = $this->api->post($action, $get, $post);
}
else {
$result = $this->api->get($action, $get);
}
// error handling
if ($code = $result->get_error_code()) {
// Invalid session, do logout
if ($code == 403) {
$this->action_logout(true, false);
}
// Log communication errors, other should be logged on API side
if ($code < 400) {
$this->raise_error($code, 'API Error: ' . $result->get_error_str());
}
}
return $result;
}
/**
* Returns execution time in seconds
*
* @param string Execution time
*/
public function gentime()
{
return sprintf('%.4f', microtime(true) - RCMAIL_START);
}
/**
* Returns HTML output of login form
*
* @param string HTML output
*/
public function login_form()
{
$post = $this->get_input('login', 'POST');
$user_input = new html_inputfield(array(
'type' => 'text',
'id' => 'login_name',
'name' => 'login[username]',
'autofocus' => true,
));
$pass_input = new html_inputfield(array(
'type' => 'password',
'id' => 'login_pass',
'name' => 'login[password]',
));
$button = new html_inputfield(array(
'type' => 'submit',
'id' => 'login_submit',
'value' => $this->translate('login.login'),
));
$username = html::label(array('for' => 'login_name'), $this->translate('login.username'))
. $user_input->show($post['username']);
$password = html::label(array('for' => 'login_pass'), $this->translate('login.password'))
. $pass_input->show('');
$form = html::tag('form', array(
'id' => 'login_form',
'name' => 'login',
'method' => 'post',
'action' => '?'),
html::span(null, $username) . html::span(null, $password) . $button->show());
return $form;
}
/**
* Create a human readable string for a number of bytes
*
* @param int Number of bytes
*
* @return string Byte string
*/
protected function show_bytes($bytes)
{
if (!$bytes) {
return null;
}
if ($bytes >= 1073741824) {
$gb = $bytes/1073741824;
$str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . $this->translate('size.GB');
}
else if ($bytes >= 1048576) {
$mb = $bytes/1048576;
$str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . $this->translate('size.MB');
}
else if ($bytes >= 1024) {
$str = sprintf("%d ", round($bytes/1024)) . $this->translate('size.KB');
}
else {
$str = sprintf("%d ", $bytes) . $this->translate('size.B');
}
return $str;
}
/**
* 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_ui_viewer_' . $matches[1];
$viewer = new $class($this);
$mimetypes = array_merge($mimetypes, $viewer->supported_mimetypes());
}
}
closedir($handle);
}
return $mimetypes;
}
/**
* 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_ui_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 $this->api->base_url() . '?method=file_get'
. '&file=' . urlencode($file)
. '&token=' . urlencode($_SESSION['user']['token']);
}
}
diff --git a/lib/kolab/kolab_file_storage.php b/lib/kolab/kolab_file_storage.php
index 4d507ee..6b81509 100644
--- a/lib/kolab/kolab_file_storage.php
+++ b/lib/kolab/kolab_file_storage.php
@@ -1,770 +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 ($sort == 'mtime') {
$sort = 'modified';
}
if (in_array($sort, array('name', 'size', 'modified'))) {
foreach ($result as $key => $val) {
$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);
+ throw new Exception("Storage error. File exists.", file_storage::ERROR_FILE_EXISTS);
}
$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);
+ throw new Exception("Storage error. File exists.", file_storage::ERROR_FILE_EXISTS);
}
// 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/lib/locale/en_US.php b/lib/locale/en_US.php
index 02f6d71..e98dcec 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -1,62 +1,67 @@
<?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['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';
diff --git a/public_html/.htaccess b/public_html/.htaccess
index 95e2340..916e894 100644
--- a/public_html/.htaccess
+++ b/public_html/.htaccess
@@ -1,6 +1,6 @@
php_flag session.auto_start Off
php_flag display_errors Off
php_flag log_errors On
php_value error_log ../logs/errors
-php_value post_max_size 5M
-php_value upload_max_filesize 5M
\ No newline at end of file
+php_value post_max_size 20M
+php_value upload_max_filesize 20M
\ No newline at end of file
diff --git a/public_html/js/files_ui.js b/public_html/js/files_ui.js
index 07984e2..6caf150 100644
--- a/public_html/js/files_ui.js
+++ b/public_html/js/files_ui.js
@@ -1,1500 +1,1610 @@
/*
+--------------------------------------------------------------------------+
| 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.ie = document.all && !window.opera;
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);
}
if (!this.env.browser_capabilities)
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 + 20,
top = pos.top - 10;
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 and response handlers *********/
/*********************************************************/
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 = ui.folder_list_row(i, f);
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', 'file.copy', 'file.move', 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');
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]) < 0)
break;
var row = this.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 || !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);
};
// file delete request
this.file_delete = function(file)
{
if (!file) {
file = [];
if (this.env.file)
file.push(this.env.file);
else
file = this.file_list_selected();
}
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_rename = function(file, newname)
{
if (file === newname)
return;
this.set_busy(true, 'saving');
this.get('file_move', {file: file, 'new': newname}, 'file_rename_response');
};
// file rename response handler
this.file_rename_response = function(response)
{
if (!this.response(response))
return;
// @TODO: we could update list/file metadata and just sort
this.file_list();
};
// file copy request
this.file_copy = function(folder)
{
var count = 0, list = {}, files = this.file_list_selected();
if (!files || !files.length || !folder)
return;
$.each(files, function(i, v) {
var name = folder + ui.env.directory_separator + ui.file_name(v);
if (name != v) {
list[v] = name;
count++;
}
});
if (!count)
return;
this.set_busy(true, 'copying');
this.get('file_copy', {file: list}, 'file_copy_response');
};
// file copy response handler
this.file_copy_response = function(response)
{
if (!this.response(response))
return;
+
+ if (response.result && response.result.already_exist && response.result.already_exist.length)
+ this.file_move_ask_user(response.result.already_exist);
};
// file move request
this.file_move = function(folder)
{
var count = 0, list = {}, files = this.file_list_selected();
if (!files || !files.length || !folder)
return;
$.each(files, function(i, v) {
var name = folder + ui.env.directory_separator + ui.file_name(v);
if (name != v) {
list[v] = name;
count++;
}
});
if (!count)
return;
this.set_busy(true, 'moving');
this.get('file_move', {file: list}, 'file_move_response');
};
// file move response handler
this.file_move_response = function(response)
{
if (!this.response(response))
return;
- this.file_list();
+ if (response.result && response.result.already_exist && response.result.already_exist.length)
+ this.file_move_ask_user(response.result.already_exist, true);
+ else
+ 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});
};
// 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();
});
}
};
/*********************************************************/
/********* Command helpers *********/
/*********************************************************/
// create folders table row
this.folder_list_row = function(folder, data)
{
var row = $('<tr><td><span class="branch"></span><span class="name"></span></td></tr>'),
span = $('span.name', row);
span.text(data.name);
row.attr('id', data.id).data('folder', folder);
if (data.depth)
$('span.branch', row).width(15 * data.depth);
if (data.virtual)
row.addClass('virtual');
else {
span.click(function() { ui.folder_select(folder); })
row.mouseenter(function() {
if (ui.drag_active && (!ui.env.folder || ui.env.folder != $(this).data('folder')))
$(this).addClass('droptarget');
})
.mouseleave(function() {
if (ui.drag_active)
$(this).removeClass('droptarget');
});
if (folder == this.env.folder)
row.addClass('selected');
}
return row;
};
// create files table row
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); })
.mousedown(function(e) { return ui.file_list_drag(e, this); });
// disables selection in IE
if (document.all)
row.on('selectstart', function() { return false; });
return row;
};
// file row click event handler
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', 'file.copy', 'file.move', selected == 1);
+ this.enable_command('file.delete', 'file.move', 'file.copy', selected);
+ this.enable_command('file.open', 'file.get', 'file.rename', selected == 1);
};
// file row drag start event handler
this.file_list_drag = function(e, row)
{
if (e.shiftKey || e.ctrlKey)
return true;
// selects currently unselected row
if (!$(row).hasClass('selected'))
this.file_list_click(e, row);
this.drag_start = true;
this.drag_mouse_start = this.mouse_pos(e);
$(document)
.on('mousemove.draghandler', function(e) { ui.file_list_drag_mouse_move(e); })
.on('mouseup.draghandler', function(e) { ui.file_list_drag_mouse_up(e); });
/*
if (bw.mobile) {
$(document)
.on('touchmove.draghandler', function(e) { ui.file_list_drag_mouse_move(e); })
.on('touchend.draghandler', function(e) { ui.file_list_drag_mouse_up(e); });
}
*/
return false;
};
// file row mouse move event handler
this.file_list_drag_mouse_move = function(e)
{
/*
// convert touch event
if (e.type == 'touchmove') {
if (e.changedTouches.length == 1)
e = rcube_event.touchevent(e.changedTouches[0]);
else
return rcube_event.cancel(e);
}
*/
var max_rows = 10, pos = this.mouse_pos(e);
if (this.drag_start) {
// check mouse movement, of less than 3 pixels, don't start dragging
if (!this.drag_mouse_start || (Math.abs(pos.left - this.drag_mouse_start.left) < 3 && Math.abs(pos.top - this.drag_mouse_start.top) < 3))
return false;
if (!this.draglayer)
this.draglayer = $('<div>').attr('id', 'draglayer')
.css({position:'absolute', display:'none', 'z-index':2000})
.appendTo(document.body);
// reset content
this.draglayer.html('');
// get subjects of selected messages
$('#filelist tr.selected').slice(0, max_rows+1).each(function(i) {
if (i == 0)
ui.drag_start_pos = $(this).offset();
else if (i == max_rows) {
ui.draglayer.append('...');
return;
}
var subject = $('td.filename', this).text();
// truncate filename to 50 characters
if (subject.length > 50)
subject = subject.substring(0, 50) + '...';
ui.draglayer.append($('<div>').text(subject));
});
this.draglayer.show();
this.drag_active = true;
}
if (this.drag_active && this.draglayer)
this.draglayer.css({left:(pos.left+20)+'px', top:(pos.top-5 + (this.ie ? document.documentElement.scrollTop : 0))+'px'});
this.drag_start = false;
return false;
};
// file row mouse up event handler
this.file_list_drag_mouse_up = function(e)
{
document.onmousemove = null;
/*
if (e.type == 'touchend') {
if (e.changedTouches.length != 1)
return rcube_event.cancel(e);
}
*/
$(document).off('.draghandler');
this.drag_active = false;
var got_folder = this.file_list_drag_end(e);
if (this.draglayer && this.draglayer.is(':visible')) {
if (this.drag_start_pos && !got_folder)
this.draglayer.animate(this.drag_start_pos, 300, 'swing').hide(20);
else
this.draglayer.hide();
}
};
// files drag end handler
this.file_list_drag_end = function(e)
{
var folder = $('#folderlist tr.droptarget').removeClass('droptarget');
if (folder.length) {
folder = folder.data('folder');
if (e.shiftKey && this.commands['file.copy']) {
this.file_drag_menu(e, folder);
return true;
}
this.command('file.move', folder);
return true;
}
};
// display file drag menu
this.file_drag_menu = function(e, folder)
{
var menu = $('#file-drag-menu');
$('li.file-copy > a', menu).off('click').click(function() { ui.command('file.copy', folder); });
$('li.file-move > a', menu).off('click').click(function() { ui.command('file.move', folder); });
this.popup_show(e, menu);
};
// display file menu
this.file_menu = function(e, file, type)
{
var href, caps, viewer,
menu = $('#file-menu'),
open_action = $('li.file-open > a', menu);
if (viewer = this.file_type_supported(type)) {
caps = this.browser_capabilities().join();
href = '?' + $.param({task: 'file', action: 'open', token: this.env.token, file: file, caps: caps, viewer: viewer == 2 ? 1 : 0});
open_action.attr({target: '_blank', href: href}).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);
};
// returns selected files (with paths)
this.file_list_selected = function()
{
var files = [];
$('#filelist tr.selected').each(function() {
files.push($(this).data('file'));
});
return files;
};
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_rename(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);
});
};
// 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});
};
+ // when file move/copy operation returns file-exists error
+ // this displays a dialog where user can decide to skip
+ // or overwrite destination file(s)
+ this.file_move_ask_user = function(list, move)
+ {
+ var file = list[0], buttons = {},
+ label = this.t('file.moveconfirm').replace('$file', file.dst);
+
+ buttons['file.overwrite'] = function() {
+ var file = list.shift(), f = {},
+ action = move ? 'file_move' : 'file_copy';
+
+ f[file.src] = file.dst;
+ ui.file_move_ask_list = list;
+ ui.file_move_ask_mode = move;
+ this.hide();
+ ui.set_busy(true, move ? 'moving' : 'copying');
+ ui.get(action, {file: f, overwrite: 1}, 'file_move_ask_user_response');
+ };
+
+ if (list.length > 1)
+ buttons['file.overwriteall'] = function() {
+ var f = {}, action = move ? 'file_move' : 'file_copy';
+
+ $.each(list, function() { f[this.src] = this.dst; });
+ this.hide();
+ ui.set_busy(true, move ? 'moving' : 'copying');
+ ui.get(action, {file: f, overwrite: 1}, action + '_response');
+ };
+
+ buttons['file.skip'] = function() {
+ list.shift();
+ this.hide();
+ if (list.length)
+ ui.file_move_ask_user(list, move);
+ else if (move)
+ ui.file_list();
+ };
+
+ if (list.length > 1)
+ buttons['file.skipall'] = function() {
+ this.hide();
+ if (move)
+ ui.file_list();
+ };
+
+ this.modal_dialog(label, buttons);
+ };
+
+ // file move (with overwrite) response handler
+ this.file_move_ask_user_response = function(response)
+ {
+ var mode = this.file_move_ask_mode, list = this.file_move_ask_list;
+
+ this.response(response);
+
+ if (list && list.length)
+ this.file_move_ask_user(list, mode);
+ else if (mode)
+ this.file_list();
+ };
/*********************************************************/
/********* Utilities *********/
/*********************************************************/
+ // modal dialog popup
+ this.modal_dialog = function(content, buttons, opts)
+ {
+ var settings = {position: 'cm', btns: {}, fxShow: 'fade'},
+ dialog = $('<div class="_wModal"></div>'),
+ body = $('<div class="_wModal"></div>'),
+ head, foot, footer = [];
+
+ // title bar
+ if (opts && opts.title)
+ $('<div class="_wModal_header"></div>')
+ .append($('<span>').text(opts.title))
+ .appendTo(body);
+
+ // dialog content
+ if (typeof content != 'object')
+ content = $('<div></div>').html(content);
+
+ content.addClass('_wModal_msg').appendTo(body);
+
+ // buttons
+ $.each(buttons, function(i, v) {
+ var n = i.replace(/[^a-z0-9_]/ig, '');
+ settings.btns[n] = v;
+ footer.push({name: n, label: ui.t(i)});
+ });
+
+// if (!settings.btns.cancel && (!opts || !opts.no_cancel))
+// settings.btns.cancel = function() { this.hide(); };
+
+ if (footer.length) {
+ foot = $('<div class="_wModal_btns"></div>');
+ $.each(footer, function() {
+ $('<div></div>').addClass('_wModal_btn_' + this.name).text(this.label).appendTo(foot);
+ });
+
+ body.append(foot);
+ }
+
+ // configure and display dialog
+ dialog.append(body).wModal(settings).wModal('show');
+ };
+
// 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);
};
// loads a file content into an iframe (with loading image)
this.load_file = function(content, filedata)
{
var iframe = $(content);
if (!iframe.length)
return;
var href = filedata.href,
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() {
// there sometimes "Permission denied to access propert document", use try/catch
try {
$(iframe.get(0).contentWindow.document).ready(function() {
parent.ui.loader_hide(content);
});
} catch (e) {};
}, 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();
});
diff --git a/public_html/js/wModal.js b/public_html/js/wModal.js
new file mode 100644
index 0000000..4992717
--- /dev/null
+++ b/public_html/js/wModal.js
@@ -0,0 +1,312 @@
+/******************************************
+ * Websanova.com
+ *
+ * Resources for web entrepreneurs
+ *
+ * @author Websanova
+ * @copyright Copyright (c) 2012 Websanova.
+ * @license This websanova jQuery modal plugin is dual licensed under the MIT and GPL licenses.
+ * @link http://www.websanova.com
+ * @github http://github.com/websanova/modal
+ * @version 1.1.1
+ *
+ ******************************************/
+
+(function($)
+{
+ $.fn.wModal = function(option, settings)
+ {
+ if(typeof option === 'object')
+ {
+ settings = option;
+ }
+ else if(typeof option === 'string')
+ {
+ var values = [];
+
+ var elements = this.each(function()
+ {
+ var data = $(this).data('_wModal');
+
+ if(data)
+ {
+ if(option === 'show') data.show(settings || {});
+ else if(option === 'hide') data.hide(settings || {});
+ else if($.fn.wModal.defaultSettings[option] !== undefined)
+ {
+ if(settings !== undefined) { data.settings[option] = settings; }
+ else { values.push(data.settings[option]); }
+ }
+ }
+ });
+
+ if(values.length === 1) { return values[0]; }
+ else if(values.length > 0) { return values; }
+ else { return elements; }
+ }
+
+ return this.each(function()
+ {
+ var _settings = $.extend({}, $.fn.wModal.defaultSettings, settings || {});
+ var modal = new Modal(_settings, $(this));
+ var $el = modal.generate();
+
+ modal.pixel.append($el);
+
+ $(this).data('_wModal', modal);
+ });
+ }
+
+ $.fn.wModal.defaultSettings = {
+ position : 'cm', // position of modal (lt, ct, rt, rm, rb, cb, lb, lm, cm)
+ offset : '10', // offset of modal from edges if not in "m" or "c" position
+ fxShow : 'none', // show effects (fade, slideUp, slideDown, slideLeft, slideRight)
+ fxHide : 'none', // hide effects (fade, slideUp, slideDown, slideLeft, slideRight)
+ btns : {}, // button callbacks
+ msg : null // optional message to set if "_wModal_msg" class is set
+ };
+
+ function Modal(settings, elem)
+ {
+ this.modal = null;
+ this.settings = settings;
+ this.elem = elem;
+
+ this.tempButtons = {};
+ this.rotationTimer = null;
+
+ return this;
+ }
+
+ Modal.prototype =
+ {
+ generate: function()
+ {
+ var _this = this;
+
+ if(this.modal) return this.modal;
+
+ // bg - check if bg already exists
+ if($('#_wModal_bg').length)
+ {
+ this.bg = $('#_wModal_bg');
+ this.pixel = $('#_wModal_pixel');
+ }
+ else
+ {
+ this.bg = $('<div id="_wModal_bg"></div>').css({position:'fixed', left:'0', top:'0', display:'none'});
+ $('body').append(this.bg);
+ $(window).resize(function(){ if(_this.bg.is(':visible')) _this.resetBg.apply(_this); });
+
+ // positioning pixel setting to body produces some weird effects wtih scrollbars when doing sliding effects
+ this.pixel = $('<div id="_wModal_pixel"></div>').css({position:'fixed', left:'0', top:'0', width:'0', height:'0', lineHeight:'0', fontSize:'0'});
+ $('body').append(this.pixel);
+ }
+
+ // modal
+ this.modal = $('<div class="_wModal_holder"></div>').css({position:'absolute', display:'none'});
+ this.modal.html(this.elem.html());
+
+ // set message - if set we use it, otherwise we try to pull from the _wModal_message container
+ var msg = this.modal.find('._wModal_msg');
+ if(msg.length)
+ {
+ if(this.settings.msg && this.settings.msg !== '') msg.html(this.settings.msg);
+ else this.settings.msg = msg.html();
+ }
+
+ $(window).resize(function(){ if(_this.modal.is(':visible')) _this.resetModal.apply(_this); });
+
+ this.resetBtns();
+
+ return this.modal;
+ },
+
+ resetModal: function()
+ {
+ var left = null, top = null, right = null, bottom = null;
+
+ var position = this.tempPosition || this.settings.position;
+ var offset = this.tempOffset || this.settings.offset;
+
+ var modalWidth = this.modal.outerWidth(true);
+ var modalHeight = this.modal.outerHeight(true);
+
+ var viewWidth = $(window).width();
+ var viewHeight = $(window).height();
+
+ this.outerX = viewWidth;
+ this.outerY = viewHeight;
+
+ var cX = (viewWidth/2) - (modalWidth/2);
+ var cY = (viewHeight/2) - (modalHeight/2);
+ var rX = viewWidth - modalWidth - offset;
+ var bY = viewHeight - modalHeight - offset;
+
+ switch(position)
+ {
+ case 'cm': left = cX; top = cY; break;
+ case 'lt': left = offset; top = offset; break;
+ case 'ct': left = cX; top = offset; break;
+ case 'rt': left = rX; top = offset; break;
+ case 'rm': left = rX; top = cY; break;
+ case 'rb': left = rX; top = bY; break;
+ case 'cb': left = cX; top = bY; break;
+ case 'lb': left = offset; top = bY; break;
+ case 'lm': left = offset; top = cY; break;
+ }
+
+ this.modal.css({left:(left ? left + 'px' : 'auto'), top:(top ? top + 'px' : 'auto'), bottom:(bottom ? bottom + 'px' : 'auto'), right: (right ? right + 'px' : 'auto') });
+ },
+
+ resetBg: function()
+ {
+ this.bg.css({width:$(window).width(), height:$(window).height()});
+ },
+
+ resetBtns: function(btns)
+ {
+ var btns = btns || this.settings.btns;
+ var _this = this;
+
+ for(var btn in btns)
+ {
+ (function(btn){
+ _this.modal.find('._wModal_btn_' + btn).unbind('click');
+ _this.modal.find('._wModal_btn_' + btn).click(function()
+ {
+ if(_this.tempBtns[btn]) _this.tempBtns[btn].apply(_this);
+ else btns[btn].apply(_this);
+ });
+ })(btn);
+ }
+ },
+
+ show: function(settings)
+ {
+ this.tempBtns = settings.btns || {};
+ this.tempPosition = settings.position || null;
+ this.tempOffset = settings.offset || null;
+
+ this.tempFxShow = settings.fxShow || this.settings.fxShow;
+ this.tempFxHide = settings.fxHide || this.settings.fxHide;
+
+ var msg = this.modal.find('._wModal_msg');
+ if(msg.length) msg.html(settings.msg || this.settings.msg);
+
+ this.resetBg();
+ this.resetModal();
+
+ this.pixel.children('._wModal_holder').hide();
+
+ this['fxShow' + this.tempFxShow.charAt(0).toUpperCase() + this.tempFxShow.substring(1)].apply(this);
+ },
+
+ hide: function()
+ {
+ this['fxHide' + this.tempFxHide.charAt(0).toUpperCase() + this.tempFxHide.substring(1)].apply(this);
+ },
+
+ /************************************************
+ * Effects
+ ************************************************/
+
+ /*** none ***/
+ fxShowNone: function()
+ {
+ this.bg.show();
+ this.modal.show();
+ },
+
+ fxHideNone: function()
+ {
+ this.modal.hide();
+ this.bg.hide();
+ },
+
+ /*** fade ***/
+ fxShowFade: function()
+ {
+ var _this = this;
+ this.bg.fadeIn(100, function(){ _this.modal.fadeIn(300); });
+ },
+
+ fxHideFade: function()
+ {
+ var _this = this;
+ this.modal.fadeOut(300, function(){ _this.bg.fadeOut(100); });
+ },
+
+ /*** slideUp ***/
+ fxShowSlideUp: function(){ this.fxSlide('show', 'top', this.outerY); },
+ fxHideSlideUp: function(){ this.fxSlide('hide', 'top', -1*this.outerY); },
+
+ /*** slideDown ***/
+ fxShowSlideDown: function(){ this.fxSlide('show', 'top', -1*this.outerY); },
+ fxHideSlideDown: function(){ this.fxSlide('hide', 'top', this.outerY); },
+
+ /*** slideLeft ***/
+ fxShowSlideLeft: function(){ this.fxSlide('show', 'left', this.outerX); },
+ fxHideSlideLeft: function(){ this.fxSlide('hide', 'left', -1*this.outerX); },
+
+ /*** slideRight ***/
+ fxShowSlideRight: function(){ this.fxSlide('show', 'left', -1*this.outerX); },
+ fxHideSlideRight: function(){ this.fxSlide('hide', 'left', this.outerX); },
+
+ /*** slide helper ***/
+ fxSlide: function(state, position, value)
+ {
+ var _this = this;
+ var css = {};
+ css[position] = value;
+
+ if(state === 'show')
+ {
+ this.modal.show();
+ var offset = this.modal.offset();
+ this.modal.hide();
+
+ this.modal.css(css);
+ this.bg.show();
+ this.modal.show();
+
+ css[position] = offset[position];
+
+ this.modal.animate(css);
+ }
+ else
+ {
+ var _this = this;
+ this.modal.animate(css, function(){ _this.modal.hide(); _this.bg.hide(); });
+ }
+ },
+
+ fxShowRotateUp: function(){ this.fxRotate(); this.fxShowSlideUp(); },
+ fxHideRotateUp: function(){ this.fxRotate(); this.fxHideSlideUp(); },
+
+ fxShowRotateDown: function(){ this.fxRotate(); this.fxShowSlideDown(); },
+ fxHideRotateDown: function(){ this.fxRotate(); this.fxHideSlideDown(); },
+
+ fxShowRotateLeft: function(){ this.fxRotate(); this.fxShowSlideLeft(); },
+ fxHideRotateLeft: function(){ this.fxRotate(); this.fxHideSlideLeft(); },
+
+ fxShowRotateRight: function(){ this.fxRotate(); this.fxShowSlideRight(); },
+ fxHideRotateRight: function(){ this.fxRotate(); this.fxHideSlideRight(); },
+
+ /*** rotate helper ***/
+ fxRotate: function()
+ {
+ var _this = this;
+ this.rotationDegree = 0;
+
+ this.rotationTimer = setInterval(function(){
+
+ _this.rotationDegree += 60;
+ _this.modal.css('-webkit-transform', 'rotate(' + _this.rotationDegree + 'deg)');
+ _this.modal.css('-moz-transform', 'rotate(' + _this.rotationDegree + 'deg)');
+
+ if(_this.rotationDegree % 360 === 0) clearInterval(_this.rotationTimer);
+ }, 80);
+ }
+ }
+})(jQuery);
\ No newline at end of file
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index 0b56017..d4d0848 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -1,1265 +1,1356 @@
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;
}
/***** 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/templates/main.html b/public_html/skins/default/templates/main.html
index 6d5968a..29fb6a0 100644
--- a/public_html/skins/default/templates/main.html
+++ b/public_html/skins/default/templates/main.html
@@ -1,80 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{$pagetitle}</title>
<link rel="stylesheet" href="{$skin_path}style.css" />
<link rel="stylesheet" href="{$skin_path}images/mimetypes/style.css" />
<link rel="shortcut icon" type="image/png" href="{$skin_path}images/favicon.png" />
<script src="js/jquery.min.js"></script>
<script src="js/files_api.js"></script>
<script src="js/files_ui.js"></script>
<script src="{$skin_path}ui.js"></script>
+ <script src="js/wModal.js"></script>
</head>
<body>
<div id="logo" onclick="document.location='.'"></div>
<div id="topmenu">
<span class="login">{$user.username}</span>
<span class="logout link" onclick="ui.main_logout()">{$engine->translate('logout')}</span>
</div>
<div id="navigation">{$main_menu}</div>
<div id="task_navigation">{$task_menu}</div>
<div id="content">
<div id="actionbar">
<a id="folder-create-button" onclick="ui.command('folder.create')" class="disabled"><span>{$engine->translate('folder.createtitle')}</span></a>
<form id="uploadform" name="uploadform" method="post" enctype="multipart/form-data">
<a id="file-upload-button" class="disabled"><span>{$engine->translate('file.upload')}</span></a>
</form>
<a id="file-search-button" onclick="ui.command('file.search')" class="disabled"><span>{$engine->translate('search')}</span></a>
</div>
<div id="folderlist">
<div class="scroller">
<table></table>
</div>
<div class="boxfooter">
<a href="#" onclick="ui.command('folder.edit')" alt="" id="folder-edit-button" class="button edit disabled">{$engine->translate('folder.edit')}</a>
<a href="#" onclick="ui.command('folder.delete')" alt="" id="folder-delete-button" class="button delete disabled">{$engine->translate('folder.delete')}</a>
</div>
</div>
<div id="forms">
{$engine->folder_create_form()}
{$engine->folder_edit_form()}
{$engine->file_search_form()}
</div>
<div id="taskcontent">
<div class="scroller">
<table id="filelist" class="list">
<thead>
<tr>
<td class="filename" onclick="file_list_sort('name', this)">{$engine->translate('file.name')}</td>
<td class="filemtime" onclick="file_list_sort('mtime', this)">{$engine->translate('file.mtime')}</td>
<td class="filesize" onclick="file_list_sort('size', this)">{$engine->translate('file.size')}</td>
</tr>
</thead>
</table>
</div>
<div class="boxfooter">
<a href="#" onclick="ui.command('file.delete')" alt="" id="file-delete-button" class="button delete disabled">{$engine->translate('file.delete')}</a>
</div>
</div>
</div>
<div id="footer">
{include file="footer.html"}
</div>
<div id="file-menu" class="popup">
<ul>
<li class="file-open"><a id="file-open-button" href="#">{$engine->translate('file.open')}</a></li>
<li class="file-download"><a id="file-download-button" href="#">{$engine->translate('file.download')}</a></li>
<li class="file-delete"><a id="file-delete-button" href="#">{$engine->translate('file.delete')}</a></li>
<li class="file-rename"><a id="file-rename-button" href="#">{$engine->translate('file.rename')}</a></li>
</ul>
</div>
<div id="file-drag-menu" class="popup">
<ul>
<li class="file-move"><a id="file-move-button" href="#">{$engine->translate('file.move')}</a></li>
<li class="file-copy"><a id="file-copy-button" href="#">{$engine->translate('file.copy')}</a></li>
</ul>
</div>
{$script}
</body>
</html>

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jun 10, 2:03 PM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
197129
Default Alt Text
(160 KB)

Event Timeline