Page MenuHomePhorge

No OneTemporary

diff --git a/lib/drivers/kolab/kolab_file_storage.php b/lib/drivers/kolab/kolab_file_storage.php
index 8e34a1d..f29e74e 100644
--- a/lib/drivers/kolab/kolab_file_storage.php
+++ b/lib/drivers/kolab/kolab_file_storage.php
@@ -1,1227 +1,1227 @@
<?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;
/**
* @var array
*/
protected $config = array();
/**
* @var string
*/
protected $title;
/**
* Class constructor
*/
public function __construct()
{
$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'));
$required = array('libkolab');
// Kolab WebDAV server supports plugins, no need to overwrite object
if (!is_a($this->rc->plugins, 'rcube_plugin_api')) {
// 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'],
));
}
/**
* Get password and name of authenticated user
*
* @return array Authenticated user data
*/
public function auth_info()
{
return array(
'username' => $this->config['username'] ?: $_SESSION['username'],
'password' => $this->config['password'] ?: $this->rc->decrypt($_SESSION['password']),
);
}
/**
* 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);
+ list($key, $val) = each($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');
}
}
/**
* Configures environment
*
* @param array $config Configuration
* @param string $title Source identifier
*/
public function configure($config, $title = null)
{
$this->config = array_merge($this->config, $config);
// @TODO: this is currently not possible to have multiple sessions in Roundcube
}
/**
* Returns current instance title
*
* @return string Instance title (mount point)
*/
public function title()
{
return '';
}
/**
* 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;
}
$storage = $this->rc->get_storage();
$quota = $storage->get_capability('QUOTA');
return array(
file_storage::CAPS_MAX_UPLOAD => $max_filesize,
file_storage::CAPS_QUOTA => $quota,
file_storage::CAPS_LOCKS => true,
);
}
/**
* Save configuration of external driver (mount point)
*
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_create($driver)
{
$drivers = $this->driver_list();
if ($drivers[$driver['title']]) {
throw new Exception("Driver exists", file_storage::ERROR);
}
$config = kolab_storage_config::get_instance();
$status = $config->save($driver, 'file_driver');
if (!$status) {
throw new Exception("Driver create failed", file_storage::ERROR);
}
$this->driver_list = null;
}
/**
* Delete configuration of external driver (mount point)
*
* @param string $name Driver instance name
*
* @throws Exception
*/
public function driver_delete($name)
{
$drivers = $this->driver_list();
if ($driver = $drivers[$name]) {
$config = kolab_storage_config::get_instance();
$status = $config->delete($driver['uid']);
if (!$status) {
throw new Exception("Driver delete failed", file_storage::ERROR);
}
$this->driver_list = null;
return;
}
throw new Exception("Driver not found", file_storage::ERROR);
}
/**
* Return list of registered drivers (mount points)
*
* @return array List of drivers data
* @throws Exception
*/
public function driver_list()
{
// use internal cache, this is specifically for iRony
// which may call this code path many times in one request
if ($this->driver_list !== null) {
return $this->driver_list;
}
// get current relations state
$config = kolab_storage_config::get_instance();
$default = true;
$filter = array(
array('type', '=', 'file_driver'),
);
$drivers = $config->get_objects($filter, $default, 100);
$result = array();
foreach ($drivers as $driver) {
$result[$driver['title']] = $driver;
}
return $this->driver_list = $result;
}
/**
* Update configuration of external driver (mount point)
*
* @param string $title Driver instance title
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_update($title, $driver)
{
$drivers = $this->driver_list();
if (!$drivers[$title]) {
throw new Exception("Driver not found", file_storage::ERROR);
}
$config = kolab_storage_config::get_instance();
$status = $config->save($driver, 'file_driver');
if (!$status) {
throw new Exception("Driver update failed", file_storage::ERROR);
}
$this->driver_list = null;
}
/**
* Returns metadata of the driver
*
* @return array Driver meta data (image, name, form)
*/
public function driver_metadata()
{
$image_content = file_get_contents(__DIR__ . '/kolab.png');
$metadata = array(
'image' => 'data:image/png;base64,' . base64_encode($image_content),
'name' => 'Kolab Groupware',
'ref' => 'http://kolab.org',
'description' => 'Kolab Groupware server',
'form' => array(
'host' => 'hostname',
'username' => 'username',
'password' => 'password',
),
);
return $metadata;
}
/**
* Validate metadata (config) of the driver
*
* @param array $metadata Driver metadata
*
* @return array Driver meta data to be stored in configuration
* @throws Exception
*/
public function driver_validate($metadata)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* 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_storage::ERROR);
}
$object = $this->to_file_object(array(
'name' => $file_name,
'type' => $file['type'],
'path' => $file['path'],
'content' => $file['content'],
));
// 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 file failed.", file_storage::ERROR);
}
}
/**
* Update 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_update($file_name, $file)
{
$file_object = $this->get_file_object($file_name, $folder);
if (empty($file_object)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$key = key($file_object['_attachments']);
$file_object['_attachments'] = array(
0 => array(
'name' => $file_name,
'path' => $file['path'],
'content' => $file['content'],
'mimetype' => $file['type'],
),
$key => false,
);
// save the file object in IMAP
$saved = $folder->save($file_object, 'file', $file_object['_msguid']);
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 file failed.", file_storage::ERROR);
}
}
/**
* 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_storage::ERROR);
}
$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 file failed.", file_storage::ERROR);
}
}
/**
* Return 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(), $fp = null)
{
$file = $this->get_file_object($file_name, $folder);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
// write to file pointer, send no headers
if ($fp) {
if ($file['size']) {
$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 = 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\"");
if ($file['size']) {
$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_storage::ERROR);
}
$file = $this->from_file_object($file);
return array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
'mtime' => $file['changed'] ? $file['changed']->format($this->config['date_format']) : '',
'ctime' => $file['created'] ? $file['created']->format($this->config['date_format']) : '',
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
}
/**
* List files in a folder.
*
* @param string $folder_name Name of a folder with full path
* @param array $params List parameters ('sort', 'reverse', 'search', 'prefix')
*
* @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 = $params['prefix'] . $folder_name . file_storage::SEPARATOR . $file['name'];
$result[$filename] = array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
'mtime' => $file['changed'] ? $file['changed']->format($this->config['date_format']) : '',
'ctime' => $file['created'] ? $file['created']->format($this->config['date_format']) : '',
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
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_storage::ERROR);
}
$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_storage::ERROR);
}
if ($file['size']) {
$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_storage::ERROR);
}
// 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');
@unlink($file_path);
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_storage::ERROR);
}
}
/**
* 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_storage::ERROR);
}
$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_storage::ERROR);
}
$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_storage::ERROR);
}
}
/**
* 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_storage::ERROR);
}
}
/**
* 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_storage::ERROR);
}
}
/**
* Move/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_move($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_storage::ERROR);
}
}
/**
* Returns list of folders.
*
* @return array List of folders
* @throws Exception
*/
public function folder_list()
{
$folders = kolab_storage::list_folders('', '*', 'file', false);
if (!is_array($folders)) {
throw new Exception("Storage error. Unable to get folders list.", file_storage::ERROR);
}
// create 'Files' folder in case there's no folder of type 'file'
if (empty($folders)) {
if (kolab_storage::folder_create('Files', 'file')) {
$folders[] = 'Files';
}
}
else {
$callback = function($folder) { return rcube_charset::convert($folder, 'UTF7-IMAP', RCUBE_CHARSET); };
$folders = array_map($callback, $folders);
}
return $folders;
}
/**
* Returns a list of locks
*
* This method should return all the locks for a particular URI, including
* locks that might be set on a parent URI.
*
* If child_locks is set to true, this method should also look for
* any locks in the subtree of the URI for locks.
*
* @param string $uri URI
* @param bool $child_locks Enables subtree checks
*
* @return array List of locks
* @throws Exception
*/
public function lock_list($uri, $child_locks = false)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->uri2resource($uri);
// get locks list
$list = $this->lock_db->lock_list($uri, $child_locks);
// convert back resource string into URIs
foreach ($list as $idx => $lock) {
$list[$idx]['uri'] = $this->resource2uri($lock['uri']);
}
return $list;
}
/**
* Locks a URI
*
* @param string $uri URI
* @param array $lock Lock data
* - depth: 0/'infinite'
* - scope: 'shared'/'exclusive'
* - owner: string
* - token: string
* - timeout: int
*
* @throws Exception
*/
public function lock($uri, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->uri2resource($uri);
if (!$this->lock_db->lock($uri, $lock)) {
throw new Exception("Database error. Unable to create a lock.", file_storage::ERROR);
}
}
/**
* Removes a lock from a URI
*
* @param string $path URI
* @param array $lock Lock data
*
* @throws Exception
*/
public function unlock($uri, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->uri2resource($uri);
if (!$this->lock_db->unlock($uri, $lock)) {
throw new Exception("Database error. Unable to remove a lock.", file_storage::ERROR);
}
}
/**
* Return disk quota information for specified folder.
*
* @param string $folder_name Name of a folder with full path
*
* @return array Quota
* @throws Exception
*/
public function quota($folder)
{
$storage = $this->rc->get_storage();
$quota = $storage->get_quota();
$quota = $this->rc->plugins->exec_hook('quota', $quota);
unset($quota['abort']);
return $quota;
}
/**
* 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_storage::SEPARATOR, $file_name);
$file_name = array_pop($path);
$folder_name = implode(file_storage::SEPARATOR, $path);
if ($folder_name === '') {
throw new Exception("Missing folder name", file_storage::ERROR);
}
// get folder object
$folder = $this->get_folder_object($folder_name);
$files = $folder->select(array(
array('type', '=', 'file'),
array('filename', '=', $file_name)
));
return $files[0];
}
/**
* 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_storage::ERROR);
}
if (empty($this->folders[$folder_name])) {
$storage = $this->rc->get_storage();
$separator = $storage->get_hierarchy_delimiter();
$folder_name = str_replace(file_storage::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_storage::ERROR);
}
$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'],
'content' => $file['content'],
'mimetype' => $file['type'],
'size' => $file['size'],
));
unset($file['name']);
unset($file['size']);
unset($file['type']);
unset($file['path']);
unset($file['fileid']);
return $file;
}
protected function uri2resource($uri)
{
$storage = $this->rc->get_storage();
$namespace = $storage->get_namespace();
$separator = $storage->get_hierarchy_delimiter();
$uri = str_replace(file_storage::SEPARATOR, $separator, $uri);
$owner = $this->rc->get_user_name();
// find the owner and remove namespace prefix
foreach ($namespace as $type => $ns) {
foreach ($ns as $root) {
if (is_array($root) && $root[0] && strpos($uri, $root[0]) === 0) {
$uri = substr($uri, strlen($root[0]));
switch ($type) {
case 'shared':
// in theory there can be more than one shared root
// we add it to dummy user name, so we can revert conversion
$owner = "shared({$root[0]})";
break;
case 'other':
list($user, $uri) = explode($separator, $uri, 2);
if (strpos($user, '@') === false) {
$domain = strstr($owner, '@');
if (!empty($domain)) {
$user .= $domain;
}
}
$owner = $user;
break;
}
break 2;
}
}
}
// convert to imap charset (to be safe to store in DB)
$uri = rcube_charset::convert($uri, RCUBE_CHARSET, 'UTF7-IMAP');
return 'imap://' . urlencode($owner) . '@' . $storage->options['host'] . '/' . $uri;
}
protected function resource2uri($resource)
{
if (!preg_match('|^imap://([^@]+)@([^/]+)/(.*)$|', $resource, $matches)) {
throw new Exception("Internal storage error. Unexpected data format.", file_storage::ERROR);
}
$storage = $this->rc->get_storage();
$separator = $storage->get_hierarchy_delimiter();
$owner = $this->rc->get_user_name();
$user = urldecode($matches[1]);
$uri = $matches[3];
// convert from imap charset (to be safe to store in DB)
$uri = rcube_charset::convert($uri, 'UTF7-IMAP', RCUBE_CHARSET);
// personal namespace
if ($user == $owner) {
// do nothing
// Note: that might not work if personal namespace uses e.g. INBOX/ prefix.
}
// shared namespace
else if (preg_match('/^shared\((.*)\)$/', $user, $matches)) {
$uri = $matches[1] . $uri;
}
// other users namespace
else {
$namespace = $storage->get_namespace('other');
list($local, $domain) = explode('@', $user);
// here we assume there's only one other users namespace root
$uri = $namespace[0][0] . $local . $separator . $uri;
}
$uri = str_replace($separator, file_storage::SEPARATOR, $uri);
return $uri;
}
/**
* Initializes file_locks object
*/
protected function init_lock_db()
{
if (!$this->lock_db) {
$this->lock_db = new file_locks;
}
}
}
diff --git a/lib/drivers/seafile/seafile_file_storage.php b/lib/drivers/seafile/seafile_file_storage.php
index 6343b7d..9459fa6 100644
--- a/lib/drivers/seafile/seafile_file_storage.php
+++ b/lib/drivers/seafile/seafile_file_storage.php
@@ -1,1218 +1,1218 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2014, 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 seafile_file_storage implements file_storage
{
/**
* @var rcube
*/
protected $rc;
/**
* @var array
*/
protected $config = array();
/**
* @var seafile_api
*/
protected $api;
/**
* List of SeaFile libraries
*
* @var array
*/
protected $libraries;
/**
* Instance title (mount point)
*
* @var string
*/
protected $title;
/**
* Class constructor
*/
public function __construct()
{
$this->rc = rcube::get_instance();
}
/**
* 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)
{
$this->init(true);
$token = $this->api->authenticate($username, $password);
if ($token) {
$_SESSION[$this->title . 'seafile_user'] = $username;
$_SESSION[$this->title . 'seafile_token'] = $this->rc->encrypt($token);
$_SESSION[$this->title . 'seafile_pass'] = $this->rc->encrypt($password);
return true;
}
$this->api = false;
return false;
}
/**
* Get password and name of authenticated user
*
* @return array Authenticated user data
*/
public function auth_info()
{
return array(
'username' => $_SESSION[$this->title . 'seafile_user'],
'password' => $this->rc->decrypt($_SESSION[$this->title . 'seafile_pass']),
);
}
/**
* Initialize SeaFile Web API connection
*/
protected function init($skip_auth = false)
{
if ($this->api !== null) {
return $this->api !== false;
}
// read configuration
$config = array(
'host' => $this->rc->config->get('fileapi_seafile_host', 'localhost'),
'ssl_verify_peer' => $this->rc->config->get('fileapi_seafile_ssl_verify_peer', true),
'ssl_verify_host' => $this->rc->config->get('fileapi_seafile_ssl_verify_host', true),
'cache' => $this->rc->config->get('fileapi_seafile_cache'),
'cache_ttl' => $this->rc->config->get('fileapi_seafile_cache', '14d'),
'debug' => $this->rc->config->get('fileapi_seafile_debug', false),
);
$this->config = array_merge($config, $this->config);
// initialize Web API
$this->api = new seafile_api($this->config);
if ($skip_auth) {
return true;
}
// try session token
if ($_SESSION[$this->title . 'seafile_token']
&& ($token = $this->rc->decrypt($_SESSION[$this->title . 'seafile_token']))
) {
$valid = $this->api->ping($token);
}
if (!$valid) {
// already authenticated in session
if ($_SESSION[$this->title . 'seafile_user']) {
$user = $_SESSION[$this->title . 'seafile_user'];
$pass = $this->rc->decrypt($_SESSION[$this->title . 'seafile_pass']);
}
// try user/pass of the main driver
else {
$user = $this->config['username'];
$pass = $this->config['password'];
}
if ($user) {
$valid = $this->authenticate($user, $pass);
}
}
// throw special exception, so we can ask user for the credentials
if (!$valid && empty($_SESSION[$this->title . 'seafile_user'])) {
throw new Exception("User credentials not provided", file_storage::ERROR_NOAUTH);
}
else if (!$valid && $this->api->is_error() == seafile_api::TOO_MANY_REQUESTS) {
throw new Exception("SeaFile storage temporarily unavailable (too many requests)", file_storage::ERROR);
}
return $valid;
}
/**
* Configures environment
*
* @param array $config Configuration
* @param string $title Source identifier
*/
public function configure($config, $title = null)
{
$this->config = array_merge($this->config, $config);
$this->title = $title;
}
/**
* Returns current instance title
*
* @return string Instance title (mount point)
*/
public function title()
{
return $this->title;
}
/**
* 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,
file_storage::CAPS_QUOTA => true,
file_storage::CAPS_LOCKS => true,
);
}
/**
* Save configuration of external driver (mount point)
*
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_create($driver)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Delete configuration of external driver (mount point)
*
* @param string $title Driver instance name
*
* @throws Exception
*/
public function driver_delete($title)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Return list of registered drivers (mount points)
*
* @return array List of drivers data
* @throws Exception
*/
public function driver_list()
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Update configuration of external driver (mount point)
*
* @param string $title Driver instance name
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_update($title, $driver)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Returns metadata of the driver
*
* @return array Driver meta data (image, name, form)
*/
public function driver_metadata()
{
$image_content = file_get_contents(__DIR__ . '/seafile.png');
$metadata = array(
'image' => 'data:image/png;base64,' . base64_encode($image_content),
'name' => 'SeaFile',
'ref' => 'http://seafile.com',
'description' => 'Storage implementing SeaFile API access',
'form' => array(
'host' => 'hostname',
'username' => 'username',
'password' => 'password',
),
);
// these are returned when authentication on folders list fails
if ($this->config['username']) {
$metadata['form_values'] = array(
'host' => $this->config['host'],
'username' => $this->config['username'],
);
}
return $metadata;
}
/**
* Validate metadata (config) of the driver
*
* @param array $metadata Driver metadata
*
* @return array Driver meta data to be stored in configuration
* @throws Exception
*/
public function driver_validate($metadata)
{
if (!is_string($metadata['username']) || !strlen($metadata['username'])) {
throw new Exception("Missing user name.", file_storage::ERROR);
}
if (!is_string($metadata['password']) || !strlen($metadata['password'])) {
throw new Exception("Missing user password.", file_storage::ERROR);
}
if (!is_string($metadata['host']) || !strlen($metadata['host'])) {
throw new Exception("Missing host name.", file_storage::ERROR);
}
$this->config['host'] = $metadata['host'];
if (!$this->authenticate($metadata['username'], $metadata['password'])) {
throw new Exception("Unable to authenticate user", file_storage::ERROR_NOAUTH);
}
return array(
'host' => $metadata['host'],
'username' => $metadata['username'],
'password' => $metadata['password'],
);
}
/**
* 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)
{
list($fn, $repo_id) = $this->find_library($file_name);
if (empty($repo_id)) {
throw new Exception("Storage error. Folder not found.", file_storage::ERROR);
}
if ($file['path']) {
$file['data'] = $file['path'];
}
else if (is_resource($file['content'])) {
$file['data'] = $file['content'];
}
else {
$fp = fopen('php://temp', 'wb');
fwrite($fp, $file['content'], strlen($file['content']));
$file['data'] = $fp;
unset($file['content']);
}
$created = $this->api->file_upload($repo_id, $fn, $file);
if ($fp) {
fclose($fp);
}
if (!$created) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving file to SeaFile server"),
true, false);
throw new Exception("Storage error. Saving file failed.", file_storage::ERROR);
}
}
/**
* Update 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_update($file_name, $file)
{
list($fn, $repo_id) = $this->find_library($file_name);
if (empty($repo_id)) {
throw new Exception("Storage error. Folder not found.", file_storage::ERROR);
}
if ($file['path']) {
$file['data'] = $file['path'];
}
else if (is_resource($file['content'])) {
$file['data'] = $file['content'];
}
else {
$fp = fopen('php://temp', 'wb');
fwrite($fp, $file['content'], strlen($file['content']));
$file['data'] = $fp;
unset($file['content']);
}
$saved = $this->api->file_update($repo_id, $fn, $file);
if ($fp) {
fclose($fp);
}
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving file to SeaFile server"),
true, false);
throw new Exception("Storage error. Saving file failed.", file_storage::ERROR);
}
}
/**
* Delete a file.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_delete($file_name)
{
list($file_name, $repo_id) = $this->find_library($file_name);
if ($repo_id && $file_name != '/') {
$deleted = $this->api->file_delete($repo_id, $file_name);
}
if (!$deleted) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting object from SeaFile server"),
true, false);
throw new Exception("Storage error. Deleting file failed.", file_storage::ERROR);
}
}
/**
* Return 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(), $fp = null)
{
list($fn, $repo_id) = $this->find_library($file_name);
$file = $this->api->file_info($repo_id, $fn);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
// get file location on SeaFile server for download
if ($file['size']) {
$link = $this->api->file_get($repo_id, $fn);
}
// write to file pointer, send no headers
if ($fp) {
if ($file['size']) {
$this->save_file_content($link, $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 = 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\"");
// just send redirect to SeaFile server
if ($file['size']) {
header("Location: $link");
}
die;
}
/**
* Returns file metadata.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_info($file_name)
{
list($file, $repo_id) = $this->find_library($file_name);
$file = $this->api->file_info($repo_id, $file);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
return array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
'mtime' => $file['changed'] ? $file['changed']->format($this->config['date_format']) : '',
'ctime' => $file['created'] ? $file['created']->format($this->config['date_format']) : '',
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
}
/**
* List files in a folder.
*
* @param string $folder_name Name of a folder with full path
* @param array $params List parameters ('sort', 'reverse', 'search', 'prefix')
*
* @return array List of files (file properties array indexed by filename)
* @throws Exception
*/
public function file_list($folder_name, $params = array())
{
list($folder, $repo_id) = $this->find_library($folder_name);
// prepare search filter
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
if ($idx == 'name') {
$params['search'][$idx] = mb_strtoupper($value);
}
else if ($idx == 'class') {
$params['search'][$idx] = file_utils::class2mimetypes($value);
}
}
}
// get directory entries
$entries = $this->api->directory_entries($repo_id, $folder);
$result = array();
foreach ((array) $entries as $idx => $file) {
if ($file['type'] != 'file') {
continue;
}
$file = $this->from_file_object($file);
// search filter
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
if ($idx == 'name') {
if (strpos(mb_strtoupper($file['name']), $value) === false) {
continue 2;
}
}
else if ($idx == 'class') {
foreach ($value as $v) {
if (stripos($file['type'], $v) === 0) {
break 2;
}
}
continue 2;
}
}
}
$filename = $params['prefix'] . $folder_name . file_storage::SEPARATOR . $file['name'];
$result[$filename] = array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
'mtime' => $file['changed'] ? $file['changed']->format($this->config['date_format']) : '',
'ctime' => $file['created'] ? $file['created']->format($this->config['date_format']) : '',
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
- unset($files[$idx]);
+ unset($entries[$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)
{
list($src_name, $repo_id) = $this->find_library($file_name);
list($dst_name, $dst_repo_id) = $this->find_library($new_name);
if ($repo_id && $dst_repo_id) {
$path_src = explode('/', $src_name);
$path_dst = explode('/', $dst_name);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = '/' . ltrim(implode('/', $path_src), '/');
$dst_dir = '/' . ltrim(implode('/', $path_dst), '/');
$success = $this->api->file_copy($repo_id, $f_old, $src_dir, $dst_dir, $dst_repo_id);
// now rename the file if needed
if ($success && $f_src != $f_dst) {
$success = $this->api->file_rename($dst_repo_id, rtrim($dst_dir, '/') . '/' . $f_src, $f_dst);
}
}
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error copying file on SeaFile server"),
true, false);
throw new Exception("Storage error. File copying failed.", file_storage::ERROR);
}
}
/**
* 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)
{
list($src_name, $repo_id) = $this->find_library($file_name);
list($dst_name, $dst_repo_id) = $this->find_library($new_name);
if ($repo_id && $dst_repo_id) {
$path_src = explode('/', $src_name);
$path_dst = explode('/', $dst_name);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = '/' . ltrim(implode('/', $path_src), '/');
$dst_dir = '/' . ltrim(implode('/', $path_dst), '/');
if ($src_dir == $dst_dir && $repo_id == $dst_repo_id) {
$success = true;
}
else {
$success = $this->api->file_move($repo_id, $src_name, $dst_dir, $dst_repo_id);
}
// now rename the file if needed
if ($success && $f_src != $f_dst) {
$success = $this->api->file_rename($dst_repo_id, rtrim($dst_dir, '/') . '/' . $f_src, $f_dst);
}
}
if (!$success) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error moving file on SeaFile server"),
true, false);
throw new Exception("Storage error. File rename failed.", file_storage::ERROR);
}
}
/**
* Create a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_create($folder_name)
{
list($folder, $repo_id) = $this->find_library($folder_name, true);
if (empty($repo_id)) {
$success = $this->api->library_create($folder_name);
}
else if ($folder != '/') {
$success = $this->api->directory_create($repo_id, $folder);
}
if (!$success) {
throw new Exception("Storage error. Unable to create folder", file_storage::ERROR);
}
// clear the cache
if (empty($repo_id)) {
$this->libraries = null;
}
}
/**
* Delete a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_delete($folder_name)
{
list($folder, $repo_id) = $this->find_library($folder_name, true);
if ($repo_id && $folder == '/') {
$success = $this->api->library_delete($repo_id);
}
else if ($repo_id) {
$success = $this->api->directory_delete($repo_id, $folder);
}
if (!$success) {
throw new Exception("Storage error. Unable to delete folder.", file_storage::ERROR);
}
}
/**
* Move/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_move($folder_name, $new_name)
{
list($folder, $repo_id, $library) = $this->find_library($folder_name, true);
list($dest_folder, $dest_repo_id) = $this->find_library($new_name, true);
// folders rename/move is possible only in the same library and folder
// @TODO: support folder move between libraries and folders
// @TODO: support converting library into a folder and vice-versa
// library rename
if ($repo_id && !$dest_repo_id && $folder == '/' && strpos($new_name, '/') === false) {
$success = $this->api->library_rename($repo_id, $new_name, $library['desc']);
}
// folder rename
else if ($folder != '/' && $dest_folder != '/' && $repo_id && $repo_id == $dest_repo_id) {
$path_src = explode('/', $folder);
$path_dst = explode('/', $dest_folder);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = implode('/', $path_src);
$dst_dir = implode('/', $path_dst);
if ($src_dir == $dst_dir) {
$success = $this->api->directory_rename($repo_id, $folder, $f_dst);
}
}
if (!$success) {
throw new Exception("Storage error. Unable to rename/move folder", file_storage::ERROR);
}
}
/**
* Returns list of folders.
*
* @return array List of folders
* @throws Exception
*/
public function folder_list()
{
$libraries = $this->libraries();
$folders = array();
if ($this->config['cache']) {
$cache = $this->rc->get_cache('seafile_' . $this->title,
$this->config['cache'], $this->config['cache_ttl'], true);
if ($cache) {
$cached = $cache->get('folders');
}
}
- foreach ($this->libraries as $library) {
+ foreach ($libraries as $library) {
if ($library['virtual'] || $library['encrypted']) {
continue;
}
$folders[$library['name']] = $library['mtime'];
if ($folder_tree = $this->folders_tree($library, '', $library, $cached)) {
$folders = array_merge($folders, $folder_tree);
}
}
if (empty($folders)) {
throw new Exception("Storage error. Unable to get folders list.", file_storage::ERROR);
}
if ($cache) {
$cache->set('folders', $folders);
}
// sort folders
$folders = array_keys($folders);
usort($folders, array($this, 'sort_folder_comparator'));
return $folders;
}
/**
* Returns a list of locks
*
* This method should return all the locks for a particular URI, including
* locks that might be set on a parent URI.
*
* If child_locks is set to true, this method should also look for
* any locks in the subtree of the URI for locks.
*
* @param string $uri URI
* @param bool $child_locks Enables subtree checks
*
* @return array List of locks
* @throws Exception
*/
public function lock_list($uri, $child_locks = false)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->uri2resource($uri);
// get locks list
$list = $this->lock_db->lock_list($uri, $child_locks);
// convert back resource string into URIs
foreach ($list as $idx => $lock) {
$list[$idx]['uri'] = $this->resource2uri($lock['uri']);
}
return $list;
}
/**
* Locks a URI
*
* @param string $uri URI
* @param array $lock Lock data
* - depth: 0/'infinite'
* - scope: 'shared'/'exclusive'
* - owner: string
* - token: string
* - timeout: int
*
* @throws Exception
*/
public function lock($uri, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->uri2resource($uri);
if (!$this->lock_db->lock($uri, $lock)) {
throw new Exception("Database error. Unable to create a lock.", file_storage::ERROR);
}
}
/**
* Removes a lock from a URI
*
* @param string $path URI
* @param array $lock Lock data
*
* @throws Exception
*/
public function unlock($uri, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->uri2resource($uri);
if (!$this->lock_db->unlock($uri, $lock)) {
throw new Exception("Database error. Unable to remove a lock.", file_storage::ERROR);
}
}
/**
* Return disk quota information for specified folder.
*
* @param string $folder_name Name of a folder with full path
*
* @return array Quota
* @throws Exception
*/
public function quota($folder)
{
if (!$this->init()) {
throw new Exception("Storage error. Unable to get SeaFile account info.", file_storage::ERROR);
}
$account_info = $this->api->account_info();
if (empty($account_info)) {
throw new Exception("Storage error. Unable to get SeaFile account info.", file_storage::ERROR);
}
$quota = array(
// expected values in kB
'total' => intval($account_info['total'] / 1024),
'used' => intval($account_info['usage'] / 1024),
);
return $quota;
}
/**
* Recursively builds folders list
*/
protected function folders_tree($library, $path, $folder, $cached)
{
$folders = array();
$fname = strlen($path) ? $path . $folder['name'] : '/';
$root = $library['name'] . ($fname != '/' ? $fname : '');
// nothing changed, use cached folders tree of this folder
if ($cached && $cached[$root] && $cached[$root] == $folder['mtime']) {
foreach ($cached as $folder_name => $mtime) {
if (strpos($folder_name, $root . '/') === 0) {
$folders[$folder_name] = $mtime;
}
}
}
// get folder content (files and sub-folders)
// there's no API method to get only folders
else if ($content = $this->api->directory_entries($library['id'], $fname)) {
if ($fname != '/') {
$fname .= '/';
}
foreach ($content as $item) {
if ($item['type'] == 'dir' && strlen($item['name'])) {
$folders[$root . '/' . $item['name']] = $item['mtime'];
// get subfolders recursively
$folders_tree = $this->folders_tree($library, $fname, $item, $cached);
if (!empty($folders_tree)) {
$folders = array_merge($folders, $folders_tree);
}
}
}
}
return $folders;
}
/**
* Callback for uasort() that implements correct
* locale-aware case-sensitive sorting
*/
protected function sort_folder_comparator($str1, $str2)
{
$path1 = explode('/', $str1);
$path2 = explode('/', $str2);
foreach ($path1 as $idx => $folder1) {
$folder2 = $path2[$idx];
if ($folder1 === $folder2) {
continue;
}
return strcoll($folder1, $folder2);
}
}
/**
* Get list of SeaFile libraries
*/
protected function libraries()
{
// get from memory, @TODO: cache in rcube_cache?
if ($this->libraries !== null) {
return $this->libraries;
}
if (!$this->init()) {
throw new Exception("Storage error. Unable to get list of SeaFile libraries.", file_storage::ERROR);
}
if ($list = $this->api->library_list()) {
$this->libraries = $list;
}
else {
$this->libraries = array();
}
return $this->libraries;
}
/**
* Find library ID from folder name
*/
protected function find_library($folder_name, $no_exception = false)
{
$libraries = $this->libraries();
foreach ($libraries as $lib) {
$path = $lib['name'] . '/';
if ($folder_name == $lib['name'] || strpos($folder_name, $path) === 0) {
if (empty($library) || strlen($library['name']) < strlen($lib['name'])) {
$library = $lib;
}
}
}
if (empty($library)) {
if (!$no_exception) {
throw new Exception("Storage error. Library not found.", file_storage::ERROR);
}
}
else {
$folder = substr($folder_name, strlen($library['name']) + 1);
}
return array(
'/' . ($folder ? $folder : ''),
$library['id'],
$library
);
}
/**
* 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_storage::SEPARATOR, $file_name);
$file_name = array_pop($path);
$folder_name = implode(file_storage::SEPARATOR, $path);
if ($folder_name === '') {
throw new Exception("Missing folder name", file_storage::ERROR);
}
// get folder object
$folder = $this->get_folder_object($folder_name);
$files = $folder->select(array(
array('type', '=', 'file'),
array('filename', '=', $file_name)
));
return $files[0];
}
/**
* Simplify internal structure of the file object
*/
protected function from_file_object($file)
{
if ($file['type'] != 'file') {
return null;
}
// file modification time
if ($file['mtime']) {
try {
$file['changed'] = new DateTime('@' . $file['mtime']);
}
catch (Exception $e) { }
}
// find file mimetype from extension
$file['type'] = file_utils::ext_to_type($file['name']);
unset($file['id']);
unset($file['mtime']);
return $file;
}
/**
* Save remote file into file pointer
*/
protected function save_file_content($location, $fp)
{
if (!$fp || !$location) {
return false;
}
$config = array_merge($this->config, array('store_bodies' => true));
$request = seafile_api::http_request($config);
if (!$request) {
return false;
}
$observer = new seafile_request_observer();
$observer->set_fp($fp);
try {
$request->setUrl($location);
$request->attach($observer);
$response = $request->send();
$status = $response->getStatus();
$response->getBody(); // returns nothing
$request->detach($observer);
if ($status != 200) {
throw new Exception("Unable to save file. Status $status.");
}
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
return false;
}
return true;
}
protected function uri2resource($uri)
{
list($file, $repo_id, $library) = $this->find_library($uri);
// convert to imap charset (to be safe to store in DB)
$uri = rcube_charset::convert($uri, RCUBE_CHARSET, 'UTF7-IMAP');
return 'seafile://' . urlencode($library['owner']) . '@' . $this->config['host'] . '/' . $uri;
}
protected function resource2uri($resource)
{
if (!preg_match('|^seafile://([^@]+)@([^/]+)/(.*)$|', $resource, $matches)) {
throw new Exception("Internal storage error. Unexpected data format.", file_storage::ERROR);
}
$user = urldecode($matches[1]);
$uri = $matches[3];
// convert from imap charset (to be safe to store in DB)
$uri = rcube_charset::convert($uri, 'UTF7-IMAP', RCUBE_CHARSET);
return $uri;
}
/**
* Initializes file_locks object
*/
protected function init_lock_db()
{
if (!$this->lock_db) {
$this->lock_db = new file_locks;
}
}
}
diff --git a/lib/file_locks.php b/lib/file_locks.php
index 7b7a40b..56651a8 100644
--- a/lib/file_locks.php
+++ b/lib/file_locks.php
@@ -1,267 +1,267 @@
<?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> |
+--------------------------------------------------------------------------+
*/
/**
* The Lock manager allows you to handle all file-locks centrally.
* It stores all its data in a sql database. Derived from SabreDAV's
* PDO Lock manager.
*/
class file_locks {
const SHARED = 1;
const EXCLUSIVE = 2;
const INFINITE = -1;
/**
* The database connection object
*
* @var rcube_db
*/
private $db;
/**
* The tablename this backend uses.
*
* @var string
*/
protected $table;
/**
* Internal cache
*
* @var array
*/
protected $icache = array();
/**
* Constructor
*
* @param string $table Table name
*/
public function __construct($table = 'chwala_locks')
{
$rcube = rcube::get_instance();
$this->db = $rcube->get_dbh();
$this->table = $this->db->table_name($table);
if ($rcube->session) {
$rcube->session->register_gc_handler(array($this, 'gc'));
}
else {
// run garbage collector with probability based on
// session settings if session does not exist.
$probability = (int) ini_get('session.gc_probability');
$divisor = (int) ini_get('session.gc_divisor');
if ($divisor > 0 && $probability > 0) {
$random = mt_rand(1, $divisor);
if ($random <= $probability) {
$this->gc();
}
}
}
}
/**
* Returns a list of locks
*
* This method should return all the locks for a particular URI, including
* locks that might be set on a parent URI.
*
* If child_locks is set to true, this method should also look for
* any locks in the subtree of the URI for locks.
*
* @param string $uri URI
* @param bool $child_locks Enables subtree checks
*
* @return array List of locks
*/
public function lock_list($uri, $child_locks = false)
{
if ($this->icache['uri'] == $uri && $this->icache['child'] == $child_locks) {
return $this->icache['list'];
}
$query = "SELECT * FROM `{$this->table}` WHERE (`uri` = ?";
$params = array($uri);
if ($child_locks) {
$query .= " OR `uri` LIKE ?";
$params[] = $uri . '/%';
}
$path = '';
$key = $uri;
$list = array();
// in case uri contains protocol/host specification e.g. imap://user@host/
// handle prefix separately
if (preg_match('~^([a-z]+://[^/]+/)~i', $uri, $matches)) {
$path = $matches[1];
$uri = substr($uri, strlen($matches[1]));
}
// We need to check locks for every part in the path
$path_parts = explode('/', $uri);
// We already covered the last part of the uri
array_pop($path_parts);
if (!empty($path_parts)) {
$root_path = $path . implode('/', $path_parts);
// this path is already cached, extract locks from cached result
// we do this because it is a common scenario to request
// for lock on every file/folder in specified location
if ($this->icache['root_path'] == $root_path) {
$length = strlen($root_path);
foreach ($this->icache['list'] as $lock) {
if ($lock['depth'] != 0 && strlen($lock['token']) <= $length) {
$list[] = $lock;
}
}
}
else {
foreach ($path_parts as $part) {
$path .= $part;
$params[] = $path;
$path .= '/';
}
$query .= " OR (`uri` IN (" . implode(',', array_pad(array(), count($path_parts), '?')) . ") AND `depth` <> 0)";
}
}
// finally, skip expired locks
$query .= ") AND `expires` > " . $this->db->now();
// run the query and parse result
$result = $this->db->query($query, $params);
while ($row = $this->db->fetch_assoc($result)) {
$created = strtotime($row['expires']) - $row['timeout'];
$list[] = array(
'uri' => $row['uri'],
'owner' => $row['owner'],
'token' => $row['token'],
'timeout' => (int) $row['timeout'],
'created' => (int) $created,
'scope' => $row['scope'] == self::EXCLUSIVE ? file_storage::LOCK_EXCLUSIVE : file_storage::LOCK_SHARED,
'depth' => $row['depth'] == self::INFINITE ? file_storage::LOCK_INFINITE : (int) $row['depth'],
);
}
// remember last result in memory, sometimes we need it (or part of it) again
$this->icache['list'] = $list;
$this->icache['uri'] = $key;
$this->icache['root_path'] = $root_path;
$this->icache['child_locks'] = $child_locks;
return $list;
}
/**
* Locks a uri
*
* @param string $uri URI
* @param array $lock Lock data
*
* @return bool
*/
public function lock($uri, $lock)
{
// We're making the lock timeout max. 30 minutes
$timeout = min($lock['timeout'], 30*60);
$data = array(
$this->db->quote_identifier('uri') => $uri,
$this->db->quote_identifier('owner') => $lock['owner'],
$this->db->quote_identifier('scope') => $lock['scope'] == file_storage::LOCK_EXCLUSIVE ? self::EXCLUSIVE : self::SHARED,
$this->db->quote_identifier('depth') => $lock['depth'] == file_storage::LOCK_INFINITE ? self::INFINITE : 0,
$this->db->quote_identifier('timeout') => $timeout,
);
// check if lock exists
$locks = $this->lock_list($uri, false);
$exists = false;
foreach ($locks as $l) {
if ($l['token'] == $lock['token']) {
$exists = true;
break;
}
}
if ($exists) {
foreach (array_keys($data) as $key) {
$update_cols[] = "$key = ?";
}
$result = $this->db->query("UPDATE `{$this->table}`"
. " SET " . implode(', ', $update_cols)
. ", `expires` = " . $this->db->now($timeout)
. " WHERE `token` = ?",
array_merge(array_values($data), array($lock['token']))
);
}
else {
$data[$this->db->quote_identifier('token')] = $lock['token'];
$result = $this->db->query("INSERT INTO `{$this->table}`"
. " (".join(', ', array_keys($data)) . ", `expires`)"
. " VALUES (" . str_repeat('?, ', count($data)) . $this->db->now($timeout) . ")",
array_values($data)
);
}
return $this->db->affected_rows();
}
/**
* Removes a lock from a URI
*
* @param string $path URI
* @param array $lock Lock data
*
* @return bool
*/
public function unlock($uri, $lock)
{
$stmt = $this->db->query("DELETE FROM `{$this->table}`"
. " WHERE `uri` = ? AND `token` = ?",
$uri, $lock['token']);
- return $this->db->affected_rows();
+ return $this->db->affected_rows($stmt);
}
/**
* Remove expired locks
*/
public function gc()
{
$this->db->query("DELETE FROM `{$this->table}` WHERE `expires` < " . $this->db->now());
}
}
diff --git a/lib/file_ui.php b/lib/file_ui.php
index 13d6f92..970aef3 100644
--- a/lib/file_ui.php
+++ b/lib/file_ui.php
@@ -1,573 +1,573 @@
<?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 extends file_locale
{
/**
* @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();
const API_VERSION = 2;
/**
* 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();
}
/**
* 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) {
$schema = rcube_utils::https_check() ? 'https' : 'http';
$port = $schema == 'http' ? 80 : 443;
$url = $schema . '://' . $_SERVER['SERVER_NAME'];
$url .= $_SERVER['SERVER_PORT'] != $port ? ':' . $_SERVER['SERVER_PORT'] : '';
$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']);
if ($_SESSION['caps']['MAX_UPLOAD']) {
$this->output->assign('max_upload', $this->show_bytes($_SESSION['caps']['MAX_UPLOAD']));
}
}
/**
* 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'],
array('version' => self::API_VERSION));
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;
+// $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 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) - 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 >= 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;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 14, 3:30 PM (13 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
287378
Default Alt Text
(105 KB)

Event Timeline