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