From a8efe3d9f505288e6f4598b2e2d7ed15b6738771 Mon Sep 17 00:00:00 2001 From: Guillaume Heurtier <guillaume.heurtier@maarch.org> Date: Fri, 26 Jun 2020 17:43:03 +0200 Subject: [PATCH] FEAT #13983 TIME 13:00 collabora online editing main document + begin edit attachment --- .../xml/documentEditorsConfig.xml | 3 +- rest/index.php | 2 +- .../controllers/CollaboraOnlineController.php | 241 +++++++++++++----- .../controllers/DocumentEditorController.php | 16 ++ .../controllers/OnlyOfficeController.php | 9 +- .../folder/controllers/FolderController.php | 1 + src/core/models/CurlModel.php | 8 +- .../app/viewer/document-viewer.component.ts | 73 ++++-- src/frontend/lang/lang-en.ts | 5 +- src/frontend/lang/lang-fr.ts | 1 + src/frontend/lang/lang-nl.ts | 5 +- .../collabora-online-viewer.component.html | 4 +- .../collabora-online-viewer.component.ts | 114 +++------ 13 files changed, 294 insertions(+), 188 deletions(-) diff --git a/apps/maarch_entreprise/xml/documentEditorsConfig.xml b/apps/maarch_entreprise/xml/documentEditorsConfig.xml index dff270c557d..d4aaaa8884c 100644 --- a/apps/maarch_entreprise/xml/documentEditorsConfig.xml +++ b/apps/maarch_entreprise/xml/documentEditorsConfig.xml @@ -12,7 +12,8 @@ </onlyoffice> <collaboraonline> <enabled>false</enabled> - <server_uri></server_uri> + <server_uri>collaboraonline.maarchcourrier.com</server_uri> <server_port>9980</server_port> + <server_ssl>false</server_ssl> </collaboraonline> </ROOT> diff --git a/rest/index.php b/rest/index.php index 70766e2ef3a..72832d46d9f 100755 --- a/rest/index.php +++ b/rest/index.php @@ -594,7 +594,7 @@ $app->get('/alfresco/autocomplete/folders', \Alfresco\controllers\AlfrescoContro // Collabora Online $app->get('/wopi/files/{id}/contents', \ContentManagement\controllers\CollaboraOnlineController::class . ':getFileContent'); $app->get('/wopi/files/{id}', \ContentManagement\controllers\CollaboraOnlineController::class . ':getCheckFileInfo'); -$app->post('/wopi/files/{id}/contents', \ContentManagement\controllers\CollaboraOnlineController::class . ':getCheckFileInfo'); +$app->post('/wopi/files/{id}/contents', \ContentManagement\controllers\CollaboraOnlineController::class . ':saveFile'); $app->post('/collaboraOnline/configuration', \ContentManagement\controllers\CollaboraOnlineController::class . ':getConfiguration'); $app->get('/collaboraOnline/available', \ContentManagement\controllers\CollaboraOnlineController::class . ':isAvailable'); diff --git a/src/app/contentManagement/controllers/CollaboraOnlineController.php b/src/app/contentManagement/controllers/CollaboraOnlineController.php index 495cd6be3fe..e026f927c11 100644 --- a/src/app/contentManagement/controllers/CollaboraOnlineController.php +++ b/src/app/contentManagement/controllers/CollaboraOnlineController.php @@ -14,19 +14,24 @@ namespace ContentManagement\controllers; +use Attachment\models\AttachmentModel; +use Convert\controllers\ConvertPdfController; use Convert\models\AdrModel; use Docserver\models\DocserverModel; use Docserver\models\DocserverTypeModel; use Firebase\JWT\JWT; +use History\controllers\HistoryController; use Resource\controllers\ResController; use Resource\controllers\StoreController; use Resource\models\ResModel; use Respect\Validation\Validator; use Slim\Http\Request; use Slim\Http\Response; +use SrcCore\controllers\CoreController; use SrcCore\controllers\UrlController; use SrcCore\models\CoreConfigModel; use SrcCore\models\CurlModel; +use SrcCore\models\ValidatorModel; use User\models\UserModel; class CollaboraOnlineController @@ -39,43 +44,33 @@ class CollaboraOnlineController return $response->withStatus(400)->withJson(['errors' => 'Query access_token is empty or not a string']); } - try { - $jwt = JWT::decode($queryParams['access_token'], CoreConfigModel::getEncryptKey(), ['HS256']); - } catch (\Exception $e) { - return $response->withStatus(401)->withJson(['errors' => 'Access token is invalid']); - } - - if ($jwt->resId != $args['id']) { - return $response->withStatus(401)->withJson(['errors' => 'Access token is invalid']); + $tokenCheckResult = CollaboraOnlineController::checkToken(['token' => $queryParams['access_token'], 'id' => $args['id']]); + if (!empty($tokenCheckResult['errors'])) { + return $response->withStatus($tokenCheckResult['code'])->withJson($tokenCheckResult['errors']); } - $GLOBALS['id'] = $jwt->userId; - - if ($jwt->type != 'resource') { - return $response->withStatus(400)->withJson(['errors' => 'WIP - only resources can be edited for now']); - } - - if (!ResController::hasRightByResId(['resId' => [$args['id']], 'userId' => $GLOBALS['id']])) { - return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']); - } + if ($tokenCheckResult['type'] == 'resource') { + if (!ResController::hasRightByResId(['resId' => [$args['id']], 'userId' => $GLOBALS['id']])) { + return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']); + } - $document = ResModel::getById(['select' => ['docserver_id', 'path', 'filename', 'category_id', 'version', 'fingerprint'], 'resId' => $args['id']]); - if (empty($document)) { - return $response->withStatus(400)->withJson(['errors' => 'Document does not exist']); - } + $document = ResModel::getById(['select' => ['docserver_id', 'path', 'filename', 'version', 'fingerprint'], 'resId' => $args['id']]); + if (empty($document['filename'])) { + return $response->withStatus(400)->withJson(['errors' => 'Document has no file']); + } - if (empty($document['filename'])) { - return $response->withStatus(400)->withJson(['errors' => 'Document has no file']); + // If the document has a signed version, it cannot be edited + $convertedDocument = AdrModel::getDocuments([ + 'select' => ['docserver_id', 'path', 'filename', 'fingerprint'], + 'where' => ['res_id = ?', 'type = ?', 'version = ?'], + 'data' => [$args['resId'], 'SIGN', $document['version']], + 'limit' => 1 + ]); + if (!empty($convertedDocument[0])) { + return $response->withStatus(400)->withJson(['errors' => 'Document was signed : it cannot be edited']); + } } - $convertedDocument = AdrModel::getDocuments([ - 'select' => ['docserver_id', 'path', 'filename', 'fingerprint'], - 'where' => ['res_id = ?', 'type = ?', 'version = ?'], - 'data' => [$args['resId'], 'SIGN', $document['version']], - 'limit' => 1 - ]); - $document = $convertedDocument[0] ?? $document; - $docserver = DocserverModel::getByDocserverId(['docserverId' => $document['docserver_id'], 'select' => ['path_template', 'docserver_type_id']]); if (empty($docserver['path_template']) || !file_exists($docserver['path_template'])) { return $response->withStatus(400)->withJson(['errors' => 'Docserver does not exist']); @@ -88,8 +83,12 @@ class CollaboraOnlineController $docserverType = DocserverTypeModel::getById(['id' => $docserver['docserver_type_id'], 'select' => ['fingerprint_mode']]); $fingerprint = StoreController::getFingerPrint(['filePath' => $pathToDocument, 'mode' => $docserverType['fingerprint_mode']]); - if (empty($convertedDocument) && empty($document['fingerprint'])) { - ResModel::update(['set' => ['fingerprint' => $fingerprint], 'where' => ['res_id = ?'], 'data' => [$args['resId']]]); + + if (empty($convertedDocument) && empty($document['fingerprint']) && $tokenCheckResult['type'] == 'resource') { + ResModel::update(['set' => ['fingerprint' => $fingerprint], 'where' => ['res_id = ?'], 'data' => [$args['id']]]); + $document['fingerprint'] = $fingerprint; + } else if (empty($convertedDocument) && empty($document['fingerprint']) && $tokenCheckResult['type'] == 'attachment') { + AttachmentModel::update(['set' => ['fingerprint' => $fingerprint], 'where' => ['res_id = ?'], 'data' => [$args['id']]]); $document['fingerprint'] = $fingerprint; } @@ -119,29 +118,28 @@ class CollaboraOnlineController return $response->withStatus(400)->withJson(['errors' => 'Query access_token is empty or not a string']); } - try { - $jwt = JWT::decode($queryParams['access_token'], CoreConfigModel::getEncryptKey(), ['HS256']); - } catch (\Exception $e) { - return $response->withStatus(401)->withJson(['errors' => 'Access token is invalid']); - } - - if ($jwt->resId != $args['id']) { - return $response->withStatus(401)->withJson(['errors' => 'Access token is invalid']); + $result = CollaboraOnlineController::checkToken(['token' => $queryParams['access_token'], 'id' => $args['id']]); + if (!empty($result['errors'])) { + return $response->withStatus($result['code'])->withJson($result['errors']); } - $GLOBALS['id'] = $jwt->userId; - - if ($jwt->type != 'resource') { - return $response->withStatus(400)->withJson(['errors' => 'WIP - only resources can be edited for now']); - } + if ($result['type'] == 'resource') { + if (!ResController::hasRightByResId(['resId' => [$args['id']], 'userId' => $GLOBALS['id']])) { + return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']); + } - if (!ResController::hasRightByResId(['resId' => [$args['id']], 'userId' => $GLOBALS['id']])) { - return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']); - } + $document = ResModel::getById(['select' => ['filename', 'filesize', 'modification_date'], 'resId' => $args['id']]); + } else if ($result['type'] == 'attachment'){ + $document = AttachmentModel::getById(['select' => ['res_id_master', 'filename', 'filesize', 'modification_date'], 'resId' => $args['id']]); + if (empty($document)) { + return $response->withStatus(400)->withJson(['errors' => 'Document does not exist']); + } - $document = ResModel::getById(['select' => ['docserver_id', 'path', 'filename', 'category_id', 'version', 'filesize', 'modification_date'], 'resId' => $args['id']]); - if (empty($document)) { - return $response->withStatus(400)->withJson(['errors' => 'Document does not exist']); + if (!ResController::hasRightByResId(['resId' => [$document['res_id_master']], 'userId' => $GLOBALS['id']])) { + return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']); + } + } else { + return $response->withStatus(501)->withJson(['errors' => 'WIP - only resources and attachments can be edited for now']); } if (empty($document['filename'])) { @@ -156,6 +154,8 @@ class CollaboraOnlineController 'Size' => $document['filesize'], 'UserCanNotWriteRelative' => true, 'UserCanWrite' => true, + 'DisablePrint' => true, + 'HideSaveOption' => true, 'UserFriendlyName' => UserModel::getLabelledUserById(['id' => $GLOBALS['id']]), 'OwnerId' => $GLOBALS['id'], 'UserId' => $GLOBALS['id'], @@ -165,6 +165,85 @@ class CollaboraOnlineController public function saveFile(Request $request, Response $response, array $args) { + $headers = $request->getHeaders(); + + // Collabora online saves automatically every X seconds, but we do not want to save the document yet + if (empty($headers['HTTP_X_LOOL_WOPI_EXTENDEDDATA'][0])) { + return $response->withStatus(200); + } + $extendedData = $headers['HTTP_X_LOOL_WOPI_EXTENDEDDATA'][0]; + $extendedData = explode('=', $extendedData); + if (empty($extendedData) || $extendedData[0] != 'FinalSave' || $extendedData[1] != 'True') { + return $response->withStatus(200); + } + + $queryParams = $request->getQueryParams(); + + if (!Validator::stringType()->notEmpty()->validate($queryParams['access_token'])) { + return $response->withStatus(400)->withJson(['errors' => 'Query access_token is empty or not a string']); + } + + $result = CollaboraOnlineController::checkToken(['token' => $queryParams['access_token'], 'id' => $args['id']]); + if (!empty($result['errors'])) { + return $response->withStatus($result['code'])->withJson($result['errors']); + } + + if ($result['type'] == 'resource') { + if (!ResController::hasRightByResId(['resId' => [$args['id']], 'userId' => $GLOBALS['id']])) { + return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']); + } + + $document = ResModel::getById(['select' => ['docserver_id', 'path', 'filename', 'category_id', 'version', 'fingerprint', 'alt_identifier'], 'resId' => $args['id']]); + if (empty($document['filename'])) { + return $response->withStatus(400)->withJson(['errors' => 'Document has no file']); + } + + AdrModel::createDocumentAdr([ + 'resId' => $args['id'], + 'type' => 'DOC', + 'docserverId' => $document['docserver_id'], + 'path' => $document['path'], + 'filename' => $document['filename'], + 'version' => $document['version'], + 'fingerprint' => $document['fingerprint'] + ]); + + $fileContent = $request->getBody()->getContents(); + $encodedFile = base64_encode($fileContent); + + $extension = pathinfo($document['filename'], PATHINFO_EXTENSION); + + $data = [ + 'resId' => $args['id'], + 'encodedFile' => $encodedFile, + 'format' => $extension + ]; + + $resId = StoreController::storeResource($data); + if (empty($resId) || !empty($resId['errors'])) { + return $response->withStatus(500)->withJson(['errors' => '[ResController update] ' . $resId['errors']]); + } + + ConvertPdfController::convert([ + 'resId' => $args['id'], + 'collId' => 'letterbox_coll', + 'version' => $document['version'] + 1 + ]); + + $customId = CoreConfigModel::getCustomId(); + $customId = empty($customId) ? 'null' : $customId; + exec("php src/app/convert/scripts/FullTextScript.php --customId {$customId} --resId {$args['id']} --collId letterbox_coll --userId {$GLOBALS['id']} > /dev/null &"); + + HistoryController::add([ + 'tableName' => 'res_letterbox', + 'recordId' => $args['id'], + 'eventType' => 'UP', + 'info' => _FILE_UPDATED . " : {$document['alt_identifier']}", + 'moduleId' => 'resource', + 'eventId' => 'fileModification' + ]); + } + return $response->withStatus(200); } @@ -182,15 +261,12 @@ class CollaboraOnlineController $uri = (string)$loadedXml->collaboraonline->server_uri; $port = (string)$loadedXml->collaboraonline->server_port; - $aUri = explode("/", $uri); - $exec = shell_exec("nc -vz -w 5 {$aUri[0]} {$port} 2>&1"); + $isAvailable = DocumentEditorController::isAvailable(['uri' => $uri, 'port' => $port]); - if (strpos($exec, 'not found') !== false) { - return $response->withStatus(400)->withJson(['errors' => 'Netcat command not found', 'lang' => 'preRequisiteMissing']); + if (!empty($isAvailable['errors'])) { + return $response->withStatus(400)->withJson($isAvailable); } - $isAvailable = strpos($exec, 'succeeded!') !== false || strpos($exec, 'open') !== false || strpos($exec, 'Connected') !== false; - return $response->withJson(['isAvailable' => $isAvailable]); } @@ -209,9 +285,8 @@ class CollaboraOnlineController if (!Validator::stringType()->notEmpty()->validate($body['type'])) { return $response->withStatus(400)->withJson(['errors' => 'Body type is empty or not a string']); } - - if ($body['type'] != 'resource') { - return $response->withStatus(400)->withJson(['errors' => 'WIP - only resources can be edited for now']); + if (!Validator::stringType()->notEmpty()->validate($body['mode'])) { + return $response->withStatus(400)->withJson(['errors' => 'Body mode is empty or not a string']); } if (!ResController::hasRightByResId(['resId' => [$body['resId']], 'userId' => $GLOBALS['id']])) { @@ -227,9 +302,9 @@ class CollaboraOnlineController $url = (string)$loadedXml->collaboraonline->server_uri . ':' . (string)$loadedXml->collaboraonline->server_port; $discovery = CurlModel::execSimple([ - 'url' => $url . '/hosting/discovery', - 'method' => 'GET', - 'jsonResponse' => false + 'url' => $url . '/hosting/discovery', + 'method' => 'GET', + 'ixXml' => true ]); if ($discovery['code'] != 200) { @@ -250,13 +325,47 @@ class CollaboraOnlineController $payload = [ 'userId' => $GLOBALS['id'], 'resId' => $body['resId'], - 'type' => $body['type'] + 'type' => $body['type'], + 'mode' => $body['mode'] ]; $jwt = JWT::encode($payload, CoreConfigModel::getEncryptKey()); - $urlIFrame = $urlSrc . 'WOPISrc=' . $coreUrl . 'rest/wopi/files/' . $body['resId'] . '&access_token=' . $jwt; + // TODO check if ssl + $urlIFrame = $urlSrc . 'WOPISrc=' . $coreUrl . 'rest/wopi/files/' . $body['resId'] . '&access_token=' . $jwt . '&NotWOPIButIframe=true'; return $response->withJson(['url' => $urlIFrame]); } + + private static function checkToken(array $args) + { + ValidatorModel::notEmpty($args, ['token', 'id']); + ValidatorModel::stringType($args, ['token']); + ValidatorModel::intVal($args, ['id']); + + try { + $jwt = JWT::decode($args['token'], CoreConfigModel::getEncryptKey(), ['HS256']); + } catch (\Exception $e) { + return ['code' => 401, 'errors' => 'Access token is invalid']; + } + + if (empty($jwt->resId) || empty($jwt->userId) || empty($jwt->type) || empty($jwt->mode)) { + return ['code' => 401, 'errors' => 'Access token is invalid']; + } + + if ($jwt->resId != $args['id']) { + return ['code' => 401, 'errors' => 'Access token is invalid']; + } + + CoreController::setGlobals(['userId' => $jwt->userId]); + + if ($jwt->type != 'resource' && $jwt->type != 'attachment') { + return ['code' => 400, 'errors' => 'WIP - only resources and attachments can be edited for now']; + } + + return [ + 'type' => $jwt->type, + 'mode' => $jwt->mode + ]; + } } diff --git a/src/app/contentManagement/controllers/DocumentEditorController.php b/src/app/contentManagement/controllers/DocumentEditorController.php index c49801a927b..fcbac07a535 100644 --- a/src/app/contentManagement/controllers/DocumentEditorController.php +++ b/src/app/contentManagement/controllers/DocumentEditorController.php @@ -17,6 +17,7 @@ namespace ContentManagement\controllers; use Slim\Http\Request; use Slim\Http\Response; use SrcCore\models\CoreConfigModel; +use SrcCore\models\ValidatorModel; class DocumentEditorController { @@ -42,4 +43,19 @@ class DocumentEditorController return $allowedMethods; } + + public static function isAvailable(array $args) + { + ValidatorModel::notEmpty($args, ['uri', 'port']); + ValidatorModel::stringType($args, ['uri', 'port']); + + $aUri = explode("/", $args['uri']); + $exec = shell_exec("nc -vz -w 5 {$aUri[0]} {$args['port']} 2>&1"); + + if (strpos($exec, 'not found') !== false) { + return ['errors' => 'Netcat command not found', 'lang' => 'preRequisiteMissing']; + } + + return strpos($exec, 'succeeded!') !== false || strpos($exec, 'open') !== false || strpos($exec, 'Connected') !== false; + } } diff --git a/src/app/contentManagement/controllers/OnlyOfficeController.php b/src/app/contentManagement/controllers/OnlyOfficeController.php index 46df58776d6..f77f55378ee 100644 --- a/src/app/contentManagement/controllers/OnlyOfficeController.php +++ b/src/app/contentManagement/controllers/OnlyOfficeController.php @@ -287,15 +287,12 @@ class OnlyOfficeController $uri = (string)$loadedXml->onlyoffice->server_uri; $port = (string)$loadedXml->onlyoffice->server_port; - $aUri = explode("/", $uri); - $exec = shell_exec("nc -vz -w 5 {$aUri[0]} {$port} 2>&1"); + $isAvailable = DocumentEditorController::isAvailable(['uri' => $uri, 'port' => $port]); - if (strpos($exec, 'not found') !== false) { - return $response->withStatus(400)->withJson(['errors' => 'Netcat command not found', 'lang' => 'preRequisiteMissing']); + if (!empty($isAvailable['errors'])) { + return $response->withStatus(400)->withJson($isAvailable); } - $isAvailable = strpos($exec, 'succeeded!') !== false || strpos($exec, 'open') !== false || strpos($exec, 'Connected') !== false; - return $response->withJson(['isAvailable' => $isAvailable]); } } diff --git a/src/app/folder/controllers/FolderController.php b/src/app/folder/controllers/FolderController.php index 936035b67ad..955aa327161 100755 --- a/src/app/folder/controllers/FolderController.php +++ b/src/app/folder/controllers/FolderController.php @@ -962,6 +962,7 @@ class FolderController $userEntities = [0]; } + $args['edition'] = $args['edition'] ?? false; if ($args['edition']) { $edition = [1]; } else { diff --git a/src/core/models/CurlModel.php b/src/core/models/CurlModel.php index 9d339aa08cc..eab21b91731 100755 --- a/src/core/models/CurlModel.php +++ b/src/core/models/CurlModel.php @@ -199,7 +199,7 @@ class CurlModel ValidatorModel::arrayType($args, ['headers', 'queryParams', 'basicAuth', 'bearerAuth']); ValidatorModel::boolType($args, ['jsonResponse']); - $args['jsonResponse'] = $args['jsonResponse'] ?? true; + $args['ixXml'] = $args['isXml'] ?? false; $opts = [CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_SSL_VERIFYPEER => false]; @@ -273,10 +273,10 @@ class CurlModel ]); } - if ($args['jsonResponse']) { - $response = json_decode($response, true); - } else { + if ($args['ixXml']) { $response = simplexml_load_string($response); + } else { + $response = json_decode($response, true); } return ['code' => $code, 'headers' => $headers, 'response' => $response, 'errors' => $errors]; diff --git a/src/frontend/app/viewer/document-viewer.component.ts b/src/frontend/app/viewer/document-viewer.component.ts index 309064c1d87..9b43be312ca 100755 --- a/src/frontend/app/viewer/document-viewer.component.ts +++ b/src/frontend/app/viewer/document-viewer.component.ts @@ -472,8 +472,8 @@ export class DocumentViewerComponent implements OnInit { getFile() { if (this.editor.mode === 'onlyoffice' && this.onlyofficeViewer !== undefined) { return this.onlyofficeViewer.getFile(); - } else if (this.editor.mode === 'collaboraOnline' && this.onlyofficeViewer !== undefined) { - return this.onlyofficeViewer.getFile(); + } else if (this.editor.mode === 'collaboraOnline' && this.collaboraOnlineViewer !== undefined) { + return this.collaboraOnlineViewer.getFile(); } else { const objFile = JSON.parse(JSON.stringify(this.file)); objFile.content = objFile.contentMode === 'route' ? null : objFile.content; @@ -767,6 +767,15 @@ export class DocumentViewerComponent implements OnInit { }; this.editInProgress = true; + } else if (this.editor.mode === 'collaboraOnline') { + this.editor.async = false; + this.editInProgress = true; + this.editor.options = { + objectType: 'resource', + objectId: this.resId, + objectMode: 'edition', + docUrl: `rest/wopi/files/` + }; } else { this.editor.async = true; this.editor.options = { @@ -799,12 +808,12 @@ export class DocumentViewerComponent implements OnInit { } else if (this.editor.mode === 'collaboraOnline') { this.editor.async = false; this.editor.options = { - objectType: 'resourceModification', + objectType: 'resource', objectId: this.resId, + objectMode: 'edition', docUrl: `rest/wopi/files/` }; this.editInProgress = true; - } else { this.editor.async = true; this.editor.options = { @@ -849,7 +858,7 @@ export class DocumentViewerComponent implements OnInit { if (this.editor.mode === 'onlyoffice') { return this.onlyofficeViewer !== undefined; } else if (this.editor.mode === 'collaboraOnline') { - return this.onlyofficeViewer !== undefined; + return this.collaboraOnlineViewer !== undefined; } else { return this.editInProgress; } @@ -1007,28 +1016,38 @@ export class DocumentViewerComponent implements OnInit { } saveMainDocument() { - return new Promise((resolve, reject) => { - this.getFile().pipe( - map((data: any) => { - const formatdatas = { - encodedFile: data.content, - format: data.format, - resId: this.resId - }; - return formatdatas; - }), - exhaustMap((data) => this.http.put(`../rest/resources/${this.resId}?onlyDocument=true`, data)), - tap(() => { - this.closeEditor(); - this.loadRessource(this.resId); - resolve(true); - }), - catchError((err: any) => { - this.notify.handleSoftErrors(err); - resolve(false); - return of(false); - }) - ).subscribe(); + return new Promise((resolve) => { + if (this.headerService.user.preferences.documentEdition === 'collaboraonline') { + this.getFile() .pipe( + tap((data: any) => { + this.closeEditor(); + this.loadRessource(this.resId); + resolve(true); + }) + ).subscribe(); + } else { + this.getFile().pipe( + map((data: any) => { + const formatdatas = { + encodedFile: data.content, + format: data.format, + resId: this.resId + }; + return formatdatas; + }), + exhaustMap((data) => this.http.put(`../rest/resources/${this.resId}?onlyDocument=true`, data)), + tap(() => { + this.closeEditor(); + this.loadRessource(this.resId); + resolve(true); + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + resolve(false); + return of(false); + }) + ).subscribe(); + } }); } diff --git a/src/frontend/lang/lang-en.ts b/src/frontend/lang/lang-en.ts index 20b71007e1a..ba375c54b20 100755 --- a/src/frontend/lang/lang-en.ts +++ b/src/frontend/lang/lang-en.ts @@ -1805,5 +1805,6 @@ export const LANG_EN = { "dbNotEmpty": "This database already exists and is not empty", "stepMailServer": "Mail server", "stepMailServer_desc": "Configure your mail server to notify users by email of the the application activities. <br/> This step can be skipped and can be configured later.", - "stepMailServer_warning": "If no mail server is configured, the new users will not receive their first login token!" -}; \ No newline at end of file + "stepMailServer_warning": "If no mail server is configured, the new users will not receive their first login token!", + "checkCollaboraOnlineServer": "Connecting to the Collabora Online server", +}; diff --git a/src/frontend/lang/lang-fr.ts b/src/frontend/lang/lang-fr.ts index b6433f79030..b6d375e41d8 100755 --- a/src/frontend/lang/lang-fr.ts +++ b/src/frontend/lang/lang-fr.ts @@ -1807,4 +1807,5 @@ export const LANG_FR = { "stepMailServer": "Serveur de mail", "stepMailServer_desc": "Configurer votre serveur de mail afin de prévenir les utilisateurs par mail des différents échanges survenus dans l'application.<br/>Cette étape peut être passé et être configurée plus tard.", "stepMailServer_warning": "Si aucun serveur de mail n'est renseigné, les nouveaux utilisateurs ne recevront pas leur jeton de première connexion !", + "checkCollaboraOnlineServer": "Communication avec le serveur Collabora Online", }; diff --git a/src/frontend/lang/lang-nl.ts b/src/frontend/lang/lang-nl.ts index e6b2403fef0..17506bcaf94 100755 --- a/src/frontend/lang/lang-nl.ts +++ b/src/frontend/lang/lang-nl.ts @@ -1817,5 +1817,6 @@ export const LANG_NL = { "dbNotEmpty": "Cette base de données existe déjà et n'est pas vide__TO_TRANSLATE", "stepMailServer": "Serveur de mail__TO_TRANSLATE", "stepMailServer_desc": "Configurer votre serveur de mail afin de prévenir les utilisateurs par mail des différents échanges survenus dans l'application.<br/>Cette étape peut être passé et être configurée plus tard.__TO_TRANSLATE", - "stepMailServer_warning": "Si aucun serveur de mail n'est renseigné, les nouveaux utilisateurs ne recevront pas leur jeton de première connexion !__TO_TRANSLATE" -}; \ No newline at end of file + "stepMailServer_warning": "Si aucun serveur de mail n'est renseigné, les nouveaux utilisateurs ne recevront pas leur jeton de première connexion !__TO_TRANSLATE", + "checkCollaboraOnlineServer": "Connecting to the Collabora Online server__TO_TRANSLATE", +}; diff --git a/src/frontend/plugins/collabora-online/collabora-online-viewer.component.html b/src/frontend/plugins/collabora-online/collabora-online-viewer.component.html index cce43120fa0..85e669083a3 100644 --- a/src/frontend/plugins/collabora-online/collabora-online-viewer.component.html +++ b/src/frontend/plugins/collabora-online/collabora-online-viewer.component.html @@ -1,4 +1,4 @@ -<div *ngIf="loading" style="display:block;padding: 10px;">{{lang.checkOnlyofficeServer}}...</div> +<div *ngIf="loading" style="display:block;padding: 10px;">{{lang.checkCollaboraOnlineServer}}...</div> <button *ngIf="!hideCloseEditor" class="onlyofficeButton_fullscreen" [class.fullScreen]="fullscreenMode" mat-mini-fab color="warn" [title]="lang.closeEditor" (click)="quit()"> <mat-icon class="fa fa-times" style="height:auto;"></mat-icon> @@ -7,6 +7,6 @@ [title]="fullscreenMode ? lang.closeFullscreen : lang.openFullscreen" (click)="openFullscreen()"> <mat-icon class="fas" [class.fa-expand]="!fullscreenMode" [class.fa-compress]="fullscreenMode" style="height:auto;"></mat-icon> </button> -<iframe *ngIf="editorUrl !== ''" [src]="editorUrl" width="100%" height="100%"> +<iframe #collaboraFrame *ngIf="editorUrl !== ''" [src]="editorUrl" id="collabora" width="100%" height="100%"> </iframe> diff --git a/src/frontend/plugins/collabora-online/collabora-online-viewer.component.ts b/src/frontend/plugins/collabora-online/collabora-online-viewer.component.ts index f9ad7984d22..78eb666cf90 100644 --- a/src/frontend/plugins/collabora-online/collabora-online-viewer.component.ts +++ b/src/frontend/plugins/collabora-online/collabora-online-viewer.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, SecurityContext} from '@angular/core'; +import {AfterViewInit, Component, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, SecurityContext, ViewChild} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {catchError, filter, tap} from 'rxjs/operators'; import {LANG} from '../../app/translate.component'; @@ -11,7 +11,6 @@ import {DomSanitizer} from '@angular/platform-browser'; // import { NotificationService } from '../../service/notification/notification.service.js'; declare var $: any; -declare var DocsAPI: any; @Component({ selector: 'app-collabora-online-viewer', @@ -43,9 +42,6 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On tmpFilename: string = ''; - appUrl: string = ''; - onlyOfficeUrl: string = ''; - allowedExtension: string[] = [ 'doc', 'docx', @@ -68,16 +64,28 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On editorUrl: any = ''; + @ViewChild('collaboraFrame', { static: false }) collaboraFrame: any; @HostListener('window:message', ['$event']) onMessage(e: any) { - // console.log(e); + console.log(e); const response = JSON.parse(e.data); // EVENT TO CONSTANTLY UPDATE CURRENT DOCUMENT - if (response.event === 'onDownloadAs') { - this.getEncodedDocument(response.data); - } else if (response.event === 'onDocumentReady') { + console.log(response); + if (response.MessageId === 'Action_Save_Resp') { + console.log('got message : action save response -> doc is saving'); + } else if (response.MessageId === 'Doc_ModifiedStatus' && response.Values.Modified === false && this.isSaving) { + console.log('got message = doc is not modified -> finished saving'); + this.triggerAfterUpdatedDoc.emit(); + this.eventAction.next(true); + } else if (response.MessageId === 'Doc_ModifiedStatus' && response.Values.Modified === true) { + console.log('got message = doc is modified'); this.triggerModifiedDocument.emit(); + } else if (response.MessageId === 'App_LoadingStatus' && response.Values.Status === 'Document_Loaded') { + console.log('got message = doc is loaded'); + const message = {'MessageId': 'Host_PostmessageReady'}; + console.log('getConfiguration, sending message : ' + JSON.stringify(message)); + this.collaboraFrame.nativeElement.contentWindow.postMessage(JSON.stringify(message), '*'); } } @@ -94,7 +102,6 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On this.dialogRef.afterClosed().pipe( filter((data: string) => data === 'ok'), tap(() => { - // this.docEditor.destroyEditor(); this.closeEditor(); }) ).subscribe(); @@ -106,55 +113,42 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On } $('iframe[name=\'frameEditor\']').css('position', 'initial'); this.fullscreenMode = false; + + // const message = { + // 'MessageId': 'Action_Close', + // 'Values': null + // }; + // this.collaboraFrame.nativeElement.contentWindow.postMessage(JSON.stringify(message), '*'); + this.triggerAfterUpdatedDoc.emit(); this.triggerCloseEditor.emit(); } getDocument() { this.isSaving = true; - // this.docEditor.downloadAs(this.file.format); - } - getEncodedDocument(data: any) { - this.http.get('../rest/onlyOffice/encodedFile', { params: { url: data } }).pipe( - tap((result: any) => { - this.file.content = result.encodedFile; - this.isSaving = false; - this.triggerAfterUpdatedDoc.emit(); - this.eventAction.next(this.file); - }) - ).subscribe(); - } - - getEditorMode(extension: string) { - if (['csv', 'fods', 'ods', 'ots', 'xls', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx'].indexOf(extension) > -1) { - return 'spreadsheet'; - } else if (['fodp', 'odp', 'otp', 'pot', 'potm', 'potx', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx'].indexOf(extension) > -1) { - return 'presentation'; - } else { - return 'text'; - } + const message = { + 'MessageId': 'Action_Save', + 'Values': { + 'Notify': true, + 'ExtendedData': 'FinalSave=True', + 'DontTerminateEdit': true, + 'DontSaveIfUnmodified': true + } + }; + console.log('getDocument, sending message : ' + JSON.stringify(message)); + this.collaboraFrame.nativeElement.contentWindow.postMessage(JSON.stringify(message), '*'); } - async ngOnInit() { this.key = this.generateUniqueId(); - console.log('in collabora init'); if (this.canLaunchCollaboraOnline()) { - console.log('can launch collabora'); - // await this.getServerConfiguration(); - await this.checkServerStatus(); - console.log('before token'); - // await this.getMergedFileTemplate(); await this.getConfiguration(); - console.log('got token + url'); this.loading = false; - } else { - console.log('cannot launch collabora'); } } @@ -168,36 +162,6 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On } } - getServerConfiguration() { - return new Promise((resolve) => { - this.http.get(`../rest/onlyOffice/configuration`).pipe( - tap((data: any) => { - if (data.enabled) { - - const serverUriArr = data.serverUri.split('/'); - const protocol = data.serverSsl ? 'https://' : 'http://'; - const domain = data.serverUri.split('/')[0]; - const path = serverUriArr.slice(1).join('/'); - const port = data.serverPort ? `:${data.serverPort}` : ':80'; - - const serverUri = [domain + port, path].join('/'); - - this.onlyOfficeUrl = `${protocol}${serverUri}`; - this.appUrl = data.coreUrl; - resolve(true); - } else { - this.triggerCloseEditor.emit(); - } - }), - catchError((err) => { - // this.notify.handleErrors(err); - this.triggerCloseEditor.emit(); - return of(false); - }), - ).subscribe(); - }); - } - checkServerStatus() { return new Promise((resolve) => { // const regex = /127\.0\.0\.1/g; @@ -266,7 +230,7 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On getConfiguration() { return new Promise((resolve) => { - this.http.post('../rest/collaboraOnline/configuration', { resId: 206, type: 'resource', documentExtension: 'docx' }).pipe( + this.http.post('../rest/collaboraOnline/configuration', { resId: this.params.objectId, type: this.params.objectType, mode: this.params.objectMode}).pipe( tap((data: any) => { this.editorUrl = data.url; // this.editorUrl = this.sanitizer.sanitize(SecurityContext.RESOURCE_URL, this.editorUrl); @@ -287,11 +251,7 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On } isLocked() { - if (this.isSaving) { - return true; - } else { - return false; - } + return this.isSaving; } getFile() { -- GitLab