Page MenuHomePhorge

No OneTemporary

Size
190 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/client/file_ui_client_file.php b/lib/client/file_ui_client_file.php
index 7b8581d..db2daa3 100644
--- a/lib/client/file_ui_client_file.php
+++ b/lib/client/file_ui_client_file.php
@@ -1,153 +1,127 @@
<?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_file extends file_ui
{
private $file;
private $filedata;
- private $viewer;
public function action_open()
{
$this->ui_init();
// assign default set of translations
$this->output->add_translation('saving', 'deleting');
$this->file = $this->get_input('file', 'GET'); // @TODO: error handling
// Set filename (without path?) as a page title
$this->page_title = $this->file;
$this->task_template = 'file_open';
// fetch file metadata
$this->file_data();
$this->output->set_env('file', $this->file);
$this->output->set_env('filedata', $this->filedata);
// read browser capabilities
if (isset($_GET['caps'])) {
$caps = explode(',', $_GET['caps']);
$capabilities = array('pdf' => 0, 'flash' => 0, 'tif' => 0);
foreach ($caps as $c) {
$capabilities[$c] = 1;
}
$this->output->set_env('browser_capabilities', $capabilities);
}
-
- if (!empty($_GET['viewer'])) {
- $this->viewer = $this->find_viewer($this->filedata['mimetype']);
- }
}
/**
* File content template object
*/
public function file_open_frame()
{
// check if viewer provides frame content
- if ($this->viewer) {
- if ($frame = $this->viewer->frame($this->file, $this->filedata['mimetype'])) {
- return $frame;
- }
+ if ($frame = $this->filedata['viewer']['frame']) {
+ return $frame;
}
// src attribute will be set on page load
return html::iframe(array('id' => 'file-content'));
}
/**
* Fetch and parse file metadata
*/
protected function file_data()
{
- $response = $this->api_get('file_info', array('file' => $this->file));
+ $response = $this->api_get('file_info', array('file' => $this->file,
+ 'viewer' => !empty($_GET['viewer'])));
+
$this->filedata = $response->get(); // @TODO: error handling
- $mimetype = $this->real_mimetype($this->filedata['type']);
+ $mimetype = file_utils::real_mimetype($this->filedata['type']);
// create href string for file load frame
- if (!empty($_GET['viewer'])) {
- $href = '?task=viewer&mimetype=' . urlencode($mimetype);
+ if ($href = $this->filedata['viewer']['href']) {
}
else {
- $href = 'api/?method=file_get';
-
- if ($mimetype != $this->filedata['type']) {
- $href .= '&force-type=' . urlencode($mimetype);
- }
+ $href = 'api/?method=file_get'
+ . '&file=' . urlencode($this->file)
+ . '&token=' . urlencode($_SESSION['user']['token']);
}
- $href .= '&file=' . urlencode($this->file)
- . '&token=' . urlencode($_SESSION['user']['token']);
-
$this->filedata['mimetype'] = $mimetype;
$this->filedata['href'] = $href;
}
- /**
- * Apply some fixes on file mimetype string
- */
- protected function real_mimetype($mimetype)
- {
- if (preg_match('/^text\/(.+)/i', $mimetype, $m)) {
- // fix pdf mimetype
- if (preg_match('/^(pdf|x-pdf)$/i', $m[1])) {
- $mimetype = 'application/pdf';
- }
- }
-
- return $mimetype;
- }
-
public function file_open_data()
{
$table = new html_table(array('cols' => 2));
// file name
$table->add('label', $this->translate('file.name').':');
$table->add('data filename', html::quote($this->filedata['name']));
// file type
// @TODO: human-readable type name
$table->add('label', $this->translate('file.type').':');
$table->add('data filetype', html::quote($this->filedata['type']));
// file size
$table->add('label', $this->translate('file.size').':');
$table->add('data filesize', html::quote($this->show_bytes($this->filedata['size'])));
// file modification time
$table->add('label', $this->translate('file.mtime').':');
$table->add('data filemtime', html::quote($this->filedata['mtime']));
// @TODO: for images: width, height, color depth, etc.
// @TODO: for text files: count of characters, lines, words
return $table->show();
}
}
diff --git a/lib/client/file_ui_client_main.php b/lib/client/file_ui_client_main.php
index 650811c..a1b7baa 100644
--- a/lib/client/file_ui_client_main.php
+++ b/lib/client/file_ui_client_main.php
@@ -1,167 +1,169 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2011-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class file_ui_client_main extends file_ui
{
public function action_default()
{
// assign default set of translations
$this->output->add_translation('saving', 'deleting', 'search', 'search.loading',
'collection.audio', 'collection.video', 'collection.image', 'collection.document',
'moving', 'copying', 'file.skip', 'file.skipall', 'file.overwrite', 'file.overwriteall',
'file.moveconfirm', 'file.progress'
);
+ $result = $this->api_get('mimetypes');
+
$this->output->set_env('search_threads', $this->config->get('files_search_threads'));
- $this->output->set_env('supported_mimetypes', $this->supported_mimetypes());
+ $this->output->set_env('supported_mimetypes', $result->get());
$this->ui_init();
}
public function folder_create_form()
{
$input_name = new html_inputfield(array(
'type' => 'text',
'name' => 'name',
'value' => '',
));
$input_parent = new html_checkbox(array(
'name' => 'parent',
'value' => '1',
'id' => 'folder-parent-checkbox',
));
$submit = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.folder_create_submit()',
'value' => $this->translate('form.submit'),
));
$cancel = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.folder_create_stop()',
'value' => $this->translate('form.cancel'),
));
$table = new html_table;
$table->add(null, $input_name->show() . $input_parent->show()
. html::label('folder-parent-checkbox', $this->translate('folder.under')));
$table->add('buttons', $submit->show() . $cancel->show());
$content = html::tag('fieldset', null,
html::tag('legend', null,
$this->translate('folder.createtitle')) . $table->show());
$form = html::tag('form', array(
'id' => 'folder-create-form',
'onsubmit' => 'ui.folder_create_submit(); return false'),
$content);
return $form;
}
public function folder_edit_form()
{
$input_name = new html_inputfield(array(
'type' => 'text',
'name' => 'name',
'value' => '',
));
$submit = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.folder_edit_submit()',
'value' => $this->translate('form.submit'),
));
$cancel = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.folder_edit_stop()',
'value' => $this->translate('form.cancel'),
));
$table = new html_table;
$table->add(null, $input_name->show());
$table->add('buttons', $submit->show() . $cancel->show());
$content = html::tag('fieldset', null,
html::tag('legend', null,
$this->translate('folder.edittitle')) . $table->show());
$form = html::tag('form', array(
'id' => 'folder-edit-form',
'onsubmit' => 'ui.folder_edit_submit(); return false'),
$content);
return $form;
}
public function file_search_form()
{
$input_name = new html_inputfield(array(
'type' => 'text',
'name' => 'name',
'value' => '',
));
$input_in1 = new html_inputfield(array(
'type' => 'radio',
'name' => 'all_folders',
'value' => '0',
'id' => 'all-folders-radio1',
));
$input_in2 = new html_inputfield(array(
'type' => 'radio',
'name' => 'all_folders',
'value' => '1',
'id' => 'all-folders-radio2',
));
$submit = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.file_search_submit()',
'value' => $this->translate('form.submit'),
));
$cancel = new html_inputfield(array(
'type' => 'button',
'onclick' => 'ui.file_search_stop()',
'value' => $this->translate('form.cancel'),
));
$table = new html_table;
$table->add(null, $input_name->show()
. $input_in1->show() . html::label('all-folders-radio1', $this->translate('search.in_current_folder'))
. $input_in2->show() . html::label('all-folders-radio2', $this->translate('search.in_all_folders'))
);
$table->add('buttons', $submit->show() . $cancel->show());
$content = html::tag('fieldset', null,
html::tag('legend', null,
$this->translate('file.search')) . $table->show());
$form = html::tag('form', array(
'id' => 'file-search-form',
'onsubmit' => 'ui.file_search_submit(); return false'),
$content);
return $form;
}
}
diff --git a/lib/client/file_ui_client_viewer.php b/lib/client/file_ui_client_viewer.php
deleted file mode 100644
index 75edbed..0000000
--- a/lib/client/file_ui_client_viewer.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?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_viewer extends file_ui
-{
- private $file;
-
-
- /**
- * Default viewer action
- */
- public function action_default()
- {
-// $this->ui_init();
-
- // @TODO: error handling
- $file = $this->get_input('file', 'GET');
- $type = $this->get_input('mimetype', 'GET');
- $viewer = $this->find_viewer($type);
-
- $viewer->output($file, $type);
- exit;
- }
-}
diff --git a/lib/file_api.php b/lib/file_api.php
index 4bed082..cafb349 100644
--- a/lib/file_api.php
+++ b/lib/file_api.php
@@ -1,588 +1,715 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class file_api
{
const ERROR_CODE = 500;
const OUTPUT_JSON = 'application/json';
const OUTPUT_HTML = 'text/html';
-
const PATH_SEPARATOR = '/';
public $session;
+ public $api;
private $app_name = 'Kolab File API';
- private $api;
private $conf;
private $output_type = self::OUTPUT_JSON;
private $config = array(
'date_format' => 'Y-m-d H:i',
'language' => 'en_US',
);
public function __construct()
{
$rcube = rcube::get_instance();
$rcube->add_shutdown_function(array($this, 'shutdown'));
$this->conf = $rcube->config;
$this->session_init();
}
/**
* Initialise backend class
*/
protected function api_init()
{
if ($this->api) {
return;
}
// @TODO: config
$driver = 'kolab';
$class = $driver . '_file_storage';
require_once $driver . '/' . $class . '.php';
$this->api = new $class;
}
/**
* Process the request and dispatch it to the requested service
*/
public function run()
{
$this->request = strtolower($_GET['method']);
// Check the session, authenticate the user
if (!$this->session_validate()) {
$this->session->destroy(session_id());
if ($this->request == 'authenticate') {
$this->session->regenerate_id(false);
if ($username = $this->authenticate()) {
$_SESSION['user'] = $username;
$_SESSION['time'] = time();
$_SESSION['config'] = $this->config;
$this->output_success(array(
'token' => session_id(),
'capabilities' => $this->capabilities(),
));
}
}
throw new Exception("Invalid session", 403);
}
// Call service method
$result = $this->request_handler($this->request);
// Send success response, errors should be handled by driver class
// by throwing exceptions or sending output by itself
$this->output_success($result);
}
/**
* Session validation check and session start
*/
private function session_validate()
{
$sess_id = rcube_utils::request_header('X-Session-Token') ?: $_REQUEST['token'];
if (empty($sess_id)) {
session_start();
return false;
}
session_id($sess_id);
session_start();
if (empty($_SESSION['user'])) {
return false;
}
$timeout = $this->conf->get('session_lifetime', 0) * 60;
if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) {
return false;
}
// update session time
$_SESSION['time'] = time();
return true;
}
/**
* Initializes session
*/
private function session_init()
{
$rcube = rcube::get_instance();
$sess_name = $this->conf->get('session_name');
$lifetime = $this->conf->get('session_lifetime', 0) * 60;
if ($lifetime) {
ini_set('session.gc_maxlifetime', $lifetime * 2);
}
ini_set('session.name', $sess_name ? $sess_name : 'file_api_sessid');
ini_set('session.use_cookies', 0);
ini_set('session.serialize_handler', 'php');
// use database for storing session data
$this->session = new rcube_session($rcube->get_dbh(), $this->conf);
$this->session->register_gc_handler(array($rcube, 'temp_gc'));
$this->session->register_gc_handler(array($rcube, 'cache_gc'));
$this->session->set_secret($this->conf->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
$this->session->set_ip_check($this->conf->get('ip_check'));
}
/**
* Script shutdown handler
*/
public function shutdown()
{
session_write_close();
// write performance stats to logs/console
if ($this->conf->get('devel_mode')) {
if (function_exists('memory_get_peak_usage'))
$mem = memory_get_peak_usage();
else if (function_exists('memory_get_usage'))
$mem = memory_get_usage();
$log = trim($this->request . ($mem ? sprintf(' [%.1f MB]', $mem/1024/1024) : ''));
if (defined('FILE_API_START')) {
rcube::print_timer(FILE_API_START, $log);
}
else {
rcube::console($log);
}
}
}
/**
* Authentication request handler (HTTP Auth)
*/
private function authenticate()
{
if (isset($_POST['username'])) {
$username = $_POST['username'];
$password = $_POST['password'];
}
else if (!empty($_SERVER['PHP_AUTH_USER'])) {
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
}
// when used with (f)cgi no PHP_AUTH* variables are available without defining a special rewrite rule
else if (!isset($_SERVER['PHP_AUTH_USER'])) {
// "Basic didhfiefdhfu4fjfjdsa34drsdfterrde..."
if (isset($_SERVER["REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REMOTE_USER"], 6));
}
else if (isset($_SERVER["REDIRECT_REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6));
}
else if (isset($_SERVER["Authorization"])) {
$basicAuthData = base64_decode(substr($_SERVER["Authorization"], 6));
}
else if (isset($_SERVER["HTTP_AUTHORIZATION"])) {
$basicAuthData = base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6));
}
if (isset($basicAuthData) && !empty($basicAuthData)) {
list($username, $password) = explode(":", $basicAuthData);
}
}
if (!empty($username)) {
$this->api_init();
$result = $this->api->authenticate($username, $password);
}
if (empty($result)) {
/*
header('WWW-Authenticate: Basic realm="' . $this->app_name .'"');
header('HTTP/1.1 401 Unauthorized');
exit;
*/
throw new Exception("Invalid password or username", file_api::ERROR_CODE);
}
return $username;
}
/**
* Storage/System method handler
*/
private function request_handler($request)
{
// handle "global" requests that don't require api driver
switch ($request) {
case 'ping':
return array();
case 'quit':
$this->session->destroy(session_id());
return array();
case 'configure':
foreach (array_keys($this->config) as $name) {
if (isset($_GET[$name])) {
$this->config[$name] = $_GET[$name];
}
}
$_SESSION['config'] = $this->config;
return $this->config;
case 'upload_progress':
return $this->upload_progress();
+ case 'mimetypes':
+ return $this->supported_mimetypes();
+
case 'capabilities':
// this one actually uses api driver, but we put it here
// because we'd need session for the api driver
return $this->capabilities();
}
// init API driver
$this->api_init();
// GET arguments
$args = &$_GET;
// POST arguments (JSON)
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$post = file_get_contents('php://input');
$args += (array) json_decode($post, true);
unset($post);
}
// handle request
switch ($request) {
case 'file_list':
$params = array('reverse' => !empty($args['reverse']) && rcube_utils::get_boolean($args['reverse']));
if (!empty($args['sort'])) {
$params['sort'] = strtolower($args['sort']);
}
if (!empty($args['search'])) {
$params['search'] = $args['search'];
if (!is_array($params['search'])) {
$params['search'] = array('name' => $params['search']);
}
}
return $this->api->file_list($args['folder'], $params);
case 'file_upload':
// for Opera upload frame response cannot be application/json
$this->output_type = self::OUTPUT_HTML;
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
$uploads = $this->upload();
$result = array();
foreach ($uploads as $file) {
$this->api->file_create($args['folder'] . self::PATH_SEPARATOR . $file['name'], $file);
unset($file['path']);
$result[$file['name']] = array(
'type' => $file['type'],
'size' => $file['size'],
);
}
return $result;
case 'file_delete':
$files = (array) $args['file'];
if (empty($files)) {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
foreach ($files as $file) {
$this->api->file_delete($file);
}
return;
case 'file_info':
if (!isset($args['file']) || $args['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
- return $this->api->file_info($args['file']);
+ $info = $this->api->file_info($args['file']);
+
+ if (!empty($args['viewer']) && rcube_utils::get_boolean($args['viewer'])) {
+ $this->file_viewer_info($args['file'], $info);
+ }
+
+ return $info;
case 'file_get':
$this->output_type = self::OUTPUT_HTML;
if (!isset($args['file']) || $args['file'] === '') {
header("HTTP/1.0 ".file_api::ERROR_CODE." Missing file name");
}
$params = array(
'force-download' => !empty($args['force-download']) && rcube_utils::get_boolean($args['force-download']),
'force-type' => $args['force-type'],
);
+ if (!empty($args['viewer'])) {
+ $this->file_view($args['file'], $args['viewer'], $args, $params);
+ }
+
try {
$this->api->file_get($args['file'], $params);
}
catch (Exception $e) {
header("HTTP/1.0 " . file_api::ERROR_CODE . " " . $e->getMessage());
}
exit;
case 'file_move':
case 'file_copy':
if (!isset($args['file']) || $args['file'] === '') {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
if (is_array($args['file'])) {
if (empty($args['file'])) {
throw new Exception("Missing file name", file_api::ERROR_CODE);
}
}
else {
if (!isset($args['new']) || $args['new'] === '') {
throw new Exception("Missing new file name", file_api::ERROR_CODE);
}
$args['file'] = array($args['file'] => $args['new']);
}
$overwrite = !empty($args['overwrite']) && rcube_utils::get_boolean($args['overwrite']);
$files = (array) $args['file'];
$errors = array();
foreach ($files as $file => $new_file) {
if ($new_file === '') {
throw new Exception("Missing new file name", file_api::ERROR_CODE);
}
if ($new_file === $file) {
throw new Exception("Old and new file name is the same", file_api::ERROR_CODE);
}
try {
$this->api->{$request}($file, $new_file);
}
catch (Exception $e) {
if ($e->getCode() == file_storage::ERROR_FILE_EXISTS) {
// delete existing file and do copy/move again
if ($overwrite) {
$this->api->file_delete($new_file);
$this->api->{$request}($file, $new_file);
}
// collect file-exists errors, so the client can ask a user
// what to do and skip or replace file(s)
else {
$errors[] = array(
'src' => $file,
'dst' => $new_file,
);
}
}
else {
throw $e;
}
}
}
if (!empty($errors)) {
return array('already_exist' => $errors);
}
return;
case 'folder_create':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
return $this->api->folder_create($args['folder']);
case 'folder_delete':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing folder name", file_api::ERROR_CODE);
}
return $this->api->folder_delete($args['folder']);
case 'folder_rename':
if (!isset($args['folder']) || $args['folder'] === '') {
throw new Exception("Missing source folder name", file_api::ERROR_CODE);
}
if (!isset($args['new']) || $args['new'] === '') {
throw new Exception("Missing destination folder name", file_api::ERROR_CODE);
}
if ($args['new'] === $args['folder']) {
return;
}
return $this->api->folder_rename($args['folder'], $args['new']);
case 'folder_list':
return $this->api->folder_list();
}
if ($request) {
throw new Exception("Unknown method", 501);
}
}
/**
* File uploads handler
*/
protected function upload()
{
$files = array();
if (is_array($_FILES['file']['tmp_name'])) {
foreach ($_FILES['file']['tmp_name'] as $i => $filepath) {
if ($err = $_FILES['file']['error'][$i]) {
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
throw new Exception("Maximum file size exceeded", file_api::ERROR_CODE);
}
throw new Exception("File upload failed", file_api::ERROR_CODE);
}
$files[] = array(
'path' => $filepath,
'name' => $_FILES['file']['name'][$i],
'size' => filesize($filepath),
'type' => rcube_mime::file_content_type($filepath, $_FILES['file']['name'][$i], $_FILES['file']['type']),
);
}
}
else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
throw new Exception("File upload failed", file_api::ERROR_CODE);
}
return $files;
}
/**
* File upload progress handler
*/
protected function upload_progress()
{
if (function_exists('apc_fetch')) {
$prefix = ini_get('apc.rfc1867_prefix');
$uploadid = rcube_utils::get_input_value('id', rcube_utils::INPUT_GET);
$status = apc_fetch($prefix . $uploadid);
if (!empty($status)) {
$status['percent'] = round($status['current']/$status['total']*100);
if ($status['percent'] < 100) {
$diff = time() - intval($status['start_time']);
// calculate time to end of uploading (in seconds)
$status['left'] = intval($diff * (100 - $status['percent']) / $status['percent']);
// average speed (bytes per second)
$status['rate'] = intval($status['current'] / $diff);
}
}
$status['id'] = $uploadid;
return $status; // id, done, total, current, percent, start_time, left, rate
}
throw new Exception("Not supported", file_api::ERROR_CODE);
}
/*
- * Returns storage (driver) capabilities
+ * Returns API capabilities
*/
protected function capabilities()
{
$this->api_init();
$caps = array();
// check support for upload progress
if (($progress_sec = $this->conf->get('upload_progress'))
&& ini_get('apc.rfc1867') && function_exists('apc_fetch')
) {
$caps[file_storage::CAPS_PROGRESS_NAME] = ini_get('apc.rfc1867_name');
$caps[file_storage::CAPS_PROGRESS_TIME] = $progress_sec;
}
foreach ($this->api->capabilities() as $name => $value) {
// skip disabled capabilities
if ($value !== false) {
$caps[$name] = $value;
}
}
return $caps;
}
+ /**
+ * Return mimetypes list supported by built-in viewers
+ *
+ * @return array List of mimetypes
+ */
+ protected function supported_mimetypes()
+ {
+ $mimetypes = array();
+ $dir = RCUBE_INSTALL_PATH . 'lib/viewers';
+
+ if ($handle = opendir($dir)) {
+ while (false !== ($file = readdir($handle))) {
+ if (preg_match('/^([a-z0-9_]+)\.php$/i', $file, $matches)) {
+ include_once $dir . '/' . $file;
+ $class = 'file_viewer_' . $matches[1];
+ $viewer = new $class($this);
+
+ $mimetypes = array_merge($mimetypes, $viewer->supported_mimetypes());
+ }
+ }
+ closedir($handle);
+ }
+
+ return $mimetypes;
+ }
+
+ /**
+ * Merge file viewer data into file info
+ */
+ protected function file_viewer_info($file, &$info)
+ {
+ if ($viewer = $this->find_viewer($info['type'])) {
+ $info['viewer'] = array();
+ if ($frame = $viewer->frame($file, $info['type'])) {
+ $info['viewer']['frame'] = $frame;
+ }
+ else if ($href = $viewer->href($file, $info['type'])) {
+ $info['viewer']['href'] = $href;
+ }
+ }
+ }
+
+ /**
+ * File vieweing request handler
+ */
+ protected function file_view($file, $viewer, &$args, &$params)
+ {
+ $path = RCUBE_INSTALL_PATH . "lib/viewers/$viewer.php";
+ $class = "file_viewer_$viewer";
+
+ if (!file_exists($path)) {
+ return;
+ }
+
+ // get file info
+ try {
+ $info = $this->api->file_info($file);
+ }
+ catch (Exception $e) {
+ header("HTTP/1.0 " . file_api::ERROR_CODE . " " . $e->getMessage());
+ exit;
+ }
+
+ include_once $path;
+ $viewer = new $class($this);
+
+ // check if specified viewer supports file type
+ // otherwise return (fallback to file_get action)
+ if (!$viewer->supports($info['type'])) {
+ return;
+ }
+
+ $viewer->output($file, $info['type']);
+ exit;
+ }
+
+ /**
+ * Return built-in viewer opbject for specified mimetype
+ *
+ * @return object Viewer object
+ */
+ protected function find_viewer($mimetype)
+ {
+ $dir = RCUBE_INSTALL_PATH . 'lib/viewers';
+
+ if ($handle = opendir($dir)) {
+ while (false !== ($file = readdir($handle))) {
+ if (preg_match('/^([a-z0-9_]+)\.php$/i', $file, $matches)) {
+ include_once $dir . '/' . $file;
+ $class = 'file_viewer_' . $matches[1];
+ $viewer = new $class($this);
+
+ if ($viewer->supports($mimetype)) {
+ return $viewer;
+ }
+ }
+ }
+ closedir($handle);
+ }
+ }
+
+ /**
+ * Returns complete File URL
+ *
+ * @param string $file File name (with path)
+ *
+ * @return string File URL
+ */
+ public function file_url($file)
+ {
+ return $_SERVER['SCRIPT_URI'] . '?method=file_get'
+ . '&file=' . urlencode($file)
+ . '&token=' . urlencode(session_id());
+ }
+
/**
* Send success response
*
* @param mixed $data Data
*/
public function output_success($data)
{
if (!is_array($data)) {
$data = array();
}
$this->output_send(array('status' => 'OK', 'result' => $data));
}
/**
* Send error response
*
* @param mixed $data Data
*/
public function output_error($errdata, $code = null)
{
if (is_string($errdata)) {
$errdata = array('reason' => $errdata);
}
if (!$code) {
$code = file_api::ERROR_CODE;
}
$this->output_send(array('status' => 'ERROR', 'code' => $code) + $errdata);
}
/**
* Send response
*
* @param mixed $data Data
*/
protected function output_send($data)
{
// Send response
header("Content-Type: {$this->output_type}; charset=utf-8");
echo json_encode($data);
exit;
}
}
diff --git a/lib/file_storage.php b/lib/file_storage.php
index 4f05ffb..2b0e73d 100644
--- a/lib/file_storage.php
+++ b/lib/file_storage.php
@@ -1,157 +1,158 @@
<?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
{
// capabilities
- const CAPS_MAX_UPLOAD = 'MAX_UPLOAD';
- const CAPS_ACL = 'ACL';
+ const CAPS_MAX_UPLOAD = 'MAX_UPLOAD';
+ const CAPS_ACL = 'ACL';
const CAPS_PROGRESS_NAME = 'PROGRESS_NAME';
const CAPS_PROGRESS_TIME = 'PROGRESS_TIME';
// error codes
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)
+ * @param resource $fp Print to file pointer instead (send no headers)
*
* @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 4f1d133..dd6ad34 100644
--- a/lib/file_ui.php
+++ b/lib/file_ui.php
@@ -1,701 +1,663 @@
<?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)
{
+ $rcube = rcube::get_instance();
+ $rcube->add_shutdown_function(array($this, 'shutdown'));
+
$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;
}
+ /**
+ * Script shutdown handler
+ */
+ public function shutdown()
+ {
+ // write performance stats to logs/console
+ if ($this->devel_mode) {
+ if (function_exists('memory_get_peak_usage'))
+ $mem = memory_get_peak_usage();
+ else if (function_exists('memory_get_usage'))
+ $mem = memory_get_usage();
+
+ $log = 'ui:' . $this->get_task() . ($this->action ? '/' . $this->action : '');
+ $log .= ($mem ? sprintf(' [%.1f MB]', $mem/1024/1024) : '');
+
+ if (defined('FILE_API_START')) {
+ rcube::print_timer(FILE_API_START, $log);
+ }
+ else {
+ rcube::console($log);
+ }
+ }
+ }
+
/**
* 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);
+ return sprintf('%.4f', microtime(true) - FILE_API_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/file_ui_api.php b/lib/file_ui_api.php
index 61c76b6..81c3832 100644
--- a/lib/file_ui_api.php
+++ b/lib/file_ui_api.php
@@ -1,266 +1,269 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
* Helper class to connect to the API
*/
class file_ui_api
{
/**
* @var HTTP_Request2
*/
private $request;
/**
* @var string
*/
private $base_url;
const ERROR_INTERNAL = 100;
const ERROR_CONNECTION = 200;
/**
* Class constructor.
*
* @param string $base_url Base URL of the Kolab API
*/
public function __construct($base_url)
{
$this->base_url = $base_url;
$this->init();
}
/**
* Initializes HTTP Request object.
*/
public function init()
{
require_once 'HTTP/Request2.php';
$this->request = new HTTP_Request2();
self::configure($this->request);
}
/**
* Configure HTTP_Request2 object
*
* @param HTTP_Request2 $request Request object
*/
public static function configure($request)
{
// Configure connection options
$config = rcube::get_instance()->config;
$options = array(
'ssl_verify_peer',
'ssl_verify_host',
'ssl_cafile',
'ssl_capath',
'ssl_local_cert',
'ssl_passphrase',
'follow_redirects',
);
foreach ($options as $optname) {
if (($optvalue = $config->get($optname)) !== null) {
try {
$request->setConfig($optname, $optvalue);
}
catch (Exception $e) {
// rcube::log_error("HTTP: " . $e->getMessage());
}
}
}
+
+ // proxy User-Agent
+ $request->setHeader('user-agent', $_SERVER['HTTP_USER_AGENT']);
}
/**
* Return API's base URL
*
* @return string Base URL
*/
public function base_url()
{
return $this->base_url;
}
/**
* Return HTTP_Request2 object
*
* @return HTTP_Request2 Request object
*/
public function request()
{
return $this->request;
}
/**
* Logs specified user into the API
*
* @param string $username User name
* @param string $password User password
*
* @return file_ui_api_result Request response
*/
public function login($username, $password)
{
$query = array(
'username' => $username,
'password' => $password,
);
$response = $this->post('authenticate', null, $query);
return $response;
}
/**
* Logs specified user out of the API
*
* @return bool True on success, False on failure
*/
public function logout()
{
$response = $this->get('quit');
return $response->get_error_code() ? false : true;
}
/**
* Sets session token value.
*
* @param string $token Token string
*/
public function set_session_token($token)
{
$this->request->setHeader('X-Session-Token', $token);
}
/**
* Gets capabilities of the API (according to logged in user).
*
* @return kolab_client_api_result Capabilities response
*/
public function get_capabilities()
{
$this->get('capabilities');
}
/**
* API's GET request.
*
* @param string $action Action name
* @param array $args Request arguments
*
* @return file_ui_api_result Response
*/
public function get($action, $args = array())
{
$url = $this->build_url($action, $args);
// Log::trace("Calling API GET: $url");
$this->request->setMethod(HTTP_Request2::METHOD_GET);
return $this->get_response($url);
}
/**
* API's POST request.
*
* @param string $action Action name
* @param array $url_args URL arguments
* @param array $post POST arguments
*
* @return kolab_client_api_result Response
*/
public function post($action, $url_args = array(), $post = array())
{
$url = $this->build_url($action, $url_args);
// Log::trace("Calling API POST: $url");
$this->request->setMethod(HTTP_Request2::METHOD_POST);
$this->request->addPostParameter($post);
return $this->get_response($url);
}
/**
* @param string $action Action GET parameter
* @param array $args GET parameters (hash array: name => value)
*
* @return Net_URL2 URL object
*/
private function build_url($action, $args)
{
$url = new Net_URL2($this->base_url);
$args['method'] = $action;
$url->setQueryVariables($args);
return $url;
}
/**
* HTTP Response handler.
*
* @param Net_URL2 $url URL object
*
* @return kolab_client_api_result Response object
*/
private function get_response($url)
{
try {
$this->request->setUrl($url);
$response = $this->request->send();
}
catch (Exception $e) {
return new file_ui_api_result(null,
self::ERROR_CONNECTION, $e->getMessage());
}
try {
$body = $response->getBody();
}
catch (Exception $e) {
return new file_ui_api_result(null,
self::ERROR_INTERNAL, $e->getMessage());
}
$body = @json_decode($body, true);
$err_code = null;
$err_str = null;
if (is_array($body) && (empty($body['status']) || $body['status'] != 'OK')) {
$err_code = !empty($body['code']) ? $body['code'] : self::ERROR_INTERNAL;
$err_str = !empty($body['reason']) ? $body['reason'] : 'Unknown error';
}
else if (!is_array($body)) {
$err_code = self::ERROR_INTERNAL;
$err_str = 'Unable to decode response';
}
return new file_ui_api_result($body, $err_code, $err_str);
}
}
diff --git a/lib/file_utils.php b/lib/file_utils.php
index 67a74ef..1242ebf 100644
--- a/lib/file_utils.php
+++ b/lib/file_utils.php
@@ -1,103 +1,123 @@
<?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_utils
{
static $class_map = array(
'document' => array(
// text
'text/',
'application/rtf',
'application/x-rtf',
'application/xml',
// office
'application/wordperfect',
'application/excel',
'application/msword',
'application/msexcel',
'application/mspowerpoint',
'application/vnd.ms-word',
'application/vnd.ms-excel',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument',
'application/vnd.oasis.opendocument',
'application/vnd.sun.xml.calc',
'application/vnd.sun.xml.writer',
'application/vnd.stardivision.calc',
'application/vnd.stardivision.writer',
// pdf
'application/pdf',
'application/x-pdf',
'application/acrobat',
'application/vnd.pdf',
),
'audio' => array(
'audio/',
),
'video' => array(
'video/',
),
'image' => array(
'image/',
'application/dxf',
'application/acad',
),
'empty' => array(
'application/x-empty',
),
);
/**
* Return list of mimetype prefixes for specified file class
*
* @param string $class Class name
*
* @return array List of mimetype prefixes
*/
static function class2mimetypes($class)
{
return isset(self::$class_map[$class]) ? self::$class_map[$class] : self::$class_map['empty'];
}
/**
* Finds class of specified mimetype
*
* @param string $mimetype File mimetype
*
* @return string Class name
*/
static function mimetype2class($mimetype)
{
$mimetype = strtolower($mimetype);
foreach (self::$class_map as $class => $prefixes) {
foreach ($prefixes as $prefix) {
if (strpos($mimetype, $prefix) === 0) {
return $class;
}
}
}
}
+
+ /**
+ * Apply some fixes on file mimetype string
+ *
+ * @param string $mimetype File type
+ *
+ * @return string File type
+ */
+ static function real_mimetype($mimetype)
+ {
+ if (preg_match('/^text\/(.+)/i', $mimetype, $m)) {
+ // fix pdf mimetype
+ if (preg_match('/^(pdf|x-pdf)$/i', $m[1])) {
+ $mimetype = 'application/pdf';
+ }
+ }
+
+ return $mimetype;
+ }
+
}
diff --git a/lib/file_ui_viewer.php b/lib/file_viewer.php
similarity index 87%
rename from lib/file_ui_viewer.php
rename to lib/file_viewer.php
index d100723..48c49d9 100644
--- a/lib/file_ui_viewer.php
+++ b/lib/file_viewer.php
@@ -1,85 +1,95 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
* Abstract viewer class
*/
-abstract class file_ui_viewer
+abstract class file_viewer
{
protected $mimetypes = array();
- protected $ui;
+ protected $api;
/**
* Class constructor
*
- * @param file_ui File UI object
+ * @param file_api File API object
*/
- public function __construct($ui)
+ public function __construct($api)
{
- $this->ui = $ui;
+ $this->api = $api;
}
/**
* Returns list of supported mimetype
*
* @return array List of mimetypes
*/
public function supported_mimetypes()
{
return $this->mimetypes;
}
/**
* Check if mimetype is supported by the viewer
*
* @param string $mimetype File type
*
* @return bool
*/
public function supports($mimetype)
{
return in_array($mimetype, $this->mimetypes);
}
/**
* Print output and exit
*
* @param string $file File name
* @param string $mimetype File type
*/
public function output($file, $mimetype = null)
{
}
/**
* Return output of file content area
*
* @param string $file File name
* @param string $mimetype File type
*/
public function frame($file, $mimetype = null)
{
}
+
+ /**
+ * Return file URL of file content area
+ *
+ * @param string $file File name
+ * @param string $mimetype File type
+ */
+ public function href($file, $mimetype = null)
+ {
+ }
}
diff --git a/lib/kolab/kolab_file_storage.php b/lib/kolab/kolab_file_storage.php
index 6b81509..e5b6a4e 100644
--- a/lib/kolab/kolab_file_storage.php
+++ b/lib/kolab/kolab_file_storage.php
@@ -1,770 +1,777 @@
<?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)
+ * @param string $file_name Name of a file (with folder path)
+ * @param array $params Parameters (force-download)
+ * @param resource $fp Print to file pointer instead (send no headers)
*
* @throws Exception
*/
- public function file_get($file_name, $params = array())
+ public function file_get($file_name, $params = array(), $fp = null)
{
$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);
+ // write to file pointer, send no headers
+ if ($fp) {
+ $folder->get_attachment($file['_msguid'], $file['fileid'], $file['_mailbox'], false, $fp);
+ return;
+ }
+
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'];
+ $mimetype = file_utils::real_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_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_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/viewers/media.php b/lib/viewers/media.php
index 82d9024..1bf7d49 100644
--- a/lib/viewers/media.php
+++ b/lib/viewers/media.php
@@ -1,106 +1,107 @@
<?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 integrating HTML5 audio/video player from http://mediaelementjs.com
*/
-class file_ui_viewer_media extends file_ui_viewer
+class file_viewer_media extends file_viewer
{
protected $mimetypes = array(
'video/mp4',
'video/m4v',
'video/ogg',
'video/webm',
// 'video/3gpp',
// 'video/flv',
// 'video/x-flv',
'application/ogg',
'audio/mp3',
'audio/mpeg',
'audio/ogg',
'audio/wav',
'audio/flv',
'audio/x-mpeg',
'audio/x-ogg',
'audio/x-wav',
'audio/x-flv',
);
/**
* Class constructor
*
- * @param file_ui File UI object
+ * @param file_api File API object
*/
- public function __construct($ui)
+ public function __construct($api)
{
// @TODO: disable types not supported by some browsers
- $this->ui = $ui;
+ $this->api = $api;
}
/**
* Return output of file content area
*
* @param string $file File name
* @param string $mimetype File type
*/
public function frame($file, $mimetype = null)
{
- $file_uri = htmlentities($this->ui->file_url($file));
+ $path = $_SERVER['SCRIPT_URI'];
+ $file_uri = htmlentities($this->api->file_url($file));
$mimetype = htmlentities($mimetype);
$source = "<source src=\"$file_uri\" type=\"$mimetype\"></source>";
if (preg_match('/^audio/', $mimetype)) {
$tag = 'audio';
}
else {
$tag = 'video';
}
return <<<EOT
- <link rel="stylesheet" type="text/css" href="viewers/media/mediaelementplayer.css" />
- <script type="text/javascript" src="js/jquery.min.js"></script>
- <script type="text/javascript" src="viewers/media/mediaelement-and-player.js"></script>
+ <link rel="stylesheet" type="text/css" href="{$path}viewers/media/mediaelementplayer.css" />
+ <script type="text/javascript" src="{$path}js/jquery.min.js"></script>
+ <script type="text/javascript" src="{$path}viewers/media/mediaelement-and-player.js"></script>
<$tag id="media-player" controls preload="auto">$source</$tag>
<style>
.mejs-container { text-align: center; }
</style>
<script>
var content_frame = $('#media-player').parent(),
height = content_frame.height(),
width = content_frame.width(),
player = new MediaElementPlayer('#media-player', {
videoHeight: height, audioHeight: height, videoWidth: width, audioWidth: width
});
player.pause();
player.play();
// add player resize handler
$(window).resize(function() {
player.setPlayerSize(content_frame.width(), content_frame.height());
});
</script>
EOT;
}
}
diff --git a/lib/viewers/odf.php b/lib/viewers/odf.php
index 099f720..5a5112b 100644
--- a/lib/viewers/odf.php
+++ b/lib/viewers/odf.php
@@ -1,123 +1,140 @@
<?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_viewer_odf extends file_ui_viewer
+/**
+ * Class integrating ODF documents viewer from http://webodf.org
+ */
+class file_viewer_odf extends file_viewer
{
protected $mimetypes = array(
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.graphics',
'application/vnd.oasis.opendocument.chart',
// 'application/vnd.oasis.opendocument.formula',
'application/vnd.oasis.opendocument.image',
'application/vnd.oasis.opendocument.text-master',
// 'application/vnd.sun.xml.base',
// 'application/vnd.oasis.opendocument.base',
// 'application/vnd.oasis.opendocument.database',
'application/vnd.oasis.opendocument.text-template',
'application/vnd.oasis.opendocument.spreadsheet-template',
'application/vnd.oasis.opendocument.presentation-template',
'application/vnd.oasis.opendocument.graphics-template',
'application/vnd.oasis.opendocument.chart-template',
// 'application/vnd.oasis.opendocument.formula-template',
'application/vnd.oasis.opendocument.image-template',
);
/**
* Class constructor
*
- * @param file_ui File UI object
+ * @param file_api File API object
*/
- public function __construct($ui)
+ public function __construct($api)
{
- $this->ui = $ui;
+ $this->api = $api;
}
/**
* Returns list of supported mimetype
*
* @return array List of mimetypes
*/
public function supported_mimetypes()
{
// @TODO: check supported browsers
return $this->mimetypes;
}
/**
* Check if mimetype is supported by the viewer
*
* @param string $mimetype File type
*
* @return bool
*/
public function supports($mimetype)
{
return in_array($mimetype, $this->mimetypes);
}
/**
* Return output of file content area
*
* @param string $file File name
* @param string $mimetype File type
*/
public function frame($file, $mimetype = null)
{
// we use iframe method, see output()
}
+ /**
+ * Return file viewer URL
+ *
+ * @param string $file File name
+ * @param string $mimetype File type
+ */
+ public function href($file, $mimetype = null)
+ {
+ return $_SERVER['SCRIPT_URI'] . '?method=file_get'
+ . '&viewer=odf'
+ . '&file=' . urlencode($file)
+ . '&token=' . urlencode(session_id());
+ }
+
/**
* Print output and exit
*
* @param string $file File name
* @param string $mimetype File type
*/
public function output($file, $mimetype = null)
{
- $file_uri = $this->ui->file_url($file);
+ $file_uri = $this->api->file_url($file);
echo <<<EOT
<html>
<head>
<link rel="stylesheet" type="text/css" href="viewers/odf/webodf.css" />
<script type="text/javascript" src="viewers/odf/webodf.js" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
function init() {
var odfelement = document.getElementById("odf"),
odfcanvas = new odf.OdfCanvas(odfelement);
odfcanvas.load("$file_uri");
}
window.setTimeout(init, 0);
</script>
</head>
<body>
<div id="odf"></div>
</body>
</html>
EOT;
}
}
diff --git a/lib/viewers/pdf.php b/lib/viewers/pdf.php
index 51aef6a..442714b 100644
--- a/lib/viewers/pdf.php
+++ b/lib/viewers/pdf.php
@@ -1,48 +1,61 @@
<?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_viewer_pdf extends file_ui_viewer
+/**
+ * Class integrating PDF viewer from https://github.com/mozilla/pdf.js
+ */
+class file_viewer_pdf extends file_viewer
{
protected $mimetypes = array(
'application/pdf',
'application/x-pdf',
'application/acrobat',
'applications/vnd.pdf',
'text/pdf',
'text/x-pdf',
);
+ /**
+ * Class constructor
+ *
+ * @param file_api File API object
+ */
+ public function __construct($api)
+ {
+ // @TODO: disable types not supported by some browsers
+ $this->api = $api;
+ }
/**
- * Print output and exit
+ * Return file viewer URL
*
* @param string $file File name
* @param string $mimetype File type
*/
- public function output($file, $mimetype = null)
+ public function href($file, $mimetype = null)
{
- header('Location: viewers/pdf/viewer.html?file=' . urlencode($this->ui->file_url($file)));
- exit;
+ return $_SERVER['SCRIPT_URI'] . 'viewers/pdf/viewer.html'
+ . '?file=' . urlencode($this->api->file_url($file));
}
}
diff --git a/lib/viewers/text.php b/lib/viewers/text.php
index 1f9c019..236396d 100644
--- a/lib/viewers/text.php
+++ b/lib/viewers/text.php
@@ -1,150 +1,183 @@
<?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 integrating text editor http://ajaxorg.github.io/ace
*/
-class file_ui_viewer_text extends file_ui_viewer
+class file_viewer_text extends file_viewer
{
/**
* Mimetype to tokenizer map
*
* @var array
*/
protected $mimetypes = array(
'text/plain' => 'text',
'text/html' => 'html',
'text/javascript' => 'javascript',
'text/ecmascript' => 'javascript',
'text/x-c' => 'c_cpp',
'text/css' => 'css',
'text/x-java-source' => 'java',
'text/x-php' => 'php',
'text/x-sh' => 'sh',
'text/xml' => 'xml',
'application/xml' => 'xml',
'application/x-vbscript' => 'vbscript',
'message/rfc822' => 'text',
);
/**
* Returns list of supported mimetype
*
* @return array List of mimetypes
*/
public function supported_mimetypes()
{
- return array_keys($this->mimetypes);
+ // we return only mimetypes not starting with text/
+ $mimetypes = array();
+
+ foreach (array_keys($this->mimetypes) as $type) {
+ if (strpos($type, 'text/') !== 0) {
+ $mimetypes[] = $type;
+ }
+ }
+
+ return $mimetypes;
}
/**
* Check if mimetype is supported by the viewer
*
* @param string $mimetype File type
*
* @return bool
*/
public function supports($mimetype)
{
- return $this->mimetypes[$mimetype] || preg_match('/^text\//', $mimetype);
+ return $this->mimetypes[$mimetype] || preg_match('/^text\/(?!(pdf|x-pdf))/', $mimetype);
}
/**
* Print file content
*/
protected function print_file($file)
{
- $observer = new file_viewer_request_observer;
- $request = $this->ui->api->request();
+ $stdout = fopen('php://output', 'w');
- $request->attach($observer);
- $this->ui->api->get('file_get', array('file' => $file, 'force-type' => 'text/plain'));
- $request->detach($observer);
+ stream_filter_register('file_viewer_text', 'file_viewer_content_filter');
+ stream_filter_append($stdout, 'file_viewer_text');
+
+ $this->api->api->file_get($file, array(), $stdout);
+ }
+
+ /**
+ * Return file viewer URL
+ *
+ * @param string $file File name
+ * @param string $mimetype File type
+ */
+ public function href($file, $mimetype = null)
+ {
+ return $_SERVER['SCRIPT_URI'] . '?method=file_get'
+ . '&viewer=text'
+ . '&file=' . urlencode($file)
+ . '&token=' . urlencode(session_id());
}
/**
* Print output and exit
*
* @param string $file File name
* @param string $mimetype File type
*/
public function output($file, $mimetype = null)
{
$mode = $this->mimetypes[$mimetype] ?: 'text';
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Editor</title>
<script src="viewers/text/ace.js" type="text/javascript" charset="utf-8"></script>
<style>
#editor { top: 0; right: 0; bottom: 0; left: 0; position: absolute; font-size: 14px; padding: 0; margin: 0; }
.ace_search_options { float: right; }
</style>
</head>
<body>
<pre id="editor">';
$this->print_file($file);
echo "</pre>
<script>
var editor = ace.edit('editor'),
session = editor.getSession();
editor.focus();
editor.setReadOnly(true);
session.setMode('ace/mode/$mode');
</script>
</body>
</html>";
}
}
/**
- * Observer for HTTP_Request2 implementing file body printing
- * with HTML special characters "escaping" for use in HTML code
+ * PHP stream filter to detect escape html special chars in a file
*/
-class file_viewer_request_observer implements SplObserver
+class file_viewer_content_filter extends php_user_filter
{
- public function update(SplSubject $subject)
+ private $buffer = '';
+ private $cutoff = 2048;
+
+ function onCreate()
{
- $event = $subject->getLastEvent();
+ $this->cutoff = rand(2048, 3027);
+ return true;
+ }
+
+ function filter($in, $out, &$consumed, $closing)
+ {
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ $bucket->data = htmlspecialchars($bucket->data, ENT_COMPAT | ENT_HTML401 | ENT_IGNORE);
+ $this->buffer .= $bucket->data;
- switch ($event['name']) {
- case 'receivedHeaders':
- case 'receivedBody':
- break;
+ // keep buffer small enough
+ if (strlen($this->buffer) > 4096) {
+ $this->buffer = substr($this->buffer, $this->cutoff);
+ }
- case 'receivedBodyPart':
- case 'receivedEncodedBodyPart':
- echo htmlspecialchars($event['data'], ENT_COMPAT | ENT_HTML401 | ENT_IGNORE);
- break;
+ $consumed += $bucket->datalen; // or strlen($bucket->data)?
+ stream_bucket_append($out, $bucket);
}
+
+ return PSFS_PASS_ON;
}
}
diff --git a/public_html/api/js b/public_html/api/js
new file mode 120000
index 0000000..f662e4e
--- /dev/null
+++ b/public_html/api/js
@@ -0,0 +1 @@
+../js
\ No newline at end of file
diff --git a/public_html/api/viewers b/public_html/api/viewers
new file mode 120000
index 0000000..b18d9e0
--- /dev/null
+++ b/public_html/api/viewers
@@ -0,0 +1 @@
+../../lib/viewers
\ No newline at end of file
diff --git a/public_html/js/files_api.js b/public_html/js/files_api.js
index 9fce3c2..d57dae2 100644
--- a/public_html/js/files_api.js
+++ b/public_html/js/files_api.js
@@ -1,544 +1,540 @@
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
function files_api()
{
var ref = this;
// default config
this.translations = {};
this.env = {
url: 'api/',
directory_separator: '/',
resources_dir: 'resources'
};
/*********************************************************/
/********* Basic utilities *********/
/*********************************************************/
// set environment variable(s)
this.set_env = function(p, value)
{
if (p != null && typeof p === 'object' && !value)
for (var n in p)
this.env[n] = p[n];
else
this.env[p] = value;
};
// add a localized label(s) to the client environment
this.tdef = function(p, value)
{
if (typeof p == 'string')
this.translations[p] = value;
else if (typeof p == 'object')
$.extend(this.translations, p);
};
// return a localized string
this.t = function(label)
{
if (this.translations[label])
return this.translations[label];
else
return label;
};
// print a message into browser console
this.log = function(msg)
{
if (window.console && console.log)
console.log(msg);
};
/********************************************************/
/********* Remote request methods *********/
/********************************************************/
// send a http POST request to the API service
this.post = function(action, data, func)
{
var url = this.env.url + '?method=' + action;
if (!func) func = 'response';
this.set_request_time();
return $.ajax({
type: 'POST', url: url, data: JSON.stringify(data), dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: function(response) { ref[func](response); },
error: function(o, status, err) { ref.http_error(o, status, err); },
cache: false,
beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); }
});
};
// send a http GET request to the API service
this.get = function(action, data, func)
{
var url = this.env.url;
if (!func) func = 'response';
this.set_request_time();
data.method = action;
return $.ajax({
type: 'GET', url: url, data: data,
success: function(response) { ref[func](response); },
error: function(o, status, err) { ref.http_error(o, status, err); },
cache: false,
beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); }
});
};
// send request with auto-selection of POST/GET method
this.request = function(action, data, func)
{
// Use POST for modification actions with probable big request size
var method = /(create|delete|move|copy|rename)/.test(action) ? 'post' : 'get';
this[method](action, data, func);
};
// handle HTTP request errors
this.http_error = function(request, status, err)
{
var errmsg = request.statusText;
this.set_busy(false);
request.abort();
if (request.status && errmsg)
this.display_message(this.t('servererror') + ' (' + errmsg + ')', 'error');
};
this.response = function(response)
{
this.update_request_time();
this.set_busy(false);
return this.response_parse(response);
};
this.response_parse = function(response)
{
if (!response || response.status != 'OK') {
// Logout on invalid-session error
if (response && response.code == 403)
this.logout(response);
else
this.display_message(response && response.reason ? response.reason : this.t('servererror'), 'error');
return false;
}
return true;
};
/*********************************************************/
/********* Utilities *********/
/*********************************************************/
// Called on "session expired" session
this.logout = function(response) {};
// set state
this.set_busy = function(a, message) {};
// displays error message
this.display_message = function(label) {};
// called when a request timed out
this.request_timed_out = function() {};
// called on start of the request
this.set_request_time = function() {};
// called on request response
this.update_request_time = function() {};
/*********************************************************/
/********* Helpers *********/
/*********************************************************/
// compose a valid url with the given parameters
this.url = function(action, query)
{
var k, param = {},
querystring = typeof query === 'string' ? '&' + query : '';
if (typeof action !== 'string')
query = action;
else if (!query || typeof query !== 'object')
query = {};
// overwrite task name
if (action)
query.method = action;
// remove undefined values
for (k in query) {
if (query[k] !== undefined && query[k] !== null)
param[k] = query[k];
}
return '?' + $.param(param) + querystring;
};
// Folder list parser, converts it into structure
this.folder_list_parse = function(list)
{
var i, n, items, items_len, f, tmp, folder, num = 1,
len = list.length, folders = {};
for (i=0; i<len; i++) {
folder = list[i];
items = folder.split(this.env.directory_separator);
items_len = items.length;
for (n=0; n<items_len-1; n++) {
tmp = items.slice(0,n+1);
f = tmp.join(this.env.directory_separator);
if (!folders[f])
folders[f] = {name: tmp.pop(), depth: n, id: 'f'+num++, virtual: 1};
}
folders[folder] = {name: items.pop(), depth: items_len-1, id: 'f'+num++};
}
return folders;
};
// folder structure presentation (structure icons)
this.folder_list_tree = function(folders)
{
var i, n, diff, tree = [], folder;
for (i in folders) {
items = i.split(this.env.directory_separator);
items_len = items.length;
// skip root
if (items_len < 2) {
tree = [];
continue;
}
folders[i].tree = [1];
for (n=0; n<tree.length; n++) {
folder = tree[n];
diff = folders[folder].depth - (items_len - 1);
if (diff >= 0)
folders[folder].tree[diff] = folders[folder].tree[diff] ? folders[folder].tree[diff] + 2 : 2;
}
tree.push(i);
}
for (i in folders) {
if (tree = folders[i].tree) {
var html = '', divs = [];
for (n=0; n<folders[i].depth; n++) {
if (tree[n] > 2)
divs.push({'class': 'l3', width: 15});
else if (tree[n] > 1)
divs.push({'class': 'l2', width: 15});
else if (tree[n] > 0)
divs.push({'class': 'l1', width: 15});
// separator
else if (divs.length && !divs[divs.length-1]['class'])
divs[divs.length-1].width += 15;
else
divs.push({'class': null, width: 15});
}
for (n=divs.length-1; n>=0; n--) {
if (divs[n]['class'])
html += '<span class="tree '+divs[n]['class']+'" />';
else
html += '<span style="width:'+divs[n].width+'px" />';
}
if (html)
$('#' + folders[i].id + ' span.branch').html(html);
}
}
};
// convert content-type string into class name
this.file_type_class = function(type)
{
if (!type)
return '';
type = type.replace(/[^a-z0-9]/g, '_');
return type;
};
// convert bytes into number with size unit
this.file_size = function(size)
{
if (size >= 1073741824)
return parseFloat(size/1073741824).toFixed(2) + ' GB';
if (size >= 1048576)
return parseFloat(size/1048576).toFixed(2) + ' MB';
if (size >= 1024)
return parseInt(size/1024) + ' kB';
return parseInt(size || 0) + ' B';
};
// Extract file name from full path
this.file_name = function(path)
{
var path = path.split(this.env.directory_separator);
return path.pop();
};
// Extract file path from full path
this.file_path = function(path)
{
var path = path.split(this.env.directory_separator);
path.pop();
return path.join(this.env.directory_separator);
};
// compare two sortable objects
this.sort_compare = function(data1, data2)
{
var key = this.env.sort_col || 'name';
if (key == 'mtime')
key = 'modified';
data1 = data1[key];
data2 = data2[key];
if (key == 'size' || key == 'modified')
// numeric comparison
return this.env.sort_reverse ? data2 - data1 : data1 - data2;
else {
// use Array.sort() for sting comparison
var arr = [data1, data2];
arr.sort(function (a, b) {
// @TODO: use localeCompare() arguments for better results
return a.localeCompare(b);
});
if (this.env.sort_reverse)
arr.reverse();
return arr[0] === data2 ? 1 : -1;
}
};
// Checks if specified mimetype is supported natively by the browser (return 1)
// or can be displayed in the browser using File API viewer (return 2)
this.file_type_supported = function(type)
{
- var i, t, img = 'jpg|jpeg|gif|bmp|png',
- regexps = [
- /^text\/(?!(pdf|x-pdf))/i,
- /^message\/rfc822/i,
- ];
+ var i, t, regexps = [], img = 'jpg|jpeg|gif|bmp|png',
+ caps = this.env.browser_capabilities || {};
- if (this.env.browser_capabilities.tif)
+ if (caps.tif)
img += '|tiff';
if ((new RegExp('^image/(' + img + ')$', 'i')).test(type))
return 1;
- // prefer text viewer
- for (i in regexps)
- if (regexps[i].test(type))
- return 2;
+ // prefer text viewer for any text type
+ if (/^text\/(?!(pdf|x-pdf))/i.test(type))
+ return 2;
- if (this.env.browser_capabilities.pdf) {
+ if (caps.pdf) {
regexps.push(/^application\/(pdf|x-pdf|acrobat|vnd.pdf)/i);
regexps.push(/^text\/(pdf|x-pdf)/i);
}
- if (this.env.browser_capabilities.flash)
+ if (caps.flash)
regexps.push(/^application\/x-shockwave-flash/i);
for (i in regexps)
if (regexps[i].test(type))
return 1;
for (i in navigator.mimeTypes) {
t = navigator.mimeTypes[i].type;
if (t == type && navigator.mimeTypes[i].enabledPlugin)
return 1;
}
// types with viewer support
if ($.inArray(type, this.env.supported_mimetypes) > -1)
return 2;
};
// Return browser capabilities
this.browser_capabilities = function()
{
var i, caps = [], ctypes = ['pdf', 'flash', 'tif'];
for (i in ctypes)
if (this.env.browser_capabilities[ctypes[i]])
caps.push(ctypes[i]);
return caps;
};
// Checks browser capabilities eg. PDF support, TIF support
this.browser_capabilities_check = function()
{
if (!this.env.browser_capabilities)
this.env.browser_capabilities = {};
if (this.env.browser_capabilities.pdf === undefined)
this.env.browser_capabilities.pdf = this.pdf_support_check();
if (this.env.browser_capabilities.flash === undefined)
this.env.browser_capabilities.flash = this.flash_support_check();
if (this.env.browser_capabilities.tif === undefined)
this.tif_support_check();
};
this.tif_support_check = function()
{
var img = new Image(), ref = this;
img.onload = function() { ref.env.browser_capabilities.tif = 1; };
img.onerror = function() { ref.env.browser_capabilities.tif = 0; };
img.src = this.env.resources_dir + '/blank.tif';
};
this.pdf_support_check = function()
{
var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {},
plugins = navigator.plugins,
len = plugins.length,
regex = /Adobe Reader|PDF|Acrobat/i,
ref = this;
if (plugin && plugin.enabledPlugin)
return 1;
if (window.ActiveXObject) {
try {
if (axObj = new ActiveXObject("AcroPDF.PDF"))
return 1;
}
catch (e) {}
try {
if (axObj = new ActiveXObject("PDF.PdfCtrl"))
return 1;
}
catch (e) {}
}
for (i=0; i<len; i++) {
plugin = plugins[i];
if (typeof plugin === 'String') {
if (regex.test(plugin))
return 1;
}
else if (plugin.name && regex.test(plugin.name))
return 1;
}
var obj = document.createElement('OBJECT');
obj.onload = function() { ref.env.browser_capabilities.pdf = 1; };
obj.onerror = function() { ref.env.browser_capabilities.pdf = 0; };
obj.style.display = 'none';
obj.type = 'application/pdf';
obj.data = this.env.resources_dir + '/blank.pdf';
document.body.appendChild(obj);
return 0;
};
this.flash_support_check = function()
{
var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/x-shockwave-flash"] : {};
if (plugin && plugin.enabledPlugin)
return 1;
if (window.ActiveXObject) {
try {
if (axObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))
return 1;
}
catch (e) {}
}
return 0;
};
};
// Add escape() method to RegExp object
// http://dev.rubyonrails.org/changeset/7271
RegExp.escape = function(str)
{
return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
// make a string URL safe (and compatible with PHP's rawurlencode())
function urlencode(str)
{
if (window.encodeURIComponent)
return encodeURIComponent(str).replace('*', '%2A');
return escape(str)
.replace('+', '%2B')
.replace('*', '%2A')
.replace('/', '%2F')
.replace('@', '%40');
};
function escapeHTML(str)
{
return str === undefined ? '' : String(str)
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
};
function object_is_empty(obj)
{
if (obj)
for (var i in obj)
if (i !== null)
return true;
return false;
}
diff --git a/public_html/js/files_ui.js b/public_html/js/files_ui.js
index c11c586..dc63802 100644
--- a/public_html/js/files_ui.js
+++ b/public_html/js/files_ui.js
@@ -1,1645 +1,1647 @@
/*
+--------------------------------------------------------------------------+
| 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.uploads = {};
this.ie = document.all && !window.opera;
this.env = {
url: 'api/',
sort_col: 'name',
sort_reverse: 0,
search_threads: 1,
- directory_separator: '/'
+ directory_separator: '/',
+ resources_dir: 'resources'
};
// 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.request('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.request('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.request('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.request('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.request('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.request('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.request('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.request('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.request('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.request('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.request('file_move', {file: list}, 'file_move_response');
};
// file move response handler
this.file_move_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, 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_upload', function(e, frame, folder) {
var doc, response, res;
try {
doc = frame.contentDocument ? frame.contentDocument : frame.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 ((res = ui.response_parse(response)) && folder == ui.env.folder)
ui.file_list();
return res;
});
}
};
/*********************************************************/
/********* 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', '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 supported (and handler exists)
if (this.env.capabilities.PROGRESS_NAME && window.progress_update) {
var fname = this.env.capabilities.PROGRESS_NAME,
field = $('input[name='+fname+']', form);
if (!field.length) {
field = $('<input>').attr({type: 'hidden', name: fname});
field.prependTo(form);
}
field.val(ts);
this.uploads[ts] = this.env.folder;
this.file_upload_progress(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).load(function(e) {
// hide progressbar on upload error
if (!onload(e, this, ui.uploads[ts]) && window.progress_update)
window.progress_update({id: ts, done: true});
delete ui.uploads[ts];
});
$(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();
};
// upload progress requests
this.file_upload_progress = function(id)
{
setTimeout(function() {
if (id && ui.uploads[id])
ui.request('upload_progress', {id: id}, 'file_upload_progress_response');
}, this.env.capabilities.PROGRESS_TIME * 1000);
};
// upload progress response
this.file_upload_progress_response = function(response)
{
if (!this.response(response))
return;
var param = response.result;
if (param.id && window.progress_update)
window.progress_update(param);
if (!param.done)
this.file_upload_progress(param.id);
};
// 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.request(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.request(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/viewers b/public_html/viewers
deleted file mode 120000
index 9e5895b..0000000
--- a/public_html/viewers
+++ /dev/null
@@ -1 +0,0 @@
-../lib/viewers
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jul 8, 9:30 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1049558
Default Alt Text
(190 KB)

Event Timeline