diff --git a/rest/index.php b/rest/index.php index 484081120f3e588c76114b361c20455ba2a058ec..6bd5d7cb8ba7d9b5a5f45e0eedafcfefc29ad6a3 100755 --- a/rest/index.php +++ b/rest/index.php @@ -603,6 +603,7 @@ $app->post('/wopi/files/{id}/contents', \ContentManagement\controllers\Collabora $app->post('/collaboraOnline/configuration', \ContentManagement\controllers\CollaboraOnlineController::class . ':getConfiguration'); $app->get('/collaboraOnline/available', \ContentManagement\controllers\CollaboraOnlineController::class . ':isAvailable'); $app->post('/collaboraOnline/file', \ContentManagement\controllers\CollaboraOnlineController::class . ':getTmpFile'); +$app->delete('/collaboraOnline/file', \ContentManagement\controllers\CollaboraOnlineController::class . ':deleteTmpFile'); $app->post('/collaboraOnline/encodedFile', \ContentManagement\controllers\CollaboraOnlineController::class . ':saveTmpEncodedDocument'); $app->run(); diff --git a/src/app/contentManagement/controllers/CollaboraOnlineController.php b/src/app/contentManagement/controllers/CollaboraOnlineController.php index d1ed6f54014bd7987a498ba5fbae32d340f1a3b2..b00dce26a8dc7e9336ade5c9cdebce4be86f9269 100644 --- a/src/app/contentManagement/controllers/CollaboraOnlineController.php +++ b/src/app/contentManagement/controllers/CollaboraOnlineController.php @@ -232,6 +232,10 @@ class CollaboraOnlineController $tmpPath = CoreConfigModel::getTmpPath(); $pathToDocument = $tmpPath . $filename; + if (!file_exists($pathToDocument)) { + return $response->withStatus(404)->withJson(['errors' => 'Document not found']); + } + if ($tokenCheckResult['mode'] == 'creation' && ($tokenCheckResult['type'] == 'resource' || $tokenCheckResult['type'] == 'attachment')) { $dataToMerge = ['userId' => $GLOBALS['id']]; if (!empty($body['data']) && is_array($body['data'])) { @@ -252,11 +256,44 @@ class CollaboraOnlineController $content = base64_encode($fileContent); } - unlink($pathToDocument); - return $response->withJson(['content' => $content, 'format' => $extension]); } + public function deleteTmpFile(Request $request, Response $response) + { + $queryParams = $request->getQueryParams(); + + if (!Validator::stringType()->notEmpty()->validate($queryParams['token'])) { + return $response->withStatus(400)->withJson(['errors' => 'Query token is empty or not a string']); + } + + $tokenCheckResult = CollaboraOnlineController::checkToken(['token' => $queryParams['token']]); + if (!empty($tokenCheckResult['errors'])) { + return $response->withStatus($tokenCheckResult['code'])->withJson(['errors' => $tokenCheckResult['errors']]); + } + + $document = CollaboraOnlineController::getDocument([ + 'id' => $tokenCheckResult['resId'], + 'type' => $tokenCheckResult['type'], + 'mode' => $tokenCheckResult['mode'], + 'format' => $tokenCheckResult['format'] + ]); + if (!empty($document['errors'])) { + return $response->withStatus($document['code'])->withJson(['errors' => $document['errors']]); + } + + $extension = pathinfo($document['filename'], PATHINFO_EXTENSION); + $filename = "collabora_{$GLOBALS['id']}_{$tokenCheckResult['type']}_{$tokenCheckResult['mode']}_{$tokenCheckResult['resId']}.{$extension}"; + $tmpPath = CoreConfigModel::getTmpPath(); + $pathToDocument = $tmpPath . $filename; + + if (file_exists($pathToDocument)) { + unlink($pathToDocument); + } + + return $response->withStatus(204); + } + public static function isAvailable(Request $request, Response $response) { $loadedXml = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/documentEditorsConfig.xml']); @@ -301,12 +338,16 @@ class CollaboraOnlineController if (!empty($body['format']) && !Validator::stringType()->validate($body['format'])) { return $response->withStatus(400)->withJson(['errors' => 'Body format is not a string']); } + if (!empty($body['path']) && !Validator::stringType()->validate($body['path'])) { + return $response->withStatus(400)->withJson(['errors' => 'Body path is not a string']); + } $document = CollaboraOnlineController::getDocument([ 'id' => $body['resId'], 'type' => $body['type'], 'mode' => $body['mode'], - 'format' => $body['format'] + 'format' => $body['format'], + 'path' => $body['path'] ]); if (!empty($document['errors'])) { @@ -444,7 +485,7 @@ class CollaboraOnlineController private static function getDocument(array $args) { ValidatorModel::notEmpty($args, ['id', 'type', 'mode']); - ValidatorModel::stringType($args, ['type', 'mode', 'format']); + ValidatorModel::stringType($args, ['type', 'mode', 'format', 'path']); ValidatorModel::intVal($args, ['id']); if ($args['mode'] == 'creation' && ($args['type'] == 'resource' || $args['type'] == 'attachment')) { @@ -547,6 +588,47 @@ class CollaboraOnlineController $document['docserver_id'] = ''; $document['path'] = CoreConfigModel::getTmpPath(); + $document['modification_date'] = new \DateTime('now'); + $document['modification_date'] = $document['modification_date']->format(\DateTime::ISO8601); + } else if ($args['type'] == 'template' && $args['mode'] == 'creation') { + if (!PrivilegeController::hasPrivilege(['privilegeId' => 'admin_templates', 'userId' => $GLOBALS['id']])) { + return ['code' => 403, 'errors' => 'Service forbidden']; + } + + $document['filename'] = "collabora_template_{$GLOBALS['id']}_{$args['id']}.{$args['format']}"; + $document['docserver_id'] = ''; + $document['path'] = CoreConfigModel::getTmpPath(); + + if (!file_exists($document['path'] . $document['filename'])) { + if (empty($args['path'])) { + return ['code' => 400, 'errors' => 'Argument path is missing']; + } + + $customId = CoreConfigModel::getCustomId(); + if (!empty($customId) && is_dir("custom/{$customId}/modules/templates/templates/styles/")) { + $stylesPath = "custom/{$customId}/modules/templates/templates/styles/"; + } else { + $stylesPath = 'modules/templates/templates/styles/'; + } + if (strpos($args['path'], $stylesPath) !== 0 || substr_count($args['path'], '.') != 1) { + return ['code' => 400, 'errors' => 'Template path is not valid']; + } + + if (!file_exists($args['path'])) { + return ['code' => 400, 'errors' => 'Document does not exists']; + } + + $fileContent = file_get_contents($args['path']); + if ($fileContent === false) { + return ['code' => 400, 'errors' => 'Document does not exists']; + } + + $result = file_put_contents($document['path'] . $document['filename'], $fileContent); + if ($result === false) { + return ['code' => 400, 'errors' => 'Document does not exists']; + } + } + $document['modification_date'] = new \DateTime('now'); $document['modification_date'] = $document['modification_date']->format(\DateTime::ISO8601); } else { diff --git a/src/app/template/controllers/TemplateController.php b/src/app/template/controllers/TemplateController.php index 76a892761a0d6be0545c687eb21e486245084d02..e20e10d692d9a5d91f7b4f8f2557a04da79041fc 100755 --- a/src/app/template/controllers/TemplateController.php +++ b/src/app/template/controllers/TemplateController.php @@ -28,7 +28,6 @@ use Resource\models\ResModel; use Respect\Validation\Validator; use Slim\Http\Request; use Slim\Http\Response; -use SrcCore\models\CoreConfigModel; use SrcCore\models\ValidatorModel; use Template\models\TemplateAssociationModel; use Template\models\TemplateModel; @@ -46,7 +45,8 @@ class TemplateController 'application/vnd.openxmlformats-officedocument.presentationml‌.slideshow', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.presentation', - 'application/vnd.oasis.opendocument.spreadsheet' + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/octet-stream' ]; public function get(Request $request, Response $response) diff --git a/src/frontend/app/administration/template/templateFileEditorModal/template-file-editor-modal.component.html b/src/frontend/app/administration/template/templateFileEditorModal/template-file-editor-modal.component.html index dda9091a33128a2785c06cb3c7a75ac17b85836f..c7adb7898d1d22e18f0d5a2357f9fbade89f9a85 100644 --- a/src/frontend/app/administration/template/templateFileEditorModal/template-file-editor-modal.component.html +++ b/src/frontend/app/administration/template/templateFileEditorModal/template-file-editor-modal.component.html @@ -2,15 +2,15 @@ <mat-dialog-content style="padding: 0px;"> <onlyoffice-viewer *ngIf="editorType === 'onlyoffice'" #onlyofficeViewer style="height:100%;width:100%;" [hideCloseEditor]="true" [params]="editorOptions" [file]="file" [editMode]="true" (triggerAfterUpdatedDoc)="close()" - (triggerCloseEditor)="dialogRef.close('');"></onlyoffice-viewer> + (triggerCloseEditor)="dialogRef.close('');" (triggerModifiedDocument)="documentIsModified = true"></onlyoffice-viewer> <app-collabora-online-viewer *ngIf="editorType === 'collaboraonline'" #collaboraOnlineViewer style="height:100%;width:100%;" [params]="editorOptions" [file]="file" [editMode]="true" (triggerAfterUpdatedDoc)="close()" - (triggerCloseEditor)="dialogRef.close('');"></app-collabora-online-viewer> + (triggerCloseEditor)="dialogRef.close('');" (triggerModifiedDocument)="documentIsModified = true"></app-collabora-online-viewer> </mat-dialog-content> <span class="divider-modal"></span> <div mat-dialog-actions class="actions"> <button mat-raised-button mat-button color="primary" - (click)="close()">{{lang.validate}}</button> + (click)="close()" [disabled]="!documentIsModified">{{lang.validate}}</button> <button mat-raised-button mat-button [disabled]="loading" [mat-dialog-close]="">{{lang.cancel}}</button> </div> </div> diff --git a/src/frontend/app/administration/template/templateFileEditorModal/template-file-editor-modal.component.ts b/src/frontend/app/administration/template/templateFileEditorModal/template-file-editor-modal.component.ts index 6302aca25b0b3a7be0718c5fab5a968983e1d519..59fb909d0e6c1b8193f4fd768386fc7cf862ae3f 100644 --- a/src/frontend/app/administration/template/templateFileEditorModal/template-file-editor-modal.component.ts +++ b/src/frontend/app/administration/template/templateFileEditorModal/template-file-editor-modal.component.ts @@ -17,6 +17,7 @@ export class TemplateFileEditorModalComponent implements OnInit { editorOptions: any = null; file: any = null; editorType: any = null; + documentIsModified: boolean = false; @ViewChild('onlyofficeViewer', { static: false }) onlyofficeViewer: EcplOnlyofficeViewerComponent; @ViewChild('collaboraOnlineViewer', { static: false }) collaboraOnlineViewer: CollaboraOnlineViewerComponent; diff --git a/src/frontend/app/viewer/document-viewer.component.ts b/src/frontend/app/viewer/document-viewer.component.ts index 9634b14a422691e3f736bb3b3278c36022c9fcae..b4f9214933015e76844845a67579e7d80545f5c9 100755 --- a/src/frontend/app/viewer/document-viewer.component.ts +++ b/src/frontend/app/viewer/document-viewer.component.ts @@ -4,7 +4,7 @@ import { LANG } from '../translate.component'; import { NotificationService } from '../../service/notification/notification.service'; import { HeaderService } from '../../service/header.service'; import { AppService } from '../../service/app.service'; -import { tap, catchError, filter, map, exhaustMap } from 'rxjs/operators'; +import {tap, catchError, filter, map, exhaustMap, take} from 'rxjs/operators'; import { ConfirmComponent } from '../../plugins/modal/confirm.component'; import { MatDialogRef, MatDialog } from '@angular/material/dialog'; import { AlertComponent } from '../../plugins/modal/alert.component'; @@ -493,6 +493,13 @@ export class DocumentViewerComponent implements OnInit { resolve(this.getBase64Document(this.file.src)); } else { this.getFile().pipe( + take(1), + tap((data: any) => { + if (this.editor.mode === 'collaboraOnline' && this.collaboraOnlineViewer !== undefined) { + this.collaboraOnlineViewer.isSaving = false; + } + return data; + }), exhaustMap((data: any) => this.http.post(`../rest/convertedFile`, { name: `${data.name}.${data.format}`, base64: `${data.content}` })), tap((data: any) => { resolve(data.encodedResource); 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 705175db3ddcdb57b9254b70a98bcdc8955a8166..9617323edb178551022c424805b8c0c8d0c40ce6 100644 --- a/src/frontend/plugins/collabora-online/collabora-online-viewer.component.ts +++ b/src/frontend/plugins/collabora-online/collabora-online-viewer.component.ts @@ -35,6 +35,7 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On editorConfig: any; key: number = 0; isSaving: boolean = false; + isModified: boolean = false; fullscreenMode: boolean = false; allowedExtension: string[] = [ @@ -67,11 +68,18 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On // console.log(e); const response = JSON.parse(e.data); // EVENT TO CONSTANTLY UPDATE CURRENT DOCUMENT - if (response.MessageId === 'Doc_ModifiedStatus' && response.Values.Modified === false && this.isSaving) { + if (response.MessageId === 'Doc_ModifiedStatus' && response.Values.Modified === false) { + this.isModified = false; + } + if (response.MessageId === 'Action_Save_Resp' && response.Values.success === true && !this.isModified) { + this.triggerAfterUpdatedDoc.emit(); + this.getTmpFile(); + } else if (response.MessageId === 'Doc_ModifiedStatus' && response.Values.Modified === false && this.isSaving) { // Collabora sends 'Action_Save_Resp' when it starts saving the document, then sends Doc_ModifiedStatus with Modified = false when it is done saving this.triggerAfterUpdatedDoc.emit(); this.getTmpFile(); } else if (response.MessageId === 'Doc_ModifiedStatus' && response.Values.Modified === true) { + this.isModified = true; this.triggerModifiedDocument.emit(); } else if (response.MessageId === 'App_LoadingStatus' && response.Values.Status === 'Document_Loaded') { const message = {'MessageId': 'Host_PostmessageReady'}; @@ -110,6 +118,8 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On }; this.collaboraFrame.nativeElement.contentWindow.postMessage(JSON.stringify(message), '*'); + this.deleteTmpFile(); + this.triggerAfterUpdatedDoc.emit(); this.triggerCloseEditor.emit(); } @@ -123,7 +133,7 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On 'Notify': true, 'ExtendedData': 'FinalSave=True', 'DontTerminateEdit': true, - 'DontSaveIfUnmodified': true + 'DontSaveIfUnmodified': false } }; this.collaboraFrame.nativeElement.contentWindow.postMessage(JSON.stringify(message), '*'); @@ -140,7 +150,11 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On this.params.objectType = 'template'; } - if (typeof this.params.objectId === 'string' && this.params.objectType === 'encodedResource') { + this.params.objectPath = undefined; + if (typeof this.params.objectId === 'string' && this.params.objectType === 'template') { + this.params.objectPath = this.params.objectId; + this.params.objectId = this.key; + } else if (typeof this.params.objectId === 'string' && this.params.objectType === 'encodedResource') { this.params.content = this.params.objectId; this.params.objectId = this.key; this.params.objectMode = 'encoded'; @@ -214,6 +228,21 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On }); } + deleteTmpFile() { + return new Promise((resolve) => { + this.http.delete('../rest/collaboraOnline/file?token=' + this.token).pipe( + tap(() => { + resolve(true); + }), + catchError((err) => { + this.notify.handleErrors(err); + this.triggerCloseEditor.emit(); + return of(false); + }), + ).subscribe(); + }); + } + saveEncodedFile() { return new Promise((resolve) => { this.http.post('../rest/collaboraOnline/encodedFile', {content: this.params.content, format: this.file.format, key: this.key}).pipe( @@ -249,7 +278,8 @@ export class CollaboraOnlineViewerComponent implements OnInit, AfterViewInit, On resId: this.params.objectId, type: this.params.objectType, mode: this.params.objectMode, - format: this.file.format + format: this.file.format, + path: this.params.objectPath }).pipe( tap((data: any) => { this.editorUrl = data.url;