2016-08-12 16:29:55 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* EGroupware - Filemanager Collab
|
|
|
|
*
|
|
|
|
* @link http://www.egroupware.org
|
|
|
|
* @package filemanager
|
|
|
|
* @author Hadi Nategh <hn-AT-stylite.de>
|
|
|
|
* @copyright (c) 2016 by Stylite AG
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @version $Id$
|
|
|
|
*/
|
|
|
|
|
|
|
|
use EGroupware\Api;
|
|
|
|
|
|
|
|
class filemanager_collab extends filemanager_collab_bo {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Methods callable via menuaction
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
var $public_functions = array(
|
|
|
|
'poll' => true
|
|
|
|
);
|
|
|
|
|
2016-08-23 17:57:03 +02:00
|
|
|
/**
|
|
|
|
* session identification for an empty new file
|
|
|
|
*/
|
|
|
|
const NEW_FILE_ES_ID = 'new';
|
|
|
|
|
2016-08-12 16:29:55 +02:00
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
function __construct()
|
|
|
|
{
|
|
|
|
parent::__construct();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Join session, initialises edit session for opened file by user
|
|
|
|
*
|
2016-08-23 17:57:03 +02:00
|
|
|
* @param type $es_id session id, 'new' session id means it's an empty
|
|
|
|
* template opened as new file, and should not be store in DB.
|
|
|
|
*
|
2016-08-12 16:29:55 +02:00
|
|
|
* @return array returns an array consists of session data
|
|
|
|
*/
|
|
|
|
function join_session ($es_id)
|
|
|
|
{
|
2016-08-23 17:57:03 +02:00
|
|
|
if ($es_id === self::NEW_FILE_ES_ID)
|
|
|
|
{
|
|
|
|
$response = array(
|
|
|
|
'member_id' => '0',
|
|
|
|
'es_id' => self::NEW_FILE_ES_ID
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$response = $this->initSession($es_id);
|
|
|
|
}
|
2016-08-12 16:29:55 +02:00
|
|
|
$response += array (
|
|
|
|
'id' => $GLOBALS['egw_info']['user']['account_id'],
|
2016-08-19 16:38:44 +02:00
|
|
|
'full_name' => $GLOBALS['egw_info']['user']['account_fullname'],
|
|
|
|
'success' => true
|
2016-08-12 16:29:55 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
2016-08-17 17:23:22 +02:00
|
|
|
/**
|
|
|
|
* This function gets called when user leaves the session
|
|
|
|
*
|
|
|
|
* @param string $es_id
|
|
|
|
* @param string $member_id
|
|
|
|
*
|
|
|
|
* @return array returns an array of data as response for client-side
|
|
|
|
*/
|
|
|
|
function leave_session ($es_id, $member_id)
|
|
|
|
{
|
2016-08-22 17:46:48 +02:00
|
|
|
if (!$this->is_sessionValid($es_id) || !$this->is_memberValid($es_id, $member_id)) throw new Exception ('Session is not valid!');
|
2016-08-25 12:51:59 +02:00
|
|
|
$this->MEMBER_UpdateActiveMember($es_id, $member_id, 0);
|
2016-08-17 17:23:22 +02:00
|
|
|
return array (
|
|
|
|
'session_id' => $es_id,
|
|
|
|
'memberid' => $member_id,
|
|
|
|
'success' => $this->OP_removeMember($es_id, $member_id)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-08-12 16:29:55 +02:00
|
|
|
/**
|
|
|
|
* Polling mechanisim to sysncronise data
|
|
|
|
*
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
function poll ()
|
|
|
|
{
|
|
|
|
// Get POST request payload
|
|
|
|
$payload = file_get_contents('php://input');
|
|
|
|
$params = $payload? json_decode ($payload, true): null;
|
|
|
|
$response = array();
|
|
|
|
if (is_array($params))
|
|
|
|
{
|
|
|
|
switch ($params['command'])
|
|
|
|
{
|
|
|
|
case 'join_session':
|
|
|
|
$response = $this->join_session($params['args']['es_id'],$params['args']['user_id']);
|
|
|
|
break;
|
2016-08-17 17:23:22 +02:00
|
|
|
case 'leave_session':
|
2016-08-23 17:57:03 +02:00
|
|
|
if ($params['args']['es_id'] === self::NEW_FILE_ES_ID)
|
|
|
|
{
|
|
|
|
$response = array ('success' => true,'memberid' => '0','session_id'=>self::NEW_FILE_ES_ID);
|
|
|
|
break;
|
|
|
|
}
|
2016-08-17 17:23:22 +02:00
|
|
|
$response = $this->leave_session($params['args']['es_id'],$params['args']['member_id']);
|
|
|
|
break;
|
2016-08-12 16:29:55 +02:00
|
|
|
case 'sync_ops':
|
|
|
|
try
|
|
|
|
{
|
2016-08-23 17:57:03 +02:00
|
|
|
// handle new file operation
|
|
|
|
if ($params['args']['es_id'] === self::NEW_FILE_ES_ID)
|
|
|
|
{
|
|
|
|
if (!$params['args']['client_ops'] && !$params['args']['seq_head'])
|
|
|
|
{
|
|
|
|
$response = $this->prepare_newFile();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$response = array(
|
|
|
|
'result' => 'added',
|
|
|
|
'seq_head' => 1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-08-12 16:29:55 +02:00
|
|
|
$memberid = $params['args']['member_id']? $params['args']['member_id']: '';
|
|
|
|
$es_id = $params['args']['es_id'];
|
|
|
|
$seq_head = (string) isset($params['args']['seq_head'])? $params['args']['seq_head']: null;
|
2016-08-22 17:46:48 +02:00
|
|
|
// we need to inform clients about session changes to update themselves
|
|
|
|
// based on that. For instance, after discarding changes other participants
|
|
|
|
// should get notified to reload their session.
|
|
|
|
if (!$this->is_memberValid($es_id, $memberid)) {
|
|
|
|
$response = array (
|
|
|
|
'result' => 'error',
|
|
|
|
'error' => 'ENOSESSION'
|
|
|
|
);
|
|
|
|
throw new Exception('Session is not valid!');
|
|
|
|
}
|
|
|
|
|
2016-08-12 16:29:55 +02:00
|
|
|
if(!is_null($seq_head))
|
|
|
|
{
|
|
|
|
$client_ops = $params['args']['client_ops']? $params['args']['client_ops']: [];
|
|
|
|
$current_seq_head = $this->OP_getHeadSeq($es_id);
|
2016-08-19 16:38:44 +02:00
|
|
|
if ($seq_head == $current_seq_head) {
|
2016-08-12 16:29:55 +02:00
|
|
|
|
|
|
|
if (count($client_ops)>0)
|
|
|
|
{
|
|
|
|
$newHead = $this->get_newHead($es_id, $memberid, $client_ops);
|
|
|
|
$response = array(
|
|
|
|
'result' => 'added',
|
|
|
|
'head_seq' => $newHead ? $newHead : $current_seq_head
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$response = array(
|
|
|
|
'result' => 'new_ops',
|
|
|
|
'ops' => array(),
|
|
|
|
'head_seq' => $current_seq_head
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$response = array(
|
|
|
|
'result' => count($client_ops)>0 ? 'conflict' : 'new_ops',
|
|
|
|
'ops' => $this->OP_getOPSECS($es_id, $seq_head),
|
|
|
|
'head_seq' => $current_seq_head,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
throw new Exception('Invalid seq head!');
|
|
|
|
}
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
error_log($ex->getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
|
|
}
|
|
|
|
}
|
|
|
|
header('content-type: application/json; charset=utf-8');
|
|
|
|
echo json_encode($response);
|
|
|
|
exit();
|
|
|
|
}
|
|
|
|
|
2016-08-23 17:57:03 +02:00
|
|
|
/**
|
|
|
|
* This function prepare an op structure for new file operation
|
|
|
|
* as new file is not saved yet in database we need to satisfy the
|
|
|
|
* client in order to be able to edit an empty document.
|
|
|
|
*
|
|
|
|
* @return array return op structure
|
|
|
|
*/
|
|
|
|
function prepare_newFile()
|
|
|
|
{
|
|
|
|
$date = new Api\DateTime();
|
|
|
|
$use_id = $GLOBALS['egw_info']['user']['account_id'];
|
|
|
|
$response = array (
|
|
|
|
'result' => 'new_ops',
|
|
|
|
'ops'=> array (
|
|
|
|
0 => array (
|
|
|
|
'optype' => 'AddMember',
|
|
|
|
'memberid' => '0',
|
|
|
|
'timestamp' => $date->getTimestamp(),
|
|
|
|
'setProperties' => array(
|
|
|
|
'fullName' => $GLOBALS['egw_info']['user']['account_fullname'],
|
|
|
|
'color' => $GLOBALS['egw_info']['user']['preferences']['filemanager']['collab_user_color'],
|
|
|
|
'imageUrl' => $GLOBALS['egw_info']['server']['webserver_url'].'/index.php?menuaction=addressbook.addressbook_ui.photo&account_id='.$use_id,
|
|
|
|
'uid' => $use_id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
),
|
|
|
|
'head_seq' => '1'
|
|
|
|
);
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
2016-08-12 16:29:55 +02:00
|
|
|
/**
|
|
|
|
* Function to get a new head sequence
|
|
|
|
*
|
|
|
|
* @param string $es_id session id
|
|
|
|
* @param string $member_id member id
|
|
|
|
* @param array $client_ops opspec from client side
|
|
|
|
*
|
|
|
|
* @return string return a seq head number
|
|
|
|
*/
|
|
|
|
function get_newHead ($es_id, $member_id, $client_ops)
|
|
|
|
{
|
|
|
|
$this->OP_addOPS($es_id, $member_id, $client_ops);
|
|
|
|
|
|
|
|
return $this->OP_getHeadSeq($es_id);
|
|
|
|
}
|
|
|
|
|
2016-08-17 17:23:22 +02:00
|
|
|
/**
|
2016-08-22 17:46:48 +02:00
|
|
|
* Ajax function to handle actions called by client-side
|
|
|
|
* types: save, delete, discard
|
2016-08-17 17:23:22 +02:00
|
|
|
*
|
2016-08-25 12:51:59 +02:00
|
|
|
* @param array $params
|
|
|
|
* @param string $action
|
2016-08-17 17:23:22 +02:00
|
|
|
*/
|
2016-08-25 12:51:59 +02:00
|
|
|
function ajax_actions ($params, $action)
|
2016-08-17 17:23:22 +02:00
|
|
|
{
|
2016-08-25 12:51:59 +02:00
|
|
|
$response = Api\Json\Response::get();
|
2016-08-17 17:23:22 +02:00
|
|
|
switch ($action)
|
|
|
|
{
|
|
|
|
case 'save':
|
2016-08-25 12:51:59 +02:00
|
|
|
$this->SESSION_Save($params['es_id']);
|
2016-08-24 17:46:09 +02:00
|
|
|
//update genesis file after save happened
|
2016-08-25 12:51:59 +02:00
|
|
|
if ($params['file_path']) self::generateGenesis ($params['file_path'], $params['es_id']);
|
2016-08-17 17:23:22 +02:00
|
|
|
break;
|
2016-08-22 13:05:47 +02:00
|
|
|
case 'delete':
|
2016-08-25 12:51:59 +02:00
|
|
|
$this->SESSION_cleanup($params['es_id']);
|
2016-08-22 13:05:47 +02:00
|
|
|
break;
|
2016-08-22 17:46:48 +02:00
|
|
|
case 'discard':
|
2016-08-25 12:51:59 +02:00
|
|
|
$this->OP_Discard($params['es_id']);
|
|
|
|
break;
|
|
|
|
case 'checkLastMember':
|
|
|
|
$activeMembers = $this->MEMBER_getActiveMembers($params['es_id']);
|
|
|
|
$response->data(is_array($activeMembers) && count($activeMembers) > 1?false:true);
|
2016-08-22 17:46:48 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
2016-08-17 17:23:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-19 17:59:44 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the collaboration is allowed for given file path
|
|
|
|
*
|
|
|
|
* @param string $file_path file path
|
|
|
|
* @param int $_right VFS file access right
|
2016-08-22 13:05:47 +02:00
|
|
|
*
|
2016-08-19 17:59:44 +02:00
|
|
|
* @return boolean returns true if allowed
|
|
|
|
*/
|
|
|
|
function is_collabAllowed ($file_path, $_right=null)
|
2016-08-17 17:23:22 +02:00
|
|
|
{
|
2016-08-19 16:38:44 +02:00
|
|
|
$paths = explode('/webdav.php', $file_path);
|
|
|
|
$right = $_right ? $_right : Api\Vfs::WRITABLE;
|
|
|
|
$allowed = Api\Vfs::check_access($paths[1], $right) &&
|
|
|
|
!preg_match('/\/api\/js\/webodf\/template.odf$/', $file_path);
|
2016-08-17 17:23:22 +02:00
|
|
|
return $allowed;
|
|
|
|
}
|
2016-08-19 16:38:44 +02:00
|
|
|
|
2016-08-22 13:05:47 +02:00
|
|
|
/**
|
|
|
|
* Check if session is valid
|
|
|
|
*
|
|
|
|
* @param type $es_id
|
|
|
|
*
|
|
|
|
* @return boolean return true if session is valid otherwise false
|
|
|
|
*/
|
|
|
|
function is_sessionValid ($es_id)
|
|
|
|
{
|
|
|
|
$session = $this->SESSION_Get($es_id);
|
|
|
|
return $session? true : false;
|
|
|
|
}
|
|
|
|
|
2016-08-22 17:46:48 +02:00
|
|
|
/**
|
|
|
|
* Check if the member id of the session has valid status
|
|
|
|
* status: 1 is valid 0 is invalid
|
|
|
|
*
|
|
|
|
* @param type $es_id
|
|
|
|
* @param type $member_id
|
|
|
|
* @return boolean returns true if it's valid
|
|
|
|
*/
|
|
|
|
function is_memberValid ($es_id, $member_id)
|
|
|
|
{
|
|
|
|
$member = $this->MEMBER_getMember($es_id, $member_id);
|
|
|
|
return $member && $member['status'] != 0 ? true: false;
|
|
|
|
}
|
|
|
|
|
2016-08-19 17:59:44 +02:00
|
|
|
/**
|
|
|
|
* Function to get genesis url by generating a temp genesis temp file
|
|
|
|
* out of given path, and returnig es_id md5 hash and genesis url to
|
|
|
|
* client.
|
|
|
|
*
|
|
|
|
* @param type $file_path file path
|
2016-08-23 17:57:03 +02:00
|
|
|
* @param boolean $_isNew true means this is an empty doc opened as new file
|
|
|
|
* in client-side and not stored yet therefore no genesis file should get generated for it.
|
2016-08-19 17:59:44 +02:00
|
|
|
*/
|
2016-08-23 17:57:03 +02:00
|
|
|
function ajax_getGenesisUrl ($file_path, $_isNew)
|
2016-08-19 16:38:44 +02:00
|
|
|
{
|
|
|
|
$result = array();
|
|
|
|
$es_id = md5($file_path);
|
|
|
|
$response = Api\Json\Response::get();
|
2016-08-23 17:57:03 +02:00
|
|
|
// handle new empty file
|
|
|
|
if ($_isNew)
|
|
|
|
{
|
|
|
|
$response->data(array (
|
|
|
|
'es_id' => self::NEW_FILE_ES_ID,
|
|
|
|
'genesis_url' => $GLOBALS['egw_info']['server']['webserver_url'].'/api/js/webodf/template.odt'
|
|
|
|
));
|
|
|
|
return;
|
|
|
|
}
|
2016-08-19 16:38:44 +02:00
|
|
|
$session = $this->SESSION_Get($es_id);
|
|
|
|
|
|
|
|
if ($session && $session['genesis_url'] !== '')
|
|
|
|
{
|
2016-08-24 17:46:09 +02:00
|
|
|
$gen_file = explode('/webdav.php',$session['genesis_url']);
|
|
|
|
if (!Api\Vfs::file_exists($gen_file[1])) self::generateGenesis ($file_path, $es_id);
|
2016-08-19 16:38:44 +02:00
|
|
|
$result = array (
|
|
|
|
'es_id' => $session['es_id'],
|
|
|
|
'genesis_url' => $session['genesis_url']
|
|
|
|
);
|
|
|
|
}
|
2016-08-19 17:59:44 +02:00
|
|
|
else if ($this->is_collabAllowed($file_path, Api\Vfs::WRITABLE))
|
2016-08-19 16:38:44 +02:00
|
|
|
{
|
|
|
|
$result = array (
|
|
|
|
'es_id' => $es_id,
|
2016-08-24 17:46:09 +02:00
|
|
|
'genesis_url' => self::generateGenesis($file_path, $es_id)
|
2016-08-19 16:38:44 +02:00
|
|
|
);
|
2016-08-24 17:46:09 +02:00
|
|
|
$this->SESSION_add2Db($es_id, $result['genesis_url']);
|
2016-08-19 16:38:44 +02:00
|
|
|
}
|
|
|
|
$response->data($result);
|
|
|
|
}
|
2016-08-24 17:46:09 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a genesis file out of given file path and session id
|
|
|
|
*
|
|
|
|
* @param type $file_path file path in webdav format example: egroupware/webdav.php/home/sysop/test.odt
|
|
|
|
* @param type $es_id session id
|
|
|
|
*
|
|
|
|
* @return string returns genesis url
|
|
|
|
*/
|
|
|
|
static function generateGenesis ($file_path, $es_id)
|
|
|
|
{
|
|
|
|
$paths = explode('/webdav.php', $file_path);
|
|
|
|
$dir_parts = explode('/',$paths[1]);
|
|
|
|
array_pop($dir_parts);
|
|
|
|
$dir = join('/', $dir_parts);
|
|
|
|
$genesis_file = $dir.'/.'.$es_id.'.webodf.odt';
|
|
|
|
$genesis_url = $paths[0].'/webdav.php'.$genesis_file;
|
|
|
|
Api\Vfs::copy($paths[1], $genesis_file);
|
|
|
|
return $genesis_url;
|
|
|
|
}
|
2016-08-12 16:29:55 +02:00
|
|
|
}
|