parent
f304fd4629
commit
75af90b4f1
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Richdocuments\AppInfo;
|
||||||
|
|
||||||
|
use OC\AppFramework\Utility\TimeFactory;
|
||||||
|
use OCA\Richdocuments\WOPI\DiscoveryManager;
|
||||||
|
use OCP\AppFramework\App;
|
||||||
|
use OCP\AppFramework\IAppContainer;
|
||||||
|
|
||||||
|
class Application extends App {
|
||||||
|
public function __construct (array $urlParams = array()) {
|
||||||
|
parent::__construct('richdocuments', $urlParams);
|
||||||
|
$container = $this->getContainer();
|
||||||
|
$container->registerService(
|
||||||
|
DiscoveryManager::class,
|
||||||
|
function(IAppContainer $container) {
|
||||||
|
return new DiscoveryManager(
|
||||||
|
$container->getServer()->getHTTPClientService(),
|
||||||
|
$container->getServer()->getAppDataDir('richdocuments'),
|
||||||
|
$container->getServer()->getConfig(),
|
||||||
|
$container->getServer()->getL10N('richdocuments'),
|
||||||
|
new TimeFactory()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,94 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2014 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This file is licensed under the Affero General Public License version 3 or
|
|
||||||
* later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments\AppInfo;
|
|
||||||
|
|
||||||
use \OCP\AppFramework\App;
|
|
||||||
|
|
||||||
use \OCA\Richdocuments\Controller\UserController;
|
|
||||||
use \OCA\Richdocuments\Controller\SessionController;
|
|
||||||
use \OCA\Richdocuments\Controller\DocumentController;
|
|
||||||
use \OCA\Richdocuments\Controller\SettingsController;
|
|
||||||
use \OCA\Richdocuments\AppConfig;
|
|
||||||
|
|
||||||
class Application extends App {
|
|
||||||
public function __construct (array $urlParams = array()) {
|
|
||||||
parent::__construct('richdocuments', $urlParams);
|
|
||||||
|
|
||||||
$container = $this->getContainer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controllers
|
|
||||||
*/
|
|
||||||
$container->registerService('UserController', function($c) {
|
|
||||||
return new UserController(
|
|
||||||
$c->query('AppName'),
|
|
||||||
$c->query('Request')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
$container->registerService('SessionController', function($c) {
|
|
||||||
return new SessionController(
|
|
||||||
$c->query('AppName'),
|
|
||||||
$c->query('Request'),
|
|
||||||
$c->query('Logger'),
|
|
||||||
$c->query('UserId')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
$container->registerService('DocumentController', function($c) {
|
|
||||||
return new DocumentController(
|
|
||||||
$c->query('AppName'),
|
|
||||||
$c->query('Request'),
|
|
||||||
$c->query('CoreConfig'),
|
|
||||||
$c->query('AppConfig'),
|
|
||||||
$c->query('L10N'),
|
|
||||||
$c->query('UserId'),
|
|
||||||
$c->query('ICacheFactory'),
|
|
||||||
$c->query('Logger')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
$container->registerService('SettingsController', function($c) {
|
|
||||||
return new SettingsController(
|
|
||||||
$c->query('AppName'),
|
|
||||||
$c->query('Request'),
|
|
||||||
$c->query('L10N'),
|
|
||||||
$c->query('AppConfig'),
|
|
||||||
$c->query('UserId')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$container->registerService('AppConfig', function($c) {
|
|
||||||
return new AppConfig(
|
|
||||||
$c->query('CoreConfig')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Core
|
|
||||||
*/
|
|
||||||
$container->registerService('Logger', function($c) {
|
|
||||||
return $c->query('ServerContainer')->getLogger();
|
|
||||||
});
|
|
||||||
$container->registerService('CoreConfig', function($c) {
|
|
||||||
return $c->query('ServerContainer')->getConfig();
|
|
||||||
});
|
|
||||||
$container->registerService('L10N', function($c) {
|
|
||||||
return $c->query('ServerContainer')->getL10N($c->query('AppName'));
|
|
||||||
});
|
|
||||||
$container->registerService('UserId', function($c) {
|
|
||||||
$user = $c->query('ServerContainer')->getUserSession()->getUser();
|
|
||||||
$uid = is_null($user) ? '' : $user->getUID();
|
|
||||||
return $uid;
|
|
||||||
});
|
|
||||||
$container->registerService('ICacheFactory', function($c) {
|
|
||||||
return $c->query('ServerContainer')->getMemCacheFactory();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,753 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2014 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This file is licensed under the Affero General Public License version 3 or
|
|
||||||
* later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments\Controller;
|
|
||||||
|
|
||||||
use \OCP\AppFramework\Controller;
|
|
||||||
use \OCP\IRequest;
|
|
||||||
use \OCP\IConfig;
|
|
||||||
use \OCP\IL10N;
|
|
||||||
use \OCP\AppFramework\Http\ContentSecurityPolicy;
|
|
||||||
use \OCP\AppFramework\Http\JSONResponse;
|
|
||||||
use \OCP\AppFramework\Http\TemplateResponse;
|
|
||||||
|
|
||||||
use \OCA\Richdocuments\AppConfig;
|
|
||||||
use \OCA\Richdocuments\Db;
|
|
||||||
use \OCA\Richdocuments\Helper;
|
|
||||||
use \OCA\Richdocuments\Storage;
|
|
||||||
use \OCA\Richdocuments\Download;
|
|
||||||
use \OCA\Richdocuments\DownloadResponse;
|
|
||||||
use \OCA\Richdocuments\File;
|
|
||||||
use \OCA\Richdocuments\Genesis;
|
|
||||||
use \OC\Files\View;
|
|
||||||
use \OCP\ICacheFactory;
|
|
||||||
use \OCP\ILogger;
|
|
||||||
|
|
||||||
class ResponseException extends \Exception {
|
|
||||||
private $hint;
|
|
||||||
|
|
||||||
public function __construct($description, $hint = '') {
|
|
||||||
parent::__construct($description);
|
|
||||||
$this->hint = $hint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHint() {
|
|
||||||
return $this->hint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DocumentController extends Controller {
|
|
||||||
|
|
||||||
private $uid;
|
|
||||||
private $l10n;
|
|
||||||
private $settings;
|
|
||||||
private $appConfig;
|
|
||||||
private $cache;
|
|
||||||
private $logger;
|
|
||||||
const ODT_TEMPLATE_PATH = '/assets/odttemplate.odt';
|
|
||||||
|
|
||||||
public function __construct($appName, IRequest $request, IConfig $settings, AppConfig $appConfig, IL10N $l10n, $uid, ICacheFactory $cache, ILogger $logger){
|
|
||||||
parent::__construct($appName, $request);
|
|
||||||
$this->uid = $uid;
|
|
||||||
$this->l10n = $l10n;
|
|
||||||
$this->settings = $settings;
|
|
||||||
$this->appConfig = $appConfig;
|
|
||||||
$this->cache = $cache->create($appName);
|
|
||||||
$this->logger = $logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \SimpleXMLElement $discovery
|
|
||||||
* @param string $mimetype
|
|
||||||
*/
|
|
||||||
private function getWopiSrcUrl($discovery_parsed, $mimetype) {
|
|
||||||
if(is_null($discovery_parsed) || $discovery_parsed == false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $discovery_parsed->xpath(sprintf('/wopi-discovery/net-zone/app[@name=\'%s\']/action', $mimetype));
|
|
||||||
if ($result && count($result) > 0) {
|
|
||||||
return array(
|
|
||||||
'urlsrc' => (string)$result[0]['urlsrc'],
|
|
||||||
'action' => (string)$result[0]['name']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log the user with given $userid.
|
|
||||||
* This function should only be used from public controller methods where no
|
|
||||||
* existing session exists, for example, when loolwsd is directly calling a
|
|
||||||
* public method with its own access token. After validating the access
|
|
||||||
* token, and retrieving the correct user with help of access token, it can
|
|
||||||
* be set as current user with help of this method.
|
|
||||||
*
|
|
||||||
* @param string $userid
|
|
||||||
*/
|
|
||||||
private function loginUser($userid) {
|
|
||||||
\OC_Util::tearDownFS();
|
|
||||||
|
|
||||||
$users = \OC::$server->getUserManager()->search($userid, 1, 0);
|
|
||||||
if (count($users) > 0) {
|
|
||||||
$user = array_shift($users);
|
|
||||||
if (strcasecmp($user->getUID(), $userid) === 0) {
|
|
||||||
// clear the existing sessions, if any
|
|
||||||
\OC::$server->getSession()->close();
|
|
||||||
|
|
||||||
// initialize a dummy memory session
|
|
||||||
$session = new \OC\Session\Memory('');
|
|
||||||
// wrap it
|
|
||||||
$cryptoWrapper = \OC::$server->getSessionCryptoWrapper();
|
|
||||||
$session = $cryptoWrapper->wrapSession($session);
|
|
||||||
// set our session
|
|
||||||
\OC::$server->setSession($session);
|
|
||||||
|
|
||||||
\OC::$server->getUserSession()->setUser($user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
\OC_Util::setupFS();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log out the current user
|
|
||||||
* This is helpful when we are artifically logged in as someone
|
|
||||||
*/
|
|
||||||
private function logoutUser() {
|
|
||||||
\OC_Util::tearDownFS();
|
|
||||||
|
|
||||||
\OC::$server->getSession()->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function responseError($message, $hint = ''){
|
|
||||||
$errors = array('errors' => array(array('error' => $message, 'hint' => $hint)));
|
|
||||||
$response = new TemplateResponse('', 'error', $errors, 'error');
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the original wopi url or test wopi url
|
|
||||||
*/
|
|
||||||
private function getWopiUrl($tester) {
|
|
||||||
$wopiurl = '';
|
|
||||||
if ($tester) {
|
|
||||||
$wopiurl = $this->appConfig->getAppValue('test_wopi_url');
|
|
||||||
} else {
|
|
||||||
$wopiurl = $this->appConfig->getAppValue('wopi_url');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $wopiurl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the currently logged in user is a tester.
|
|
||||||
* This depends on whether current user is the member of one of the groups
|
|
||||||
* mentioned in settings (test_server_groups)
|
|
||||||
*/
|
|
||||||
private function isTester() {
|
|
||||||
$tester = false;
|
|
||||||
|
|
||||||
$user = \OC::$server->getUserSession()->getUser()->getUID();
|
|
||||||
$testgroups = array_filter(explode('|', $this->appConfig->getAppValue('test_server_groups')));
|
|
||||||
\OC::$server->getLogger()->debug('Testgroups are {testgroups}', [
|
|
||||||
'app' => $this->appName,
|
|
||||||
'testgroups' => $testgroups
|
|
||||||
]);
|
|
||||||
foreach ($testgroups as $testgroup) {
|
|
||||||
$test = \OC::$server->getGroupManager()->get($testgroup);
|
|
||||||
if ($test !== null && sizeof($test->searchUsers($user)) > 0) {
|
|
||||||
\OC::$server->getLogger()->debug('User {user} found in {group}', [
|
|
||||||
'app' => $this->appName,
|
|
||||||
'user' => $user,
|
|
||||||
'group' => $testgroup
|
|
||||||
]);
|
|
||||||
|
|
||||||
$tester = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tester;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Return the content of discovery.xml - either from cache, or download it.
|
|
||||||
*/
|
|
||||||
private function getDiscovery(){
|
|
||||||
\OC::$server->getLogger()->debug('getDiscovery(): Getting discovery.xml from the cache.');
|
|
||||||
|
|
||||||
$tester = $this->isTester();
|
|
||||||
$wopiRemote = $this->getWopiUrl($tester);
|
|
||||||
$discoveryKey = 'discovery.xml';
|
|
||||||
if ($tester) {
|
|
||||||
$discoveryKey = 'discovery.xml_test';
|
|
||||||
}
|
|
||||||
// Provides access to information about the capabilities of a WOPI client
|
|
||||||
// and the mechanisms for invoking those abilities through URIs.
|
|
||||||
$wopiDiscovery = $wopiRemote . '/hosting/discovery';
|
|
||||||
|
|
||||||
// Read the memcached value (if the memcache is installed)
|
|
||||||
$discovery = $this->cache->get($discoveryKey);
|
|
||||||
|
|
||||||
if (is_null($discovery)) {
|
|
||||||
$contact_admin = $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote));
|
|
||||||
|
|
||||||
try {
|
|
||||||
$wopiClient = \OC::$server->getHTTPClientService()->newClient();
|
|
||||||
$discovery = $wopiClient->get($wopiDiscovery)->getBody();
|
|
||||||
}
|
|
||||||
catch (\Exception $e) {
|
|
||||||
$error_message = $e->getMessage();
|
|
||||||
if (preg_match('/^cURL error ([0-9]*):/', $error_message, $matches)) {
|
|
||||||
$admin_check = $this->l10n->t('Please ask your administrator to check the Collabora Online server setting. The exact error message was: ') . $error_message;
|
|
||||||
|
|
||||||
$curl_error = $matches[1];
|
|
||||||
switch ($curl_error) {
|
|
||||||
case '1':
|
|
||||||
throw new ResponseException($this->l10n->t('Collabora Online: The protocol specified in "%s" is not allowed.', array($wopiRemote)), $admin_check);
|
|
||||||
case '3':
|
|
||||||
throw new ResponseException($this->l10n->t('Collabora Online: Malformed URL "%s".', array($wopiRemote)), $admin_check);
|
|
||||||
case '6':
|
|
||||||
throw new ResponseException($this->l10n->t('Collabora Online: Cannot resolve the host "%s".', array($wopiRemote)), $admin_check);
|
|
||||||
case '7':
|
|
||||||
throw new ResponseException($this->l10n->t('Collabora Online: Cannot connect to the host "%s".', array($wopiRemote)), $admin_check);
|
|
||||||
case '60':
|
|
||||||
throw new ResponseException($this->l10n->t('Collabora Online: SSL certificate is not installed.'), $this->l10n->t('Please ask your administrator to add ca-chain.cert.pem to the ca-bundle.crt, for example "cat /etc/loolwsd/ca-chain.cert.pem >> <server-installation>/resources/config/ca-bundle.crt" . The exact error message was: ') . $error_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ResponseException($this->l10n->t('Collabora Online unknown error: ') . $error_message, $contact_admin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$discovery) {
|
|
||||||
throw new ResponseException($this->l10n->t('Collabora Online: Unable to read discovery.xml from "%s".', array($wopiRemote)), $contact_admin);
|
|
||||||
}
|
|
||||||
|
|
||||||
\OC::$server->getLogger()->debug('Storing the discovery.xml under key ' . $discoveryKey . ' to the cache.');
|
|
||||||
$this->cache->set($discoveryKey, $discovery, 3600);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $discovery;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Prepare document(s) structure
|
|
||||||
*/
|
|
||||||
private function prepareDocuments($rawDocuments){
|
|
||||||
$discovery_parsed = null;
|
|
||||||
try {
|
|
||||||
$discovery = $this->getDiscovery();
|
|
||||||
|
|
||||||
$loadEntities = libxml_disable_entity_loader(true);
|
|
||||||
$discovery_parsed = simplexml_load_string($discovery);
|
|
||||||
libxml_disable_entity_loader($loadEntities);
|
|
||||||
|
|
||||||
if ($discovery_parsed === false) {
|
|
||||||
$this->cache->remove('discovery.xml');
|
|
||||||
$wopiRemote = $this->getWopiUrl($this->isTester());
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => $this->l10n->t('Collabora Online: discovery.xml from "%s" is not a well-formed XML string.', array($wopiRemote)),
|
|
||||||
'hint' => $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ResponseException $e) {
|
|
||||||
return array(
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => $e->getMessage(),
|
|
||||||
'hint' => $e->getHint()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$fileIds = array();
|
|
||||||
$documents = array();
|
|
||||||
$lolang = strtolower(str_replace('_', '-', $this->settings->getUserValue($this->uid, 'core', 'lang', 'en')));
|
|
||||||
foreach ($rawDocuments as $key=>$document) {
|
|
||||||
if (is_object($document)){
|
|
||||||
$documents[] = $document->getData();
|
|
||||||
} else {
|
|
||||||
$documents[$key] = $document;
|
|
||||||
}
|
|
||||||
$documents[$key]['icon'] = preg_replace('/\.png$/', '.svg', \OCP\Template::mimetype_icon($document['mimetype']));
|
|
||||||
$documents[$key]['hasPreview'] = \OC::$server->getPreviewManager()->isMimeSupported($document['mimetype']);
|
|
||||||
$ret = $this->getWopiSrcUrl($discovery_parsed, $document['mimetype']);
|
|
||||||
$documents[$key]['urlsrc'] = $ret['urlsrc'];
|
|
||||||
$documents[$key]['action'] = $ret['action'];
|
|
||||||
$documents[$key]['lolang'] = $lolang;
|
|
||||||
$fileIds[] = $document['fileid'];
|
|
||||||
}
|
|
||||||
|
|
||||||
usort($documents, function($a, $b){
|
|
||||||
return @$b['mtime']-@$a['mtime'];
|
|
||||||
});
|
|
||||||
|
|
||||||
$session = new Db\Session();
|
|
||||||
$sessions = $session->getCollectionBy('file_id', $fileIds);
|
|
||||||
|
|
||||||
$members = array();
|
|
||||||
$member = new Db\Member();
|
|
||||||
foreach ($sessions as $session) {
|
|
||||||
$members[$session['es_id']] = $member->getActiveCollection($session['es_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'status' => 'success', 'documents' => $documents,'sessions' => $sessions,'members' => $members
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @NoCSRFRequired
|
|
||||||
*/
|
|
||||||
public function index(){
|
|
||||||
$wopiRemote = $this->getWopiUrl($this->isTester());
|
|
||||||
if (($parts = parse_url($wopiRemote)) && isset($parts['scheme']) && isset($parts['host'])) {
|
|
||||||
$webSocketProtocol = "ws://";
|
|
||||||
if ($parts['scheme'] == "https") {
|
|
||||||
$webSocketProtocol = "wss://";
|
|
||||||
}
|
|
||||||
$webSocket = sprintf(
|
|
||||||
"%s%s%s",
|
|
||||||
$webSocketProtocol,
|
|
||||||
$parts['host'],
|
|
||||||
isset($parts['port']) ? ":" . $parts['port'] : "");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return $this->responseError($this->l10n->t('Collabora Online: Invalid URL "%s".', array($wopiRemote)), $this->l10n->t('Please ask your administrator to check the Collabora Online server setting.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = \OC::$server->getUserSession()->getUser();
|
|
||||||
$usergroups = array_filter(\OC::$server->getGroupManager()->getUserGroupIds($user));
|
|
||||||
$usergroups = join('|', $usergroups);
|
|
||||||
\OC::$server->getLogger()->debug('User is in groups: {groups}', [ 'app' => $this->appName, 'groups' => $usergroups ]);
|
|
||||||
|
|
||||||
\OC::$server->getNavigationManager()->setActiveEntry( 'richdocuments_index' );
|
|
||||||
$maxUploadFilesize = \OCP\Util::maxUploadFilesize("/");
|
|
||||||
$response = new TemplateResponse('richdocuments', 'documents', [
|
|
||||||
'enable_previews' => $this->settings->getSystemValue('enable_previews', true),
|
|
||||||
'uploadMaxFilesize' => $maxUploadFilesize,
|
|
||||||
'uploadMaxHumanFilesize' => \OCP\Util::humanFileSize($maxUploadFilesize),
|
|
||||||
'allowShareWithLink' => $this->settings->getAppValue('core', 'shareapi_allow_links', 'yes'),
|
|
||||||
'wopi_url' => $webSocket,
|
|
||||||
'doc_format' => $this->appConfig->getAppValue('doc_format')
|
|
||||||
]);
|
|
||||||
|
|
||||||
$policy = new ContentSecurityPolicy();
|
|
||||||
$policy->addAllowedScriptDomain('\'self\' http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.min.js \'unsafe-eval\' ' . $wopiRemote);
|
|
||||||
/* frame-src is deprecated on Firefox, but Safari wants it! */
|
|
||||||
$policy->addAllowedFrameDomain('\'self\' http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.min.js \'unsafe-eval\' ' . $wopiRemote . ' blob:');
|
|
||||||
$policy->addAllowedChildSrcDomain('\'self\' http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js http://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.12/jquery.mousewheel.min.js \'unsafe-eval\' ' . $wopiRemote);
|
|
||||||
$policy->addAllowedConnectDomain($webSocket);
|
|
||||||
$policy->addAllowedImageDomain('*');
|
|
||||||
$policy->allowInlineScript(true);
|
|
||||||
$policy->addAllowedFontDomain('data:');
|
|
||||||
$response->setContentSecurityPolicy($policy);
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*/
|
|
||||||
public function create(){
|
|
||||||
$mimetype = $this->request->post['mimetype'];
|
|
||||||
$filename = $this->request->post['filename'];
|
|
||||||
$dir = $this->request->post['dir'];
|
|
||||||
|
|
||||||
$view = new View('/' . $this->uid . '/files');
|
|
||||||
if (!$dir){
|
|
||||||
$dir = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
$basename = $this->l10n->t('New Document.odt');
|
|
||||||
switch ($mimetype) {
|
|
||||||
case 'application/vnd.oasis.opendocument.spreadsheet':
|
|
||||||
$basename = $this->l10n->t('New Spreadsheet.ods');
|
|
||||||
break;
|
|
||||||
case 'application/vnd.oasis.opendocument.presentation':
|
|
||||||
$basename = $this->l10n->t('New Presentation.odp');
|
|
||||||
break;
|
|
||||||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
|
||||||
$basename = $this->l10n->t('New Document.docx');
|
|
||||||
break;
|
|
||||||
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
|
||||||
$basename = $this->l10n->t('New Spreadsheet.xlsx');
|
|
||||||
break;
|
|
||||||
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
|
||||||
$basename = $this->l10n->t('New Presentation.pptx');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// to be safe
|
|
||||||
$mimetype = 'application/vnd.oasis.opendocument.text';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$filename){
|
|
||||||
$path = Helper::getNewFileName($view, $dir . '/' . $basename);
|
|
||||||
} else {
|
|
||||||
$path = $dir . '/' . $filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
$content = '';
|
|
||||||
if (class_exists('\OC\Files\Type\TemplateManager')){
|
|
||||||
$manager = \OC_Helper::getFileTemplateManager();
|
|
||||||
$content = $manager->getTemplate($mimetype);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$content){
|
|
||||||
$content = file_get_contents(dirname(__DIR__) . self::ODT_TEMPLATE_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
$discovery_parsed = null;
|
|
||||||
try {
|
|
||||||
$discovery = $this->getDiscovery();
|
|
||||||
|
|
||||||
$loadEntities = libxml_disable_entity_loader(true);
|
|
||||||
$discovery_parsed = simplexml_load_string($discovery);
|
|
||||||
libxml_disable_entity_loader($loadEntities);
|
|
||||||
|
|
||||||
if ($discovery_parsed === false) {
|
|
||||||
$this->cache->remove('discovery.xml');
|
|
||||||
$wopiRemote = $this->getWopiUrl($this->isTester());
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => $this->l10n->t('Collabora Online: discovery.xml from "%s" is not a well-formed XML string.', array($wopiRemote)),
|
|
||||||
'hint' => $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ResponseException $e) {
|
|
||||||
return array(
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => $e->getMessage(),
|
|
||||||
'hint' => $e->getHint()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($content && $view->file_put_contents($path, $content)){
|
|
||||||
$info = $view->getFileInfo($path);
|
|
||||||
$ret = $this->getWopiSrcUrl($discovery_parsed, $mimetype);
|
|
||||||
$response = array(
|
|
||||||
'status' => 'success',
|
|
||||||
'fileid' => $info['fileid'],
|
|
||||||
'urlsrc' => $ret['urlsrc'],
|
|
||||||
'action' => $ret['action'],
|
|
||||||
'lolang' => $this->settings->getUserValue($this->uid, 'core', 'lang', 'en'),
|
|
||||||
'data' => \OCA\Files\Helper::formatFileInfo($info)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$response = array(
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => (string) $this->l10n->t('Can\'t create document')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* Generates and returns an access token for a given fileId.
|
|
||||||
* Only for authenticated users!
|
|
||||||
*/
|
|
||||||
public function wopiGetToken($fileId){
|
|
||||||
$arr = explode('_', $fileId, 2);
|
|
||||||
$version = '0';
|
|
||||||
if (count($arr) == 2) {
|
|
||||||
$fileId = $arr[0];
|
|
||||||
$version = $arr[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
\OC::$server->getLogger()->debug('Generating WOPI Token for file {fileId}, version {version}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version ]);
|
|
||||||
|
|
||||||
$view = \OC\Files\Filesystem::getView();
|
|
||||||
$path = $view->getPath($fileId);
|
|
||||||
$updatable = (bool)$view->isUpdatable($path);
|
|
||||||
|
|
||||||
// Check if the editor (user who is accessing) is in editable group
|
|
||||||
// UserCanWrite only if
|
|
||||||
// 1. No edit groups are set or
|
|
||||||
// 2. if they are set, it is in one of the edit groups
|
|
||||||
$editorUid = \OC::$server->getUserSession()->getUser()->getUID();
|
|
||||||
$editGroups = array_filter(explode('|', $this->appConfig->getAppValue('edit_groups')));
|
|
||||||
if ($updatable && count($editGroups) > 0) {
|
|
||||||
$updatable = false;
|
|
||||||
foreach($editGroups as $editGroup) {
|
|
||||||
$editorGroup = \OC::$server->getGroupManager()->get($editGroup);
|
|
||||||
if ($editorGroup !== null && sizeof($editorGroup->searchUsers($editorUid)) > 0) {
|
|
||||||
\OC::$server->getLogger()->debug("Editor {editor} is in edit group {group}", [
|
|
||||||
'app' => $this->appName,
|
|
||||||
'editor' => $editorUid,
|
|
||||||
'group' => $editGroup
|
|
||||||
]);
|
|
||||||
$updatable = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If token is for some versioned file
|
|
||||||
if ($version !== '0') {
|
|
||||||
\OC::$server->getLogger()->debug('setting updatable to false');
|
|
||||||
$updatable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
\OC::$server->getLogger()->debug('File with {fileid} has updatable set to {updatable}', [ 'app' => $this->appName, 'fileid' => $fileId, 'updatable' => $updatable ]);
|
|
||||||
|
|
||||||
$row = new Db\Wopi();
|
|
||||||
$serverHost = $this->request->getServerProtocol() . '://' . $this->request->getServerHost();
|
|
||||||
$token = $row->generateFileToken($fileId, $version, $updatable, $serverHost);
|
|
||||||
|
|
||||||
// Return the token.
|
|
||||||
return array(
|
|
||||||
'status' => 'success',
|
|
||||||
'token' => $token
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @NoCSRFRequired
|
|
||||||
* @PublicPage
|
|
||||||
* Returns general info about a file.
|
|
||||||
*/
|
|
||||||
public function wopiCheckFileInfo($fileId){
|
|
||||||
$token = $this->request->getParam('access_token');
|
|
||||||
|
|
||||||
$arr = explode('_', $fileId, 2);
|
|
||||||
$version = '0';
|
|
||||||
if (count($arr) == 2) {
|
|
||||||
$fileId = $arr[0];
|
|
||||||
$version = $arr[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
\OC::$server->getLogger()->debug('Getting info about file {fileId}, version {version} by token {token}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version, 'token' => $token ]);
|
|
||||||
|
|
||||||
$row = new Db\Wopi();
|
|
||||||
$row->loadBy('token', $token);
|
|
||||||
|
|
||||||
$res = $row->getPathForToken($fileId, $version, $token);
|
|
||||||
if ($res == false || http_response_code() != 200)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login the user to see his mount locations
|
|
||||||
$this->loginUser($res['owner']);
|
|
||||||
$view = new \OC\Files\View('/' . $res['owner'] . '/files');
|
|
||||||
$info = $view->getFileInfo($res['path']);
|
|
||||||
$this->logoutUser();
|
|
||||||
|
|
||||||
if (!$info) {
|
|
||||||
http_response_code(404);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$editorName = \OC::$server->getUserManager()->get($res['editor'])->getDisplayName();
|
|
||||||
return array(
|
|
||||||
'BaseFileName' => $info['name'],
|
|
||||||
'Size' => $info['size'],
|
|
||||||
'Version' => $version,
|
|
||||||
'UserId' => $res['editor'],
|
|
||||||
'UserFriendlyName' => $editorName,
|
|
||||||
'UserCanWrite' => $res['canwrite'] ? true : false,
|
|
||||||
'PostMessageOrigin' => $res['server_host']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @NoCSRFRequired
|
|
||||||
* @PublicPage
|
|
||||||
* Given an access token and a fileId, returns the contents of the file.
|
|
||||||
* Expects a valid token in access_token parameter.
|
|
||||||
*/
|
|
||||||
public function wopiGetFile($fileId){
|
|
||||||
$token = $this->request->getParam('access_token');
|
|
||||||
|
|
||||||
$arr = explode('_', $fileId, 2);
|
|
||||||
$version = '0';
|
|
||||||
if (count($arr) == 2) {
|
|
||||||
$fileId = $arr[0];
|
|
||||||
$version = $arr[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
\OC::$server->getLogger()->debug('Getting contents of file {fileId}, version {version} by token {token}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version, 'token' => $token ]);
|
|
||||||
|
|
||||||
$row = new Db\Wopi();
|
|
||||||
$row->loadBy('token', $token);
|
|
||||||
|
|
||||||
//TODO: Support X-WOPIMaxExpectedSize header.
|
|
||||||
$res = $row->getPathForToken($fileId, $version, $token);
|
|
||||||
$ownerid = $res['owner'];
|
|
||||||
|
|
||||||
// Login the user to see his mount locations
|
|
||||||
$this->loginUser($ownerid);
|
|
||||||
$view = new \OC\Files\View('/' . $res['owner'] . '/files');
|
|
||||||
$info = $view->getFileInfo($res['path']);
|
|
||||||
|
|
||||||
if (!$info) {
|
|
||||||
http_response_code(404);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$filename = '';
|
|
||||||
// If some previous version is requested, fetch it from Files_Version app
|
|
||||||
if ($version !== '0') {
|
|
||||||
\OCP\JSON::checkAppEnabled('files_versions');
|
|
||||||
|
|
||||||
$filename = '/files_versions/' . $info['name'] . '.v' . $version;
|
|
||||||
} else {
|
|
||||||
$filename = '/files' . $res['path'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logoutUser();
|
|
||||||
|
|
||||||
return new DownloadResponse($this->request, $ownerid, $filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @NoCSRFRequired
|
|
||||||
* @PublicPage
|
|
||||||
* Given an access token and a fileId, replaces the files with the request body.
|
|
||||||
* Expects a valid token in access_token parameter.
|
|
||||||
*/
|
|
||||||
public function wopiPutFile($fileId){
|
|
||||||
$token = $this->request->getParam('access_token');
|
|
||||||
|
|
||||||
$arr = explode('_', $fileId, 2);
|
|
||||||
$version = '0';
|
|
||||||
if (count($arr) == 2) {
|
|
||||||
$fileId = $arr[0];
|
|
||||||
$version = $arr[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
\OC::$server->getLogger()->debug('Putting contents of file {fileId}, version {version} by token {token}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version, 'token' => $token ]);
|
|
||||||
|
|
||||||
$row = new Db\Wopi();
|
|
||||||
$row->loadBy('token', $token);
|
|
||||||
|
|
||||||
$res = $row->getPathForToken($fileId, $version, $token);
|
|
||||||
if (!$res['canwrite']) {
|
|
||||||
return array(
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => 'Permission denied'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log-in as the user to regiser the change under her name.
|
|
||||||
$editorid = $res['editor'];
|
|
||||||
// This call is made from loolwsd, so we need to initialize the
|
|
||||||
// session before we can make the user who opened the document
|
|
||||||
// login. This is necessary to make activity app register the
|
|
||||||
// change made to this file under this user's (editorid) name.
|
|
||||||
$this->loginUser($editorid);
|
|
||||||
|
|
||||||
// Set up the filesystem view for the owner (where the file actually is).
|
|
||||||
$userid = $res['owner'];
|
|
||||||
$root = '/' . $userid . '/files';
|
|
||||||
$view = new \OC\Files\View($root);
|
|
||||||
|
|
||||||
// Read the contents of the file from the POST body and store.
|
|
||||||
$content = fopen('php://input', 'r');
|
|
||||||
\OC::$server->getLogger()->debug('Storing file {fileId} by {editor} owned by {owner}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'editor' => $editorid, 'owner' => $userid ]);
|
|
||||||
|
|
||||||
// Setup the FS which is needed to emit hooks (versioning).
|
|
||||||
\OC_Util::tearDownFS();
|
|
||||||
\OC_Util::setupFS($userid);
|
|
||||||
|
|
||||||
$view->file_put_contents($res['path'], $content);
|
|
||||||
|
|
||||||
$this->logoutUser();
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'status' => 'success'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @PublicPage
|
|
||||||
* Process partial/complete file download
|
|
||||||
*/
|
|
||||||
public function serve($esId){
|
|
||||||
$session = new Db\Session();
|
|
||||||
$session->load($esId);
|
|
||||||
|
|
||||||
$filename = $session->getGenesisUrl() ? $session->getGenesisUrl() : '';
|
|
||||||
return new DownloadResponse($this->request, $session->getOwner(), $filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*/
|
|
||||||
public function download($path){
|
|
||||||
if (!$path){
|
|
||||||
$response = new JSONResponse();
|
|
||||||
$response->setStatus(Http::STATUS_BAD_REQUEST);
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fullPath = '/files' . $path;
|
|
||||||
$fileInfo = \OC\Files\Filesystem::getFileInfo($path);
|
|
||||||
if ($fileInfo){
|
|
||||||
$file = new File($fileInfo->getId());
|
|
||||||
$genesis = new Genesis($file);
|
|
||||||
$fullPath = $genesis->getPath();
|
|
||||||
}
|
|
||||||
return new DownloadResponse($this->request, $this->uid, $fullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*/
|
|
||||||
public function rename($fileId){
|
|
||||||
$name = $this->request->post['name'];
|
|
||||||
|
|
||||||
$view = \OC\Files\Filesystem::getView();
|
|
||||||
$path = $view->getPath($fileId);
|
|
||||||
|
|
||||||
if ($name && $view->is_file($path) && $view->isUpdatable($path)) {
|
|
||||||
$newPath = dirname($path) . '/' . $name;
|
|
||||||
if ($view->rename($path, $newPath)) {
|
|
||||||
return array('status' => 'success');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return array(
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => (string) $this->l10n->t('You don\'t have permission to rename this document')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* Get file information about single document with fileId
|
|
||||||
*/
|
|
||||||
public function get($fileId){
|
|
||||||
$documents = array();
|
|
||||||
$documents[0] = Storage::getDocumentById($fileId);
|
|
||||||
|
|
||||||
return $this->prepareDocuments($documents);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* lists the documents the user has access to (including shared files, once the code in core has been fixed)
|
|
||||||
* also adds session and member info for these files
|
|
||||||
*/
|
|
||||||
public function listAll(){
|
|
||||||
return $this->prepareDocuments(Storage::getDocuments());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,314 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2014 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This file is licensed under the Affero General Public License version 3 or
|
|
||||||
* later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments\Controller;
|
|
||||||
|
|
||||||
use \OCP\AppFramework\Controller;
|
|
||||||
use \OCP\IRequest;
|
|
||||||
use \OCP\AppFramework\Http;
|
|
||||||
use \OCP\AppFramework\Http\JSONResponse;
|
|
||||||
|
|
||||||
use \OCA\Richdocuments\Db;
|
|
||||||
use \OCA\Richdocuments\File;
|
|
||||||
use \OCA\Richdocuments\Helper;
|
|
||||||
use OCA\Richdocuments\Filter;
|
|
||||||
use \OC\Files\View;
|
|
||||||
|
|
||||||
class BadRequestException extends \Exception {
|
|
||||||
|
|
||||||
protected $body = "";
|
|
||||||
|
|
||||||
public function setBody($body){
|
|
||||||
$this->body = $body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBody(){
|
|
||||||
return $this->body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SessionController extends Controller{
|
|
||||||
|
|
||||||
protected $uid;
|
|
||||||
protected $logger;
|
|
||||||
protected $shareToken;
|
|
||||||
|
|
||||||
public function __construct($appName, IRequest $request, $logger, $uid){
|
|
||||||
parent::__construct($appName, $request);
|
|
||||||
$this->uid = $uid;
|
|
||||||
$this->logger = $logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @PublicPage
|
|
||||||
*/
|
|
||||||
public function joinAsGuest($token, $name){
|
|
||||||
$uid = substr($name, 0, 16);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$file = File::getByShareToken($token);
|
|
||||||
if ($file->isPasswordProtected() && !$file->checkPassword('')){
|
|
||||||
throw new \Exception('Not authorized');
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = array_merge(
|
|
||||||
Db\Session::start($uid, $file),
|
|
||||||
[ 'status'=>'success' ]
|
|
||||||
);
|
|
||||||
} catch (\Exception $e){
|
|
||||||
$this->logger->warning('Starting a session failed. Reason: ' . $e->getMessage(), ['app' => $this->appName]);
|
|
||||||
$response = [ 'status'=>'error' ];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @PublicPage
|
|
||||||
*/
|
|
||||||
public function pollAsGuest($command, $args){
|
|
||||||
$this->shareToken = $this->request->getParam('token');
|
|
||||||
return $this->poll($command, $args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the document content to its origin
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @PublicPage
|
|
||||||
*/
|
|
||||||
public function saveAsGuest(){
|
|
||||||
$this->shareToken = $this->request->getParam('token');
|
|
||||||
return $this->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*/
|
|
||||||
public function join($fileId){
|
|
||||||
try {
|
|
||||||
$view = \OC\Files\Filesystem::getView();
|
|
||||||
$path = $view->getPath($fileId);
|
|
||||||
|
|
||||||
$file = new File($fileId);
|
|
||||||
$response = Db\Session::start($this->uid, $file);
|
|
||||||
|
|
||||||
$response = array_merge(
|
|
||||||
$response,
|
|
||||||
[ 'status'=>'success' ]
|
|
||||||
);
|
|
||||||
} catch (\Exception $e){
|
|
||||||
$this->logger->warning('Starting a session failed. Reason: ' . $e->getMessage(), [ 'app' => $this->appName ]);
|
|
||||||
$response = [ 'status'=>'error' ];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*/
|
|
||||||
public function poll($command, $args){
|
|
||||||
$response = new JSONResponse();
|
|
||||||
|
|
||||||
try{
|
|
||||||
$esId = isset($args['es_id']) ? $args['es_id'] : null;
|
|
||||||
$session = $this->loadSession($esId);
|
|
||||||
|
|
||||||
$memberId = isset($args['member_id']) ? $args['member_id'] : null;
|
|
||||||
$member = $this->loadMember($memberId);
|
|
||||||
|
|
||||||
$this->validateSession($session);
|
|
||||||
|
|
||||||
switch ($command){
|
|
||||||
case 'sync_ops':
|
|
||||||
$seqHead = (string) isset($args['seq_head']) ? $args['seq_head'] : null;
|
|
||||||
if (!is_null($seqHead)){
|
|
||||||
$ops = isset($args['client_ops']) ? $args['client_ops'] : [];
|
|
||||||
|
|
||||||
$op = new Db\Op();
|
|
||||||
$currentHead = $op->getHeadSeq($esId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$member->updateActivity($memberId);
|
|
||||||
} catch (\Exception $e){
|
|
||||||
//Db error. Not critical
|
|
||||||
}
|
|
||||||
$response->setData(
|
|
||||||
$session->syncOps($memberId, $currentHead, $seqHead, $ops)
|
|
||||||
);
|
|
||||||
|
|
||||||
$inactiveMembers = $member->updateByTimeout($esId);
|
|
||||||
foreach ($inactiveMembers as $inactive){
|
|
||||||
$op->removeCursor($esId, $inactive);
|
|
||||||
$op->removeMember($esId, $inactive);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Error - no seq_head passed
|
|
||||||
throw new BadRequestException();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$ex = new BadRequestException();
|
|
||||||
$ex->setBody(
|
|
||||||
implode(',', $this->request->getParams())
|
|
||||||
);
|
|
||||||
throw $ex;
|
|
||||||
}
|
|
||||||
} catch (BadRequestException $e){
|
|
||||||
$response->setStatus(Http::STATUS_BAD_REQUEST);
|
|
||||||
$response->setData(
|
|
||||||
[ 'err' => 'bad request:[' . $e->getBody() . ']' ]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the document content to its origin
|
|
||||||
* @NoAdminRequired
|
|
||||||
*/
|
|
||||||
public function save(){
|
|
||||||
$response = new JSONResponse();
|
|
||||||
try {
|
|
||||||
$esId = $this->request->server['HTTP_WEBODF_SESSION_ID'];
|
|
||||||
$session = $this->loadSession($esId);
|
|
||||||
|
|
||||||
$memberId = $this->request->server['HTTP_WEBODF_MEMBER_ID'];
|
|
||||||
$currentMember = $this->loadMember($memberId, $esId);
|
|
||||||
|
|
||||||
// Extra info for future usage
|
|
||||||
// $sessionRevision = $this->request->server['HTTP_WEBODF_SESSION_REVISION'];
|
|
||||||
|
|
||||||
//NB ouch! New document content is passed as an input stream content
|
|
||||||
$stream = fopen('php://input','r');
|
|
||||||
if (!$stream){
|
|
||||||
throw new \Exception('New content missing');
|
|
||||||
}
|
|
||||||
$content = stream_get_contents($stream);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($currentMember->getIsGuest()){
|
|
||||||
$file = File::getByShareToken($currentMember->getToken());
|
|
||||||
} else {
|
|
||||||
$file = new File($session->getFileId());
|
|
||||||
}
|
|
||||||
|
|
||||||
$view = $file->getOwnerView(true);
|
|
||||||
$path = $file->getPath(true);
|
|
||||||
} catch (\Exception $e){
|
|
||||||
//File was deleted or unshared. We need to save content as new file anyway
|
|
||||||
//Sorry, but for guests it would be lost :(
|
|
||||||
if ($this->uid){
|
|
||||||
$view = new View('/' . $this->uid . '/files');
|
|
||||||
|
|
||||||
$dir = '/';
|
|
||||||
$path = Helper::getNewFileName($view, $dir . 'New Document.odt');
|
|
||||||
} else {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$member = new Db\Member();
|
|
||||||
$members = $member->getActiveCollection($esId);
|
|
||||||
$memberIds = array_map(
|
|
||||||
function($x){
|
|
||||||
return ($x['member_id']);
|
|
||||||
},
|
|
||||||
$members
|
|
||||||
);
|
|
||||||
|
|
||||||
// Active users except current user
|
|
||||||
$memberCount = count($memberIds) - 1;
|
|
||||||
|
|
||||||
if ($view->file_exists($path)){
|
|
||||||
$currentHash = $view->hash('sha1', $path, false);
|
|
||||||
|
|
||||||
if (!Helper::isVersionsEnabled() && $currentHash !== $session->getGenesisHash()){
|
|
||||||
// Original file was modified externally. Save to a new one
|
|
||||||
$path = Helper::getNewFileName($view, $path, '-conflict');
|
|
||||||
}
|
|
||||||
|
|
||||||
$mimetype = $view->getMimeType($path);
|
|
||||||
} else {
|
|
||||||
$mimetype = Storage::MIMETYPE_LIBREOFFICE_WORDPROCESSOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = Filter::write($content, $mimetype);
|
|
||||||
|
|
||||||
if ($view->file_put_contents($path, $data['content'])){
|
|
||||||
// Not a last user
|
|
||||||
if ($memberCount>0){
|
|
||||||
// Update genesis hash to prevent conflicts
|
|
||||||
$this->logger->debug('Update hash', [ 'app' => $this->appName ]);
|
|
||||||
$session->updateGenesisHash($esId, sha1($data['content']));
|
|
||||||
} else {
|
|
||||||
// Last user. Kill session data
|
|
||||||
Db\Session::cleanUp($esId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$view->touch($path);
|
|
||||||
}
|
|
||||||
$response->setData(['status'=>'success']);
|
|
||||||
} catch (\Exception $e){
|
|
||||||
$response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
|
|
||||||
$response->setData([]);
|
|
||||||
$this->logger->warning('Saving failed. Reason:' . $e->getMessage(), [ 'app' => $this->appName ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function validateSession($session){
|
|
||||||
try {
|
|
||||||
if (is_null($this->shareToken)) {
|
|
||||||
new File($session->getFileId());
|
|
||||||
} else {
|
|
||||||
File::getByShareToken($this->shareToken);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e){
|
|
||||||
$this->logger->warning('Error. Session no longer exists. ' . $e->getMessage(), [ 'app' => $this->appName ]);
|
|
||||||
$ex = new BadRequestException();
|
|
||||||
$ex->setBody(
|
|
||||||
implode(',', $this->request->getParams())
|
|
||||||
);
|
|
||||||
throw $ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function loadSession($esId){
|
|
||||||
if (!$esId){
|
|
||||||
throw new \Exception('Session id can not be empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
$session = new Db\Session();
|
|
||||||
$session->load($esId);
|
|
||||||
if (!$session->getEsId()){
|
|
||||||
throw new \Exception('Session does not exist');
|
|
||||||
}
|
|
||||||
return $session;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function loadMember($memberId, $expectedEsId = null){
|
|
||||||
if (!$memberId){
|
|
||||||
throw new \Exception('Member id can not be empty');
|
|
||||||
}
|
|
||||||
$member = new Db\Member();
|
|
||||||
$member->load($memberId);
|
|
||||||
//check if member belongs to the session
|
|
||||||
if (!is_null($expectedEsId) && $expectedEsId !== $member->getEsId()){
|
|
||||||
throw new \Exception($memberId . ' does not belong to session ' . $expectedEsId);
|
|
||||||
}
|
|
||||||
return $member;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2014 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This file is licensed under the Affero General Public License version 3 or
|
|
||||||
* later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments\Controller;
|
|
||||||
|
|
||||||
use \OCP\AppFramework\Controller;
|
|
||||||
use \OCP\IRequest;
|
|
||||||
use \OCP\AppFramework\Http\JSONResponse;
|
|
||||||
|
|
||||||
use \OCA\Richdocuments\Db;
|
|
||||||
|
|
||||||
class UserController extends Controller {
|
|
||||||
|
|
||||||
public function __construct($appName, IRequest $request){
|
|
||||||
parent::__construct($appName, $request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*/
|
|
||||||
public function disconnectUser($memberId, $esId){
|
|
||||||
return $this->disconnect($memberId, $esId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @PublicPage
|
|
||||||
*/
|
|
||||||
public function disconnectGuest($memberId, $esId){
|
|
||||||
return $this->disconnect($memberId, $esId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function disconnect($memberId, $esId){
|
|
||||||
$member = new Db\Member();
|
|
||||||
$member->loadBy('member_id', $memberId);
|
|
||||||
if ($esId && $member->hasData()){
|
|
||||||
if ($member->getEsId() === $esId && $member->getStatus() == Db\Member::MEMBER_STATUS_ACTIVE){
|
|
||||||
$member->deactivate(array($memberId));
|
|
||||||
$op = new Db\Op();
|
|
||||||
$op->removeMember($esId, $memberId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array('status'=>'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @PublicPage
|
|
||||||
* @param int $memberId
|
|
||||||
* @param string $name
|
|
||||||
*/
|
|
||||||
public function rename($memberId, $name){
|
|
||||||
$member = new Db\Member();
|
|
||||||
$member->load($memberId);
|
|
||||||
|
|
||||||
if ($member->getEsId()
|
|
||||||
&& $member->getStatus() == Db\Member::MEMBER_STATUS_ACTIVE
|
|
||||||
&& $member->getIsGuest()
|
|
||||||
){
|
|
||||||
$guestMark = Db\Member::getGuestPostfix();
|
|
||||||
if (substr($name, -strlen($guestMark)) !== $guestMark){
|
|
||||||
$name = $name . ' ' . $guestMark;
|
|
||||||
}
|
|
||||||
|
|
||||||
$op = new Db\Op();
|
|
||||||
$op->changeNick($member->getEsId(), $memberId, $name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array('status'=>'success');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,170 +0,0 @@
|
|||||||
/* Copyright (c) 2011, Jan-Christoph Borchardt, http://jancborchardt.net
|
|
||||||
This file is licensed under the Affero General Public License version 3 or later.
|
|
||||||
See the COPYING-README file. */
|
|
||||||
|
|
||||||
#dropdown {
|
|
||||||
background: #eee;
|
|
||||||
border-bottom-left-radius: 3px;
|
|
||||||
border-bottom-right-radius: 3px;
|
|
||||||
box-shadow: 0 2px 3px rgba(50, 50, 50, .4);
|
|
||||||
display: block;
|
|
||||||
margin-right: 0;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
width: 420px;
|
|
||||||
z-index: 500;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 768px) and (max-width: 990px) {
|
|
||||||
#dropdown {
|
|
||||||
/* this limits the dropdown to float below the sidebar for mid narrow screens */
|
|
||||||
left: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#dropdown.shareDropDown .unshare.icon-loading-small {
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dropdown.shareDropDown .shareWithLoading,
|
|
||||||
#dropdown.shareDropDown .linkShare .icon-loading-small {
|
|
||||||
display: inline-block !important;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
#dropdown.shareDropDown .shareWithLoading {
|
|
||||||
position: relative;
|
|
||||||
right: 70px;
|
|
||||||
top: 2px;
|
|
||||||
}
|
|
||||||
#dropdown.shareDropDown .icon-loading-small.hidden {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dropdown .shareWithRemoteInfo {
|
|
||||||
padding: 11px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dropdown .avatar {
|
|
||||||
margin-right: 8px;
|
|
||||||
display: inline-block;
|
|
||||||
overflow: hidden;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#shareWithList {
|
|
||||||
list-style-type:none;
|
|
||||||
padding:8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#shareWithList li {
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 21px;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
#shareWithList .unshare img, #shareWithList .showCruds img {
|
|
||||||
vertical-align:text-bottom; /* properly align icons */
|
|
||||||
}
|
|
||||||
|
|
||||||
#shareWithList label input[type=checkbox]{
|
|
||||||
margin-left: 0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
#shareWithList .username{
|
|
||||||
padding-right: 8px;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 254px;
|
|
||||||
display: inline-block;
|
|
||||||
overflow: hidden;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
#shareWithList li label{
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
#dropdown label {
|
|
||||||
font-weight:400;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dropdown input[type="checkbox"] {
|
|
||||||
margin:0 3px 0 8px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.showCruds {
|
|
||||||
display:inline;
|
|
||||||
opacity:.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.unshare {
|
|
||||||
display:inline;
|
|
||||||
float:right;
|
|
||||||
opacity:.5;
|
|
||||||
padding:5px 0 0 5px !important;
|
|
||||||
margin-top:-5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#link {
|
|
||||||
border-top:1px solid #ddd;
|
|
||||||
padding-top:8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dropdown input[type="text"],#dropdown input[type="password"] {
|
|
||||||
width: 86%;
|
|
||||||
margin-left: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dropdown form {
|
|
||||||
font-size: 100%;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#linkText,#linkPass,#expiration {
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#link #showPassword img {
|
|
||||||
padding-left:5px;
|
|
||||||
width:12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reshare,#link label,
|
|
||||||
#expiration label {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 6px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.showCruds:hover,a.unshare:hover {
|
|
||||||
opacity:1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#defaultExpireMessage, /* fix expire message going out of box */
|
|
||||||
.reshare { /* fix shared by text going out of box */
|
|
||||||
white-space:normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
#defaultExpireMessage { /* show message on new line */
|
|
||||||
display: block;
|
|
||||||
padding-left: 4px;
|
|
||||||
/* TODO: style the dropdown in a proper way - border-box, etc. */
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-autocomplete { /* limit dropdown height to 4 1/2 entries */
|
|
||||||
max-height:103px;
|
|
||||||
overflow-y:auto;
|
|
||||||
overflow-x:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notCreatable {
|
|
||||||
padding-left: 12px;
|
|
||||||
padding-top: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,166 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ownCloud - Richdocuments App
|
||||||
|
*
|
||||||
|
* @author Victor Dubiniuk
|
||||||
|
* @copyright 2014 Victor Dubiniuk victor.dubiniuk@gmail.com
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Richdocuments\Controller;
|
||||||
|
|
||||||
|
use OCA\Richdocuments\WOPI\DiscoveryManager;
|
||||||
|
use \OCP\AppFramework\Controller;
|
||||||
|
use \OCP\IRequest;
|
||||||
|
use \OCP\IConfig;
|
||||||
|
use \OCP\IL10N;
|
||||||
|
use \OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
|
use \OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
use \OCA\Richdocuments\AppConfig;
|
||||||
|
use \OCA\Richdocuments\Helper;
|
||||||
|
use \OC\Files\View;
|
||||||
|
use \OCP\ICacheFactory;
|
||||||
|
|
||||||
|
class DocumentController extends Controller {
|
||||||
|
|
||||||
|
private $uid;
|
||||||
|
private $l10n;
|
||||||
|
private $settings;
|
||||||
|
private $appConfig;
|
||||||
|
private $cache;
|
||||||
|
/** @var DiscoveryManager */
|
||||||
|
private $discoveryManager;
|
||||||
|
const ODT_TEMPLATE_PATH = '/assets/odttemplate.odt';
|
||||||
|
|
||||||
|
public function __construct($appName,
|
||||||
|
$UserId,
|
||||||
|
IRequest $request,
|
||||||
|
IConfig $settings,
|
||||||
|
AppConfig $appConfig,
|
||||||
|
IL10N $l10n,
|
||||||
|
ICacheFactory $cache,
|
||||||
|
DiscoveryManager $discoveryManager) {
|
||||||
|
parent::__construct($appName, $request);
|
||||||
|
$this->uid = $UserId;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
$this->settings = $settings;
|
||||||
|
$this->appConfig = $appConfig;
|
||||||
|
$this->cache = $cache->create($appName);
|
||||||
|
$this->discoveryManager = $discoveryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return TemplateResponse
|
||||||
|
*/
|
||||||
|
public function index(){
|
||||||
|
$response = new TemplateResponse('richdocuments', 'documents');
|
||||||
|
$policy = new ContentSecurityPolicy();
|
||||||
|
$policy->addAllowedFrameDomain($this->appConfig->getAppValue('wopi_url'));
|
||||||
|
$response->setContentSecurityPolicy($policy);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function create(){
|
||||||
|
$mimetype = $this->request->post['mimetype'];
|
||||||
|
$filename = $this->request->post['filename'];
|
||||||
|
$dir = $this->request->post['dir'];
|
||||||
|
|
||||||
|
$view = new View('/' . $this->uid . '/files');
|
||||||
|
if (!$dir){
|
||||||
|
$dir = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
$basename = $this->l10n->t('New Document.odt');
|
||||||
|
switch ($mimetype) {
|
||||||
|
case 'application/vnd.oasis.opendocument.spreadsheet':
|
||||||
|
$basename = $this->l10n->t('New Spreadsheet.ods');
|
||||||
|
break;
|
||||||
|
case 'application/vnd.oasis.opendocument.presentation':
|
||||||
|
$basename = $this->l10n->t('New Presentation.odp');
|
||||||
|
break;
|
||||||
|
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||||
|
$basename = $this->l10n->t('New Document.docx');
|
||||||
|
break;
|
||||||
|
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
||||||
|
$basename = $this->l10n->t('New Spreadsheet.xlsx');
|
||||||
|
break;
|
||||||
|
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
||||||
|
$basename = $this->l10n->t('New Presentation.pptx');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// to be safe
|
||||||
|
$mimetype = 'application/vnd.oasis.opendocument.text';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$filename){
|
||||||
|
$path = Helper::getNewFileName($view, $dir . '/' . $basename);
|
||||||
|
} else {
|
||||||
|
$path = $dir . '/' . $filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = '';
|
||||||
|
if (class_exists('\OC\Files\Type\TemplateManager')){
|
||||||
|
$manager = \OC_Helper::getFileTemplateManager();
|
||||||
|
$content = $manager->getTemplate($mimetype);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$content){
|
||||||
|
$content = file_get_contents(dirname(__DIR__) . self::ODT_TEMPLATE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
$discovery_parsed = null;
|
||||||
|
try {
|
||||||
|
$discovery = $this->discoveryManager->get();
|
||||||
|
|
||||||
|
$loadEntities = libxml_disable_entity_loader(true);
|
||||||
|
$discovery_parsed = simplexml_load_string($discovery);
|
||||||
|
libxml_disable_entity_loader($loadEntities);
|
||||||
|
|
||||||
|
if ($discovery_parsed === false) {
|
||||||
|
$this->cache->remove('discovery.xml');
|
||||||
|
$wopiRemote = $this->getWopiUrl(false);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => $this->l10n->t('Collabora Online: discovery.xml from "%s" is not a well-formed XML string.', array($wopiRemote)),
|
||||||
|
'hint' => $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ResponseException $e) {
|
||||||
|
return array(
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'hint' => $e->getHint()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($content && $view->file_put_contents($path, $content)){
|
||||||
|
$info = $view->getFileInfo($path);
|
||||||
|
$ret = $this->getWopiSrcUrl($discovery_parsed, $mimetype);
|
||||||
|
$response = array(
|
||||||
|
'status' => 'success',
|
||||||
|
'fileid' => $info['fileid'],
|
||||||
|
'urlsrc' => $ret['urlsrc'],
|
||||||
|
'action' => $ret['action'],
|
||||||
|
'lolang' => $this->settings->getUserValue($this->uid, 'core', 'lang', 'en'),
|
||||||
|
'data' => \OCA\Files\Helper::formatFileInfo($info)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$response = array(
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => (string) $this->l10n->t('Can\'t create document')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,245 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Richdocuments\Controller;
|
||||||
|
|
||||||
|
use OCA\Richdocuments\Db\Wopi;
|
||||||
|
use OCA\Richdocuments\WOPI\Parser;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\Files\File;
|
||||||
|
use OCP\Files\IRootFolder;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
use OCP\AppFramework\Http\StreamResponse;
|
||||||
|
|
||||||
|
class WopiController extends Controller {
|
||||||
|
/** @var IRootFolder */
|
||||||
|
private $rootFolder;
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
/** @var IUserManager */
|
||||||
|
private $userManager;
|
||||||
|
/** @var Parser */
|
||||||
|
private $wopiParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $appName
|
||||||
|
* @param IRequest $request
|
||||||
|
* @param IRootFolder $rootFolder
|
||||||
|
* @param string $UserId
|
||||||
|
* @param IUserManager $userManager
|
||||||
|
* @param Parser $wopiParser
|
||||||
|
*/
|
||||||
|
public function __construct($appName,
|
||||||
|
$UserId,
|
||||||
|
IRequest $request,
|
||||||
|
IRootFolder $rootFolder,
|
||||||
|
IUserManager $userManager,
|
||||||
|
Parser $wopiParser) {
|
||||||
|
parent::__construct($appName, $request);
|
||||||
|
$this->rootFolder = $rootFolder;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->wopiParser = $wopiParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates and returns an access token for a given fileId
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @param string $fileId
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getToken($fileId) {
|
||||||
|
$arr = explode('_', $fileId, 2);
|
||||||
|
$version = '0';
|
||||||
|
if (count($arr) === 2) {
|
||||||
|
list($fileId, $version) = $arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var File $file */
|
||||||
|
$file = $this->rootFolder->getUserFolder($this->userId)->getById($fileId)[0];
|
||||||
|
$updatable = $file->isUpdateable();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If token is for some versioned file
|
||||||
|
if ($version !== '0') {
|
||||||
|
$updatable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = new Wopi();
|
||||||
|
$serverHost = $this->request->getServerProtocol() . '://' . $this->request->getServerHost();
|
||||||
|
$token = $row->generateFileToken($fileId, $version, $updatable, $serverHost);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||||
|
/** @var File $file */
|
||||||
|
$file = $userFolder->getById($fileId)[0];
|
||||||
|
$sessionData['title'] = basename($file->getPath());
|
||||||
|
$sessionData['permissions'] = $file->getPermissions();
|
||||||
|
$sessionData['file_id'] = $file->getId();
|
||||||
|
|
||||||
|
$sessionData['documents'] = [
|
||||||
|
0 => [
|
||||||
|
'urlsrc' => $this->wopiParser->getUrlSrc($file->getMimeType())['urlsrc'],
|
||||||
|
'path' => $file->getPath(),
|
||||||
|
'token' => $token,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return new JSONResponse($sessionData);
|
||||||
|
} catch (\Exception $e){
|
||||||
|
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns general info about a file.
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @PublicPage
|
||||||
|
*
|
||||||
|
* @param string $fileId
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function checkFileInfo($fileId) {
|
||||||
|
$token = $this->request->getParam('access_token');
|
||||||
|
|
||||||
|
$arr = explode('_', $fileId, 2);
|
||||||
|
$version = '0';
|
||||||
|
if (count($arr) === 2) {
|
||||||
|
list($fileId, $version) = $arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = new Wopi();
|
||||||
|
$row->loadBy('token', $token);
|
||||||
|
|
||||||
|
$res = $row->getPathForToken($fileId, $version, $token);
|
||||||
|
if ($res === false) {
|
||||||
|
return new JSONResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login the user to see his mount locations
|
||||||
|
try {
|
||||||
|
/** @var File $file */
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($res['editor']);
|
||||||
|
$file = $userFolder->getById($fileId)[0];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse(
|
||||||
|
[
|
||||||
|
'BaseFileName' => $file->getName(),
|
||||||
|
'Size' => $file->getSize(),
|
||||||
|
'Version' => $version,
|
||||||
|
'UserId' => $res['editor'],
|
||||||
|
'UserFriendlyName' => $this->userManager->get($res['editor'])->getDisplayName(),
|
||||||
|
'UserCanWrite' => $res['canwrite'] ? true : false,
|
||||||
|
'PostMessageOrigin' => $res['server_host'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an access token and a fileId, returns the contents of the file.
|
||||||
|
* Expects a valid token in access_token parameter.
|
||||||
|
*
|
||||||
|
* @PublicPage
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param string $fileId
|
||||||
|
* @param string $access_token
|
||||||
|
* @return Http\Response
|
||||||
|
*/
|
||||||
|
public function getFile($fileId,
|
||||||
|
$access_token) {
|
||||||
|
$arr = explode('_', $fileId, 2);
|
||||||
|
$version = '0';
|
||||||
|
if (count($arr) === 2) {
|
||||||
|
list($fileId, $version) = $arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = new Wopi();
|
||||||
|
$row->loadBy('token', $access_token);
|
||||||
|
|
||||||
|
$res = $row->getPathForToken($fileId, $version, $access_token);
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var File $file */
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($res['editor']);
|
||||||
|
$file = $userFolder->getById($fileId)[0];
|
||||||
|
$response = new StreamResponse($file->fopen('rb'));
|
||||||
|
$response->addHeader('Content-Disposition', 'attachment');
|
||||||
|
$response->addHeader('Content-Type', 'application/octet-stream');
|
||||||
|
return $response;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an access token and a fileId, replaces the files with the request body.
|
||||||
|
* Expects a valid token in access_token parameter.
|
||||||
|
*
|
||||||
|
* @PublicPage
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param string $fileId
|
||||||
|
* @param string $access_token
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function putFile($fileId, $access_token) {
|
||||||
|
$arr = explode('_', $fileId, 2);
|
||||||
|
$version = '0';
|
||||||
|
if (count($arr) === 2) {
|
||||||
|
list($fileId, $version) = $arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = new Wopi();
|
||||||
|
$row->loadBy('token', $access_token);
|
||||||
|
|
||||||
|
$res = $row->getPathForToken($fileId, $version, $access_token);
|
||||||
|
if (!$res['canwrite']) {
|
||||||
|
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var File $file */
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($res['editor']);
|
||||||
|
$file = $userFolder->getById($fileId)[0];
|
||||||
|
$content = fopen('php://input', 'rb');
|
||||||
|
$file->putContent($content);
|
||||||
|
return new JSONResponse();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Richdocuments\WOPI;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Utility\ITimeFactory;
|
||||||
|
use OCP\Files\IAppData;
|
||||||
|
use OCP\Files\NotFoundException;
|
||||||
|
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||||
|
use OCP\Http\Client\IClientService;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IL10N;
|
||||||
|
use OCP\Notification\IApp;
|
||||||
|
|
||||||
|
class DiscoveryManager {
|
||||||
|
/** @var IClientService */
|
||||||
|
private $clientService;
|
||||||
|
/** @var ISimpleFolder */
|
||||||
|
private $appData;
|
||||||
|
/** @var IConfig */
|
||||||
|
private $config;
|
||||||
|
/** @var IL10N */
|
||||||
|
private $l10n;
|
||||||
|
/** @var ITimeFactory */
|
||||||
|
private $timeFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IClientService $clientService
|
||||||
|
* @param IAppData $appData
|
||||||
|
* @param IConfig $config
|
||||||
|
* @param IL10N $l10n
|
||||||
|
* @param ITimeFactory $timeFactory
|
||||||
|
*/
|
||||||
|
public function __construct(IClientService $clientService,
|
||||||
|
IAppData $appData,
|
||||||
|
IConfig $config,
|
||||||
|
IL10N $l10n,
|
||||||
|
ITimeFactory $timeFactory) {
|
||||||
|
$this->clientService = $clientService;
|
||||||
|
try {
|
||||||
|
$this->appData = $appData->getFolder('richdocuments');
|
||||||
|
} catch (NotFoundException $e) {
|
||||||
|
$this->appData = $appData->newFolder('richdocuments');
|
||||||
|
}
|
||||||
|
$this->config = $config;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
$this->timeFactory = $timeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get() {
|
||||||
|
// First check if there is a local valid discovery file
|
||||||
|
try {
|
||||||
|
$file = $this->appData->getFile('discovery.xml');
|
||||||
|
$decodedFile = json_decode($file->getContent(), true);
|
||||||
|
if($decodedFile['timestamp'] + 3600 > $this->timeFactory->getTime()) {
|
||||||
|
return $decodedFile['data'];
|
||||||
|
}
|
||||||
|
} catch (NotFoundException $e) {
|
||||||
|
$file = $this->appData->newFile('discovery.xml');
|
||||||
|
}
|
||||||
|
|
||||||
|
$remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url');
|
||||||
|
$wopiDiscovery = $remoteHost . '/hosting/discovery';
|
||||||
|
|
||||||
|
$client = $this->clientService->newClient();
|
||||||
|
try {
|
||||||
|
$response = $client->get($wopiDiscovery);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$error_message = $e->getMessage();
|
||||||
|
if (preg_match('/^cURL error ([0-9]*):/', $error_message, $matches)) {
|
||||||
|
$admin_check = $this->l10n->t('Please ask your administrator to check the Collabora Online server setting. The exact error message was: ') . $error_message;
|
||||||
|
|
||||||
|
$curl_error = $matches[1];
|
||||||
|
switch ($curl_error) {
|
||||||
|
case '1':
|
||||||
|
throw new ResponseException($this->l10n->t('Collabora Online: The protocol specified in "%s" is not allowed.', array($wopiRemote)), $admin_check);
|
||||||
|
case '3':
|
||||||
|
throw new ResponseException($this->l10n->t('Collabora Online: Malformed URL "%s".', array($wopiRemote)), $admin_check);
|
||||||
|
case '6':
|
||||||
|
throw new ResponseException($this->l10n->t('Collabora Online: Cannot resolve the host "%s".', array($wopiRemote)), $admin_check);
|
||||||
|
case '7':
|
||||||
|
throw new ResponseException($this->l10n->t('Collabora Online: Cannot connect to the host "%s".', array($wopiRemote)), $admin_check);
|
||||||
|
case '60':
|
||||||
|
throw new ResponseException($this->l10n->t('Collabora Online: SSL certificate is not installed.'), $this->l10n->t('Please ask your administrator to add ca-chain.cert.pem to the ca-bundle.crt, for example "cat /etc/loolwsd/ca-chain.cert.pem >> <server-installation>/resources/config/ca-bundle.crt" . The exact error message was: ') . $error_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ResponseException($this->l10n->t('Collabora Online unknown error: ') . $error_message, $contact_admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
$responseBody = $response->getBody();
|
||||||
|
$file->putContent(
|
||||||
|
json_encode([
|
||||||
|
'data' => $responseBody,
|
||||||
|
'timestamp' => $this->timeFactory->getTime(),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
return $responseBody;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Richdocuments\WOPI;
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
/** @var DiscoveryManager */
|
||||||
|
private $discoveryManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DiscoveryManager $discoveryManager
|
||||||
|
*/
|
||||||
|
public function __construct(DiscoveryManager $discoveryManager) {
|
||||||
|
$this->discoveryManager = $discoveryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $mimetype
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function getUrlSrc($mimetype) {
|
||||||
|
$discovery = $this->discoveryManager->get();
|
||||||
|
$loadEntities = libxml_disable_entity_loader(true);
|
||||||
|
$discoveryParsed = simplexml_load_string($discovery);
|
||||||
|
libxml_disable_entity_loader($loadEntities);
|
||||||
|
|
||||||
|
$result = $discoveryParsed->xpath(sprintf('/wopi-discovery/net-zone/app[@name=\'%s\']/action', $mimetype));
|
||||||
|
if ($result && count($result) > 0) {
|
||||||
|
return [
|
||||||
|
'urlsrc' => (string)$result[0]['urlsrc'],
|
||||||
|
'action' => (string)$result[0]['name'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \Exception('Could not find urlsrc in WOPI');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,130 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2013 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This file is licensed under the Affero General Public License version 3 or
|
|
||||||
* later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments\Db;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @method boolean getIsGuest()
|
|
||||||
* @method string getEsId()
|
|
||||||
* @method string getToken()
|
|
||||||
* @method int getStatus()
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Member extends \OCA\Richdocuments\Db{
|
|
||||||
|
|
||||||
const DB_TABLE = '`*PREFIX*richdocuments_member`';
|
|
||||||
|
|
||||||
const ACTIVITY_THRESHOLD = 90; // 1.5 Minutes
|
|
||||||
|
|
||||||
const MEMBER_STATUS_ACTIVE = 1;
|
|
||||||
const MEMBER_STATUS_INACTIVE = 2;
|
|
||||||
|
|
||||||
protected $tableName = '`*PREFIX*richdocuments_member`';
|
|
||||||
|
|
||||||
protected $insertStatement = 'INSERT INTO `*PREFIX*richdocuments_member` (`es_id`, `uid`, `color`, `last_activity`, `is_guest`, `token`)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)';
|
|
||||||
|
|
||||||
protected $loadStatement = 'SELECT * FROM `*PREFIX*richdocuments_member` WHERE `member_id`= ?';
|
|
||||||
|
|
||||||
public static function getGuestPostfix(){
|
|
||||||
return '(' . \OC::$server->getL10n('richdocuments')->t('guest') . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function updateActivity($memberId){
|
|
||||||
return $this->execute(
|
|
||||||
'UPDATE ' . $this->tableName . ' SET `last_activity`=?, `status`=? WHERE `member_id`=?',
|
|
||||||
array(
|
|
||||||
time(),
|
|
||||||
self::MEMBER_STATUS_ACTIVE,
|
|
||||||
$memberId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function getActiveCollection($esId){
|
|
||||||
$result = $this->execute('
|
|
||||||
SELECT `es_id`, `member_id`
|
|
||||||
FROM ' . self::DB_TABLE . '
|
|
||||||
WHERE `es_id`= ?
|
|
||||||
AND `status`=?
|
|
||||||
',
|
|
||||||
array(
|
|
||||||
$esId,
|
|
||||||
self::MEMBER_STATUS_ACTIVE
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$members = $result->fetchAll();
|
|
||||||
if (!is_array($members)){
|
|
||||||
$members = array();
|
|
||||||
}
|
|
||||||
return $members;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark members as inactive
|
|
||||||
* @param string $esId - session Id
|
|
||||||
* @return array - list of memberId that were marked as inactive
|
|
||||||
*/
|
|
||||||
public function updateByTimeout($esId){
|
|
||||||
$time = $this->getInactivityPeriod();
|
|
||||||
|
|
||||||
$result = $this->execute('
|
|
||||||
SELECT `member_id`
|
|
||||||
FROM ' . self::DB_TABLE . '
|
|
||||||
WHERE `es_id`= ?
|
|
||||||
AND `last_activity`<?
|
|
||||||
AND `status`=?
|
|
||||||
',
|
|
||||||
array(
|
|
||||||
$esId,
|
|
||||||
$time,
|
|
||||||
self::MEMBER_STATUS_ACTIVE
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$deactivated = $result->fetchAll();
|
|
||||||
if (is_array($deactivated) && count($deactivated)){
|
|
||||||
$deactivated = array_map(
|
|
||||||
function($x){
|
|
||||||
return ($x['member_id']);
|
|
||||||
}, $deactivated
|
|
||||||
);
|
|
||||||
$this->deactivate($deactivated);
|
|
||||||
} else {
|
|
||||||
$deactivated = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $deactivated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update members to inactive state
|
|
||||||
* @param array $memberIds
|
|
||||||
*/
|
|
||||||
public function deactivate($memberIds){
|
|
||||||
$stmt = $this->buildInQuery('member_id', $memberIds);
|
|
||||||
array_unshift($memberIds, self::MEMBER_STATUS_INACTIVE);
|
|
||||||
$this->execute('
|
|
||||||
UPDATE ' . $this->tableName . '
|
|
||||||
SET `status`=?
|
|
||||||
WHERE ' . $stmt,
|
|
||||||
$memberIds
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getInactivityPeriod(){
|
|
||||||
return time() - self::ACTIVITY_THRESHOLD;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2013 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This file is licensed under the Affero General Public License version 3 or
|
|
||||||
* later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments\Db;
|
|
||||||
|
|
||||||
class Op extends \OCA\Richdocuments\Db {
|
|
||||||
|
|
||||||
const DB_TABLE = '`*PREFIX*richdocuments_op`';
|
|
||||||
|
|
||||||
protected $tableName = '`*PREFIX*richdocuments_op`';
|
|
||||||
|
|
||||||
protected $insertStatement = 'INSERT INTO `*PREFIX*richdocuments_op` (`es_id`, `optype`, `member`, `opspec`) VALUES (?, ?, ?, ?)';
|
|
||||||
|
|
||||||
public static function addOpsArray($esId, $memberId, $ops){
|
|
||||||
$opObj = new Op();
|
|
||||||
foreach ($ops as $op) {
|
|
||||||
if (!$opObj->canInsertOp($esId, $memberId, $op)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$opObj->setData(array(
|
|
||||||
$esId,
|
|
||||||
$op['optype'],
|
|
||||||
$memberId,
|
|
||||||
json_encode($op)
|
|
||||||
));
|
|
||||||
$opObj->insert();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $opObj->getHeadSeq($esId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns "" when there are no Ops, or the seq of the last Op
|
|
||||||
*/
|
|
||||||
public function getHeadSeq($esId){
|
|
||||||
$query = \OC::$server->getDatabaseConnection()->prepare('
|
|
||||||
SELECT `seq`
|
|
||||||
FROM ' . $this->tableName . '
|
|
||||||
WHERE `es_id`=?
|
|
||||||
ORDER BY `seq` DESC
|
|
||||||
', 1);
|
|
||||||
$result = $query->execute([$esId]);
|
|
||||||
return !$result ? "" : $query->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOpsAfterJson($esId, $seq){
|
|
||||||
$ops = $this->getOpsAfter($esId, $seq);
|
|
||||||
if (!is_array($ops)){
|
|
||||||
$ops = array();
|
|
||||||
}
|
|
||||||
$ops = array_map(
|
|
||||||
function($x){
|
|
||||||
$decoded = json_decode($x['opspec'], true);
|
|
||||||
$decoded['memberid'] = strval($decoded['memberid']);
|
|
||||||
return $decoded;
|
|
||||||
},
|
|
||||||
$ops
|
|
||||||
);
|
|
||||||
return $ops;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOpsAfter($esId, $seq){
|
|
||||||
if ($seq == ""){
|
|
||||||
$seq = -1;
|
|
||||||
}
|
|
||||||
$query = \OC::$server->getDatabaseConnection()->prepare('
|
|
||||||
SELECT `opspec`
|
|
||||||
FROM ' . self::DB_TABLE . '
|
|
||||||
WHERE `es_id`=?
|
|
||||||
AND `seq`>?
|
|
||||||
ORDER BY `seq` ASC
|
|
||||||
');
|
|
||||||
$query->execute(array($esId, $seq));
|
|
||||||
return $query->fetchAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addMember($esId, $memberId, $fullName, $userId, $color, $imageUrl){
|
|
||||||
$op = array(
|
|
||||||
'optype' => 'AddMember',
|
|
||||||
'memberid' => (string) $memberId,
|
|
||||||
'timestamp' => $this->getMillisecondsAsString(),
|
|
||||||
'setProperties' => array(
|
|
||||||
'fullName' => $fullName,
|
|
||||||
'color' => $color,
|
|
||||||
'imageUrl' => $imageUrl,
|
|
||||||
'uid' => $userId,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$this->insertOp($esId, $memberId, $op);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeCursor($esId, $memberId){
|
|
||||||
$op = array(
|
|
||||||
'optype' => 'RemoveCursor',
|
|
||||||
'memberid' => (string) $memberId,
|
|
||||||
'reason' => 'server-idle',
|
|
||||||
'timestamp' => $this->getMillisecondsAsString()
|
|
||||||
);
|
|
||||||
$this->insertOp($esId, $memberId, $op);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeMember($esId, $memberId){
|
|
||||||
$op = array(
|
|
||||||
'optype' => 'RemoveMember',
|
|
||||||
'memberid' => (string) $memberId,
|
|
||||||
'timestamp' => $this->getMillisecondsAsString()
|
|
||||||
);
|
|
||||||
$this->insertOp($esId, $memberId, $op);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Implement https://github.com/kogmbh/WebODF/blob/master/webodf/lib/ops/OpUpdateMember.js#L95
|
|
||||||
public function changeNick($esId, $memberId, $fullName){
|
|
||||||
$op = array(
|
|
||||||
'optype' => 'UpdateMember',
|
|
||||||
'memberid' => (string) $memberId,
|
|
||||||
'timestamp' => $this->getMillisecondsAsString(),
|
|
||||||
'setProperties' => array(
|
|
||||||
'fullName' => $fullName,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$this->insertOp($esId, $memberId, $op);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function insertOp($esId, $memberId, $op){
|
|
||||||
if ($this->canInsertOp($esId, $memberId, $op)){
|
|
||||||
$op = new Op(array(
|
|
||||||
$esId,
|
|
||||||
$op['optype'],
|
|
||||||
$memberId,
|
|
||||||
json_encode($op)
|
|
||||||
));
|
|
||||||
$op->insert();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function canInsertOp($esId, $memberId, $op){
|
|
||||||
$cursorOps = array('AddCursor', 'RemoveCursor');
|
|
||||||
$memberOps = array('AddMember', 'RemoveMember');
|
|
||||||
$result = true;
|
|
||||||
|
|
||||||
switch ($op['optype']){
|
|
||||||
case 'AddCursor':
|
|
||||||
$ops = $this->getFilteredMemberOps($esId, $memberId, $cursorOps);
|
|
||||||
$result = !count($ops) || $ops[0]['optype'] === 'RemoveCursor';
|
|
||||||
break;
|
|
||||||
case 'RemoveCursor':
|
|
||||||
$ops = $this->getFilteredMemberOps($esId, $memberId, $cursorOps);
|
|
||||||
$result = count($ops) && $ops[0]['optype'] === 'AddCursor';
|
|
||||||
break;
|
|
||||||
case 'AddMember':
|
|
||||||
$ops = $this->getFilteredMemberOps($esId, $memberId, $memberOps);
|
|
||||||
$result = !count($ops) || $ops[0]['optype'] === 'RemoveMember';
|
|
||||||
break;
|
|
||||||
case 'RemoveMember':
|
|
||||||
$ops = $this->getFilteredMemberOps($esId, $memberId, $memberOps);
|
|
||||||
$result = count($ops) && $ops[0]['optype'] === 'AddMember';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getFilteredMemberOps($esId, $memberId, $targetOps){
|
|
||||||
$stmt = $this->buildInQuery('optype', $targetOps);
|
|
||||||
$result = $this->execute('
|
|
||||||
SELECT `optype` FROM ' . $this->tableName . '
|
|
||||||
WHERE es_id=? AND member=? AND ' . $stmt . 'ORDER BY `seq` DESC',
|
|
||||||
array_merge(array($esId, $memberId), $targetOps)
|
|
||||||
);
|
|
||||||
$ops = $result->fetchAll();
|
|
||||||
if (!is_array($ops)){
|
|
||||||
$ops = array();
|
|
||||||
}
|
|
||||||
return $ops;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getMillisecondsAsString(){
|
|
||||||
$microtime = microtime();
|
|
||||||
list($usec, $sec) = explode(" ", $microtime);
|
|
||||||
$milliseconds = $sec.substr($usec, 2, 3);
|
|
||||||
return $milliseconds;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2013 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This file is licensed under the Affero General Public License version 3 or
|
|
||||||
* later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments\Db;
|
|
||||||
|
|
||||||
use OCP\Security\ISecureRandom;
|
|
||||||
|
|
||||||
use OCA\Richdocuments\Filter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Session management
|
|
||||||
*
|
|
||||||
* @method string getEsId()
|
|
||||||
* @method int getFileId()
|
|
||||||
* @method string getGenesisUrl()
|
|
||||||
* @method string getOwner()
|
|
||||||
* @method string getGenesisHash()
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class Session extends \OCA\Richdocuments\Db {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DB table
|
|
||||||
*/
|
|
||||||
const DB_TABLE = '`*PREFIX*richdocuments_session`';
|
|
||||||
protected $tableName = '`*PREFIX*richdocuments_session`';
|
|
||||||
|
|
||||||
protected $insertStatement = 'INSERT INTO `*PREFIX*richdocuments_session` (`es_id`, `genesis_url`, `genesis_hash`, `owner`, `file_id`)
|
|
||||||
VALUES (?, ?, ?, ?, ?)';
|
|
||||||
|
|
||||||
protected $loadStatement = 'SELECT * FROM `*PREFIX*richdocuments_session` WHERE `es_id`= ?';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a editing session or return an existing one
|
|
||||||
* @param string $uid of the user starting a session
|
|
||||||
* @param \OCA\Richdocuments\File $file - file object
|
|
||||||
* @return array
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public static function start($uid, $file){
|
|
||||||
// Create a directory to store genesis
|
|
||||||
$genesis = new \OCA\Richdocuments\Genesis($file);
|
|
||||||
|
|
||||||
$oldSession = new Session();
|
|
||||||
$oldSession->loadBy('file_id', $file->getFileId());
|
|
||||||
|
|
||||||
//If there is no existing session we need to start a new one
|
|
||||||
if (!$oldSession->hasData()){
|
|
||||||
$newSession = new Session(array(
|
|
||||||
$genesis->getPath(),
|
|
||||||
$genesis->getHash(),
|
|
||||||
$file->getOwner(),
|
|
||||||
$file->getFileId()
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!$newSession->insert()){
|
|
||||||
throw new \Exception('Failed to add session into database');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sessionData = $oldSession
|
|
||||||
->loadBy('file_id', $file->getFileId())
|
|
||||||
->getData()
|
|
||||||
;
|
|
||||||
|
|
||||||
$memberColor = \OCA\Richdocuments\Helper::getMemberColor($uid);
|
|
||||||
$member = new \OCA\Richdocuments\Db\Member([
|
|
||||||
$sessionData['es_id'],
|
|
||||||
$uid,
|
|
||||||
$memberColor,
|
|
||||||
time(),
|
|
||||||
intval($file->isPublicShare()),
|
|
||||||
$file->getToken()
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!$member->insert()){
|
|
||||||
throw new \Exception('Failed to add member into database');
|
|
||||||
}
|
|
||||||
$sessionData['member_id'] = (string) $member->getLastInsertId();
|
|
||||||
|
|
||||||
// Do we have OC_Avatar in out disposal?
|
|
||||||
if (\OC::$server->getConfig()->getSystemValue('enable_avatars', true) !== true){
|
|
||||||
$imageUrl = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==';
|
|
||||||
} else {
|
|
||||||
$imageUrl = $uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
$displayName = $file->isPublicShare()
|
|
||||||
? $uid . ' ' . \OCA\Richdocuments\Db\Member::getGuestPostfix()
|
|
||||||
: \OC::$server->getUserSession()->getUser()->getDisplayName($uid)
|
|
||||||
;
|
|
||||||
$userId = $file->isPublicShare() ? $displayName : \OC::$server->getUserSession()->getUser()->getUID();
|
|
||||||
$op = new \OCA\Richdocuments\Db\Op();
|
|
||||||
$op->addMember(
|
|
||||||
$sessionData['es_id'],
|
|
||||||
$sessionData['member_id'],
|
|
||||||
$displayName,
|
|
||||||
$userId,
|
|
||||||
$memberColor,
|
|
||||||
$imageUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
$sessionData['title'] = basename($file->getPath());
|
|
||||||
$sessionData['permissions'] = $file->getPermissions();
|
|
||||||
|
|
||||||
return $sessionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function cleanUp($esId){
|
|
||||||
$session = new Session();
|
|
||||||
$session->deleteBy('es_id', $esId);
|
|
||||||
|
|
||||||
$member = new \OCA\Richdocuments\Db\Member();
|
|
||||||
$member->deleteBy('es_id', $esId);
|
|
||||||
|
|
||||||
$op= new \OCA\Richdocuments\Db\Op();
|
|
||||||
$op->deleteBy('es_id', $esId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function syncOps($memberId, $currentHead, $clientHead, $clientOps){
|
|
||||||
$hasOps = count($clientOps)>0;
|
|
||||||
$op = new \OCA\Richdocuments\Db\Op();
|
|
||||||
|
|
||||||
// TODO handle the case ($currentHead == "") && ($seqHead != "")
|
|
||||||
if ($clientHead == $currentHead) {
|
|
||||||
// matching heads
|
|
||||||
if ($hasOps) {
|
|
||||||
// incoming ops without conflict
|
|
||||||
// Add incoming ops, respond with a new head
|
|
||||||
$newHead = \OCA\Richdocuments\Db\Op::addOpsArray($this->getEsId(), $memberId, $clientOps);
|
|
||||||
$result = array(
|
|
||||||
'result' => 'added',
|
|
||||||
'head_seq' => $newHead ? $newHead : $currentHead
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// no incoming ops (just checking for new ops...)
|
|
||||||
$result = array(
|
|
||||||
'result' => 'new_ops',
|
|
||||||
'ops' => array(),
|
|
||||||
'head_seq' => $currentHead
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else { // HEADs do not match
|
|
||||||
$result = array(
|
|
||||||
'result' => $hasOps ? 'conflict' : 'new_ops',
|
|
||||||
'ops' => $op->getOpsAfterJson($this->getEsId(), $clientHead),
|
|
||||||
'head_seq' => $currentHead,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function insert(){
|
|
||||||
$esId = $this->getUniqueSessionId();
|
|
||||||
array_unshift($this->data, $esId);
|
|
||||||
return parent::insert();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateGenesisHash($esId, $genesisHash){
|
|
||||||
return $this->execute(
|
|
||||||
'UPDATE `*PREFIX*richdocuments_session` SET `genesis_hash`=? WHERE `es_id`=?',
|
|
||||||
array(
|
|
||||||
$genesisHash, $esId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInfo($esId){
|
|
||||||
$result = $this->execute('
|
|
||||||
SELECT `s`.*, COUNT(`m`.`member_id`) AS `users`
|
|
||||||
FROM ' . $this->tableName . ' AS `s`
|
|
||||||
LEFT JOIN `*PREFIX*richdocuments_member` AS `m` ON `s`.`es_id`=`m`.`es_id`
|
|
||||||
AND `m`.`status`=' . Db\Member::MEMBER_STATUS_ACTIVE . '
|
|
||||||
AND `m`.`uid` != ?
|
|
||||||
WHERE `s`.`es_id` = ?
|
|
||||||
GROUP BY `m`.`es_id`
|
|
||||||
',
|
|
||||||
[
|
|
||||||
\OC::$server->getUserSession()->getUser()->getUID(),
|
|
||||||
$esId
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$info = $result->fetch();
|
|
||||||
if (!is_array($info)){
|
|
||||||
$info = array();
|
|
||||||
}
|
|
||||||
return $info;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getUniqueSessionId(){
|
|
||||||
$testSession = new Session();
|
|
||||||
do{
|
|
||||||
$id = \OC::$server->getSecureRandom()
|
|
||||||
->getMediumStrengthGenerator()
|
|
||||||
->generate(30, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
|
|
||||||
} while ($testSession->load($id)->hasData());
|
|
||||||
|
|
||||||
return $id;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2014 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This file is licensed under the Affero General Public License version 3 or
|
|
||||||
* later.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments;
|
|
||||||
|
|
||||||
use \OCP\AppFramework\Http;
|
|
||||||
use \OCP\IRequest;
|
|
||||||
use \OC\Files\View;
|
|
||||||
|
|
||||||
class DownloadResponse extends \OCP\AppFramework\Http\Response {
|
|
||||||
private $request;
|
|
||||||
private $view;
|
|
||||||
private $path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param IRequest $request
|
|
||||||
* @param string $user
|
|
||||||
* @param string $path
|
|
||||||
*/
|
|
||||||
public function __construct(IRequest $request, $user, $path) {
|
|
||||||
$this->request = $request;
|
|
||||||
$this->user = $user;
|
|
||||||
$this->path = $path;
|
|
||||||
|
|
||||||
$this->view = new View('/' . $user);
|
|
||||||
if (!$this->view->file_exists($path)){
|
|
||||||
$this->setStatus(Http::STATUS_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render(){
|
|
||||||
if ($this->getStatus() === Http::STATUS_NOT_FOUND){
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
$info = $this->view->getFileInfo($this->path);
|
|
||||||
$this->ETag = $info['etag'];
|
|
||||||
|
|
||||||
$content = $this->view->file_get_contents($this->path);
|
|
||||||
$data = \OCA\Richdocuments\Filter::read($content, $info['mimetype']);
|
|
||||||
$size = strlen($data['content']);
|
|
||||||
|
|
||||||
|
|
||||||
if (isset($this->request->server['HTTP_RANGE']) && !is_null($this->request->server['HTTP_RANGE'])){
|
|
||||||
$isValidRange = preg_match('/^bytes=\d*-\d*(,\d*-\d*)*$/', $this->request->server['HTTP_RANGE']);
|
|
||||||
if (!$isValidRange){
|
|
||||||
return $this->sendRangeNotSatisfiable($size);
|
|
||||||
}
|
|
||||||
|
|
||||||
$ranges = explode(',', substr($this->request->server['HTTP_RANGE'], 6));
|
|
||||||
foreach ($ranges as $range){
|
|
||||||
$parts = explode('-', $range);
|
|
||||||
|
|
||||||
if ($parts[0]==='' && $parts[1]=='') {
|
|
||||||
$this->sendNotSatisfiable($size);
|
|
||||||
}
|
|
||||||
if ($parts[0]==='') {
|
|
||||||
$start = $size - $parts[1];
|
|
||||||
$end = $size - 1;
|
|
||||||
} else {
|
|
||||||
$start = $parts[0];
|
|
||||||
$end = ($parts[1]==='') ? $size - 1 : $parts[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($start > $end){
|
|
||||||
$this->sendNotSatisfiable($size);
|
|
||||||
}
|
|
||||||
|
|
||||||
$buffer = substr($data['content'], $start, $end - $start);
|
|
||||||
$md5Sum = md5($buffer);
|
|
||||||
|
|
||||||
// send the headers and data
|
|
||||||
$this->addHeader('Content-Length', $end - $start);
|
|
||||||
$this->addHeader('Content-md5', $md5Sum);
|
|
||||||
$this->addHeader('Accept-Ranges', 'bytes');
|
|
||||||
$this->addHeader('Content-Range', 'bytes ' . $start . '-' . ($end) . '/' . $size);
|
|
||||||
$this->addHeader('Connection', 'close');
|
|
||||||
$this->addHeader('Content-Type', $data['mimetype']);
|
|
||||||
$this->addContentDispositionHeader();
|
|
||||||
return $buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addHeader('Content-Type', $data['mimetype']);
|
|
||||||
$this->addContentDispositionHeader();
|
|
||||||
$this->addHeader('Content-Length', $size);
|
|
||||||
|
|
||||||
return $data['content'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send 416 if we can't satisfy the requested ranges
|
|
||||||
* @param integer $filesize
|
|
||||||
*/
|
|
||||||
protected function sendRangeNotSatisfiable($filesize){
|
|
||||||
$this->setStatus(Http::STATUS_REQUEST_RANGE_NOT_SATISFIABLE);
|
|
||||||
$this->addHeader('Content-Range', 'bytes */' . $filesize); // Required in 416.
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function addContentDispositionHeader(){
|
|
||||||
$encodedName = rawurlencode(basename($this->path));
|
|
||||||
$isIE = preg_match("/MSIE/", $this->request->server["HTTP_USER_AGENT"]);
|
|
||||||
if ($isIE){
|
|
||||||
$this->addHeader(
|
|
||||||
'Content-Disposition',
|
|
||||||
'attachment; filename="' . $encodedName . '"'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->addHeader(
|
|
||||||
'Content-Disposition',
|
|
||||||
'attachment; filename*=UTF-8\'\'' . $encodedName . '; filepath="' . $encodedName . '"'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,200 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2013 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 3 of the License, or any later version.
|
|
||||||
*
|
|
||||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments;
|
|
||||||
|
|
||||||
use \OC\Files\View;
|
|
||||||
|
|
||||||
class File {
|
|
||||||
protected $fileId;
|
|
||||||
protected $owner;
|
|
||||||
protected $sharing;
|
|
||||||
protected $token;
|
|
||||||
protected $passwordProtected = false;
|
|
||||||
protected $ownerView;
|
|
||||||
protected $ownerViewFiles;
|
|
||||||
protected $path;
|
|
||||||
protected $pathFiles;
|
|
||||||
|
|
||||||
public function __construct($fileId, $shareOps = null, $token = ''){
|
|
||||||
if (!$fileId){
|
|
||||||
throw new \Exception('No valid file has been passed');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->fileId = $fileId;
|
|
||||||
$this->sharing = $shareOps;
|
|
||||||
$this->token = $token;
|
|
||||||
|
|
||||||
if ($this->isPublicShare()) {
|
|
||||||
if (isset($this->sharing['uid_owner'])){
|
|
||||||
$this->owner = $this->sharing['uid_owner'];
|
|
||||||
if (!\OC::$server->getUserManager()->userExists($this->sharing['uid_owner'])) {
|
|
||||||
throw new \Exception('Share owner' . $this->sharing['uid_owner'] . ' does not exist ');
|
|
||||||
}
|
|
||||||
|
|
||||||
\OC_Util::tearDownFS();
|
|
||||||
\OC_Util::setupFS($this->sharing['uid_owner']);
|
|
||||||
} else {
|
|
||||||
throw new \Exception($this->fileId . ' is a broken share');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->owner = \OC::$server->getUserSession()->getUser()->getUID();
|
|
||||||
}
|
|
||||||
$this->initViews();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static function getByShareToken($token){
|
|
||||||
$linkItem = \OCP\Share::getShareByToken($token, false);
|
|
||||||
if (is_array($linkItem) && isset($linkItem['uid_owner'])) {
|
|
||||||
// seems to be a valid share
|
|
||||||
$rootLinkItem = \OCP\Share::resolveReShare($linkItem);
|
|
||||||
} else {
|
|
||||||
throw new \Exception('This file was probably unshared');
|
|
||||||
}
|
|
||||||
|
|
||||||
$file = new File($rootLinkItem['file_source'], $rootLinkItem, $token);
|
|
||||||
|
|
||||||
if (isset($linkItem['share_with']) && !empty($linkItem['share_with'])){
|
|
||||||
$file->setPasswordProtected(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getToken(){
|
|
||||||
return $this->token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFileId(){
|
|
||||||
return $this->fileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setToken($token){
|
|
||||||
$this->token = $token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isPublicShare(){
|
|
||||||
return !empty($this->token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isPasswordProtected(){
|
|
||||||
return $this->passwordProtected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $password
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function checkPassword($password){
|
|
||||||
$shareId = $this->sharing['id'];
|
|
||||||
if (!$this->isPasswordProtected()
|
|
||||||
|| (\OC::$server->getSession()->exists('public_link_authenticated')
|
|
||||||
&& \OC::$server->getSession()->get('public_link_authenticated') === $shareId
|
|
||||||
)
|
|
||||||
){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check Password
|
|
||||||
$newHash = '';
|
|
||||||
if(\OC::$server->getHasher()->verify($password, $this->getPassword(), $newHash)) {
|
|
||||||
\OC::$server->getSession()->set('public_link_authenticated', $shareId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FIXME: Migrate old hashes to new hash format
|
|
||||||
* Due to the fact that there is no reasonable functionality to update the password
|
|
||||||
* of an existing share no migration is yet performed there.
|
|
||||||
* The only possibility is to update the existing share which will result in a new
|
|
||||||
* share ID and is a major hack.
|
|
||||||
*
|
|
||||||
* In the future the migration should be performed once there is a proper method
|
|
||||||
* to update the share's password. (for example `$share->updatePassword($password)`
|
|
||||||
*
|
|
||||||
* @link https://github.com/owncloud/core/issues/10671
|
|
||||||
*/
|
|
||||||
if(!empty($newHash)) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param boolean $value
|
|
||||||
*/
|
|
||||||
public function setPasswordProtected($value){
|
|
||||||
$this->passwordProtected = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOwner(){
|
|
||||||
return $this->owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOwnerView($relativeToFiles = false){
|
|
||||||
return $relativeToFiles ? $this->ownerViewFiles : $this->ownerView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPath($relativeToFiles = false){
|
|
||||||
return $relativeToFiles ? $this->pathFiles : $this->path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPermissions(){
|
|
||||||
$fileInfo = $this->ownerView->getFileInfo($this->path);
|
|
||||||
return $fileInfo->getPermissions();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function initViews(){
|
|
||||||
$this->ownerView = new View('/' . $this->owner);
|
|
||||||
$this->ownerViewFiles = new View('/' . $this->owner . '/files');
|
|
||||||
$this->path = $this->ownerView->getPath($this->fileId);
|
|
||||||
$this->pathFiles = $this->ownerViewFiles->getPath($this->fileId);
|
|
||||||
|
|
||||||
if (!$this->path || !$this->pathFiles) {
|
|
||||||
throw new \Exception($this->fileId . ' can not be resolved');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->ownerView->file_exists($this->path)) {
|
|
||||||
throw new \Exception($this->path . ' doesn\'t exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->ownerViewFiles->file_exists($this->pathFiles)) {
|
|
||||||
throw new \Exception($this->pathFiles . ' doesn\'t exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->ownerView->is_file($this->path)){
|
|
||||||
throw new \Exception('Object ' . $this->path . ' is not a file.');
|
|
||||||
}
|
|
||||||
//TODO check if it is a valid odt
|
|
||||||
|
|
||||||
$mimetype = $this->ownerView->getMimeType($this->path);
|
|
||||||
if (!Filter::isSupportedMimetype($mimetype)){
|
|
||||||
throw new \Exception( $this->path . ' is ' . $mimetype . ' and is not supported by RichDocuments app');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getPassword(){
|
|
||||||
return $this->sharing['share_with'];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* ownCloud - Richdocuments App
|
|
||||||
*
|
|
||||||
* @author Victor Dubiniuk
|
|
||||||
* @copyright 2013 Victor Dubiniuk victor.dubiniuk@gmail.com
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 3 of the License, or any later version.
|
|
||||||
*
|
|
||||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace OCA\Richdocuments;
|
|
||||||
|
|
||||||
use \OC\Files\View;
|
|
||||||
|
|
||||||
class Genesis {
|
|
||||||
|
|
||||||
const DOCUMENTS_DIRNAME='/documents';
|
|
||||||
|
|
||||||
protected $view;
|
|
||||||
|
|
||||||
protected $path;
|
|
||||||
|
|
||||||
protected $hash;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create new genesis document
|
|
||||||
* @param File $file
|
|
||||||
* */
|
|
||||||
public function __construct(File $file){
|
|
||||||
$view = $file->getOwnerView();
|
|
||||||
$path = $file->getPath();
|
|
||||||
$owner = $file->getOwner();
|
|
||||||
|
|
||||||
$this->view = new View('/' . $owner);
|
|
||||||
|
|
||||||
if (!$this->view->file_exists(self::DOCUMENTS_DIRNAME)){
|
|
||||||
$this->view->mkdir(self::DOCUMENTS_DIRNAME );
|
|
||||||
}
|
|
||||||
$this->validate($view, $path);
|
|
||||||
|
|
||||||
$this->hash = $view->hash('sha1', $path, false);
|
|
||||||
$this->path = self::DOCUMENTS_DIRNAME . '/' . $this->hash . '.odt';
|
|
||||||
if (!$this->view->file_exists($this->path)){
|
|
||||||
//copy new genesis to /user/documents/{hash}.odt
|
|
||||||
// get decrypted content
|
|
||||||
$content = $view->file_get_contents($path);
|
|
||||||
$mimetype = $view->getMimeType($path);
|
|
||||||
|
|
||||||
$data = Filter::read($content, $mimetype);
|
|
||||||
$this->view->file_put_contents($this->path, $data['content']);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->validate($this->view, $this->path);
|
|
||||||
} catch (\Exception $e){
|
|
||||||
throw new \Exception('Failed to copy genesis');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPath(){
|
|
||||||
return $this->path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHash(){
|
|
||||||
return $this->hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if genesis is valid
|
|
||||||
* @param \OC\Files\View $view
|
|
||||||
* @param string $path relative to the view
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function validate($view, $path){
|
|
||||||
if (!$view->file_exists($path)){
|
|
||||||
throw new \Exception('Document not found ' . $path);
|
|
||||||
}
|
|
||||||
if (!$view->is_file($path)){
|
|
||||||
throw new \Exception('Object ' . $path . ' is not a file.');
|
|
||||||
}
|
|
||||||
//TODO check if it is a valid odt
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue