Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F1974812
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
52 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/api/common.php b/lib/api/common.php
index 69d2fc7..b60bc66 100644
--- a/lib/api/common.php
+++ b/lib/api/common.php
@@ -1,166 +1,182 @@
<?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 file_api_common
{
protected $api;
protected $rc;
protected $args = array();
public function __construct($api)
{
$this->rc = rcube::get_instance();
$this->api = $api;
}
/**
* Request handler
*/
public function handle()
{
// GET arguments
$this->args = &$_GET;
// POST arguments (JSON)
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$post = file_get_contents('php://input');
$this->args += (array) json_decode($post, true);
unset($post);
}
// disable script execution time limit, so we can handle big files
@set_time_limit(360);
}
/**
* 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) {
$maxsize = ini_get('upload_max_filesize');
$maxsize = $this->show_bytes(parse_bytes($maxsize));
throw new Exception("Maximum file size ($maxsize) exceeded", file_api_core::ERROR_CODE);
}
throw new Exception("File upload failed", file_api_core::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') {
// if filesize exceeds post_max_size then $_FILES array is empty,
if ($maxsize = ini_get('post_max_size')) {
$maxsize = $this->show_bytes(parse_bytes($maxsize));
throw new Exception("Maximum file size ($maxsize) exceeded", file_api_core::ERROR_CODE);
}
throw new Exception("File upload failed", file_api_core::ERROR_CODE);
}
return $files;
}
/**
* 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->api);
if ($viewer->supports($mimetype)) {
return $viewer;
}
}
}
closedir($handle);
}
}
/**
* Parse driver metadata information
*/
protected function parse_metadata($metadata, $default = false)
{
if ($default) {
unset($metadata['form']);
$metadata['name'] .= ' (' . $this->api->translate('localstorage') . ')';
}
// localize form labels
foreach ($metadata['form'] as $key => $val) {
$label = $this->api->translate('form.' . $val);
if (strpos($label, 'form.') !== 0) {
$metadata['form'][$key] = $label;
}
}
return $metadata;
}
/**
* Get folder rights
*/
protected function folder_rights($folder)
{
list($driver, $path) = $this->api->get_driver($folder);
$rights = $driver->folder_rights($path);
$result = array();
$map = array(
file_storage::ACL_READ => 'read',
file_storage::ACL_WRITE => 'write',
);
foreach ($map as $key => $value) {
if ($rights & $key) {
$result[] = $value;
}
}
return $result;
}
+
+ /**
+ * Update manticore session on file/folder move
+ */
+ protected function session_uri_update($from, $to, $is_folder = false)
+ {
+ // check Manticore support. Note: we don't use config->get('fileapi_manticore')
+ // here as it may be not properly set if backend driver wasn't initialized yet
+ $capabilities = $this->api->capabilities(false);
+ if (empty($capabilities['MANTICORE'])) {
+ return;
+ }
+
+ $manticore = new file_manticore($this->api);
+ $manticore->session_uri_update($from, $to, $is_folder);
+ }
}
diff --git a/lib/api/file_move.php b/lib/api/file_move.php
index 6b89bc7..ce2ba10 100644
--- a/lib/api/file_move.php
+++ b/lib/api/file_move.php
@@ -1,158 +1,163 @@
<?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 file_api_file_move extends file_api_common
{
/**
* Request handler
*/
public function handle()
{
parent::handle();
if (!isset($this->args['file']) || $this->args['file'] === '') {
throw new Exception("Missing file name", file_api_core::ERROR_CODE);
}
if (is_array($this->args['file'])) {
if (empty($this->args['file'])) {
throw new Exception("Missing file name", file_api_core::ERROR_CODE);
}
}
else {
if (!isset($this->args['new']) || $this->args['new'] === '') {
throw new Exception("Missing new file name", file_api_core::ERROR_CODE);
}
$this->args['file'] = array($this->args['file'] => $this->args['new']);
}
$overwrite = rcube_utils::get_boolean((string) $this->args['overwrite']);
$request = $this instanceof file_api_file_copy ? 'file_copy' : 'file_move';
$errors = array();
foreach ((array) $this->args['file'] as $file => $new_file) {
if ($new_file === '') {
throw new Exception("Missing new file name", file_api_core::ERROR_CODE);
}
if ($new_file === $file) {
throw new Exception("Old and new file name is the same", file_api_core::ERROR_CODE);
}
list($driver, $path) = $this->api->get_driver($file);
list($new_driver, $new_path) = $this->api->get_driver($new_file);
try {
// source and destination on the same driver...
if ($driver == $new_driver) {
$driver->{$request}($path, $new_path);
}
// cross-driver move/copy...
else {
// first check if destination file exists
$info = null;
try {
$info = $new_driver->file_info($new_path);
}
catch (Exception $e) { }
if (!empty($info)) {
throw new Exception("File exists", file_storage::ERROR_FILE_EXISTS);
}
// copy/move between backends
$this->file_copy($driver, $new_driver, $path, $new_path, $request == 'file_move');
}
}
catch (Exception $e) {
if ($e->getCode() == file_storage::ERROR_FILE_EXISTS) {
// delete existing file and do copy/move again
if ($overwrite) {
$new_driver->file_delete($new_path);
if ($driver == $new_driver) {
$driver->{$request}($path, $new_path);
}
else {
$this->file_copy($driver, $new_driver, $path, $new_path, $request == 'file_move');
}
}
// 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;
}
}
+
+ // Update manticore sessions
+ if ($request == 'file_move') {
+ $this->session_uri_update($file, $new_file, false);
+ }
}
if (!empty($errors)) {
return array('already_exist' => $errors);
}
}
/**
* File copy/move between storage backends
*/
protected function file_copy($driver, $new_driver, $path, $new_path, $move = false)
{
// unable to put file on mount point
if (strpos($new_path, file_storage::SEPARATOR) === false) {
throw new Exception("Unable to copy/move file into specified location", file_api_core::ERROR_CODE);
}
// get the file from source location
$fp = fopen('php://temp', 'w+');
if (!$fp) {
throw new Exception("Internal server error", file_api_core::ERROR_CODE);
}
$driver->file_get($path, null, $fp);
rewind($fp);
$chunk = stream_get_contents($fp, 102400);
$type = rcube_mime::file_content_type($chunk, $new_path, 'application/octet-stream', true);
rewind($fp);
// upload the file to new location
$new_driver->file_create($new_path, array('content' => $fp, 'type' => $type));
fclose($fp);
// now we can remove the original file if it was a move action
if ($move) {
$driver->file_delete($path);
}
}
}
diff --git a/lib/api/folder_move.php b/lib/api/folder_move.php
index a06a05a..87bd546 100644
--- a/lib/api/folder_move.php
+++ b/lib/api/folder_move.php
@@ -1,183 +1,187 @@
<?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 file_api_folder_move extends file_api_common
{
/**
* Request handler
*/
public function handle()
{
parent::handle();
if (!isset($this->args['folder']) || $this->args['folder'] === '') {
throw new Exception("Missing folder name", file_api_core::ERROR_CODE);
}
if (!isset($this->args['new']) || $this->args['new'] === '') {
throw new Exception("Missing destination folder name", file_api_core::ERROR_CODE);
}
if ($this->args['new'] === $this->args['folder']) {
return;
}
list($src_driver, $src_path, $cfg) = $this->api->get_driver($this->args['folder']);
list($dst_driver, $dst_path) = $this->api->get_driver($this->args['new']);
// source folder is a mount point (driver title)...
if ($src_driver->title() === $this->args['folder']) {
// ...rename
if (strpos($this->args['new'], file_storage::SEPARATOR) === false) {
// destination folder is an existing mount point
if (!strlen($dst_path)) {
throw new Exception("Destination folder already exists", file_api_core::ERROR_CODE);
}
return $this->driver_rename($src_driver, $this->args['new'], $cfg);
}
throw new Exception("Unsupported operation", file_api_core::ERROR_CODE);
}
// cross-driver move
if ($src_driver != $dst_driver) {
// destination folder is an existing mount point
if (!strlen($dst_path)) {
throw new Exception("Destination folder already exists", file_api_core::ERROR_CODE);
}
- return $this->folder_move_to_other_driver($src_driver, $src_path, $dst_driver, $dst_path);
+ $this->folder_move_to_other_driver($src_driver, $src_path, $dst_driver, $dst_path);
+ }
+ else {
+ $src_driver->folder_move($src_path, $dst_path);
}
- return $src_driver->folder_move($src_path, $dst_path);
+ // Update manticore
+ $this->session_uri_update($this->args['folder'], $this->args['new'], true);
}
/**
* Move folder between two external storage locations
*/
protected function folder_move_to_other_driver($src_driver, $src_path, $dst_driver, $dst_path)
{
$src_folders = $src_driver->folder_list();
$dst_folders = $dst_driver->folder_list();
// first check if destination folder not exists
if (in_array($dst_path, $dst_folders)) {
throw new Exception("Destination folder already exists", file_api_core::ERROR_CODE);
}
// now recursively create/delete folders and copy their content
$this->move_folder_with_content($src_driver, $src_path, $dst_driver, $dst_path, $src_folders);
// now we can delete the folder
$src_driver->folder_delete($src_path);
}
/**
* Recursively moves folder and it's content to another location
*/
protected function move_folder_with_content($src_driver, $src_path, $dst_driver, $dst_path, $src_folders)
{
// create folder
$dst_driver->folder_create($dst_path);
foreach ($src_driver->file_list($src_path) as $filename => $file) {
$this->file_copy($src_driver, $dst_driver, $filename, $dst_path . file_storage::SEPARATOR . $file['name']);
}
// sub-folders...
foreach ($src_folders as $folder) {
if (strpos($folder, $src_path . file_storage::SEPARATOR) === 0
&& strpos($folder, file_storage::SEPARATOR, strlen($src_path) + 2) === false
) {
$destination = $dst_path . file_storage::SEPARATOR . substr($folder, strlen($src_path) + 1);
$this->move_folder_with_content($src_driver, $folder, $dst_driver, $destination, $src_folders);
}
}
}
/**
* File move between storage backends
*/
protected function file_copy($src_driver, $dst_driver, $src_path, $dst_path)
{
// unable to put file on mount point
if (strpos($dst_path, file_storage::SEPARATOR) === false) {
throw new Exception("Unable to move file into specified location", file_api_core::ERROR_CODE);
}
// get the file from source location
$fp = fopen('php://temp', 'w+');
if (!$fp) {
throw new Exception("Internal server error", file_api_core::ERROR_CODE);
}
$src_driver->file_get($src_path, null, $fp);
rewind($fp);
$chunk = stream_get_contents($fp, 102400);
$type = rcube_mime::file_content_type($chunk, $dst_path, 'application/octet-stream', true);
rewind($fp);
// upload the file to new location
$dst_driver->file_create($dst_path, array('content' => $fp, 'type' => $type));
fclose($fp);
}
/**
* External storage (mount point) rename
*/
protected function driver_rename($driver, $new_name, $config)
{
$backend = $this->api->get_backend();
$folders = $backend->folder_list();
// first check if destination folder not exists
if (in_array($new_name, $folders)) {
throw new Exception("Destination folder already exists", file_api_core::ERROR_CODE);
}
$title = $driver->title();
$config['title'] = $new_name;
// store passwords if it was already stored
if (empty($config['password']) || $config['password'] != '%u') {
unset($config['password']);
}
$backend->driver_update($title, $config);
// authenticate again to set session password
if (empty($config['password']) || $config['password'] != '%u') {
$auth = $driver->auth_info();
list($driver) = $this->api->get_driver($new_name);
$driver->authenticate($auth['username'], $auth['password']);
}
}
}
diff --git a/lib/file_manticore.php b/lib/file_manticore.php
index 18dfb2f..bb4af34 100644
--- a/lib/file_manticore.php
+++ b/lib/file_manticore.php
@@ -1,826 +1,860 @@
<?php
/**
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2015, 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> |
+--------------------------------------------------------------------------+
*/
/**
* Document editing sessions handling
*/
class file_manticore
{
protected $api;
protected $rc;
protected $request;
protected $user;
protected $sessions_table = 'chwala_sessions';
protected $invitations_table = 'chwala_invitations';
protected $icache = array();
const STATUS_INVITED = 'invited';
const STATUS_REQUESTED = 'requested';
const STATUS_ACCEPTED = 'accepted';
const STATUS_DECLINED = 'declined';
const STATUS_DECLINED_OWNER = 'declined-owner'; // same as 'declined' but done by the session owner
const STATUS_ACCEPTED_OWNER = 'accepted-owner'; // same as 'accepted' but done by the session owner
/**
* Class constructor
*
* @param file_api Chwala API app instance
*/
public function __construct($api)
{
$this->rc = rcube::get_instance();
$this->api = $api;
$this->user = $_SESSION['user'];
$db = $this->rc->get_dbh();
$this->sessions_table = $db->table_name($this->sessions_table);
$this->invitations_table = $db->table_name($this->invitations_table);
}
/**
* Return viewer URI for specified file/session. This creates
* a new collaborative editing session when needed.
*
* @param string $file File path
* @param string &$session_id Optional session ID to join to
*
* @return string Manticore URI
* @throws Exception
*/
public function session_start($file, &$session_id = null)
{
if ($file !== null) {
- list($driver, $path) = $this->api->get_driver($file);
- $uri = $driver->path2uri($path);
+ $uri = $this->path2uri($file, $driver);
}
$backend = $this->api->get_backend();
if ($session_id) {
$session = $this->session_info($session_id);
if (empty($session)) {
throw new Exception("Document session not found.", file_api_core::ERROR_CODE);
}
// check session ownership
if ($session['owner'] != $this->user) {
// check if the user was invited
$invitations = $this->invitations_find(array('session_id' => $session_id, 'user' => $this->user));
$states = array(self::STATUS_INVITED, self::STATUS_ACCEPTED, self::STATUS_ACCEPTED_OWNER);
if (empty($invitations) || !in_array($invitations[0]['status'], $states)) {
throw new Exception("No permission to join the editing session.", file_api_core::ERROR_CODE);
}
// automatically accept the invitation, if not done yet
if ($invitations[0]['status'] == self::STATUS_INVITED) {
$this->invitation_update($session_id, $this->user, self::STATUS_ACCEPTED);
}
}
// authenticate to Manticore, we need auth token for frame_uri
$req = $this->get_request();
// @TODO: make sure the session exists in Manticore?
}
else if (!empty($uri)) {
// To prevent from creating new sessions for the same file+user
// (e.g. when user uses F5 to refresh the page), we check first
// if such a session exist and continue with it
$db = $this->rc->get_dbh();
$res = $db->query("SELECT `id` FROM `{$this->sessions_table}`"
. " WHERE `owner` = ? AND `uri` = ?", $this->user, $uri);
if ($row = $db->fetch_assoc($res)) {
$session_id = $row['id'];
$res = true;
}
else if (!$db->is_error($res)) {
$session_id = rcube_utils::bin2ascii(md5(time() . $uri, true));
$data = array();
$owner = $this->user;
// we'll store user credentials if the file comes from
// an external source that requires authentication
if ($backend != $driver) {
$auth = $driver->auth_info();
$auth['password'] = $this->rc->encrypt($auth['password']);
$data['auth_info'] = $auth;
}
$res = $this->session_create($session_id, $uri, $owner, $data);
}
if (!$res) {
throw new Exception("Failed creating document editing session", file_api_core::ERROR_CODE);
}
}
else {
throw new Exception("Failed creating document editing session (unknown file)", file_api_core::ERROR_CODE);
}
return $this->frame_uri($session_id);
}
/**
* Get file path (not URI) from session.
*
* @param string $id Session ID
* @param bool $join_mode Throw exception only if session does not exist
*
* @return string File path
* @throws Exception
*/
public function session_file($id, $join_mode = false)
{
$session = $this->session_info($id);
if (empty($session)) {
throw new Exception("Document session not found.", file_api_core::ERROR_CODE);
}
$path = $this->uri2path($session['uri']);
if (empty($path) && (!$join_mode || $session['owner'] == $this->user)) {
throw new Exception("Document session not found.", file_api_core::ERROR_CODE);
}
// check permissions to the session
if ($session['owner'] != $this->user) {
$invitations = $this->invitations_find(array('session_id' => $id, 'user' => $this->user));
$states = array(self::STATUS_INVITED, self::STATUS_ACCEPTED, self::STATUS_ACCEPTED_OWNER);
if (empty($invitations) || !in_array($invitations[0]['status'], $states)) {
throw new Exception("No permission to join the editing session.", file_api_core::ERROR_CODE);
}
}
return $path;
}
/**
* Get editing session info
*
* @param string $id Session identifier
* @param bool $with_invitations Return invitations list
*
* @return array Session data
*/
public function session_info($id, $with_invitations = false)
{
$session = $this->icache["session:$id"];
if (!$session) {
$db = $this->rc->get_dbh();
$result = $db->query("SELECT * FROM `{$this->sessions_table}`"
. " WHERE `id` = ?", $id);
if ($row = $db->fetch_assoc($result)) {
$session = $this->session_info_parse($row);
$this->icache["session:$id"] = $session;
}
}
if ($session) {
if ($session['owner'] == $this->user) {
$session['is_owner'] = true;
}
if ($with_invitations && $session['is_owner']) {
$session['invitations'] = $this->invitations_find(array('session_id' => $id));
}
}
return $session;
}
/**
* Find editing sessions for specified path
*/
public function session_find($path, $invitations = true)
{
// create an URI for specified path
- list($driver, $path) = $this->api->get_driver($path);
-
- $uri = trim($driver->path2uri($path), '/') . '/';
+ $uri = trim($this->path2uri($path), '/') . '/';
// get existing sessions
$sessions = array();
$filter = array('file', 'owner', 'owner_name', 'is_owner');
$db = $this->rc->get_dbh();
$result = $db->query("SELECT * FROM `{$this->sessions_table}`"
. " WHERE `uri` LIKE '" . $db->escape($uri) . "%'");
while ($row = $db->fetch_assoc($result)) {
if ($path = $this->uri2path($row['uri'])) {
$sessions[$row['id']] = $this->session_info_parse($row, $path, $filter);
}
}
// set 'is_invited' flag
if ($invitations && !empty($sessions)) {
$invitations = $this->invitations_find(array('user' => $this->user));
$states = array(self::STATUS_INVITED, self::STATUS_ACCEPTED, self::STATUS_ACCEPTED_OWNER);
foreach ($invitations as $invitation) {
if (!empty($sessions[$invitation['session_id']]) && in_array($invitation['status'], $states)) {
$sessions[$invitation['session_id']]['is_invited'] = true;
}
}
}
return $sessions;
}
/**
* Delete editing session (only owner can do that)
*
* @param string $id Session identifier
* @param bool $local Remove session only from local database
*/
public function session_delete($id, $local = false)
{
$db = $this->rc->get_dbh();
$result = $db->query("DELETE FROM `{$this->sessions_table}`"
. " WHERE `id` = ? AND `owner` = ?",
$id, $this->user);
$success = $db->affected_rows($result) > 0;
// Send document delete to Manticore
if ($success && !$local) {
$req = $this->get_request();
$res = $req->document_delete($id);
}
return $success;
}
/**
* Create editing session
*/
protected function session_create($id, $uri, $owner, $data)
{
// get user name
$owner_name = $this->api->resolve_user($owner) ?: '';
// Do this before starting the session in Manticore,
// it will immediately call api/document to get the file body
$db = $this->rc->get_dbh();
$result = $db->query("INSERT INTO `{$this->sessions_table}`"
. " (`id`, `uri`, `owner`, `owner_name`, `data`)"
. " VALUES (?, ?, ?, ?, ?)",
$id, $uri, $owner, $owner_name, json_encode($data));
$success = $db->affected_rows($result) > 0;
// create the session in Manticore
if ($success) {
$req = $this->get_request();
$res = $req->document_create(array(
'id' => $id,
'title' => '', // @TODO: maybe set to a file path without extension?
'access' => array(
array(
'identity' => $owner,
'permission' => file_manticore_api::ACCESS_WRITE,
),
),
));
if (!$res) {
$this->session_delete($id, true);
return false;
}
}
return $success;
}
/**
* Find sessions, including:
* 1. to which the user has access (is a creator or has been invited)
* 2. to which the user is considered eligible to request authorization
* to participate in the session by already having access to the file
*
* @param array $param List parameters
*
* @return array Sessions list
*/
public function sessions_list($param = array())
{
$db = $this->rc->get_dbh();
$sessions = array();
// 1. Get sessions user has access to
$result = $db->query("SELECT s.`id`, s.`uri`, s.`owner`, s.`owner_name`"
. " FROM `{$this->sessions_table}` s"
. " WHERE s.`owner` = ? OR s.`id` IN ("
. "SELECT i.`session_id` FROM `{$this->invitations_table}` i"
. " WHERE i.`user` = ?"
. ")",
$this->user, $this->user);
if ($db->is_error($result)) {
throw new Exception("Internal error.", file_api_core::ERROR_CODE);
}
while ($row = $db->fetch_assoc($result)) {
if ($path = $this->uri2path($row['uri'], true)) {
$sessions[$row['id']] = $this->session_info_parse($row, $path);
// For performance reasons we don't want to fetch info of every file
// on the list. As we support only ODT files here...
$sessions[$row['id']]['type'] = 'application/vnd.oasis.opendocument.text';
}
}
- // 2. Get sessions user is eligible by access to the file
+ // 2. Get sessions user is eligible
// - get list of all folder URIs and find sessions for files in these locations
// @FIXME: in corner cases (user has many folders) this may produce a big query,
// maybe fetching all sessions and then comparing with list of locations would be faster?
$uris = $this->all_folder_locations();
$where = array_map(function($uri) use ($db) {
return 's.`uri` LIKE ' . $db->quote(str_replace('%', '_', $uri) . '/%');
}, $uris);
$result = $db->query("SELECT s.`id`, s.`uri`, s.`owner`, s.`owner_name`"
. " FROM `{$this->sessions_table}` s WHERE " . join(' OR ', $where));
if ($db->is_error($result)) {
throw new Exception("Internal error.", file_api_core::ERROR_CODE);
}
while ($row = $db->fetch_assoc($result)) {
if (empty($sessions[$row['id']])) {
// remove filename (and anything after it) so we have the folder URI
// to check if it's on the folders list we have
$uri = substr($row['uri'], 0, strrpos($row['uri'], '/'));
if (in_array($uri, $uris) && ($path = $this->uri2path($row['uri'], true))) {
$sessions[$row['id']] = $this->session_info_parse($row, $path);
// For performance reasons we don't want to fetch info of every file
// on the list. As we support only ODT files here...
$sessions[$row['id']]['type'] = 'application/vnd.oasis.opendocument.text';
}
}
}
// set 'is_invited' flag
if (!empty($sessions)) {
$invitations = $this->invitations_find(array('user' => $this->user));
$states = array(self::STATUS_INVITED, self::STATUS_ACCEPTED, self::STATUS_ACCEPTED_OWNER);
foreach ($invitations as $invitation) {
if (!empty($sessions[$invitation['session_id']]) && in_array($invitation['status'], $states)) {
$sessions[$invitation['session_id']]['is_invited'] = true;
}
}
}
// Sorting
$sort = !empty($params['sort']) ? $params['sort'] : 'name';
$index = array();
if (in_array($sort, array('name', 'file', 'owner'))) {
foreach ($sessions as $key => $val) {
if ($sort == 'name' || $sort == 'file') {
$path = explode(file_storage::SEPARATOR, $val['file']);
$index[$key] = $path[count($path) - 1];
continue;
}
$index[$key] = $val[$sort];
}
array_multisort($index, SORT_ASC, SORT_LOCALE_STRING, $sessions);
}
if ($params['reverse']) {
$sessions = array_reverse($sessions, true);
}
return $sessions;
}
/**
* Find invitations for current user. This will return all
* invitations related to the user including his sessions.
*
* @param array $filter Search filter (see self::invitations_find())
*
* @return array Invitations list
*/
public function invitations_list($filter = array())
{
$filter['user'] = $this->user;
// list of invitations to the user or requested by him
$result = $this->invitations_find($filter, true);
unset($filter['user']);
$filter['owner'] = $this->user;
// other invitations that belong to the sessions owned by the user
if ($other = $this->invitations_find($filter, true)) {
$result = array_merge($result, $other);
}
return $result;
}
/**
* Find invitations for specified filter
*
* @param array $filter Search filter (see self::invitations_find())
* - session_id: session identifier
* - timestamp: "changed > ?" filter
* - user: Invitation user identifier
* - owner: Session owner identifier
* @param bool $extended Return session file names
*
* @return array Invitations list
*/
public function invitations_find($filter, $extended = false)
{
$db = $this->rc->get_dbh();
$query = '';
$select = "i.*";
foreach ($filter as $column => $value) {
if ($column == 'timestamp') {
$where[] = "i.`changed` > " . $db->fromunixtime($value);
}
else if ($column == 'owner') {
$join[] = "`{$this->sessions_table}` s ON (i.`session_id` = s.`id`)";
$where[] = "s.`owner` = " . $db->quote($value);
}
else {
$where[] = "i.`$column` = " . $db->quote($value);
}
}
if ($extended) {
$select .= ", s.`uri`, s.`owner`, s.`owner_name`";
$join[] = "`{$this->sessions_table}` s ON (i.`session_id` = s.`id`)";
}
if (!empty($join)) {
$query .= ' JOIN ' . implode(' JOIN ', array_unique($join));
}
if (!empty($where)) {
$query .= ' WHERE ' . implode(' AND ', array_unique($where));
}
$result = $db->query("SELECT $select FROM `{$this->invitations_table}` i"
. "$query ORDER BY i.`changed`");
if ($db->is_error($result)) {
throw new Exception("Internal error.", file_api_core::ERROR_CODE);
}
$invitations = array();
while ($row = $db->fetch_assoc($result)) {
if ($extended) {
try {
// add unix-timestamp of the `changed` date to the result
$dt = new DateTime($row['changed']);
$row['timestamp'] = $dt->format('U');
}
catch(Exception $e) { }
// add filename to the result
$filename = parse_url($row['uri'], PHP_URL_PATH);
$filename = pathinfo($filename, PATHINFO_BASENAME);
$filename = rawurldecode($filename);
$row['filename'] = $filename;
if ($path = $this->uri2path($row['uri'])) {
$row['file'] = $path;
}
unset($row['uri']);
}
$invitations[] = $row;
}
return $invitations;
}
/**
* Create an invitation
*
* @param string $session_id Document session identifier
* @param string $user User identifier (use null for current user)
* @param string $status Invitation status (invited, requested)
* @param string $comment Invitation description/comment
* @param string &$user_name Optional user name
*
* @throws Exception
*/
public function invitation_create($session_id, $user, $status = 'invited', $comment = '', &$user_name = '')
{
if (empty($user)) {
$user = $this->user;
}
if ($status != self::STATUS_INVITED && $status != self::STATUS_REQUESTED) {
throw new Exception("Invalid invitation status.", file_api_core::ERROR_CODE);
}
// get session information
$session = $this->session_info($session_id);
if (empty($session)) {
throw new Exception("Document session not found.", file_api_core::ERROR_CODE);
}
// check session ownership, only owner can create 'new' invitations
if ($status == self::STATUS_INVITED && $session['owner'] != $this->user) {
throw new Exception("No permission to create an invitation.", file_api_core::ERROR_CODE);
}
if ($session['owner'] == $user) {
throw new Exception("Not possible to create an invitation for the session creator.", file_api_core::ERROR_CODE);
}
// Update Manticore 'access' array
if ($status == self::STATUS_INVITED) {
$req = $this->get_request();
$res = $req->editor_add($session_id, $user, file_manticore_api::ACCESS_WRITE);
if (!$res) {
throw new Exception("Failed to create an invitation.", file_api_core::ERROR_CODE);
}
}
// get user name
if (empty($user_name)) {
$user_name = $this->api->resolve_user($user) ?: '';
}
// insert invitation
$db = $this->rc->get_dbh();
$result = $db->query("INSERT INTO `{$this->invitations_table}`"
. " (`session_id`, `user`, `user_name`, `status`, `comment`, `changed`)"
. " VALUES (?, ?, ?, ?, ?, " . $db->now() . ")",
$session_id, $user, $user_name, $status, $comment ?: '');
if (!$db->affected_rows($result)) {
throw new Exception("Failed to create an invitation.", file_api_core::ERROR_CODE);
}
}
/**
* Delete an invitation (only session owner can do that)
*
* @param string $session_id Session identifier
* @param string $user User identifier
*
* @throws Exception
*/
public function invitation_delete($session_id, $user)
{
$db = $this->rc->get_dbh();
$result = $db->query("DELETE FROM `{$this->invitations_table}`"
. " WHERE `session_id` = ? AND `user` = ?"
. " AND EXISTS (SELECT 1 FROM `{$this->sessions_table}` WHERE `id` = ? AND `owner` = ?)",
$session_id, $user, $session_id, $this->user);
if (!$db->affected_rows($result)) {
throw new Exception("Failed to delete an invitation.", file_api_core::ERROR_CODE);
}
// Update Manticore 'access' array
$req = $this->get_request();
$res = $req->editor_delete($session_id, $user);
if (!$res) {
throw new Exception("Failed to remove an invitation.", file_api_core::ERROR_CODE);
}
}
/**
* Update an invitation status
*
* @param string $session_id Session identifier
* @param string $user User identifier (use null for current user)
* @param string $status Invitation status (accepted, declined)
* @param string $comment Invitation description/comment
*
* @throws Exception
*/
public function invitation_update($session_id, $user, $status, $comment = '')
{
if (empty($user)) {
$user = $this->user;
}
if ($status != self::STATUS_ACCEPTED && $status != self::STATUS_DECLINED) {
throw new Exception("Invalid invitation status.", file_api_core::ERROR_CODE);
}
// get session information
$session = $this->session_info($session_id);
if (empty($session)) {
throw new Exception("Document session not found.", file_api_core::ERROR_CODE);
}
// check session ownership
if ($user != $this->user && $session['owner'] != $this->user) {
throw new Exception("No permission to update an invitation.", file_api_core::ERROR_CODE);
}
if ($session['owner'] == $this->user) {
$status = $status . '-owner';
}
$db = $this->rc->get_dbh();
$result = $db->query("UPDATE `{$this->invitations_table}`"
. " SET `status` = ?, `comment` = ?, `changed` = " . $db->now()
. " WHERE `session_id` = ? AND `user` = ?",
$status, $comment ?: '', $session_id, $user);
if (!$db->affected_rows($result)) {
throw new Exception("Failed to update an invitation status.", file_api_core::ERROR_CODE);
}
// Update Manticore 'access' array if an owner accepted an invitation request
if ($status == self::STATUS_ACCEPTED_OWNER) {
$req = $this->get_request();
$res = $req->editor_add($session_id, $user, file_manticore_api::ACCESS_WRITE);
if (!$res) {
throw new Exception("Failed to update an invitation status.", file_api_core::ERROR_CODE);
}
}
}
+ /**
+ * Update a session URI (e.g. on file/folder move)
+ *
+ * @param string $from Source file/folder path
+ * @param string $to Destination file/folder path
+ * @param bool $is_folder True if the path is a folder
+ */
+ public function session_uri_update($from, $to, $is_folder = false)
+ {
+ $db = $this->rc->get_dbh();
+
+ // Resolve paths
+ $from = $this->path2uri($from);
+ $to = $this->path2uri($to);
+
+ if ($is_folder) {
+ $set = "`uri` = REPLACE(`uri`, " . $db->quote($from . '/') . ", " . $db->quote($to .'/') . ")";
+ $where = "`uri` LIKE " . $db->quote(str_replace('%', '_', $from) . '/%');
+ }
+ else {
+ $set = "`uri` = " . $db->quote($to);
+ $where = "`uri` = " . $db->quote($from);
+ }
+
+ $db->query("UPDATE `{$this->sessions_table}` SET $set WHERE $where");
+ }
+
/**
* Parse session info data
*/
protected function session_info_parse($record, $path = null, $filter = array())
{
$session = array();
$fields = array('id', 'uri', 'owner', 'owner_name');
foreach ($fields as $field) {
if (isset($record[$field])) {
$session[$field] = $record[$field];
}
}
if ($path) {
$session['file'] = $path;
}
// @TODO: is_invited?, last_modified?
if ($session['owner'] == $this->user) {
$session['is_owner'] = true;
}
if (!empty($filter)) {
$session = array_intersect_key($session, array_flip($filter));
}
return $session;
}
/**
* Generate URI of Manticore editing session
*/
protected function frame_uri($id)
{
$base_url = rtrim($this->rc->config->get('fileapi_manticore'), ' /');
return $base_url . '/document/' . $id . '/' . $_SESSION['manticore_token'];
}
+ /**
+ * Get file URI from path
+ */
+ protected function path2uri($path, &$driver = null)
+ {
+ list($driver, $path) = $this->api->get_driver($path);
+
+ return $driver->path2uri($path);
+ }
+
/**
* Get file path from the URI
*/
protected function uri2path($uri, $use_fallback = false)
{
$backend = $this->api->get_backend();
try {
return $backend->uri2path($uri);
}
catch (Exception $e) {
// do nothing
}
foreach ($this->api->get_drivers(true) as $driver) {
try {
$path = $driver->uri2path($uri);
$title = $driver->title();
if ($title) {
$path = $title . file_storage::SEPARATOR . $path;
}
return $path;
}
catch (Exception $e) {
// do nothing
}
}
// likely user has no access to the file, but has been invited,
// extract filename from the URI
if ($use_fallback && $uri) {
$path = parse_url($uri, PHP_URL_PATH);
$path = explode('/', $path);
$path = $path[count($path) - 1];
return $path;
}
}
/**
* Initialize Manticore API request handler
*/
protected function get_request()
{
if (!$this->request) {
$uri = rcube_utils::resolve_url($this->rc->config->get('fileapi_manticore'));
$this->request = new file_manticore_api($uri);
// Use stored session token, check if it's still valid
if ($_SESSION['manticore_token']) {
$is_valid = $this->request->set_session_token($_SESSION['manticore_token'], true);
if ($is_valid) {
return $this->request;
}
}
$backend = $this->api->get_backend();
$auth = $backend->auth_info();
$_SESSION['manticore_token'] = $this->request->login($auth['username'], $auth['password']);
if (empty($_SESSION['manticore_token'])) {
throw new Exception("Unable to login to Manticore server.", file_api_core::ERROR_CODE);
}
}
return $this->request;
}
/**
* Get URI of all user folders (with shared locations)
*/
protected function all_folder_locations()
{
$locations = array();
foreach (array_merge(array($this->api->get_backend()), $this->api->get_drivers(true)) as $driver) {
// Performance optimization: We're interested here in shared folders,
// Kolab is the only driver that currently supports them, ignore others
if (get_class($driver) != 'kolab_file_storage') {
continue;
}
try {
foreach ($driver->folder_list() as $folder) {
if ($uri = $driver->path2uri($folder)) {
$locations[] = $uri;
}
}
}
catch (Exception $e) {
// do nothing
}
}
return $locations;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Sep 15, 2:15 AM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
287455
Default Alt Text
(52 KB)
Attached To
Mode
R26 chwala
Attached
Detach File
Event Timeline
Log In to Comment