2013-09-02 19:54:23 +03:00
< ? php
/**
2015-12-16 17:57:44 +03:00
* ownCloud - Richdocuments App
2013-09-02 19:54:23 +03:00
*
* @ author Victor Dubiniuk
2014-10-23 23:08:55 +03:00
* @ copyright 2014 Victor Dubiniuk victor . dubiniuk @ gmail . com
2013-09-02 19:54:23 +03:00
*
* This file is licensed under the Affero General Public License version 3 or
* later .
*/
2015-12-16 17:57:44 +03:00
namespace OCA\Richdocuments\Controller ;
2014-10-23 23:08:55 +03:00
use \OCP\AppFramework\Controller ;
use \OCP\IRequest ;
use \OCP\IConfig ;
use \OCP\IL10N ;
2015-10-26 11:00:25 +01:00
use \OCP\AppFramework\Http\ContentSecurityPolicy ;
2014-10-23 23:08:55 +03:00
use \OCP\AppFramework\Http\JSONResponse ;
2015-10-26 11:00:25 +01:00
use \OCP\AppFramework\Http\TemplateResponse ;
2014-10-23 23:08:55 +03:00
2015-12-16 17:57:44 +03:00
use \OCA\Richdocuments\Db ;
use \OCA\Richdocuments\Helper ;
use \OCA\Richdocuments\Storage ;
use \OCA\Richdocuments\Download ;
use \OCA\Richdocuments\DownloadResponse ;
use \OCA\Richdocuments\File ;
2016-03-08 10:51:49 -04:00
use \OCA\Richdocuments\Genesis ;
2014-11-11 03:22:31 +03:00
use \OC\Files\View ;
2016-03-08 10:51:49 -04:00
use \OCP\ICacheFactory ;
2016-03-08 18:35:44 -04:00
use \OCP\ILogger ;
2013-09-02 19:54:23 +03:00
2016-04-12 20:16:04 +02:00
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 {
2016-01-12 15:20:38 +01:00
2014-10-23 23:08:55 +03:00
private $uid ;
private $l10n ;
private $settings ;
2016-03-08 10:51:49 -04:00
private $cache ;
2016-03-08 18:35:44 -04:00
private $logger ;
2016-01-12 15:20:38 +01:00
2015-11-04 16:58:18 +01:00
const ODT_TEMPLATE_PATH = '/assets/odttemplate.odt' ;
2016-01-12 15:20:38 +01:00
2016-03-08 18:35:44 -04:00
public function __construct ( $appName , IRequest $request , IConfig $settings , IL10N $l10n , $uid , ICacheFactory $cache , ILogger $logger ){
2014-10-23 23:08:55 +03:00
parent :: __construct ( $appName , $request );
$this -> uid = $uid ;
$this -> l10n = $l10n ;
$this -> settings = $settings ;
2016-03-08 10:51:49 -04:00
$this -> cache = $cache -> create ( $appName );
2016-03-08 18:35:44 -04:00
$this -> logger = $logger ;
2014-10-23 23:08:55 +03:00
}
2016-01-12 15:20:38 +01:00
2016-03-10 08:00:52 -04:00
/**
* @ param \SimpleXMLElement $discovery
* @ param string $mimetype
* @ param string $action
*/
2016-04-12 20:16:04 +02:00
private function getWopiSrcUrl ( $discovery_parsed , $mimetype , $action ) {
if ( is_null ( $discovery_parsed ) || $discovery_parsed == false ) {
2016-03-10 08:00:52 -04:00
return null ;
}
2016-04-12 20:16:04 +02:00
$result = $discovery_parsed -> xpath ( sprintf ( '/wopi-discovery/net-zone/app[@name=\'%s\']/action[@name=\'%s\']' , $mimetype , $action ));
2016-03-10 08:00:52 -04:00
if ( $result && count ( $result ) > 0 ) {
return ( string ) $result [ 0 ][ 'urlsrc' ];
}
return null ;
}
2016-03-16 23:46:13 -04:00
private function responseError ( $message , $hint = '' ){
$errors = array ( 'errors' => array ( array ( 'error' => $message , 'hint' => $hint )));
$response = new TemplateResponse ( '' , 'error' , $errors , 'error' );
return $response ;
2016-03-10 08:00:52 -04:00
}
2016-04-12 20:16:04 +02:00
/** Return the content of discovery . xml - either from cache , or download it .
2015-08-26 19:09:34 +03:00
*/
2016-04-12 20:16:04 +02:00
private function getDiscovery (){
\OC :: $server -> getLogger () -> debug ( 'getDiscovery(): Getting discovery.xml from the cache.' );
2016-03-08 18:35:44 -04:00
$wopiRemote = $this -> settings -> getAppValue ( 'richdocuments' , 'wopi_url' );
2016-03-16 09:04:35 -04:00
2016-04-12 20:16:04 +02:00
// Provides access to information about the capabilities of a WOPI client
// and the mechanisms for invoking those abilities through URIs.
$wopiDiscovery = $wopiRemote . '/hosting/discovery' ;
2016-03-16 23:46:13 -04:00
2016-04-12 20:16:04 +02:00
// Read the memcached value (if the memcache is installed)
2016-03-16 23:46:13 -04:00
$discovery = $this -> cache -> get ( 'discovery.xml' );
2016-04-12 20:16:04 +02:00
2016-03-16 23:46:13 -04:00
if ( is_null ( $discovery )) {
2016-04-12 20:16:04 +02:00
$contact_admin = $this -> l10n -> t ( 'Please contact the "%s" administrator.' , array ( $wopiRemote ));
2016-03-16 23:46:13 -04:00
try {
$wopiClient = \OC :: $server -> getHTTPClientService () -> newClient ();
2016-04-12 20:16:04 +02:00
$discovery = $wopiClient -> get ( $wopiDiscovery ) -> getBody ();
2016-03-16 23:46:13 -04:00
}
catch ( \Exception $e ) {
2016-04-12 15:18:02 +02:00
$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' :
2016-04-12 20:16:04 +02:00
throw new ResponseException ( $this -> l10n -> t ( 'Collabora Online: The protocol specified in "%s" is not allowed.' , array ( $wopiRemote )), $admin_check );
2016-04-12 15:18:02 +02:00
case '3' :
2016-04-12 20:16:04 +02:00
throw new ResponseException ( $this -> l10n -> t ( 'Collabora Online: Malformed URL "%s".' , array ( $wopiRemote )), $admin_check );
2016-04-12 15:18:02 +02:00
case '6' :
2016-04-12 20:16:04 +02:00
throw new ResponseException ( $this -> l10n -> t ( 'Collabora Online: Cannot resolve the host "%s".' , array ( $wopiRemote )), $admin_check );
2016-04-12 15:18:02 +02:00
case '60' :
2016-04-12 20:16:04 +02:00
throw new ResponseException ( $this -> l10n -> t ( 'Collabora Online: SSL certificate is not installed.' ), $this -> l10n -> t ( 'Please ask your administrator to add CollaboraCloudSuiteCA_ca-chain.cert.pem to the ownCloud\'s ca-bundle.crt, for example "cat /etc/loolwsd/CollaboraCloudSuiteCA_ca-chain.cert.pem >> owncloud/resources/config/ca-bundle.crt" . The exact error message was: ' ) . $error_message );
2016-04-12 15:18:02 +02:00
}
}
2016-04-12 20:16:04 +02:00
throw new ResponseException ( $this -> l10n -> t ( 'Collabora Online unknown error: ' ) . $error_message , $contact_admin );
2016-03-16 23:46:13 -04:00
}
2016-04-12 20:16:04 +02:00
if ( ! $discovery ) {
throw new ResponseException ( $this -> l10n -> t ( 'Collabora Online: Unable to read discovery.xml from "%s".' , array ( $wopiRemote )), $contact_admin );
2016-03-17 09:58:11 -04:00
}
2016-04-12 20:16:04 +02:00
\OC :: $server -> getLogger () -> debug ( 'Storing the discovery.xml to the cache.' );
$this -> cache -> set ( 'discovery.xml' , $discovery , 3600 );
}
return $discovery ;
}
/**
* @ NoAdminRequired
* @ NoCSRFRequired
*/
public function index (){
$wopiRemote = $this -> settings -> getAppValue ( 'richdocuments' , 'wopi_url' );
if (( $parts = parse_url ( $wopiRemote ))) {
$webSocketProtocol = " ws:// " ;
if ( $parts [ 'scheme' ] == " https " ) {
$webSocketProtocol = " wss:// " ;
2016-03-17 09:58:11 -04:00
}
2016-04-12 20:16:04 +02:00
$webSocket = sprintf (
" %s%s%s " ,
$webSocketProtocol ,
isset ( $parts [ 'host' ]) ? $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.' ));
2016-03-16 23:46:13 -04:00
}
\OC :: $server -> getNavigationManager () -> setActiveEntry ( 'richdocuments_index' );
$maxUploadFilesize = \OCP\Util :: maxUploadFilesize ( " / " );
2015-12-16 17:57:44 +03:00
$response = new TemplateResponse ( 'richdocuments' , 'documents' , [
2015-08-26 19:09:34 +03:00
'enable_previews' => $this -> settings -> getSystemValue ( 'enable_previews' , true ),
2015-12-16 17:57:44 +03:00
'savePath' => $this -> settings -> getUserValue ( $this -> uid , 'richdocuments' , 'save_path' , '/' ),
2015-08-26 19:09:34 +03:00
'uploadMaxFilesize' => $maxUploadFilesize ,
'uploadMaxHumanFilesize' => \OCP\Util :: humanFileSize ( $maxUploadFilesize ),
'allowShareWithLink' => $this -> settings -> getAppValue ( 'core' , 'shareapi_allow_links' , 'yes' ),
2016-03-16 09:04:35 -04:00
'wopi_url' => $webSocket ,
2015-08-26 19:09:34 +03:00
]);
2015-10-26 11:00:25 +01:00
$policy = new ContentSecurityPolicy ();
2016-03-08 18:35:44 -04:00
$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 );
$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 );
2016-03-16 09:04:35 -04:00
$policy -> addAllowedConnectDomain ( $webSocket );
2015-10-26 11:00:25 +01:00
$policy -> addAllowedImageDomain ( '*' );
$policy -> allowInlineScript ( true );
$policy -> addAllowedFontDomain ( 'data:' );
$response -> setContentSecurityPolicy ( $policy );
return $response ;
2015-08-26 19:09:34 +03:00
}
2016-01-12 15:20:38 +01:00
2014-10-23 23:08:55 +03:00
/**
* @ NoAdminRequired
*/
public function create (){
2015-11-04 21:49:23 +01:00
$mimetype = $this -> request -> post [ 'mimetype' ];
2014-11-11 03:22:31 +03:00
$view = new View ( '/' . $this -> uid . '/files' );
2014-10-23 23:08:55 +03:00
$dir = $this -> settings -> getUserValue ( $this -> uid , $this -> appName , 'save_path' , '/' );
2014-04-02 23:18:39 +03:00
if ( ! $view -> is_dir ( $dir )){
$dir = '/' ;
}
2015-11-04 21:49:23 +01:00
$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 ;
default :
// to be safe
$mimetype = 'application/vnd.oasis.opendocument.text' ;
break ;
}
$path = Helper :: getNewFileName ( $view , $dir . '/' . $basename );
2016-01-12 15:20:38 +01:00
2014-11-05 17:07:46 +03:00
$content = '' ;
2013-10-03 20:34:17 +03:00
if ( class_exists ( '\OC\Files\Type\TemplateManager' )){
$manager = \OC_Helper :: getFileTemplateManager ();
2015-11-04 21:49:23 +01:00
$content = $manager -> getTemplate ( $mimetype );
2014-11-05 17:07:46 +03:00
}
2016-01-12 15:20:38 +01:00
2014-11-05 17:07:46 +03:00
if ( ! $content ){
$content = file_get_contents ( dirname ( __DIR__ ) . self :: ODT_TEMPLATE_PATH );
2013-10-03 20:34:17 +03:00
}
2016-01-12 15:20:38 +01:00
2014-11-05 17:07:46 +03:00
if ( $content && $view -> file_put_contents ( $path , $content )){
2014-04-02 23:18:39 +03:00
$info = $view -> getFileInfo ( $path );
2014-10-23 23:08:55 +03:00
$response = array (
'status' => 'success' ,
'fileid' => $info [ 'fileid' ]
);
2014-04-02 23:18:39 +03:00
} else {
2014-10-23 23:08:55 +03:00
$response = array (
'status' => 'error' ,
2014-10-26 15:47:56 +03:00
'message' => ( string ) $this -> l10n -> t ( 'Can\'t create document' )
2014-04-02 23:18:39 +03:00
);
}
2014-10-23 23:08:55 +03:00
return $response ;
2013-09-13 13:18:45 +03:00
}
2016-03-08 21:16:19 -05:00
/**
2016-03-23 21:57:22 -04:00
* @ NoAdminRequired
2016-03-08 21:16:19 -05:00
* Generates and returns an access token for a given fileId .
* Only for authenticated users !
*/
public function wopiGetToken ( $fileId ){
\OC :: $server -> getLogger () -> debug ( 'Generating WOPI Token for file {fileId}.' , [ 'app' => $this -> appName , 'fileId' => $fileId ]);
$row = new Db\Wopi ();
$token = $row -> generateFileToken ( $fileId );
// Return the token.
return array (
'status' => 'success' ,
'token' => $token
);
}
2016-03-23 21:56:06 -04:00
/**
* @ NoAdminRequired
* @ NoCSRFRequired
* @ PublicPage
* Returns general info about a file .
*/
public function wopiCheckFileInfo ( $fileId ){
$token = $this -> request -> getParam ( 'access_token' );
\OC :: $server -> getLogger () -> debug ( 'Getting info about file {fileId} by token {token}.' , [ 'app' => $this -> appName , 'fileId' => $fileId , 'token' => $token ]);
$row = new Db\Wopi ();
$row -> loadBy ( 'token' , $token );
$res = $row -> getPathForToken ( $fileId , $token );
2016-03-23 21:57:22 -04:00
if ( $res == false || http_response_code () != 200 )
{
return false ;
}
2016-03-23 21:56:06 -04:00
$view = new \OC\Files\View ( '/' . $res [ 'user' ] . '/' );
$info = $view -> getFileInfo ( $res [ 'path' ]);
\OC :: $server -> getLogger () -> debug ( 'File info: {info}.' , [ 'app' => $this -> appName , 'info' => $info ]);
$baseFileName = $info [ 'name' ];
$size = $info [ 'size' ];
return array (
'BaseFileName' => $baseFileName ,
'Size' => $size ,
//'DownloadUrl' => '',
//'FileUrl' => '',
);
}
2016-03-08 21:16:19 -05:00
/**
* @ 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' );
\OC :: $server -> getLogger () -> debug ( 'Getting contents of file {fileId} by token {token}.' , [ 'app' => $this -> appName , 'fileId' => $fileId , 'token' => $token ]);
$row = new Db\Wopi ();
$row -> loadBy ( 'token' , $token );
//TODO: Support X-WOPIMaxExpectedSize header.
$res = $row -> getPathForToken ( $fileId , $token );
return new DownloadResponse ( $this -> request , $res [ 'user' ], $res [ 'path' ]);
}
/**
* @ 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' );
\OC :: $server -> getLogger () -> debug ( 'Putting contents of file {fileId} by token {token}.' , [ 'app' => $this -> appName , 'fileId' => $fileId , 'token' => $token ]);
$row = new Db\Wopi ();
$row -> loadBy ( 'token' , $token );
2016-03-13 23:23:57 -04:00
$res = $row -> getPathForToken ( $fileId , $token );
2016-03-08 21:16:19 -05:00
$view = new \OC\Files\View ( '/' . $res [ 'user' ] . '/' );
// Read the contents of the file from the POST body and store.
$content = file_get_contents ( 'php://input' );
2016-03-13 23:23:57 -04:00
\OC :: $server -> getLogger () -> debug ( 'Putting {size} bytes.' , [ 'app' => $this -> appName , 'size' => strlen ( $content ) ]);
2016-03-08 21:16:19 -05:00
$view -> file_put_contents ( $res [ 'path' ], $content );
return array (
'status' => 'success'
);
}
2014-01-21 21:18:53 +03:00
/**
2014-10-23 23:08:55 +03:00
* @ NoAdminRequired
* @ PublicPage
2013-09-02 19:54:23 +03:00
* Process partial / complete file download
*/
2014-10-23 23:08:55 +03:00
public function serve ( $esId ){
2014-08-04 20:51:50 +03:00
$session = new Db\Session ();
2014-10-23 23:08:55 +03:00
$session -> load ( $esId );
2016-01-12 15:20:38 +01:00
2014-04-11 23:12:23 +03:00
$filename = $session -> getGenesisUrl () ? $session -> getGenesisUrl () : '' ;
2014-10-29 00:52:43 +03:00
return new DownloadResponse ( $this -> request , $session -> getOwner (), $filename );
2013-09-02 19:54:23 +03:00
}
2016-01-12 15:20:38 +01:00
2014-10-28 01:59:49 +03:00
/**
* @ NoAdminRequired
*/
public function download ( $path ){
2014-11-04 20:55:52 +03:00
if ( ! $path ){
$response = new JSONResponse ();
$response -> setStatus ( Http :: STATUS_BAD_REQUEST );
return $response ;
}
2016-01-12 15:20:38 +01:00
2014-11-04 20:55:52 +03:00
$fullPath = '/files' . $path ;
$fileInfo = \OC\Files\Filesystem :: getFileInfo ( $path );
if ( $fileInfo ){
2016-03-14 23:34:29 +01:00
$file = new File ( $fileInfo -> getId ());
$genesis = new Genesis ( $file );
$fullPath = $genesis -> getPath ();
2014-10-28 01:59:49 +03:00
}
2014-11-04 20:55:52 +03:00
return new DownloadResponse ( $this -> request , $this -> uid , $fullPath );
2014-10-28 01:59:49 +03:00
}
2016-01-12 15:20:38 +01:00
2014-10-29 00:52:43 +03:00
2014-10-23 23:08:55 +03:00
/**
* @ NoAdminRequired
*/
public function rename ( $fileId ){
$name = $this -> request -> post [ 'name' ];
2014-04-09 18:54:22 +03:00
2014-04-08 22:12:08 +03:00
$view = \OC\Files\Filesystem :: getView ();
$path = $view -> getPath ( $fileId );
2013-12-19 00:07:17 +03:00
2014-10-23 23:08:55 +03:00
if ( $name && $view -> is_file ( $path ) && $view -> isUpdatable ( $path )) {
2014-04-08 22:12:08 +03:00
$newPath = dirname ( $path ) . '/' . $name ;
if ( $view -> rename ( $path , $newPath )) {
2014-10-23 23:08:55 +03:00
return array ( 'status' => 'success' );
2013-12-19 00:07:17 +03:00
}
}
2014-10-23 23:08:55 +03:00
return array (
'status' => 'error' ,
2014-10-26 15:47:56 +03:00
'message' => ( string ) $this -> l10n -> t ( 'You don\'t have permission to rename this document' )
2014-10-23 23:08:55 +03:00
);
2013-12-19 00:07:17 +03:00
}
2013-09-02 19:54:23 +03:00
/**
2014-10-23 23:08:55 +03:00
* @ NoAdminRequired
2013-09-02 19:54:23 +03:00
* 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
*/
2014-10-23 23:08:55 +03:00
public function listAll (){
2014-04-14 18:05:18 +03:00
$found = Storage :: getDocuments ();
2016-04-12 20:16:04 +02:00
$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' );
return responseError ( $this -> l10n -> t ( 'Collabora Online: discovery.xml from "%s" is not a well-formed XML string.' , array ( $wopiRemote )), $this -> l10n -> t ( 'Please contact the "%s" administrator.' , array ( $wopiRemote )));
}
}
catch ( ResponseException $e ) {
return $this -> responseError ( $e -> getMessage (), $e -> getHint ());
}
2013-09-02 19:54:23 +03:00
$fileIds = array ();
2014-04-14 18:05:18 +03:00
$documents = array ();
foreach ( $found as $key => $document ) {
if ( is_object ( $document )){
$documents [] = $document -> getData ();
} else {
$documents [ $key ] = $document ;
}
2015-08-28 19:32:54 +03:00
$documents [ $key ][ 'icon' ] = preg_replace ( '/\.png$/' , '.svg' , \OCP\Template :: mimetype_icon ( $document [ 'mimetype' ]));
2015-08-28 20:49:50 +03:00
$documents [ $key ][ 'hasPreview' ] = \OC :: $server -> getPreviewManager () -> isMimeSupported ( $document [ 'mimetype' ]);
2016-04-12 20:16:04 +02:00
$documents [ $key ][ 'urlsrc' ] = $this -> getWopiSrcUrl ( $discovery_parsed , $document [ 'mimetype' ], 'edit' );
2013-09-02 19:54:23 +03:00
$fileIds [] = $document [ 'fileid' ];
}
2013-10-01 17:49:03 +03:00
usort ( $documents , function ( $a , $b ){
return @ $b [ 'mtime' ] -@ $a [ 'mtime' ];
});
2016-01-12 15:20:38 +01:00
2014-08-04 20:51:50 +03:00
$session = new Db\Session ();
2013-09-27 18:43:10 +03:00
$sessions = $session -> getCollectionBy ( 'file_id' , $fileIds );
2013-09-02 19:54:23 +03:00
$members = array ();
2014-08-04 21:00:58 +03:00
$member = new Db\Member ();
2016-01-12 15:20:38 +01:00
foreach ( $sessions as $session ) {
2013-10-31 20:24:55 +03:00
$members [ $session [ 'es_id' ]] = $member -> getActiveCollection ( $session [ 'es_id' ]);
2013-09-02 19:54:23 +03:00
}
2014-10-23 23:08:55 +03:00
return array (
'status' => 'success' , 'documents' => $documents , 'sessions' => $sessions , 'members' => $members
);
2013-09-02 19:54:23 +03:00
}
2013-11-08 15:46:30 +00:00
}